转载自:http://blog.csdn.net/xbk123123/article/details/53611283
本周的第一篇博客,自己想讲讲刚学习的新知识,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对象,根据自己的要求设置音频流的信息,如下:
- BOOL CIP_PHONEDlg::initAudioDevice()
- {
- try
- {
- waveForm.nSamplesPerSec = 44100; /* sample rate */
- waveForm.wBitsPerSample = 16; /* sample size */
- waveForm.nChannels= 2; /* channels*/
- waveForm.cbSize = 0; /* size of _extra_ info */
- waveForm.wFormatTag = WAVE_FORMAT_PCM;
- waveForm.nBlockAlign = (waveForm.wBitsPerSample * waveForm.nChannels) >> 3;
- waveForm.nAvgBytesPerSec = waveForm.nBlockAlign * waveForm.nSamplesPerSec;
- return TRUE;
- }
- catch(...)
- {
- return FALSE;
- }
- }
该函数在使用时,需要一个设备句柄,以后就是在这个设备上录音的。定义一个设备对象:HWAVEIN hWaveIn;//输入设备
- MMRESULT mRet=waveInOpen(&hWaveIn, WAVE_MAPPER, &waveForm,(DWORD)MicCallBack, (DWORD)this, CALLBACK_FUNCTION);
- if (mRet != MMSYSERR_NOERROR)
- {
- return FALSE;
- }
第二参数表示:需要启动的设备ID。一般不会手动的指定某个设备,而是通过设置WAVE_MAPPER,通过系统查找可用的设备
第三个参数表示:音频流信息对象的指针。这个参数就是我们第一步设置的对象
第四个参数表示:录音消息的处理程序,可以设置为一个函数、或者事件句柄、窗口句柄、一个特定的线程。也就是说录音消息产生后,由这个参数对应的值来处理该消息。包括关闭录音、缓冲区已满、开启设备。
第5个参数表示:第四个参数的参数列表
第6个参数表示:打开设备的标示符。对应第四个参数,如果第四个参数设置为函数,则第6个参数的值为CALLBACK_FUNCTION;如果是事件,则为CALLBACK_EVENT;如果为窗体句柄(第5个参数设置为0),则为CALLBACK_WINDOW;如过设置为0,则为CALLBACK_NULL;如果为线程,则为CALLBACK_THREAD
表示的意思就是当录音产生消息后,由谁来处理相应的消息。
注意:要想该函数成功执行,必须在开始之前,有录音设备的存在(台式电脑一定要插入麦克风才可以被检测到)
3)、当录音设备启动后,接下来需要声明两个缓冲区和两个缓冲区头部结构体WAVEHDR对象,缓冲区用来存放录音音频,并用缓冲区初始化头部对象
- <span style="white-space:pre;"> </span>pBuffer1 = new BYTE[bufsize];
- if (pBuffer1 == NULL)
- {
- return FALSE;
- }
- memset(pBuffer1,0,bufsize);
- Whdr1.lpData = (LPSTR)pBuffer1;
- Whdr1.dwBufferLength = bufsize;
- Whdr1.dwBytesRecorded = 0;
- Whdr1.dwUser = 0;
- Whdr1.dwFlags = 0;
- Whdr1.dwLoops = 1;
- pBuffer2 = new BYTE[bufsize];
- if (pBuffer2 == NULL)
- {
- return FALSE;
- }
- memset(pBuffer2,0,bufsize);
- Whdr2.lpData = (LPSTR)pBuffer2;
- Whdr2.dwBufferLength = bufsize;
- Whdr2.dwBytesRecorded = 0;
- Whdr2.dwUser = 0;
- Whdr2.dwFlags = 0;
- 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函数。
- waveInPrepareHeader(hWaveIn, &Whdr1, sizeof(WAVEHDR));//准备一个波形数据块头用于录音
- waveInPrepareHeader(hWaveIn, &Whdr2, sizeof(WAVEHDR));//准备二个波形数据块头用于录音
5)、当准备好录音缓冲区之后,就可以将录音缓冲区加入到指定的录音设备中。该步骤使用waveInAddBuffer函数
- waveInAddBuffer(hWaveIn, &Whdr1, sizeof (WAVEHDR));//指定波形数据块为录音输入缓存
- waveInAddBuffer(hWaveIn, &Whdr2, sizeof (WAVEHDR));//指定波形数据块为录音输入缓存
6)、开始录音,使用waveInStart函数
waveInStart(hWaveIn);//开始录音
这个函数的意思就是,通过hWaveIn录音设备,将波形音频放入录音缓冲区(前面已经指定了缓冲区)
7)、当缓冲区满时,waveinstart函数,就会自动的调用waveInOpen函数中制定的函数/窗体/事件;通过该函数,用户可以将缓冲区的波形文件发送给其他的用户,也可以将缓冲区的文件保存,即就是用户对缓冲区的拷贝。声卡自动将音频缓冲区从缓冲队列中删除。拷贝完成后,就将该缓冲区以及对应的音频头文件初始化,并通过waveInAddBuffer函数重新加入录音缓冲队列中。
- DWORD CIP_PHONEDlg::MicCallBack(HWAVEIN hWaveIn,UINT uMsg,DWORD dwInstance,DWORD dwParam1,DWORD dwParam2)
- {//所有的这些录音消息都是有录音函数自动触发的,开发者不需要自己触发
- CIP_PHONEDlg* pwnd=(CIP_PHONEDlg*)dwInstance;//表示录音的窗体
- PWAVEHDR whd=(PWAVEHDR)dwParam1; //录音的头结构体对象
- switch(uMsg)
- {
- case WIM_OPEN://打开录音设备,这里不做处理
- break;
- case WIM_DATA://表示数据缓冲区已满,我们将信息写入一个pcm文件
- //保存数据
- pwnd->pf=fopen( pwnd->soundName, "ab+");//一定要以二进制数据写入,否则录制的音频会出现杂音
- Sleep(1000);//等待声音录制1s
- fwrite(whd->lpData, 1, whd->dwBufferLength, pwnd->pf);
- if (pwnd->isGetSound)
- {
- waveInAddBuffer(hWaveIn,whd,sizeof(WAVEHDR));
- }
- fclose(pwnd->pf);
- break;
- case WIM_CLOSE://停止录音
- waveInStop(hWaveIn);
- waveInReset(hWaveIn);
- waveInClose(hWaveIn);
- break;
- default:
- break;
- }
- return 0;
- }
- delete []pBuffer1;
- delete []pBuffer2;//将开辟的缓冲区空间释放
- waveInClose(hWaveIn);//停止录音
在这个过程中首先执行waveInStop函数:表示禁止向输入缓冲区中输入波形数据;
然后执行waveInReset函数:表示停止波形数据的输入并且将当前的位置为0,将所有挂起的输入缓冲区设置为完成,并返回给应用程序(其实就是一个复位操作)
最后执行waveInClose函数:表示关闭录音设备
通过上面8步运行,可以正常的录入一段音频了。
详细的代码:
类的定义:
- #include <MMSYSTEM.H>
- #pragma comment(lib, "WINMM.LIB")
- class CIP_PHONEDlg : public CDialog
- {
- private://音频的采集
- BOOL initAudioDevice();//初始话音频结构体
- BOOL getSound();//采集声音
- static DWORD CALLBACK MicCallBack(HWAVEIN hWaveIn,UINT uMsg,DWORD dwInstance,DWORD dwParam1,DWORD dwParam2);//采集声音的信息处理函数
- void getTimeStr();//获得录音文件的名称
- public://采集声音时的变量
- HWAVEIN hWaveIn;//输入设备
- WAVEFORMATEX waveForm;//采集声音的格式,结构体
- BYTE *pBuffer1,*pBuffer2;//采集声音的缓冲区
- WAVEHDR Whdr1,Whdr2;//采集音频时数据缓冲的结构体
- bool isGetSound;//是否采集声音
- FILE *pf;//音频文件的句柄
- CString soundName;//录音的音频文件名称
- };
- BOOL CIP_PHONEDlg::initAudioDevice()
- {
- try
- {
- waveForm.nSamplesPerSec = 44100; /* sample rate */
- waveForm.wBitsPerSample = 16; /* sample size */
- waveForm.nChannels= 2; /* channels*/
- waveForm.cbSize = 0; /* size of _extra_ info */
- waveForm.wFormatTag = WAVE_FORMAT_PCM;
- waveForm.nBlockAlign = (waveForm.wBitsPerSample * waveForm.nChannels) >> 3;
- waveForm.nAvgBytesPerSec = waveForm.nBlockAlign * waveForm.nSamplesPerSec;
- return TRUE;
- }
- catch(...)
- {
- return FALSE;
- }
- }
- void CIP_PHONEDlg::getTimeStr()
- {//获得录音文件的名称
- try
- {
- SYSTEMTIME times;
- ::GetSystemTime(×);
- CString sTimeStr="";
- sTimeStr.Format("%d_%d_%d_%d_%d_%d_%d",times.wYear,times.wMonth,times.wDay,times.wHour,times.wMinute,times.wSecond,times.wMilliseconds);
- this->soundName="./RecordSounds\\"+sTimeStr+".pcm";//获得音频的存放路径
- }
- catch(...)
- {
- this->soundName="./RecordSounds\\出错文件.pcm";//获得音频的存放路径
- }
- return ;
- }
- BOOL CIP_PHONEDlg::getSound()
- {
- if (!initAudioDevice())
- {
- return FALSE;
- }
- MMRESULT mRet=waveInOpen(&hWaveIn, WAVE_MAPPER, &waveForm,(DWORD)MicCallBack, (DWORD)this, CALLBACK_FUNCTION);
- if (mRet != MMSYSERR_NOERROR)
- {
- return FALSE;
- }
- getTimeStr();//生成录音文件的名称
- pBuffer1 = new BYTE[bufsize];
- if (pBuffer1 == NULL)
- {
- return FALSE;
- }
- memset(pBuffer1,0,bufsize);
- Whdr1.lpData = (LPSTR)pBuffer1;
- Whdr1.dwBufferLength = bufsize;
- Whdr1.dwBytesRecorded = 0;
- Whdr1.dwUser = 0;
- Whdr1.dwFlags = 0;
- Whdr1.dwLoops = 1;
- pBuffer2 = new BYTE[bufsize];
- if (pBuffer2 == NULL)
- {
- return FALSE;
- }
- memset(pBuffer2,0,bufsize);
- Whdr2.lpData = (LPSTR)pBuffer2;
- Whdr2.dwBufferLength = bufsize;
- Whdr2.dwBytesRecorded = 0;
- Whdr2.dwUser = 0;
- Whdr2.dwFlags = 0;
- Whdr2.dwLoops = 1;
- waveInPrepareHeader(hWaveIn, &Whdr1, sizeof(WAVEHDR));//准备一个波形数据块头用于录音
- waveInPrepareHeader(hWaveIn, &Whdr2, sizeof(WAVEHDR));//准备二个波形数据块头用于录音
- waveInAddBuffer(hWaveIn, &Whdr1, sizeof (WAVEHDR));//指定波形数据块为录音输入缓存
- waveInAddBuffer(hWaveIn, &Whdr2, sizeof (WAVEHDR));//指定波形数据块为录音输入缓存
- waveInStart(hWaveIn);//开始录音
- delete []pBuffer1;
- delete []pBuffer2;
- waveInClose(hWaveIn);//停止录音
- return TRUE;
- }
- DWORD CIP_PHONEDlg::MicCallBack(HWAVEIN hWaveIn,UINT uMsg,DWORD dwInstance,DWORD dwParam1,DWORD dwParam2)
- {
- CIP_PHONEDlg* pwnd=(CIP_PHONEDlg*)dwInstance;
- PWAVEHDR whd=(PWAVEHDR)dwParam1;
- switch(uMsg)
- {
- case WIM_OPEN:
- break;
- case WIM_DATA:
- //保存数据
- pwnd->pf=fopen( pwnd->soundName, "ab+");//一定要以二进制数据写入,否则录入的音频会有杂音
- fwrite(whd->lpData, 1, whd->dwBufferLength, pwnd->pf);
- if (pwnd->isGetSound)
- {
- waveInAddBuffer(hWaveIn,whd,sizeof(WAVEHDR));
- }
- fclose(pwnd->pf);
- break;
- case WIM_CLOSE:
- waveInStop(hWaveIn);
- waveInReset(hWaveIn);
- waveInClose(hWaveIn);
- break;
- default:
- break;
- }
- return 0;
- }
注意:在录音时,可能需要设置自己的电脑,使麦克风可以录音。
好了关于录音的相关操作,今天就写在这里,具体的播放音频的操作,将在下一节讲解。