上一篇通过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播放声音和录音的实现(三)相关推荐

  1. 高通音频驱动录音流程(三)

    高通音频驱动录音流程(三) 目录 高通音频驱动录音流程(三) Back End CPU的注册 Back End Codec注册 Back End PCM的注册 Back End CPU的注册 vend ...

  2. cordova media android不播放声音,cordova-plugin-media 录音并打包上传

    一.录音. 插件:cordova-plugin-media 官网中的示例代码很详细了,src可以为"cdvfile://localhost/persistent/"的形式,但我未能 ...

  3. MFC接收命令行参数的三种方法

    方法一: CString sCmdline = ::GetCommandLine(); AfxMessageBox(sCmdline); 将获取到 "C:\test\app.exe -1 - ...

  4. 在MFC对话框中显示图片的三种方法(有两种使用OpenCv)

    最近写了一个用对话框显示图片的程序,这里将学习到的东西整理一下: 编程环境:VC6.0+OpenCv1.0 准备工作:用VC6.0生成一个对话框外壳(全被采用默认设置),然后在对话框中添加一个静态控件 ...

  5. MFC单文档框架编程(三): CTabView的使用

    1. 重载CTabView类,实现CConfBrdView. class CConfBrdView : public CTabView 2. 添加选项卡 void CConfBrdView::OnIn ...

  6. VS2013 TeeChart_v5 MFC C++ 使用手册干货(三)TeeChart ColorGrid类的简单使用

    前面几步安装上面两节进行 1.注册TeeChart5 2.VS添加TeeChart控件并添加变量 3.添加所需类 ColorGrid绘制示例:此方法只适用于TeeChart_v5 VS2010, 在C ...

  7. MFC第三节-多线程

    一.程序,进程,线程 程序是指令的集合,以文件形式储存在磁盘上.一个程序可以对应多个进程,一个进程代表一个实例. 进程由管理进程的内核对象.地址空间组成.内核对象存放关于进程的统计信息,地址空间包含可 ...

  8. MFC的静态库.lib、动态库.dll(包含引入库.lib)以及Unicode库示例

    以vs2012为标准.转自:http://technet.microsoft.com/zh-cn/library/w4zd66ye ,有改动. 一 MFC的静态库(.lib) MFC静态库使用下列命名 ...

  9. 录音文件下载_苹果手机录音常见问题解答

    iPhone录音的使用越来越多,你在使用iPhone录音时遇到过哪些问题? iPhone录音质量高吗?适用于什么录音场景? iPhone 6s以上型号,都拥有多个麦克风,底部的两个麦克风,其中之一就是 ...

最新文章

  1. RegularExpressionValidator 控件用法
  2. java的object_Java中的Object类详细介绍
  3. 神经网络的收敛标准有最优值吗?
  4. LeetCode-56-Merge Intervals
  5. gazebo仿真环境加载多个机器人
  6. QDoc状态status
  7. linux+proc+原理,Linux内核中的Proc文件系统(一)
  8. 《网易编程题》疯狂队列
  9. 关于程序员的脑筋急转弯(附答案)
  10. IntelliJ IDEA for Mac 在MacOS模式的重构快捷键(Refactoring Shortcut)
  11. 如何使用django显示一张图片
  12. Html 中判断某个class的个数
  13. SqList顺序表实现笔记
  14. DirectShow 基础教程
  15. 2018中国企业云计算应用现状及需求调研报告
  16. Tornado get/post请求异步处理框架分析
  17. php文件档结构图,ecshop文件结构名称详细版
  18. 2021年中国牛肉市场供需现状、进出口贸易及价格走势分析[图]
  19. 微信小游戏马甲包过审(马甲包过包)
  20. 基于51单片机的校园教室打铃系统

热门文章

  1. etabs数据_ETABS二次开发入门(一)——简介
  2. String 的 endsWith() 方法
  3. crsd.bin Fail With Error CRS-1019 When ohasd Restarted (文档 ID 2291799.1)
  4. 《Word中设置英文单词首字母不自动变为大写》
  5. pcb二次钻孔_PCB二次钻孔是什么?PCB钻孔有哪些常见问题
  6. c语言求圆柱体体积只用int,C语言程序:求常用圆形体的体积
  7. 关于英文名GAVIN
  8. ecshop珠宝首饰 奢侈品商城PC网站模板 微信分销+wap手机网站
  9. QT Designer 生成的ui文件转化成py文件以及简单使用
  10. 幼儿园关于计算机的应用教案,幼儿园中班教案《聪明的电脑》含反思(通用)...