c++实现waveinopen录音功能

本周的第一篇博客,自己想讲讲刚学习的新知识,Windows下怎样录制音频。因为自己的需要写一个关于网络电话的程序。这个程序毫无疑问必须用到录制音频和播放音频这样的基本功能。首先先说说怎样录制音频:

在Windows中提供了相应的API函数(waveIn这个族的函数)实现录音功能(具体的播放功能是使用waveOut族的函数)

在使用这些函数是,一定要引入相应的头文件:

#include<windows.h>

#include <MMSYSTEM.H>
#pragma comment(lib, "WINMM.LIB")

1)、在开始录音之前,需要首先定义音频流的相关信息:使用WAVEFORMATEX结构体,设置相关的音频流信息。以下是MSDN中的定义:

typedef struct
{
WORD wFormatTag;//表示:波形音频的格式,一般的情况下设置为WAVE_FORMAT_PCM
WORD nChannels;//表示:音频声道的数量。可以是1或者2(现在电脑基本上都是左右两个声道,因此一般设置为2)
DWORD nSamplesPerSec;//表示:每个声道播放和接收的音频的样本频率(一般的频率为8khz,11.025khz,22.05khz,41.1khz)
DWORD nAvgBytesPerSec;//表示:平均的数据传输率,单位为byte/s
WORD nBlockAlign;//表示:以字节为单位的块对齐的大小,一般为: (nChannels*wBitsPerSample)/8
WORD wBitsPerSample;//表示:根据wFormatTag设置的类型,设置采样率的大小,如果设置为WAVE_FORMAT_PCM,则大小为8的整数倍
WORD cbSize;//表示:额外的空间,一般不需要,设置为0
} WAVEFORMATEX; *PWAVEFORMATEX;

定义一个WAVEFORMATEX对象,根据自己的要求设置音频流的信息,如下:

  1. BOOL CIP_PHONEDlg::initAudioDevice()  
  2. {  
  3.     try  
  4.     {  
  5.         waveForm.nSamplesPerSec = 44100; /* sample rate */  
  6.         waveForm.wBitsPerSample = 16; /* sample size */  
  7.         waveForm.nChannels= 2; /* channels*/  
  8.         waveForm.cbSize = 0; /* size of _extra_ info */  
  9.         waveForm.wFormatTag = WAVE_FORMAT_PCM;  
  10.         waveForm.nBlockAlign = (waveForm.wBitsPerSample * waveForm.nChannels) >> 3;  
  11.         waveForm.nAvgBytesPerSec = waveForm.nBlockAlign * waveForm.nSamplesPerSec;  
  12.         return TRUE;  
  13.     }  
  14.     catch(...)  
  15.     {  
  16.         return FALSE;  
  17.     }  
  18. }  
2)、当音频流信息设置完成后,接下来需要启动录音设备:使用waveInOpen函数

该函数在使用时,需要一个设备句柄,以后就是在这个设备上录音的。定义一个设备对象:HWAVEIN hWaveIn;//输入设备

  1. MMRESULT mRet=waveInOpen(&hWaveIn, WAVE_MAPPER, &waveForm,(DWORD)MicCallBack, (DWORD)this, CALLBACK_FUNCTION);  
  2. if (mRet != MMSYSERR_NOERROR)  
  3. {  
  4.     return FALSE;  
  5. }  
waveInOpen函数的第一个参数表示:一个特定的录音设备指针,如果设备启动成功,该参数的值将会被赋值为启动的设备

第二参数表示:需要启动的设备ID。一般不会手动的指定某个设备,而是通过设置WAVE_MAPPER,通过系统查找可用的设备

第三个参数表示:音频流信息对象的指针。这个参数就是我们第一步设置的对象

第四个参数表示:录音消息的处理程序,可以设置为一个函数、或者事件句柄、窗口句柄、一个特定的线程。也就是说录音消息产生后,由这个参数对应的值来处理该消息。包括关闭录音、缓冲区已满、开启设备。

第5个参数表示:第四个参数的参数列表

第6个参数表示:打开设备的标示符。对应第四个参数,如果第四个参数设置为函数,则第6个参数的值为CALLBACK_FUNCTION;如果是事件,则为CALLBACK_EVENT;如果为窗体句柄(第5个参数设置为0),则为CALLBACK_WINDOW;如过设置为0,则为CALLBACK_NULL;如果为线程,则为CALLBACK_THREAD

表示的意思就是当录音产生消息后,由谁来处理相应的消息。

注意:要想该函数成功执行,必须在开始之前,有录音设备的存在(台式电脑一定要插入麦克风才可以被检测到)

3)、当录音设备启动后,接下来需要声明两个缓冲区和两个缓冲区头部结构体WAVEHDR对象,缓冲区用来存放录音音频,并用缓冲区初始化头部对象

  1. <span style="white-space: pre;">    </span>pBuffer1 = new BYTE[bufsize];  
  2.     if (pBuffer1 == NULL)  
  3.     {  
  4.         return FALSE;  
  5.     }  
  6.     memset(pBuffer1,0,bufsize);  
  7.     Whdr1.lpData = (LPSTR)pBuffer1;  
  8.     Whdr1.dwBufferLength = bufsize;  
  9.     Whdr1.dwBytesRecorded = 0;  
  10.     Whdr1.dwUser = 0;  
  11.     Whdr1.dwFlags = 0;  
  12.     Whdr1.dwLoops = 1;  
  13.   
  14.     pBuffer2 = new BYTE[bufsize];  
  15.     if (pBuffer2 == NULL)  
  16.     {  
  17.         return FALSE;  
  18.     }  
  19.     memset(pBuffer2,0,bufsize);  
  20.     Whdr2.lpData = (LPSTR)pBuffer2;  
  21.     Whdr2.dwBufferLength = bufsize;  
  22.     Whdr2.dwBytesRecorded = 0;  
  23.     Whdr2.dwUser = 0;  
  24.     Whdr2.dwFlags = 0;  
  25.     Whdr2.dwLoops = 1;  

WAVEHDR对象定义如下:
typedef struct { 
    LPSTR      lpData;//缓冲区存放的内容
    DWORD      dwBufferLength; //缓冲区的大小
    DWORD      dwBytesRecorded; //缓冲区中存放的字节数
    DWORD_PTR  dwUser; //
    DWORD      dwFlags; //
    DWORD      dwLoops; //
    struct wavehdr_tag * lpNext;// 
    DWORD_PTR reserved; //
} WAVEHDR;
4)、接下来将这两个头部对象,加入到准备的录音缓冲区中。该过程使用waveInPrepareHeader函数。
  1. waveInPrepareHeader(hWaveIn, &Whdr1, sizeof(WAVEHDR));//准备一个波形数据块头用于录音  
  2. waveInPrepareHeader(hWaveIn, &Whdr2, sizeof(WAVEHDR));//准备二个波形数据块头用于录音  
waveInPrePareHeader的第一个参数表示:录音设备句柄;第二个参数表示:录音的缓冲区对象;第三个参数表示:录音缓冲区结构体的大小。

5)、当准备好录音缓冲区之后,就可以将录音缓冲区加入到指定的录音设备中。该步骤使用waveInAddBuffer函数

  1. waveInAddBuffer(hWaveIn, &Whdr1, sizeof (WAVEHDR));//指定波形数据块为录音输入缓存  
  2. waveInAddBuffer(hWaveIn, &Whdr2, sizeof (WAVEHDR));//指定波形数据块为录音输入缓存  
分别将缓冲区1和2设置为录音缓冲区。这些缓冲区将会被加入到录音缓冲队列中,缓冲区循环执行。

6)、开始录音,使用waveInStart函数

waveInStart(hWaveIn);//开始录音

这个函数的意思就是,通过hWaveIn录音设备,将波形音频放入录音缓冲区(前面已经指定了缓冲区)

7)、当缓冲区满时,waveinstart函数,就会自动的调用waveInOpen函数中制定的函数/窗体/事件;通过该函数,用户可以将缓冲区的波形文件发送给其他的用户,也可以将缓冲区的文件保存,即就是用户对缓冲区的拷贝。声卡自动将音频缓冲区从缓冲队列中删除。拷贝完成后,就将该缓冲区以及对应的音频头文件初始化,并通过waveInAddBuffer函数重新加入录音缓冲队列中。

  1. DWORD CIP_PHONEDlg::MicCallBack(HWAVEIN hWaveIn,UINT uMsg,DWORD dwInstance,DWORD dwParam1,DWORD dwParam2)  
  2. {//所有的这些录音消息都是有录音函数自动触发的,开发者不需要自己触发  
  3.     CIP_PHONEDlg* pwnd=(CIP_PHONEDlg*)dwInstance;//表示录音的窗体  
  4.     PWAVEHDR whd=(PWAVEHDR)dwParam1; //录音的头结构体对象  
  5.     switch(uMsg)  
  6.     {  
  7.     case WIM_OPEN://打开录音设备,这里不做处理  
  8.         break;  
  9.     case WIM_DATA://表示数据缓冲区已满,我们将信息写入一个pcm文件  
  10.         //保存数据  
  11.         pwnd->pf=fopen( pwnd->soundName, "ab+");//一定要以二进制数据写入,否则录制的音频会出现杂音  
  12.         Sleep(1000);//等待声音录制1s  
  13.         fwrite(whd->lpData, 1, whd->dwBufferLength, pwnd->pf);  
  14.         if (pwnd->isGetSound)  
  15.         {  
  16.             waveInAddBuffer(hWaveIn,whd,sizeof(WAVEHDR));  
  17.         }  
  18.         fclose(pwnd->pf);  
  19.         break;  
  20.     case WIM_CLOSE://停止录音  
  21.         waveInStop(hWaveIn);  
  22.         waveInReset(hWaveIn);     
  23.         waveInClose(hWaveIn);  
  24.         break;  
  25.     default:  
  26.         break;  
  27.     }  
  28.     return 0;  
  29. }  
8)、停止录音,使用waveInClose函数执行该操作
  1. delete []pBuffer1;  
  2. delete []pBuffer2;//将开辟的缓冲区空间释放  
  3. waveInClose(hWaveIn);//停止录音  
停止录音时,将会触发WIM_CLOSE消息。

在这个过程中首先执行waveInStop函数:表示禁止向输入缓冲区中输入波形数据;

然后执行waveInReset函数:表示停止波形数据的输入并且将当前的位置为0,将所有挂起的输入缓冲区设置为完成,并返回给应用程序(其实就是一个复位操作)

最后执行waveInClose函数:表示关闭录音设备

通过上面8步运行,可以正常的录入一段音频了。

详细的代码:

类的定义:

  1. #include <MMSYSTEM.H>  
  2. #pragma comment(lib, "WINMM.LIB")  
  3.   
  4. class CIP_PHONEDlg : public CDialog  
  5. {  
  6. private://音频的采集  
  7.     BOOL initAudioDevice();//初始话音频结构体  
  8.     BOOL getSound();//采集声音  
  9.     static DWORD CALLBACK MicCallBack(HWAVEIN hWaveIn,UINT uMsg,DWORD dwInstance,DWORD dwParam1,DWORD dwParam2);//采集声音的信息处理函数  
  10.     void getTimeStr();//获得录音文件的名称  
  11. public://采集声音时的变量  
  12.     HWAVEIN hWaveIn;//输入设备  
  13.     WAVEFORMATEX waveForm;//采集声音的格式,结构体  
  14.     BYTE *pBuffer1,*pBuffer2;//采集声音的缓冲区  
  15.     WAVEHDR Whdr1,Whdr2;//采集音频时数据缓冲的结构体  
  16.     bool isGetSound;//是否采集声音  
  17.     FILE *pf;//音频文件的句柄  
  18.     CString soundName;//录音的音频文件名称  
  19. };  
具体的代码实现
  1. BOOL CIP_PHONEDlg::initAudioDevice()  
  2. {  
  3.     try  
  4.     {  
  5.         waveForm.nSamplesPerSec = 44100; /* sample rate */  
  6.         waveForm.wBitsPerSample = 16; /* sample size */  
  7.         waveForm.nChannels= 2; /* channels*/  
  8.         waveForm.cbSize = 0; /* size of _extra_ info */  
  9.         waveForm.wFormatTag = WAVE_FORMAT_PCM;  
  10.         waveForm.nBlockAlign = (waveForm.wBitsPerSample * waveForm.nChannels) >> 3;  
  11.         waveForm.nAvgBytesPerSec = waveForm.nBlockAlign * waveForm.nSamplesPerSec;  
  12.         return TRUE;  
  13.     }  
  14.     catch(...)  
  15.     {  
  16.         return FALSE;  
  17.     }  
  18. }  
  19. void CIP_PHONEDlg::getTimeStr()  
  20. {//获得录音文件的名称  
  21.     try  
  22.     {  
  23.         SYSTEMTIME times;  
  24.         ::GetSystemTime(×);  
  25.         CString sTimeStr="";  
  26.         sTimeStr.Format("%d_%d_%d_%d_%d_%d_%d",times.wYear,times.wMonth,times.wDay,times.wHour,times.wMinute,times.wSecond,times.wMilliseconds);  
  27.         this->soundName="./RecordSounds\\"+sTimeStr+".pcm";//获得音频的存放路径  
  28.     }  
  29.     catch(...)  
  30.     {  
  31.         this->soundName="./RecordSounds\\出错文件.pcm";//获得音频的存放路径  
  32.     }  
  33.       
  34.     return ;  
  35. }  
  36. BOOL CIP_PHONEDlg::getSound()  
  37. {  
  38.     if (!initAudioDevice())  
  39.     {  
  40.         return FALSE;  
  41.     }  
  42.     MMRESULT mRet=waveInOpen(&hWaveIn, WAVE_MAPPER, &waveForm,(DWORD)MicCallBack, (DWORD)this, CALLBACK_FUNCTION);  
  43.     if (mRet != MMSYSERR_NOERROR)  
  44.     {  
  45.         return FALSE;  
  46.     }  
  47.     getTimeStr();//生成录音文件的名称  
  48.     pBuffer1 = new BYTE[bufsize];  
  49.     if (pBuffer1 == NULL)  
  50.     {  
  51.         return FALSE;  
  52.     }  
  53.     memset(pBuffer1,0,bufsize);  
  54.     Whdr1.lpData = (LPSTR)pBuffer1;  
  55.     Whdr1.dwBufferLength = bufsize;  
  56.     Whdr1.dwBytesRecorded = 0;  
  57.     Whdr1.dwUser = 0;  
  58.     Whdr1.dwFlags = 0;  
  59.     Whdr1.dwLoops = 1;  
  60.   
  61.     pBuffer2 = new BYTE[bufsize];  
  62.     if (pBuffer2 == NULL)  
  63.     {  
  64.         return FALSE;  
  65.     }  
  66.     memset(pBuffer2,0,bufsize);  
  67.     Whdr2.lpData = (LPSTR)pBuffer2;  
  68.     Whdr2.dwBufferLength = bufsize;  
  69.     Whdr2.dwBytesRecorded = 0;  
  70.     Whdr2.dwUser = 0;  
  71.     Whdr2.dwFlags = 0;  
  72.     Whdr2.dwLoops = 1;  
  73.     waveInPrepareHeader(hWaveIn, &Whdr1, sizeof(WAVEHDR));//准备一个波形数据块头用于录音  
  74.     waveInPrepareHeader(hWaveIn, &Whdr2, sizeof(WAVEHDR));//准备二个波形数据块头用于录音  
  75.     waveInAddBuffer(hWaveIn, &Whdr1, sizeof (WAVEHDR));//指定波形数据块为录音输入缓存  
  76.     waveInAddBuffer(hWaveIn, &Whdr2, sizeof (WAVEHDR));//指定波形数据块为录音输入缓存  
  77.     waveInStart(hWaveIn);//开始录音  
  78.   
  79.     delete []pBuffer1;  
  80.     delete []pBuffer2;  
  81.     waveInClose(hWaveIn);//停止录音  
  82.     return TRUE;  
  83. }  
  84.   
  85. DWORD CIP_PHONEDlg::MicCallBack(HWAVEIN hWaveIn,UINT uMsg,DWORD dwInstance,DWORD dwParam1,DWORD dwParam2)  
  86. {  
  87.     CIP_PHONEDlg* pwnd=(CIP_PHONEDlg*)dwInstance;  
  88.     PWAVEHDR whd=(PWAVEHDR)dwParam1;   
  89.     switch(uMsg)  
  90.     {  
  91.     case WIM_OPEN:  
  92.         break;  
  93.     case WIM_DATA:  
  94.         //保存数据  
  95.         pwnd->pf=fopen( pwnd->soundName, "ab+");//一定要以二进制数据写入,否则录入的音频会有杂音  
  96.         fwrite(whd->lpData, 1, whd->dwBufferLength, pwnd->pf);  
  97.         if (pwnd->isGetSound)  
  98.         {  
  99.             waveInAddBuffer(hWaveIn,whd,sizeof(WAVEHDR));  
  100.         }  
  101.         fclose(pwnd->pf);  
  102.         break;  
  103.     case WIM_CLOSE:  
  104.         waveInStop(hWaveIn);  
  105.         waveInReset(hWaveIn);     
  106.         waveInClose(hWaveIn);  
  107.         break;  
  108.     default:  
  109.         break;  
  110.     }  
  111.     return 0;  
  112. }  

注意:在录音时,可能需要设置自己的电脑,使麦克风可以录音。

好了关于录音的相关操作,今天就写在这里,具体的播放音频的操作,将在下一节讲解。




版权声明:本文为博主原创文章,未经博主允许不得转载。 http://blog.csdn.net/xbk123123/article/details/53611283

猜你喜欢

转载自blog.csdn.net/u014421422/article/details/79497776