一 什么是音頻的采樣率和采樣大小
自然界中的聲音非常復雜,波形極其復雜,通常我們采用的是脈沖代碼調制編碼。即PCM編碼。PCM通過抽樣、量化、編碼三個步驟將連續變化的模擬信號轉換為數字編碼。 抽樣:在音頻采集中叫做采樣率。 由於聲音其實是一種能量波,因此也有頻率和振幅的特征,頻率對應於時間軸線,振幅對應於電平軸線。波是無限光滑的,弦線可以看成由無數點組成,由於存儲空間是相對有限的,數字編碼過程中,必須對弦線的點進行采樣。采樣的過程就是抽取某點的頻率值,很顯然,在一秒中內抽取的點越多,獲取得頻率信息更豐富,為了復原波形,一次振動中,必須有2個點的采樣,人耳能夠感覺到的最高頻率為20kHz,因此要滿足人耳的聽覺要求,則需要至少每秒進行40k次采樣,用40kHz表達,這個40kHz就是采樣率。我們常見的CD,采樣率為44.1kHz。 量化:我們這里的采樣大小就是量化的過程,將該頻率的能量值並量化,用於表示信號強度。量化電平數為 2的整數次冪,我們常見的CD位16bit的采樣大小,即2的16次方。 編碼: 根據采樣率和采樣大小可以得知,相對自然界的信號,音頻編碼最多只能做到無限接近,至少目前的技術只能這樣了,相對自然界的信號,任何數字音頻編碼方案都是有損的,因為無法完全還原。在計算機應用中,能夠達到最高保真水平的就是PCM編碼,被廣泛用於素材保存及音樂欣賞,CD、DVD以及我們常見的WAV文件中均有應用。因此,PCM約定俗成了無損編碼,因為PCM代表了數字音頻中最佳的保真水准,並不意味着PCM就能夠確保信號絕對保真,PCM也只能做到最大程度的無限接近。我們而習慣性的把MP3列入有損音頻編碼范疇,是相對PCM編碼的。強調編碼的相對性的有損和無損,是為了告訴大家,要做到真正的無損是困難的,就像用數字去表達圓周率,不管精度多高,也只是無限接近,而不是真正等於圓周率的值 為什么要使用音頻壓縮技術 要算一個PCM音頻流的碼率是一件很輕松的事情,采樣率值×采樣大小值×聲道數bps。一個采樣率為44.1KHz,采樣大小為16bit,雙聲道的PCM編碼的WAV文件,它的數據速率則為 44.1K×16×2 =1411.2 Kbps。我們常說128K的MP3,對應的WAV的參數,就是這個1411.2 Kbps,這個參數也被稱為數據帶寬,它和ADSL中的帶寬是一個概念。將碼率除以8,就可以得到這個WAV的數據速率,即176.4KB/s。這表示存儲一秒鍾采樣率為44.1KHz,采樣大小為16bit,雙聲道的PCM編碼的音頻信號,需要176.4KB的空間,1分鍾則約為10.34M,這對大部分用戶是不可接受的,尤其是喜歡在電腦上聽音樂的朋友,要降低磁盤占用,只有2種方法,降低采樣指標或者壓縮。降低指標是不可取的,因此專家們研發了各種壓縮方案。由於用途和針對的目標市場不一樣,各種音頻壓縮編碼所達到的音質和壓縮比都不一樣,在后面的文章中我們都會一一提到。有一點是可以肯定的,他們都壓縮過。 頻率與采樣率的關系 采樣率表示了每秒對原始信號采樣的次數,我們常見到的音頻文件采樣率多為44.1KHz,這意味着什么呢?假設我們有2段正弦波信號,分別為20Hz和20KHz,長度均為一秒鍾,以對應我們能聽到的最低頻和最高頻,分別對這兩段信號進行 40KHz的采樣,我們可以得到一個什么樣的結果呢?結果是:20Hz的信號每次振動被采樣了40K/20=2000次,而20K的信號每次振動只有2次采樣。顯然,在相同的采樣率下,記錄低頻的信息遠比高頻的詳細。這也是為什么有些音響發燒友指責CD有數碼聲不夠真實的原因,CD的44.1KHz采樣也無法保證高頻信號被較好記錄。要較好的記錄高頻信號,看來需要更高的采樣率,於是有些朋友在捕捉CD音軌的時候使用48KHz的采樣率,這是不可取的!這其實對音質沒有任何好處,對抓軌軟件來說,保持和CD提供的44.1KHz一樣的采樣率才是最佳音質的保證之一,而不是去提高它。較高的采樣率只有相對模擬信號的時候才有用,如果被采樣的信號是數字的,請不要去嘗試提高采樣率。 流特征 隨着網絡的發展,人們對在線收聽音樂提出了要求,因此也要求音頻文件能夠一邊讀一邊播放,而不需要把這個文件全部讀出后然后回放,這樣就可以做到不用下載就可以實現收聽了。也可以做到一邊編碼一邊播放,正是這種特征,可以實現在線的直播,架設自己的數字廣播電台成為了現實。
二 android中AudioRecord采集音頻的參數說明
在android中采集音頻的api是android.media.AudioRecord類
其中構造器的幾個參數就是標准的聲音采集參數
以下是參數的含義解釋
public AudioRecord (int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes)
Since: API Level 3 Class constructor.
Parameters
audioSource |
the recording source. See MediaRecorder.AudioSource for recording source definitions. 音頻源:指的是從哪里采集音頻。這里我們當然是從麥克風采集音頻,所以此參數的值為MIC |
sampleRateInHz |
the sample rate expressed in Hertz. Examples of rates are (but not limited to) 44100, 22050 and 11025. 采樣率:音頻的采樣頻率,每秒鍾能夠采樣的次數,采樣率越高,音質越高。給出的實例是44100、22050、11025但不限於這幾個參數。例如要采集低質量的音頻就可以使用4000、8000等低采樣率。 |
channelConfig |
describes the configuration of the audio channels. SeeCHANNEL_IN_MONO and CHANNEL_IN_STEREO 聲道設置:android支持雙聲道立體聲和單聲道。MONO單聲道,STEREO立體聲 |
audioFormat |
the format in which the audio data is represented. SeeENCODING_PCM_16BIT and ENCODING_PCM_8BIT 編碼制式和采樣大小:采集來的數據當然使用PCM編碼(脈沖代碼調制編碼,即PCM編碼。PCM通過抽樣、量化、編碼三個步驟將連續變化的模擬信號轉換為數字編碼。) android支持的采樣大小16bit 或者8bit。當然采樣大小越大,那么信息量越多,音質也越高,現在主流的采樣大小都是16bit,在低質量的語音傳輸的時候8bit 足夠了。 |
bufferSizeInBytes |
the total size (in bytes) of the buffer where audio data is written to during the recording. New audio data can be read from this buffer in smaller chunks than this size. SeegetMinBufferSize(int, int, int) to determine the minimum required buffer size for the successful creation of an AudioRecord instance. Using values smaller than getMinBufferSize() will result in an initialization failure. 采集數據需要的緩沖區的大小,如果不知道最小需要的大小可以在getMinBufferSize()查看。 |
采集到的數據保存在一個byteBuffer中,可以使用流將其讀出。亦可保存成為文件的形式
三 Android 使用AudioRecord錄音相關和音頻文件的封裝
在Android中錄音可以用MediaRecord錄音,操作比較簡單。但是不夠專業,就是不能對音頻進行處理。如果要進行音頻的實時的處理或者音頻的一些封裝 就可以用AudioRecord來進行錄音了。 這里給出一段代碼。實現了AudioRecord的錄音和WAV格式音頻的封裝。 用AudioTrack和AudioTrack類可以進行邊錄邊播,可以參考:http://blog.sina.com.cn/s/blog_6309e1ed0100j1rw.html 我們這里的代碼沒有播放。但是有封裝和詳解,如下:
- package com.ppmeet;
-
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import android.app.Activity;
- import android.graphics.PixelFormat;
- import android.media.AudioFormat;
- import android.media.AudioRecord;
- import android.media.MediaRecorder;
- import android.os.Bundle;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.view.Window;
- import android.view.WindowManager;
- import android.widget.Button;
-
-
-
-
-
-
-
-
-
- public class TestAudioRecord extends Activity {
-
- private int audioSource = MediaRecorder.AudioSource.MIC;
-
- private static int sampleRateInHz = 44100;
-
- private static int channelConfig = AudioFormat.CHANNEL_IN_STEREO;
-
- private static int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
-
- private int bufferSizeInBytes = 0;
- private Button Start;
- private Button Stop;
- private AudioRecord audioRecord;
- private boolean isRecord = false;
-
- private static final String AudioName = "/sdcard/love.raw";//不推薦這么寫,可以用Enviroment.
-
- private static final String NewAudioName = "/sdcard/new.wav";
-
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- getWindow().setFormat(PixelFormat.TRANSLUCENT);
- requestWindowFeature(Window.FEATURE_NO_TITLE);
- getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
- WindowManager.LayoutParams.FLAG_FULLSCREEN);
-
- setContentView(R.layout.main);
- init();
- }
-
- private void init() {
- Start = (Button) this.findViewById(R.id.start);
- Stop = (Button) this.findViewById(R.id.stop);
- Start.setOnClickListener(new TestAudioListener());
- Stop.setOnClickListener(new TestAudioListener());
- creatAudioRecord();
- }
-
- private void creatAudioRecord() {
-
- bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz,
- channelConfig, audioFormat);
-
- audioRecord = new AudioRecord(audioSource, sampleRateInHz,
- channelConfig, audioFormat, bufferSizeInBytes);
- }
-
- class TestAudioListener implements OnClickListener {
-
- @Override
- public void onClick(View v) {
- if (v == Start) {
- startRecord();
- }
- if (v == Stop) {
- stopRecord();
- }
-
- }
-
- }
-
- private void startRecord() {
- audioRecord.startRecording();
-
- isRecord = true;
-
- new Thread(new AudioRecordThread()).start();
- }
-
- private void stopRecord() {
- close();
- }
-
- private void close() {
- if (audioRecord != null) {
- System.out.println("stopRecord");
- isRecord = false;
- audioRecord.stop();
- audioRecord.release();
- audioRecord = null;
- }
- }
-
- class AudioRecordThread implements Runnable {
- @Override
- public void run() {
- writeDateTOFile();
- copyWaveFile(AudioName, NewAudioName);
- }
- }
-
-
-
-
-
-
- private void writeDateTOFile() {
-
- byte[] audiodata = new byte[bufferSizeInBytes];
- FileOutputStream fos = null;
- int readsize = 0;
- try {
- File file = new File(AudioName);
- if (file.exists()) {
- file.delete();
- }
- fos = new FileOutputStream(file);
- } catch (Exception e) {
- e.printStackTrace();
- }
- while (isRecord == true) {
- readsize = audioRecord.read(audiodata, 0, bufferSizeInBytes);
- if (AudioRecord.ERROR_INVALID_OPERATION != readsize) {
- try {
- fos.write(audiodata);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- try {
- fos.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
-
- private void copyWaveFile(String inFilename, String outFilename) {
- FileInputStream in = null;
- FileOutputStream out = null;
- long totalAudioLen = 0;
- long totalDataLen = totalAudioLen + 36;
- long longSampleRate = sampleRateInHz;
- int channels = 2;
- long byteRate = 16 * sampleRateInHz * channels / 8;
- byte[] data = new byte[bufferSizeInBytes];
- try {
- in = new FileInputStream(inFilename);
- out = new FileOutputStream(outFilename);
- totalAudioLen = in.getChannel().size();
- totalDataLen = totalAudioLen + 36;
- WriteWaveFileHeader(out, totalAudioLen, totalDataLen,
- longSampleRate, channels, byteRate);
- while (in.read(data) != -1) {
- out.write(data);
- }
- in.close();
- out.close();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
-
-
-
-
-
-
- private void WriteWaveFileHeader(FileOutputStream out, long totalAudioLen,
- long totalDataLen, long longSampleRate, int channels, long byteRate)
- throws IOException {
- byte[] header = new byte[44];
- header[0] = 'R';
- header[1] = 'I';
- header[2] = 'F';
- header[3] = 'F';
- header[4] = (byte) (totalDataLen & 0xff);
- header[5] = (byte) ((totalDataLen >> 8) & 0xff);
- header[6] = (byte) ((totalDataLen >> 16) & 0xff);
- header[7] = (byte) ((totalDataLen >> 24) & 0xff);
- header[8] = 'W';
- header[9] = 'A';
- header[10] = 'V';
- header[11] = 'E';
- header[12] = 'f';
- header[13] = 'm';
- header[14] = 't';
- header[15] = ' ';
- header[16] = 16;
- header[17] = 0;
- header[18] = 0;
- header[19] = 0;
- header[20] = 1;
- header[21] = 0;
- header[22] = (byte) channels;
- header[23] = 0;
- header[24] = (byte) (longSampleRate & 0xff);
- header[25] = (byte) ((longSampleRate >> 8) & 0xff);
- header[26] = (byte) ((longSampleRate >> 16) & 0xff);
- header[27] = (byte) ((longSampleRate >> 24) & 0xff);
- header[28] = (byte) (byteRate & 0xff);
- header[29] = (byte) ((byteRate >> 8) & 0xff);
- header[30] = (byte) ((byteRate >> 16) & 0xff);
- header[31] = (byte) ((byteRate >> 24) & 0xff);
- header[32] = (byte) (2 * 16 / 8);
- header[33] = 0;
- header[34] = 16;
- header[35] = 0;
- header[36] = 'd';
- header[37] = 'a';
- header[38] = 't';
- header[39] = 'a';
- header[40] = (byte) (totalAudioLen & 0xff);
- header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
- header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
- header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
- out.write(header, 0, 44);
- }
-
- @Override
- protected void onDestroy() {
- close();
- super.onDestroy();
- }
- }
|
聲音的數字信息可以轉化成波形圖:貌似是很簡單的一些算大
byte[] buffer = new byte[bs]; isRun = true; while (isRun) {
int r = ar.read(buffer, 0, bs);
int v = 0;
// 將 buffer 內容取出,進行平方和運算
for (int i = 0; i < buffer.length; i++) {
// 這里沒有做運算的優化,為了更加清晰的展示代碼
v += buffer[i] * buffer[i];
}
// 平方和除以數據總長度,得到音量大小。可以獲取白噪聲值,然后對實際采樣進行標准化。
// 如果想利用這個數值進行操作,建議用 sendMessage 將其拋出,在 Handler 里進行處理。
Log.d("spl", String.valueOf(v / (float) r));
原帖地址:
點擊打開