Windows下录音功能WareIn实现(含死锁破解、检测外部输入设备更改和重启录音服务)

编程入门 行业动态 更新时间:2024-10-10 05:25:09

Windows下录音功能WareIn实现(含<a href=https://www.elefans.com/category/jswz/34/1769948.html style=死锁破解、检测外部输入设备更改和重启录音服务)"/>

Windows下录音功能WareIn实现(含死锁破解、检测外部输入设备更改和重启录音服务)

Windows下录音功能WareIn实现(含死锁破解、检测外部输入设备更改和重启录音服务)

  • WareIn的使用步骤
    • 设置WAVEFORMATEX 采集声音的格式
    • 获取设备句柄
    • 准备2个buff加入到队列中
    • 开始录音
    • 缓存区满,系统触发回调函数
    • 停止录音
  • 防止系统死锁
  • 录音数据来源切换(麦克风或者媒体播放器)
  • 重点:强制切换输入设备,录音功能实现自动重启
    • 该问题出现的广泛性
    • 解决办法

WareIn的使用步骤

WareIn这一族类,是微软提供操作录音设备的一系列API。如果想播放声音,则使用WareOut。

设置WAVEFORMATEX 采集声音的格式

		WAVEFORMATEX waveForm;waveForm.nSamplesPerSec = 22050;waveForm.wBitsPerSample = 16; /* sample size */waveForm.nChannels = 2; /* channels*/waveForm.cbSize = 0; /* size of _extra_ info */waveForm.wFormatTag = WAVE_FORMAT_PCM;waveForm.nBlockAlign = (waveForm.wBitsPerSample * waveForm.nChannels) >> 3;waveForm.nAvgBytesPerSec = waveForm.nBlockAlign * waveForm.nSamplesPerSec;

nSamplesPerSec :代表采样频率,一般为8000hz,11025hz,22050hz,41100khz。
wBitsPerSample :根据wFormatTag设置的类型,设置采样率的大小,如果设置为WAVE_FORMAT_PCM,则大小为8的整数倍。
nChannels :音频声道的数量。可以是1或者2,一般都是2,代表左右2个声道。
cbSize :额外的空间,一般不需要,设置为0。
wFormatTag :波形音频的格式,一般的情况下设置为WAVE_FORMAT_PCM。
nBlockAlign :以字节为单位的块对齐的大小,一般为: (nChannels*wBitsPerSample)/8。
nAvgBytesPerSec :平均的数据传输率,单位为byte/s

获取设备句柄

		waveInOpen(&hWaveIn, WAVE_MAPPER, &waveForm, (DWORD)MicCallBack, (DWORD)this, CALLBACK_FUNCTION);

hWaveIn:在正确执行该函数后,hWaveIn就被赋值。
WAVE_MAPPER:通过系统查找可用的设备。
waveForm:音频流信息对象的指针。这个参数就是我们第一步设置的对象。
MicCallBack:录音消息的处理程序,可以设置为一个函数、或者事件句柄、窗口句柄、一个特定的线程。也就是说录音消息产生后,由这个参数对应的值来处理该消息。包括关闭录音、缓冲区已满、开启设备。
(DWORD)this:第四个参数的参数列表。
CALLBACK_FUNCTION:打开设备的标示符。对应第四个参数,如果第四个参数设置为函数,则第6个参数的值为CALLBACK_FUNCTION;如果是事件,则为CALLBACK_EVENT;如果为窗体句柄(第5个参数设置为0),则为CALLBACK_WINDOW;如过设置为0,则为CALLBACK_NULL;如果为线程,则为CALLBACK_THREAD。

注意:要想该函数成功执行,必须在开始之前,有录音设备的存在(台式电脑一定要插入麦克风才可以被检测到)

准备2个buff加入到队列中

	waveInPrepareHeader(hWaveIn, &Whdr1, sizeof(WAVEHDR));//准备一个波形数据块头用于录音waveInPrepareHeader(hWaveIn, &Whdr2, sizeof(WAVEHDR));//准备二个波形数据块头用于录音waveInAddBuffer(hWaveIn, &Whdr1, sizeof(WAVEHDR));//指定波形数据块为录音输入缓存waveInAddBuffer(hWaveIn, &Whdr2, sizeof(WAVEHDR));//指定波形数据块为录音输入缓存

开始录音

waveInStart(hWaveIn);

缓存区满,系统触发回调函数

CAudioWare* pwnd = (CAudioWare*)dwInstance;PWAVEHDR header = (PWAVEHDR)dwParam1;switch (uMsg){case WIM_OPEN:break;case WIM_DATA:{if (pwnd->isStop) // 防止死锁,后文有重点介绍{break;}if (header->dwBytesRecorded != BUF_MAX_SIZE) // stop时的缓冲区不一定会被填满,后文有重点介绍{break;}// 收到了录音的数据,开始进行业务处理。selfFunction();memset(header->lpData, 0, BUF_MAX_SIZE);MMRESULT result = waveInAddBuffer(hWaveIn, header, sizeof(WAVEHDR)); // 将处理完的缓冲区,重新放到队列中if (result != MMSYSERR_NOERROR){return FALSE;}}break;case WIM_CLOSE:break;

当队列中buff存储到最大值BUF_MAX_SIZE,系统会自动触发回调函数(即,wareInOpen中设立的回调函数)。我们在WIM_DATA中完成业务上要做的事情。

停止录音

	isStop = TRUE;if (NULL != pBuffer1){delete[] pBuffer1;pBuffer1 = NULL;}if (NULL != pBuffer2){delete[] pBuffer2;pBuffer2 = NULL;}waveInStop(hWaveIn); // 不在WIM_CLOSE中实现,避免出现缓冲区还在队列的错误waveInReset(hWaveIn); // 阻塞函数, 函数会终止输入, 并触发WIM_DATA;Sleep(10);return waveInClose(hWaveIn);//停止录音

调用顺序:waveInStop、waveInReset,最后再waveInClose。

防止系统死锁

WareIn的死锁产生情况:在停止录音时,要调用waveInReset,这个函数的机制是:将队列中剩下的buff(此时的buff很可能并没有填充满)发送到回调函数的WIM_DATA,而WIM_DATA中如果执行到waveInAddBuffer这一步,就会把这个buff又放到队列中,从而产生死锁,无法正常停止录音。

解决办法:用一个原子量isStop 来解决,防止回调函数执行到waveInAddBuffer这一步。

补充:waveInReset,处理录音结束前队列中的清理工作,所以最后一个buff很可能并没有达到BUF_MAX_SIZE的大小就被强制触发了回调。如果对数据有严格要求,这一步可以通过判断buff块大小,将这一块舍弃。
我的业务内容,是将音频输入,进行傅里叶转换,所以对输入的数据个数有严格要求,所以不足BUF_MAX_SIZE大小的都被我舍弃了,避免FFT转换时出现崩溃。

录音数据来源切换(麦克风或者媒体播放器)

在我们操作系统的声音设置界面,输入设备一般有麦克风和立体声混音。
当麦克风启用,立体声禁用时,录音来源于麦克风;
当麦克风禁用,立体声启用时,录音来源于系统内的媒体播放器;
当两者都启用,麦克风和系统的媒体播放器,都可以采集到声音。
当两者都禁用,那么waveInOpen时就会失败。

重点:强制切换输入设备,录音功能实现自动重启

这一块是今天要说的重点,也是微软目前存在的一个bug。
该技术点产生的实际情景:当音频采集服务开始执行后,用户从系统声音设置中,强制更换了输入设备。
产生的影响:相当于设备丢失。
技术上的影响:waveInOpen函数执行成功后获得了设备的句柄。当强制禁用设备用,句柄虽然在内存中不为空,但是却无效。如果继续使用微软的api,比如回调函数中的waveInAddBuffer,会意外阻塞,一直没有返回值,最后导致整个程序卡死,必须重启才能解决。这看起来像是微软的一个bug。

该问题出现的广泛性

我特意下载了诸如 腾讯旗下的“全民K歌PC版”、"酷狗音乐"等百万级用户的大平台旗下的产品,发现这些软件在录音时,如果手动强制更改输入设备,会突然卡住;当恢复到更改前的设置状态后,点击录音依然没有办法继续录音。除非退出录音重新开启,甚至退出软件重新打开,录音功能才能正常使用。

对比输入设备,我又试了输出设备:QQ音乐、暴风影音等媒体播放器,在播放的过程中,外部禁用输出设备扬声器,发现这些软件全部卡住无法继续播放,当然,禁用后不能播放是正常的。接下来,重新启用扬声器,发现这些软件都无法继续播放,只能关闭软件重新打开,才可以播放。

由此可见,禁用正在使用中的音频设备,然后启用另外一个闲置设备后,继续执行录音/播放功能,是很多大平台都没有考虑到的事情,甚至可能是微软都没有考虑到的问题。

解决办法

之前的思路一直在想,如何找到一个非阻塞的waveInAddBuffer(因设备打开后,我的回调函数里只会执行waveInAddBuffer这一个系统API),结果发现网上根本没有任何资料,甚至连强制禁用正在使用的输入设备的问题都没有。估计我是第一个遇到这种问题的人。

既然没有非阻塞的API,那么接下来的思路就是如何检测到这种外部强制性的改动。这时候,灵机一动,既然禁用设备后,再执行wareIn族类的API函数会出错,那么,我就将这种出错作为强制切换的信号。

于是,用Event在音频文件每次成功执行waveInAddBuffer后,将Event置为有信号。同时,外部再写一个类,调用这个音频文件,用WaitForSingleObject等待音频文件中的Event变为有信号,如果等待超时,就认为是被切换了输入设备,于是清理内存,删除音频文件对象,重新new 一个音频文件对象,再次执行录音。这样,检测到输入设备切换后,就实现了自动重启音频。

部分代码如下:

	while (TRUE) {ResetEvent(pService->m_audioWare->m_deviceChangeEvent);if (WAIT_OBJECT_0 != WaitForSingleObject(pService->m_audioWare->m_deviceChangeEvent, 10000)){if (pService->m_audioWare->isStop){break;}cout << "time out" << endl;pService->m_audioWare->isStop = TRUE;delete pService->m_audioWare;Sleep(1000);pService->m_audioWare = new CAudioWare();pService->startWare();}}

关于WareIn的使用教程,和容易疏忽产生死锁,以及外部切换正在使用中的输入设备导致的技术难关,就介绍到这里啦。

更多推荐

Windows下录音功能WareIn实现(含死锁破解、检测外部输入设备更改和重启录音服务)

本文发布于:2024-03-10 16:44:43,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1728500.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:死锁   重启   录音功能   设备   Windows

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!