zoukankan      html  css  js  c++  java
  • MFC录制音频和播放音频

    一、录制音频

      在windows中提供了相应的API函数(waveIn这个族的函数)实现录音功能;在使用这些函数时,一定要引入相应的头文件

    #include <windows.h>

    #include <Mmsystem.h>

    #pragram 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,44.1khz)
    DWORD nAvgBytesPerSec;  // 平均的数据传输率,单位为byte/s
    WORD nBlockAlign;       // 以字节为单位的块对齐的大小,一般为:(nChannels * wBitsPerSample)/8
    WORD wBitsPerSample;    // 根据wFormatTag设置的类型,设置采样率的大小,如果设置为WAVE_FORMAT_PCM,则大小为8的整倍数
    WORD cbSize;            // 额外的空间,一般不需要,设置为0
    }WAVEFORMATEX, *PWAVEFORMATEX;
    

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

    WAVEFORMATEX waveFormat;
    waveFormat.nSamplesPerSec = 44100;
    waveFormat.wBitsPerSample = 16;
    waveFormat.nChannels = 2;
    waveFormat.cbSize = 0;
    waveFormat.wFormatTag = WAVE_FORMAT_PCM;
    waveFormat.nBlockAlign = (waveFormat.wBitsPerSample * waveFormat.nChannels)/8;
    waveFormat.nAvgBytesPerSec = waveFormat.nBlockAlign * waveFormat.nSamplesPersec;
    

    2、当音频流信息设置完成后,接下来需要启动录音设备:使用waveInOpen函数。

    waveInOpen函数原型为:

    WINMMAPI
    MMRESULT
    WINAPI
    waveInOpen(
        _Out_opt_ LPHWAVEIN phwi,    // 一个特定的录音设备指针,如果设备启动成功,该参数的值将会被赋值为启动的设备
        _In_ UINT uDeviceID,       // 需要启动的设备ID。一般不会手动指定某个设备,而是通过设置WAVE_MAPPER,通过系统查找可用设备
        _In_ LPCWAVEFORMATEX pwfx,     // 音频流信息对象的指针。这个参数就是我们第一步设置的对象
        _In_opt_ DWORD_PTR dwCallback,  // 录音消息的处理程序,可以设置一个函数、事件句柄、窗口句柄、一个特定的线程。也就是说录音消息产生后,由这个参数对应的值来处理该消息。包括关闭录音、缓冲区已满、开启设备
        _In_opt_ DWORD_PTR dwInstance,   // dwCallback参数的参数列表
        _In_ DWORD fdwOpen               // 打开设备的标识符。对应dwCallback,如果第四个参数设置为函数,则这个参数的值为CALLBACK_FUNCTION;如果为线程,则为CALLBACK_THREAD
        );

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

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

    INT bufSize = 512;
    
    BYTE *pBuffer1 = new BYTE[bufSize];
    if (pBuffer1 == NULL) return;
    memset(pBuffer1, 0, bufSize);
    
    WAVEHDR wHdr1;
    wHdr1.lpData = (LPSTR)pBuffer1;
    wHdr1.dwBufferLength = bufSize;
    wHdr1.dwBytesRecorded = 0;
    wHdr1.dwUser = 0;
    wHdr1.dwFlags = 0;
    wHdr1.dwLoops = 1;
    
    BYTE *pBuffer2 = new BYTE[bufSize];
    if (pBuffer2 == NULL) return;
    memset(pBuffer2,0, bufSize);
    
    WAVEHDR wHdr2;
    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 dwButesRecorded; // 缓冲区中存放的字节数
    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*)dwInstace;     // 表示录音的窗体
        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->lpData;
    delete [] pBuffer2->lpData;
    waveInClose(hWaveIn); // 停止录音
    

    停止录音时,将会触发WIM_CLOSE消息。

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

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

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

      

  • 相关阅读:
    Redis学习六(java中使用redis)
    Redis学习五(新数据类型)
    Redis学习四(发布和订阅)
    Redis学习三(配置文件说明)
    Redis学习二(常用五大数据类型)
    Redis学习一(源码安装redis)
    RocketMq常见问题记录
    总结多种容器化技术对比
    关于Redis集群部署和持久化的相关问题
    配置Jenkins连接kubernetes的Pod Template模板
  • 原文地址:https://www.cnblogs.com/mupiaomiao/p/6425308.html
Copyright © 2011-2022 走看看