[源代碼以及工程實例下載 ]
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;