C语言里的out函数,c语言 vc 用waveout函数写wave文件播放器
用WaveOut函數寫wave文件播放器
要炒菜的話,就得先準備工具,如鍋、鏟子、爐灶等。對程序來說,就是各種函數的應用。WaveOut函數在windowsAPI中屬于低階接口,用來播放的話需要用到下面幾個:
waveOutOpen – 打開波形輸出設備
waveOutPrepareHeader – 準備播放緩沖區
waveOutUnprepareHeader – 取消播放緩沖區
waveOutWrite – 將數據寫入波形輸出設備
waveOutReset – 波形輸出設備復位(清除正在播放的數據,停止播放)
waveOutPause – 波形輸出設備暫停(暫停播放)
waveOutRestart – 波形輸出設備恢復(繼續播放)
waveOutClose – 關閉波形輸出設備
播放時使用的順序大致如下:
waveOutOpen 打開設備
waveOutPrepareHeader 準備緩沖區
waveOutWrite 寫入波形設備
waveOutReset 波形設備復位
waveOutClose 關閉波形設備
至于暫停就更簡單,播放時執行waveOutPause時暫停播放,再執行waveOutRestart時繼續播放。
現在工具已經齊備了,下來就是準備材料了。對于這個播放器來說,最重要的材料是RIFF檔案、WAVEFORMATEX和WAVEHDR這三個結構。下面就簡單介紹一下這三個結構:
RIFF全稱為資源互換文件格式(ResourcesInterchange FileFormat),RIFF文件是windows環境下大部分多媒體文件遵循的一種文件結構,RIFF文件所包含的數據類型由該文件的擴展名來標識,能以RIFF文件存儲的數據包括:音頻視頻交錯格式數據(.AVI) 波形格式數據(.WAV) 位圖格式數據(.RDI) MIDI格式數據(.RMI)調色板格式(.PAL)多媒體電影(.RMN)動畫光標(.ANI)其它RIFF文件(.BND) 。具體格式如下:
WAV文件的基本格式
內容
變量名
大小
取值
RIFF頭
文件標識符串
fileId
4B
“RIFF”
頭后文件長度
fileLen
4B
非負整數(=文件長度-8)
數據類型標識符
波形文件標識符
waveId
4B
“WAVE”
格式塊
塊頭
格式塊標識符串
chkId
4B
“fmt ”
頭后塊長度
chkLen
4B
非負整數(= 16或18)
塊數據
格式標記
wFormatTag
2B
非負短整數(PCM=1)
聲道數
wChannels
2B
非負短整數(= 1或2)
采樣率
dwSampleRate
4B
非負整數(單聲道采樣數/秒)
平均字節率
dwAvgBytesRate
4B
非負整數(字節數/秒)
數據塊對齊
wBlockAlign
2B
非負短整數(不足補零)
采樣位數
wBitsPerSample
2B
非負短整數(PCM時才有)
擴展域大小
wExtSize
2B
非負短整數
可選(根據chkLen=16 or 18判斷)
擴展域
extraInfo
extSize B
擴展信息
數據塊
塊頭
數據塊標識符串
chkId
4B
“data”
頭后塊長度
chkLen
4B
非負整數
塊數據
波形采樣數據
x或xl、xr
chkLen B
左右聲道樣本交叉排列
樣本值為整數(整字節存儲,不足位補零),
整個數據塊按blockAlign對齊
注意:波形聲音檔案以文字字串「RIFF」開始,用來標識這是一個 RIFF 檔案。字串後面是一個 32 位元的資料塊大小,表示檔案其余部分的大小,或者是小於 8位元組的檔案大小。 資料塊以文字字串「WAVE」開始,用來標識這是一個波形聲音塊,後面是文字字串「fmt」——注意用空白使之成為 4 字元的字串——用來標識包含波形聲音資料格式的子資料塊。「fmt」字串的後面是格式資訊大小,這里是 16 位元組。格式資訊是 WAVEFORMATEX 結構的前 16 個位元組,或者,像最初定義時一樣,是包含 WAVEFORMAT 結構的 PCMWAVEFORMAT 結構,其定義如下:
typedef struct pcmwaveformat - tag
{
WAVEFORMAT wf ; /*音頻波形格式結構*/
WORD wBitsPerSample; /* 采樣大小 */
} PCMWAVEFORMAT;
typedef struct waveformat - tag
{
WORD wFormatTag ; /* 指定格式類型; 默認 WAVE_FORMAT_PCM = 1; */
WORD nChannels;/* 指出波形數據的聲道數; 單聲道為 1, 立體聲為 2 */
DWORD nSamplesPerSec;/* 指定采樣頻率(每秒的樣本數) */
DWORD nAvgBytesperSec;/* 指定數據傳輸的傳輸速率(每秒的字節數) */
WORD nBlockAlign;/* 指定塊對齊塊對齊是數據的最小單位 */
} WAVEFORMAT; /*音頻波形格式結構*/
格式資訊的後面是文字字串「data」,然後是 32 位元的資料大小,最後是波形資料本身。 用於讀取標記檔案的一個重要規則是忽略不準備處理的資料塊。
音頻波形擴展格式結構WAVEFORMATEX用于打開音頻設備,其定義如下:
typedef struct
{
WORD wFormatTag; /* 指定格式類型; 默認 WAVE_FORMAT_PCM = 1; */
WORD nChannels; /* 指出波形數據的聲道數; 單聲道為 1, 立體聲為 2 */
DWORD nSamplesPerSec; /* 指定采樣頻率(每秒的樣本數) */
DWORD nAvgBytesPerSec; /* 指定數據傳輸的傳輸速率(每秒的字節數) */
WORD nBlockAlign; /* 指定塊對齊塊對齊是數據的最小單位 */
WORD wBitsPerSample; /* 采樣大小 */
WORD cbSize; /* 附加信息的字節大小 */
} WAVEFORMATEX;
音頻數據塊緩存結構WAVEHDR
其聲明如下:
type struct{
LPSTR lpData; /* 指向鎖定的數據緩沖區的指針 */
DWORD dwBufferLength; /* 數據緩沖區的大小 */
DWORD dwBytesRecorded; /* 錄音時指明緩沖區中的數據量 */
DWORD dwUser; /* 用戶數據 */
DWORD dwFlag; /* 提供緩沖區信息的標志 */
DWORD dwLoops; /* 循環播放的次數 */
struct wavehdr_tag *lpNext; /* 保留 */
DWORD reserved; /* 保留 */
} WAVEHDR;
dwFlags中提供緩沖區信息的標志。定義以下值:
WHDR_DONE被設備驅動程序設置,用來標識它是完成的(緩沖區)并且正在返回它到應用程序
WHDR_PREPARED由Windows設置表明,在緩沖區已準備waveInPrepareHeader或waveOutPrepareHeader功能。
WHDR_BEGINLOOP這個緩沖區是在第一個循環緩沖區。這個標志僅用于輸出緩沖器。
WHDR_ENDLOOP這個緩沖區是在一個循環中的最后一個緩沖區。這個標志僅用于輸出緩沖器。
WHDR_INQUEUE由Windows設置為顯示緩沖區排隊播放。
現在材料、工具都有了,接下來就是如何炒的問題了。windows程序設計是一種以物件為導向的創作,所謂物件就是程式與資料的組合,你所見到的程序界面就是一種物件。你可以先在紙上畫出程序的界面,然后再根據它逐步添加各個模塊功能。下面就是播放器的界面:
如圖有8個按鈕和一個列表框,它們代表了程序的各個功能模塊。有了界面就等于建好了房子的框架,下來只要添磚加瓦就可以了。windows程序是以消息作為驅動的,對這些功能模塊的處理就是各種消息的處理,下面請看偽代碼:
//對話方塊回調函數BOOL CALLBACK DlgProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
//處理窗口消息
switch (message)
{
case WM_INITDIALOG://處理對話方塊初始化消息
//初始化代碼
return TRUE ;
//處理窗口控件消息
case WM_COMMAND:
switch (LOWORD (wParam))
{
case IDC_OPEN://處理打開按鈕消息
//獲取文件名
//輸出音樂文件列表
//將循環標志改為正常
//獲取wave音頻格式
//打開波形設備if(waveOutOpen(&(params.hWaveOut), WAVE_MAPPER, &wfx, (DWORD)waveOutProc,
(DWORD)&waveFreeBlockCount, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
{
//顯示錯誤信息
return TRUE ;
}//將當前播放文件加亮
//開啟音頻緩沖線程
return TRUE ;
//處理暫停播放消息
case IDC_PAUSE:
if//如果暫停條件為真
{
//繼續播放
}
else//如果暫停條件為假
{
//暫停播放
}
return TRUE ;
//處理上一首消息
case IDC_LAST:
//如果播放的是第一首則不處理
//發送WM_WAVEPLAY消息
return TRUE ;
//處理下一首消息
case IDC_NEXT:
//如果播放的是最后一首則不處理
//發送WM_WAVEPLAY消息
return TRUE ;
//處理正常播放消息
case IDC_NORMAL:
//將標志置為正常播放
return TRUE ;
//處理單次循環消息
case IDC_REPLAY_ONE:
//將標志置為單次循環
return TRUE ;
//處理全部循環消息
case IDC_REPLAY_ALL:
//將標志置為全部循環
return TRUE ;
//處理停止消息
case IDC_STOP:
//設置停止標志
//發送WM_WAVEPLAY消息
return TRUE ;
//處理列表控件消息
case IDC_LIST:
if(HIWORD(wParam) == LBN_DBLCLK)//如果雙擊文件
{//設置線程關閉條件為真
//重置波形設備
//解除所有的WAVEHDR結構
//關閉波形設備
//如果存在打開文件句柄則關閉它
//取得雙擊的文件序號
//獲取wave文件音頻格式結構
//打開波形設備//開啟音頻緩沖線程
}
return TRUE ;
}
break ;
//處理自定義消息
case WM_WAVEPLAY:
//設置線程關閉條件為真
//重置波形設備
//解鎖所有的WAVEHDR結構
//關閉波形設備
//如果存在文件句柄則關閉它
//如果停止條件為真則退出處理
//如果單次循環條件為真
{
//代碼
}
//如果全部循環條件為真
{
//代碼
}
//如果播放上一首條件為真
{
//代碼
}
//如果播放下一首條件為真
{
//代碼
}//如果播放的是最后一首則不處理
//獲取wave文件音頻格式結構
//打開波形設備
//發送列表框控件當前選擇加亮消息
//開啟音頻緩沖線程
return TRUE ;
//處理系統控件消息
case WM_SYSCOMMAND:
switch (wParam)
{
case SC_CLOSE://處理窗口關閉消息
if (音頻設備打開)
{
//設置線程關閉條件為真
//重置波形設備
//解鎖所有的WAVEHDR結構
//釋放所有的音頻緩沖塊
//關閉波形設備
//如果文件句柄存在則關閉它
//結束對話方塊
}
else
{
//釋放所有的音頻緩沖塊
//如果文件句柄存在則關閉它
//結束對話方塊
}
return TRUE ;
}
break ;
}
return FALSE ;//返回假值
}
對于程序設計來說,最重要的就是邏輯。具體到這個播放器,就是各個模塊的邏輯以及它們之間的聯系。比如,按下打開按鈕時,彈出打開文件對話框,然后讀取文件名,打開設備,鎖定緩沖,再進行播放,然后循環,直到所有文件播放完畢。除了各個模塊自身的消息外,這個程序設計了一個自定義消息WM_WAVEPLAY,利用它對上一首、下一首、單次循環、全部循環、停止等按鈕消息進行處理,這就大大縮減了代碼,使邏輯結構更加清晰。邏輯搞清楚了,接下來就是最后一步了。將各個模塊的代碼填充完整,這個程序就基本完工了,剩下的就是調試工作了。
不過這里還有兩個問題需要注意,一個是打開音頻設備要用回調函數處理,另一個就是多線程技術。首先說一下打開音頻設備的問題,請看一看上面的紅色代碼:
if(waveOutOpen(&(params.hWaveOut), WAVE_MAPPER, &wfx, (DWORD)waveOutProc,
(DWORD)&waveFreeBlockCount, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
{
//顯示錯誤信息
return TRUE ;
}waveOutProc就是回調函數的名稱,waveFreeBlockCount就是傳給它的參數,CALLBACK_FUNCTION指明使用回調函數處理。回調函數的名稱可以自定義,但是格式卻必須如下:static void CALLBACK waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwInstance, DWORD dwParam1,DWORD dwParam2 )
{
//處理音頻設備播放時的各種消息
return ;
}
為什么要在回調函數中處理設備消息?這是因為防止設備消息會干擾到程序與外界的交互消息,比如設備在播放文件時,程序也許會收到用戶輸入的消息,如果同時處理設備消息,這時就有可能會導致故障,而用回調函數就不會出現這種狀況,因為它是在另一線程中處理消息。
這個播放器還必須使用多線程技術,因為播放文件時需要不停讀音頻緩沖,這會導致界面暫時卡死(即不能與用戶進行交互),只能等待播放完畢。怎么解決呢?用一個線程專門進行音頻數據的緩沖與播放,而主線程用于各種用戶消息的處理。請看代碼:
#include //包含多線程處理頭文件
VOID bufThread(PVOID pvoid) ;//聲明音頻緩沖線程函數
_beginthread(bufThread,0,¶ms) ;//開啟音頻緩沖線程
//定義音頻緩沖線程函數
VOID bufThread(PVOID pvoid)
{
DWORD readBytes ;//定義一個儲存讀取文件字節數的DWORD變量
char buffer[BLOCK_SIZE] = {'\0'} ; /* 定義一個臨時緩沖 */
volatile PPARAMS pparams ;//定義一個參數結構指針
waveFreeBlockCount = BLOCK_COUNT; //設置空閑緩沖塊的總數量
waveCurrentBlock= 0;//設置當前緩沖塊為第一塊
pparams = (PPARAMS)pvoid ;//給參數結構指針賦值
if(pparams->bShutoff)//判斷線程關閉條件
return ;
//移動文件指針到音頻數據起始地址
if(INVALID_SET_FILE_POINTER == SetFilePointer(pparams->hFile,
pparams->iCount,NULL,FILE_BEGIN))
{
MessageBox(NULL,TEXT("指針神經病!"),
TEXT("提示"),MB_OK | MB_ICONWARNING);
return ;
}
//在循環中讀取音頻數據到臨時緩沖,再寫入波形設備
while(!pparams->bShutoff) //判斷線程關閉條件
{
//讀取數據到臨時緩沖
if(!ReadFile(pparams->hFile, buffer, sizeof(buffer), &readBytes, NULL))
break ;
//如果數據為零則退出循環
if(readBytes == 0)
break;
//如果讀取的結果小于臨時緩沖,則填入0補充完整
if(readBytes < sizeof(buffer))
memset(buffer + readBytes, 0, sizeof(buffer) - readBytes);
//將音頻數據寫入波形設備進行播放
writeAudio(pparams->hWaveOut, buffer,sizeof(buffer));
}
//關閉文件
CloseHandle(pparams->hFile);
if(pparams->bShutoff)//判斷線程關閉條件
return ;
//如果線程關閉條件為假且空閑緩沖塊數量小于全部緩沖塊數量則等待
while(!pparams->bShutoff && waveFreeBlockCount < BLOCK_COUNT)
Sleep(0) ;//線程交出自己的時間片段給別的線程
if(!pparams->bShutoff)//如果線程關閉條件為假則發送用戶自定義消息
PostMessage(pparams->hwnd,WM_WAVEPLAY,0,0) ;
return ;
}
在建立多執行緒的 Windows 程式時,需要在「Project Settings」對話方塊中做一些修改。選擇「C/C++」頁面標簽,然後在「Category」下拉式清單方塊中選擇「Code Generation」。在「Use Run-Time Library」下拉式清單方塊中,可以看到用於「Release」設定的「Single-Threaded」和用於 Debug 設定的「Debug Single-Threaded」。將這些分別改為「Multithreaded」和「Debug Multithreaded」。這將把編譯器旗標改為/MT,它是編譯器在編譯多執行緒的應用程式所需要的。 具體地說, 編譯器將在.OBJ 檔案中插入 LIBCMT.LIB 檔案名,而不是 LIBC.LIB。連結程式使用這個名稱與執行期程式庫函式連結。 LIBC.LIB 和 LIBCMT.LIB 檔案包含 C 語言程式庫函式, 有些 C 語言程式庫函式包含靜態資料。例如,由於 strtok 函式可能被連續地多次呼叫,所以它在靜態記憶體中儲存了一個指標。在多執行緒程式中,每個執行緒必須在 strtok 函式中有它自己的靜態指標。因此,這個函式的多執行緒版本稍微不同於單執行緒的 strtok 函式。 同時請注意,必須包含表頭檔案 PROCESS.H,這個檔案定義一個名為_beginthread 的函式,它啟動一個新的執行緒。只有定義了_MT 識別字,才會宣告這個函式,這是/MT 旗標的另一個結果。下面簡單介紹一下_beginthread函數:
beginthreaduintptr_t _beginthread(
void( *start_address )( void * ),
unsigned stack_size,
void *arglist
);
Parameters 參數:
start_address:程序執行一個新線程的起始地址,即線程函數的名稱。
Start address of a routine that begins execution of a new thread. For _beginthread, the calling convention is either __cdecl or __clrcall; for _beginthreadex, it is either __stdcall or __clrcall.
stack_size:新線程的堆棧大小或0值。
Stack size for a new thread or 0.
Arglist:傳給新線程的變量清單或空值。
Argument list to be passed to a new thread or NULL.
Return Value 返回值:
如果新線程建立成功,函數返回該線程的句柄;然而,如果新線程退出太快,_beginthread函數可能返回一個有誤的句柄。_beginthread發生錯誤時返回1L。
在使用多線程時,還需注意共享變量的互斥,這里利用了臨界區域的技術。這種臨界區域技術專用于多線程中共享變量的互斥使用,在任何時刻,只有一個執行緒能擁有一個臨界區域。因此,一個執行緒可以進入一個臨界區域,設定一個變量,然後退出臨界區域。另一個使用該變量的執行緒在存取變量中的項目之前也要先進入該臨界區域,然後再退出臨界區域。請看代碼:
static CRITICAL_SECTION waveCriticalSection;//定義一個臨界區域
InitializeCriticalSection(&waveCriticalSection);//初始化臨界區域
EnterCriticalSection(&waveCriticalSection);//進行臨界區域
(*freeBlockCounter)++;//設定變量
LeaveCriticalSection(&waveCriticalSection);//退出臨界區域
DeleteCriticalSection(&waveCriticalSection);//刪除臨界區域
注意,您可以定義多個臨界區域物件,比如 cs1 和 cs2。例如,如果一個程式有四個執行緒,而前兩個執行緒共用一些資料,那么它們可以使用一個臨界區域物件,而另外兩個執行緒共用一些其他的資料,那么它們可以使用另一個臨界區域物件。 您在主執行緒中使用臨界區域時應該小心,如果從屬執行緒在它自己的臨界區域中花費了一段很長的時間,那么它可能會將主執行緒的執行阻礙很長一段時間。
至此播放器就順利地誕生了,盡管它還比較粗糙,但各項功能還算完備。不過,請注意此播放器只能播放wave文件,不支持其它類型的文件。下面就是完整代碼的附件,有需要的就請下載。http://download.csdn.net/detail/dnfyg_000/4976055
總結
以上是生活随笔為你收集整理的C语言里的out函数,c语言 vc 用waveout函数写wave文件播放器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 开源游戏服务器框架NoahGameFra
- 下一篇: 程序员笔试之海康威视2021应用软件开发