音頻操作所需頭文件和鏈接庫
#include <mmreg.h>
#pragma comment( lib, "winmm.lib")
由於音頻采集過程是一個持續過程,所以建議為它們各自分配一個線程,而使用MFC的 CWinThread 類是一個不錯的選擇,筆者就是利用CWinThread類將這兩個功能封裝成了兩個獨立的類,為以后的使用提供了很大的便利性。筆者在此為讀者提供本人寫好的一個工程,此工程為視頻語音采集的不完善版,目前實現語音本地采集與播放,VFW視頻采集與顯示(視頻不清晰),在后續章節會將VFW視頻采集進行總結,敬請期待。。。。。
工程下載地址:http://pan.baidu.com/share/link?shareid=190628&uk=2735225556 中選擇 VideoPlay.rar 下載,此項目是用vs2010編譯
一、音頻采集
操作步驟:
1、分配數據buffer,通過WAVEHDR結構體保存,准備存儲采集到的音頻數據,此處應該根據采集頻率設置足量的buffer
void CRecodeSound::PreCreateHeader()
{
for( int i=0;i<MAXRECBUFFER;i++)
m_RecHead[i]=CreateWaveHeader();
m_IsAllocated = 1;
}
LPWAVEHDR CRecodeSound::CreateWaveHeader()
{
LPWAVEHDR lpHdr = new WAVEHDR;
if(lpHdr==NULL)
{
m_RecodeLog.WriteString(TEXT("\n Unable to allocate the memory"));
return NULL;
}ZeroMemory(lpHdr, sizeof(WAVEHDR));
char* lpByte = new char[RECBUFFER];//m_WaveFormatEx.nBlockAlign*SOUNDSAMPLES)];if(lpByte==NULL)
{
m_RecodeLog.WriteString(TEXT("\n Unable to allocate the memory"));
return NULL;
}
lpHdr->lpData = lpByte;
lpHdr->dwBufferLength =RECBUFFER; // (m_WaveFormatEx.nBlockAlign*SOUNDSAMPLES);
return lpHdr;}
2、初始化音頻格式結構體 WAVEFORMATEX。
memset(&m_WaveFormatEx, 0, sizeof(m_WaveFormatEx));
m_WaveFormatEx.wFormatTag = WAVE_FORMAT_PCM; //聲音格式為PCM
m_WaveFormatEx.nChannels = 1; //采樣聲道數,對於單聲道音頻設置為1,立體聲設置為2
m_WaveFormatEx.wBitsPerSample = 8; //采樣比特 8bits/次
m_WaveFormatEx.cbSize = 0; //一般為0
m_WaveFormatEx.nSamplesPerSec = 8000; //采樣率 16000 次/秒
m_WaveFormatEx.nBlockAlign = 1; //一個塊的大小,采樣bit的字節數乘以聲道數
m_WaveFormatEx.nAvgBytesPerSec = 8000; //每秒的數據率,就是每秒能采集多少字節的數據
3、waveInOpen打開音頻輸入設備准備開始采集
//開啟音頻采集
MMRESULT mmReturn = ::waveInOpen( &m_hRecord, WAVE_MAPPER,
&m_WaveFormatEx, ::GetCurrentThreadId(), 0, CALLBACK_THREAD);
//Error has occured while opening device
if(mmReturn != MMSYSERR_NOERROR ) //打開采集失敗
{
displayError(mmReturn,"Open");
return ;//FALSE;
}
4、waveInPrepareHeader 和 waveInAddBuffer 配合將准備好的buffer提供給設備
//將准備好的buffer提供給音頻輸入設備
for( int i=0; i < MAXRECBUFFER ; i++)
{
//准備一個bufrer給輸入設備
mmReturn = ::waveInPrepareHeader(m_hRecord,m_RecHead[i], sizeof(WAVEHDR));
//發送一個buffer給指定的輸入設備,當buffer填滿將會通知程序
mmReturn = ::waveInAddBuffer(m_hRecord, m_RecHead[i], sizeof(WAVEHDR));
}
5、waveInStart正式開始采集
//開啟指定的輸入采集設備
mmReturn = ::waveInStart(m_hRecord);
if(mmReturn!=MMSYSERR_NOERROR ) //開始采集失敗
displayError(mmReturn,"Start");
else
m_IsRecoding = TRUE;
6、每當一個buffer數據填滿時,會觸發 MM_WIM_DATA 消息,在程序中捕獲此消息,通過消息傳遞過來的 lParam,為指向數據buffer的WAVEHDR指針。采集到此數據時可以根據程序需要對其做相應的處理。本程序是直接將采集到的數據提供給播放線程直接播放,你也可以通過socket發送到遠端在播放,就可以網絡語音了。
void CRecodeSound::OnSoundData(WPARAM wParam, LPARAM lParam)
{
m_RecodeLog.WriteString(TEXT( "\nIn the onsound data"));
if(m_IsRecoding==FALSE) //如果當前不在采集狀態
return ;//FALSE;LPWAVEHDR lpHdr = (LPWAVEHDR) lParam;
if(lpHdr->dwBytesRecorded==0 || lpHdr==NULL)
return ;//ERROR_SUCCESS;//使采集過程,知道此buffer已經沾滿,不能再填充
::waveInUnprepareHeader(m_hRecord, lpHdr, sizeof(WAVEHDR));//將采集到的聲音發送給播放線程
((CVideoPlayDlg *)m_pDlg)->m_pPlaySound->PostThreadMessage(WM_PLAYSOUND_PLAYBLOCK, lpHdr->dwBytesRecorded, (LPARAM)lpHdr->lpData);
// Send recorded audio to remote host…
/*
if(lpHdr->lpData!=NULL )
( (CVideoNetDlg*) dlg )->daudio.SendAudioData((unsigned char *)lpHdr->lpData,lpHdr->dwBytesRecorded);
*/if(m_IsRecoding)
{
//重新將buffer恢復到准備填充狀態
::waveInPrepareHeader(m_hRecord, lpHdr, sizeof(WAVEHDR));
::waveInAddBuffer(m_hRecord, lpHdr, sizeof(WAVEHDR));
}
}
7、在要停止采集是使用waveInStop停止采集數據。
mmReturn = ::waveInStop(m_hRecord);
8、停止采集成功,立即waveInReset重置設備,重置設備將會導致所有的采集buffer反饋給程序。
if(!mmReturn) //停止采集成功,立即重置設備,重置設備將會導致所有的buffer反饋給程序
{
m_IsRecoding = FALSE;
mmReturn = ::waveInReset(m_hRecord); //重置設備
}
9、延時一段時間,等待所有的數據buffer都被程序處理完成
Sleep(500); //等待一段時間,使buffer反饋完成
10、waveInClose關閉設備
if(!mmReturn) //重置設備成功,立即關閉設備
mmReturn = ::waveInClose(m_hRecord); //關閉設備
二、音頻播放
操作步驟:
1、初始化音頻數據格式結構體 WAVEFORMATEX
//初始化音頻格式結構體
memset(&m_WaveFormatEx, 0, sizeof(m_WaveFormatEx));
m_WaveFormatEx.wFormatTag = WAVE_FORMAT_PCM;
m_WaveFormatEx.nChannels = 1;
m_WaveFormatEx.wBitsPerSample = 8;
m_WaveFormatEx.cbSize = 0;
m_WaveFormatEx.nSamplesPerSec = 8000;
m_WaveFormatEx.nAvgBytesPerSec = 8000 ;
m_WaveFormatEx.nBlockAlign = 1;
2、打開音頻輸出設備
//打開音頻輸出設備
mmReturn = ::waveOutOpen( &m_hPlay, WAVE_MAPPER,
&m_WaveFormatEx, ::GetCurrentThreadId(), 0, CALLBACK_THREAD);
3、設置音頻輸出音量
if(mmReturn) //打開設備失敗
displayError(mmReturn, "PlayStart");
else
{
m_IsPlaying = TRUE;
DWORD volume = 0xffffffff;
waveOutSetVolume(m_hPlay, volume); //設置輸出設備的輸出量
}
4、等待要輸出的數據,通過waveOutPrepareHeader將數據提交給設備准備輸出,通過waveOutWrite將提交給設備的數據輸出。
void CPlaySound::OnWriteSoundData(WPARAM wParam, LPARAM lParam)
{
MMRESULT mmResult = 0;
int length=( int) wParam;
if(m_IsPlaying == FALSE)
return ; //FALSE;
m_PlayLog.WriteString(TEXT("\nplaying sound data…."));
// Prepare wave header for playing
WAVEHDR *lpHdr=new WAVEHDR;
memset(lpHdr,0,sizeof(WAVEHDR));
lpHdr->lpData=(char *)lParam;
lpHdr->dwBufferLength=length;//將要輸出的數據寫入buffer
mmResult = ::waveOutPrepareHeader(m_hPlay, lpHdr, sizeof(WAVEHDR));if(mmResult)
{
m_PlayLog.WriteString(TEXT("\nError while preparing header"));
return ;//ERROR_SUCCESS;
}//將輸出數據發送給輸出設備
mmResult = ::waveOutWrite(m_hPlay, lpHdr, sizeof(WAVEHDR));if(mmResult)
{
m_PlayLog.WriteString(TEXT("\nError while writing to device"));
return ;//ERROR_SUCCESS;
}
return ;//ERROR_SUCCESS;
}
5、當提交給設備的數據輸出結束,設備會發送一條MM_WOM_DONE消息反饋給設備,設備應該用waveOutUnprepareHeader將提交給設備輸出的數據清除。
void CPlaySound::OnEndPlaySoundData(WPARAM wParam, LPARAM lParam)
{
LPWAVEHDR lpHdr = (LPWAVEHDR) lParam;
if(lpHdr)
{
::waveOutUnprepareHeader(m_hPlay, lpHdr, sizeof(WAVEHDR));//音頻輸出結束,清空buffer
}
return ;//ERROR_SUCCESS;
}
6、結束輸出前先用waveOutReset重置輸出設備,重置能夠使輸出設備全部buffer輸出結束,所以在waveOutReset后要延遲一段時間,然后調用waveOutClose關閉設備。
void CPlaySound::OnStopPlaying(WPARAM wParam, LPARAM lParam)
{
MMRESULT mmReturn = 0;
if(m_IsPlaying == FALSE)
return;// FALSE;m_PlayLog.WriteString(TEXT("\n Stopped playing"));
mmReturn = ::waveOutReset(m_hPlay);//重置輸出設備,重置能夠使輸出設備全部buffer輸出結束
if(!mmReturn)
{
m_IsPlaying = FALSE;
Sleep(500); //等待所有buffer輸出完成
mmReturn = ::waveOutClose(m_hPlay);//關閉設備
}
}