最近要一个语音识别的Demo,研究了下语音识别知识之后,发现先要实现一个录音模块,百度的过程中,最先看到的是使用DirectSound实现录音,于是欣欣然准备开始(以前大概了解过DirectX,一直想找机会学习学习),然后在看DirectX SDK的过程中,DirectSound有一段话介绍录音也可以使用Windows Multimedia functions,并且DirectSound在这方面并没有性能优势。鉴于最近工作比较空闲,想多学点东西的目的,打算研究下这两种实现方式。
- DirectSound实现方法
- 主要参考链接:利用DirectSound实现声卡录音,还有基础使之补充:http://www.cnblogs.com/stg609/archive/2008/10/24/1318931.html。
- 这里补充一下自己在这个过程中遇到的一点问题。
1、IID_IDirectSoundCaptureBuffer8的问题
在创建一个录音的Buffer对象LPDIRECTSOUNDCAPTUREBUFFER8的过程中,用到QuerryInterface函数,其中第一个参数为IID_IDirectSoundCaptureBuffer8,我当时是链接不上这个标识的,试着加了InitGuid.h 等发现也不行,百度什么的也没找到结果,然后问了以前一个同事,结果人家在Google上一下就搜到结果了,要加上DXGuid.lib,我这里才了解到,原来baidu和google搜索同样的内容,结果会这么不一样。汗颜。
2、IDirectSoundCaptureBuffer8.GetCurrentPosition参数意义
这个函数的两个参数,一个是Capture指针在缓冲中的位置,一个是Read指针在缓冲中的位置。要注意的是,Read指针是指名当前你可以读到哪个位置,而不是从哪个位置开始读,比如第一次取数据的时候,会取缓冲的最开始位置到Read指针所指的位置。又由于是一个环形的缓冲,所以Capture指针的位置不一定总是大于Read指针的,可以理解成,Capture表明了在调用这个函数时正在录取的数据将要写到(为止)的位置,而Read是已经写好了的到这个位置为止的位置。所以我们在读取数据的时候要保存一个偏移量,记录每次要读取的数据的起始位置。
3、录出来的声音总是不对
根据SDK以及资料整理好了Demo的代码,发现生成的文件播放出来的声音总是不对,检查了好久都没找到问题,因为对这方面研究的少所以不知道从何入手了。目前确认的问题有,IDirectSoundCaptureBuffer8.GetCurrentPosition有时候前后两次会返回相同的值,另外生成的wave文件比实际录取的时间要长。
虽然这个问题还没解决,我也发了帖子进行询问,还没找到问题所在,也附上源码,有经验的朋友也可以帮忙看看。
(注:以上问题我已经找到了,原因是我在创建BUFFER的时候指定的WAVE文件格式和创建WAVE文件时指定的格式不同,这里当时写的时候是从不同的地方复制的,导致犯了这样的错误。由于源代码在我的公司电脑上,公司电脑上无法往U盘写入数据,并且不能联网,目前源代码我无法弄出来。所以附件暂时没有更新。)
- Windows WaveIn系类API 实现方式
- 参考链接:基于API的录音机程序,这个链接里粗略介绍了下整个实现流程,具体的实现方式还是要参考各函数的MSDN说明。链接中提供的Demo由于是VC6的,并且我发现没有dsw工程文件(有可能是我下载的时候出错了),所以我在VS2010下新建了一个同名的MFC对话框工程并把源文件和资源文件全部覆盖掉,更改掉消息映射函数(VC6和VS2010的消息映射函数形式不一样了,可参见ON_MESSAGE宏),另外去掉了那个HYPELINK控件(有它编译不过),加上winmm.lib链接,就能编过去了,跑起来效果还不错。
- 这里也补充上自己遇到的一些问题。
1、枚举设备的问题
目前我发现waveIn系列的API没有直接提供枚举录音设备的接口,可以采取的一种方式是利用设备描述表的树结构去访问设备并通过判断设备类型来进行枚举,这种方法是同事介绍的,我没有做尝试,比较繁琐。所以就用了DirectX的枚举接口,但是DirectX使用的是GUID去标识一个设备,而waveInOpen函数是要求一个设备ID,所以就需要一个通过GUID获取设备ID的方法,这里也是询问了之前的一个同事,然后从之前做过的项目中借用的部分代码实现了这个功能,具体实现方式我没太看懂,大概是使用了DirectX的属性方式之类的,具体的可以看代码。
2、停止录音的问题
waveIn系列API的缓冲机制是边提供边使用的方式,就是你提供给他,他使用完了就转交给你,你再提交这种循环。通常这个操作是做在消息WIM_DATA的处理过程中。这里需要注意停止录音的时候,首先调用waveInStop停止录音(此时系统内部可能还有数据没有写到缓冲,所以WIM_DATA消息在这之后还会来),然后调用waveInReset,这个函数SDK有明确说明会将所有提交了的未使用的缓冲都返回给我们,所以WIM_DATA仍然会来,但在这个过程中我们不能再提交缓冲了(有时候会提交失败),所以我这里在调用waveInReset函数之前设置了个标志,当这个标志被置位的时候就不提交缓冲了。最后调用waveInClose函数,这个函数会使得标识设备的句柄失效,所以在这之后就不能使用设备句柄了。
最后总结
我在看第二种方式的Demo的时候,明显感觉比第一种方式的简单,有可能是我对这方面的理解加深了,所以看起来简单些了。缓冲机制方面,第一种是直接提供一个环形缓冲就行,第二种是用户自己提供若干个缓冲,当系统用完之后就转交给你,你如果要继续使用就又要递交给它,显然第一种在算法上高级一些,但第二种要简单更容易理解一些。我个人更倾向于第二种。
又是很久没写博客了,之前辞职在家待了好几个月,然后又出来工作,感觉太生疏了,这个小程序也算是又一个开端吧。
最后附上整个过程中的所有源码,其中SoundCapturer为我使用waveIn所封装的demo,DSRecordDemo为我使用DirectX的demo,RecordHWnd为我从VC知识库下载的demo进行修改之后的demo,waveInDemo是从网上找到的代码。
下载地址:
http://files.cnblogs.com/monotone/RecordDemo.zip