waveout系列API实现pcm音频播放
最近做一个播放组件,也算是折腾1周了,收获还算不少。
回想下整个编码过程中磕磕碰碰走了不少弯路,最大的杯具就是,太相信网上现有代码例子。
国内网上关于waveout的文章不少,但基本就那几篇转载,其中的问题也没有人指出。
为了方便大家用到时少被误导,在此留下我的笔记(如果被我误导了,我先道歉-,-)
代码不多,直接上关键部分(本人认为多余代码贴上去百害而无一利):
一、初始化设备
bool WinAudioPlay::DevOpen() { if (!m_bPalyStata) { WAVEFORMATEX wfx; ZeroMemory(&wfx,sizeof(WAVEFORMATEX)); wfx.wFormatTag = WAVE_FORMAT_PCM; wfx.nChannels = 2; wfx.nSamplesPerSec = 44100L; wfx.wBitsPerSample = 16; wfx.cbSize = 0; wfx.nBlockAlign = wfx.wBitsPerSample * wfx.nChannels / 8; wfx.nAvgBytesPerSec = wfx.nChannels * wfx.nSamplesPerSec * wfx.wBitsPerSample / 8; if(::waveOutOpen (0,0,&wfx,0,0,WAVE_FORMAT_QUERY)) { Plug::PlugMessageBox(L"wave设备初始化失败~"); return false; } if (::waveOutOpen(&m_hWaveOut, WAVE_MAPPER, &wfx, (DWORD)&WinAudioPlay::waveOutProc, (DWORD)this, CALLBACK_FUNCTION)) { Plug::PlugMessageBox(L"wave设备初始化失败~"); return false; } m_iBlockNum = 0; m_bPalyStata = true; m_spbrTgthr.reset(new boost::barrier(2)); m_sptrdWaveOutTgthr.reset(new boost::thread(boost::bind(&WinAudioPlay::ThrdWaveOutTogether,this))); } return true; }
二、接收pcm格式数据,并加载到声卡缓冲区
bool __stdcall WinAudioPlay::play_audio( const void* buffer, int len ) { if (!m_bPalyStata) return false; if (BLOCK_MAX <= m_iBlockNum || len <= 0) { return true; //超过缓冲最大包,不继续播放 } LPWAVEHDR pWaveHeader = new WAVEHDR; memset(pWaveHeader, 0, sizeof(WAVEHDR)); pWaveHeader->dwLoops = 1; pWaveHeader->dwBufferLength = len; pWaveHeader->lpData = new char[len]; if (!pWaveHeader->lpData) { delete pWaveHeader; return false; } memcpy(pWaveHeader->lpData, buffer, len); if (::waveOutPrepareHeader(m_hWaveOut, pWaveHeader, sizeof(WAVEHDR))) { delete pWaveHeader->lpData; delete pWaveHeader; return false; } if (::waveOutWrite(m_hWaveOut, pWaveHeader, sizeof(WAVEHDR))) { delete pWaveHeader->lpData; delete pWaveHeader; return false; } m_iBlockNum++; return true; }
三、回调函数
void CALLBACK WinAudioPlay::waveOutProc( HWAVEOUT hwo, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2 ) { WinAudioPlay* pThis=(WinAudioPlay*)dwInstance; if(WOM_DONE == uMsg) //播放完成 { while(NULL != pThis->m_lpWaveHdrFromCallbackProc) { boost::this_thread::interruptible_wait(1); } pThis->m_lpWaveHdrFromCallbackProc = (LPWAVEHDR)dwParam1; pThis->m_spbrTgthr->wait(); } return ; }
四、线程同步播放
void WinAudioPlay::ThrdWaveOutTogether() { while(!m_b_exit) { m_spbrTgthr->wait(); if (NULL != m_lpWaveHdrFromCallbackProc) { ::waveOutUnprepareHeader(m_hWaveOut, m_lpWaveHdrFromCallbackProc, sizeof(WAVEHDR)); delete[] m_lpWaveHdrFromCallbackProc->lpData; delete m_lpWaveHdrFromCallbackProc; m_lpWaveHdrFromCallbackProc = NULL; (m_iBlockNum > 0)?(m_iBlockNum--):(m_iBlockNum = 0); } } if (m_hWaveOut != NULL) { ::waveOutReset(m_hWaveOut); ::waveOutClose(m_hWaveOut); } }
五、关闭线程,释放资源
WinAudioPlay::~WinAudioPlay() { m_bPalyStata = false; while(0 != m_iBlockNum) { Sleep(1); } m_b_exit = true; m_spbrTgthr->wait(); m_sptrdWaveOutTgthr->join(); }
我相信聪明的你,借助msdn能够很快理解上面意思,我就不多打字了(水平有限,怕误人子弟~)
但是需要注意以下几点:
一、waveOutProc回调函数中绝对不能调用waveOut系列函数(可用线程同步实现通知在另一个线程调用)
二、调用waveOutReset函数时,函数执行完毕才返回,期间不可以调用hWaveOut
三、注意释放资源
and so on...
其实还有很多,我就不多写了,以上3点中前两点处理不好,会发生线程死锁。
当初我就在上面耗费了很长时间,后来多亏孙总点醒(其实msdn中有那么一句的。但是E语要加强啊。。。)
最后赠送一个免费的建议,听不听由你:不要太过于相信网络上的代码,win32平台最有说服力的还数msdn~
欢迎各位转载,但必须在文章页面中给出作者和原文链接!