|
发表于 2016-10-9
|
2.3 处理音频数据块
获取音频数据块的方式有多种(详见msdn),处理方式大同小异,本文以回调函数方式为例说明处理过程。如上所述,waveinproc在waveinopen调用中被设置为回调函数。
waveinproc通过处理wim_open(打开设备)、wim_data(音频数据)、wim_close(设备关闭)消息的方式与系统交互,通常只需处理wim_data消息,其他两条消息可以忽略。
当系统返回一个音频数据块时,系统调用回调函数。此时,回调函数第一个参数被设置为录音设备句柄;第二个参数是消息,即wim_data;第三个参数则是在waveinopen第五个参数中给出的实例数据;其他参数是与消息有关的参数,参见msdn。
处理wim_data的任务由成员函数onwaveindata完成,因此waveinproc及其简单:
cwaverecord *pobject = (cwaverecord *)dwinstance;
if( umsg == wim_data )
pobject->onwaveindata( (wavehdr*)dwparam1 );
在onwaveindata中需要完成两件事:一是保存系统返回的音频数据,二是给录音设备添加数据块以继续录音,否则会因数据块耗尽而停止录音。
在保存系统返回的音频数据块之前,需要调用waveinunprepareheader发送一个通知告诉设备驱动程序该数据块已不能用于录音。保存音频数据块是个单纯的数据保存问题,不需多说,只是有三个要点需要特别关注:
一是msdn中注明在回调函数中严格限制系统调用,因此你不能随心所欲地设计方案,比如说直接将音频数据写入文件是不允许的。通常,你需要设计一个数据块链表结构进行缓冲,这多半涉及多线程编程。特别是如果你使用mfc中的csocket类,那么必须清楚csocket依赖一个名为csocketwnd的内部类,而该类与tls(线程局部存贮)有关,这意味着csocket类是不支持多线程共享的。然而,win32的socket句柄是全局性。因此,获取csocketwnd类对象的窗口句柄之后,我通过直接调用win32 apis使用csocket::m_hsocket避开这个由mfc封装引起的问题。
二是系统是在另一个线程(不是你自己创建的线程)中调用回调函数,这会给使用tls机制的程序带来一些微妙的影响。如果你使用mfc,afxgetapp之类的函数返回值不正确。
三是尽可能地快速。
至于给录音设备添加数据块以继续录音,其步骤与初始化过程一样:
waveinprepareheader( hwavein, pwh, sizeof(wavehdr));
waveinaddbuffer( hwavein, pwh, sizeof(wavehdr) );
2.4 关闭设备释放资源
简单的调用waveinclose即可关闭设备。不过,在关闭时必须保证所有录音数据块已全部返回,这有点麻烦。我的解决方法是设置一个停止录音标志和一个录音数据块计数。当用户发出停止录音的命令后,该标志置为true,而onwaveindata(在回调函数中被调用)检查该标志,在标志置位的情况下,onwaveindata不添加录音数据块。每添加一个录音数据块计数增加,每返回一个则减少(均在onwaveindata中实现)。执行停止录音命令时,先将停止标志值位,等待计数变为零,然后才调用waveinclose。需要注意的是,计数涉及多线程数据共享,应使用线程互斥机制,在单cpu的机子上使用interlockedincrement和interlockeddecrement是个简单的选择。
3 编程技巧
3.1 简单的声音检测
如果你通过网络发送音频数据,自然需要进行音频处理。专业级的处理难度相当高,如果你是在一个局域网上进行通话,那么无需进行音频压缩也可以将就使用(性能自然不高)。但如果你不说话,程序还不停地发送音频数据(静音或噪音),那就太说不过去了。
你可以定义一个静音区间,一旦音频强度进入该区间就意味着没有声音。统计音频数据块中不是静音采样点的个数,当总数超过预设的限制时,才发送该数据块。
对于8位采样,127是静音点。简单的代码如下:
for( i=0; i
{
uvalue = (uchar)data[i];
if( uvalue<=125 || uvalue>=129 )
uhascnt++;
if( uhascnt >= ulestnum )
return true;
}
return false;
3.2 创建音频文件
创建任意格式的音频文件在编程上不是一件轻松的事,但对于8位单声道音频数据而言较容易。定义文件信息头结构:
// wav文件头结构, 对齐方式为1字节
typedef struct
{
char riffid[4]; // "riff"
dword dwfiledatasize; // file size - 8
char waveid[4]; // "wave"
char fmtid[4]; // "fmt "
dword dwfmtsize; // 16
word wformattag; // wave_format_pcm
word wchannels; // 1
dword dwsamplespersec; // 11025
dword dwavgbytespersec; // 11025
word wblockalign; // 1
word wbitspersample; // 8
char dataid[4]; // "data"
dword dwdatasize;
} wavefilehdr, *pwavefilehdr;
打开文件时,预留该结构大小的空间:
setfilepointer(hfile,sizeof(wavefilehdr), null, file_begin );
然后就如写一般文件一样将接收的音频数据写入文件:
writefile(hfile,pwh->lpdata,pwh->dwbytesrecorded,&n, null );
m_dwsizewrite += dwsize;
最后一行代码是将写入的数据量统计下来用于信息头填写。
在关闭音频文件时填写信息头:
wavefilehdr wfd =
{
'r', 'i', 'f', 'f', 0,
'w', 'a', 'v', 'e',
'f', 'm', 't', ' ', 16,
wave_format_pcm, 1, 11025, 11025, 1, 8,
'd', 'a', 't', 'a', 0
};
wfd.dwfiledatasize = dwbytes + sizeof(wavefilehdr)- 8;
wfd.dwdatasize = dwbytes;
::setfilepointer( hfile, 0, null, file_begin );
::writefile( hfile, &wfd, sizeof(wfd), &dwsize, null );
参考文献 |
|