【知識積累】android PCM編碼

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

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

 // 音頻獲取源
private int audioSource = MediaRecorder.AudioSource.MIC;
// 設置音頻採樣率,44100是目前的標準,但是某些設備仍然支持22050,16000,11025
private static int sampleRateInHz = 44100;
// 設置音頻的錄製的聲道CHANNEL_IN_STEREO爲雙聲道,CHANNEL_CONFIGURATION_MONO爲單聲道
private static int channelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;
// 音頻數據格式:PCM 16位每個樣本。保證設備支持。PCM 8位每個樣本。不一定能得到設備支持。
private static int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
File file = new File(Environment.getExternalStorageDirectory()
                        .getAbsolutePath() + "/test.pcm");
                // 刪除錄音文件
                if (file.exists())
                    file.delete();
                // 創建錄音文件
                try {
                    file.createNewFile();
                } catch (IOException e) {
                    throw new IllegalStateException("Failed to create "
                            + file.toString());
                }
                try {
                    // Create a DataOuputStream to write the audio data into the
                    // saved file.
                    FileOutputStream fos = new FileOutputStream(file);// 建立一個可存取字節的文件
                    // Create a new AudioRecord object to record the audio.
                    // 獲得滿足條件的最小緩衝區大小
                    bufferSizeInBytes = AudioRecord.getMinBufferSize(
                            sampleRateInHz, channelConfig, audioFormat);
                    // 創建AudioRecord對象
                    audioRecord = new AudioRecord(audioSource, sampleRateInHz,
                            channelConfig, audioFormat, bufferSizeInBytes);
                    byte[] buffer = new byte[bufferSizeInBytes];
                    audioRecord.startRecording();
                    isRecording = true;
                    while (isRecording) {
                        audioRecord.read(buffer, 0, bufferSizeInBytes);
                        fos.write(buffer);
                    }
                    audioRecord.stop();
                    audioRecord.stop();
                    audioRecord.release();// 釋放資源
                    audioRecord = null;
                    fos.close();
                } catch (Throwable t) {
                    Log.e("AudioRecord", "Recording Failed");
                }
// 放音的文件
                File file = new File(Environment.getExternalStorageDirectory()
                        .getAbsolutePath() + "/test.pcm");
                FileInputStream in = null;
                try {
                    in = new FileInputStream(file);
                } catch (FileNotFoundException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                // 獲得滿足條件的最小緩衝區大小
                bufferSizeInBytes = AudioRecord.getMinBufferSize(
                        sampleRateInHz, channelConfig, audioFormat);
                byte[] buffer = new byte[bufferSizeInBytes];
                int byteread=0;
                AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
                        sampleRateInHz, channelConfig, audioFormat,
                        bufferSizeInBytes, AudioTrack.MODE_STREAM);
                // 放音
                audioTrack.play();
                try {
                    while ((byteread = in.read(buffer)) != -1) {
                        System.out.write(buffer, 0, byteread);
                        System.out.flush();
                        audioTrack.write(buffer, 0, bufferSizeInBytes);
                    }
                } catch (Exception e) {
                    Log.e("AudioTrack", "Playback Failed");
                }
發佈了19 篇原創文章 · 獲贊 2 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章