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相加,得到的总长度就播放的音频文件长度。


































































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