程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> webrtc學習(二): audio_device之opensles,webrtcaudio_device

webrtc學習(二): audio_device之opensles,webrtcaudio_device

編輯:C++入門知識

webrtc學習(二): audio_device之opensles,webrtcaudio_device


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了一遍, 也就找到兩篇有點用的文章.

OpenSL ES for Android  對於代碼是ndk samples 中的native-audio.

Lock-free audio IO with OpenSL ES on Android  另一篇opensl的應用.

 

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來實現.

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved