WebRTC學習記錄(2):播放音頻文件原理一探

同樣的,根據上篇WebRTC學習記錄(1):採集microphone到文件原理實踐&講解,我還是需要有一個可運行的例子。

經過多方研究,得到如下的例子:


#include "webrtc/base/ssladapter.h"
#include "webrtc/base/win32socketinit.h"
#include "webrtc/base/win32socketserver.h"

#include "webrtc\voice_engine\voe_file_impl.h"
#include "webrtc\voice_engine\include\voe_base.h"
#include "webrtc/modules/audio_device/include/audio_device.h"

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <assert.h>
#include "webrtc/modules/audio_device/include/audio_device.h"
#include "webrtc/common_audio/resampler/include/resampler.h"
#include "webrtc/modules/audio_processing/aec/include/echo_cancellation.h"
#include "webrtc/common_audio/vad/include/webrtc_vad.h"
//#include "log.cpp"

#include "dbgtool.h"
#include "string_useful.h"




// This sub-API supports the following functionalities:
//
//  - File playback.
//  - File recording.
//  - File conversion.
//
// Usage example, omitting error checking:
//
//  using namespace webrtc;
//  VoiceEngine* voe = VoiceEngine::Create();
//  VoEBase* base = VoEBase::GetInterface(voe);
//  VoEFile* file  = VoEFile::GetInterface(voe);
//  base->Init();
//  int ch = base->CreateChannel();
//  ...
//  base->StartPlayout(ch);
//  file->StartPlayingFileAsMicrophone(ch, "data_file_16kHz.pcm", true);
//  ...
//  file->StopPlayingFileAsMicrophone(ch);
//  base->StopPlayout(ch);
//  ...
//  base->DeleteChannel(ch);
//  base->Terminate();
//  base->Release();
//  file->Release();
//  VoiceEngine::Delete(voe);

///////////////////////////////////////////////////
using namespace webrtc;
VoiceEngine* g_voe = NULL;
VoEBase* g_base = NULL;
VoEFile* g_file = NULL;
int g_ch = -1;

HANDLE g_hEvQuit = NULL;
void Begin_PlayFile();
void End_PlayFile();

// 錄製microphone輸入的聲音
void Begin_PlayFile()
{
  int iRet = -1;

  using namespace webrtc;
  g_voe = VoiceEngine::Create();
  g_base = VoEBase::GetInterface(g_voe);
  g_file = VoEFile::GetInterface(g_voe);
  g_base->Init();
  g_ch = g_base->CreateChannel();

  // 播放輸入文件audio_long16.pcm並將其錄入到audio_long16_out.pcm中
  //iRet = file->StartPlayingFileLocally(ch, "E:\\webrtc_compile\\webrtc_windows\\src\\talk\\examples\\hh_sample\\audio_long16.pcm", true);
  //iRet = file->StartRecordingPlayout(ch, "E:\\webrtc_compile\\webrtc_windows\\src\\talk\\examples\\hh_sample\\audio_long16_out.pcm");

  //iRet = file->StartRecordingMicrophone("E:\\webrtc_compile\\webrtc_windows\\src\\talk\\examples\\hh_sample\\audio_long16_from_microphone.pcm");
  iRet = g_file->StartPlayingFileLocally(g_ch, "E:\\webrtc_compile\\webrtc_windows\\src\\talk\\examples\\hh_sample\\audio_tiny16.wav", false);
  //iRet = g_file->StartPlayingFileAsMicrophone(g_ch,
  //  "E:\\webrtc_compile\\webrtc_windows\\src\\talk\\examples\\hh_sample\\audio_long16.pcm",
  //  true,
  //  true);
  
  // 開始播放
  g_base->StartPlayout(g_ch);

  while (TRUE) {
    DWORD dwRet = ::WaitForSingleObject(g_hEvQuit, INFINITE);
    if (dwRet == WAIT_OBJECT_0) {
      End_PlayFile();
      break;
    }
  }
}

void End_PlayFile()
{
  g_base->StopPlayout(g_ch); 
  g_file->StopPlayingFileLocally(g_ch);
  //g_file->StopPlayingFileAsMicrophone(g_ch);
  
  g_base->DeleteChannel(g_ch);
  g_base->Terminate();
  g_base->Release();
  g_file->Release();

  VoiceEngine::Delete(g_voe);
}

////////////////////////////////////////////////////////////////////
DWORD WINAPI ThreadFunc(LPVOID lpParameter) {

  Begin_PlayFile();

  return 0;
}

int main()
{
  // 初始化SSL
  rtc::InitializeSSL();

  DWORD IDThread;
  HANDLE hThread;

  hThread = CreateThread(NULL,
    0,
    (LPTHREAD_START_ROUTINE)ThreadFunc,
    NULL,
    0,
    &IDThread);
  if (hThread == NULL) {
    return -1;
  }

  printf("Input 'Q' to stop play!!!");
  char ch;
  while (ch = getch()) {
    if (ch == 'Q') {
      if (g_hEvQuit) {
        ::SetEvent(g_hEvQuit);
        if (hThread) {
          ::WaitForSingleObject(hThread, INFINITE);
          CloseHandle(hThread);
          hThread = NULL;
        }

        CloseHandle(g_hEvQuit);
        g_hEvQuit = NULL;
      }

      break;
    }
  }

  rtc::CleanupSSL();

  return 0;
}

對他裏面的播放的整個流程做了深入的跟蹤,並記錄爲如下的文檔: 

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

播放音頻文件時,也分爲輸入端和輸出端。
輸入端:音頻文件
輸出端:系統音頻回調




有了剛纔錄製麥克風的經驗,現在來分析播放音頻文件的流程就簡單了。
那下面開始吧!!!


//////////////////////////////////////////////////////////////////////////
// PartA--音頻輸入端-音頻文件
1. 首先調用VoEFileImpl::StartPlayingFileLocally設置音頻輸入端,該函數裏面會獲取一個Channel,然後指派該Channel來播放音頻文件,代碼如下:
int VoEFileImpl::StartPlayingFileLocally(int channel,
                                         const char fileNameUTF8[1024],
                                         bool loop,
                                         FileFormats format,
                                         float volumeScaling,
                                         int startPointMs,
                                         int stopPointMs)
{
// ...
voe::Channel* channelPtr = ch.channel();
// ...
return channelPtr->StartPlayingFileLocally(fileNameUTF8, loop, format,
                                             startPointMs, volumeScaling,
                                             stopPointMs, NULL);
// ...
}


2. Channel::StartPlayingFileLocally設置相關的回調,並創建一個FilePlayer的指針_outputFilePlayerPtr,然後指派其的函數StartPlayingFile播放音頻文件,代碼如下:
int Channel::StartPlayingFileLocally(const char* fileName,
                                     bool loop,
                                     FileFormats format,
                                     int startPosition,
                                     float volumeScaling,
                                     int stopPosition,
                                     const CodecInst* codecInst)
{
// ...
_outputFilePlayerPtr = FilePlayer::CreateFilePlayer(
            _outputFilePlayerId, (const FileFormats)format);
// ...
if (_outputFilePlayerPtr->StartPlayingFile(
                fileName,
                loop,
                startPosition,
                volumeScaling,
                notificationTime,
                stopPosition,
                (const CodecInst*)codecInst) != 0)
{
// ...
}
// ...
}


3. FilePlayer只是接口,其具體實現類爲FilePlayerImpl。在FilePlayerImpl::StartPlayingFile裏面我們會根據音頻文件格式設置相應的參數,然後調用MediaFileImpl::StartPlayingAudioFile來具體實現播放音頻文件,代碼如下:
int32_t FilePlayerImpl::StartPlayingFile(const char* fileName,
                                         bool loop,
                                         uint32_t startPosition,
                                         float volumeScaling,
                                         uint32_t notification,
                                         uint32_t stopPosition,
                                         const CodecInst* codecInst)
{
// ...


// 設置音頻編碼相關信息(採樣頻率, 採樣pac大小)


//
if (_fileModule.StartPlayingAudioFile(fileName, notification, loop,
                                              _fileFormat, &codecInstL16,
                                              startPosition,
                                              stopPosition) == -1)
{
// ...
}
// ...
}
 
4. 在MediaFileImpl::StartPlayingAudioFile裏面,我們會新建一個FileWrapper,用該FileWrapper打開文件,然後再調用MediaFileImpl::StartPlayingStream初始化音頻Stream,代碼如下:
int32_t MediaFileImpl::StartPlayingAudioFile(
    const char* fileName,
    const uint32_t notificationTimeMs,
    const bool loop,
    const FileFormats format,
    const CodecInst* codecInst,
    const uint32_t startPointMs,
    const uint32_t stopPointMs)
{
// 驗證輸入的參數(文件名, 編解碼格式等的)合法性


// ...
FileWrapper* inputStream = FileWrapper::Create();


// ...
if(StartPlayingStream(*inputStream, loop, notificationTimeMs,
                          format, codecInst, startPointMs, stopPointMs) == -1)
{
// ...
}


// ...
}


5. MediaFileImpl::StartPlayingStream初始化相關的音頻參數,檢查音頻文件數據是否正確,然後保存inputStream爲_ptrInStream,代碼如下:
int32_t MediaFileImpl::StartPlayingStream(
    InStream& stream,
    bool loop,
    const uint32_t notificationTimeMs,
    const FileFormats format,
    const CodecInst*  codecInst,
    const uint32_t startPointMs,
    const uint32_t stopPointMs)
{
// ...
if(!ValidFrequency(codecInst->plfreq) ||
               _ptrFileUtilityObj->InitPCMReading(stream, startPointMs,
                                                  stopPointMs,
                                                  codecInst->plfreq) == -1)
{
// ...
}
_ptrInStream = &stream;
// ...
}




//////////////////////////////////////////////////////////////////////////
// 上面我們將音頻的輸入端(即文件)的設置流程跟蹤了一把,下面我們還需要跟蹤音頻的播放時如何實現的。
那麼讓我們開始吧。




//////////////////////////////////////////////////////////////////////////
// 
1. 調用VoEBaseImpl::StartPlayout開始打開某一個Channel,然後調用其內部工具函數VoEBaseImpl::StartPlayout函數打開底層的音頻播放端,並在該Channel上面開始播放音頻;
部分代碼如下:
int VoEBaseImpl::StartPlayout(int channel)
{
// ...
if (StartPlayout() != 0) {
    shared_->SetLastError(VE_AUDIO_DEVICE_MODULE_ERROR, kTraceError,
                          "StartPlayout() failed to start playout");
    return -1;
  }
// ...
}


2. 在VoEBaseImpl::StartPlayout內部初始化平臺相關的音頻播放模塊,並委託其成員變量AudioDeviceModule播放音頻,代碼如下:
int32_t VoEBaseImpl::StartPlayout()
{
// ...
if (shared_->audio_device()->StartPlayout() != 0) {
    LOG_F(LS_ERROR) << "Failed to start playout";
    return -1;
}
// ...
}


3. AudioDeviceModuleImpl播放音頻也只不過是將該操作委託給其成員變量_ptrAudioDevice(其類型爲AudioDeviceGeneric)來處理,代碼如下:
int32_t AudioDeviceModuleImpl::StartPlayout()
{
    CHECK_INITIALIZED();
    return (_ptrAudioDevice->StartPlayout());
}


4. 在Windows上,AudioDeviceGeneric的實現爲AudioDeviceWindowsCore,因此在AudioDeviceWindowsCore::StartPlayout函數裏面,我們會創建一個WSAPIRenderThread,並交由該線程來渲染音頻,代碼如下:
int32_t AudioDeviceWindowsCore::StartPlayout()
{
// ...
_hPlayThread = CreateThread(
                         NULL,
                         0,
                         WSAPIRenderThread,
                         this,
                         0,
                         NULL);
// ...
}


5. 該線程會不斷通過成員變量AudioDeviceBuffer* _ptrAudioBuffer; 向上層請求音頻的buffer,然後播放之,代碼如下:
DWORD AudioDeviceWindowsCore::DoRenderThread()
{
// ...
_ptrAudioBuffer->RequestPlayoutData(_playBlockSize);
// ...
}


6. 同樣的,在AudioDeviceBuffer::RequestPlayoutData內部也只是做了一些相關的參數檢查工作,並調用其成員變量AudioTransport* _ptrCbAudioTransport;(這裏的AudioTransport的實現爲VoEBaseImpl)的NeedMorePlayData函數向上層請求音頻buffer,代碼如下:
int32_t AudioDeviceBuffer::RequestPlayoutData(uint32_t nSamples)
{
// ...
res = _ptrCbAudioTransport->NeedMorePlayData(_playSamples,
                                                     playBytesPerSample,
                                                     playChannels,
                                                     playSampleRate,
                                                     &_playBuffer[0],
                                                     nSamplesOut,
                                                     &elapsed_time_ms,
                                                     &ntp_time_ms);
// ...
}


7. VoEBaseImpl::NeedMorePlayData內部只是保存了採樣輸出字節數,並委託給GetPlayoutData來處理,代碼如下:
int32_t VoEBaseImpl::NeedMorePlayData(uint32_t nSamples,
                                      uint8_t nBytesPerSample,
                                      uint8_t nChannels, uint32_t samplesPerSec,
                                      void* audioSamples, uint32_t& nSamplesOut,
                                      int64_t* elapsed_time_ms,
                                      int64_t* ntp_time_ms) {
  GetPlayoutData(static_cast<int>(samplesPerSec), static_cast<int>(nChannels),
                 static_cast<int>(nSamples), true, audioSamples,
                 elapsed_time_ms, ntp_time_ms);
  nSamplesOut = audioFrame_.samples_per_channel_;
  return 0;
}


8. 在VoEBaseImpl::GetPlayoutData裏面混響相關通道的音頻Buffer,代碼如下:
void VoEBaseImpl::GetPlayoutData(int sample_rate, int number_of_channels,
                                 int number_of_frames, bool feed_data_to_apm,
                                 void* audio_data, int64_t* elapsed_time_ms,
                                 int64_t* ntp_time_ms)
{
// ...
shared_->output_mixer()->MixActiveChannels();
// ...
}


9. 這個OutputMixer::MixActiveChannels將該操作委託給其成員變量AudioConferenceMixer& _mixerModule;來執行,代碼如下:
int32_t
OutputMixer::MixActiveChannels()
{
    return _mixerModule.Process();
}


10. AudioConferenceMixerImpl::Process內部檢查相關的參數,並獲取音頻buffer,代碼如下:
int32_t AudioConferenceMixerImpl::Process()
{
// ...
GetAdditionalAudio(&additionalFramesList);
// ...
}


11. void AudioConferenceMixerImpl::GetAdditionalAudio內部調用GetAudioFrame獲取音頻Buffer,
這裏的participant其實是一Channel類型,代碼如下:
void AudioConferenceMixerImpl::GetAdditionalAudio(
    AudioFrameList* additionalFramesList)
{
// ...
if((*participant)->GetAudioFrame(_id, *audioFrame) != 0) {
// ...
}
// ...
}


12. 在Channel::GetAudioFrame內部,混合解碼的音頻,代碼如下:
int32_t Channel::GetAudioFrame(int32_t id, AudioFrame& audioFrame)
{
// ...
MixAudioWithFile(audioFrame, audioFrame.sample_rate_hz_);
// ...
}


13. Channel::MixAudioWithFile內部,會委託FilePlayer* _outputFilePlayerPtr;的Get10msAudioFromFile函數來獲取音頻buffer,需要注意的是這裏的_outputFilePlayerPtr就是我們在PartA-2裏面提到的那個指針,代碼如下:
int32_t
Channel::MixAudioWithFile(AudioFrame& audioFrame,
                          int mixingFrequency)
{
// ...
// We should get the frequency we ask for.
_outputFilePlayerPtr->Get10msAudioFromFile(fileBuffer.get(), fileSamples, mixingFrequency);
// ...
}


14. 在FilePlayerImpl::Get10msAudioFromFile內部還會繼續委託MediaFile& _fileModule;的PlayoutAudioData函數來繼續獲取音頻buffer,代碼如下:
int32_t FilePlayerImpl::Get10msAudioFromFile(
    int16_t* outBuffer,
    int& lengthInSamples,
    int frequencyInHz)
{
// ...
_fileModule.PlayoutAudioData(
                (int8_t*)unresampledAudioFrame.data_,
                lengthInBytes);
// ...
}


15. 在MediaFileImpl::PlayoutAudioData內部,會從文件流裏面獲取音頻buffer,代碼如下:
int32_t MediaFileImpl::PlayoutAudioData(int8_t* buffer,
                                        size_t& dataLengthInBytes)
{
// ...
bytesRead = _ptrFileUtilityObj->ReadPCMData(
                    *_ptrInStream,
                    buffer,
                    bufferLengthInBytes);

if( bytesRead > 0)
{
    dataLengthInBytes = static_cast<size_t>(bytesRead);


    // BD
static int iIndex = 0;
static long long llTotal = 0;
char szTmp[200] = { 0 };
llTotal += dataLengthInBytes;
    sprintf(szTmp, "Index: %d, size: %d, total: %d\n",
    ++iIndex, dataLengthInBytes, llTotal);


    OutputDebugStringA(szTmp);
    // ED
}
// ...
}
若將每次調用PlayoutAudioData得到的bytesRead相加,得到的總長度就播放的音頻文件長度。


































































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