流化是一个过程,在这个过程中,我们只需要在内存中维护一小块内存来播放音频文件。这样我们就可以使用很大的音频文件作为背景音乐,而不占用很大的内存。当我们流化一个音频文件的时候,音频数据是从硬盘上一块一块的读取,而不是将整个文件一次性全部加载。流化是通过异步读取音频数据到硬盘缓冲区队列来完成的。当一个缓冲区填充好数据后,提交给source voice。当source voice播放完成一个缓冲区后,这个缓冲区就可以再次用来读取文件中的数据。通过这种方式循环使用缓冲区,允许我们只加载一部分文件的数据就可以完成对一个很大的音频文件的播放。流化的代码应该放在一个单独的线程中,这样当需要等待长时间的读取或者音频播放操作时可以睡眠。当音频播放完成时,我们可以使用回调触发事件来唤醒线程。
创建一个类继承IXAudio2VoiceCallback接口,并实现。在 void OnBufferEnd( void* ){ SetEvent( hBufferEndEvent ); }方法中设置一个事件,当音频播放需要等待时,线程会自动进入睡眠,当当前buffer播放完成时,SetEvent中的事件被触发从而唤醒线程。
例子的主要实现方法是,在主线程中完成COM初始化,XAudio2引擎和IXAudio2MasteringVoice的创建,并创建线程,把流化文件的回调函数传递给线程,并将文件名通过线程传递给回调。启动线程,会进入回调函数中进行文件解析,读取文件数据,并进行播放。
示例中的缓冲区是循环使用,这样就达到了只维护一小块内存就可以满足播放一个很大的音频文件的要求。
每个缓冲区的大小设置为10240,共5个缓冲区,在一个数组里面。通过维护数组的索引进行循环利用,通过IXAudio2VoiceCallback来保证等待队列不超过5,这样就可以保证等待队列中的数据在播放完成之前不被覆盖。如果数据还没播放完成就失效,那么会导致XAudio2崩溃。
DirectX提供了StreamAudios示例,不过我没有采用它的,代码是我自己重新实现的。DirectX示例:http://download.csdn.net/detail/u011417605/9484060
完整代码:
#pragma once #include "WaveFile.h" #include "XAudio2.h" #include <thread> #define STREAMBUFFERSIZE 10240//每个缓冲区大小 #define BUFFERNUM 5//缓冲区个数 BYTE streamBuffers[BUFFERNUM][STREAMBUFFERSIZE];//缓冲区数组 class StreamingVoiceContext : public IXAudio2VoiceCallback { public: HANDLE hBufferEndEvent; StreamingVoiceContext() : hBufferEndEvent(CreateEvent(NULL, FALSE, FALSE, NULL)){} ~StreamingVoiceContext(){ CloseHandle(hBufferEndEvent); } void OnStreamEnd() { /*SetEvent(hBufferEndEvent);*/ } void OnVoiceProcessingPassEnd() { } void OnVoiceProcessingPassStart(UINT32 SamplesRequired) { } //Called when the voice has just finished playing an audio buffer. void OnBufferEnd(void * pBufferContext) { SetEvent(hBufferEndEvent); } void OnBufferStart(void * pBufferContext) { } void OnLoopEnd(void * pBufferContext) { /*SetEvent(hBufferEndEvent);*/ } void OnVoiceError(void * pBufferContext, HRESULT Error) { } }; IXAudio2 *pEngine = NULL; int currentBufferIndex = 0; void StreamAudioFile(LPWSTR fileName)//线程回调 { CWaveFile waveFile; HRESULT hr = waveFile.Open(fileName, NULL, WAVEFILE_READ);//加载文件 if (FAILED(hr)) return; WAVEFORMATEX *waveFormat = waveFile.GetFormat();//获取文件格式 StreamingVoiceContext pCallBack; IXAudio2SourceVoice *pSourceVoice = NULL; hr = pEngine->CreateSourceVoice(&pSourceVoice, waveFormat, 0, 1.0f, &pCallBack);//创建源声音,用来提交数据 if (FAILED(hr)) return; int fileSize = waveFile.GetSize();//获取文件的大小 int currentPos = 0; while (currentPos < fileSize) { DWORD size = STREAMBUFFERSIZE; hr = waveFile.Read(streamBuffers[currentBufferIndex], size, &size);//读取文件内容 if (FAILED(hr)) break; currentPos += size;//已经播放的数据大小 XAUDIO2_BUFFER buffer = { 0 };//将读取的文件数据,赋值XAUDIO2_BUFFER buffer.AudioBytes = size; buffer.pAudioData = streamBuffers[currentBufferIndex]; hr = pSourceVoice->SubmitSourceBuffer(&buffer);//提交内存数据 if (FAILED(hr)) break; hr = pSourceVoice->Start(XAUDIO2_COMMIT_NOW);//启动源声音 if (FAILED(hr)) break; XAUDIO2_VOICE_STATE state; pSourceVoice->GetState(&state);//获取状态 while (state.BuffersQueued > BUFFERNUM - 1)//不让缓冲区的音频数据覆盖 { WaitForSingleObject(pCallBack.hBufferEndEvent, INFINITE); pSourceVoice->GetState(&state); } currentBufferIndex++;//循环利用 缓冲区 currentBufferIndex %= BUFFERNUM; } XAUDIO2_VOICE_STATE state; while (pSourceVoice->GetState(&state), state.BuffersQueued > 0)//等待队列中的数据播放完成,退出线程 { WaitForSingleObject(pCallBack.hBufferEndEvent, INFINITE); } pSourceVoice->DestroyVoice();//释放资源 } int main(int argc, char *argv[]) { HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);;//com初始化 if (FAILED(hr)) return 0; hr = XAudio2Create(&pEngine);//创建引擎 if (FAILED(hr)) return 0; IXAudio2MasteringVoice *pMasterVoice = NULL; hr = pEngine->CreateMasteringVoice(&pMasterVoice);//创建主声音,默认是输出当前扬声器 if (FAILED(hr)) return 0; LPWSTR fileName = L"F:\\桌面\\TestSong.wav"; std::thread t(StreamAudioFile, fileName); t.join(); fileName = L"F:\\桌面\\Example2.wav"; std::thread p(StreamAudioFile, fileName); p.join(); pMasterVoice->DestroyVoice();//释放资源 pEngine->Release();//释放资源 CoUninitialize();//释放资源 return 0; }
交流QQ:1245178753
源码下载:http://download.csdn.net/detail/u011417605/9484075
本文地址:http://blog.csdn.net/u011417605/article/details/51087826