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~

原文地址:https://www.cnblogs.com/wuyaSama/p/1889623.html