int GetTimeLength()
//獲取聲音文件數據的函數,pString參數指向要打開的聲音文件;
{
HMMIO file;//定義HMMIO文件句柄;
file=mmioOpen(".//TTS.wav",NULL,MMIO_READWRITE);//以讀寫模式打開所給的WAVE文件;
if(file==NULL)
{
TRACE("WAVE文件打開失敗!");
return -1;
}
char style[4];//定義一個四字節的數據,用來存放文件的類型;
mmioSeek(file,8,SEEK_SET);//定位到WAVE文件的類型位置
mmioRead(file,style,4);
if(style[0]!='W'||style[1]!='A'||style[2]!='V'||style[3]!='E')//判斷該文件是否爲"WAVE"文件格式
{
TRACE("該文件不是WAVE格式的文件!");
return -1;
}
PCMWAVEFORMAT format; //定義PCMWAVEFORMAT結構對象,用來判斷WAVE文件格式;
mmioSeek(file,20,SEEK_SET);
//對打開的文件進行定位,此時指向WAVE文件的PCMWAVEFORMAT結構的數據;
mmioRead(file,(char*)&format,sizeof(PCMWAVEFORMAT));//獲取該結構的數據;
//獲取WAVE文件的聲音數據的大小;
mmioSeek(file,42,SEEK_SET);
unsigned long size;
mmioRead(file,(char*)&size,4);
//計算文件時長
int timeLength;
timeLength = size/format.wf.nAvgBytesPerSec;
::mmioClose(file, 0);
return timeLength;
}
一、前言
當前Visual C++相關的編程資料中,無論是大部頭的參考書,還是一些計算機雜誌,對聲音文件的處理都是泛泛的涉及一下,許多編程愛好者都感到對該部分的內容瞭解不是很透徹,本文希望能夠給剛剛涉及到聲音處理領域的朋友們起到一個引路的作用,幫助他們儘快進入聲音處理的更深奧空間。
當前計算機系統處理聲音文件有兩種辦法:一是使用現成的軟件,如微軟的錄音機、SoundForge、CoolEdit等軟件可以實現對聲音信號進行錄音、編輯、播放的處理,但它們的功能是有限的,爲了更靈活,更大限度地處理聲音數據,就不得不使用另外一種方法,既利用微軟提供的多媒體服務,在Windows環境下自己編寫程序來進行聲音處理來實現一些特定的功能。下面就開始介紹聲音文件的格式和在Windows環境下使用Visual C++開發工具進行聲音文件編程處理的方法,本文所有的程序代碼都在Windows2000、Visual
C++6.0環境下編譯通過,運行正常。
二、RIFF文件結構和WAVE文件格式
Windows支持兩種RIFF(Resource Interchange File Format,"資源交互文件格式")格式的音頻文件:MIDI的RMID文件和波形音頻文件格式WAVE文件,其中在計算機領域最常用的數字化聲音文件格式是後者,它是微軟專門爲Windows系統定義的波形文件格式(Waveform Audio),由於其擴展名爲"*.wav",因而該類文件也被稱爲WAVE文件。爲了突出重點,有的放矢,本文涉及到的聲音文件所指的就是WAVE文件。常見的WAVE語音文件主要有兩種,分別對應於單聲道(11.025KHz採樣率、8Bit的採樣值)和雙聲道(44.1KHz採樣率、16Bit的採樣值)。這裏的採樣率是指聲音信號在進行"模→數"轉換過程中單位時間內採樣的次數。採樣值是指每一次採樣週期內聲音模擬信號的積分值。對於單聲道聲音文件,採樣數據爲八位的短整數(short
int 00H-FFH);而對於雙聲道立體聲聲音文件,每次採樣數據爲一個16位的整數(int),高八位和低八位分別代表左右兩個聲道。WAVE文件數據塊包含以脈衝編碼調製(PCM)格式表示的樣本。在進行聲音編程處理以前,首先讓我們來了解一下RIFF文件和WAVE文件格式。
RIFF文件結構可以看作是樹狀結構,其基本構成是稱爲"塊"(Chunk)的單元,每個塊有"標誌符"、"數據大小"及"數據"所組成,塊的結構如圖1所示:
塊的標誌符(4BYTES) |
數據大小 (4BYTES) |
數據 |
從上圖可以看出,其中"標誌符"爲4個字符所組成的代碼,如"RIFF","LIST"等,指定塊的標誌ID;數據大小用來指定塊的數據域大小,它的尺寸也爲4個字符;數據用來描述具體的聲音信號,它可以由若干個子塊構成,一般情況下塊與塊是平行的,不能相互嵌套,但是有兩種類型的塊可以嵌套子塊,他們是"RIFF"或"LIST"標誌的塊,其中RIFF塊的級別最高,它可以包括LIST塊。另外,RIFF塊和LIST塊與其他塊不同,RIFF塊的數據總是以一個指定文件中數據存儲格式的四個字符碼(稱爲格式類型)開始,如WAVE文件有一個"WAVE"的格式類型。LIST塊的數據總是以一個指定列表內容的4個字符碼(稱爲列表類型)開始,例如擴展名爲".AVI"的視頻文件就有一個"strl"的列表類型。RIFF和LIST的塊結構如下:
RIFF/LIST標誌符 | |
數據1大小 | |
數據1 | 格式/列表類型 |
數據 |
WAVE文件是非常簡單的一種RIFF文件,它的格式類型爲"WAVE"。RIFF塊包含兩個子塊,這兩個子塊的ID分別是"fmt"和"data",其中"fmt"子塊由結構PCMWAVEFORMAT所組成,其子塊的大小就是sizeofof(PCMWAVEFORMAT),數據組成就是PCMWAVEFORMAT結構中的數據。WAVE文件的結構如下圖三所示:
標誌符(RIFF) |
數據大小 |
格式類型("WAVE") |
"fmt" |
Sizeof(PCMWAVEFORMAT) |
PCMWAVEFORMAT |
"data" |
聲音數據大小 |
聲音數據 |
PCMWAVEFORMAT結構定義如下:
Typedef struct { WAVEFORMAT wf;//波形格式; WORD wBitsPerSample;//WAVE文件的採樣大小; }PCMWAVEFORMAT; WAVEFORMAT結構定義如下: typedef struct { WORD wFormatag;//編碼格式,包括WAVE_FORMAT_PCM,WAVEFORMAT_ADPCM等 WORD nChannls;//聲道數,單聲道爲1,雙聲道爲2; DWORD nSamplesPerSec;//採樣頻率; DWORD nAvgBytesperSec;//每秒的數據量; WORD nBlockAlign;//塊對齊; }WAVEFORMAT; |
"data"子塊包含WAVE文件的數字化波形聲音數據,其存放格式依賴於"fmt"子塊中wFormatTag成員指定的格式種類,在多聲道WAVE文件中,樣本是交替出現的。如16bit的單聲道WAVE文件和雙聲道WAVE文件的數據採樣格式分別如圖四所示:
16位單聲道:
採樣一 | 採樣二 | …… | ||
低字節 | 高字節 | 低字節 | 高字節 | …… |
16位雙聲道:
採樣一 …… | ||||
左聲道 | 右聲道 | …… | ||
低字節 | 高字節 | 低字節 | 高字節 | …… |
圖四、WAVE文件數據採樣格式
三、聲音文件的聲音數據的讀取操作
操作聲音文件,也就是將WAVE文件打開,獲取其中的聲音數據,根據所需要的聲音數據處理算法,進行相應的數學運算,然後將結果重新存儲與WAVE格式的文件中去。可以使用CFILE類來實現讀取操作,也可以使用另外一種方法,拿就是使用Windows提供的多媒體處理函數(這些函數都以mmino打頭)。這裏就介紹如何使用這些相關的函數來獲取聲音文件的數據,至於如何進行處理,那要根據你的目的來選擇不同的算法了。WAVE文件的操作流程如下:
1.調用mminoOpen函數來打開WAVE文件,獲取HMMIO類型的文件句柄;
2.根據WAVE文件的結構,調用mmioRead、mmioWrite和mmioSeek函數實現文件的讀、寫和定位操作;
3.調用mmioClose函數來關閉WAVE文件。
下面的函數代碼就是根據WAVE文件的格式,實現了讀取雙聲道立體聲數據,但是在使用下面的代碼過程中,注意需要在程序中鏈接Winmm.lib庫,並且包含頭文件"Mmsystem.h"。
BYTE * GetData(Cstring *pString) //獲取聲音文件數據的函數,pString參數指向要打開的聲音文件; { if (pString==NULL) return NULL; HMMIO file1;//定義HMMIO文件句柄; file1=mmioOpen((LPSTR)pString,NULL,MMIO_READWRITE);//以讀寫模式打開所給的WAVE文件; if(file1==NULL) { MessageBox("WAVE文件打開失敗!"); Return NULL; } char style[4];//定義一個四字節的數據,用來存放文件的類型; mmioSeek(file1,8,SEEK_SET);//定位到WAVE文件的類型位置 mmioRead(file1,style,4); if(style[0]!='W'||style[1]!='A'||style[2]!='V'||style[3]!='E')//判斷該文件是否爲"WAVE"文件格式 { MessageBox("該文件不是WAVE格式的文件!"); Return NULL; } PCMWAVEFORMAT format; //定義PCMWAVEFORMAT結構對象,用來判斷WAVE文件格式; mmioSeek(file1,20,SEEK_SET); //對打開的文件進行定位,此時指向WAVE文件的PCMWAVEFORMAT結構的數據; mmioRead(file1,(char*)&format,sizeof(PCMWAVEFORMAT));//獲取該結構的數據; if(format.wf.nChannels!=2)//判斷是否是立體聲聲音; { MessageBox("該聲音文件不是雙通道立體聲文件"); return NULL; } mmioSeek(file1,24+sizeof(PCMWAVEFORMAT),SEEK_SET); //獲取WAVE文件的聲音數據的大小; long size; mmioRead(file1,(char*)&size,4); BYTE *pData; pData=(BYTE*)new char[size];//根據數據的大小申請緩衝區; mmioSeek(file1,28+sizeof(PCMWAVEFORMAT),SEEK_SET);//對文件重新定位; mmioRead(file1,(char*)pData,size);//讀取聲音數據; mmioClose(file1, MMIO_FHOPEN);//關閉WAVE文件; return pData; } |
四、使用MCI方法操作聲音文件
WAVE聲音文件一個最基本的操作就是將文件中的聲音數據播放出來,用Windows提供的API函數BOOL sndPlaySound(LPCSTR lpszSound, UINT fuSound)可以實現小型WAV文件的播放,其中參數lpszSound 爲所要播放的聲音文件,fuSound爲播放聲音文件時所用的標誌位。例如實現Sound.wav 文件的異步播放,只要調用函數sndPlaySound("c:\windows\Sound.wav",SND_ASYNC)就可以了,由此可以看到sndPlaySound函數使用是很簡單的。但是當WAVE文件大於100K時,這時候系統無法將聲音數據一次性的讀入內存,sndPlaySound函數就不能進行播放了。爲了解決這個問題,你的一個選擇就是用MCI方法來操作聲音文件了。在使用MCI方法之前,首先需要在你開發的項目設置Project->Setting->Link->Object/library modules中加入winmm.lib。並在頭文件中包括"mmsystem.h"頭文件。
MicroSoft API提供了MCI(The Media Control Interface)的方法mciSendCommand()和mciSendString()來完成WAVE文件的播放,這裏僅介紹mciSendCommand()函數的使用。
原型:DWORD mciSendCommand(UINT wDeviceID,UINT wMessage,DWORD dwParam1,DWORD dwParam2);
參數:wDeviceID:接受消息的設備ID;
Message:MCI命令消息;
wParam1:命令的標誌位;
wParam2:所使用參數塊的指針
返值:調用成功,返回零;否則,返回雙字中的低字存放有錯誤信息。
在使用MCI播放聲音文件時,首先要打開音頻設備,爲此要定義MCI_OPEN_PARMS變量 OpenParms,並設置該結構的相應分量:
OpenParms.lpstrDeviceType = (LPCSTR) MCI_DEVTYPE_WAVEFORM_AUDIO;//WAVE類型 OpenParms.lpstrElementName = (LPCSTR) Filename;//打開的聲音文件名; OpenParms.wDeviceID = 0;//打開的音頻設備的ID |
mciSendCommand (NULL, MCI_OPEN,MCI_WAIT | MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID | MCI_OPEN_ELEMENT, (DWORD)(LPVOID) &OpenParms)函數調用發送MCI_OPEN命令後,返回的參數 OpenParms中成員變量的wDeviceID指明打開了哪個設備。需要關閉音頻設備時只要調用mciSendCommand (m_wDeviceID, MCI_CLOSE, NULL, NULL)就可以了。
播放WAVE文件時,需要定義MCI_PLAY_PARMS變量PlayParms,對該變量進行如下設置:PlayParms.dwFrom = 0,這是爲了指定從什麼地方(時間)播放WAVE文件,設置好以後,調用函數mciSendCommand (m_wDeviceID, MCI_PLAY,MCI_FROM, (DWORD)(LPVOID)&PlayParms));就實現了WAVE聲音文件的播放。
另外,調用mciSendCommand (m_wDeviceID, MCI_PAUSE, 0,(DWORD)(LPVOID)&PlayParms)實現了暫停功能。調用mciSendCommand (m_wDeviceID, MCI_STOP, NULL, NULL)實現停止功能等,可以看出,這些不同的功能實現都是依靠參數"Message"取不同的值來實現的。不同的Message和dwParam1、dwParam2的組合還可以實現文件的跳躍功能。如下面的代碼實現了跳轉到WAVE文件末端的操作:mciSendCommand (m_wDeviceID, MCI_SEEK, MCI_SEEK_TO_END, NULL)。
下面的代碼實現了WAVE聲音文件的播放:
void CTest1View::OnMciPlayWave() { // TODO: Add your command handler code here MCI_OPEN_PARMS mciOpenParms; MCI_PLAY_PARMS PlayParms; mciOpenParms.dwCallback=0; mciOpenParms.lpstrElementName="d:\\chimes.wav"; mciOpenParms.wDeviceID=0; mciOpenParms.lpstrDeviceType="waveaudio"; mciOpenParms.lpstrAlias=" "; PlayParms.dwCallback=0; PlayParms.dwTo=0; PlayParms.dwFrom=0; mciSendCommand(NULL,MCI_OPEN,MCI_OPEN_TYPE|MCI_OPEN_ELEMENT,(DWORD)(LPVOID)&mciOpenParms);//打開音頻設備; mciSendCommand(mciOpenParms.wDeviceID,MCI_PLAY,MCI_WAIT,(DWORD)(LPVOID)&PlayParms);//播放WAVE聲音文件; mciSendCommand(mciOpenParms.wDeviceID,MCI_CLOSE,NULL,NULL);//關閉音頻設備; }
|