Windows下⾳频数据采集和播放
⾳频操作所需头⽂件和链接库
#include<mmsystem.h>
#include<mmreg.h>
#pragma  comment(lib, "winmm.lib")
由于⾳频采集过程是⼀个持续过程,所以建议为它们各⾃分配⼀个线程,⽽使⽤MFC的 CWinThread 类是⼀个不错的选择,笔者就是利⽤CWinThread类将这两个功能封装成了两个独⽴的类,为以后的使⽤提供了很⼤的便利性。笔者在此为读者提供本⼈写好的⼀个⼯程,此⼯程为视频语⾳采集的不完善版,⽬前实现语⾳本地采集与播放,VFW视频采集与显⽰(视频不清晰),在后续章节会将VFW视频采集进⾏总结,敬请期待。。。。。
⼯程下载地址:中选择  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");
postthreadmessagereturn ;//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);//关闭设备
}
}
from: