OpenVINOTM,给你看得见的未来!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » MCU » 作为A/D卡的声卡编程

共1条 1/1 1 跳转至

作为A/D卡的声卡编程

菜鸟
2005-08-16 21:52:00    评分
一、特点 声卡作为语音信号与计算机的接口卡件,其最基本的一项功 能就是A/D转换。实际上, 除了语音外,很多信号的频率都落在音频范围内(比如机械量 信号,过程量信号等), 当我们需要对这些信号进行采集时,使用声卡作为采集卡是一 种相当令人满意的解决方 案,其理由如下: 1. 价格便宜。一般声卡的价格才一百多元,比起自己从头到 尾开发一块采集卡的成本 低 得多。比起目前市场上的采集卡的价格,更是不可同日而语 。相应地,产品成本也会 降低。 2. 即买即用。完全省略了A/D卡的的硬件开发过程,很大程度 上缩短了产品的开发周期 。 3. 灵活性好。量化位数可编程(8位或16位);采样频率可编 程(一般声卡的最高采样 频率可达200KHz,并且连续可调);采样通道可编程(1通道或 2通道);由于可以使用 在Windows操作系统下,可以用通用的软件开发工具对其进行开 发(如Delphi,VB,VC等 )。 当然也有其局限性,那就是声卡一般只能作为PC插卡用在PC 机上,很难用微控制器对 其进行控制(因为要用到中断和DMA技术),因而很难用在小型 的仪器仪表里。再者由于 声卡是专门针对音频信号而设计的,所以它的采样速率不可能 很高。 二、编程技术 既然声卡本身就是一块很好的A/D卡,硬件部分已经不需操心 了,那么最终的问题就是 怎样对其编程才能够取得A/D的数据。 事实上,声卡是PC的一种多媒体设备,所以可以用Windows 的MCI(Media Control I nterface)命令来控制声卡。MCI它提供了一组与设备无关的控 制命令,是一种访问多媒 体设备的高层次方法。也正因为它属于一种高层次方法,所以 它提供给程序员的灵活性 有限,利用MCI命令来控制声卡录音时,程序员不能在录音的过 程中访问内存中的采样数 据,只有在录音完成后通过访问*.WAV文件才可以得到采样数据 ,尽管最终还是得到了采 样数据,但是这样做一方面嫌其麻烦,更重要的是存取文件需 要耗费时间,声卡在采样 的过程中有可能会停止下来等待文件操作,造成了采样的断续 。在一些实时性要求比较 高的场合(比如波形分析,实时控制等),断续的采样明显是 不行的。 Windows的低级波形音频函数提供了对声卡的最大灵活性的操 作,它允许在采样过程中 随机地访问内存中的每个采样数据,完全可以克服使用MCI命令 所遇到的实时性问题。 Windows以动态连接库Mmsystem.dll的形式提供低级波形音 频函数,在Mmsystem.dll中 总共包括了以下几个有关波形录入的函数: waveInAddBuffer :向声音输入设备发送缓冲区 ;waveInClose :关闭声音输入设 备 waveInGetDevCaps:获取声音输入设备性能; waveInGetErrorText:获取声音出错 信息文本 waveInGetID :获取声音输入设备ID; waveInGetNumDevs:返回声音输入设备 数量 waveInGetPosition :获取声音设备输入位置; waveInMessage :向声音输入设 备发送信息 waveInOpen :打开声音输入设备; waveInPrepareHeader:预备声音输入缓 冲区 waveInReset :停止声音输入设备工作; waveInStart :停止声音输 入设备工作 waveInStop :停止声音输入; waveInUnprepareHeader : 清除预备的声音 文件头 需要说明的是:不同的编程工具多会含有对这些低级波形音 频函数进行说明的头文件 (比如在Delphi4.0中,对Mmsystem.dll说明的文件 是Mmsystem.pas),所以在不同的编 程工具中调用这些函数时有可能会使用不同的名称。 与使用其他设备一样,要想用波形音频函数来控制声卡,必 须要经过以下的步骤: 1. 打开波形输入设备。函数waveInOpen用于打开波形输入设 备,其原型如下: ① WORD waveInOpen(lphWaveIn,wDeviceID,lpFormat,dwCallback, dwCallbackInstan ce, dwFlags) LPHWaveIn: lphWaveIn 该变量用来接收波形输入设备的句柄, 该句柄应当保存下来,因为 其他的波形输入函数还会用到它. ②WORD wDeviceID 该变量用来指明波形输入设备的标记号.当 PC中有多块声卡(准确地说 是波形输入设备)时,操作系统会为每一块声卡分配一个标记号. 可以用waveInGetNumDev s函数来得到能够作为波形输入设备的数目N,则wDeviceID的取 值范围为0~N-1.如果想得 到没个标记号所对应的录音性能,可以使用函 数waveInGetDevCaps.若把wDeviceID设为W AVE_MAPPER(即-1),则系统会自动选择一符合要求的设备(根据 lpFormat的要求). ③lpFormat是一个指向PCMWAVEFORMAT数据结构的指针,应当在 这个数据结构中指明所期 望的采样模式,这个数据结构的定义是这样的: Typedef structure pcmwaveformat_tag { WAVEFORMAT wf; //有关PCM格式设置的另外一种数据结构 WORD wBitsPerSample; //量化位数 }PCMWAVEFORMAT; Typedef structure waveformat_tag { WORD wFormatTag; //采样数据格式,目前只能用PCM格式 WORD nChannels; //通道数目(1或2) DWORD nSamplesPerSec; //采样速率 DWORD nAvgBytesPerSec;//每秒采样得到的数据 WORD nBlockAlign; //记录区块对齐的单位。此值 为nChannels*wBitsPerSample/8 }WAVEFORMAT; ④ DWORD dwCallback.定义回调函数的地址或回调窗口的句柄 。回调函数的地址或回调 窗口用来处理波形 输入设备产生的消息。 ⑤DWORD dwCallbackInstance。这是一个用户自定义的数据, 该数据会一并传给回调函 数(或窗口)。 ⑥DWORD dwFlags。定义打开波形输入设备的标记。 CALLBACK_WINDOW 定义dwCallback为窗口句柄。 CALLBACK_FUNCTION 定义dwCallback为函数地址。 另外还可以在此指定: WAVE_FORMAT_QUERY 只查询波形输入设备是否支持给定 格式而不真的打开波形输 入设备。 WAVE_ALLOWSYNC 同步方式开启波形输入设备,录音工作在后 台进行。 下面一段Delphi程序说明了打开波形输入设备的过程: type TRecorder = class private … FWaveFmt : TWaveFormatEx;//Delphi中,WAVEFORMAT和PCMWAVEFORMAT合 为TwaveFor matEx。 WaveHandle : HWaveIn; WaveHdr1 : PWAVEHDR; //数据缓冲区头结构的指针 (见 下文) WaveBuffer1 : lpstr; //数据缓冲区的指针 (见下文) procedure CallBack(uMsg,dwInstance,dwParam1,dwParam2 : DWORD); stdcall; … end; … Recorder:=TRecorder.Create; … Recorder.FWaveFmt.wFormatTag:=WAVE_FORMAT_PCM; Recorder.FWaveFmt.wBitsPerSample:=16; Recorder.FWaveFmt.nSamplesPerSec:=11025; Recorder.FWaveFmt.nAvgBytesPerSec:=22050; Recorder.FWaveFmt.nBlockAlign:=2; WaveInOpen(@Recorder.WaveHandle,Wave_Mapper,mailt@ Recorder.FWaveFmt, DWORD(@TRecorder.CallBack),DWORD(@Recorder),CALLBACK _FUNCTION + WAVE_ALLOW SYNC); … 2. 为采样数据分配缓冲空间 在Windows环境,可以用GlobalAllocPtr来获取一段内存空 间,但是由于Windows操作 系统采用了虚拟存储管理机制,这块内存空间随时有可能会被 置换到硬盘上,读写硬盘 所耗费的时间会造成采样的不连续。因此,在将缓冲区送往波 形输入设备之前,必须调 用WaveInPrepareHeader函数以保证缓冲区不会被置换到硬盘 上。当然在用GlobalFreeP tr来释放缓冲区之前,必须先要用WaveInUnprepareHeader函 数来解除这种保护。 下面几行Delphi语句说明了使用录音缓冲区的过程。 … Recorder.WaveHdr1:=GlobalAllocPtr(GHND or GMEM_SHARE,Sizeof(WAVEHDR)); Recorder.WaveBuffer1:=GlobalAllocPtr(GHND or GMEM_SHARE,1024); Recorder.WaveHdr1.lpData := Recorder.WaveBuffer1; Recorder.WaveHdr1.dwBufferLength:=1024; WaveInPrepareHeader(Recorder.WaveHandle, Recorder.WaveHdr1, sizeof(WAVEHDR )); WaveInAddBuffer(Recorder.WaveHandle, Recorder.WaveHdr1, sizeof(WAVEHDR)); … WaveInUnprepareHeader(Recorder.WaveHandle, Recorder.WaveHdr1, sizeof(TWAVE HDR)); GlobalFreePtr(Recorder.WaveBuffer1); … 但是,如果只为波形输入设备开辟一个缓冲区,则当该缓冲 区被采样数据填满后,波 形输入设备就无缓冲区可用,不得不停止采样,从而造成了采 样的断续。所以在实际应 用中,至少要为波形输入设备准备两个缓冲区,用上述方法同 时送给波形输入设备。 3. 启动波形输入设备 当上述一切都准备好后,用WaveInStart启动波形输入设备 ,即可开始进行数据采集, 在采集的过程中,一旦有缓冲区被采样数据填满,系统就回 调WaveInOpen中指定的dwCa llback函数(或向指定的窗口发送消息)。在Delphi4.0中, 回调函数的格式是这样的: procedure CallBack(uMsg,dwInstance,dwParam1,dwParam2 : DWORD); stdcall; 其中uMsg是Windows的消息标记号,有三种情况: MM_WIM_OPEN 表示波形输入设备开启成功 MM_WIM_DATA 表示一个缓冲区已满。此时dwParam1中携带有 数据缓冲区头结的 指针。正是通过这个指针,才可以随机地访问缓冲区中的 每一个采样数据。如下面 程序所示: … procedure TRecorder.CallBack(uMsg,dwInstance,dwParam1,dwParam2 : DWORD); st dcall; var i:Integer; SPByte : ^Byte; //假设在打开设备时采用8位量化 SingleData : Integer; BEGIN case uMsg of //uMsg是Windows的消息标记号 MM_WIM_OPEN : //波形输入设备开启成功发回的消息 … MM_WIM_DATA : //一个缓冲区已满发回的消息 begin SPByte := Pointer(dwParam1); for i :=0 to Recorder.DataLength-1 do begin SingleData := SPByte^; //通过SPByte来访问缓冲 区中的数据 … Inc(SPByte); end; end; MM_WIM_CLOSE : //波形输入设备关闭成功发回的消息 … end; END; … MM_WIM_CLOSE 表示波形输入设备关闭成功。当波形输入设 备关闭后,别忘了用Wave InPrepareHeader和GlobalFreePtr来释放缓冲区内存。 4. 关闭语音输入设备 waveInStop(hWaveIn) 停止语音输入 waveInReset(hWaveIn) 重置语音输入设备 waveInClose(hWaveIn) 关闭语音输入设备。其中hWaveIn是 WaveInOpen得到的设备句 柄。 在关闭语音输入设备前,必须重置语音输入设备,否则系统 会出现这样的错误提示: "MMSYSTEM033 媒体数据仍在播放中,请重置设备或等到数据 播放完毕"。但是只有当一 个缓冲区填满数据后,才能重置语音输入设备 以上波形输入函数,若调用成功则返回0;否则返回非0,此 时可以用waveInGetError Text函数来得到出错信息,这样做的目的是方便调试。 三、必须注意的几点 以上阐述了作为A/D卡的声卡编程技术,但是还必须注意以下 几点 1. 声卡的采样频率并不只限于11025Hz,22050Hz,44100 Hz 三种,大多数声卡的采样 频率在一定的范围内是可调的(当然会存在一定的偏差)。有 的声卡的最高采样频率可 达200K Hz(有可能随不同品牌而异)。 2. 缓冲区不能设得太小,否则也会造成采样的不连续。在作 者的声卡上,若采用16为 量化,22050Hz的采样速率,缓冲区设为1K字节,理论上每秒钟 可以得到22050*2个字节 的数据,实际上每秒钟只能得到大约16000*2个字节的数据。若 缓冲区设为2K字节,则与 理论值一致。 3. A/D转化后的数据格式是PCM格式,即:若是8位量化,对应 着8位无符号数据,0对应 着负满幅值,128对应着零电平,255对应着正满幅值;若是16 位量化,对应着16位有符 号数据,-32768对应着负满幅值,0对应着零电平,32767对应 着正满幅值。编程过程中 应注意所声明的数据类型是否与之相符合,比如在Delphi4.0中 ,8位无符号数据对应着 Byte型数据,16位有符号数据对应着SmallInt型数据。 4. 由于声卡的输入端往往带有隔直电容,所以不能用声卡直 接对直流量进行采集。解 决的办法就是将这个隔直电容短接。 5. 同样地,利用windows的API函数和声卡的D/A功能也可以 使声卡产生模拟音频信号输 出。



关键词: 作为     声卡     编程     采样     数据     设备     波形     函数         

共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]