用WaveX实现音频文件的录音
原文地址:https://blog.csdn.net/gongluck93/article/details/53096013
1、WaveInOpen
- waveInOpen
- MMRESULT waveInOpen(
- LPHWAVEIN phwi, // phwi是返回的句柄存放地址
- UINT uDeviceID, // uDeviceID是要打开的音频设备ID号,一般都指定为WAVE_MAPPER
- LPWAVEFORMATEX pwfx,
- DWORD dwCallback, // dwCallback则为指定的回调函数或线程,窗口等的地址
- DWORD dwCallbackInstance, // dwCallbackInstance为需要向回调函数或线程送入的用户参数
- DWORD fdwOpen // fdwOpen指定回调方式:CALLBACK_FUNCTION, CALLBACK_THREAD和CALLBACK_WINDOW
- );
2、至于pwfx,则比较关键,它指定了要以什么音频格式打开音频输入设备,它是一个结构WAVEFORMATEX:
- typedef struct {
- WORD wFormatTag; //可以在wFormatTag中指定一些压缩的音频格式,如G723.1,TURE DSP,等之类。不过一般都是选用WAVEFORMAT_PCM格式,
- //即未压缩的音频格式,至于压缩,可以在录完后调用下面将要谈到的ACM单独进行。
- WORD nChannels; //nChannels为声道数,1或者2。
- DWORD nSamplesPerSec; //nSamplesPerSec为每秒采样数,8000、11025、22050、44100为几个标准值。
- DWORD nAvgBytesPerSec; //每秒平均的字节数,在PCM方式中就等于nChannels*nSamplesPerSec*wBitsPerSample/8,
- //但对于其它的压缩的音频格式,由于很多压缩方式是按时间片进行的,如G723.1,就是以30ms为一个压缩单位,
- //这样,nAvgBytesPerSec只是一个大概的数字,并不准确,程序中的计算是不应该以这个量为准的。
- //这一点在下面的压缩音频输出和ACM音频压缩中非常重要。
- WORD nBlockAlign; //nBlockAlign是一个比较特殊的值,表示对音频处理时的最小处理单位,对于PCM非压缩,它就是wBitsPerSample*nChannels/8,
- //而对于非压缩格式,则表示压缩/解压处理的最小单位了,如G723.1,就是30ms的数据大小(20bytes或者24bytes)。
- WORD wBitsPerSample; //wBitsPerSample就是每采样值的位数,8或者16。
- WORD cbSize; //cbSize则是表示该WAVEFORMATEX的结构在标准的头部之后还有多少字节数,对于很多非PCM的音频格式,
- //有一些自己的定义格式参数,这些就紧跟在标准的WAVEFORMATEX后面,其大小就由cbSize指定。对于PCM格式而言,为0,或者忽略不检查。
- } WAVEFORMATEX;
1)从声卡获取的数据格式一般设置为WAVE_FORMAT_PCM,得到的是一种最原始的音频数值。采样率,采样位宽和采样通道数比较重要,尤其采样位宽--1此采样数据量的大小,这里采用16bits。
2)录音得到的数据是存放在自己设定的内存块处的,为了录音的连续,通常是用多个内存数据块。当一块数据内存满了之后,是通过消息响应的方式给指定的窗口或者回调函数。在回调函数中处理这些数据。
开启录音的代码段如下:
1 //启动录音 2 BOOL CAudioRec::StartRec() 3 { 4 BOOL bRet = FALSE; 5 6 //启动线程 7 if(!StartThread()) 8 { 9 goto Exit; 10 } 11 12 //打开音频设备 13 if(!OpenDev()) 14 { 15 goto Exit1; 16 } 17 18 //准备缓存 19 if(!PrepareBuffer()) 20 { 21 goto Exit2; 22 } 23 24 //开始录音 25 if(!OpenRecord()) 26 { 27 goto Exit3; 28 } 29 30 bRet = TRUE; 31 goto Exit; 32 33 Exit3: 34 //释放缓存 35 FreeBuffer(); 36 37 Exit2: 38 //关闭录音设备 39 CloseDev(); 40 41 Exit1: 42 //停止线程 43 StopThread(); 44 45 Exit: 46 return bRet; 47 }
停止录音的代码段如下:
1 //终止录音 2 BOOL CAudioRec::StopRec() 3 { 4 //停止录音 5 if(!CloseRecord()) 6 { 7 return FALSE; 8 } 9 10 //暂停一会 11 Sleep(500); 12 13 //停止线程 14 if(!StopThread()) 15 { 16 return FALSE; 17 } 18 19 //释放录音分配内存 20 if(!FreeBuffer()) 21 { 22 return FALSE; 23 } 24 25 //关闭设备 26 if(!CloseDev()) 27 { 28 return FALSE; 29 } 30 31 32 33 return TRUE; 34 }
线程内数据处理的代码如下:
1 //线程回调函数 2 DWORD WINAPI CAudioRec::AudioInThreadProc(LPVOID lpParameter) 3 { 4 CAudioRec *pAudioRec = (CAudioRec *)lpParameter; 5 6 // char buffer[1024]; 7 8 MSG msg; 9 while(GetMessage(&msg,0,0,0)) 10 { 11 switch(msg.message) 12 { 13 //打开音频设备 14 case MM_WIM_OPEN: 15 break; 16 17 //关闭录音设备 18 case MM_WIM_CLOSE: 19 break; 20 21 //录音设备返回数据 22 case MM_WIM_DATA: 23 WAVEHDR *pWH = (WAVEHDR *)msg.lParam; 24 25 waveInUnprepareHeader((HWAVEIN)msg.wParam,pWH,sizeof(WAVEHDR)); 26 27 //dwBytesRecord表示此缓存中已有的数据大小 28 if(pWH->dwBytesRecorded != AUDIO_BUF_SIZE) 29 break; 30 31 if(pAudioRec->m_pCallBackProc != NULL) 32 pAudioRec->m_pCallBackProc((BYTE *)pWH->lpData,AUDIO_BUF_SIZE,pAudioRec->m_dwUser); 33 34 //获得的数据就在pWH->lpData,长度为pWH->dwBytesRecord 35 waveInPrepareHeader((HWAVEIN)msg.wParam,pWH,sizeof(WAVEHDR)); 36 waveInAddBuffer((HWAVEIN)msg.wParam,pWH,sizeof(WAVEHDR)); 37 } 38 } 39 40 return msg.wParam; 41 }
其实数据处理的关键语句只有下面这一条:pAudioRec->m_pCallBackProc((BYTE *)pWH->lpData,AUDIO_BUF_SIZE,pAudioRec->m_dwUser);
它利用了一个函数指针,由调用者指定相应的处理函数。
今天下午调试了半天,卡在结束录音这个地方。其中语句的执行顺序很重要。
1)先停止录音即调用waveInStop()函数 2)停止线程StopThread() 3)释放为录音准备的缓存 4)关闭录音设备
我自己写的时候将关闭录音设备放在了停止线程的前面,在这里结束录音总是出错。
2018/4/28日增加以下内容:
封装更好的示例参见github项目,nadernt/whistle-recognizer,github地址:https://github.com/nadernt/whistle-recognizer/tree/7bfab2839150b0df7404886a4cf60db1b7be866b
参见此项目中WaveIOLib文件夹内容。