waveInAddBuffer死锁的大雷解决

news2024/9/25 8:22:57

项目场景:

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


问题描述

我用异步回调函数方法来获取数据,当然还可以采用直接方法来获取数据,这里就不多说了,可以看下官方文档。回调部分类似下面这样:

void CALLBACK waveInProc(HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
    if (uMsg == WIM_DATA) {
        if (pcm_mutex.try_lock()) {
#ifndef NDEBUG
            std::cout << "producer acquired" << std::endl;
#endif
            auto pwh = (LPWAVEHDR) dwParam1;
            if (pwh->lpData && pwh->dwBytesRecorded > 0) {
                pcm_str.assign(pwh->lpData, pwh->dwBytesRecorded);
                waveInAddBuffer(hwi, pwh, sizeof(WAVEHDR));
            } else {
                std::cerr << "wave data invalid" << std::endl;
            }
#ifndef NDEBUG
            std::cout << "producer released" << std::endl;
#endif
            pcm_mutex.unlock();
            conn.notify_one();
            this_thread::sleep_for(std::chrono::microseconds{1});
        }
    } else if (uMsg == WIM_CLOSE) {
        std::cout << "wave close" << std::endl;
    } else if (uMsg == WIM_OPEN) {
        std::cout << "wave open" << std::endl;
    } else {
        std::cerr << "unknown option" << std::endl;
    }
}

真正的问题来了,正常使用肯定没问题,但是偏偏我的问题别人不一定遇到,我需要切换麦克风,也就是说我有一种需求电脑上同时连着几个麦克风,我需要根据场景切换到不同的麦克风上去。

不要怀疑我为什么会有多个麦克风,客户要求的,注意:好戏要登场了!

我获取设备的方法和别人一样,就像下面的代码:

auto rc = waveInOpen(&hWaveIn, WAVE_MAPPER, &wfx, (DWORD_PTR) waveInProc, 0, CALLBACK_FUNCTION);

这是官方接口的写法,这么写在绝大多数场景下都是没问题的。问题出在什么地方呢?就是这个参数:WAVE_MAPPER,先看看官方的解释:

MMRESULT waveInOpen(
  LPHWAVEIN       phwi,
  UINT            uDeviceID,
  LPCWAVEFORMATEX pwfx,
  DWORD_PTR       dwCallback,
  DWORD_PTR       dwInstance,
  DWORD           fdwOpen
);

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,可以通过下面的函数获取:

    UINT numDevs = waveInGetNumDevs();
    WAVEINCAPS wic;
    std::cout << "Number of input devices: " << numDevs << std::endl;
    for (UINT i = 0; i < numDevs; ++i) {
        if (waveInGetDevCaps(i, &wic, sizeof(WAVEINCAPS)) == MMSYSERR_NOERROR) {
            std::wcout << L"Device ID: " << i << std::endl;
            std::wcout << L"Device Name: " << wic.szPname << std::endl;
        }
    }

然后你根据看下自己电脑上大概有多少个设备,光这点还不够,我观察0默认设备,请看下图:

在这里插入图片描述
你勾选了谁,谁的设备ID就变成了0,这就好办了,我只要手动选择想用的麦克风就可以了。然后我用下面的函数永久指定0为我要用的设备:

        auto rc = waveInOpen(&hWaveIn, 0, &wfx, (DWORD_PTR) waveInProc, 0, CALLBACK_FUNCTION);
        if (rc) {
            std::cerr << "waveInOpen failed: " << rc << std::endl;
            goto NONE;
        }

注意:当你勾选默认麦克风时候,重启电脑也不会重置,前提是这个麦克风必须一直处于可用状态,你不能把它拔掉或禁用。另外,除了0以外其他的设备排序是不固定的,不能想当然的认为是UI上的排序!

这个问题解决了就好办了,我可以在接收线程设置超时就行了,比如3秒或5秒没有收到数据大概率是麦克风改变了或挂掉了,也有可能是硬件问题。正常取一个buffer也就是最多几十毫秒(和硬件性能有关系),所以3-5秒已经很长了,我测试下来是没有问题的。借助condition_veriable代码可以这样写:

                std::unique_lock<std::mutex> lck(pcm_mutex);
                auto status = conn.wait_for(lck, std::chrono::milliseconds{Config::recv_data_timeout},
                                            []() { return !pcm_str.empty(); });
                if(status){
                	//正常流程
				}else{
					//异常处理
				}

我测试下来conn.wait_for的耗时Debug20ms左右,Release5-7ms左右,对时间要求高的童鞋可以再优化下。

还有一种方法稍微难一点,我没采用,我可以说下思路,感兴趣的同学可以尝试下,具体思路就是通过监控麦克风状态来决定操作,比如麦克风插入麦克风移除麦克风改变等等。下面贴出示例代码:

#include <windows.h>
#include <iostream>
#include <mmdeviceapi.h>
#include <audiopolicy.h>
#include <atlbase.h>

#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "avrt.lib")

class DeviceNotificationCallback : public IMMNotificationClient {
public:
    // Implement required methods
    STDMETHODIMP OnDeviceStateChanged(LPCWSTR deviceId, DWORD newState) override {
        std::wcout << L"Device state changed: " << deviceId << std::endl;
        return S_OK;
    }

    STDMETHODIMP OnDeviceAdded(LPCWSTR deviceId) override {
        std::wcout << L"Device added: " << deviceId << std::endl;
        return S_OK;
    }

    STDMETHODIMP OnDeviceRemoved(LPCWSTR deviceId) override {
        std::wcout << L"Device removed: " << deviceId << std::endl;
        return S_OK;
    }

    STDMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId) override {
        std::wcout << L"Default device changed." << pwstrDefaultDeviceId << std::endl;
        return S_OK;
    }

    STDMETHODIMP OnPropertyValueChanged(LPCWSTR deviceId, const PROPERTYKEY key) override {
        std::wcout << L"Property value changed: " << deviceId << std::endl;
        return S_OK;
    }

    // Unused methods
    STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject) override {
        if (riid == __uuidof(IUnknown) || riid == __uuidof(IMMNotificationClient)) {
            *ppvObject = static_cast<IMMNotificationClient *>(this);
            AddRef();
            return S_OK;
        }
        return E_NOINTERFACE;
    }

    STDMETHODIMP_(ULONG) AddRef() override {
        return InterlockedIncrement(&m_refCount);
    }

    STDMETHODIMP_(ULONG) Release() override {
        ULONG refCount = InterlockedDecrement(&m_refCount);
        if (refCount == 0) {
            delete this;
        }
        return refCount;
    }

private:
    LONG m_refCount = 1;
};

int main() {
    CoInitialize(nullptr);

    CComPtr<IMMDeviceEnumerator> pEnumerator;
    CComPtr<DeviceNotificationCallback> pCallback;

    HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER,
                                  IID_PPV_ARGS(&pEnumerator));
    if (FAILED(hr)) {
        std::cerr << "Failed to create device enumerator. Error code: " << hr << std::endl;
        return -1;
    }

    pCallback = new DeviceNotificationCallback();

    hr = pEnumerator->RegisterEndpointNotificationCallback(pCallback);
    if (FAILED(hr)) {
        std::cerr << "Failed to register endpoint notification callback. Error code: " << hr << std::endl;
        return -1;
    }

    std::cout << "Monitoring device changes. Press Enter to exit." << std::endl;
    std::cin.get();

    // Clean up
    pEnumerator->UnregisterEndpointNotificationCallback(pCallback);

    CoUninitialize();
    return 0;
}

每个方法名对应一个事件,你们自行钻研下吧,我用规避的方法就行了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1996039.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

导函数相对于原函数特有的性质

引入 f(x) 在区间[a,b]上存在&#xff0c;函数值之间是无牵无挂的&#xff0c;连续存在还是间断存在都不一定。f(x) 在区间[a,b]上连续&#xff0c;函数值之间距离是一个无穷小量。f(x) 在区间[a,b]上可导&#xff0c;函数值之间距离是一个比连续跟小的无穷小量&#xff0c;函…

2024剪辑神器盘点:四大热门剪辑软件推荐!

亲爱的朋友们&#xff0c;想要制作出精彩短视频&#xff0c;却苦于找不到合适的剪辑工具&#xff1f;别担心&#xff0c;今天要向大家推荐几款剪辑软件&#xff0c;它们能帮助大家更好地完成视频创作&#xff01; 福昕视频剪辑 链接&#xff1a;www.pdf365.cn/foxit-clip/ 对…

基于VEH的无痕HOOK

这里的无痕HOOK指的是不破坏程序机器码,这样就可以绕过CRC或MD5的校验。 VEH利用了Windows的调试机制和异常处理,人为抛出异常,从异常的上下文中获取寄存器信息。 DLL入口 // dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "pch.h" #include "CHoo…

PI-GNN论文翻译.md

PI-GNN论文翻译 Continual Learning on Dynamic Graphs via Parameter Isolation 基于参数隔离的动态图的持续学习 ABSTRACT 许多现实世界的图学习任务需要处理出现新节点和边的动态图。动态图学习方法通常会遇到灾难性遗忘问题&#xff0c;即先前图学到的知识会被新图的更…

VueTreeselect自定义label

插槽 使用插槽 //node.raw&#xff1a;所有传入的数据项<treeselectv-model"areaCode":options"deptOptions":normalizer"normalizer"><div slot"value-label" slot-scope"{ node }">{{ node.raw.title }}<…

CommonJs模块化简明笔记

1、什么是模块化 模块化是指将一个复杂的程序依据一定的规则&#xff08;规范&#xff09;封装成几个块&#xff08;文件&#xff09;&#xff0c;并进行组合在一起。 最早我们开发将所有的代码写在一个js文件中&#xff0c;随着需求越来越复杂&#xff0c;代码量越来越大&am…

硬盘文件数据销毁|文件销毁|硬盘销毁|数据销毁|中国成就的伟大与数据安全在新时代国家安全中的关键作用

在当今世界&#xff0c;中国的发展成就举世瞩目&#xff0c;但令人惊讶的是&#xff0c;大多数人可能并未充分意识到其伟大之处。与此同时&#xff0c;数据安全作为国家安全的重要组成部分&#xff0c;其重要性在新时代愈发凸显。 二、中国取得的伟大成就 中国在经济领域的崛起…

iOS ------ 事件响应链

响应者链 响应者链是由一系列链接在一起的响应者&#xff08;UIResponser之类&#xff1a;UIApplication&#xff0c;UIViewController&#xff0c;UIView&#xff09;注组成的。一般情况下&#xff0c;一条响应链开始于第一响应者&#xff0c;结束于application对象。如果一个…

Ashok:一款多功能开源网络侦查OSINT工具

关于Ashok Ashok是一款多功能开源网络侦查公开资源情报OSINT工具&#xff0c;该工具可谓是OSINT领域中的瑞士军刀&#xff0c;广大研究人员可以使用该工具轻松完成网络侦查任务。 侦察是渗透测试的第一阶段&#xff0c;这意味着在计划任何实际攻击之前收集信息。因此&#xff…

【Linux】输入输出重定向

目录 一、概念 二、重定向的本质 三、系统调用接口dup和dup2 3.1 dup 3.2 dup2 一、概念 在之前对Linux的学习中&#xff0c;我们有接触过一系列的重定向命令&#xff0c;例如 >、>>等 它们可以将一个命令的输出或输入重定向到其他地方&#xff0c;如echo命令…

Java泛型的理解

前言 泛型是Java中一个比较重要的特性&#xff0c;是于JDK5引入新特性&#xff0c;其主要目的是为了提供编译时的类型安全检测机制和简化代码。本文主要探讨一下泛型的使用。 假如说没有泛型 假如说没有泛型&#xff0c;可以举一个例子&#xff1a; ArrayList list new Ar…

怎么把pdf转换成jpg图片免费?分享11款超实用的PDF转图片工具,工作效率直接拉满!

怎么把pdf转换成jpg图片&#xff1f;将PDF转换成图片&#xff0c;有多种方法可供选择&#xff0c;包括使用专业的PDF转换软件、在线转换工具&#xff0c;甚至电脑上有自带的转换方法。 pdf转图片的优势有很多&#xff0c;其中包括以下几点&#xff1a; 1、兼容性和可访性 转换…

Linux的platform设备驱动框架

platform其实就是linux中用来匹配设备和驱动的一种虚拟总线技术。之所以有这么个技术&#xff0c;是为了将驱动和设备分开来&#xff0c;设备负责描述设备信息&#xff0c;驱动负责实现功能逻辑。是一种分层思想下的产物。platform并不是局限于某一类设备&#xff0c;而是整个框…

鸿蒙(API 12 Beta3版)【视频解码】 音视频编码

开发者可以调用本模块的Native API接口&#xff0c;完成视频解码&#xff0c;即将媒体数据解码成YUV文件或送显。 当前支持的解码能力如下&#xff1a; 视频硬解类型视频软解类型AVC(H.264)、HEVC(H.265)AVC(H.264) 视频解码软/硬件解码存在差异&#xff0c;基于MimeType创建…

打造高品质短视频,四款好用的剪辑软件推荐!

作为一个热爱记录生活点滴的Vlogger&#xff0c;今天我想聊聊那些让我的视频剪辑之路变得轻松又有趣的剪辑软件。 福昕视频剪辑 链接&#xff1a;www.pdf365.cn/foxit-clip/ 首先&#xff0c;我要说的是福昕视频剪辑。它的界面设计非常人性化&#xff0c;让我可以一目了然地…

绝望者的希望——《这几年》

屈原在《离骚》里面说道&#xff1a;“路漫漫其修远兮&#xff0c;吾将上下而求索。”这句诗最有分量的两个字就是“求索”。许国忠的人生就是在用行动践行着这两个字。书作《这几年》正是他求索的轨迹。 他求索人间真情味。在书作里&#xff0c;我们大抵看到许国忠柔情的笔锋…

爬虫入门--了解相关工具

目录 1.爬虫与python 2.第一个爬虫 3.web请求的全过程 3.1服务器渲染 3.2前端JS渲染 4.浏览器工具 4.1Elements 4.2Console 4.3Source 4.4network&#xff08;重点&#xff09; 5.小结 1.爬虫与python 首先我们要知道&#xff0c;爬虫一定要用Python么? 非也~…

运算放大器电气测试中的典型参数

为了评估与空间应用相关的某些应力源的影响&#xff0c;在运算放大器的电气测试过程中会测量一组电气参数。以下是运算放大器中常用的典型参数。 尽管理想情况下&#xff0c;运放在输入端没有电流&#xff0c;输入和输出没有失调&#xff0c;增益无限&#xff0c;速度无限&…

环境配置:如何在IntelliJ IDEA中安装和修改JDK版本配置(以Windows为例)

环境配置&#xff1a;如何在IntelliJ IDEA中安装和修改JDK版本配置&#xff08;以Windows为例&#xff09; 为了在Java开发中使用最新的功能和优化&#xff0c;升级和配置JDK版本是必不可少的。本文将详细介绍如何下载、安装、配置最新的JDK版本&#xff0c;并在IntelliJ IDEA…

04_Electron 模块介绍

Electron 模块介绍 一、Electron 主进程和渲染进程中的模块&#xff08;介绍&#xff09;二、Electron remote 模块三、Electron 渲染进程中通过 remote 模块调用主进程中的 BrowserWindow 打开新窗口1、安装 electron/remote2、主进程中配置启用 remote 模块3、渲染进程引入 r…