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 。 定义了以下标志:
名称 | 说明 |
---|---|
|
此缓冲区是循环中的第一个缓冲区。 此标志仅用于输出缓冲区。 |
|
由设备驱动程序设置,以指示它已完成缓冲区并将其返回到应用程序。 |
|
此缓冲区是循环中的最后一个缓冲区。 此标志仅用于输出缓冲区。 |
|
由 Windows 设置以指示缓冲区已排队等待播放。 |
|
由 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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有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节 电流和电路