项目场景:
从来没有一个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的耗时Debug在20ms左右,Release在5-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;
- }
复制代码 每个方法名对应一个变乱,你们自行研讨下吧,我用规避的方法就行了。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!qidao123.com:ToB企服之家,中国第一个企服评测及软件市场,开放入驻,技术点评得现金 |