MFC播放声音和录音的实现(三)
上一篇通过Win32控制台程序简单地完成了声音的录取和回放,但是这个过程都只是是在内存中进行的,没有进行文件的操作,这样录取的声音也就无法保存。这一篇介绍一下用MFC实现录音并生成wave文件,最后存储到指定的目录的方法。
新建一个MFC对话框应用程序,命名为VoiceRecord, 打开资源视图,Dialog目录下的IDD_VOICERECORD_DIALOG,往这个对话框中添加3个Button控件,修改对应的属性栏中的Caption和ID,分别为:
Caption ID
开始 IDC_RECORD_START
结束 IDC_RECORD_STOP
播放 IDC_RECORD_PLAY
说明:此处的播放相当于回放刚才的录音,没有选择性。要播放指定路径音频文件参考第一篇。
给这三个按钮分别添加消息处理函数:
OnRecordStart()
OnRecordStop()
OnRecordPlay()
在往这三个函数中添加消息响应代码之前,先介绍一下关于录音Windows提供的一组函数wave***的函数,比较重要的有以下几个:
(一)相关函数
1) 打开录音设备函数
MMRESULT waveInOpen(
LPHWAVEIN phwi, //输入设备句柄
UINT uDeviceID, //输入设备ID
LPWAVEFORMATEX pwfx, //录音格式指针
DWORD dwCallback, //处理MM_WIM_***消息的回调函数或窗口句柄,线程ID
DWORD dwCallbackInstance,
DWORD fdwOpen //处理消息方式的符号位
);
2) 为录音设备准备缓存函数
MMRESULT waveInPrepareHeader( HWAVEIN hwi, LPWAVEHDR pwh, UINT bwh );
3) 给输入设备增加一个缓存
MMRESULT waveInAddBuffer( HWAVEIN hwi, LPWAVEHDR pwh, UINT cbwh );
4) 开始录音
MMRESULT waveInStart( HWAVEIN hwi );
5) 清除缓存
MMRESULT waveInUnprepareHeader( HWAVEIN hwi,LPWAVEHDR pwh, UINT cbwh);
6) 停止录音
MMRESULT waveInReset( HWAVEIN hwi );
7) 关闭录音设备
MMRESULT waveInClose( HWAVEIN hwi );
8) 打开回放设备
MMRESULT waveOutOpen(
LPHWAVEOUT phwo, //输出设备句柄
UINT uDeviceID, //输出设备ID
LPWAVEFORMATEX pwfx, //放音格式指针
DWORD dwCallback, //处理MM_WIM_***消息的回调函数或窗口句柄,线程ID
DWORD dwCallbackInstance,
DWORD fdwOpen //处理消息方式的符号位
);
9) 为回放设备准备内存块
MMRESULT waveOutPrepareHeader( HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh );
10) 写数据(放音)
MMRESULT waveOutWrite( HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh );
(二)相关数据结构
Wave_audio数据格式
typedef struct {
WORD wFormatTag; //数据格式,一般为WAVE_FORMAT_PCM即脉冲编码
WORD nChannels; //声道
DWORD nSamplesPerSec; //采样频率
DWORD nAvgBytesPerSec; //每秒数据量
WORD nBlockAlign;
WORD wBitsPerSample;//样本大小
WORD cbSize;
} WAVEFORMATEX;
waveform-audio 缓存格式
typedef struct {
LPSTR lpData; //内存指针
DWORD dwBufferLength;//长度
DWORD dwBytesRecorded; //已录音的字节长度
DWORD dwUser;
DWORD dwFlags;
DWORD dwLoops; //循环次数
struct wavehdr_tag * lpNext;
DWORD reserved;
} WAVEHDR;
(三)相关消息
录音
MM_WIM_OPEN:打开设备时消息,在此期间我们可以进行一些初始化工作 。
MM_WIM_DATA:当缓存已满或者停止录音时的消息,处理这个消息可以对缓存进行重新分配,实现不限长度录音 。
MM_WIM_CLOSE:关闭录音设备时的消息。
回放
OnMM_WOM_OPEN:打开设备
OnMM_WOM_DONE:处理声音的回放
OnMM_WOM_CLOSE:关闭设备
(四)实现过程
有了上面的基础,实现录音已经不难了。
首先在 初始化函数OnInitDialog()中分配内存
//shufac
//allocate memory for wave header
pWaveHdr1=reinterpret_cast<PWAVEHDR>(malloc(sizeof(WAVEHDR)));
pWaveHdr2=reinterpret_cast<PWAVEHDR>(malloc(sizeof(WAVEHDR)));
//allocate memory for save buffer
pSaveBuffer = reinterpret_cast<PBYTE>(malloc(1));
开始录音函数的代码如下:
void CVoiceRecordDlg::OnRecordRecord()
{//allocate buffer memory pBuffer1=(PBYTE)malloc(INP_BUFFER_SIZE); pBuffer2=(PBYTE)malloc(INP_BUFFER_SIZE); if (!pBuffer1 || !pBuffer2) { if (pBuffer1) free(pBuffer1); if (pBuffer2) free(pBuffer2); MessageBeep(MB_ICONEXCLAMATION); AfxMessageBox(_T("Memory erro!")); return; } //open waveform audo for input waveform.wFormatTag=WAVE_FORMAT_PCM; waveform.nChannels=2; waveform.nSamplesPerSec=44100; waveform.nAvgBytesPerSec=176400; waveform.nBlockAlign=4; waveform.wBitsPerSample=16; waveform.cbSize=0; if (waveInOpen(&hWaveIn,WAVE_MAPPER,&waveform,(DWORD)this->m_hWnd,NULL,CALLBACK_WINDOW)) { free(pBuffer1); free(pBuffer2); MessageBeep(MB_ICONEXCLAMATION); AfxMessageBox(_T("Audio can not be open!")); } pWaveHdr1->lpData=(LPSTR)pBuffer1; pWaveHdr1->dwBufferLength=INP_BUFFER_SIZE; pWaveHdr1->dwBytesRecorded=0; pWaveHdr1->dwUser=0; pWaveHdr1->dwFlags=0; pWaveHdr1->dwLoops=1; pWaveHdr1->lpNext=NULL; pWaveHdr1->reserved=0; waveInPrepareHeader(hWaveIn,pWaveHdr1,sizeof(WAVEHDR)); pWaveHdr2->lpData=(LPSTR)pBuffer2; //pWaveHdr2->dwBufferLength=INP_BUFFER_SIZE; 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) ; // Add the buffers waveInAddBuffer (hWaveIn, pWaveHdr1, sizeof (WAVEHDR)) ; waveInAddBuffer (hWaveIn, pWaveHdr2, sizeof (WAVEHDR)) ; // Begin sampling bEnding=FALSE; dwDataLength = 0 ; waveInStart (hWaveIn) ;
}
录音文件的保存是在一次录音结束之后进行的。因此,保存录音的操作添加到了结束录音的函数中。后面会展示这个完整的函数,这里还是简单地做一下说明:关于生成的录音文件的存储问题:
1.存到哪儿
2.命名(每一次录音文件名不能相同,否则会产生覆盖)
第一个问题还是用前面介绍的相对路径的获取方法,直接存到Debug目录下,详细参考:MFC获取系统当前路径
这里还有一个创建目录的问题,录音文件肯定是存放在Debug目录下的一个单独的文件夹中,这个问题也已经解决,
详细参考:MFC中创建目录的相关问题
第二个问题,通过添加系统时间为后缀,确保文件不会重名;关于系统时间的获取方法,可参考:MFC获取系统时间的方法
最后还有一个字符串的拼接问题,这里在MFC中字符串的拼接问题会做一个简要后续再补充。
这样完整的录音结束函数为:
void CVoiceRecordDlg::OnRecordStop()
{// TODO: 在此添加控件通知处理程序代码bEnding=TRUE; //停止录音waveInReset(hWaveIn); //存储声音文件CFile m_file; CFileException fileException; SYSTEMTIME sys2; //获取系统时间确保文件的保存不出现重名GetLocalTime(&sys2);//以下实现将录入的声音转换为wave格式文件 //查找当前目录中有没有Voice文件夹 没有就先创建一个,有就直接存储TCHAR szPath[MAX_PATH]; GetModuleFileName(NULL, szPath, MAX_PATH);CString PathName(szPath);//获取exe目录CString PROGRAM_PATH = PathName.Left(PathName.ReverseFind(_T('\\')) + 1);//Debug目录下RecordVoice文件夹中PROGRAM_PATH+=_T("RecordVoice\\");if (!(GetFileAttributes(PROGRAM_PATH)==FILE_ATTRIBUTE_DIRECTORY)){if (!CreateDirectory(PROGRAM_PATH,NULL)){AfxMessageBox(_T("Make Dir Error"));}}//kn_string strFilePath = _T("RecordVoice\\");//GetFilePath(strFilePath);CString m_csFileName=PROGRAM_PATH+_T("\\audio");//strVoiceFilePath//CString m_csFileName= _T("D:\\audio"); wchar_t s[30] = {0}; _stprintf(s,_T("%d%d%d%d%d%d"),sys2.wYear,sys2.wMonth,sys2.wDay,sys2.wHour,sys2.wMinute,sys2.wSecond/*,sys2.wMilliseconds*/);m_csFileName.Append(s);m_csFileName.Append(_T(".wav"));m_file.Open(m_csFileName,CFile::modeCreate|CFile::modeReadWrite, &fileException);DWORD m_WaveHeaderSize = 38; DWORD m_WaveFormatSize = 18; m_file.SeekToBegin(); m_file.Write("RIFF",4); //unsigned int Sec=(sizeof + m_WaveHeaderSize); unsigned int Sec=(sizeof pSaveBuffer + m_WaveHeaderSize); m_file.Write(&Sec,sizeof(Sec)); m_file.Write("WAVE",4); m_file.Write("fmt ",4); m_file.Write(&m_WaveFormatSize,sizeof(m_WaveFormatSize)); 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)); m_file.Write(pSaveBuffer,dwDataLength); m_file.Seek(dwDataLength,CFile::begin); m_file.Close();
}
这里还需要注意一点的是写文件wave的各个字段都要要赋值,才能保证生成的wave文件有效。
录音的回放函数代码处理相对简单,不多重述。代码如下:
void CVoiceRecordDlg::OnRecordPlay()
{ //open waveform audio for output 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)) { MessageBeep(MB_ICONEXCLAMATION); AfxMessageBox(_T("Audio output erro")); } return ;
}
最后,还有几个Windows提供的几个消息响应函数,它们消息响应类似于鼠标操作的消息响应,这一点在前面的扫雷程序中做过详细的介绍,不做重述。
代码如下(录音和回放各三个):
LRESULT CVoiceRecordDlg::OnMM_WIM_OPEN(UINT wParam, LONG lParam)
{ // TODO: Add your message handler code here and/or call default ((CWnd *)(this->GetDlgItem(IDB_RECORD_RECORD)))->EnableWindow(FALSE); ((CWnd *)(this->GetDlgItem(IDB_RECORD_STOP)))->EnableWindow(TRUE); ((CWnd *)(this->GetDlgItem(IDB_RECORD_PLAY)))->EnableWindow(FALSE); SetTimer(1,100,NULL); return NULL;}
这里采集声音信号还要添加一个定时器,另外在这个程序的基础上拓展做一个小型的录音机也是可以的,还需要添加两个定时器,分别控制录音盒放音时间。关于定时器的用法,做过总结,可参考: MFC中定时器的使用
LRESULT CVoiceRecordDlg::OnMM_WIM_DATA(UINT wParam, LONG lParam)
{ // TODO: Add your message handler code here and/or call default // Reallocate save buffer memory pNewBuffer = (PBYTE)realloc (pSaveBuffer, dwDataLength + ((PWAVEHDR) lParam)->dwBytesRecorded) ; if (pNewBuffer == NULL) { waveInClose (hWaveIn) ; MessageBeep (MB_ICONEXCLAMATION); AfxMessageBox(_T("erro memory")); 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 NULL;
} LRESULT CVoiceRecordDlg::OnMM_WIM_CLOSE(UINT wParam, LONG lParam)
{ KillTimer(1); if (0==dwDataLength) { return TRUE; } waveInUnprepareHeader (hWaveIn, pWaveHdr1, sizeof (WAVEHDR)) ; waveInUnprepareHeader (hWaveIn, pWaveHdr2, sizeof (WAVEHDR)) ; free (pBuffer1) ; free (pBuffer2) ; if (dwDataLength > 0) { //enable play ((CWnd *)(this->GetDlgItem(IDB_RECORD_RECORD)))->EnableWindow(TRUE); ((CWnd *)(this->GetDlgItem(IDB_RECORD_STOP)))->EnableWindow(FALSE); ((CWnd *)(this->GetDlgItem(IDB_RECORD_PLAY)))->EnableWindow(TRUE); } ((CWnd *)(this->GetDlgItem(IDB_RECORD_RECORD)))->EnableWindow(TRUE); ((CWnd *)(this->GetDlgItem(IDB_RECORD_STOP)))->EnableWindow(FALSE); return NULL;
} //放音
LRESULT CVoiceRecordDlg::OnMM_WOM_OPEN(UINT wParam, LONG lParam){ // Set up header 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; // Prepare and write waveOutPrepareHeader (hWaveOut, pWaveHdr1, sizeof (WAVEHDR)) ; waveOutWrite (hWaveOut, pWaveHdr1, sizeof (WAVEHDR)) ; ((CWnd *)(this->GetDlgItem(IDB_RECORD_RECORD)))->EnableWindow(TRUE); ((CWnd *)(this->GetDlgItem(IDB_RECORD_STOP)))->EnableWindow(FALSE); ((CWnd *)(this->GetDlgItem(IDB_RECORD_PLAY)))->EnableWindow(FALSE); return NULL;
} LRESULT CVoiceRecordDlg::OnMM_WOM_DONE(UINT wParam, LONG lParam)
{ waveOutUnprepareHeader (hWaveOut, pWaveHdr1, sizeof (WAVEHDR)); waveOutClose (hWaveOut); return NULL;
}
LRESULT CVoiceRecordDlg::OnMM_WOM_CLOSE(UINT wParam, LONG lParam)
{ ((CWnd *)(this->GetDlgItem(IDB_RECORD_RECORD)))->EnableWindow(TRUE); ((CWnd *)(this->GetDlgItem(IDB_RECORD_STOP)))->EnableWindow(FALSE); ((CWnd *)(this->GetDlgItem(IDB_RECORD_PLAY)))->EnableWindow(TRUE);return NULL;
}
最后的最后,不要忘了添加对声音的头文件支持(第一篇中已做过介绍)。
经过上面的步骤,一个简易版的录音机已经实现。在此基础上,添加两个定时器以及显示录音和放音时间的编辑框,在添加一个进度条控件等,基本上可以完成一个比较完善的小型录音机了,有兴趣的可以试一试。
(待完善)
MFC播放声音和录音的实现(三)相关推荐
- 高通音频驱动录音流程(三)
高通音频驱动录音流程(三) 目录 高通音频驱动录音流程(三) Back End CPU的注册 Back End Codec注册 Back End PCM的注册 Back End CPU的注册 vend ...
- cordova media android不播放声音,cordova-plugin-media 录音并打包上传
一.录音. 插件:cordova-plugin-media 官网中的示例代码很详细了,src可以为"cdvfile://localhost/persistent/"的形式,但我未能 ...
- MFC接收命令行参数的三种方法
方法一: CString sCmdline = ::GetCommandLine(); AfxMessageBox(sCmdline); 将获取到 "C:\test\app.exe -1 - ...
- 在MFC对话框中显示图片的三种方法(有两种使用OpenCv)
最近写了一个用对话框显示图片的程序,这里将学习到的东西整理一下: 编程环境:VC6.0+OpenCv1.0 准备工作:用VC6.0生成一个对话框外壳(全被采用默认设置),然后在对话框中添加一个静态控件 ...
- MFC单文档框架编程(三): CTabView的使用
1. 重载CTabView类,实现CConfBrdView. class CConfBrdView : public CTabView 2. 添加选项卡 void CConfBrdView::OnIn ...
- VS2013 TeeChart_v5 MFC C++ 使用手册干货(三)TeeChart ColorGrid类的简单使用
前面几步安装上面两节进行 1.注册TeeChart5 2.VS添加TeeChart控件并添加变量 3.添加所需类 ColorGrid绘制示例:此方法只适用于TeeChart_v5 VS2010, 在C ...
- MFC第三节-多线程
一.程序,进程,线程 程序是指令的集合,以文件形式储存在磁盘上.一个程序可以对应多个进程,一个进程代表一个实例. 进程由管理进程的内核对象.地址空间组成.内核对象存放关于进程的统计信息,地址空间包含可 ...
- MFC的静态库.lib、动态库.dll(包含引入库.lib)以及Unicode库示例
以vs2012为标准.转自:http://technet.microsoft.com/zh-cn/library/w4zd66ye ,有改动. 一 MFC的静态库(.lib) MFC静态库使用下列命名 ...
- 录音文件下载_苹果手机录音常见问题解答
iPhone录音的使用越来越多,你在使用iPhone录音时遇到过哪些问题? iPhone录音质量高吗?适用于什么录音场景? iPhone 6s以上型号,都拥有多个麦克风,底部的两个麦克风,其中之一就是 ...
最新文章
- RegularExpressionValidator 控件用法
- java的object_Java中的Object类详细介绍
- 神经网络的收敛标准有最优值吗?
- LeetCode-56-Merge Intervals
- gazebo仿真环境加载多个机器人
- QDoc状态status
- linux+proc+原理,Linux内核中的Proc文件系统(一)
- 《网易编程题》疯狂队列
- 关于程序员的脑筋急转弯(附答案)
- IntelliJ IDEA for Mac 在MacOS模式的重构快捷键(Refactoring Shortcut)
- 如何使用django显示一张图片
- Html 中判断某个class的个数
- SqList顺序表实现笔记
- DirectShow 基础教程
- 2018中国企业云计算应用现状及需求调研报告
- Tornado get/post请求异步处理框架分析
- php文件档结构图,ecshop文件结构名称详细版
- 2021年中国牛肉市场供需现状、进出口贸易及价格走势分析[图]
- 微信小游戏马甲包过审(马甲包过包)
- 基于51单片机的校园教室打铃系统
热门文章
- etabs数据_ETABS二次开发入门(一)——简介
- String 的 endsWith() 方法
- crsd.bin Fail With Error CRS-1019 When ohasd Restarted (文档 ID 2291799.1)
- 《Word中设置英文单词首字母不自动变为大写》
- pcb二次钻孔_PCB二次钻孔是什么?PCB钻孔有哪些常见问题
- c语言求圆柱体体积只用int,C语言程序:求常用圆形体的体积
- 关于英文名GAVIN
- ecshop珠宝首饰 奢侈品商城PC网站模板 微信分销+wap手机网站
- QT Designer 生成的ui文件转化成py文件以及简单使用
- 幼儿园关于计算机的应用教案,幼儿园中班教案《聪明的电脑》含反思(通用)...