基于VC++60的音频信号采集系统.docx
《基于VC++60的音频信号采集系统.docx》由会员分享,可在线阅读,更多相关《基于VC++60的音频信号采集系统.docx(26页珍藏版)》请在冰豆网上搜索。
![基于VC++60的音频信号采集系统.docx](https://file1.bdocx.com/fileroot1/2022-11/27/42dd724e-8276-4998-b1c1-2f2fca828b18/42dd724e-8276-4998-b1c1-2f2fca828b181.gif)
基于VC++60的音频信号采集系统
C++课程设计报告
课题名称:
基于VC++6.0的音频信号采集系统
课程名称C++程序课程
指导教师
班级
学号
学生姓名
完成日期
基于VC++6.0的音频采集系统
【摘要】本课程设计将实现一个音频采集的录音系统。
其不仅实现了录音的功能,同时可以播放录音。
整个系统利用MFC进行框架设计,同时利用消息响应机制对窗口的消息进行处理。
整个系统主要分为录音和播放录音,录音时要打开录音设备,准备缓存区,录音完成后可以播放录音,播放时要打开设备播放录音。
在实现录音和播放录音的过程中,系统会自动调用程序的消息函数从而对进行相应的操作。
【关键词】MFC框架;消息响应机制;音频采集
1、引言
本文详细介绍了声音波形文件的存储格式,以及Windows提供的有关WAVE输入输出的API函数,对VC++中声音波形文件及声卡的编程进行了说明,并给出了一个实现声卡操作的类。
1.1课题背景
随着多媒体计算机的发展,声卡已经成为标准设备,它为我们提供了一个非常有用的接口,除播放声音之外,还可利用其A/D转换功能进行低速数据采集、甚至通信。
在Windows环境下,波形文件的播放和录制可以通过MCI调用很方便的实现,但是它无法做到对原始数据进行操作,难以在如数据采集情况下应用。
本文详细介绍了声音波形文件的存储格式,以及Windows提供的有关WAVE输入输出的API函数,对VC++中声音波形文件及声卡的编程进行了说明,并给出了一个实现声卡操作的类。
在具体说明软件编程以前,了解一些声卡的硬件原理和音频采样的基本知识是有帮助的。
声卡是一个能够完成A/D和D/A转换的设备,录音时,它将输入的模拟的信号进行采样并将其幅度用二进制数值来表示,回放过程与之相反。
对声音波形而言,有两个重要的参数:
采样频率和量化位数。
采样频率表示每秒从模拟信号中取多少个点的数据,根据采样定理,采样频率必须大于原信号最高频率的2倍,因此在采样之前都对模拟信号进行低通滤波,使之满足采样定律。
电话的采样频率为8KHz,理论上其频率范围为0-4KHz,CD为48KHz,频率范围为0-24KHz。
1.2课程设计目的
在程序设计中,通过设计、编制、调试音频采集的程序,加深对语法及语义分析原理的理解,并实现对命令语句的灵活应用。
VC++编程方两种法有,一种是非windows编程,另一种就是windows编程,本系统用到的编程方法就是第二种。
面向对象程序设计中关键是如何将问题域中的实体(即日常所见的概念)抽取出来,作为C++程序中的类,而属性与行为作为类的两类要素通常是必不可少的,甚至还应考虑类必须满足的约束。
2、需求分析
2.1开发工具介绍
C++语言是在C语言的基础上不断发展和完善起来的,而C是吸收了其他语言的一些优点逐步成为实用性很强的一种语言。
C++语言主要有如下特点:
a)C语言是一种结构化的程序设计语言,语言简洁,使用灵活方便。
它既适用于设计和编写大的系统程序,又适用于编写小的控制程序,同样也适用于科学计算器。
b)它既有高级语言的特点,又有汇编语言的特点。
其运算丰富,除了能提供对数据的算术运算外,还提供了二进制的位运算,并且提供了灵活的数据结构。
c)程序的可移植性好,在某一种型号的计算机上开发的用C语言编写的程序,基本上可以不做修改,而可直接移植到其他型号和不同档次的计算机上运行。
d)程序的结构不够严密,程序设计的自由度大。
这对于比较精通C语言的程序设计者来说,可以更方便的设计出高质量的非常通用的程序。
2.2需求分析
本系统要实现一个录音的功能,除了录音的功能,还需要能够播放录音和暂停播放录音,整个系统通过VisualC++6.0实现,需要调用电脑的声卡来完成整个系统。
在录音开始阶段通过调用声卡录音,录音完成并保存,最后通过声卡来播放录音。
3、设计背景
3.1、MFC框架
MFC(MicrosoftFoundationClasses),是一个微软公司提供的类库(classlibraries),以C++类的形式封装了Windows的API,并且包含一个应用程序框架,以减少应用程序开发人员的工作量。
其中包含的类包含大量Windows句柄封装类和很多Windows的内建控件和组件的封装类。
MFC提供了MFCAppWizard自动生成框架。
3.2窗体设计及控件资源
利用VisualC++提供的资源编辑器对对话框资源进行编辑。
删除对话框中默认的”确定”和”取消”按钮。
添加Text控件和添加若干按钮控件,其ID如表3-1所示:
表3-1控件资源清单
控件名称
控件ID
用途
信息采集
IDC_REC_START
开始录音
计时器
IDC_STATIC
记录录音时长
停止
IDC_REC_STOP
停止录音
播放
IDC_PLAY_START
播放录音
暂停
IDC_PLAY_PAUSE
暂停播放
停止
IDC_PLAY_STOP
停止播放
退出
IDC_exit
退出窗口
3.3、MFC实现框架
下图1-1为整体界面的截图:
图1-1界面截图
下图1-2为图标界面的截图:
图1-2图标设计截图
4、设计概要
4.1、设计思路
1.设定音频采集参数(采样率、声道等);
2.打开音频设备、准备wave数据头和开辟缓存,操作采集的数据并保存录音;
3.设定音频回放参数,打开回放设备、准备wave数据头和写wave数据;
4.界面框架利用MFC生成。
下图2-1为系统整体架构流程图:
图2-1总体架构设计流程图
4.2、Windows波形录入的函数
Windows下的音频采集,会用到的函数及类库:
1、WaveIn函数簇
(1).WaveInOpen//为录音而打开一个波形输入设备
(2).WaveInPrepareHeader//为波形输入准备一个输入缓冲区
(3).WaveInAddBuffer//向波形输入设备添加一个输入缓冲区
(4).WaveInStart//启动在指定的波形输入设备的输入
2、WaveOut函数簇
(1).WaveOutOpen//为播放而打开一个波形输出设备
(2).WaveOutPrepareHeader//为播放准备一个波形缓冲区
(3).WaveOutPause//暂停指定波形输出设备上的播放
(4).WaveOutRestart//重新启动一个被暂停的波形输出设备
4.3、系统分析
Ä设计一个以Cdialog为基类的CrecordHWndDlg派生类,定义数据成员和成员函数如下:
/*******数据成员*********/
HICONm_hIcon;//图标的句柄
BOOLbRecording,bPlaying,bReverse,bPaused,bEnding,bTerminating;
//bEnding用来判断外部是否按下"停止"按钮;是则不分配内存,直接返回;
DWORDdwDataLength,dwRepetitions;
//dwRepetitions是重复次数
//dwDataLength是一个外部定义的DWORD变量,用来记录数据的长度;
HWAVEINhWaveIn;//输入设备句柄
HWAVEOUThWaveOut;//输出设备句柄
PBYTEpBuffer1,pBuffer2,pSaveBuffer,pNewBuffer;
//pBuffer1pBuffer2保存输入数据的两个缓冲区,
//pSaveBuffer是外部定义的一个临时缓存
PWAVEHDRpWaveHdr1,pWaveHdr2;//两个声音文件头
WAVEFORMATEXwaveform;//音频格式
/*******成员函数*********/
voidDrawWave(CDC*pDC,PBYTEpData,DWORDdwLength);//绘制音频波形的函数
Ä设计一个以Cdialog为基类的CAboutDlg派生类,该类首先获取系统菜单句柄并对其进行处理,然后对窗口进行初始化等操作。
这样之后就可以进入采集音频阶段了。
Ä音频采集流程:
1.设置两个缓冲区:
pBuffer1pBuffer2
/*
最先,我们要分配两个缓冲区.因为数据首先要保存到内存中,两个内存缓存区间可以较快进行切换,可以避免录音有断断续续的现象.
*/
pBuffer1=(PBYTE)malloc(INP_BUFFER_SIZE);
pBuffer2=(PBYTE)malloc(INP_BUFFER_SIZE);
/*
如果只要一个缓冲区,当缓冲区满,保存数据时,会无法保存这段时间采集的语音,导致最后获得的声音断断续续。
使用两个缓冲区,当一个缓冲区满的时候,保存这个已满的缓冲区数据,而由另一个缓冲区继续采集语音。
*/
2.设置录音方式:
waveform
waveform.wFormatTag=WAVE_FORMAT_PCM;//录音的格式
waveform.nChannels=1;//单声道数,数值可为1或2
waveform.nSamplesPerSec=40000;//采样率
waveform.nAvgBytesPerSec=40000;//是每秒钟的字节数
waveform.nBlockAlign=1;//是每个样本的字节数
waveform.wBitsPerSample=8;//采样位,模拟信号转数字信号的精准度
waveform.cbSize=0;//是附加信息的字节大小
3.打开录音设备:
waveInOpen
waveInOpen(&hWaveIn,WAVE_MAPPER,&waveform,(DWORD)this->m_hWnd,NULL,CALLBACK_WINDOW)
4.为录音设备准备缓存:
waveInPrepareHeader
/*
设备可以打开后,就需要初始化两个输入缓存区的声音文件头了
声音文件头主要是在录音时,记录相关的数据,以方便后期的处理.
*/
waveInPrepareHeader(hWaveIn,pWaveHdr1,sizeof(WAVEHDR));
waveInPrepareHeader(hWaveIn,pWaveHdr2,sizeof(WAVEHDR));
5.为输入设备增加缓存:
waveInAddBuffer
waveInAddBuffer(hWaveIn,pWaveHdr1,sizeof(WAVEHDR));
waveInAddBuffer(hWaveIn,pWaveHdr2,sizeof(WAVEHDR));
6.启动录音:
waveInStart
waveInStart(hWaveIn);//打开输入设备,开始录音
7.清除缓存:
waveInUnprepareHeader
waveInUnprepareHeader(hWaveIn,pWaveHdr1,sizeof(WAVEHDR));
waveInUnprepareHeader(hWaveIn,pWaveHdr2,sizeof(WAVEHDR));
8.停止录音:
waveInReset
waveInReset(hWaveIn);//停止录音,关闭输入设备
//waveInReset停止给定的波形输入设备的输入,且将当前位置清零.
9.关闭录音设备:
waveInClose
waveInClose(hWaveIn);
Ä音频输出流程
1.打开输出设备:
waveOutOpen
waveOutOpen(&hWaveOut,WAVE_MAPPER,&waveform,(DWORD)this->m_hWnd,NULL,CALLBACK_WINDOW)
2.为输出设备准备缓存:
waveOutPrepareHeader
waveOutPrepareHeader(hWaveOut,pWaveHdr1,sizeof(WAVEHDR));
3.写数据导输出设备缓存:
waveOutWrite
waveOutWrite(hWaveOut,pWaveHdr1,sizeof(WAVEHDR));
//写数据导输出设备缓存
4.清除输出缓存:
waveOutUnprepareHeader
waveOutUnprepareHeader(hWaveOut,pWaveHdr1,sizeof(WAVEHDR));
5.停止输出:
waveOutReset
waveOutReset(hWaveOut);//停止回放,关闭输出设备
6.关闭输出设备:
waveOutClose
waveOutClose(hWaveOut);//waveOutClose关闭指定的波形输出设备
Ä实现声音波形的绘制
DrawWave(CDC*pDC,PBYTEpData,DWORDdwDrawLength)
//通过采集的音频进行绘制声音波形
Ä退出
voidCRecordHWndDlg:
:
Onexit()//退出(按钮)
4.4、设计重点
上面主要概述了采集音频和播放录音的整体流程,在采集音频和播放音频是有几个很重要的消息处理函数,下面主要来解释它们的作用和调用时间.
采集音频消息函数:
MM_WIM_OPEN//录音设备的打开
MM_WIM_DATA//录音设备数据的采集及操作
MM_WIM_CLOSE//录音设备的关闭
采集音频消息函数:
MM_WOM_OPEN//录音播放时
MM_WOM_DATA//录音暂停时
MM_WOM_CLOSE//录音停止时
其中,MM_WIM_DATA函数是本程序的核心。
其主要作用是将输入数据另行保存在一缓冲区内(pSaveBuffer),该缓冲区的长度将随着已录入数据的大小而增加,从而实现保存输入话音数据的功能。
注意:
消息处理函数是消息自我驱动的,不需要我们的人为干预。
比如:
当我们打开设备时,系统会自动调用MM_WIM_OPEN,当我们将数据添加到缓冲区,而缓冲区满时,系统会自动调用MM_WIM_DATA,我们所需要做的,就是对该函数编好相应的源代码。
那么消息函数到底是怎么实现的呢?
消息响应机制实现:
1、消息的组成:
一个消息由一个消息名称(UINT),和两个参数(WPARAM,LPARAM)。
当用户进行了输入或是窗口的状态发生改变时系统都会发送消息到某一个窗口。
例如当菜单转中之后会有WM_COMMAND消息发送,WPARAM的低字中(LOWORD(wParam))是命令的ID号,对菜单来讲就是菜单ID。
当然用户也可以定义自己的消息名称,也可以利用自定义消息来发送通知和传送数据。
2、谁将收到消息:
一个消息必须由一个窗口接收。
在窗口的过程(WNDPROC)中可以对消息进行分析,对自己感兴趣的消息进行处理。
例如你希望对菜单选择进行处理那么你可以定义对WM_COMMAND进行处理的代码,如果希望在窗口中进行图形输出就必须对WM_PAINT进行处理。
3、未处理的消息到那里去了:
M$为窗口编写了默认的窗口过程,这个窗口过程将负责处理那些你不处理消息。
正因为有了这个默认窗口过程我们才可以利用Windows的窗口进行开发而不必过多关注窗口各种消息的处理。
例如窗口在被拖动时会有很多消息发送,而我们都可以不予理睬让系统自己去处理。
4、窗口句柄:
说到消息就不能不说窗口句柄,系统通过窗口句柄来在整个系统中唯一标识一个窗口,发送一个消息时必须指定一个窗口句柄表明该消息由那个窗口接收。
而每个窗口都会有自己的窗口过程,所以用户的输入就会被正确的处理。
例如有两个窗口共用一个窗口过程代码,你在窗口一上按下鼠标时消息就会通过窗口一的句柄被发送到窗口一而不是窗口二。
5、系统实现
5.1、调试环境
测试环境如下:
测试系统:
WindowsXP或Win7
测试工具:
VisualStudioC++6.0
5.2、运行调试
下图3-1为采集音频时截图:
图3-1采集音频截图
通过点击图3-1中信息采集按钮就可以开始采集音频了,同时声音波形也开始绘制,直到停止采集.
下图3-2为停止采集音频时的截图:
图3-2停止采集音频截图
通过点击图3-2停止按钮时,系统停止音频采集同时声音波形的绘制也停止,系统内部会调用相应的消息函数来实现.
下图3-3为播放音频时的截图:
图3-3播放音频截图
通过点击图3-3中播放按钮,可以听到刚才采集音频时录的音,这是通过系统中消息相应机制实现的。
6、心得体会
6.1、系统改进
虽然这个系统对于基本的音频采集功能能够实现,但是它还存在许多不足之处,比如:
1.设计界面过于简洁,不够美观;
2.录音后必须手动存储,只能回放当前录音;
3.录音中存在杂音较严重的问题;没有调节音量大小功能等
所以后期我还要对系统进行更进一步的升级,使其尽可能满足各方面的需求。
6.2、总结
两周的课程设计很快就结束了,通过这次C++课程设计,我学到了很多课程之外的C++知识,其中利用MFC框架设计界面非常方便,也很快捷,还有消息相应机制是怎么实现的等。
实践出真知,之前我的C++知识只是停留在理论水平,而且就算理论水平,也存在很多漏洞。
有时,在做课题的时候,理论的漏洞冒了出来,我就只能在看着课本慢慢的再学习一遍,因为当时有些东西就没有搞懂,所以现在就又翻出课本,看着课本编程,也算是将旧的东西复习了一遍。
同时,有的理论在实习操作过程中印象更加深刻。
成功=勤奋+合作。
我暂且将我的课程设计成果用成功来代替,虽然离成功差的还很远。
开始时我笨拙的读着老师给的相关的程序,发现有什么不懂得,先是一个人自主的思考,实在解决不了的就和其他同学讨论,有时别的同学会很清晰的给我说明一些课题的思路,但大多数,其他同学也不懂、或者也在这方面存在疑问,于是,大家发表各自的看法、思路,然后就讨论了起来,最后,在大家的共同努力之下,问题的答案逐渐浮出水面。
交流与合作在实习过程中给我很大的帮助,我得到了很多,每次看到解决一道问题后大家的愉悦,我想大家应该与我一样收获很大吧。
说真的,我挺喜欢这种讨论的氛围,它也让实习过程变得趣味横生,不再只是呆滞的盯着屏幕写程序。
本次课程设计的缺陷,对我来说就是,所有程序的原始数据都来自老师给的数据或者是网上的数据,跟其他同学会有所重复,但所幸代码能够读懂。
这次实习总体来说,每天我的过的挺累,但累是值得的,这个值得不仅仅是课题成果上,更重要的让我成长好多,也许这就是我在这短短的十天左右的时间里最好的回报吧。
最后,真心感激跟我一起完成课题的同学们和耐心指导我们的惠老师,我会在以后的路上更加努力!
7、参考文献
[1]刘锐宁,梁水等编.《VisualC++开发实例1200例》.清华大学出版社.2011.1
[2]侯俊杰编.《深入浅出MFC》.华中科技大学出版社.2001.1
[3]爬罗塞斯等编.《MFCWindows程序设计》.清华大学出版社.2007.5
[4]佩措尔德等编.方敏,张胜等译.《Windows程序设计》.清华大学出版社.2010.9
[5]陈哲,魏衍君编.《VisualC++程序设计》.西北工业大学出版社.2009.2
8、附录
部分核心代码:
voidCRecordHWndDlg:
:
OnRecStart()//开始录音准备(信号采集按钮)
{
//TODO:
Addyourcontrolnotificationhandlercodehere
//allocatebuffermemory
/*最先,我们要分配两个缓冲区.因为数据首先要保存到内存中,
两个内存缓存区间可以较快进行切换,可以避免录音有断断续续的现象.*/
//如果只要一个缓冲区,当缓冲区满,保存数据时,会无法保存这段时间采集的语音,导致最后获得的声音断断续续。
//使用两个缓冲区,当一个缓冲区满的时候,保存这个已满的缓冲区数据,而由另一个缓冲区继续采集语音。
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("Memoryerro!
");
return;
}
/*接下来需要设置录音的方式,需要用到WAVEFORMATEX结构.声道数,
采样位和采样率都可以在这结构中设置.*/
waveform.wFormatTag=WAVE_FORMAT_PCM;//录音的格式
waveform.nChannels=1;//单声道数,数值可为1或2
waveform.nSamplesPerSec=40000;//采样率,每秒采集次数,数值有:
11025,22050,44100
waveform.nAvgBytesPerSec=40000;//是每秒钟的字节数
waveform.nBlockAlign=1;//是每个样本的字节数
waveform.wBitsPerSample=8;//采样位,模拟信号转数字信号的精准度,数值有:
8,16
waveform.cbSize=0;//是附加信息的字节大小方式为WAVE_FORMAT_PCM时此参数可以忽略
//openwaveformaudoforinput
//设置完毕之后,就可以用waveInOpen函数打开输入设备.
if(waveInOpen(&hWaveIn,WAVE_MAPPER,&waveform,(DWORD)this->m_hWnd,NULL,CALLBACK_WINDOW)){
free(pBuffer1);
free(pBuffer2);
MessageBeep(MB_ICONEXCLAMATION);
AfxMessageBox("Audiocannotbeopen!
");
}
/*设备可以打开后,就需要初始化两个输入缓存区的声音文件头了
声音文件头主要是在录音时,记录相关的数据,以方便后期的处理.*/
/**********************第一个缓冲区************************************/
pWaveHdr1->lpData=(LPTSTR)pBuffer1;//第一个缓冲区地址
pWaveHdr1->dwBufferLength=INP_BUFFER_SIZE;//缓冲区长度
pWaveHdr1->dwBytesRecorded=0;//是已录音数据大小
pWaveHdr1->dwUser=0;//是用户数据
pWaveHdr1->dwFlags=0;//是控制标志表