audio_device是webrtc的音頻設備模塊. 封裝了各個平台的音頻設備相關的代碼
audio device 在android下封裝了兩套音頻代碼.
1. 通過jni調用java的media進行操作.
2. 直接通過opensl es的native c接口進行操作.
native 接口自然比較高效, 但缺點在於opensl 要求 android 2.3+.
OpenSL ES (Open Sound Library for Embedded Systems) 是無授權費、跨平台、針對嵌入式系統精心優化的硬件音頻加速API
opensl的資料非常少, google了一遍, 也就找到兩篇有點用的文章.
webrtc 的example中提供了一個opensl es的例子. opensl_loopbakc(opensldemo-debug.apk) 用於示范 opensl的使用 (回放聲音).
花了一點時間分析了下全部的流程, 因為無法調試, 所以看起來很煩. 線程處理的地方加了log才看明白.
主要有這幾個類:
1. AudioDeviceBuffer
緩存類, 方法RegisterAudioCallback. 通過callback來通知數據采集(record), 或者請求數據(playout).
2. OpenSlesInput
record 的實現.
3. OpenSlesOutput
playout的實現.
4. SingleRwFifo
實現了一個無鎖隊列.
播放的流程:
1. 創建OpenSlesOutput 並且 AttachAudioBuffer, 初始化opensl的相關信息(engine, outmix等). 初始化需要的播放緩存.
2. StartPlayout中, 創建opensl 的audio player, 注冊player 緩存播放的callback. 並對所有的播放緩存Enqueue, 然後創建音頻數據處理線程CbThreadImpl
說明: 音頻數據Enqueue到player. 就會播放出來, 並且每次播放完成後player會回調注冊的callback.
3. CbThreadImpl的 喚醒是由event_ 來控制的. 有kUnderrun 和 kNoUnderrun兩種狀態. kUnderrun 表示音頻數據低於預計值. kNoUnderrun表示音頻數據正常.
在callback(PlayerSimpleBufferQueueCallbackHandler)回調時的處理是這樣的.
當fifo_中沒有數據需要播放時, 以kUnderrun 喚醒CbThreadImpl.
當fifo_中有數據時, 把音頻數據Enqueue 入player. 以kNoUnderrun 喚醒CbThreadImpl
void OpenSlesOutput::PlayerSimpleBufferQueueCallbackHandler( SLAndroidSimpleBufferQueueItf sles_player_sbq_itf) { if (fifo_->size() <= 0 || number_underruns_ > 0) { ++number_underruns_; event_.SignalEvent(kUnderrun, number_underruns_); return; } int8_t* audio = fifo_->Pop(); if (audio) OPENSL_RETURN_ON_FAILURE( (*sles_player_sbq_itf)->Enqueue(sles_player_sbq_itf, audio, buffer_size_bytes_), VOID_RETURN); event_.SignalEvent(kNoUnderrun, 0); }
4. 當CbThreadImpl被喚醒時. 如果是kUnderrun 則player會重新啟動. 並重新把所有播放緩存Enqueue.
OPENSL_RETURN_ON_FAILURE( (*sles_player_itf_)->SetPlayState(sles_player_itf_, SL_PLAYSTATE_STOPPED), true); EnqueueAllBuffers(); OPENSL_RETURN_ON_FAILURE( (*sles_player_itf_)->SetPlayState(sles_player_itf_, SL_PLAYSTATE_PLAYING), true);
如果是kNoUnderrun , 則開始處理.
while (fifo_->size() < num_fifo_buffers_needed_ && playing_) { int8_t* audio = play_buf_[active_queue_].get(); fine_buffer_->GetBufferData(audio); fifo_->Push(audio); active_queue_ = (active_queue_ + 1) % TotalBuffersUsed(); }
fine_buffer_ 的GetBufferData會自動處理10ms的數據. 如果數據不足, 則從audio buffer的callback –> NeedMorePlayData請求數據. 如果數據太多則存入緩存中.
fifo_ 把獲取到的數據入棧. 當fifo_的大小等於num_fifo_buffers_needed_(預分配的播放緩存數量) 時, CbThreadImpl停止處理, 等待下次喚醒.
5.
webrtc中的threadWrapper::create創建的線程. start的處理代碼是這樣的.
result |= pthread_create(&thread_, &attr_, &StartThread, this);
StartThread的代碼:
bool alive = true; bool run = true; while (alive) { run = run_function_(obj_); CriticalSectionScoped cs(crit_state_); if (!run) { alive_ = false; } alive = alive_; }
run_function_ 就是create時, 傳進去的函數.
所以opensl 的CbThreadImpl處理是不斷被調用的. 這是我原先非常疑惑的一點( 沒看threadwrapper的代碼之前, 我並不知道CbThreadImpl會一直被調用).
錄制的流程 就不贅述了. 大體沒啥差別.
opensl demo中是FakeAudioDeviceBuffer繼承了AudioDeviceBuffer, 在GetPlayoutData中把record的數據交付給playout. 而不是通過外部的callback來實現.