ANDROID音訊系統散記之二:resample-1
Android上的resample處理
預設的情況下,Android放音的取樣率固定為44.1khz,錄音的取樣率固定為8khz,因此底層的音訊裝置驅動只需設定好這兩個固定的取樣率。如果上層傳過來的取樣率與其不符的話,則Android Framework層會對音訊流做resample(重取樣)處理。
Resample的大致流程如下:
AudioResample作為最基本的類,回放和錄音resample最終都會呼叫到這個類;有興趣可以研究下resample的演算法和實現,這裡不闡述。
AudioMixer僅僅提供給回放使用的,這個類功能不僅僅是resample了,混音、音量設定都由這個類實現的。
錄音:
1、AudioFlinger::RecordThread是錄音執行緒類,每當有錄音請求時,進入AudioFlinger::openInput建立這個執行緒;
2、在建立這個執行緒的同時,呼叫readInputParameters,檢查上層傳過來的錄音取樣率是否與底層音訊介面固定的錄音取樣率一致;如果不一致,則呼叫AudioResampler::create建立一個resampler;
3、關於AudioFlinger::RecordThread::ThreadLoop,當錄音工作執行緒啟動後,會不斷迴圈該ThreadLoop方法,主要是:1)讀取底層音訊裝置獲取錄音資料mBytesRead = mInput->read(mRsmpInBuffer, mInputBytes); 2)對錄音資料做重取樣 mResampler->resample(mRsmpOutBuffer, framesOut, this);
放音:
1、AudioFlinger::MixerThread是預設的放音執行緒,派生自PlaybackThread,由AudioFlinger::openOutput負責建立;
2、MixerThread建立時,1)呼叫readOutputParameters獲取底層音訊介面固定的放音取樣率;2)建立一個AudioMixer;
3、關於AudioFlinger::MixerThread:: ThreadLoop,當放音工作執行緒啟動後,會不斷迴圈該TreadLoop方法,主要是:1)混合各track音訊資料mAudioMixer->process(); 2)將混合後的音訊資料寫到底層音訊裝置int bytesWritten = (int)mOutput->write(mMixBuffer, mixBufferSize);
Android預設的取樣率
一步一步跟下去,會發現是alsa_default.cpp裡面固定好的:
- // 放音引數配置
- static alsa_handle_t _defaultsOut = {
- module : 0,
- devices : AudioSystem::DEVICE_OUT_ALL,
- curDev : 0,
- curMode : 0,
- handle : 0,
- format : SND_PCM_FORMAT_S16_LE, // AudioSystem::PCM_16_BIT
- channels : 2,
- sampleRate : DEFAULT_SAMPLE_RATE, // 放音取樣率,固定為44.1khz
- latency : 200000, // Desired Delay in usec
- bufferSize : DEFAULT_SAMPLE_RATE / 5, // Desired Number of samples
- modPrivate : 0,
- };
- // 錄音引數配置
- static alsa_handle_t _defaultsIn = {
- module : 0,
- devices : AudioSystem::DEVICE_IN_ALL,
- curDev : 0,
- curMode : 0,
- handle : 0,
- format : SND_PCM_FORMAT_S16_LE, // AudioSystem::PCM_16_BIT
- channels : 1,
- sampleRate : AudioRecord::DEFAULT_SAMPLE_RATE, // 錄音取樣率,固定為8khz
- latency : 250000, // Desired Delay in usec
- bufferSize : 2048, // Desired Number of samples
- modPrivate : 0,
- };
網上有談到Android這種音訊resample方式導致音質變差,我想修改起來應該也不是很難,以後找機會實踐一下。思路:先嚐試用上層傳過來的錄音取樣率來設定底層音訊裝置,如果設定成功則不需要resample,不成功才使用預設的取樣率。放音暫不能這樣改,因為放音可能要混合多個track的資料,而各個track的取樣率不一定是一樣的。
ALSA中resample處理??
這章節我是用問號的,因為在這裡我的確困惑了,先細細道來。
一般來說,resample的流程是這樣:
- +---------------------+ +-------------------------------+
- | app sample rate | | Android Framework sample rate |
- | 16khz | <--resample-- | 8khz | <--ALSA interface
- +---------------------+ +-------------------------------+
那麼ALSA interface用8khz的取樣率進行錄音就好。
但事實上我發現底層音訊裝置用其他的取樣率也是可以的。比如說44.1khz,我測量到I2S的ADCLRC是44.1khz並且CODEC的暫存器也是設定為44.1khz錄音取樣率,就這樣Android應用錄出來的聲音也是正常的。
可明明在alsa_default.cpp中設定的取樣率是8khz的,而且設定成功:
- err = snd_pcm_hw_params_set_rate_near(handle->handle, hardwareParams,
- &requestedRate, 0);
返回來的requestedRate是8000,這是Android Framework固定的錄音取樣率。
那麼到底在哪裡進行了44.1khz->8khz的resample處理?唯一是在alsa-lib或alsa-driver裡面了,到目前為止我還未找到相關程式碼。
--------------------------------------------------------------------------------------------------------------------
2011/10/21
近期瑣事頗多,找房子租房子搬家一團糟,昨天終於全部搞定了。
回到正題,有關alsa的resample處理,應該是在alsa-lib中實現的。摘錄別人的分析,如下:
- snd_pcm_mmap_write_areas()函式迴圈寫入資料,直到資料為空,首先將找到對映記憶體pcm->running_areas的地址,然後呼叫snd_pcm_areas_copy()進行資料轉換,如sample rate、channels等(如果源資料和硬體支援格式一致,就簡單地通過memcpy拷貝資料),轉換成硬體支援格式對應的資料後,呼叫snd_pcm_mmap_commit()將轉換後的資料寫入對映記憶體。寫入由snd_pcm_dmix_mmap_commit()完成,在對資料進行混音(do_mix_areas)同時,寫入對映記憶體。
拋棄alsa-lib,其實我們還是有其他選擇的,還記得ANDROID2.3音訊系統HAL提到的mini alsa-lib(android2.3.1-gingerbread/device/samsung/crespo/libaudio)嗎?我們可以從這裡分析,由於篇幅可能比較長,就獨立成章吧,待整理。。。