MFC-音频

一.利用API

基础知识

有几个与声音采集和播放相关的专业术语必须要先了解一下,否则,后面的介绍将无法展开。语音采集指的是从麦克风采集音频数据,即声音样本转换成数字信号。其涉及到几个重要的参数:采样率、采样位数、声道数。

简单的来说:

采样率:即采样频率,就是在1秒内进行采集动作的次数。

    常取值8.0KHz,11.025 KHz,22.05 KHz,44.1 KHz

    对于PCM音频,这个值不能大于44.1 KHz

采样位数:又叫采样深度,就是每次采集动作得到的数据长度,即使用多少个bit来记录一个样本。

声道数:一般是单声道或双声道(立体声)。普通的麦克风采集几乎都是单声道的。

这样,1秒钟采集得到的声音数据的大小为(单位byte):(采样频率×采样位数×声道数x时间)/8。

音频帧:通第一个音频帧的时长为10ms,即每10ms的数据构成一个音频帧。假设:采样率16k、采样位数16bit、声道数1,那么一个10ms的音频帧的大小为:(16000*16*1*0.01)/8=320字节。计算式中的0.01为秒,即10ms

 

需要

#include <mmsystem.h>
#pragma comment(lib, "WINMM.LIB")

 

播放系统wav声音 

方法一:

复制代码
PlaySound(_T("C:\\WINDOWS\\Media\\Windows 启动.wav"), NULL, SND_FILENAME | SND_ASYNC);  //播放系统开机的声音
    /*
    需要:#include <mmsystem.h>
    #pragma comment(lib, "WINMM.LIB")
    
    参数1:LPCSTR pszSound     可以是WAVE文件的名字,或是WAV资源的名字,或是内存中声音数据的指针,
            或是在系统注册表WIN.INI中定义的系统事件声音。
            如果该参数为NULL则停止正在播放的声音
    参数2:HMODULE hmod  应用程序的实例句柄,当播放WAV资源时要用到该参数,否则它必须为NULL
    参数3:DWORD fdwSound   是标志的组合,如下表所示。若成功则函数返回TRUE,否则返回FALSE
            播放标志
            SND_APPLICATION      用应用程序指定的关联来播放声音
            SND_ALIAS            参数1指定了注册表或WIN.INI中的系统事件的别名
            SND_ALIAS_ID         参数1指定了预定义的声音标识符
            SND_ASYNC            用异步方式播放声音,PlaySound函数在开始播放后立即返回
            SND_FILENAME         参数1指定了WAVE文件名
            SND_LOOP             重复播放声音,必须与SND_ASYNC标志一块使用
            SND_MEMORY           播放载入到内存中的声音,此时pszSound是指向声音数据的指针
            SND_NODEFAULT        不播放缺省声音,若无此标志,则PlaySound在没找到声音时会播放缺省声音
            SND_NOSTOP           PlaySound不打断原来的声音播出并立即返回FALSE
            SND_NOWAIT           如果驱动程序正忙则函数就不播放声音并立即返回
            SND_PURGE            停止所有与调用任务有关的声音。若参数pszSound为NULL,就停止所有的声音,否则,停止pszSound指定的声音
            SND_RESOURCE         pszSound参数是WAVE资源的标识符,这时要用到hmod参数
            SND_SYNC             同步播放声音,在播放完后PlaySound函数才返回

    */
复制代码

 

方法二:

sndPlaySound(_T("C:\\WINDOWS\\Media\\Windows 启动.wav"), SND_ASYNC);

 

 

播放本地wav文件 

方法一

PlaySound(_T("D:\\bb\\太想念.wav"), NULL, SND_FILENAME | SND_ASYNC);  // 播放本地wav文件 

 

方法二

sndPlaySound(_T("D:\\bb\\太想念.wav"), SND_ASYNC);

 

 

播放资源中wav声音 

1.添加wav声音资源选项

打开资源视图界面,右键添加资源 ,初始界面中没有wav格式的选项,选择 自定义 的选项,资源类型输入WAV

 

2.添加资源

选中-->导入

 3.在属性中修改它的ID

 4.代码

    
    PlaySound(MAKEINTRESOURCE(WAVE1), AfxGetResourceHandle(), SND_ASYNC | SND_RESOURCE | SND_NODEFAULT);  //播放资源中声音
    //WAVE1  是wav资源ID

 

获取系统中存在的波形音频输入设备数 

    int nReturn = waveInGetNumDevs();//获取系统中存在的波形音频输入设备数
    //返回值为零表示不存在设备或发生错误

    CString str;
    str.Format(_T("系统中存在的音频输入设备数=%d\r\n"), nReturn);
    ::OutputDebugString(str);

 

枚举输入设备 

复制代码
    CString str;
    int nReturn = waveInGetNumDevs();//获取系统中存在的波形音频输入设备数
    


    //********枚举输入的设备*****************
    for (int i = 0; i < nReturn; i++)
    {
        WAVEINCAPS wic;  //WAVEINCAPS结构描述波形音频输入设备信息的结构
        /*
        typedef struct waveincaps_tag {
            WORD    wMid;         //制造商标识符
            WORD    wPid;         //产品标识符
            VERSION vDriverVersion;  //设备驱动程序的版本号。 高阶字节是主要版本号,低顺序字节是次要版本号
            char    szPname[MAXPNAMELEN]; //以 null 结尾的制造商名称
            DWORD   dwFormats;  //支持的标准格式,可以是以下组合:
                        看:https://learn.microsoft.com/zh-cn/windows/win32/api/mmeapi/ns-mmeapi-waveincaps   
            WORD    wChannels;  //支持的声道数
            WORD   wReserved1;      //保留参数
    } WAVEINCAPS, *PWAVEINCAPS, *NPWAVEINCAPS, *LPWAVEINCAPS;
                
        */


        MMRESULT mmresult=waveInGetDevCaps(i, &wic, sizeof(WAVEINCAPS)); //获取给定的波形音频输入设备的信息
        /*
        参数1:UINT uDeviceID  音频输入设备标识【设备ID】,也可以为一个打开的音频输入设备的句柄
        参数2:LPWAVEINCAPS pwic    WAVEINCAPS 结构的指针
        参数3:UINT cbwic     WAVEINCAPS 结构的大小(以字节为单位)
        返回值:MMRESULT 函数执行的结果
                MMSYSERR_NOERROR   表示执行成功
                MMSYSERR_BADDEVICEID 索引越界 
                MMSYSERR_NODRIVER 没有就绪的设备 
                MMSYSERR_NOMEM 不能分配或者锁定内存
        
        */


        str.Format(_T("i=%d 设备名:%s\r\n "),i, wic.szPname);
        ::OutputDebugString(str);
        

        /*
        我的设备:
        i=0 设备名:麦克风 (BDBA-C1000)
        i=1 设备名:麦克风 (Realtek(R) Audio)
        */
        
    }
复制代码

 

WAVEHDR结构

 

复制代码
typedef struct {
LPSTR lpData; //波形缓冲数据(传入首地址)
DWORD dwBufferLength; //缓冲区长度
DWORD dwBytesRecorded; //当设备用于录音时,标志已经录入的数据长度
DWORD dwUser; //用户数据
DWORD dwFlags; //标示缓冲区状态
DWORD dwLoops; //循环次数
struct wavehdr_tag * lpNext; //预留,NULL
DWORD reserved; //预留,0
} WAVEHDR;
复制代码

 

dwFlags:
零个或多个标志的按位 OR 。 定义了以下标志:
名称说明
WHDR_BEGINLOOP=0x00000004
此缓冲区是循环中的第一个缓冲区。 此标志仅用于输出缓冲区。
WHDR_DONE=0x00000001
由设备驱动程序设置,以指示它已完成缓冲区并将其返回到应用程序。
WHDR_ENDLOOP=0x00000008
此缓冲区是循环中的最后一个缓冲区。 此标志仅用于输出缓冲区。
WHDR_INQUEUE=0x00000010
由 Windows 设置以指示缓冲区已排队等待播放。
WHDR_PREPARED=0x00000002
由 Windows 设置以指示已使用 waveInPrepareHeader 或 waveOutPrepareHeader 函数准备缓冲区。


dwBytesRecorded录音时缓冲区容量:
如果 dwBytesRecorded 的值为 0,表示当前缓冲区中没有录制到任何数据。
    这可能是由于录音设备未能成功录制音频或者录制的音频数据长度为 0
如果 dwBytesRecorded 的值不为 0,表示当前缓冲区中录制了一定数量的音频数据。
    它会告知应用程序实际录制了多少字节的音频数据,
    应用程序可以根据这个值来处理录制后的数据
在使用 WAVEHDR 结构进行音频录制时,通过检查 dwBytesRecorded 的值,
    可以判断是否成功录制到音频数据,并根据实际录制的字节数来进行后续的处理和分析
需要注意的是,dwBytesRecorded 的值需要在调用录音函数之后才会被更新,
    因此在处理录音数据时,应在录音函数返回后再查看该值


dwLoops循环次数:
在 WAVEHDR 结构中,dwLoops 字段表示缓冲区的循环次数。当设置循环次数为一个大于 1 的值时,会使缓冲区的音频数据在播放或录制时进行多次重复。

具体效果取决于使用 WAVEHDR 结构的函数和操作。以下是一些可能的效果:

1. 播放重复:如果使用该结构的是用于播放音频的函数,设置 dwLoops 的值大于 1 可以使音频数据在播放完一次后自动进行多次重复播放。这在需要循环播放音频片段或创建循环背景音乐的场景中很有用。

2. 录制循环:如果使用该结构的是用于录制音频的函数,设置 dwLoops 的值大于 1 可以使音频数据在录制完成后自动进行多次重复录制。这可以用于需要连续录制多个相同音频片段的场景,而无需手动启动每次录制过程。

请注意,WAVEHDR 结构中的 dwLoops 字段并不是所有的音频接口和函数都支持的,它只在一些特定的音频操作中才具有意义。因此,在使用该字段之前,需要查阅相关文档以确保所使用的函数和接口支持循环播放或录制功能。

总结:将 dwLoops 设置为大于 1 的值可以实现缓冲区音频数据的多次重复播放或录制,适用于需要循环播放或录制的场景 
    0 表示无限次循环

 

 

 

WAVEFORMATEX结构

复制代码
    typedef struct tWAVEFORMATEX
{
    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; // 设置每个样本的位深,多为8或16
    WORD    cbSize;         // 额外信息的大小,一般不需要,设置为0
} WAVEFORMATEX;
复制代码

 

实例代码:

功能:录音    播放内存音频

实例资料:https://blog.csdn.net/qq_38161654/article/details/103871140

cpp代码:

复制代码
// csyinDlg.cpp: 实现文件
//

#include "pch.h"
#include "framework.h"
#include "csyin.h"
#include "csyinDlg.h"
#include "afxdialogex.h"

//添加头文件
#include <mmsystem.h>
#pragma comment(lib, "WINMM.LIB")


#ifdef _DEBUG
#define new DEBUG_NEW
#endif

//添加一些宏定义以及全局变量
WAVEHDR* pWaveHdr1;
WAVEHDR* pWaveHdr2;
PBYTE pSaveBuffer;//用来保存音频数据
PBYTE pBuffer1;
#define FRAGMENT_SIZE 1024*4        // 缓存区大小
int dwDataLength = FRAGMENT_SIZE;      //音频数据大小+缓冲区大小
PBYTE pBuffer2;
WAVEFORMATEX waveform;//
//WAVEFORMATEX结构:https://www.cnblogs.com/liming19680104/p/17538589.html
HWAVEIN hWaveIn;//输入设备句柄
//保存启动的录音设备
//波形音频数据格式Wave_audio数据格式

bool bEnding;
HWAVEOUT hWaveOut;//回放设备句柄 

PBYTE pNewBuffer;





// 用于应用程序“关于”菜单项的 CAboutDlg 对话框

class CAboutDlg : public CDialogEx
{
public:
    CAboutDlg();

// 对话框数据
#ifdef AFX_DESIGN_TIME
    enum { IDD = IDD_ABOUTBOX };
#endif

    protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 支持

// 实现
protected:
    DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()


// CcsyinDlg 对话框



CcsyinDlg::CcsyinDlg(CWnd* pParent /*=nullptr*/)
    : CDialogEx(IDD_CSYIN_DIALOG, pParent)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CcsyinDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CcsyinDlg, CDialogEx)
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_BN_CLICKED(IDC_RECORD_START, &CcsyinDlg::OnBnClickedRecordStart)
    ON_BN_CLICKED(IDC_RECORD_STOP, &CcsyinDlg::OnBnClickedRecordStop)
    ON_BN_CLICKED(IDC_RECORD_PLAY, &CcsyinDlg::OnBnClickedRecordPlay)
    ON_MESSAGE(MM_WIM_OPEN, &CcsyinDlg::OnMM_WIM_OPEN)
    ON_MESSAGE(MM_WIM_DATA, &CcsyinDlg::OnMM_WIM_DATA)
    ON_MESSAGE(MM_WIM_CLOSE, &CcsyinDlg::OnMM_WIM_CLOSE)
    ON_MESSAGE(MM_WOM_OPEN, &CcsyinDlg::OnMM_WOM_OPEN)
    ON_MESSAGE(MM_WOM_DONE, &CcsyinDlg::OnMM_WOM_DONE)
    ON_MESSAGE(MM_WOM_CLOSE, &CcsyinDlg::OnMM_WOM_CLOSE)
END_MESSAGE_MAP()


// CcsyinDlg 消息处理程序

BOOL CcsyinDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    // 将“关于...”菜单项添加到系统菜单中。

    // IDM_ABOUTBOX 必须在系统命令范围内。
    ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
    ASSERT(IDM_ABOUTBOX < 0xF000);

    CMenu* pSysMenu = GetSystemMenu(FALSE);
    if (pSysMenu != nullptr)
    {
        BOOL bNameValid;
        CString strAboutMenu;
        bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
        ASSERT(bNameValid);
        if (!strAboutMenu.IsEmpty())
        {
            pSysMenu->AppendMenu(MF_SEPARATOR);
            pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
        }
    }

    // 设置此对话框的图标。  当应用程序主窗口不是对话框时,框架将自动
    //  执行此操作
    SetIcon(m_hIcon, TRUE);            // 设置大图标
    SetIcon(m_hIcon, FALSE);        // 设置小图标

    // TODO: 在此添加额外的初始化代码


    pWaveHdr1 = (PWAVEHDR)malloc(sizeof(WAVEHDR)); //申请空间
    pWaveHdr2 = (PWAVEHDR)malloc(sizeof(WAVEHDR));
    pSaveBuffer = (PBYTE)malloc(1);






    return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
}

void CcsyinDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
    if ((nID & 0xFFF0) == IDM_ABOUTBOX)
    {
        CAboutDlg dlgAbout;
        dlgAbout.DoModal();
    }
    else
    {
        CDialogEx::OnSysCommand(nID, lParam);
    }
}

// 如果向对话框添加最小化按钮,则需要下面的代码
//  来绘制该图标。  对于使用文档/视图模型的 MFC 应用程序,
//  这将由框架自动完成。

void CcsyinDlg::OnPaint()
{
    if (IsIconic())
    {
        CPaintDC dc(this); // 用于绘制的设备上下文

        SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

        // 使图标在工作区矩形中居中
        int cxIcon = GetSystemMetrics(SM_CXICON);
        int cyIcon = GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(&rect);
        int x = (rect.Width() - cxIcon + 1) / 2;
        int y = (rect.Height() - cyIcon + 1) / 2;

        // 绘制图标
        dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {
        CDialogEx::OnPaint();
    }
}

//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CcsyinDlg::OnQueryDragIcon()
{
    return static_cast<HCURSOR>(m_hIcon);
}



void CcsyinDlg::OnBnClickedRecordStart()
{
    pBuffer1 = (PBYTE)malloc(dwDataLength);//申请缓存区空间
    //dwDataLength->1024*4   
    pBuffer2 = (PBYTE)malloc(dwDataLength);
    if (!pBuffer1 || !pBuffer2) {
        if (pBuffer1) free(pBuffer1);
        if (pBuffer2) free(pBuffer2);
        MessageBeep(MB_ICONEXCLAMATION);
        AfxMessageBox(_T("缓存空间申请失败!"));
        return;
    }

    waveform.wFormatTag = WAVE_FORMAT_PCM;
    waveform.nChannels = 2;//音频声道的数量
    waveform.nSamplesPerSec = 44100;// 样本频率
    waveform.nAvgBytesPerSec = 176400;// 设置请求的平均数据传输率,,单位为byte/s,创建缓冲大小要参考该值
    //44100 * 16 * 2 / 8=176400
    waveform.nBlockAlign = 4;//以字节为单位的块对齐的大小,通常为(nChannels*wBitsPerSample)/8
    waveform.wBitsPerSample = 16;// 设置每个样本的位深,多为8或16
    waveform.cbSize = 0;// 额外信息的大小,一般不需要,设置为0

    if (waveInOpen(&hWaveIn, WAVE_MAPPER, &waveform, (DWORD)this->m_hWnd, NULL, CALLBACK_WINDOW)) //如果打开录音设备失败
        /*
        MMRESULT waveInOpen()打开录音设备函数
        参数1:LPHWAVEIN phwi  【输出】一个特定的录音设备指针,如果设备启动成功,该参数的值将会被赋值为启动的设备
        参数2:UINT_PTR uDeviceID   指定一个需要打开的设备标识【设备ID】
                        可以使用WAVE_MAPPER选择系统默认录音的设备
        参数3:LPWAVEFORMATEX pwfx  进行录音的WAVEFORMATEX结构的指针【音频数据信息】
        参数4:DWORD_PTR dwCallback   录音消息的处理程序,可以设置一个函数、事件句柄、窗口句柄、一个特定的线程。
                也就是说录音消息产生后,由这个参数对应的值来处理该消息。包括关闭录音、缓冲区已满、开启设备
                如果不需要回调函数,则此值可以为零
        参数5:DWORD_PTR dwCallbackInstance  dwCallback参数的参数列表
        参数6:DWORD fdwOpen      打开设备的标志。 定义了以下值
                CALLBACK_EVENT        dwCallback参数是事件句柄
                CALLBACK_FUNCTION    dwCallback参数是回调函数
                CALLBACK_NULL                  无回调机制。 这是默认设置。
                CALLBACK_THREAD        dwCallback参数是线程标识符。
                CALLBACK_WINDOW        dwCallback参数是窗口句柄。
                WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE    如果指定了此标志并且 uDeviceID 参数 WAVE_MAPPER,则函数将打开默认通信设备
                                    仅 当 uDeviceID 等于 WAVE_MAPPER时,此标志才适用。注意 需要 Windows 7
                WAVE_FORMAT_DIRECT    如果指定了此标志,ACM 驱动程序不会对音频数据执行转换。
                WAVE_FORMAT_QUERY    该函数将查询设备以确定它是否支持给定格式,但它不打开设备
                WAVE_MAPPED    uDeviceID 参数指定要由波形映射器映射到的波形音频设备

        返回值:如果成功,则返回 MMSYSERR_NOERROR=0
                可能的错误值包括以下内容
                MMSYSERR_ALLOCATED     已分配指定资源
                MMSYSERR_BADDEVICEID   指定的设备标识符范围不足
                MMSYSERR_NODRIVER      不存在设备驱动程序
                MMSYSERR_NOMEM         无法分配或锁定内存
                WAVERR_BADFORMAT       尝试使用不受支持的波形音频格式打开

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

    {
        free(pBuffer1);
        free(pBuffer2);
        MessageBeep(MB_ICONEXCLAMATION);
        AfxMessageBox(_T("录音设备打开失败!"));
        return;

    }

    pWaveHdr1->lpData = (LPSTR)pBuffer1; //输入缓存区首地址
    //"LPSTR"相当于char*
    pWaveHdr1->dwBufferLength = dwDataLength;//输入缓冲区长度
    pWaveHdr1->dwBytesRecorded = 0;        //指明录音时缓冲区容量
    pWaveHdr1->dwUser = 0;            //用户数据
    pWaveHdr1->dwFlags = 0;           //标示缓冲区状态
    pWaveHdr1->dwLoops = 1;           //循环次数
    pWaveHdr1->lpNext = NULL;  //预留,NULL
    pWaveHdr1->reserved = 0;   //预留,0
    waveInPrepareHeader(hWaveIn, pWaveHdr1, sizeof(WAVEHDR));//为波形输入准备一个缓冲区
    //为输入设备准备第一个缓冲区
    /*
    参数1:HWAVEIN    设备句柄
    参数2:WAVEHDR结构指针
    参数3:WAVEHDR结构大小(以字节为单位)
    返回值:
        成功返回 0
        MMSYSERR_INVALHANDLE = 5    设备句柄无效
        MMSYSERR_NOMEM       = 7    不能分配或锁定内存
        MMSYSERR_HANDLEBUSY  = 12   其他线程正在使用该设备
    在调用此函数之前,必须设置 WAVEHDR 结构的 lpData、dwBufferLength 和 dwFlags 成员, (dwFlags 必须为零)
    */


    pWaveHdr2->lpData = (LPSTR)pBuffer2;  
    pWaveHdr2->dwBufferLength = dwDataLength;
    pWaveHdr2->dwBytesRecorded = 0;
    pWaveHdr2->dwUser = 0;
    pWaveHdr2->dwFlags = 0;
    pWaveHdr2->dwLoops = 1;
    pWaveHdr2->lpNext = NULL;
    pWaveHdr2->reserved = 0;
    waveInPrepareHeader(hWaveIn, pWaveHdr2, sizeof(WAVEHDR));
    //为输入设备准备第二个缓冲区
    //一个输入设备通常需要两个缓冲区是为了实现连续的音频数据采集。
    //当一个缓冲区正在被设备使用来接收音频数据时,应用程序可以同时准备
    //另一个缓冲区以便在第一个缓冲区被填满后立即切换到第二个缓冲区,以保证连续的数据采集
    
    pSaveBuffer = (PBYTE)realloc(pSaveBuffer, 1);//修改数组容量
    //给pSaveBuffer指定容量
    
    waveInAddBuffer(hWaveIn, pWaveHdr1, sizeof(WAVEHDR));//向波形输入设备指定一个输入缓冲区
    /*
    在缓冲区给 waveInAddBuffer 前, 先要调用 waveInPrepareHeader 准备
    填充缓冲区时,将在 WAVEHDR 结构的 dwFlags 成员中设置WHDR_DONE位

    说明:发出MM_WIM_DATA时,会设置dwFlags的WHDR_DONE,并将缓冲区返回给应用程序,
          也就是说此时麦克风不在 拥有 这个缓冲区,需要  waveInAddBuffer重新 指定 
    */

    waveInAddBuffer(hWaveIn, pWaveHdr2, sizeof(WAVEHDR));
    

    bEnding = FALSE;
    dwDataLength = 0;


    waveInStart(hWaveIn);  //启动给定波形音频输入设备上的输入
    /*
    【启动麦克风】
    参数:HWAVEIN
    返回值:
        如果成功   则返回MMSYSERR_NOERROR
        MMSYSERR_INVALHANDLE     指定的设备句柄无效
        MMSYSERR_NODRIVER        不存在设备驱动程序
        MMSYSERR_NOMEM           无法分配或锁定内存
    */
    


}


void CcsyinDlg::OnBnClickedRecordStop()
{
    bEnding = TRUE;
    waveInReset(hWaveIn);//停止给定波形音频输入设备上的输入
    /*
    【关闭麦克风】
    将当前位置重置为零
    所有挂起的缓冲区都标记为已完成,并返回到应用程序
    TWaveHdr 结构中的 dwBytesRecorded 将包含实际数据的长度
    返回值:
        如果成功返回MMSYSERR_NOERROR
        MMSYSERR_INVALHANDLE=5   指定的设备句柄无效
        MMSYSERR_NODRIVER      不存在设备驱动程序
        MMSYSERR_NOMEM         无法分配或锁定内存
        MMSYSERR_HANDLEBUSY= 12  设备已被另一线程使用

    */


    //*******存储声音文件**********
    CFile m_file;
    CFileException fileException;
    
    CString m_csFileName = _T("D:\\audio.wav");

    m_file.Open(m_csFileName, CFile::modeCreate | CFile::modeReadWrite, &fileException);

    DWORD m_WaveHeaderSize = 38;//头部信息总字节数
    /*
    这个38是以下部分的总和:

1. "RIFF" 标识符占用4字节。
2. 用于存储文件大小信息的 4 字节整数( unsigned int Sec )。
3. "WAVE" 标识符占用4字节。
4. "fmt " 标识符占用4字节。
5. 用于存储 WAVEFORMATEX 结构大小的 4 字节整数。
6. WAVEFORMATEX 结构中的成员 
    
    
    */
    DWORD m_WaveFormatSize = 18;//WAVEFORMATEX结构大小
    //WAVEFORMATEX结构看:https://www.cnblogs.com/liming19680104/p/17538589.html

    m_file.SeekToBegin();


    m_file.Write("RIFF", 4);//写入标识符RIFF
    
    unsigned int Sec = (sizeof(pSaveBuffer) + m_WaveHeaderSize);
    //音频数据大小+头部信息的大小
    m_file.Write(&Sec, sizeof(Sec));
    m_file.Write("WAVE", 4);   //写入格式辨别吗


    //***********fmt子块****************
    m_file.Write("fmt ", 4);  //写入fmt
    m_file.Write(&m_WaveFormatSize, sizeof(m_WaveFormatSize));//写入WAVEFORMATEX结构大小

    //写入WAVEFORMATEX结构的6个或7个成员值
    m_file.Write(&waveform.wFormatTag, sizeof(waveform.wFormatTag));
    m_file.Write(&waveform.nChannels, sizeof(waveform.nChannels));
    m_file.Write(&waveform.nSamplesPerSec, sizeof(waveform.nSamplesPerSec));
    m_file.Write(&waveform.nAvgBytesPerSec, sizeof(waveform.nAvgBytesPerSec));
    m_file.Write(&waveform.nBlockAlign, sizeof(waveform.nBlockAlign));
    m_file.Write(&waveform.wBitsPerSample, sizeof(waveform.wBitsPerSample));
    m_file.Write(&waveform.cbSize, sizeof(waveform.cbSize));



    m_file.Write("data", 4);
    m_file.Write(&dwDataLength, sizeof(dwDataLength));
    //dwDataLength=缓冲区大小

    m_file.Write(pSaveBuffer, dwDataLength);//写入音频数据

    m_file.Seek(dwDataLength, CFile::begin);
    m_file.Close();
    


}


void CcsyinDlg::OnBnClickedRecordPlay()
{
    waveform.wFormatTag = WAVE_FORMAT_PCM;

    //设置不同的声音采样格式
/*    waveform.nChannels      =   1;
    waveform.nSamplesPerSec =11025;
    waveform.nAvgBytesPerSec=11025;
    waveform.nBlockAlign    =1;
    waveform.wBitsPerSample =8; */

    waveform.nChannels = 2;
    waveform.nSamplesPerSec = 44100;
    waveform.nAvgBytesPerSec = 176400;
    waveform.nBlockAlign = 4;
    waveform.wBitsPerSample = 16;

    if (waveOutOpen(&hWaveOut, WAVE_MAPPER, &waveform, (DWORD)this->m_hWnd, NULL, CALLBACK_WINDOW)) {
        /*
        waveOutOpen函数:打开播放设备
        参数1:LPHWAVEOUT phwo 输出设备句柄的指针
        参数2:UINT uDeviceID  可以是设备标识符,也可以是开放波形音频输入设备的句柄
                                还可以使用标志:WAVE_MAPPER    该函数选择能够播放给定格式的波形音频输出设备【默认 设备 】
        参数3:LPCWAVEFORMATEX pwfx  WAVEFORMATEX   结构指针
        参数4:DWORD_PTR dwCallback  指定回调机制。 该值必须为下列值之一:
                                    指向回调函数的指针
                                    窗口的句柄
                                    线程标识符
                                    事件的句柄
                                    值为 NULL
        参数5:DWORD_PTR dwInstance   传递给回调机制的用户实例数据
        参数6:DWORD fdwOpen      用于打开设备的标志。 定义了以下值
                CALLBACK_EVENT    dwCallback 参数是事件句柄。
                CALLBACK_FUNCTION    dwCallback 参数是回调过程地址。
                CALLBACK_NULL    无回调机制。 这是默认设置。
                CALLBACK_THREAD    dwCallback 参数是线程标识符。
                CALLBACK_WINDOW    dwCallback 参数是窗口句柄。
                WAVE_ALLOWSYNC    如果指定了此标志,则可以打开同步波形音频设备。 如果在打开同步驱动程序时未指定此标志,则设备将无法打开
                WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE    如果指定了此标志并且 uDeviceID 参数 WAVE_MAPPER,则函数将打开默认通信设备
                            仅当 uDeviceID 等于 WAVE_MAPPER时,此标志才适用。
                            注意 需要 Windows 7
                WAVE_FORMAT_DIRECT    如果指定了此标志,则 ACM 驱动程序不会对音频数据执行转换。
                WAVE_FORMAT_QUERY    如果指定了此标志, waveOutOpen 会查询设备以确定它是否支持给定格式,但实际上未打开该设备
                WAVE_MAPPED    如果指定了此标志, 则 uDeviceID 参数指定要由波形映射器映射到的波形音频设备。
        返回值:MMRESULT
                如果成功,则返回 MMSYSERR_NOERROR
                MMSYSERR_ALLOCATED    已分配指定资源。
                MMSYSERR_BADDEVICEID  指定的设备标识符范围不足。
                MMSYSERR_NODRIVER     不存在设备驱动程序。
                MMSYSERR_NOMEM        无法分配或锁定内存。
                WAVERR_BADFORMAT      尝试使用不支持的波形音频格式打开
                WAVERR_SYNC           设备是同步的,但 waveOutOpen 是在不使用 WAVE_ALLOWSYNC 标志的情况下调用的。
        
        */
        
        
        MessageBeep(MB_ICONEXCLAMATION);
        AfxMessageBox(_T("Audio output erro"));
    }
    return;
    
}


afx_msg LRESULT CcsyinDlg::OnMM_WIM_OPEN(WPARAM wParam, LPARAM lParam)//当音频输入设备开始采集音频数据时触发
{
    ((CWnd*)(this->GetDlgItem(IDC_RECORD_START)))->EnableWindow(FALSE);//禁用
    ((CWnd*)(this->GetDlgItem(IDC_RECORD_STOP)))->EnableWindow(TRUE);  //启用
    ((CWnd*)(this->GetDlgItem(IDC_RECORD_PLAY)))->EnableWindow(FALSE);
            
    return 0;
}



afx_msg LRESULT CcsyinDlg::OnMM_WIM_DATA(WPARAM wParam, LPARAM lParam) //当音频输入设备采集到一定量的音频数据时触发
{
    /*
    wParam = (WPARAM) hInputDev     接收数据的波形音频输入设备的句柄
    lParam = (LONG) lpwvhdr       指向 WAVEHDR 结构的指针,该结构标识包含数据的缓冲区
    */
    
    pNewBuffer = (PBYTE)realloc(pSaveBuffer, dwDataLength + ((PWAVEHDR)lParam)->dwBytesRecorded);
    /*
    ((PWAVEHDR)lParam)->dwBytesRecorded   缓冲区中已有的音频数据
        
    说明:通过将已录制的音频数据字节数与当前缓冲区大小相加,你确保新分配的内存能够容纳已录制的数据以及之前已经存在的数据。

    这是因为在音频数据流中,每次处理完缓冲区中的数据后,你需要将新录制的数据追加到之前已经录制的数据之后,
    以便完整地保存整个音频流。所以你需要确保重新分配的内存大小足够大,以容纳已有的数据和新录制的数据
    */



    if (pNewBuffer == NULL)
    {
        waveInClose(hWaveIn);     //关闭指定的波形输入设备
        /*
        参数:HWAVEIN hwi
        返回值:MMRESULT    如果成功则返回MMSYSERR_NOERROR
                        MMSYSERR_INVALHANDLE     指定的设备句柄无效
                        MMSYSERR_NODRIVER        不存在设备驱动程序
                        MMSYSERR_NOMEM           无法分配或锁定内存
                        WAVERR_STILLPLAYING      队列中仍有缓冲区
        */


        MessageBeep(MB_ICONEXCLAMATION);
        AfxMessageBox(_T("内存数据错误"));
        return TRUE;
    }

    pSaveBuffer = pNewBuffer;
    CopyMemory(pSaveBuffer + dwDataLength, ((PWAVEHDR)lParam)->lpData,((PWAVEHDR)lParam)->dwBytesRecorded);  //复制音频数据
    dwDataLength += ((PWAVEHDR)lParam)->dwBytesRecorded;  

    if (bEnding)
    {
        waveInClose(hWaveIn);
        return TRUE;
    }

    waveInAddBuffer(hWaveIn, (PWAVEHDR)lParam, sizeof(WAVEHDR));
    //重新指定缓冲区,使缓冲区能够重新接收音频数据
        
    
    return 0;
}


afx_msg LRESULT CcsyinDlg::OnMM_WIM_CLOSE(WPARAM wParam, LPARAM lParam)  //当音频输入设备停止采集音频数据时触发
{
    
    if (0 == dwDataLength) {
        return TRUE;
    }
    waveInUnprepareHeader(hWaveIn, pWaveHdr1, sizeof(WAVEHDR));     //去掉缓冲区的准备  
    /*
    waveInUnprepareHeader 函数清理 waveInPrepareHeader 函数执行的准备。 
    设备驱动程序填充缓冲区并将其返回到应用程序后,必须调用此函数。 
    在释放缓冲区之前,必须调用此函数

    参数1:HWAVEIN   hwi  波形音频输入设备的句柄
    参数2:LPWAVEHDR pwh  指向用于标识要清理的缓冲区的 WAVEHDR 结构的指针
    参数3:UINT   cbwh    WAVEHDR 结构的大小(以字节为单位)
    返回值:MMRESULT 如果成功,则返回MMSYSERR_NOERROR
                               MMSYSERR_INVALHANDLE       指定的设备句柄无效
                               MMSYSERR_NODRIVER          不存在设备驱动程序
                               MMSYSERR_NOMEM             无法分配或锁定内存
                               WAVERR_STILLPLAYING        pwh 参数指向的缓冲区仍在队列中
                               取消准备尚未准备的缓冲区不起作用,函数返回零

    
    */
    waveInUnprepareHeader(hWaveIn, pWaveHdr2, sizeof(WAVEHDR));

    free(pBuffer1);  //释放缓冲区
    free(pBuffer2);

    if (dwDataLength > 0)
    {
        //enable play   
        ((CWnd*)(this->GetDlgItem(IDC_RECORD_START)))->EnableWindow(TRUE);
        ((CWnd*)(this->GetDlgItem(IDC_RECORD_STOP)))->EnableWindow(FALSE);
        ((CWnd*)(this->GetDlgItem(IDC_RECORD_PLAY)))->EnableWindow(TRUE);
    }

    ((CWnd*)(this->GetDlgItem(IDC_RECORD_START)))->EnableWindow(TRUE);
    ((CWnd*)(this->GetDlgItem(IDC_RECORD_STOP)))->EnableWindow(FALSE);
    
    
    return 0;
}


afx_msg LRESULT CcsyinDlg::OnMM_WOM_OPEN(WPARAM wParam, LPARAM lParam)  //当音频输出设备开始播放音频数据时触发
{
    // 播放内存中的音频数据
    pWaveHdr1->lpData = (LPSTR)pSaveBuffer;  
    pWaveHdr1->dwBufferLength = dwDataLength;
    pWaveHdr1->dwBytesRecorded = 0;
    pWaveHdr1->dwUser = 0;
    pWaveHdr1->dwFlags = WHDR_BEGINLOOP | WHDR_ENDLOOP;
    //此缓冲区是循环中的第一个缓冲区    此缓冲区是循环中的最后一个缓冲区
    pWaveHdr1->dwLoops = 1;
    pWaveHdr1->lpNext = NULL;
    pWaveHdr1->reserved = 0;

     

    waveOutPrepareHeader(hWaveOut, pWaveHdr1, sizeof(WAVEHDR));  //准备输出缓冲区
    waveOutWrite(hWaveOut, pWaveHdr1, sizeof(WAVEHDR)); //向输出设备发送一个数据块(播放)
    /*
    把数据缓冲区传给 waveOutWrite 之前, 必须使用 waveOutPrepareHeader 准备该缓冲区
    把数据块发送给设备时即开始播放
    参数1:hWaveOut: HWAVEOUT;     {设备句柄}
    参数2:lpWaveOutHdr: PWaveHdr; {WAVEHDR结构指针}
    参数3:  uSize: UINT             {WAVEHDR结构大小}
    返回值:MMRESULT
            成功返回 0
            MMSYSERR_INVALHANDLE = 5;  {设备句柄无效}
            MMSYSERR_HANDLEBUSY  = 12; {设备已被另一线程使用}
            WAVERR_UNPREPARED    = 34; {未准备数据块}

    */

    ((CWnd*)(this->GetDlgItem(IDC_RECORD_START)))->EnableWindow(TRUE);
    ((CWnd*)(this->GetDlgItem(IDC_RECORD_STOP)))->EnableWindow(FALSE);
    ((CWnd*)(this->GetDlgItem(IDC_RECORD_PLAY)))->EnableWindow(FALSE);
    
    
    return 0;
}


afx_msg LRESULT CcsyinDlg::OnMM_WOM_DONE(WPARAM wParam, LPARAM lParam)  //当音频输出设备完成对一块音频数据的播放时触发
{
    
    waveOutUnprepareHeader(hWaveOut, pWaveHdr1, sizeof(WAVEHDR));//去除缓冲区的准备 
    /*
    参数1:hWaveOut: HWAVEOUT;     {设备句柄}
    参数2:lpWaveOutHdr: PWaveHdr; {WAVEHDR结构指针}
    参数 3:uSize: UINT             {WAVEHDR结构大小}
    返回值:MMRESULT
            成功返回 0
            MMSYSERR_INVALHANDLE = 5;  {设备句柄无效}
            MMSYSERR_HANDLEBUSY  = 12; {设备已被另一线程使用}
            WAVERR_STILLPLAYING  = 33; {缓冲区还在队列中}
    
    
    */
    waveOutClose(hWaveOut);//关闭设备
    /*
    正在播放, 应先调用 waveOutReset 终止播放, 然后再关闭, 不然会失败
    参数:hWaveOut: HWAVEOUT {设备句柄}
    返回值:MMRESULT
            成功返回 0
            MMSYSERR_INVALHANDLE = 5;  {设备句柄无效}
            MMSYSERR_HANDLEBUSY  = 12; {设备已被另一线程使用}
            WAVERR_STILLPLAYING  = 33; {缓冲区还在队列中}


    */
    return  NULL;
    
    
    return 0;
}


afx_msg LRESULT CcsyinDlg::OnMM_WOM_CLOSE(WPARAM wParam, LPARAM lParam)  //当音频输出设备停止播放音频数据时触发
{
    
    ((CWnd*)(this->GetDlgItem(IDC_RECORD_START)))->EnableWindow(TRUE);
    ((CWnd*)(this->GetDlgItem(IDC_RECORD_STOP)))->EnableWindow(FALSE);
    ((CWnd*)(this->GetDlgItem(IDC_RECORD_PLAY)))->EnableWindow(TRUE);
    
    
    return 0;
}
复制代码

 

实例工程下载:

链接:https://pan.baidu.com/s/1z1GGim_Q3uNHUiOBC_BKxw
提取码:6666

 

    

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @   天子骄龙  阅读(273)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
历史上的今天:
2020-07-09 pygame.mask--图形遮罩模块
2019-07-09 第三节 串联和并联
2019-07-09 第2节 电流和电路
点击右上角即可分享
微信分享提示

目录导航