android中AudioRecord使用

一 什麼是音頻的採樣率和採樣大小  

自然界中的聲音非常複雜,波形極其複雜,通常我們採用的是脈衝代碼調製編碼。即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中採集音頻的apiandroid.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.

採樣率:音頻的採樣頻率,每秒鐘能夠採樣的次數,採樣率越高,音質越高。給出的實例是441002205011025但不限於這幾個參數。例如要採集低質量的音頻就可以使用40008000等低採樣率。

channelConfig

describes the configuration of the audio channels. See CHANNEL_IN_MONO andCHANNEL_IN_STEREO

聲道設置:android支持雙聲道立體聲和單聲道。MONO單聲道,STEREO立體聲

audioFormat

the format in which the audio data is represented. See ENCODING_PCM_16BIT andENCODING_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 demo.camera;  
import java.io.BufferedInputStream;  
import java.io.BufferedOutputStream;  
import java.io.DataInputStream;  
import java.io.DataOutputStream;  
import java.io.File;  
import java.io.FileInputStream;  
import java.io.FileOutputStream;  
import java.io.IOException;  
import android.app.Activity;  
import android.content.ContentValues;  
import android.content.Intent;  
import android.hardware.Camera.AutoFocusCallback;  
import android.media.AudioFormat;  
import android.media.AudioManager;  
import android.media.AudioRecord;  
import android.media.AudioTrack;  
import android.media.MediaPlayer;  
import android.media.MediaRecorder;  
import android.net.Uri;  
import android.os.AsyncTask;  
import android.os.Bundle;  
import android.os.Environment;  
import android.provider.MediaStore;  
import android.util.Log;  
import android.view.View;  
import android.widget.Button;  
import android.widget.TextView;  
/** 
 * 該實例中,我們使用AudioRecord類來完成我們的音頻錄製程序 
 * AudioRecord類,我們可以使用三種不同的read方法來完成錄製工作, 
 * 每種方法都有其實用的場合 
 * 一、實例化一個AudioRecord類我們需要傳入幾種參數 
 * 1、AudioSource:這裏可以是MediaRecorder.AudioSource.MIC 
 * 2、SampleRateInHz:錄製頻率,可以爲8000hz或者11025hz等,不同的硬件設備這個值不同 
 * 3、ChannelConfig:錄製通道,可以爲AudioFormat.CHANNEL_CONFIGURATION_MONO和AudioFormat.CHANNEL_CONFIGURATION_STEREO 
 * 4、AudioFormat:錄製編碼格式,可以爲AudioFormat.ENCODING_16BIT和8BIT,其中16BIT的仿真性比8BIT好,但是需要消耗更多的電量和存儲空間 
 * 5、BufferSize:錄製緩衝大小:可以通過getMinBufferSize來獲取 
 * 這樣我們就可以實例化一個AudioRecord對象了 
 * 二、創建一個文件,用於保存錄制的內容 
 * 同上篇 
 * 三、打開一個輸出流,指向創建的文件 
 * DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file))) 
 * 四、現在就可以開始錄製了,我們需要創建一個字節數組來存儲從AudioRecorder中返回的音頻數據,但是 
 * 注意,我們定義的數組要小於定義AudioRecord時指定的那個BufferSize 
 * short[]buffer = new short[BufferSize/4]; 
 * startRecording(); 
 * 然後一個循環,調用AudioRecord的read方法實現讀取 
 * 另外使用MediaPlayer是無法播放使用AudioRecord錄製的音頻的,爲了實現播放,我們需要 
 * 使用AudioTrack類來實現 
 * AudioTrack類允許我們播放原始的音頻數據 
 *  
 *  
 * 一、實例化一個AudioTrack同樣要傳入幾個參數 
 * 1、StreamType:在AudioManager中有幾個常量,其中一個是STREAM_MUSIC; 
 * 2、SampleRateInHz:最好和AudioRecord使用的是同一個值 
 * 3、ChannelConfig:同上 
 * 4、AudioFormat:同上 
 * 5、BufferSize:通過AudioTrack的靜態方法getMinBufferSize來獲取 
 * 6、Mode:可以是AudioTrack.MODE_STREAM和MODE_STATIC,關於這兩種不同之處,可以查閱文檔 
 * 二、打開一個輸入流,指向剛剛錄製內容保存的文件,然後開始播放,邊讀取邊播放 
 *  
 * 實現時,音頻的錄製和播放分別使用兩個AsyncTask來完成  
 */  
public class MyAudioRecord2 extends Activity{  
      
    private TextView stateView;  
      
    private Button btnStart,btnStop,btnPlay,btnFinish;  
      
    private RecordTask recorder;  
    private PlayTask player;  
      
    private File audioFile;  
      
    private boolean isRecording=true, isPlaying=false; //標記  
      
    private int frequence = 8000; //錄製頻率,單位hz.這裏的值注意了,寫的不好,可能實例化AudioRecord對象的時候,會出錯。我開始寫成11025就不行。這取決於硬件設備  
    private int channelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;  
    private int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;  
      
      
    public void onCreate(Bundle savedInstanceState){  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.my_audio_record);  
          
        stateView = (TextView)this.findViewById(R.id.view_state);  
        stateView.setText("準備開始");  
        btnStart = (Button)this.findViewById(R.id.btn_start);  
        btnStop = (Button)this.findViewById(R.id.btn_stop);  
        btnPlay = (Button)this.findViewById(R.id.btn_play);  
        btnFinish = (Button)this.findViewById(R.id.btn_finish);  
        btnFinish.setText("停止播放");  
        btnStop.setEnabled(false);  
        btnPlay.setEnabled(false);  
        btnFinish.setEnabled(false);  
          
        //在這裏我們創建一個文件,用於保存錄制內容  
        File fpath = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+"/data/files/");  
        fpath.mkdirs();//創建文件夾  
        try {  
            //創建臨時文件,注意這裏的格式爲.pcm  
            audioFile = File.createTempFile("recording", ".pcm", fpath);  
        } catch (IOException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }         
    }  
      
      
    public void onClick(View v){  
        int id = v.getId();  
        switch(id){  
        case R.id.btn_start:  
            //開始錄製  
              
            //這裏啓動錄製任務  
            recorder = new RecordTask();  
            recorder.execute();  
              
            break;  
        case R.id.btn_stop:  
            //停止錄製  
            this.isRecording = false;  
            //更新狀態  
            //在錄製完成時設置,在RecordTask的onPostExecute中完成  
            break;  
        case R.id.btn_play:  
              
            player = new PlayTask();  
            player.execute();  
            break;  
        case R.id.btn_finish:  
            //完成播放  
            this.isPlaying = false;  
            break;  
              
        }  
    }  
      
    class RecordTask extends AsyncTask<Void, Integer, Void>{  
        @Override  
        protected Void doInBackground(Void... arg0) {  
            isRecording = true;  
            try {  
                //開通輸出流到指定的文件  
                DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(audioFile)));  
                //根據定義好的幾個配置,來獲取合適的緩衝大小  
                int bufferSize = AudioRecord.getMinBufferSize(frequence, channelConfig, audioEncoding);  
                //實例化AudioRecord  
                AudioRecord record = new AudioRecord(MediaRecorder.AudioSource.MIC, frequence, channelConfig, audioEncoding, bufferSize);  
                //定義緩衝  
                short[] buffer = new short[bufferSize];  
                  
                //開始錄製  
                record.startRecording();  
                  
                int r = 0; //存儲錄製進度  
                //定義循環,根據isRecording的值來判斷是否繼續錄製  
                while(isRecording){  
                    //從bufferSize中讀取字節,返回讀取的short個數  
                    //這裏老是出現buffer overflow,不知道是什麼原因,試了好幾個值,都沒用,TODO:待解決  
                    int bufferReadResult = record.read(buffer, 0, buffer.length);  
                    //循環將buffer中的音頻數據寫入到OutputStream中  
                    for(int i=0; i<bufferReadResult; i++){  
                        dos.writeShort(buffer[i]);  
                    }  
                    publishProgress(new Integer(r)); //向UI線程報告當前進度  
                    r++; //自增進度值  
                }  
                //錄製結束  
                record.stop();  
                Log.v("The DOS available:", "::"+audioFile.length());  
                dos.close();  
            } catch (Exception e) {  
                // TODO: handle exception  
            }  
            return null;  
        }  
          
        //當在上面方法中調用publishProgress時,該方法觸發,該方法在UI線程中被執行  
        protected void onProgressUpdate(Integer...progress){  
            stateView.setText(progress[0].toString());  
        }  
          
        protected void onPostExecute(Void result){  
            btnStop.setEnabled(false);  
            btnStart.setEnabled(true);  
            btnPlay.setEnabled(true);  
            btnFinish.setEnabled(false);  
        }  
          
        protected void onPreExecute(){  
            //stateView.setText("正在錄製");  
            btnStart.setEnabled(false);  
            btnPlay.setEnabled(false);  
            btnFinish.setEnabled(false);  
            btnStop.setEnabled(true);         
        }  
          
    }  
      
    class PlayTask extends AsyncTask<Void, Integer, Void>{  
        @Override  
        protected Void doInBackground(Void... arg0) {  
            isPlaying = true;  
            int bufferSize = AudioTrack.getMinBufferSize(frequence, channelConfig, audioEncoding);  
            short[] buffer = new short[bufferSize/4];  
            try {  
                //定義輸入流,將音頻寫入到AudioTrack類中,實現播放  
                DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(audioFile)));  
                //實例AudioTrack  
                AudioTrack track = new AudioTrack(AudioManager.STREAM_MUSIC, frequence, channelConfig, audioEncoding, bufferSize, AudioTrack.MODE_STREAM);  
                //開始播放  
                track.play();  
                //由於AudioTrack播放的是流,所以,我們需要一邊播放一邊讀取  
                while(isPlaying && dis.available()>0){  
                    int i = 0;  
                    while(dis.available()>0 && i<buffer.length){  
                        buffer[i] = dis.readShort();  
                        i++;  
                    }  
                    //然後將數據寫入到AudioTrack中  
                    track.write(buffer, 0, buffer.length);  
                      
                }  
                  
                //播放結束  
                track.stop();  
                dis.close();  
            } catch (Exception e) {  
                // TODO: handle exception  
            }  
            return null;  
        }  
          
        protected void onPostExecute(Void result){  
            btnPlay.setEnabled(true);  
            btnFinish.setEnabled(false);  
            btnStart.setEnabled(true);  
            btnStop.setEnabled(false);  
        }  
          
        protected void onPreExecute(){    
              
            //stateView.setText("正在播放");  
            btnStart.setEnabled(false);  
            btnStop.setEnabled(false);  
            btnPlay.setEnabled(false);  
            btnFinish.setEnabled(true);           
        }  
          
    }  
}




 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章