waveOutPause 收藏
Windows通過高級音頻函數(shù)、媒體控制接口MCI設備驅(qū)動程序;低級音頻函數(shù)MIDI Mapper、低級音頻設備驅(qū)動;以及DirectSound提供了音頻服務,可以從聲卡獲取音頻流。
1. 播放聲音文件的其它方法
在介紹wavex系列之前,我先來介紹之外的其它幾種方法:
1.1 MCI方法簡介
用MCI方法是很方便的,它對媒體設備控制主要通過命令接口函數(shù)mciSendCommand()或者字符串接口函數(shù)mciSendString()來完成的,這兩個函數(shù)的作用相同。命令接口函數(shù)比命令字符串使用起來要復雜,但它為MCI提供了更為強大的控制能力,兩個接口函數(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中設置要播放的文件并發(fā)送MCI_OPEN命令打開聲音設備,發(fā)送MCI_PLAY命令消息播放,結(jié)束后發(fā)送MCI_STOP命令關閉設備。關于它們的具體使用方法可以參考MSDN。
1.2 PlaySound方法
BOOL sndPlaySound(LPCSTR lpszSound, UINT fuSound );
BOOL PlaySound(LPCSTR pszSound,HMODULE hmod, DWORD fdwSound);
其中參數(shù)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文件,第一種格式將播放系統(tǒng)默認的聲音,第二種格式不會播放系統(tǒng)默認的聲音[1],這是SND_NODEFAULT標志的作用。
當然我們也可以將聲音文件作為用戶自定義資源加入程序資源文件中,經(jīng)過編譯連接生成EXE文件,這樣就可以實現(xiàn)無.WAV文件的聲音播放。利用上面的函數(shù)也很簡單,如下,其中IDR_YOUR_WAVE是加入的wave文件資源標識符:
PlaySound(MAKEINTRESOURCE(IDR_YOUR_WAVE),GetModuleHandle(NULL), SND_RESOURCE);
2. 使用低級音頻函數(shù)WaveX
下面將進入文章的主題。
2.1 概述
低層音頻服務及重要的數(shù)據(jù)結(jié)構(gòu)低級音頻服務控制著不同的音頻設備,這些設備包括WAVE、MIDI和輔助音頻設備[2]。低級音頻服務包括如下內(nèi)容: (1)查詢音頻設備;(2)打開和關閉設備驅(qū)動程序;(3)分配和準備音頻數(shù)據(jù)塊;(4)管理音頻數(shù)據(jù)塊;(5)應用MMTIME結(jié)構(gòu);(6)處理錯誤。
2.2 重要消息及數(shù)據(jù)結(jié)構(gòu)
使用低級音頻函數(shù)之所以能夠?qū)Ω鱾€聲音數(shù)據(jù)塊操作,要歸功于Windows的消息映射,Windows在采集、播放完一個數(shù)據(jù)塊之后就會發(fā)送有關的消息。播放聲音涉及到的重要消息及觸發(fā)條件如下:
MM_WOM_CLOSE:在一個波形聲音輸出設備關閉時發(fā)出,之后該設備句柄不再有效
MM_WOM_DONE:當給定的輸出緩存播放完畢返回給應用程序,或者直接調(diào)用waveOutReset函數(shù)停止播放并重置管理器
MM_WOM_OPEN:當給定的波形聲音輸出設備被打開時
MOM_CLOSE:當MIDI輸出設備關閉時
WOM_DONE:當留緩沖播放完畢并正被返回程序時發(fā)到MIDI輸出回調(diào)函數(shù)
WOM_OPEN:當MIDI輸出設備打開時
重要的數(shù)據(jù)結(jié)構(gòu):
波形數(shù)據(jù)格式 WAVEFORMAT/WAVEFORMATEX
波形數(shù)據(jù)緩沖區(qū)格式 WAVEHDR
音頻輸出設備性能 WAVEOUTCAPS
這些內(nèi)容都定義在mmsystem.h頭文件中,更為具體的信息請參閱MSDN。
2.3 wavex播放聲音波形文件方法的大致流程
常用mmio函數(shù):
mmioOpen( ) 打開一個RIFF文件
mmioDescend ( ) 進入塊
mmioRead( ); 該取RIFF文件
mmioAscend ( ); 跳出塊
mmioClose( ); 關閉PIFF文件
對于塊來說,進入塊和跳出塊是配對的。
讀取WAV文件的讀取過程:
mmioOpen( ) 打開文件
↓
mmioDescend ("WAVE") 進入"fmt"塊
↓
mmioRead( ) 讀取WAVE文件格式信息
↓
mmioAscend ( ) 跳出"fmt"塊
↓
mmioDescend ("data") 進入"data"塊
↓
mmioRead( ) 讀取WAVE數(shù)據(jù)信息
↓
mmioClose( ) 關閉文件。
輸出WAV文件的過程:
WaveOutOpen () 打開一個輸出設備
↓
WaveOutPrepareHeader() 準備WAVE數(shù)據(jù)頭。
↓
WaveOutWrite() 將數(shù)據(jù)寫入設備并開始播放
↓
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)))
{
/*-------------------------------------------------------------------------------
信息顯示函數(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;
}
//進入塊,檢查打開文件是否是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 ´塊的大小,申請內(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;
}
//檢查音頻設備,返回音頻輸出設備的性能
if (waveOutGetDevCaps(WAVE_MAPPER, &pwoc, sizeof(WAVEOUTCAPS)) != 0)
{
ShowMsg("waveOutGetDevCaps() failed!");
OnStop();
return;
}
//檢查音頻輸出設備是否能播放指定的音頻文件
/*----------------------------------------------------------------------------------------------
waveOutOpen函數(shù)最后三個參數(shù)的設置對消息處理方式起決定性作用,需要特別注意,通常我們用下列處理方法:
1. 使用窗口作為消息的接收者,則第四個參數(shù)設置為該窗口的句柄,則和這次播放有關的消息都將進入該窗口的消息隊列,這時 第五個參數(shù)為NULL,第六個參數(shù)為CALLBACK_WINDOW,表明由窗口的過程來處理消息。
2. 直接使用回調(diào)函數(shù)來處理消息,則第四個參數(shù)設置為該回調(diào)函數(shù)的指針,則和這次播放有關的消息都將由該函數(shù)處理,這時第 五個參數(shù)為傳入該函數(shù)的參數(shù),第六個參數(shù)為CALLBACK_FUNCTION,表明由指定函數(shù)來處理消息。
3. 使用新的線程來處理消息,則第四個參數(shù)設置為該線程函數(shù)的指針,和這次播放有關的消息都將由該線程處理,這時第五個參 數(shù)為傳入該函數(shù)的參數(shù),第六個參數(shù)為CALLBACK_THREAD,表明由線程來處理消息。
4. 如果你不需要處理消息,這后面三個參數(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;
}
//準備待播放的數(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ù)寫入設備并開始播放
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));
//釋放內(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 相關頭文件
/*-----------------------------------------------------------------------------------------------------------------------
說明:本文介紹的操作函數(shù)的聲明包含在mmsystem.h頭文件中,因此在程序中必須用#include "mmsystem.h"語句加入頭文件。同時在編譯時要加入動態(tài)連接導入庫winmm.lib,具體實現(xiàn)方法有兩種:
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;//音頻數(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. 應用
低級音頻函數(shù)能夠具體的在內(nèi)存中對各個聲音數(shù)據(jù)塊進行細節(jié)控制,比如可以通過檢測聲音的振幅強度進行聲音采集的篩選,或者進行聲音文件的剪切合并等,這就為聲音文件的靈活操作提供了很好的方法;因為它能夠操作聲音數(shù)據(jù)塊,從而也能為聲音的實時傳輸提供有效的途徑。
參考文獻:
1. 李燦偉 VC++中播放聲音的方法
2. 李博軒 Visuanl C++ 6.0多媒體開發(fā)指南。北京:清華大學出版社,2000年2月.71-75
轉(zhuǎn)貼自:http