Windows PCM音頻捕獲與播放實現


在WINDOWS下,音頻函數有多種類型,如MCI、多媒體OLE控制、高級音頻等,使用方法都比較簡單。但如果想編寫一個功能較強大的音頻處理程序,那就必須使用低級音頻函數和多媒體文件I/O來控制音頻設備的輸入和輸出。因為低級音頻函數可直接與音頻驅動程序交互,通過窗口消息或回調(CALL BACK)函數來管理音頻數據塊的記錄和播放,控制非常靈活。重要的一點是,低級音頻函數為我們提供了一個設備無關的接口。
 WINDOWS下音頻的采集,播放有三種模式:
(1)通過高級音頻函數、媒體控制接口MCI[1、2]設備驅動程序。
(2)低級音頻函數MIDI Mapper、低級音頻設備驅動(WaveXAPI)。
(3)利用DirectX中的DirectSound。
 使用MCI的方法極其簡便,靈活性較差;使用低級音頻函數的方法相對來說難一點,但是能夠對音頻數據進行靈活的操控;而采用DirectSound的方法,控制聲音數據靈活,效果比前二者都好,但實現起來是三者中最難的。
DirectSound是DirectXAudio的一個較底層的部件,提供了豐富的接口函數,實現.wav格式的波形聲音數據的播放控制。與一般的WindowsAPI提供的聲音播放函數不同,DirectSound可實現多個聲音的混合播放。DirectSound可充分使用聲卡的內存資源,同時也提供了3D聲效算法,模擬出真實的3D立體聲。
使用 Wave API 進行Windows音頻編程可以保持很大的自由度,而且與Linux中的OSS編程模式很像。這里我們主要介紹Wave API。
使用waveOutGetNumDevs和waveOutGetDevCaps來獲取波形輸出設備的個數和能力。只有在確定設備存在之后,才可以打開設備、使用設備。當waveInOpen/waveOutOpen的第二個參數為WAVE_MAPPER時,函數會自 動挑選最適合播放給定的數據格式的設備。 當有多種波形輸出設備時,建議使用WAVE_MAPPER常數作為設備ID。
錄音:
waveInOpen -> waveInPrepareHeader -> waveInAddBuffer -> waveInStart -> waveInStop -> waveInReset ->waveInUnprepareHeader -> waveInClose
播放:
waveOutOpen -> waveOutPrepareHeader -> waveOutWrite -> waveOutReset -> waveOutUnprepareHeader -> waveOutClose
1、查詢設備數目和能力
使用waveOutGetNumDevs和waveOutGetDevCaps來獲取波形輸出設備的個數和能力。只有在確定設備存在之后,才可以打開設備、使用設備。
2、打開波形輸出設備
使用waveInOpen/waveOutOpen為進行重放操作打開特定的波形設備。該函數打開與指定的設備ID相關聯的設備,並以給出指定內存句柄的方法返回打開波形設備的句柄。
3、准備音頻數據塊
在波形捕獲/播放之前,要准備好音頻數據塊,里面包含捕獲所需的緩沖區地址和播放所需的數據地址。將數據塊傳遞給設備驅動程序就實現了捕獲/播放。使用的函數是waveInPrepareHeader /waveOutPerpareHeader。在用完數據塊之后,必須用waveInUnprepareHeader/waveOutUnPrepareHeader函數來清除對波形數據塊的准備。
4、發送音頻數據塊
在成功打開波形輸出設備之后,就可以進行波形重放,使用waveOutWrite函數。在調用該函數后,必須等到設備驅動程序使用完音頻數據塊之后才可以把該數據塊釋放掉。
5、管理波形播放
在使用低級音頻函數播放音頻時,應用程序必須不斷地向設備驅動程序提供數據塊,直到播放結束。WINDOWS提供兩種方法管理波形重放:一是使用窗口消息管理,二是使用低級回調函數管理。另外,通過使用waveOutPause、waveOutRestart和waveOutReset來進行暫停、重新啟動和停止播放。
6、開始音頻捕獲
在成功打開波形輸入設備之后,使用waveInStart函數。在調用該函數后,開始捕獲輸入設備的音頻數據拷貝至音頻數據塊中的緩沖區地址中。
7、停止音頻捕獲
調用waveInStop函數停止在指定的波形輸入設備上的輸入
8、關閉波形設備
用完設備之后,必須調用waveInClose/waveOutClose函數關閉波形設備,以便其他程序可以使用設備。
更多函數見附錄。
下面給出實例代碼:
PS:工程在鏈接中使用了winmm.lib
#include "stdafx.h"
#include
#include
 
#define MUTE_LENGTH  128
 
HWAVEIN hWaveIn;//輸入設備
HWAVEOUT hWaveOut;//輸出設備
WAVEFORMATEX waveform;//采集音頻的格式,結構體
BYTE *pBuffer;//采集音頻時的數據緩存
WAVEHDR wHdr;//采集音頻時包含數據緩存的結構體
FILE *pf;
char mute[MUTE_LENGTH];//靜音符號串
 
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE wait;
waveform.wFormatTag = WAVE_FORMAT_PCM;//聲音格式為PCM
waveform.nSamplesPerSec = 8000;//采樣率,8000次/秒
waveform.wBitsPerSample = 16;//采樣比特,16bits/次
waveform.nChannels = 1;//采樣聲道數,單聲道
waveform.nAvgBytesPerSec = 16000;//每秒的數據率,就是每秒能采集多少字節的數據
waveform.nBlockAlign = 2;//一個塊的大小,采樣bit的字節數乘以聲道數/8
waveform.cbSize = 0;
 
wait = CreateEvent(NULL,0,0,NULL);
 
FillMemory(mute,MUTE_LENGTH,(BYTE)0xFE);
//用靜音符號填充.
//pcm表示的是時域信號,有震動才有聲音,所以為靜音並不一定表示都為0,如果都為1同樣也是靜音,所以只要是每個樣本值沒有變化都是靜音 
 
DWORD bufsize = 1024*1024;//每次開辟10k的緩存存儲錄音數據
int i = 20;//錄音時間
#if 0
    // Device   
    int nReturn = waveInGetNumDevs();  
    printf("輸入設備數目:%d\n", nReturn);  
    for (int i=0; i
    {  
        WAVEINCAPS wic;  
        waveInGetDevCaps(i, &wic, sizeof(WAVEINCAPS));  
        printf("%d\t設備名:%s\n", i, wic.szPname);  
    }  
//使用waveInOpen函數開啟音頻采集
waveInOpen(&hWaveIn,WAVE_MAPPER,&waveform,(DWORD_PTR)wait,0L,CALLBACK_EVENT);
WAVEINCAPS wic;  
    waveInGetDevCaps((UINT_PTR)hWaveIn, &wic, sizeof(WAVEINCAPS));  
    printf("打開的輸入設備:%s\n", wic.szPname); 
fopen_s(&pf,"錄音測試.pcm","wb");
pBuffer = new BYTE[bufsize];
wHdr.lpData = (LPSTR)pBuffer;
wHdr.dwBufferLength = bufsize;
wHdr.dwBytesRecorded = 0;
wHdr.dwUser = 0;
wHdr.dwFlags = 0;
wHdr.dwLoops = 1;
waveInPrepareHeader(hWaveIn, &wHdr, sizeof(WAVEHDR));
//准備一個波形數據塊頭用於錄音
waveInAddBuffer(hWaveIn,&wHdr,sizeof(WAVEHDR));
//指定波形數據塊為錄音輸入緩存
waveInStart(hWaveIn);//開始錄音
while(--i)
{
Sleep(1000);
printf("%ds\n",i);
}
waveInStop(hWaveIn);  
    waveInReset(hWaveIn); //停止錄音
waveInClose(hWaveIn);
fwrite(pBuffer,1,wHdr.dwBytesRecorded,pf);
delete pBuffer;
fclose(pf);
#else
    // Device   
    int nReturn = waveOutGetNumDevs();  
    printf("\n輸出設備數目:%d\n", nReturn);  
    for (int i=0; i
    {  
        WAVEOUTCAPS woc;  
        waveOutGetDevCaps(i, &woc, sizeof(WAVEOUTCAPS));  
        printf("#%d\t設備名:%s\n", i, woc.szPname);  
    }  
pBuffer = new BYTE[bufsize];
wHdr.lpData = mute;
wHdr.dwBufferLength = MUTE_LENGTH;
wHdr.dwBytesRecorded = 0;
wHdr.dwUser = 0;
wHdr.dwFlags = 0;
wHdr.dwLoops = 1;
waveOutOpen (&hWaveOut, WAVE_MAPPER, &waveform, (DWORD)wait, 0, CALLBACK_EVENT);
waveOutPrepareHeader (hWaveOut, &wHdr, sizeof(WAVEHDR));
while(1)
{
waveOutWrite (hWaveOut, &wHdr, sizeof (WAVEHDR)) ;
printf(".");
//Sleep(1000);
WaitForSingleObject(wait,INFINITE);
}
    waveOutReset(hWaveOut);  
    waveOutUnprepareHeader(hWaveOut, &wHdr, sizeof(WAVEHDR));  
    waveOutClose(hWaveOut);  
#endif
return 0;
}
附錄:

waveInGetNumDevs

返回系統中存在的波形輸入設備的數量

waveInAddBuffer

向波形輸入設備添加一個輸入緩沖區

waveInGetDevCaps

查詢指定的波形輸入設備以確定其性能

waveInGetErrorText

檢取由指定的錯誤代碼標識的文本說明

waveInGetID

獲取指定的波形輸入設備的標識符

waveInGetPosition

檢取指定波形輸入設備的當前位置

waveInMessage

發送一條消息給波形輸入設備的驅動器

waveInOpen

為錄音而打開一個波形輸入設備

waveInPrepareHeader

為波形輸入准備一個輸入緩沖區

waveInStart

啟動在指定的波形輸入設備的輸入

waveInReset

停止給定的波形輸入設備的輸入,且將當前位置清零

waveInStop

停止在指定的波形輸入設備上的輸入

waveInUnprepareHeader

清除由waveInPrepareHeader函數實現的准備

WaveInClose

關閉指定的波形輸入設置

waveOutBreakLoop

中斷給定的波形輸出設備上一個循環,並允許播放驅動取列表中的下一個塊

waveOutClose

關閉指定的波形輸出設備

waveOutGetDevCaps

查詢一個指定的波形輸出設備以確定其性能

waveOutGetErrorText

檢取由指定的錯誤代碼標識的文本說明

waveOutGetID

檢取指定的波形輸出設備的標識符

waveOutGetNumDevs

檢取系統中存在的波形輸出設備的數量

waveOutGetPitch

查詢一個波形輸出設備的當前音調設置

waveOutGetPlaybackRate

查詢一個波形輸出設備當前播放的速度

waveOutGetPosition

檢取指定波形輸出設備的當前播放位置

waveOutGetVolume

查詢指定波形輸出設備的當前音量設置

waveOutMessage

發送一條消息給一個波形輸出設備的驅動器

waveOutOpen

為播放打開一個波形輸出設備

waveOutPause

暫停指定波形輸出設備上的播放

waveOutPrepareHeader

為播放准備一個波形緩沖區

waveOutRestart

重新啟動一個被暫停的波形輸出設備

waveOutSetPitch

設置一個波形輸出設備的音調

waveOutSetPlaybackRate

設置指定波形輸出設備的速度

waveOutSetVolume

設置指定的波形輸出設備的音量

waveOutUnprepareHeader

清除由waveOutPrepareHeader函數實現的准備

waveOutWrite

向指定的波形輸出設備發送一個數據塊


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM