VC中使用低级音频函数WaveX播放声音文件
文章摘要:
本文討論并實現了在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():
? 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;
?}
?//進入塊,檢查打開文件是否是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;
?}
?
?//檢查音頻輸出設備是否能播放指定的音頻文件
?/*----------------------------------------------------------------------------------------------
?waveOutOpen函數最后三個參數的設置對消息處理方式起決定性作用,需要特別注意,通常我們用下列處理方法:
?1. 使用窗口作為消息的接收者,則第四個參數設置為該窗口的句柄,則和這次播放有關的消息都將進入該窗口的消息隊列,這時?????????? 第五個參數為NULL,第六個參數為CALLBACK_WINDOW,表明由窗口的過程來處理消息。
?2. 直接使用回調函數來處理消息,則第四個參數設置為該回調函數的指針,則和這次播放有關的消息都將由該函數處理,這時第?????????? 五個參數為傳入該函數的參數,第六個參數為CALLBACK_FUNCTION,表明由指定函數來處理消息。
?3. 使用新的線程來處理消息,則第四個參數設置為該線程函數的指針,和這次播放有關的消息都將由該線程處理,這時第五個參?????????? 數為傳入該函數的參數,第六個參數為CALLBACK_THREAD,表明由線程來處理消息。
?4. 如果你不需要處理消息,這后面三個參數分別為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;
?}
?//準備待播放的數據
?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 相關頭文件
/*-----------------------------------------------------------------------------------------------------------------------
說明:本文介紹的操作函數的聲明包含在mmsystem.h頭文件中,因此在程序中必須用#include "mmsystem.h"語句加入頭文件。同時在編譯時要加入動態連接導入庫winmm.lib,具體實現方法有兩種:
1. 從Developer Studio的Project菜單中選擇Settings,然后在Link選項卡上的Object/Library Modules控制中加入winmm.lib
2. 如下所示在代碼中加入#pragma comment(lib, "winmm.lib")
-----------------------------------------------------------------------------------------------------------------------*/
#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 ?
總結
以上是生活随笔為你收集整理的VC中使用低级音频函数WaveX播放声音文件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql delete in 结果集_
- 下一篇: mysql 分组 字符串_MySQL查