摘 要 Python编程语言具有极高的开发效率,DirectSound提供了强大高效的声音处理功能。文中利用Python编程语言和DirectSound的音频数据捕获技术,并对采集到的音频数据进行了相关分析处理,实时显示其波形和频谱。
关键词 DirectSound;Python;音频捕获;频谱分析
1 引言
Python是一种面向对象、支持动态语义、内置高级数据结构、语法简洁优美、易于扩展的解释型脚本语言。Python最大的特点在于其快速开发功能,其开发效率是C/C++等开发语言所无法匹敌的,因此Python受到越来越多程序员的青睐。
DirectX SDK是微软开发的一套主要用于设计多媒体游戏及程序的API,其中包含了各类与制作多媒体功能相关的组件,DirectSound是其中之一。它提供了强大的声音处理功能,使得开发者能在不熟悉声音硬件细节的前提下开发出高性能的多媒体程序。
Python中的DirectSound模块封装了DirectSound接口,鉴于Python开发的高效性和易扩展性,利用Python开发基于DirectSound的音频处理程序,可以大大缩短开发周期,而且同样能达到利用C/C++等语言开发的性能和效果。
2 技术原理
Python语言中,利用 DirectSound进行音频捕获是基于PyIDirectSoundCapture和PyIDirectSoundCaptureBuffer接口实现的。首先根据选择的录音设备创建接口对象,然后为该接口创建缓冲区对象,声音捕获工作由缓冲区对象的函数完成。开始录音时,程序在缓冲区创建一个或多个通知点,设备将数据写入缓冲区,在捕获数据到达通知点时即触发一个事件,程序就可以取出声卡设备捕获的声音数据
2.1 创建设备对象
调用directsound.DirectSoundCaptureCreate()方法创建音频捕获对象,其参数guid表示录音设备,可由directsound.DirectSoundCaptureEnumerate()函数返回值提供,也可以设为None,表示使用系统默认录音设备。
2.2 创建缓冲区对象
创建缓冲区对象,首先需对一个DSCBUFFERDESC结构对象赋值,指定要创建的缓冲区特征,如缓冲区大小、单双声道、采样率、采样量化位数等。DSCBUFFERDESC结构有两个成员,其中dwBufferBytes定义了缓冲区的大小,lpwfxFormat为WAVEFORMATEX结构对象,规定了声音数据的结构,由pywintypes.WAVEFORMATEX()创建。
WAVEFORMATEX结构包含以下属性:
wFormatTag:格式标识,对于DirectSound只能是WAVE_FORMAT_PCM,表示捕获格式为PCM(Pulse Code Modulation,脉冲编码调制)格式。
nChannels:表示通道数,1为单声道,2为立体声。
nSamplesPerSec:表示采样频率,一般为8000Hz、11025Hz、22050Hz、44100Hz。
wBitsPerSample:表示采样量化位数,通常是8或16。
nBlockAlign:表示采样块字节数,对于PCM是wBitsPerSample*nChannels/8。
nAvgBytesPerSec:表示采样率,以字节为单位,为nBlockAlign和wBitsPerSample的乘积。
通过调用设备对象的方法PyIDirectSoundCapture.CreateCaptureBuffer()创建缓冲区对象,其参数lpDSCBufferDesc即DSCBUFFERDESC结构对象,通过调用directsound.DSCBUFFERDESC()创建。
创建缓冲区对象之后,调用其方法PyIDirectSoundCaptureBuffer.Start()开始采集数据,其参数dwFlags可以设置缓冲区的工作方式:静态缓冲(dwFlags参数设为0)或动态缓冲(dwFlags参数设为directsound.DSCBSTART_LOOPING或1)。其中静态缓冲指一次将一段完整的音频数据写入缓冲,适合容量有限、实时性要求不高的音频数据流;而动态缓冲并不将全部的音频数据一次写入缓冲,而是由程序周期性地动态写入,占用内存空间较小,可以通过使用较小的内存来播放较长的声音,适合于容量大、实时性要求较高的音频数据流。
停止录音时可调用PyIDirectSoundCaptureBuffer.Stop()方法。
2.3 设置通知机制
为确保声音信号的实时采集和处理,应用程序须及时知道什么时候可以开始读取缓冲区内的音频数据。IDirectSoundNotify接口为此提供了一种高效率的方式,即位置通告方式,在缓冲区设定一个或多个通知位置,当缓冲捕获的数据到达设定位置时即发出一个事件通知。当读取位置在缓冲区中循环时,事件将不断地被发送。
首先调用函数win32event.CreateEvent()创建通知事件句柄,其次调用缓冲区对象方法PyIDirectSoundCaptureBuffer.QueryInterface()获取IDirectSoundNotify接口对象,最后通过方法PyDirectSoundNotify.SetNotificationPositions()设置通知位置。
2.4 获取缓冲区数据
设置了通知机制,进行数据捕获的子程序将等待缓冲区的事件通知,通过调用函数win32event.WaitForSingleObject()设置等待一个事件通知或调用win32event. WaitForMultipleObjects()设置等待多个事件通知。当缓冲区被填充到设定位置时,通知事件被触发,调用缓冲区对象PyIDirectSoundBuffer.Update()方法,将音频数据从指定位置读出,同时将该部分缓冲区锁定,以确保音频数据的准确无误。
3 二次封装
为了更简单高效地使用DirectSound编程接口,利用Python面向对象技术对DirectSound音频捕获接口进行二次封装,编写AudioRecord模块,定义类AudioRecord,主要提供以下函数接口:
Record:开始录音并实时捕获音频数据.
Stop:停止录音。
Python中的DirectSound模块封装在win32com扩展库下,可通过以下语句导入:
from win32com.directsound import directsound
另外还需导入下列模块:
import pywintypes
import win32event
3.1 AudioRecord类的初始化
在AudioRecord类的初始化中,完成音频捕获的前期准备工作,包括创建设备对象、创建缓冲区对象、设置通知位置等。主要代码如下:
def __init__(self,nchnl=2,sps=8000,bps=8,t=0.1):
dsc = directsound.DirectSoundCaptureCreate(None, None)#创建设备对象
cdesc = directsound.DSCBUFFERDESC()#创建DSCBUFFERDESC结构对象
self.bSize=int(sps*nchnl*bps/8*t)
cdesc.dwBufferBytes =self.bSize #缓存大小
cdesc.lpwfxFormat = pywintypes.WAVEFORMATEX()#DirectSound数据块格式
cdesc.lpwfxFormat.wFormatTag = pywintypes.WAVE_FORMAT_PCM
cdesc.lpwfxFormat.nChannels = nchnl
cdesc.lpwfxFormat.nSamplesPerSec = sps
cdesc.lpwfxFormat.nAvgBytesPerSec = sps*nchnl*bps/8
cdesc.lpwfxFormat.nBlockAlign = nchnl*bps/8
cdesc.lpwfxFormat.wBitsPerSample = bps
self.buffer = dsc.CreateCaptureBuffer(cdesc)#创建缓冲区对象
self.evt=[]
for i in range(2):
self.evt.append(win32event.CreateEvent(None, 0, 0, None))#创建两个事件通知
Notify=self.buffer.QueryInterface(directsound.IID_IDirectSoundNotify)#创建事件通知接口
Notify.SetNotificationPositions([(self.bSize/2-1,self.evt[0]),(self.bSize-1, self.evt[1])]) #设置两个通知位置,缓冲区每填充bSize/2个样本即发送一个通知消息
self.data=''#用于实时存储捕获的音频数据
self.STATUS=False#录音状态标志
self.wfx=cdesc.lpwfxFormat#存储声音格式
3.2开始及停止录音接口
函数Record作为开始录音及捕获音频数据接口,调用方法PyIDirectSoundCaptureBuff er.Start()告诉缓冲区对象开始录音(动态缓冲模式),通过函数win32event.WaitForSingleObject(),监控AudioRecord初始化里所创建的事件对象,当有事件通知发出时,即从缓冲区取出音频数据。函数Stop调用缓冲区对象的PyIDirectSound CaptureBuffer.Stop()方法,让缓冲区停止工作。主要代码如下:
def Record(self):
self.buffer.Start(directsound.DSCBSTART_LOOPING)#开始录音,动态缓冲模式
self.STATUS=True#设置录音状态标志
i=0
n=self.bSize/2
while self.STATUS:
win32event.WaitForSingleObject(self.evt[i])#等待事件通知
self.data = self.buffer.Update(i*n, n)#从缓冲区取出音频数据,第一个参数为偏移量,第二个参数为数据大小。
i=(i+1)%2
def Stop(self):
self.buffer.Stop()#停止录音
self.STATUS=False#设置录音状态
4 软件实现
利用自定义的AudioRecord模块,编制了简易虚拟示波及频谱分析程序,连接MIC或其他声音输入设备,即可以实时地实现音频信号的波形显示和频谱显示,图2所示为一段音乐的波形图和频谱,其采样率为8000Hz,双声道,量化比特数为8位,PCM格式,每次计算一个声道的采样数为512(时间间隔1024/8000 s)。
4.1 FFT算法
Python提供了丰富的科学计算功能,如Numpy、Scipy等,其均由编译好的C语言写成,因此运算速度很快,完全满足程序实时显示的要求。实验程序采用Scipy模块的fft方法,并进行相关设置,最终频谱显示横坐标表示频率,对于8000Hz的双声道音频信号频率范围为1~4000Hz ,纵坐标表示幅度。
4.2 多线程技术
为了保证图形显示的实时性及音频数据捕获的准确性,两者功能应该相对独立,即在两个独立的线程中完成相关工作,但两个线程之间又应该是相互联系,即完成某一段音频数据捕获后再进行该段数据的显示,即两线程的同步。利用Python的threading模块,调用threading.Thread类创建两个线程,调用threading.Condition类创建条件变量,以使绘图线程与录音线程保持同步。
5 结语
利用Python和DirectSound可以非常方便地开发音频程序和进行科学计算,有着其他编程语言无法比拟的效率。本文基于Python编程语言及DirectSound模块,介绍了利用声卡进行数据采集的方法及关键代码,并借助多线程技术和Python高效的科学计算和绘图功能,将其应用于音频数据波形和频谱的实时显示,编制音频信号的软件示波器和频谱分析仪,程序实现简单、高效,易于根据需要增加更丰富的功能。
参考文献
[1] 杨昆,汪兴东.Python程序员指南. 北京:中国青年出版社[M],2001
[2] 张小虹,王丽娟,任姝婕.数字信号处理基础. 北京:清华大学出版社[M],2007.
[3] 李春洪,毛跃奇,陈贵来等.基于DirectSound的声音实时仿真研究[J]. 计算机仿真,2001;18(3):47-49.
|