waveInAddBuffer死锁的大雷解决

项目场景:

从来没有一个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 NDEBUGstd::cout << "producer acquired" << std::endl;
#endifauto 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 NDEBUGstd::cout << "producer released" << std::endl;
#endifpcm_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 methodsSTDMETHODIMP 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 methodsSTDMETHODIMP 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 uppEnumerator->UnregisterEndpointNotificationCallback(pCallback);CoUninitialize();return 0;
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Springboot整合hutool验证码

在 Spring Boot 中&#xff0c;你可以将 Hutool 生成验证码的功能集成到 RESTful API 接口中。 依赖 <dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.14</version> <!-- 使用最新版…

pytorch,用lenet5识别cifar10数据集(训练+测试+单张图片识别)

目录 LeNet-5 LeNet-5 结构 CIFAR-10 pytorch实现 lenet模型 训练模型 1.导入数据 2.训练模型 3.测试模型 测试单张图片 代码 运行结果 LeNet-5 LeNet-5 是由 Yann LeCun 等人在 1998 年提出的一种经典卷积神经网络&#xff08;CNN&#xff09;模型&#xff0c;主要…

Solana真假繁荣调查:机器人横行占7成交易,Meme数据下滑严重

随着Solana链上数据全面赶超以太坊&#xff0c;关于Solana将超越以太坊的讨论逐渐升温。然而&#xff0c;与此同时&#xff0c;关于Solana真实繁荣度的争议也引发了广泛关注。本文将深入探讨Solana生态中机器人泛滥、交易量数据虚高、MEV问题、财务亏损问题&#xff0c;以及SOL…

从零搭建xxl-job(四):xxljob进行一些性能优化

之前的代码这部分并没有补充完毕&#xff0c;假如调度中心如果判断有定时任务要执行了&#xff0c;该怎么远程通知给执行定时任务的程序呢&#xff1f;当定时任务要把自己的信息发送给调度中心时&#xff0c;是通过一个RegistryParam对象发送的。该对象内部封装了定时任务相关的…

【C#】explicit、implicit与operator

字面解释 explicit&#xff1a;清楚明白的;易于理解的;(说话)清晰的&#xff0c;明确的;直言的;坦率的;直截了当的;不隐晦的;不含糊的。 implicit&#xff1a;含蓄的;不直接言明的;成为一部分的;内含的;完全的;无疑问的。 operator&#xff1a;操作人员;技工;电话员;接线员;…

HarmonyOS应用开发者高级认证(一)

1、依次点击A、B、C、D四个按钮&#xff0c;其中不会触发UI刷新的是&#xff1a; 答案&#xff1a; Button("C").onClick(() > {this.nameList[0].name "Jim"})分析&#xff1a;直接更新非一级数据不会触发UI刷新 2、如果要实现Row组件内的子元素均匀…

基于JSP的个性化影片推荐系统

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a;JSP 数据库&#xff1a;MySQL 技术&#xff1a;JSP技术 工具&#xff1a;MyEclipse、Tomcat、MySQL 系统展示 首页 管理员功能模块 用户功能模块 …

Rancher的RKE和RKE2部署K8s集群kube-proxy开启strictARP

kube-proxy配置strictARPtrue 1、非RKE部署的K8s集群&#xff1a;配置首先&#xff0c;需要为kube-proxy启动strictARP&#xff0c;以便Kubernetes集群中的所有网卡停止响应其他网卡的ARP请求&#xff0c;而由OpenELB来处理ARP请求。 $ kubectl edit configmap kube-proxy -n…

C# 在Word中插入或删除分节符

在Word中&#xff0c;分节符是一种强大的工具&#xff0c;用于将文档分成不同的部分&#xff0c;每个部分可以有独立的页面设置&#xff0c;如页边距、纸张方向、页眉和页脚等。正确使用分节符可以极大地提升文档的组织性和专业性&#xff0c;特别是在长文档中&#xff0c;需要…

【STM32】USART通用同步/异步收发器(串口数据的接收与发送)

本篇博客重点在于标准库函数的理解与使用&#xff0c;搭建一个框架便于快速开发 目录 USART简介 USART时钟使能 USART初始化 串口参数 串口数据时序 USART中断配置 USART使能 数据的接收与发送 Serial.h Serial.c main.c USART简介 USART&#xff08;Universal S…

leedCode - - - 栈和队列

目录 1.有效的括号&#xff08; LeetCode 20 &#xff09; 2.最小栈&#xff08; LeetCode 155 &#xff09; 3.接雨水&#xff08; LeetCode 42 &#xff09; 4.逆波兰表达式求值&#xff08;LeetCode 150&#xff09; 5.柱状图中最大的矩形&#xff08;LeetCode 84&…

计算机毕业设计选题推荐-大学生就业招聘管理系统-Java/Python项目实战

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

【Redis】Redis 初探:特性、应用场景与高并发架构演进之路

目录 初识 Redis关于 Redis服务端高并发分布式结构演进之路概述常⻅概念基本概念应⽤&#xff08;Application&#xff09;/ 系统&#xff08;System&#xff09;模块&#xff08;Module&#xff09;/ 组件&#xff08;Component&#xff09;分布式&#xff08;Distributed&…

SSM养老院信息管理系统—计算机毕业设计源码16963

目 录 摘要 1 绪论 1.1研究意义 1.2开发意义 1.3ssm框架介绍 1.4论文结构与章节安排 2 养老院信息管理系统系统分析 2.1 可行性分析 2.1.1 技术可行性分析 2.1.2 经济可行性分析 2.1.3 法律可行性分析 2.2 系统功能分析 2.2.1 功能性分析 2.2.2 非功能性分析 2.…

C++ STL初阶(9):list 中关于reverse_iterator的实现

在完成vector和list的iterator相关部分的实践后来完成反向迭代器的实现 1. list的反向迭代器 书接上回&#xff0c;反向迭代器应当重新封装一个类。 反向迭代器和正向迭代器最大的区别就是&#xff0c;反向迭代器是倒着走的&#xff0c;所以最核心的逻辑就是将封装成-- 注意&am…

Cadence Allegro 入门教程笔记:如何绘制原理图和原理图库?

文章目录 一、用 Capture CIS 17.4 绘制原理图库 Cadence Allegro QQ交流学习裙&#xff1a;173416628 1、凡亿教育的Cadence Allegro 17.4基础教程 2、小哥Cadence Allegro 132讲 技巧视频 3、小哥Cadence Allegro 两层板 基础视频 4、小哥Cadence Allegro 四层板 提高视频…

【NLP】文本处理的基本方法【jieba分词、命名实体、词性标注】

文章目录 1、本章目标2、什么是分词3、jieba的使用3.1、精确模式分词3.2、全模式分词3.3、搜索引擎模式分词3.4、中文繁体分词3.5、使用用户自定义词典 4、什么是命名实体识别5、什么是词性标注6、小结7、jieba词性对照表⭐ &#x1f343;作者介绍&#xff1a;双非本科大三网络…

opencv-python图像增强三:图像清晰度增强

文章目录 一、简介&#xff1a;二、图像清晰度增强方案&#xff1a;三、算法实现步骤3.1高反差保留实现3.2. usm锐化3.3 Overlay叠加 四&#xff1a;整体代码实现五&#xff1a;效果 一、简介&#xff1a; 你是否有过这样的烦恼&#xff0c;拍出来的照片总是不够清晰&#xff…

【Linux】网络编程套接字Scoket:UDP网络编程

目录 一、了解UDP协议 二、了解端口和IP地址 三、套接字概述与Socket的概念 四、Socket的类型 五、 Socket的信息数据结构 六、网络字节序与主机字节序的互相转换 七、地址转换函数 八、UDP网络编程流程及相关函数 socket函数 bind函数 recvfrom函数 sendto函数 …

UIAbility组件基础(一)

一、概述 UIAbility组件是一种包含UI的应用组件&#xff0c;主要用于和用户交互。UIAbility组件是系统调度的基本单元&#xff0c;为应用提供绘制界面的窗口。一个应用可以包含一个或多个UIAbility组件。每一个UIAbility组件实例都会在最近任务列表中显示一个对应的任务。 U…