c++实现waveinopen录音功能

在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;
    }
}
2)、当音频流信息设置完成后,接下来需要启动录音设备:使用waveInOpen函数
该函数在使用时,需要一个设备句柄,以后就是在这个设备上录音的。定义一个设备对象:HWAVEIN hWaveIn;//输入设备

MMRESULT mRet=waveInOpen(&hWaveIn, WAVE_MAPPER, &waveForm,(DWORD)MicCallBack, (DWORD)this, CALLBACK_FUNCTION);
if (mRet != MMSYSERR_NOERROR)
{
    return FALSE;
}
waveInOpen函数的第一个参数表示:一个特定的录音设备指针,如果设备启动成功,该参数的值将会被赋值为启动的设备
第二参数表示:需要启动的设备ID。一般不会手动的指定某个设备,而是通过设置WAVE_MAPPER,通过系统查找可用的设备

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

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

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

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

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

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

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

    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));//准备二个波形数据块头用于录音
waveInPrePareHeader的第一个参数表示:录音设备句柄;第二个参数表示:录音的缓冲区对象;第三个参数表示:录音缓冲区结构体的大小。
5)、当准备好录音缓冲区之后,就可以将录音缓冲区加入到指定的录音设备中。该步骤使用waveInAddBuffer函数

waveInAddBuffer(hWaveIn, &Whdr1, sizeof (WAVEHDR));//指定波形数据块为录音输入缓存
waveInAddBuffer(hWaveIn, &Whdr2, sizeof (WAVEHDR));//指定波形数据块为录音输入缓存
分别将缓冲区1和2设置为录音缓冲区。这些缓冲区将会被加入到录音缓冲队列中,缓冲区循环执行。
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;
}
8)、停止录音,使用waveInClose函数执行该操作
delete []pBuffer1;
delete []pBuffer2;//将开辟的缓冲区空间释放
waveInClose(hWaveIn);//停止录音
停止录音时,将会触发WIM_CLOSE消息。
在这个过程中首先执行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;
}
 

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

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值