waveInAddBuffer死锁的大雷办理

[复制链接]
发表于 2026-2-11 00:51:23 | 显示全部楼层 |阅读模式
项目场景:

从来没有一个bug让我这么抓狂,足足查了3天3夜,官方文档翻了一遍说的根本无用。具体项目就是使用waveIn系列函数获取windows体系麦克风数据,固然windows上有好几种方法获取麦克风数据,我终极还是选择了它。

标题形貌

我用异步回调函数方法来获取数据,固然还可以接纳直接方法来获取数据,这里就不多说了,可以看下官方文档。回调部门雷同下面如许:
  1. void CALLBACK waveInProc(HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
  2.     if (uMsg == WIM_DATA) {
  3.         if (pcm_mutex.try_lock()) {
  4. #ifndef NDEBUG
  5.             std::cout << "producer acquired" << std::endl;
  6. #endif
  7.             auto pwh = (LPWAVEHDR) dwParam1;
  8.             if (pwh->lpData && pwh->dwBytesRecorded > 0) {
  9.                 pcm_str.assign(pwh->lpData, pwh->dwBytesRecorded);
  10.                 waveInAddBuffer(hwi, pwh, sizeof(WAVEHDR));
  11.             } else {
  12.                 std::cerr << "wave data invalid" << std::endl;
  13.             }
  14. #ifndef NDEBUG
  15.             std::cout << "producer released" << std::endl;
  16. #endif
  17.             pcm_mutex.unlock();
  18.             conn.notify_one();
  19.             this_thread::sleep_for(std::chrono::microseconds{1});
  20.         }
  21.     } else if (uMsg == WIM_CLOSE) {
  22.         std::cout << "wave close" << std::endl;
  23.     } else if (uMsg == WIM_OPEN) {
  24.         std::cout << "wave open" << std::endl;
  25.     } else {
  26.         std::cerr << "unknown option" << std::endl;
  27.     }
  28. }
复制代码
真正的标题来了,正常使用肯定没标题,但是偏偏我的标题别人不肯定碰到,我须要切换麦克风,也就是说我有一种需求电脑上同时连着几个麦克风,我须要根据场景切换到差异的麦克风上去。
不要猜疑我为什么会有多个麦克风,客户要求的,注意:好戏要登场了!
我获取装备的方法和别人一样,就像下面的代码
  1. auto rc = waveInOpen(&hWaveIn, WAVE_MAPPER, &wfx, (DWORD_PTR) waveInProc, 0, CALLBACK_FUNCTION);
复制代码
这是官方接口的写法,这么写在绝大多数场景下都是没标题标。标题出在什么地方呢?就是这个参数:WAVE_MAPPER,先看看官方的表明:
  1. MMRESULT waveInOpen(
  2.   LPHWAVEIN       phwi,
  3.   UINT            uDeviceID,
  4.   LPCWAVEFORMATEX pwfx,
  5.   DWORD_PTR       dwCallback,
  6.   DWORD_PTR       dwInstance,
  7.   DWORD           fdwOpen
  8. );
复制代码
uDeviceID
要打开的波形音频输入装备的标识符。 它可以是装备标识符,也可以是开放波形音频输入装备的句柄。 可以使用以下标志而不是装备标识符。
WAVE_MAPPER函数选择可以或许以指定格式录制的波形音频输入装备。
以是如果你使用了WAVE_MAPPER这个值,当你正在获取声音回调的时间,你忽然切换麦克风(或取消麦克风权限),我们的主角来了waveInAddBuffer就会很大概率进入死锁状态(不是一定),是不是感觉很诡异。这跟很多其他网友说的waveInReset进入死锁状态是一个性子,这曾经让我一度以为WAVE_MAPPER这个值是有bug的。

缘故起因分析:

只能说不是全部人都面对我这种场景,如果你是单麦克风按照我那种写法我是没有碰到bug。
简单分析下,还是要从谁人回调函数动手,起首你调用waveInOpen函数才会触发回调函数里的 WIM_OPEN变乱,同样你调用waveInClose才会触发回调函数里的WIM_CLOSE,条件是这两个函数必须实行乐成才行,他们俩是有返回值的。
然后,其他的环境就是有数据上来的时间会触发WIM_DATA变乱,标题就出在这里,当你不绝吸收WIM_DATA变乱的时间忽然切换麦克风(或取消麦克风权限,Windows10和Windows11有麦克风权限设置,Windows7似乎没有),没有触发WIM_CLOSE变乱,由于你确实没手动调waveInClose函数,末了一个Buffer发来的时间我无法判定当前的麦克风状态,waveInAddBuffer函数将有概率进入假死状态。
我分析,如果我收到了数据阐明下面的锁已经排除了,这就跟生产者和消耗者的模子是一样的,那么为什么会报错呢,缘故起因很大概是Handle的标题,就是说持有音频装备的句柄进入了不确定状态,有点像你正在往硬盘里写东西忽然硬盘被人拔掉一样,我乃至猜疑是底层的bug,究竟Windows11的状态各人都相识。
我就不说谎话了,偶尔间我会向巨硬扣问下的,我固然没有100%确定标题,但是肯定和这个有关系。神奇的是我想到相识决的方法,大概说规避的方案,请看办理方案。

办理方案:

还是要着眼于WAVE_MAPPER这个参数自己,我们不担当它的发起,我们传入自己的值。每个麦克风装备都有自己的ID和Name,可以通过下面的函数获取:
  1.     UINT numDevs = waveInGetNumDevs();
  2.     WAVEINCAPS wic;
  3.     std::cout << "Number of input devices: " << numDevs << std::endl;
  4.     for (UINT i = 0; i < numDevs; ++i) {
  5.         if (waveInGetDevCaps(i, &wic, sizeof(WAVEINCAPS)) == MMSYSERR_NOERROR) {
  6.             std::wcout << L"Device ID: " << i << std::endl;
  7.             std::wcout << L"Device Name: " << wic.szPname << std::endl;
  8.         }
  9.     }
复制代码
然后你根据看下自己电脑上大概有多少个装备,光这点还不敷,我观察0是默认装备,请看下图:

你勾选了谁,谁的装备ID就酿成了0,这就好办了,我只要手动选择想用的麦克风就可以了。然后我用下面的函数永世指定0为我要用的装备:
  1.         auto rc = waveInOpen(&hWaveIn, 0, &wfx, (DWORD_PTR) waveInProc, 0, CALLBACK_FUNCTION);
  2.         if (rc) {
  3.             std::cerr << "waveInOpen failed: " << rc << std::endl;
  4.             goto NONE;
  5.         }
复制代码
注意:当你勾选默认麦克风时间,重启电脑也不会重置,条件是这个麦克风必须不绝处于可用状态,你不能把它拔掉或禁用。别的,除了0以外其他的装备排序是不固定的,不能想固然的以为是UI上的排序!
这个标题办理了就好办了,我可以在吸收线程设置超时就行了,比如3秒或5秒没有收到数据大概率是麦克风改变了或挂掉了,也有大概是硬件标题。正常取一个buffer也就是最多几十毫秒(和硬件性能有关系),以是3-5秒已经很长了,我测试下来是没有标题标。借助condition_veriable代码可以如许写:
  1.                 std::unique_lock<std::mutex> lck(pcm_mutex);
  2.                 auto status = conn.wait_for(lck, std::chrono::milliseconds{Config::recv_data_timeout},
  3.                                             []() { return !pcm_str.empty(); });//防止伪唤醒
  4.                 if(status){
  5.                         //正常流程
  6.                                 }else{
  7.                                         //异常处理
  8.                                 }
复制代码
我测试下来conn.wait_for的耗时Debug在20ms左右,Release在5-7ms左右,对时间要求高的童鞋可以再优化下。
另有一种方法轻微难一点,我没接纳,我可以说下思绪,感爱好的同砚可以实验下,具体思绪就是通过监控监控麦克风状态来决定利用,比如麦克风插入、麦克风移除、麦克风改变等等。下面贴出示例代码:
  1. #include <windows.h>
  2. #include <iostream>
  3. #include <mmdeviceapi.h>
  4. #include <audiopolicy.h>
  5. #include <atlbase.h>
  6. #pragma comment(lib, "ole32.lib")
  7. #pragma comment(lib, "avrt.lib")
  8. class DeviceNotificationCallback : public IMMNotificationClient {
  9. public:
  10.     // Implement required methods
  11.     STDMETHODIMP OnDeviceStateChanged(LPCWSTR deviceId, DWORD newState) override {
  12.         std::wcout << L"Device state changed: " << deviceId << std::endl;
  13.         return S_OK;
  14.     }
  15.     STDMETHODIMP OnDeviceAdded(LPCWSTR deviceId) override {
  16.         std::wcout << L"Device added: " << deviceId << std::endl;
  17.         return S_OK;
  18.     }
  19.     STDMETHODIMP OnDeviceRemoved(LPCWSTR deviceId) override {
  20.         std::wcout << L"Device removed: " << deviceId << std::endl;
  21.         return S_OK;
  22.     }
  23.     STDMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId) override {
  24.         std::wcout << L"Default device changed." << pwstrDefaultDeviceId << std::endl;
  25.         return S_OK;
  26.     }
  27.     STDMETHODIMP OnPropertyValueChanged(LPCWSTR deviceId, const PROPERTYKEY key) override {
  28.         std::wcout << L"Property value changed: " << deviceId << std::endl;
  29.         return S_OK;
  30.     }
  31.     // Unused methods
  32.     STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject) override {
  33.         if (riid == __uuidof(IUnknown) || riid == __uuidof(IMMNotificationClient)) {
  34.             *ppvObject = static_cast<IMMNotificationClient *>(this);
  35.             AddRef();
  36.             return S_OK;
  37.         }
  38.         return E_NOINTERFACE;
  39.     }
  40.     STDMETHODIMP_(ULONG) AddRef() override {
  41.         return InterlockedIncrement(&m_refCount);
  42.     }
  43.     STDMETHODIMP_(ULONG) Release() override {
  44.         ULONG refCount = InterlockedDecrement(&m_refCount);
  45.         if (refCount == 0) {
  46.             delete this;
  47.         }
  48.         return refCount;
  49.     }
  50. private:
  51.     LONG m_refCount = 1;
  52. };
  53. int main() {
  54.     CoInitialize(nullptr);
  55.     CComPtr<IMMDeviceEnumerator> pEnumerator;
  56.     CComPtr<DeviceNotificationCallback> pCallback;
  57.     HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER,
  58.                                   IID_PPV_ARGS(&pEnumerator));
  59.     if (FAILED(hr)) {
  60.         std::cerr << "Failed to create device enumerator. Error code: " << hr << std::endl;
  61.         return -1;
  62.     }
  63.     pCallback = new DeviceNotificationCallback();
  64.     hr = pEnumerator->RegisterEndpointNotificationCallback(pCallback);
  65.     if (FAILED(hr)) {
  66.         std::cerr << "Failed to register endpoint notification callback. Error code: " << hr << std::endl;
  67.         return -1;
  68.     }
  69.     std::cout << "Monitoring device changes. Press Enter to exit." << std::endl;
  70.     std::cin.get();
  71.     // Clean up
  72.     pEnumerator->UnregisterEndpointNotificationCallback(pCallback);
  73.     CoUninitialize();
  74.     return 0;
  75. }
复制代码
每个方法名对应一个变乱,你们自行研讨下吧,我用规避的方法就行了。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!qidao123.com:ToB企服之家,中国第一个企服评测及软件市场,开放入驻,技术点评得现金

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表