多線程應用---使用WaveOut* API開發AMR音頻播放器(含源碼下載)

[源代碼以及工程實例下載 ]

1、 語音播放API

1.1 waveOutOpen - 打開播放設備

MMRESULT waveOutOpen( 
    LPHWAVEOUT      phwo,               /* 一個指向接收波形音頻輸出設備的句柄 */ 
    UINT_PTR        uDeviceID,          /* 將要被打開的波形音頻輸出設備的ID */ 
    LPWAVEFORMATEX  pwfx,               /* 一個指向將被送到設備的音頻數據格式的WAVEFORMATEX結構的指針 */ 
    DWORD_PTR       dwCallback,         /* 它指向一個特定的CALLBACK函數,事件柄,窗口柄... */
    DWORD_PTR       dwCallbackInstance, /* 傳遞到CALLBACK進程的用戶實例數據,如是窗口,該參數設爲0 */ 
    DWORD           fdwOpen             /* 用來打開設備的標識(FLAGS) */
    );

    1)phwo:一個指向接收波形音頻輸出設備的句柄。用句柄來區別(identify)別的波形輸出設備。如果fdwOpen被設定爲 WAVE_FORMAT_QUERY,那麼這個參數可能爲NULL 。
    2)uDevideID:將要被打開的波形音頻輸出設備的ID ,它可以是一個設備ID,也可以是一個已經打開的波形音頻輸入設備句柄,你可以用以下的值來貨替:
WAVE_MAPPER - 該函數選一個能夠播放給定格式的波形音頻輸出設備
    3)pwfx:一個指向將被送到設備的音頻數據格式的WAVEFORMATEX結構的指針,當調用waveOutOpen 函數之後可立即釋放該結構;
    4)dwCallback:它指向一個特定的CALLBACK函數,事件柄,窗口柄,或一個線程ID(用於在音頻回放時以便處理與回放進度相關的消息)。如果無須CALLBACK函數,可以將其設爲0 。
    5)dwCallbackInstance :傳遞到CALLBACK進程的用戶實例數據。如果是窗口CALLBACK進程的話,該參數不用(設爲0)
    6)fwOpen:用來打開設備的標識(FLAGS),它們的定義如下:

含義
CALLBACK_EVENT dwCallback 參數欄是事件句柄
CALLBACK_FUNCTION dwCallback 參數欄是CALLBACK函數地址
CALLBACK_NULL 默認的設置,即無CALLBACK進程
CALLBACK_THREAD dwCallback 參數欄是線程ID
CALLBACK_WINDOW dwCallback 參數欄是窗口句柄
WAVE_ALLOWSYNC 如果該項被設置,一個同步的波形音頻設備能被打開。如果在打開一個同步驅動時沒有用該項,設備打開將會失敗。
WAVE_FORMAT_DIRECT 如果設定該項,音頻設備不會對輸出的音頻數據執行轉換
WAVE_FORMAT_QUERY 如果設定該項,waveOutOpen 用於詢問設備是否支持給定的格式,但設備實際上並沒有被打開。此時phwo參數可爲NULL
WAVE_MAPPED 該項被設定後uDeviceID參數表示將通過波形映射器映射到一個波形格式音頻設備。

    7)返回值:成功後返回MMSYSERR_NOERROR ,否則返回以下值:

描述
MMSYSERR_ALLOCATED 表示資源已存在
MMSYSERR_BADDEVICEID 設備ID超出範圍
MMSYSERR_NODRIVER 沒有驅動
MMSYSERR_NOMEM 不能分配內存
WAVERR_BADFORMAT 企圖打開一個不被支持的格式
WAVERR_SYNC 設備是可同步的,但waveOutOpen沒用有WAVE_ALLOWSYNC設置。

注:
    waveoutopen創建設備實例句柄用於,使用其他waveoutAPI時將之作爲參數,用於區別不同的音頻流。
    可用waveOutGetNumDevs函數測定在當前系統中輸出設備的數目。
    如果uDeviceID參數項是一個ID,它將會表示從0 到總的數目,WAAVE_MAPPER常量也可以用作裝置ID。
    pwfc所指的結構能夠擴展到包含某些數據格式的特殊信息,例如,對於PCM數據,一個額外的UNIT類型用來表示取樣的位數目。在這個情況下用PCMWAVEFORAMT結構。對於其它的格式,用WAVEFORMATEX結構來表示額外的數據長度。
    如果你選用的是一個窗口或線程來接收CALLBACK信息,如下的信息將會被送到窗口處理函數中,來表明波形音頻輸出進程:MM_WOM_OPEN,MM_WOM_CLOSE ,和MM_WOM_DONE,如果你選用的是一個函數來接收CALLBACK信息,如下的信息將會被傳到函數中,來顯示波形音頻輸出進程:WOM_OPEN ,WOM_CLOSE,WOM_DONE。

1.2 waveOutPrepareHeader - 準備數據塊

MMRESULT waveOutPrepareHeader(
    HWAVEOUT hwo,  /* 波形音頻輸出設備的句柄 */
    LPWAVEHDR pwh, /* 一個WAVEHDR結構的指針,其基址必須與樣本大小對齊 */
    UINT cbwh      /* WAVEHDR結構的大小,單位:字節 */
    );
備註:
   調用此函數之前必須設置WAVEHDR結構的lpData,dwBufferLength,dwFlags成員,dwFlags成員必須設置爲0。
   一旦準備完成,不可以修改lpData指針。
   嘗試準備一個處於準備狀態的數據塊,將無效果,返回0
   不應該在同一時刻爲不同的波形設備準備同一個數據,如果想從一個設備錄製並在另一個設備播放數據,但不想拷貝緩存塊,可以分配兩塊WAVEHDR並將lpData指向同一數據緩存地址,其中一個調用waveInPrepareHeader,另一個調用waveOutPrepareHeader

1.3 waveOutWrite - 將音頻數據塊送到指定的音頻輸出設備

MMRESULT waveOutWrite(
    HWAVEOUT hwo,    /* 音頻輸出設備句柄 */   
    LPWAVEHDR pwh,  /* 包含音頻數據塊信息的WAVEHDR結構指針 */ 
    UINT cbwh      /* WAVEHDR結構大小,單位byte */
    ); 

注意:
    要播放的數據一般在聲音文件裏面獲得,並填入這個結構。由於是直接播放數據。所以要播放多少數據可以通過修改這個結構來達到目的。
    播放完數據後 WHDR_DONE 會設置到pwh指向的結構體中的dwFlags 成員
    在調用本函數之前必須調用waveOutPrepareHeader函數
    除非是恢復被waveOutPause函數暫停的設備,回調函數會在第一個數據塊一送達設備的時候就開始運作.回調函數在waveOutOpen裏面設置
    請不要在回調函數裏面調用任何的waveOut系列的函數,否則一定會造成死鎖。哪怕是waveOutUnprepareHeader,waveOutClose

1.4 waveOutUnprepareHeader - 清除音頻緩存數據

MMRESULT waveOutUnprepareHeader(
  HWAVEOUT hwo, 
  LPWAVEHDR pwh, 
  UINT cbwh 
);

注意:
    調用時機,送到的音頻輸出設備的數據已經播放完成即:pwh的dwFlags中WHDR_DONE位有效

1.5 waveOutPause - 暫停音頻播放

1.6 waveOutRestart - 繼續播放已暫停的音頻設備

1.7 waveOutReset - 重設音頻輸出設備,將已準備好的緩存塊均設置爲已播放完成

1.8 waveOutClose - 關閉音頻輸出設備

2、解碼器開發

class AMRFileDecoder
{
public:
    AMRFileDecoder(void);
    AMRFileDecoder(LPCTSTR lpszFile);
    virtual ~AMRFileDecoder(void);

private: // 屏蔽拷貝構造函數和賦值運算
    AMRFileDecoder(const AMRFileDecoder& )
    {
        ATLASSERT(FALSE);
    }
    AMRFileDecoder& operator=(const AMRFileDecoder&)
    {
        ATLASSERT(FALSE);
        return *this;
    }

public:
    /// 設置需解碼文件路徑
    virtual void SetFilePathName(LPCTSTR lpszFile);
    /// 獲取總時間長度,單位ms
    virtual ULONGLONG GetTimeLength();
    /// 獲取解碼後的波形格式
    virtual WAVEFORMATEX GetWaveFromatX();
    /// 開始解碼,初始化解碼器
    virtual BOOL BeginDecode();
    /// 解碼,每解碼一幀,遊標後移至下一幀,返回解碼後的幀大小,輸出解碼後的波形數據
    virtual DWORD Decode(LPSTR& pData);
    /// 判斷是否解碼結束
    virtual bool IsEOF();
    /// 結束解碼,銷燬解碼器
    virtual void EndDecode();
    /// 判斷解碼器是否正常
    virtual bool IsVaild();
    /// 獲取解碼後的波形數據大小,單位byte
    virtual DWORD GetDecodedMaxSize();
    /// 獲取解碼後的波形數據幀大小,單位byte
    virtual DWORD GetDecodedFrameMaxSize();

private:
    DWORD GetFrameCount();

private:
    LPSTR       m_pBaseAddress;         // 文件映射內存塊首地址
    LONGLONG    m_liFileSize;           // 文件映射內存塊大小
    ULONG       m_dwFrameCount;         // 幀總數
    LPSTR       m_pCurAddress;          // 解碼遊標,指示當前解碼位置
    LPVOID      m_pvDecoderState;       // 解碼器狀態指針
    CString     m_sFilePathName;        // 解碼文件路徑
};

3、解碼線程

if(m_pDecoder == NULL || !m_pDecoder->IsVaild())
        return 0;

    // 開始解碼,初始化解碼器
    if(!m_pDecoder->BeginDecode())
    {
        return 0;
    }

    // 申請臨時內存塊,存儲解碼後的波形數據
    DWORD dwFrameMaxSize = m_pDecoder->GetDecodedFrameMaxSize();
    LPSTR pBufferBase = (LPSTR)malloc(dwFrameMaxSize);
    ATLASSERT(pBufferBase);
    memset(pBufferBase, 0, dwFrameMaxSize);
    if(pBufferBase == NULL) return 0;

    register ThreadMsg tmsg = TMSG_ALIVE;

    DWORD dwSizeAmount = 0;
    while(!m_pDecoder->IsEOF() && tmsg)
    {
        // 解碼,每幀
        DWORD dwSize = m_pDecoder->Decode(pBufferBase);
        dwSizeAmount += dwSize;

        // 將解碼後數據寫入緩存區,供播放線程使用
        EnterCriticalSection(&m_cs);           
        memcpy(m_waveData.pData + m_waveData.dwSize, pBufferBase, dwSize);
        m_waveData.dwSize += dwSize;
        LeaveCriticalSection(&m_cs);

        // 當解碼數據量操作了一個播放緩存時,發個信號,通知可以開始播放了
        if(dwSizeAmount > BLOCK_SIZE)
        {
            dwSizeAmount = 0;
            SetEvent(m_hEventDecode);
        }

        // 節省CPU時間,讓CPU有時間去幹別的事兒
        Sleep(1);

        // 檢測線程是否將被強制退出
        EnterCriticalSection(&m_cs);
        tmsg = m_msgDecodeThread;
        LeaveCriticalSection(&m_cs);
    }

    // 如果數據量很小,根本不足一個播放緩存,也要發個信號
    if(dwSizeAmount > 0)
    {
        SetEvent(m_hEventDecode);
    }

    m_waveData.bDecodeFinished = true;

    // 解碼結束
    m_pDecoder->EndDecode();

    free(pBufferBase);
    pBufferBase = NULL;

    return 0;
}

4、播放線程

unsigned int WavePlayer::PlayThreadProcImpl()
{
    /// 定義爲寄存器變量,因爲它將會被高頻率的使用,用於編譯器優化
    register    ThreadMsg       tmsg  = TMSG_ALIVE;         

    /// 線程循環
    while( tmsg )
    {
        // 每次循環後,交出CPU控制權,放在此處,因爲下面有continue語句
        Sleep(10);

        /// 首先檢查線程消息
        EnterCriticalSection( &m_cs );
        tmsg = m_msgPlayThread;
        LeaveCriticalSection( &m_cs );
        // 線程要結束,退出線程循環
        if(!tmsg)   break;

        // 如果設備爲空,表示還沒有打開設備,需要打開設備
        if(m_hWaveoutDev == NULL)
        {
            EnterCriticalSection(&m_cs);
            MMRESULT mmres = waveOutOpen(&m_hWaveoutDev, WAVE_MAPPER, &m_waveData.wfmtx, (DWORD_PTR)WaveOutProc, (DWORD_PTR)this, CALLBACK_FUNCTION);
            LeaveCriticalSection(&m_cs);
            if(mmres != MMSYSERR_NOERROR)
            {
                // failed, try again.
                continue;
            }
        }
        
        // 檢查空閒緩存塊
        EnterCriticalSection( &m_cs );
        int free = m_wBlock.wfreeblock;
        LeaveCriticalSection( &m_cs );

        // 如果沒有空閒的緩存了,等待...
        if(free < BP_TURN)
        {
            continue;
        }

        /////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////
        ///                       < 播放主循環 >                              ///
        /////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////

        WAVEHDR     *current = NULL;

        /// BP_TURN爲每次寫入播放隊列的塊數
        for( unsigned int m = 0; m < BP_TURN; m++ )
        {   
            /// 當前空閒播放緩存塊
            current = &m_wBlock.pWaveHdr[m_wBlock.wcurrblock]; 

            // 首先需要檢查有沒有被Unprepare掉
            if( current->dwFlags & WHDR_PREPARED )
            {
                waveOutUnprepareHeader( m_hWaveoutDev, current, sizeof(WAVEHDR) );
            }

            /// 計算剩餘需要播放的數據
            EnterCriticalSection(&m_cs);
            unsigned long left  = m_waveData.dwSize - m_wBlock.wpos;
            unsigned int bDecodeFinished = m_waveData.bDecodeFinished;
            LeaveCriticalSection(&m_cs);
            unsigned long chunk = 0;

            if( left  >= BLOCK_SIZE )
            {
                chunk  = BLOCK_SIZE;
            }
            else if(!bDecodeFinished)
            {
                // 如果解碼還沒有結束,現有的數據量有不足以填滿一個緩存塊,先不寫入緩存
                break;
            }
            else if( left && left < BLOCK_SIZE)
            {
                chunk  = left;
            }
            else
            {   
                //////////////////////////////////////////////////////////////////////
                ///                 < 播放完成>                                    ///
                //////////////////////////////////////////////////////////////////////

                /// 獲取空閒緩存塊數量
                EnterCriticalSection( &m_cs );
                int free = m_wBlock.wfreeblock;
                LeaveCriticalSection( &m_cs );

                /// 當所有的緩存塊都播放完了,才意味着播放真正完成
                if( free == BLOCK_COUNT )
                {
                    /// Unprepare緩存塊
                    for( int j = 0; j < m_wBlock.wfreeblock; j++) 
                    {
                        if( m_wBlock.pWaveHdr[j].dwFlags & WHDR_PREPARED )
                        {
                            waveOutUnprepareHeader(m_hWaveoutDev, &m_wBlock.pWaveHdr[j], sizeof(WAVEHDR));
                        }
                    }

                    // 此時,纔算真正的播放完成,關閉線程
                    tmsg = TMSG_CLOSE;
                    // 處理播放完成事件
                    OnPlayFinished();
                }

                // 此break僅跳出該循環,沒有跳出線程循環
                break;
            }

            /// prepare current wave data block header
            EnterCriticalSection(&m_cs);
            memcpy( current->lpData, &m_waveData.pData[m_wBlock.wpos], chunk );
            LeaveCriticalSection(&m_cs);

            current->dwBufferLength  = chunk;   // sizeof block
            m_wBlock.wpos           += chunk;   // update position

            /// prepare for playback
            waveOutPrepareHeader( m_hWaveoutDev, current, sizeof(WAVEHDR) );

            /// push to the queue
            waveOutWrite(m_hWaveoutDev, current, sizeof(WAVEHDR));

            /// 減小空閒塊計數
            EnterCriticalSection( &m_cs );
            m_wBlock.wfreeblock--;
            LeaveCriticalSection( &m_cs );

            /// 使當前空閒塊指向下一個
            m_wBlock.wcurrblock++;
            m_wBlock.wcurrblock %= BLOCK_COUNT;
        }

    }/// thread

    ///////////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////////
    ///
    ///            < force to close device which are still playing > 
    ///////////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////////
    if(m_hWaveoutDev)
    {
        waveOutReset( m_hWaveoutDev );

        /// unprepare any blocks that are still prepared
        for( int j = 0; j < BLOCK_COUNT; j++) 
        {
            if( m_wBlock.pWaveHdr[j].dwFlags & WHDR_PREPARED )
            {
                waveOutUnprepareHeader(m_hWaveoutDev, &m_wBlock.pWaveHdr[j], sizeof(WAVEHDR));
            }
        }

        waveOutClose(m_hWaveoutDev);
        m_hWaveoutDev = NULL;
    }

    return THREAD_EXIT;
}

5、主播放線程

// 如果已經有播放的了,先停止
    if(m_ePlayStat != Play_Stop)
    {
        Stop();
    }

    // 設置解碼器
    if(m_pDecoder == NULL)
    {
        m_pDecoder = new AMRFileDecoder(lpszFile);
    }
    else
    {
        m_pDecoder->SetFilePathName(lpszFile);
    }

    // 取播放時間
    if(pLength)
    {
        *pLength = (DWORD)m_pDecoder->GetTimeLength();
    }

    // 申請解碼後的數據堆內存塊
    DWORD dwWaveMaxSize = m_pDecoder->GetDecodedMaxSize();
    EnterCriticalSection(&m_cs);
    m_waveData.wfmtx = m_pDecoder->GetWaveFromatX();
    m_waveData.pData = (LPSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwWaveMaxSize);
    LeaveCriticalSection(&m_cs);

    // 設置回調函數

    // 創建解碼線程
    if(m_hThreadDecode == NULL)
    {
        m_msgDecodeThread = TMSG_ALIVE;
        m_hThreadDecode = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)DecodeThread, (LPVOID)this, CREATE_SUSPENDED, NULL);
        ATLASSERT(m_hThreadDecode);
        ResumeThread(m_hThreadDecode);
    }

    // 等待解碼緩存信號
    WaitForSingleObject(m_hEventDecode, INFINITE);

    // 創建播放線程
    if(m_hThreadPlay == NULL)
    {
        m_msgPlayThread = TMSG_ALIVE;
        m_hThreadPlay = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)PlayThread, (LPVOID)this, CREATE_SUSPENDED, NULL );
        ATLASSERT(m_hThreadPlay);
        ResumeThread(m_hThreadPlay);
    }

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