1. 程式人生 > >MFC 聲音的播放和錄音的實現(一)

MFC 聲音的播放和錄音的實現(一)

程式設計中經常會又到播放聲音的操作,現總結一下在MFC中關於聲音有關的問題。這一篇介紹一下播放聲音的操作。下一篇介紹一下錄音操作的實現。

(一)呼叫Windows API函式實現聲音的播放

VC++ 中的多媒體動態連線庫中提供了一組與音訊裝置有關的函式。利用這些函式可以方便地播放聲音。最簡單的播放聲音方法就是在需要播放聲音的地方直接呼叫WindowsAPI提供的兩個聲音播放函式:PlaySound和sndPlaySound,現在對這個兩個函式的用法分別做一個小結:

1.PlaySound函式用法

PlaySound函式的宣告為:
BOOL PlaySound(LPCSTR pszSound, HMODULE hmod,DWORD fdwSound);

引數說明:

1) 引數pszSound是指定了要播放聲音的字串,該引數可以是WAVE檔案的名字,或是WAV資源的名字,或是記憶體中聲音資料的指標,或是在系統登錄檔WIN.INI中定義的系統事件聲音。如果該引數為NULL則停止正在播放的聲音。

2) 引數hmod是應用程式的例項控制代碼,當播放WAV資源時要用到該引數,否則它必須為NULL

3) 引數fdwSound是標誌的組合,如表1-1所示。若成功則函式返回TRUE,否則返回FALSE

播放標誌

含義

SND_APPLICATION

用應用程式指定的關聯來播放聲音

SND_ALIAS

pszSound引數指定了登錄檔或

WIN.INI中的系統事件的別名

SND_ALIAS_ID

pszSound引數指定了預定義的聲音識別符號

SND_ASYNC

用非同步方式播放聲音,PlaySound函式在開始播放後立即返回

SND_FILENAME

pszSound引數指定了WAVE檔名

SND_LOOP

重複播放聲音,必須與SND_ASYNC標誌一塊使用

SND_MEMORY

播放載入到記憶體中的聲音,此時pszSound是指向聲音資料的指標

SND_NODEFAULT

不播放預設聲音,若無此標誌,則PlaySound在沒找到聲音時會播放預設聲音

SND_NOSTOP

PlaySound不打斷原來的聲音播出並立即返回FALSE

SND_NOWAIT

如果驅動程式正忙則函式就不播放聲音並立即返回

SND_PURGE

停止所有與呼叫任務有關的聲音。若引數pszSoundNULL,就停止所有的聲音,否則,停止pszSound指定的聲音

SND_RESOURCE

pszSound引數是WAVE資源的識別符號,這時要用到hmod引數

SND_SYNC

同步播放聲音,在播放完後PlaySound函式才返回

1-1播放標誌的含義

4) 舉例:

1.4.1.直接播放本地存放的.wav格式的檔案為例。播放系統開機的聲音,示例程式碼:

PlaySound(_T("C:\\WINDOWS\\Media\\Windows XP 啟動.wav"), NULLSND_FILENAME | SND_ASYNC);

注意:這裡一定要注意需要新增標頭檔案的支援,在對應的原始檔中新增標頭檔案的引用,函式sndPlaySound也必須新增,如下

//聲音相關

#include <windows.h>

#include <mmsystem.h>

#pragma comment(lib"WINMM.LIB")

1.4.2把聲音檔案加入到資源中,然後從資源中播放聲音

VC++的程式設計中,可以利用各種標準的資源,如點陣圖,選單,對話方塊等。同時VC++也允許使用者自定義資源,因此我們可以將聲音檔案作為使用者自定義資源加入程式資原始檔中,經過編譯連線生成EXE檔案,實現無.WAV檔案的聲音播放。
  要實現作為資源的聲音檔案的播放,首先要在資源管理器中加入待播放的聲音檔案。前面介紹的掃雷程式中的爆炸聲和點選聲音都是通過這種方式實現的。具體步驟:開啟資源檢視介面,右鍵新增資源,初始介面中沒有wav格式的選項(VS2008),選擇 自定義 的選項,資源型別輸入WAV,然後資源檢視中添加了WAV的選項,右鍵點選它,新增資源,選擇匯入,在資源型別中選中Wave 檔案(*.wav,選中本地存放的wave檔案,開啟,然後再屬性中修改它的ID。最後在需要播放這個聲音的地方呼叫這個函式(以掃雷爆炸聲為例)

在滑鼠左鍵點選到了有雷的方格後呼叫

PlaySound(MAKEINTRESOURCE(IDR_BOMB),AfxGetResourceHandle(), SND_ASYNC|SND_RESOURCE|SND_NODEFAULT);

IDR_BOMB為新增的爆炸聲的ID

關於這個函式在這種情況下的用法再補充一點:

PlaySound(MAKEINTRESOURCE(IDR_WAVE1),AfxGetResourceHandle(), SND_ASYNC|SND_RESOURCE|SND_NODEFAULT|SND_LOOP);
其中MAKEINTRESOURCE()巨集將整數資源識別符號轉變為字串,

AfxGetResourceHandle()函式返回包含資源的模組控制代碼,

SND_RESOURCE是必須的標誌。  

作為資源的聲音檔案的第二種播放方法是把資源讀入記憶體後作為記憶體資料播放。具體步驟入下:
  1.獲得包含資源的模組控制代碼:
  HMODULE hmod=AfxGetResourceHandle();
  2.檢索資源塊資訊:

HRSRC hSndResource=FindResource(hmod,MAKEINTRESOURCE(IDR_WAVE1),

_T("WAVE"));
  3. 裝載資源資料並加鎖:
  HGLOBAL hGlobalMem=LoadResource(hmod,hSndResource);
LPCTSTR lpMemSound=(LPCSTR)LockResource(hGlobalMem);
  4.播放聲音檔案:
  PlaySound(lpMemSound,AfxGetResourceHandle(),SND_MEMORY))
  5.釋放資源控制代碼:
  FreeResource(hGlobalMem);

2.sndPlaySound函式用法

函式sndPlaySound的功能與PlaySound類似,但少了一個引數。

sndPlaySound函式的宣告為:
BOOL sndPlaySound(LPCSTR lpszSound, UINT fuSound);

1) 除了不能指定資源名字外,引數lpszSoundPlaySound的是一樣的。

2) 引數fuSound是如何播放聲音的標誌,可以是SND_ASYNCSND_LOOPSND_MEMORYSND_NODEFAULTSND_NOSTOPSND_SYNC的組合,這些標誌的含義與PlaySound的一樣,詳見表格1-1

可以看出,sndPlaySound不能直接播放聲音資源(上面介紹的在MFC新增自定義資源的那種方式)。要用該函式播放WAVE檔案,按照1.4.1的方式即可

3) 舉例說明

sndPlaySound(_T("C:\\WINDOWS\\Media\\Windows XP 啟動.wav"),SND_ASYNC);

控制第一個引數為路徑名即可。例如:

TCHAR szPath[MAX_PATH]; 

GetModuleFileName(NULLszPathMAX_PATH);

CString PathName(szPath);

//獲取exe目錄

CString PROGRAM_PATH = PathName.Left(PathName.ReverseFind(_T('\\')) + 1);

PROGRAM_PATH+=_T("\Sound\\BOMB.WAV");

sndPlaySound(PROGRAM_PATH,SND_ASYNC);


(二)選擇播放本地的聲音檔案

上面的兩種方法對於播放聲音有很大的侷限性:只能播放wav格式的音訊檔案,其他格式的音訊檔案都不支援。下面介紹的這種方法可以實現播放本地的任何格式的音訊檔案。注:這種方法是在網上搜集整理的,在掃雷程式中試過,有效。將掃雷程式中的程式碼作為示例,程式碼中已新增詳細的註釋,以供參考。

DWORD CMineSweepingView::GetInfo(DWORD item)  
{  
	//MCI介面物件的狀態   
	MCI_STATUS_PARMS mcistatusparms;  
	//歌曲格式   
	mcistatusparms.dwCallback=(DWORD)GetSafeHwnd();  
	//待獲取的專案   
	mcistatusparms.dwItem=item;  
	mcistatusparms.dwReturn=0;  
	//想多媒體裝置傳送指令,獲取當前的狀態引數   
	mciSendCommand(m_ulCount,MCI_STATUS,MCI_STATUS_ITEM,(DWORD)&mcistatusparms);  
	return mcistatusparms.dwReturn;   
}  

在播放聲音的訊息響應函式程式碼:
void CMineSweepingView::OnGamePlaymusic()
{

	CFileDialog filedlg(true);// 檔案對話方塊處於開啟狀態   
	// TODO: Add your command handler code here   
	filedlg.m_ofn.Flags |=512;//可以選取多個曲目   
	filedlg.m_ofn.lpstrFilter=_T("All Support type\0*.*\0\0");//檔案格式任意   
	POSITION pos=filedlg.GetStartPosition();//選取目標音訊檔案 
	CString aa=_T("");         
	if(filedlg.DoModal()==IDOK)//選定List控制元件中的新增位置,將檔案新增在選定位置後邊   
	{     
		while (pos != NULL)  
		{  
			aa=filedlg.GetNextPathName(pos);//將新增的音訊檔案路徑存放在陣列file中   

			UpdateData(false);            
		}  
	}  
	mciSendString(_T("close wave"), 0, 0, NULL);  
	mciSendCommand(m_ulCount,MCI_CLOSE,0,NULL);//向多媒體裝置傳送訊息,關閉多媒體設//備   
	MCI_OPEN_PARMS mciopenparms;//開啟   
	MCI_PLAY_PARMS mciplayparms;//播放   
	mciopenparms.lpstrElementName=aa;//播放路徑   

	mciopenparms.lpstrDeviceType=NULL;//檔案型別   
	mciSendCommand(0,MCI_OPEN,MCI_DEVTYPE_WAVEFORM_AUDIO,(DWORD)(LPVOID)&mciopenparms);//向MCI裝置傳送命令訊息,包含歌曲檔案的型別和路徑   
	m_ulCount=mciopenparms.wDeviceID;//多媒體裝置型別編號   
	mciplayparms.dwCallback=(DWORD)GetSafeHwnd();//歌曲播放支援型別   
	DWORD cdlen=0;//某個音訊檔案的總時間長度   
	cdlen=GetInfo(MCI_STATUS_LENGTH);    //得到曲目長度
	DWORD cdfrom=0;
	DWORD cdto=0;//歌曲的起點和終點 
	cdto=MCI_MAKE_HMS(MCI_HMS_HOUR(cdlen),MCI_HMS_MINUTE(cdlen),MCI_HMS_SECOND(cdlen));//設定播放完畢某音訊檔案需要的時間n時n分n秒     
	mciplayparms.dwFrom=cdfrom;  
	mciplayparms.dwTo=cdto;  
	mciSendCommand(m_ulCount,MCI_PLAY,MCI_TO|MCI_FROM,(DWORD)(LPVOID)& mciplayparms);//向多媒體裝置傳送播放檔案命令   
	UpdateData(false);  
}
第二種方法用法比第一種更廣泛。用這種方式如果需要停止播放聲音,呼叫函式:

mciSendCommand (m_ulCount, MCI_CLOSE, NULL, NULL); 

即可。