VC中使用低级音频函数WaveX播放声音文件
文章摘要:
本文討論并實(shí)現(xiàn)了在VC++中使用低級(jí)音頻函數(shù)WaveX播放聲音文件的方法。
---------------------------------------------------------------------------------------------------------------------
??? Windows通過高級(jí)音頻函數(shù)、媒體控制接口MCI設(shè)備驅(qū)動(dòng)程序;低級(jí)音頻函數(shù)MIDI Mapper、低級(jí)音頻設(shè)備驅(qū)動(dòng);以及DirectSound提供了音頻服務(wù),可以從聲卡獲取音頻流。
1. 播放聲音文件的其它方法
?? 在介紹wavex系列之前,我先來介紹之外的其它幾種方法:
1.1 MCI方法簡(jiǎn)介
???
??? 用MCI方法是很方便的,它對(duì)媒體設(shè)備控制主要通過命令接口函數(shù)mciSendCommand()或者字符串接口函數(shù)mciSendString()來完成的,這兩個(gè)函數(shù)的作用相同。命令接口函數(shù)比命令字符串使用起來要復(fù)雜,但它為MCI提供了更為強(qiáng)大的控制能力,兩個(gè)接口函數(shù)的原型:
MCIERROR mciSendCommand(MCIDEVICEID IDDevice,UINT uMsg,DWORD fdwCommand,DWORD dwParam);
MCIERROR mciSendString(LPCTSTR lpszCommand, LPTSTR lpszReturnString, UINT cchReturn, HANDLE hwndCallback);
比如要使用mciSendCommand方法,我們先在MCI_OPEN_PARMS中設(shè)置要播放的文件并發(fā)送MCI_OPEN命令打開聲音設(shè)備,發(fā)送MCI_PLAY命令消息播放,結(jié)束后發(fā)送MCI_STOP命令關(guān)閉設(shè)備。關(guān)于它們的具體使用方法可以參考MSDN。
1.2 PlaySound方法
???
??? BOOL sndPlaySound(LPCSTR lpszSound, UINT fuSound );
??? BOOL PlaySound(LPCSTR pszSound,HMODULE hmod, DWORD fdwSound);
??? 其中參數(shù)lpszSound是需要播放聲音的.WAV文件的路徑和文件名,hmod在這里為NULL,fuSound是播放聲音的標(biāo)志,詳細(xì)說明請(qǐng)參考VC++中的幫助。 例如播放C:/sound/music.wav可以用sndPlaySound ("c://sound//music.wav",SND_ASYNC);或PlaySound("c://sound//music.wav",NULL, SND_ASYNC|SND_NODEFAULT );如果沒有找到music.wav文件,第一種格式將播放系統(tǒng)默認(rèn)的聲音,第二種格式不會(huì)播放系統(tǒng)默認(rèn)的聲音[1],這是SND_NODEFAULT標(biāo)志的作用。
??? 當(dāng)然我們也可以將聲音文件作為用戶自定義資源加入程序資源文件中,經(jīng)過編譯連接生成EXE文件,這樣就可以實(shí)現(xiàn)無.WAV文件的聲音播放。利用上面的函數(shù)也很簡(jiǎn)單,如下,其中IDR_YOUR_WAVE是加入的wave文件資源標(biāo)識(shí)符:
??? PlaySound(MAKEINTRESOURCE(IDR_YOUR_WAVE),GetModuleHandle(NULL), SND_RESOURCE);
2. 使用低級(jí)音頻函數(shù)WaveX
???
??? 下面將進(jìn)入文章的主題。
2.1 概述
??? 低層音頻服務(wù)及重要的數(shù)據(jù)結(jié)構(gòu)低級(jí)音頻服務(wù)控制著不同的音頻設(shè)備,這些設(shè)備包括WAVE、MIDI和輔助音頻設(shè)備[2]。低級(jí)音頻服務(wù)包括如下內(nèi)容:(1)查詢音頻設(shè)備;(2)打開和關(guān)閉設(shè)備驅(qū)動(dòng)程序;(3)分配和準(zhǔn)備音頻數(shù)據(jù)塊;(4)管理音頻數(shù)據(jù)塊;(5)應(yīng)用MMTIME結(jié)構(gòu);(6)處理錯(cuò)誤。
???
2.2 重要消息及數(shù)據(jù)結(jié)構(gòu)
???
??? 使用低級(jí)音頻函數(shù)之所以能夠?qū)Ω鱾€(gè)聲音數(shù)據(jù)塊操作,要?dú)w功于Windows的消息映射,Windows在采集、播放完一個(gè)數(shù)據(jù)塊之后就會(huì)發(fā)送有關(guān)的消息。播放聲音涉及到的重要消息及觸發(fā)條件如下:
??? MM_WOM_CLOSE:在一個(gè)波形聲音輸出設(shè)備關(guān)閉時(shí)發(fā)出,之后該設(shè)備句柄不再有效
??? MM_WOM_DONE:當(dāng)給定的輸出緩存播放完畢返回給應(yīng)用程序,或者直接調(diào)用waveOutReset函數(shù)停止播放并重置管理器
??? MM_WOM_OPEN:當(dāng)給定的波形聲音輸出設(shè)備被打開時(shí)
???
??? MOM_CLOSE:當(dāng)MIDI輸出設(shè)備關(guān)閉時(shí)
??? WOM_DONE:當(dāng)留緩沖播放完畢并正被返回程序時(shí)發(fā)到MIDI輸出回調(diào)函數(shù)
??? WOM_OPEN:當(dāng)MIDI輸出設(shè)備打開時(shí)
??? 重要的數(shù)據(jù)結(jié)構(gòu):
??? 波形數(shù)據(jù)格式 WAVEFORMAT/WAVEFORMATEX
??? 波形數(shù)據(jù)緩沖區(qū)格式 WAVEHDR
??? 音頻輸出設(shè)備性能 WAVEOUTCAPS
???
??? 這些內(nèi)容都定義在mmsystem.h頭文件中,更為具體的信息請(qǐng)參閱MSDN。
???
2.3 wavex播放聲音波形文件方法的大致流程
??? 常用mmio函數(shù):
??? mmioOpen( ) 打開一個(gè)RIFF文件
??? mmioDescend ( ) 進(jìn)入塊
??? mmioRead( ); 該取RIFF文件
??? mmioAscend ( ); 跳出塊
??? mmioClose( ); 關(guān)閉PIFF文件
??? 對(duì)于塊來說,進(jìn)入塊和跳出塊是配對(duì)的。
??? 讀取WAV文件的讀取過程:
??? mmioOpen( ) 打開文件
??? ↓
??? mmioDescend ("WAVE") 進(jìn)入"fmt"塊
??? ↓
??? mmioRead( ) 讀取WAVE文件格式信息
??? ↓
??? mmioAscend ( ) 跳出"fmt"塊
??? ↓?
??? mmioDescend ("data") 進(jìn)入"data"塊
??? ↓
??? mmioRead( ) 讀取WAVE數(shù)據(jù)信息
??? ↓
??? mmioClose( ) 關(guān)閉文件。?
???
??? 輸出WAV文件的過程:
??? WaveOutOpen () 打開一個(gè)輸出設(shè)備
??? ↓
??? WaveOutPrepareHeader() 準(zhǔn)備WAVE數(shù)據(jù)頭。?
??? ↓?
??? WaveOutWrite() 將數(shù)據(jù)寫入設(shè)備并開始播放?
??? ↓?
??? WaveOutReset() 停止播放并重置管理器?
??? ↓?
??? WaveOutClose() 并閉播放設(shè)備?
??? ↓?
??? WaveOutUnpareHeader() 清理用WaveOutPrepareHeader準(zhǔn)備的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)))
?{
? /*-------------------------------------------------------------------------------
? 信息顯示函數(shù)ShowMsg():
? void CPlayWaveDlg::ShowMsg(char* szMsg, ...)
? {
?? va_list vl;
?? char szBuf[256];
?
?? va_start(vl, szMsg);
?? vsprintf(szBuf, szMsg, vl);
?? va_end(vl);
?
?? ::MessageBox(NULL, szBuf, "WavePlayer", MB_OK | MB_ICONEXCLAMATION);
? }
? ---------------------------------------------------------------------------------*/
? ShowMsg("Failed to open file: %s", szFileName);
? return;
?}
?//進(jìn)入塊,檢查打開文件是否是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 ''''塊的大小,申請(qǐng)內(nèi)存
?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;
?}
?//為音頻數(shù)據(jù)分配內(nèi)存
?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;
?}
?//檢查音頻設(shè)備,返回音頻輸出設(shè)備的性能
?if (waveOutGetDevCaps(WAVE_MAPPER, &pwoc, sizeof(WAVEOUTCAPS)) != 0)
?{
? ShowMsg("waveOutGetDevCaps() failed!");
? OnStop();
? return;
?}
?
?//檢查音頻輸出設(shè)備是否能播放指定的音頻文件
?/*----------------------------------------------------------------------------------------------
?waveOutOpen函數(shù)最后三個(gè)參數(shù)的設(shè)置對(duì)消息處理方式起決定性作用,需要特別注意,通常我們用下列處理方法:
?1. 使用窗口作為消息的接收者,則第四個(gè)參數(shù)設(shè)置為該窗口的句柄,則和這次播放有關(guān)的消息都將進(jìn)入該窗口的消息隊(duì)列,這時(shí)?????????? 第五個(gè)參數(shù)為NULL,第六個(gè)參數(shù)為CALLBACK_WINDOW,表明由窗口的過程來處理消息。
?2. 直接使用回調(diào)函數(shù)來處理消息,則第四個(gè)參數(shù)設(shè)置為該回調(diào)函數(shù)的指針,則和這次播放有關(guān)的消息都將由該函數(shù)處理,這時(shí)第?????????? 五個(gè)參數(shù)為傳入該函數(shù)的參數(shù),第六個(gè)參數(shù)為CALLBACK_FUNCTION,表明由指定函數(shù)來處理消息。
?3. 使用新的線程來處理消息,則第四個(gè)參數(shù)設(shè)置為該線程函數(shù)的指針,和這次播放有關(guān)的消息都將由該線程處理,這時(shí)第五個(gè)參?????????? 數(shù)為傳入該函數(shù)的參數(shù),第六個(gè)參數(shù)為CALLBACK_THREAD,表明由線程來處理消息。
?4. 如果你不需要處理消息,這后面三個(gè)參數(shù)分別為NULL,NULL,CALLBACK_NULL
?----------------------------------------------------------------------------------------------*/
?if (waveOutOpen(&hWaveOut, WAVE_MAPPER, lpFormat, (ULONG)m_hWnd, NULL, CALLBACK_WINDOW) !=
? 0)
?{
? ShowMsg("Open the wave out devices failed!");
? OnStop();
? return;
?}
?//準(zhǔn)備待播放的數(shù)據(jù)
?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();
?}
?//將數(shù)據(jù)寫入設(shè)備并開始播放
?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);
?//關(guān)閉播放設(shè)備
?waveOutClose(hWaveOut);
?//清理用WaveOutPrepareHeader準(zhǔn)備的Wave。
?waveOutUnprepareHeader(hWaveOut, &pWaveOutHdr, sizeof(WAVEHDR));
?//釋放內(nèi)存
?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 相關(guān)頭文件
/*-----------------------------------------------------------------------------------------------------------------------
說明:本文介紹的操作函數(shù)的聲明包含在mmsystem.h頭文件中,因此在程序中必須用#include "mmsystem.h"語句加入頭文件。同時(shí)在編譯時(shí)要加入動(dòng)態(tài)連接導(dǎo)入庫winmm.lib,具體實(shí)現(xiàn)方法有兩種:
1. 從Developer Studio的Project菜單中選擇Settings,然后在Link選項(xiàng)卡上的Object/Library Modules控制中加入winmm.lib
2. 如下所示在代碼中加入#pragma comment(lib, "winmm.lib")
-----------------------------------------------------------------------------------------------------------------------*/
#include "mmsystem.h"
#pragma comment(lib, "winmm.lib")
class CPlayWaveDlg : public CDialog
{
//省略與播放無關(guān)部分
//................
protected:
?HANDLE m_hData;
?HWAVEOUT hWaveOut;
?WAVEHDR pWaveOutHdr;
?HANDLE m_hFormat;
?HPSTR lpData;//音頻數(shù)據(jù)
?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. 應(yīng)用
???
??? 低級(jí)音頻函數(shù)能夠具體的在內(nèi)存中對(duì)各個(gè)聲音數(shù)據(jù)塊進(jìn)行細(xì)節(jié)控制,比如可以通過檢測(cè)聲音的振幅強(qiáng)度進(jìn)行聲音采集的篩選,或者進(jìn)行聲音文件的剪切合并等,這就為聲音文件的靈活操作提供了很好的方法;因?yàn)樗軌虿僮髀曇魯?shù)據(jù)塊,從而也能為聲音的實(shí)時(shí)傳輸提供有效的途徑。
???
參考文獻(xiàn):
1. 李燦偉 VC++中播放聲音的方法
2. 李博軒 Visuanl C++ 6.0多媒體開發(fā)指南。北京:清華大學(xué)出版社,2000年2月.71-75 ?
總結(jié)
以上是生活随笔為你收集整理的VC中使用低级音频函数WaveX播放声音文件的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql delete in 结果集_
- 下一篇: mysql 分组 字符串_MySQL查