同样的,根据上篇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
#include
#include
#include
#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
static_cast
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
// 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相加,得到的总长度就播放的音频文件长度。