VC中使用低級音頻函數WaveX播放聲音檔案
王結太 2004.08.08
---------------------------------------------------------------------------------------------------------------------
文章摘要:
本文讨論并實作了在VC++中使用低級音頻函數WaveX播放聲音檔案的方法。
---------------------------------------------------------------------------------------------------------------------
Windows通過進階音頻函數、媒體控制接口MCI裝置驅動程式;低級音頻函數MIDI Mapper、低級音頻裝置驅動;以及DirectSound提供了音頻服務,可以從聲霸卡擷取音頻流。
1. 播放聲音檔案的其它方法
在介紹wavex系列之前,我先來介紹之外的其它幾種方法:
1.1 MCI方法簡介
用MCI方法是很友善的,它對媒體裝置控制主要通過指令接口函數mciSendCommand()或者字元串接口函數mciSendString()來完成的,這兩個函數的作用相同。指令接口函數比指令字元串使用起來要複雜,但它為MCI提供了更為強大的控制能力,兩個接口函數的原型:
MCIERROR mciSendCommand(MCIDEVICEID IDDevice,UINT uMsg,DWORD fdwCommand,DWORD dwParam);
MCIERROR mciSendString(LPCTSTR lpszCommand, LPTSTR lpszReturnString, UINT cchReturn, HANDLE hwndCallback);
比如要使用mciSendCommand方法,我們先在MCI_OPEN_PARMS中設定要播放的檔案并發送MCI_OPEN指令打開聲音裝置,發送MCI_PLAY指令消息播放,結束後發送MCI_STOP指令關閉裝置。關于它們的具體使用方法可以參考MSDN。
1.2 PlaySound方法
BOOL sndPlaySound(LPCSTR lpszSound, UINT fuSound );
BOOL PlaySound(LPCSTR pszSound,HMODULE hmod, DWORD fdwSound);
其中參數lpszSound是需要播放聲音的.WAV檔案的路徑和檔案名,hmod在這裡為NULL,fuSound是播放聲音的标志,詳細說明請參考VC++中的幫助。 例如播放C:/sound/music.wav可以用sndPlaySound ("c://sound//music.wav",SND_ASYNC);或PlaySound("c://sound//music.wav",NULL, SND_ASYNC|SND_NODEFAULT );如果沒有找到music.wav檔案,第一種格式将播放系統預設的聲音,第二種格式不會播放系統預設的聲音[1],這是SND_NODEFAULT标志的作用。
當然我們也可以将聲音檔案作為使用者自定義資源加入程式資源檔案中,經過編譯連接配接生成EXE檔案,這樣就可以實作無.WAV檔案的聲音播放。利用上面的函數也很簡單,如下,其中IDR_YOUR_WAVE是加入的wave檔案資源辨別符:
PlaySound(MAKEINTRESOURCE(IDR_YOUR_WAVE),GetModuleHandle(NULL), SND_RESOURCE);
2. 使用低級音頻函數WaveX
下面将進入文章的主題。
2.1 概述
低層音頻服務及重要的資料結構低級音頻服務控制着不同的音頻裝置,這些裝置包括WAVE、MIDI和輔助音頻裝置[2]。低級音頻服務包括如下内容:(1)查詢音頻裝置;(2)打開和關閉裝置驅動程式;(3)配置設定和準備音頻資料塊;(4)管理音頻資料塊;(5)應用MMTIME結構;(6)處理錯誤。
2.2 重要消息及資料結構
使用低級音頻函數之是以能夠對各個聲音資料塊操作,要歸功于Windows的消息映射,Windows在采集、播放完一個資料塊之後就會發送有關的消息。播放聲音涉及到的重要消息及觸發條件如下:
MM_WOM_CLOSE:在一個波形聲音輸出裝置關閉時發出,之後該裝置句柄不再有效
MM_WOM_DONE:當給定的輸出緩存播放完畢傳回給應用程式,或者直接調用waveOutReset函數停止播放并重置管理器
MM_WOM_OPEN:當給定的波形聲音輸出裝置被打開時
MOM_CLOSE:當MIDI輸出裝置關閉時
WOM_DONE:當留緩沖播放完畢并正被傳回程式時發到MIDI輸出回調函數
WOM_OPEN:當MIDI輸出裝置打開時
重要的資料結構:
波形資料格式 WAVEFORMAT/WAVEFORMATEX
波形資料緩沖區格式 WAVEHDR
音頻輸出裝置性能 WAVEOUTCAPS
這些内容都定義在mmsystem.h頭檔案中,更為具體的資訊請參閱MSDN。
2.3 wavex播放聲音波形檔案方法的大緻流程
常用mmio函數:
mmioOpen( ) 打開一個RIFF檔案
mmioDescend ( ) 進入塊
mmioRead( ); 該取RIFF檔案
mmioAscend ( ); 跳出塊
mmioClose( ); 關閉PIFF檔案
對于塊來說,進入塊和跳出塊是配對的。
讀取WAV檔案的讀取過程:
mmioOpen( ) 打開檔案
↓
mmioDescend ("WAVE") 進入"fmt"塊
↓
mmioRead( ) 讀取WAVE檔案格式資訊
↓
mmioAscend ( ) 跳出"fmt"塊
↓
mmioDescend ("data") 進入"data"塊
↓
mmioRead( ) 讀取WAVE資料資訊
↓
mmioClose( ) 關閉檔案。
輸出WAV檔案的過程:
WaveOutOpen () 打開一個輸出裝置
↓
WaveOutPrepareHeader() 準備WAVE資料頭。
↓
WaveOutWrite() 将資料寫入裝置并開始播放
↓
WaveOutReset() 停止播放并重置管理器
↓
WaveOutClose() 并閉播放裝置
↓
WaveOutUnpareHeader() 清理用WaveOutPrepareHeader準備的Wave
2.4 主要程式清單
2.4.1 播放部分
void CPlayWaveDlg::OnPlay()
{
LPSTR szFileName;//聲音檔案名
LPSTR szPathName;
MMCKINFO mmckinfoParent;
MMCKINFO mmckinfoSubChunk;
DWORD dwFmtSize;
DWORD m_WaveLong;
WAVEFORMATEX* lpFormat;
DWORD m_dwDataOffset;
DWORD m_dwDataSize;
WAVEOUTCAPS pwoc;
LONG lSoundOffset;
LONG lSoundLong;
CEdit* pEdit = (CEdit*) GetDlgItem(IDC_FILE);
pEdit->GetWindowText(m_strFileName);
if (m_strFileName == "")
{
ShowMsg("Please select a wave file to play!");
return;
}
szPathName = m_strPathName.GetBuffer(0);
szFileName = m_strFileName.GetBuffer(0);
//打開波形檔案
if (!(m_hmmio = mmioOpen(szPathName, NULL, MMIO_READ | MMIO_ALLOCBUF)))
{
ShowMsg("Failed to open file: %s", szFileName);
return;
}
//進入塊,檢查打開檔案是否是wave檔案
mmckinfoParent.fccType = mmioFOURCC('W', 'A', 'V', 'E');
if (mmioDescend(m_hmmio, (LPMMCKINFO) & mmckinfoParent, NULL,
MMIO_FINDRIFF))
{
ShowMsg("%s is an invalid wave file!", szFileName);
mmioClose(m_hmmio, NULL);
return;
}
//尋找 'fmt' 塊
mmckinfoSubChunk.ckid = mmioFOURCC('f', 'm', 't', ' ');
if (mmioDescend(m_hmmio, &mmckinfoSubChunk, &mmckinfoParent,
MMIO_FINDCHUNK))
{
ShowMsg("Cannot find fmt chunk in %s!", szFileName);
mmioClose(m_hmmio, NULL);
return;
}
//獲得 'fmt '塊的大小,申請記憶體
dwFmtSize = mmckinfoSubChunk.cksize ;
m_hFormat = LocalAlloc(LMEM_MOVEABLE, LOWORD(dwFmtSize));
if (!m_hFormat)
{
ShowMsg("Alloc memory failed!");
return;
}
lpFormat = (WAVEFORMATEX *) LocalLock(m_hFormat);
if (!lpFormat)
{
ShowMsg("Lock memory failed!");
OnStop();
return;
}
if ((unsigned long) mmioRead(m_hmmio, (HPSTR) lpFormat, dwFmtSize) !=
dwFmtSize)
{
ShowMsg("Read format chunk of %s failed!", szFileName);
OnStop();
return;
}
//離開 fmt 塊
mmioAscend(m_hmmio, &mmckinfoSubChunk, 0);
//尋找 'data' 塊
mmckinfoSubChunk.ckid = mmioFOURCC('d', 'a', 't', 'a');
if (mmioDescend(m_hmmio, &mmckinfoSubChunk, &mmckinfoParent,
MMIO_FINDCHUNK))
{
ShowMsg("Cannot find data chunk in: %s", szFileName);
OnStop();
return;
}
//獲得 'data'塊的大小
m_dwDataSize = mmckinfoSubChunk.cksize ;
m_dwDataOffset = mmckinfoSubChunk.dwDataOffset ;
if (m_dwDataSize == 0L)
{
ShowMsg("%s has no data!", szFileName);
OnStop();
return;
}
//為音頻資料配置設定記憶體
lpData = new char[m_dwDataSize];
if (!lpData)
{
ShowMsg("Alloc memory for wave data failed!");
OnStop();
return;
}
lSoundOffset = m_dwDataOffset;
LONG lSize = mmioSeek(m_hmmio, lSoundOffset, SEEK_SET);
if (lSize < 0)
{
ShowMsg("Seek data chunk of %s failed!", szFileName);
OnStop();
return;
}
lSoundLong = m_dwDataSize;
m_WaveLong = mmioRead(m_hmmio, lpData, lSoundLong);
if (m_WaveLong < 0)
{
ShowMsg("Read data chunk of %s failed!", szFileName);
OnStop();
return;
}
//檢查音頻裝置,傳回音頻輸出裝置的性能
if (waveOutGetDevCaps(WAVE_MAPPER, &pwoc, sizeof(WAVEOUTCAPS)) != 0)
{
ShowMsg("waveOutGetDevCaps() failed!");
OnStop();
return;
}
//檢查音頻輸出裝置是否能播放指定的音頻檔案
if (waveOutOpen(&hWaveOut, WAVE_MAPPER, lpFormat, (ULONG)m_hWnd, NULL, CALLBACK_WINDOW) !=
0)
{
ShowMsg("Open the wave out devices failed!");
OnStop();
return;
}
//準備待播放的資料
pWaveOutHdr.lpData = (HPSTR) lpData;
pWaveOutHdr.dwBufferLength = m_WaveLong;
pWaveOutHdr.dwFlags = 0;
pWaveOutHdr.dwLoops = 5;
if (waveOutPrepareHeader(hWaveOut, &pWaveOutHdr, sizeof(WAVEHDR)) != 0)
{
ShowMsg("Failed to prepare the wave data buffer!");
OnStop();
}
//将資料寫入裝置并開始播放
if (waveOutWrite(hWaveOut, &pWaveOutHdr, sizeof(WAVEHDR)) != 0)
{
ShowMsg("Failed to write the wave data buffer");
OnStop();
}
}
2.4.2 停止播放部分
void CPlayWaveDlg::OnStop()
{
if (m_hmmio != NULL)
{
mmioClose(m_hmmio, NULL);
}
//停止播放并重置管理器
waveOutReset(hWaveOut);
//關閉播放裝置
waveOutClose(hWaveOut);
//清理用WaveOutPrepareHeader準備的Wave。
waveOutUnprepareHeader(hWaveOut, &pWaveOutHdr, sizeof(WAVEHDR));
//釋放記憶體
if (m_hFormat != NULL)
{
LocalUnlock(m_hFormat);
m_hFormat = NULL;
}
if (m_hFormat != NULL)
{
LocalFree(m_hFormat);
m_hFormat = NULL;
}
if (lpData != NULL)
{
delete[] lpData;
lpData = NULL;
}
}
2.4.3 處理消息部分:
添加消息映射:ON_MESSAGE(MM_WOM_DONE,OnMMWomDone)
void CPlayWaveDlg::OnMMWomDone(UINT wParam, LONG lParam)
{
// ShowMsg("Play finished!");
OnStop();
}
2.4.4 相關頭檔案
#include "mmsystem.h"
#pragma comment(lib, "winmm.lib")
class CPlayWaveDlg : public CDialog
{
//省略與播放無關部分
//................
protected:
HANDLE m_hData;
HWAVEOUT hWaveOut;
WAVEHDR pWaveOutHdr;
HANDLE m_hFormat;
HPSTR lpData;//音頻資料
HMMIO m_hmmio;//音頻檔案句柄
CString m_strPathName;
CString m_strFileName;
void ShowMsg(char *szMsg, ...);
afx_msg void OnPlay();
afx_msg void OnStop();
afx_msg void OnMMWomDone(UINT wParam, LONG lParam);
DECLARE_MESSAGE_MAP()
};
以上代碼在Visual C++ 6.0 + windows 2000 pro 上通過。
3. 應用
低級音頻函數能夠具體的在記憶體中對各個聲音資料塊進行細節控制,比如可以通過檢測聲音的振幅強度進行聲音采集的篩選,或者進行聲音檔案的剪切合并等,這就為聲音檔案的靈活操作提供了很好的方法;因為它能夠操作聲音資料塊,進而也能為聲音的實時傳輸提供有效的途徑。
參考文獻:
1. 李燦偉 VC++中播放聲音的方法
2. 李博軒 Visuanl C++ 6.0多媒體開發指南。北京:清華大學出版社,2000年2月.71-75