From 2cd6e995cb39a1c157aed30f95ddbac02ea1a981 Mon Sep 17 00:00:00 2001 From: Matthew Fagan Date: Fri, 20 Mar 2026 22:39:13 +1100 Subject: [PATCH 1/4] Rework WASAPNINotifications to check if added devices are configured or default devices --- src/mumble/WASAPI.cpp | 956 ++++++++++++++---------- src/mumble/WASAPI.h | 28 + src/mumble/WASAPINotificationClient.cpp | 77 +- src/mumble/WASAPINotificationClient.h | 23 +- 4 files changed, 646 insertions(+), 438 deletions(-) diff --git a/src/mumble/WASAPI.cpp b/src/mumble/WASAPI.cpp index 4059b4c645f..a30917b7a1f 100644 --- a/src/mumble/WASAPI.cpp +++ b/src/mumble/WASAPI.cpp @@ -331,14 +331,75 @@ const QHash< QString, QString > WASAPISystem::getDevices(EDataFlow dataflow) { return devices; } -WASAPIInput::WASAPIInput(){}; +void WASAPIDevice::ClearUsage() { + if (wanted) { + WASAPINotificationClient::get().unlistDeviceAsWanted(devName); + wanted = false; + } + if (used) { + WASAPINotificationClient::get().unlistDeviceAsUsed(devName); + used = false; + } + if (usedDefault) { + WASAPINotificationClient::get().unlistDefaultDeviceAsUsed(defaultDevName); + usedDefault = false; + } + if (wantDefault) { + WASAPINotificationClient::get().decrementWantDefault(); + wantDefault = false; + } +} -WASAPIInput::~WASAPIInput() { - bRunning = false; - wait(); +void WASAPIDevice::ClearDevice() { + if (pDevice) { + pDevice->Release(); + pDevice = nullptr; + } +} + +void WASAPIDevice::Move(WASAPIDevice&& other) { + devName = std::move(other.devName); + defaultDevName = std::move(other.defaultDevName); + used = other.used; + other.used = false; + wanted = other.wanted; + other.wanted = false; + usedDefault = other.usedDefault; + other.usedDefault = false; + wantDefault = other.wantDefault; + other.wantDefault = false; + pDevice = other.pDevice; + other.pDevice = nullptr; +} + +WASAPIDevice::WASAPIDevice() {} +WASAPIDevice::WASAPIDevice(nullptr_t) {} +WASAPIDevice::WASAPIDevice(WASAPIDevice&& other) { + Move(std::move(other)); +} +WASAPIDevice::~WASAPIDevice() { + ClearDevice(); +} +WASAPIDevice& WASAPIDevice::operator=(WASAPIDevice&& other) { + ClearDevice(); + ClearUsage(); + Move(std::move(other)); } -static IMMDevice *openNamedOrDefaultDevice(const QString &name, EDataFlow dataFlow, ERole role) { +WASAPIDevice::operator bool() const { + return (pDevice != nullptr); +} +IMMDevice* WASAPIDevice::operator->() const { + return pDevice; +} +WASAPIDevice::operator IMMDevice*() const { + return pDevice; +} + +void WASAPIDevice::OpenNamedOrDefaultDevice(const QString &name, EDataFlow dataFlow, ERole role) { + ClearDevice(); + ClearUsage(); + HRESULT hr; IMMDeviceEnumerator *pEnumerator = nullptr; @@ -346,22 +407,21 @@ static IMMDevice *openNamedOrDefaultDevice(const QString &name, EDataFlow dataFl reinterpret_cast< void ** >(&pEnumerator)); if (!pEnumerator || FAILED(hr)) { qWarning("WASAPI: Failed to instantiate enumerator: hr=0x%08lx", hr); - return nullptr; + return; } - IMMDevice *pDevice = nullptr; // Try to find a device pointer for |name|. if (!name.isEmpty()) { - std::vector< wchar_t > devname; - devname.resize(name.length() + 1); - int len = name.toWCharArray(devname.data()); - devname[len] = 0; - hr = pEnumerator->GetDevice(devname.data(), &pDevice); + devName = name; + hr = pEnumerator->GetDevice(reinterpret_cast(devName.utf16()), &pDevice); if (FAILED(hr)) { qWarning("WASAPI: Failed to open selected device %s %ls (df=%d, e=%d, hr=0x%08lx), falling back to default", - qPrintable(name), devname.data(), dataFlow, role, hr); + qPrintable(name), devName.utf16(), dataFlow, role, hr); + WASAPINotificationClient::get().enlistDeviceAsWanted(devName); + wanted = true; } else { - WASAPINotificationClient::get().enlistDeviceAsUsed(devname.data()); + WASAPINotificationClient::get().enlistDeviceAsUsed(devName); + used = true; } } @@ -375,35 +435,50 @@ static IMMDevice *openNamedOrDefaultDevice(const QString &name, EDataFlow dataFl qWarning("WASAPI: Failed to open device: df=%d, e=%d, hr=0x%08lx", dataFlow, role, hr); goto cleanup; } - wchar_t *devname = nullptr; - hr = pDevice->GetId(&devname); + wchar_t *defdevname = nullptr; + hr = pDevice->GetId(&defdevname); if (FAILED(hr)) { qWarning("WASAPI: Failed to query device: df=%d, e=%d, hr=0x%08lx", dataFlow, role, hr); goto cleanup; } pDevice->Release(); - hr = pEnumerator->GetDevice(devname, &pDevice); + pDevice = nullptr; + + defaultDevName = QString::fromWCharArray(defdevname); + + hr = pEnumerator->GetDevice(defdevname, &pDevice); if (FAILED(hr)) { qWarning("WASAPI: Failed to reopen default device: df=%d, e=%d, hr=0x%08lx", dataFlow, role, hr); - goto cleanup; + wantDefault = true; + WASAPINotificationClient::get().incrementWantDefault(); + } else { + usedDefault = true; + WASAPINotificationClient::get().enlistDefaultDeviceAsUsed(defaultDevName); } - WASAPINotificationClient::get().enlistDefaultDeviceAsUsed(devname); - CoTaskMemFree(devname); + CoTaskMemFree(defdevname); } cleanup: if (pEnumerator) pEnumerator->Release(); +} - return pDevice; + + + +WASAPIInput::WASAPIInput(){}; + +WASAPIInput::~WASAPIInput() { + bRunning = false; + wait(); } void WASAPIInput::run() { HRESULT hr; - IMMDevice *pMicDevice = nullptr; + WASAPIDevice pMicDevice; IAudioClient *pMicAudioClient = nullptr; IAudioCaptureClient *pMicCaptureClient = nullptr; - IMMDevice *pEchoDevice = nullptr; + WASAPIDevice pEchoDevice; IAudioClient *pEchoAudioClient = nullptr; IAudioCaptureClient *pEchoCaptureClient = nullptr; WAVEFORMATEX *micpwfx = nullptr, *echopwfx = nullptr; @@ -428,283 +503,315 @@ void WASAPIInput::run() { CoInitialize(nullptr); - hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); - hMmThread = AvSetMmThreadCharacteristics(L"Pro Audio", &dwTaskIndex); if (!hMmThread) { qWarning("WASAPIInput: Failed to set Pro Audio thread priority"); } - // Open mic device. - pMicDevice = openNamedOrDefaultDevice(Global::get().s.qsWASAPIInput, eCapture, WASAPIRoleFromSettings()); - if (!pMicDevice) - goto cleanup; - - // Open echo capture device. - if (doecho) { - pEchoDevice = openNamedOrDefaultDevice(Global::get().s.qsWASAPIOutput, eRender, WASAPIRoleFromSettings()); - if (!pEchoDevice) - doecho = false; - } - - hr = pMicDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, (void **) &pMicAudioClient); - if (FAILED(hr)) { - qWarning("WASAPIInput: Activate Mic AudioClient failed: hr=0x%08lx", hr); - goto cleanup; - } - - def = min = latency = 0; - - pMicAudioClient->GetDevicePeriod(&def, &min); - - want = qMax< REFERENCE_TIME >(min, 100000); - qWarning("WASAPIInput: Latencies %lld %lld => %lld", def, min, want); + // Outer retry loop - For handling AUDCLNT_E_DEVICE_INVALIDATED which requires complete cleanup and then re-creation of audio endpoint. + bool doOuterRetry = true; + while (doOuterRetry) { + hr = 0; - if (Global::get().s.bExclusiveInput && !doecho) { - for (int channels = 1; channels <= 2; ++channels) { - ZeroMemory(&wfe, sizeof(wfe)); - wfe.Format.cbSize = 0; - wfe.Format.wFormatTag = WAVE_FORMAT_PCM; - wfe.Format.nChannels = channels; - wfe.Format.nSamplesPerSec = 48000; - wfe.Format.wBitsPerSample = 16; - wfe.Format.nBlockAlign = wfe.Format.nChannels * wfe.Format.wBitsPerSample / 8; - wfe.Format.nAvgBytesPerSec = wfe.Format.nBlockAlign * wfe.Format.nSamplesPerSec; + hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); - micpwfxe = &wfe; - micpwfx = reinterpret_cast< WAVEFORMATEX * >(&wfe); - - hr = pMicAudioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, want, want, - micpwfx, nullptr); - if (SUCCEEDED(hr)) { - eMicFormat = SampleShort; - exclusive = true; - qWarning("WASAPIInput: Successfully opened exclusive mode"); - break; - } - - micpwfxe = nullptr; - micpwfx = nullptr; - } - } - - if (!micpwfxe) { - if (Global::get().s.bExclusiveInput) - qWarning("WASAPIInput: Failed to open exclusive mode."); - - if (!getAndCheckMixFormat("WASAPIInput", "Mic", pMicAudioClient, &micpwfx, &micpwfxe, &eMicFormat)) { + // Open mic device. + pMicDevice.OpenNamedOrDefaultDevice(Global::get().s.qsWASAPIInput, eCapture, WASAPIRoleFromSettings()); + if (!pMicDevice) goto cleanup; + + // Open echo capture device. + if (doecho) { + pEchoDevice.OpenNamedOrDefaultDevice(Global::get().s.qsWASAPIOutput, eRender, WASAPIRoleFromSettings()); + if (!pEchoDevice) + doecho = false; } - hr = pMicAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, 0, 0, micpwfx, - nullptr); + hr = pMicDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, (void **) &pMicAudioClient); if (FAILED(hr)) { - qWarning("WASAPIInput: Mic Initialize failed: hr=0x%08lx", hr); - if (hr == E_ACCESSDENIED) { - WASAPIInputRegistrar::hasOSPermissionDenied = true; - Global::get().mw->msgBox( - tr("Access to the microphone was denied. Please check that your operating system's " - "microphone settings allow Mumble to use the microphone.")); - } + qWarning("WASAPIInput: Activate Mic AudioClient failed: hr=0x%08lx", hr); goto cleanup; } - } - - qWarning() << "WASAPIInput: Mic Stream format" << eMicFormat; - - pMicAudioClient->GetStreamLatency(&latency); - hr = pMicAudioClient->GetBufferSize(&bufferFrameCount); - qWarning("WASAPIInput: Stream Latency %lld (%d)", latency, bufferFrameCount); - hr = pMicAudioClient->GetService(__uuidof(IAudioCaptureClient), (void **) &pMicCaptureClient); - if (FAILED(hr)) { - qWarning("WASAPIInput: Mic GetService failed: hr=0x%08lx", hr); - goto cleanup; - } + def = min = latency = 0; + + pMicAudioClient->GetDevicePeriod(&def, &min); + + want = qMax< REFERENCE_TIME >(min, 100000); + qWarning("WASAPIInput: Latencies %lld %lld => %lld", def, min, want); + + if (Global::get().s.bExclusiveInput && !doecho) { + for (int channels = 1; channels <= 2; ++channels) { + ZeroMemory(&wfe, sizeof(wfe)); + wfe.Format.cbSize = 0; + wfe.Format.wFormatTag = WAVE_FORMAT_PCM; + wfe.Format.nChannels = channels; + wfe.Format.nSamplesPerSec = 48000; + wfe.Format.wBitsPerSample = 16; + wfe.Format.nBlockAlign = wfe.Format.nChannels * wfe.Format.wBitsPerSample / 8; + wfe.Format.nAvgBytesPerSec = wfe.Format.nBlockAlign * wfe.Format.nSamplesPerSec; + + micpwfxe = &wfe; + micpwfx = reinterpret_cast< WAVEFORMATEX * >(&wfe); + + hr = pMicAudioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, want, want, + micpwfx, nullptr); + if (SUCCEEDED(hr)) { + eMicFormat = SampleShort; + exclusive = true; + qWarning("WASAPIInput: Successfully opened exclusive mode"); + break; + } - pMicAudioClient->SetEventHandle(hEvent); - if (FAILED(hr)) { - qWarning("WASAPIInput: Failed to set mic event: hr=0x%08lx", hr); - goto cleanup; - } + micpwfxe = nullptr; + micpwfx = nullptr; + } + } - hr = pMicAudioClient->Start(); - if (FAILED(hr)) { - qWarning("WASAPIInput: Failed to start mic: hr=0x%08lx", hr); - goto cleanup; - } + if (!micpwfxe) { + if (Global::get().s.bExclusiveInput) + qWarning("WASAPIInput: Failed to open exclusive mode."); - iMicChannels = micpwfx->nChannels; - iMicFreq = micpwfx->nSamplesPerSec; + if (!getAndCheckMixFormat("WASAPIInput", "Mic", pMicAudioClient, &micpwfx, &micpwfxe, &eMicFormat)) { + goto cleanup; + } - if (doecho) { - hr = pEchoDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, (void **) &pEchoAudioClient); - if (FAILED(hr)) { - qWarning("WASAPIInput: Activate Echo AudioClient failed: hr=0x%08lx", hr); - goto cleanup; + hr = pMicAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, 0, 0, micpwfx, + nullptr); + if (FAILED(hr)) { + qWarning("WASAPIInput: Mic Initialize failed: hr=0x%08lx", hr); + if (hr == E_ACCESSDENIED) { + WASAPIInputRegistrar::hasOSPermissionDenied = true; + Global::get().mw->msgBox( + tr("Access to the microphone was denied. Please check that your operating system's " + "microphone settings allow Mumble to use the microphone.")); + } + goto cleanup; + } } - if (!getAndCheckMixFormat("WASAPIInput", "Echo", pEchoAudioClient, &echopwfx, &echopwfxe, &eEchoFormat)) { - goto cleanup; - } + qWarning() << "WASAPIInput: Mic Stream format" << eMicFormat; - hr = pEchoAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, - AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_LOOPBACK, 0, 0, - echopwfx, nullptr); - if (FAILED(hr)) { - qWarning("WASAPIInput: Echo Initialize failed: hr=0x%08lx", hr); - goto cleanup; - } + pMicAudioClient->GetStreamLatency(&latency); + hr = pMicAudioClient->GetBufferSize(&bufferFrameCount); + qWarning("WASAPIInput: Stream Latency %lld (%d)", latency, bufferFrameCount); - hr = pEchoAudioClient->GetBufferSize(&bufferFrameCount); - hr = pEchoAudioClient->GetService(__uuidof(IAudioCaptureClient), (void **) &pEchoCaptureClient); + hr = pMicAudioClient->GetService(__uuidof(IAudioCaptureClient), (void **) &pMicCaptureClient); if (FAILED(hr)) { - qWarning("WASAPIInput: Echo GetService failed: hr=0x%08lx", hr); + qWarning("WASAPIInput: Mic GetService failed: hr=0x%08lx", hr); goto cleanup; } - pEchoAudioClient->SetEventHandle(hEvent); + pMicAudioClient->SetEventHandle(hEvent); if (FAILED(hr)) { - qWarning("WASAPIInput: Failed to set echo event: hr=0x%08lx", hr); + qWarning("WASAPIInput: Failed to set mic event: hr=0x%08lx", hr); goto cleanup; } - hr = pEchoAudioClient->Start(); + hr = pMicAudioClient->Start(); if (FAILED(hr)) { - qWarning("WASAPIInput: Failed to start Echo: hr=0x%08lx", hr); + qWarning("WASAPIInput: Failed to start mic: hr=0x%08lx", hr); goto cleanup; } - qWarning() << "WASAPIInput: Echo Stream format" << eEchoFormat; + iMicChannels = micpwfx->nChannels; + iMicFreq = micpwfx->nSamplesPerSec; - iEchoChannels = echopwfx->nChannels; - iEchoFreq = echopwfx->nSamplesPerSec; - } - - initializeMixer(); + if (doecho) { + hr = pEchoDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, (void **) &pEchoAudioClient); + if (FAILED(hr)) { + qWarning("WASAPIInput: Activate Echo AudioClient failed: hr=0x%08lx", hr); + goto cleanup; + } - allocLength = (iMicLength / 2) * micpwfx->nChannels; + if (!getAndCheckMixFormat("WASAPIInput", "Echo", pEchoAudioClient, &echopwfx, &echopwfxe, &eEchoFormat)) { + goto cleanup; + } - if (exclusive) { - sbuff = new short[allocLength]; - while (bRunning && !FAILED(hr)) { - hr = pMicCaptureClient->GetBuffer(&pData, &numFramesAvailable, &flags, &devicePosition, &qpcPosition); - if (hr != AUDCLNT_S_BUFFER_EMPTY) { - if (FAILED(hr)) - goto cleanup; + hr = pEchoAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_LOOPBACK, 0, 0, + echopwfx, nullptr); + if (FAILED(hr)) { + qWarning("WASAPIInput: Echo Initialize failed: hr=0x%08lx", hr); + goto cleanup; + } - UINT32 nFrames = numFramesAvailable * micpwfx->nChannels; - if (nFrames > allocLength) { - delete[] sbuff; - allocLength = nFrames; - sbuff = new short[allocLength]; - } + hr = pEchoAudioClient->GetBufferSize(&bufferFrameCount); + hr = pEchoAudioClient->GetService(__uuidof(IAudioCaptureClient), (void **) &pEchoCaptureClient); + if (FAILED(hr)) { + qWarning("WASAPIInput: Echo GetService failed: hr=0x%08lx", hr); + goto cleanup; + } - memcpy(sbuff, pData, nFrames * sizeof(short)); - hr = pMicCaptureClient->ReleaseBuffer(numFramesAvailable); - if (FAILED(hr)) - goto cleanup; - addMic(sbuff, numFramesAvailable); + pEchoAudioClient->SetEventHandle(hEvent); + if (FAILED(hr)) { + qWarning("WASAPIInput: Failed to set echo event: hr=0x%08lx", hr); + goto cleanup; } - if (!FAILED(hr)) - WaitForSingleObject(hEvent, 100); - } - } else { - tbuff = new float[allocLength]; - while (bRunning && !FAILED(hr)) { - hr = pMicCaptureClient->GetNextPacketSize(&micPacketLength); - if (!FAILED(hr) && iEchoChannels) - hr = pEchoCaptureClient->GetNextPacketSize(&echoPacketLength); + + hr = pEchoAudioClient->Start(); if (FAILED(hr)) { - qWarning("WASAPIInput: GetNextPacketSize failed: hr=0x%08lx", hr); + qWarning("WASAPIInput: Failed to start Echo: hr=0x%08lx", hr); goto cleanup; } - while ((micPacketLength > 0) || (echoPacketLength > 0)) { - if (echoPacketLength > 0) { - hr = pEchoCaptureClient->GetBuffer(&pData, &numFramesAvailable, &flags, &devicePosition, - &qpcPosition); - if (FAILED(hr)) { - qWarning("WASAPIInput: GetBuffer failed: hr=0x%08lx", hr); - goto cleanup; - } + qWarning() << "WASAPIInput: Echo Stream format" << eEchoFormat; - UINT32 nFrames = numFramesAvailable * echopwfx->nChannels; - if (nFrames > allocLength) { - delete[] tbuff; - allocLength = nFrames; - tbuff = new float[allocLength]; - } - memcpy(tbuff, pData, nFrames * sizeof(float)); - hr = pEchoCaptureClient->ReleaseBuffer(numFramesAvailable); - if (FAILED(hr)) { - qWarning("WASAPIInput: ReleaseBuffer failed: hr=0x%08lx", hr); - goto cleanup; - } - addEcho(tbuff, numFramesAvailable); - } else if (micPacketLength > 0) { - hr = pMicCaptureClient->GetBuffer(&pData, &numFramesAvailable, &flags, &devicePosition, - &qpcPosition); - if (FAILED(hr)) { - qWarning("WASAPIInput: GetBuffer failed: hr=0x%08lx", hr); + iEchoChannels = echopwfx->nChannels; + iEchoFreq = echopwfx->nSamplesPerSec; + } + + initializeMixer(); + + allocLength = (iMicLength / 2) * micpwfx->nChannels; + + if (exclusive) { + sbuff = new short[allocLength]; + while (bRunning && !FAILED(hr)) { + hr = pMicCaptureClient->GetBuffer(&pData, &numFramesAvailable, &flags, &devicePosition, &qpcPosition); + if (hr != AUDCLNT_S_BUFFER_EMPTY) { + if (FAILED(hr)) goto cleanup; - } UINT32 nFrames = numFramesAvailable * micpwfx->nChannels; if (nFrames > allocLength) { - delete[] tbuff; + delete[] sbuff; allocLength = nFrames; - tbuff = new float[allocLength]; + sbuff = new short[allocLength]; } - memcpy(tbuff, pData, nFrames * sizeof(float)); + + memcpy(sbuff, pData, nFrames * sizeof(short)); hr = pMicCaptureClient->ReleaseBuffer(numFramesAvailable); - if (FAILED(hr)) { - qWarning("WASAPIInput: ReleaseBuffer failed: hr=0x%08lx", hr); + if (FAILED(hr)) goto cleanup; - } - addMic(tbuff, numFramesAvailable); + addMic(sbuff, numFramesAvailable); } + if (!FAILED(hr)) + WaitForSingleObject(hEvent, 100); + } + } else { + tbuff = new float[allocLength]; + while (bRunning && !FAILED(hr)) { hr = pMicCaptureClient->GetNextPacketSize(&micPacketLength); if (!FAILED(hr) && iEchoChannels) hr = pEchoCaptureClient->GetNextPacketSize(&echoPacketLength); + if (FAILED(hr)) { + qWarning("WASAPIInput: GetNextPacketSize failed: hr=0x%08lx", hr); + goto cleanup; + } + + while ((micPacketLength > 0) || (echoPacketLength > 0)) { + if (echoPacketLength > 0) { + hr = pEchoCaptureClient->GetBuffer(&pData, &numFramesAvailable, &flags, &devicePosition, + &qpcPosition); + if (FAILED(hr)) { + qWarning("WASAPIInput: GetBuffer failed: hr=0x%08lx", hr); + goto cleanup; + } + + UINT32 nFrames = numFramesAvailable * echopwfx->nChannels; + if (nFrames > allocLength) { + delete[] tbuff; + allocLength = nFrames; + tbuff = new float[allocLength]; + } + memcpy(tbuff, pData, nFrames * sizeof(float)); + hr = pEchoCaptureClient->ReleaseBuffer(numFramesAvailable); + if (FAILED(hr)) { + qWarning("WASAPIInput: ReleaseBuffer failed: hr=0x%08lx", hr); + goto cleanup; + } + addEcho(tbuff, numFramesAvailable); + } else if (micPacketLength > 0) { + hr = pMicCaptureClient->GetBuffer(&pData, &numFramesAvailable, &flags, &devicePosition, + &qpcPosition); + if (FAILED(hr)) { + qWarning("WASAPIInput: GetBuffer failed: hr=0x%08lx", hr); + goto cleanup; + } + + UINT32 nFrames = numFramesAvailable * micpwfx->nChannels; + if (nFrames > allocLength) { + delete[] tbuff; + allocLength = nFrames; + tbuff = new float[allocLength]; + } + memcpy(tbuff, pData, nFrames * sizeof(float)); + hr = pMicCaptureClient->ReleaseBuffer(numFramesAvailable); + if (FAILED(hr)) { + qWarning("WASAPIInput: ReleaseBuffer failed: hr=0x%08lx", hr); + goto cleanup; + } + addMic(tbuff, numFramesAvailable); + } + hr = pMicCaptureClient->GetNextPacketSize(&micPacketLength); + if (!FAILED(hr) && iEchoChannels) + hr = pEchoCaptureClient->GetNextPacketSize(&echoPacketLength); + } + if (!FAILED(hr)) + WaitForSingleObject(hEvent, 2000); } - if (!FAILED(hr)) - WaitForSingleObject(hEvent, 2000); } - } cleanup: - if (micpwfx && !exclusive) - CoTaskMemFree(micpwfx); - if (echopwfx) - CoTaskMemFree(echopwfx); - - if (pMicAudioClient) { - pMicAudioClient->Stop(); - pMicAudioClient->Release(); - } - if (pMicCaptureClient) - pMicCaptureClient->Release(); - if (pMicDevice) - pMicDevice->Release(); - - if (pEchoAudioClient) { - pEchoAudioClient->Stop(); - pEchoAudioClient->Release(); + doOuterRetry = (hr == AUDCLNT_E_DEVICE_INVALIDATED); + if (doOuterRetry) { + qWarning("WASAPIInput: Got AUDCLNT_E_DEVICE_INVALIDATED. Reinitializing"); + } + + if (micpwfx && !exclusive) { + CoTaskMemFree(micpwfx); + } + micpwfx = nullptr; + micpwfxe = nullptr; + + if (echopwfx) { + CoTaskMemFree(echopwfx); + echopwfx = nullptr; + echopwfxe = nullptr; + } + + if (pMicAudioClient) { + pMicAudioClient->Stop(); + pMicAudioClient->Release(); + pMicAudioClient = nullptr; + } + if (pMicCaptureClient) { + pMicCaptureClient->Release(); + pMicCaptureClient = nullptr; + } + pMicDevice.ClearDevice(); + + if (pEchoAudioClient) { + pEchoAudioClient->Stop(); + pEchoAudioClient->Release(); + pEchoAudioClient = nullptr; + } + if (pEchoCaptureClient) { + pEchoCaptureClient->Release(); + pEchoCaptureClient = nullptr; + } + pEchoDevice.ClearDevice(); + + if (hEvent) { + CloseHandle(hEvent); + hEvent = nullptr; + } + + if (tbuff) { + delete[] tbuff; + tbuff = nullptr; + } + if (sbuff) { + delete[] sbuff; + sbuff = nullptr; + } + } - if (pEchoCaptureClient) - pEchoCaptureClient->Release(); - if (pEchoDevice) - pEchoDevice->Release(); if (hMmThread) AvRevertMmThreadCharacteristics(hMmThread); - if (hEvent) - CloseHandle(hEvent); - - delete[] tbuff; - delete[] sbuff; } WASAPIOutput::WASAPIOutput() { @@ -888,7 +995,7 @@ static void SetDuckingOptOut(IMMDevice *pDevice) { void WASAPIOutput::run() { HRESULT hr; - IMMDevice *pDevice = nullptr; + WASAPIDevice pDevice; IAudioClient *pAudioClient = nullptr; IAudioRenderClient *pRenderClient = nullptr; WAVEFORMATEX *pwfx = nullptr; @@ -910,252 +1017,271 @@ void WASAPIOutput::run() { CoInitialize(nullptr); - hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); - hMmThread = AvSetMmThreadCharacteristics(L"Pro Audio", &dwTaskIndex); if (!hMmThread) { qWarning("WASAPIOutput: Failed to set Pro Audio thread priority"); } - // Open the output device. - pDevice = openNamedOrDefaultDevice(Global::get().s.qsWASAPIOutput, eRender, WASAPIRoleFromSettings()); - if (!pDevice) - goto cleanup; + // Outer retry loop - For handling AUDCLNT_E_DEVICE_INVALIDATED which requires complete cleanup and then re-creation of audio endpoint. + bool doOuterRetry = true; + while (doOuterRetry) { + hr = 0; - // Opt-out of the Windows 7 ducking behavior - SetDuckingOptOut(pDevice); + hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); - hr = pDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, (void **) &pAudioClient); - if (FAILED(hr)) { - qWarning("WASAPIOutput: Activate AudioClient failed: hr=0x%08lx", hr); - goto cleanup; - } + // Open the output device. + pDevice.OpenNamedOrDefaultDevice(Global::get().s.qsWASAPIOutput, eRender, WASAPIRoleFromSettings()); + if (!pDevice) + goto cleanup; - pAudioClient->GetDevicePeriod(&def, &min); - want = qMax< REFERENCE_TIME >(min, 100000); - qWarning("WASAPIOutput: Latencies %lld %lld => %lld", def, min, want); + // Opt-out of the Windows 7 ducking behavior + SetDuckingOptOut(pDevice); - if (Global::get().s.bExclusiveOutput) { - hr = pAudioClient->GetMixFormat(&pwfx); + hr = pDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, (void **) &pAudioClient); if (FAILED(hr)) { - qWarning("WASAPIOutput: GetMixFormat failed: hr=0x%08lx", hr); + qWarning("WASAPIOutput: Activate AudioClient failed: hr=0x%08lx", hr); goto cleanup; } - if (pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { - pwfxe = reinterpret_cast< WAVEFORMATEXTENSIBLE * >(pwfx); - } + pAudioClient->GetDevicePeriod(&def, &min); + want = qMax< REFERENCE_TIME >(min, 100000); + qWarning("WASAPIOutput: Latencies %lld %lld => %lld", def, min, want); - if (!Global::get().s.bPositionalAudio) { - // Override mix format and request stereo - pwfx->nChannels = 2; - if (pwfxe) { - pwfxe->dwChannelMask = KSAUDIO_SPEAKER_STEREO; + if (Global::get().s.bExclusiveOutput) { + hr = pAudioClient->GetMixFormat(&pwfx); + if (FAILED(hr)) { + qWarning("WASAPIOutput: GetMixFormat failed: hr=0x%08lx", hr); + goto cleanup; } - } - pwfx->cbSize = 0; - if (pwfxe) { - pwfxe->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - } else { - pwfx->wFormatTag = WAVE_FORMAT_PCM; - } - pwfx->nSamplesPerSec = 48000; - pwfx->wBitsPerSample = 16; - pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8; - pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec; + if (pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { + pwfxe = reinterpret_cast< WAVEFORMATEXTENSIBLE * >(pwfx); + } - hr = pAudioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, want, want, pwfx, - nullptr); - if (SUCCEEDED(hr)) { - eSampleFormat = SampleShort; - exclusive = true; - qWarning("WASAPIOutput: Successfully opened exclusive mode"); - } else { - CoTaskMemFree(pwfx); + if (!Global::get().s.bPositionalAudio) { + // Override mix format and request stereo + pwfx->nChannels = 2; + if (pwfxe) { + pwfxe->dwChannelMask = KSAUDIO_SPEAKER_STEREO; + } + } - pwfxe = nullptr; - pwfx = nullptr; - } - } + pwfx->cbSize = 0; + if (pwfxe) { + pwfxe->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + } else { + pwfx->wFormatTag = WAVE_FORMAT_PCM; + } + pwfx->nSamplesPerSec = 48000; + pwfx->wBitsPerSample = 16; + pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8; + pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec; - if (!pwfx) { - if (Global::get().s.bExclusiveOutput) - qWarning("WASAPIOutput: Failed to open exclusive mode."); + hr = pAudioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, want, want, pwfx, + nullptr); + if (SUCCEEDED(hr)) { + eSampleFormat = SampleShort; + exclusive = true; + qWarning("WASAPIOutput: Successfully opened exclusive mode"); + } else { + CoTaskMemFree(pwfx); - if (!getAndCheckMixFormat("WASAPIOutput", "Output", pAudioClient, &pwfx, &pwfxe, &eSampleFormat)) { - goto cleanup; + pwfxe = nullptr; + pwfx = nullptr; + } } - if (!Global::get().s.bPositionalAudio) { - pwfx->nChannels = 2; - pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8; - pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec; + if (!pwfx) { + if (Global::get().s.bExclusiveOutput) + qWarning("WASAPIOutput: Failed to open exclusive mode."); - if (pwfxe) { - pwfxe->dwChannelMask = KSAUDIO_SPEAKER_STEREO; + if (!getAndCheckMixFormat("WASAPIOutput", "Output", pAudioClient, &pwfx, &pwfxe, &eSampleFormat)) { + goto cleanup; } - WAVEFORMATEX *closestFormat = nullptr; - hr = pAudioClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, pwfx, &closestFormat); - if (hr == S_FALSE) { - qWarning("WASAPIOutput: Driver says no to 2 channel output. Closest format: %d channels @ %lu kHz", - closestFormat->nChannels, static_cast< unsigned long >(closestFormat->nSamplesPerSec)); - CoTaskMemFree(pwfx); + if (!Global::get().s.bPositionalAudio) { + pwfx->nChannels = 2; + pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8; + pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec; - // Fall back to whatever the device offers. + if (pwfxe) { + pwfxe->dwChannelMask = KSAUDIO_SPEAKER_STEREO; + } - if (!getAndCheckMixFormat("WASAPIOutput", "Output", pAudioClient, &pwfx, &pwfxe, &eSampleFormat)) { - CoTaskMemFree(closestFormat); - goto cleanup; + WAVEFORMATEX *closestFormat = nullptr; + hr = pAudioClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, pwfx, &closestFormat); + if (hr == S_FALSE) { + qWarning("WASAPIOutput: Driver says no to 2 channel output. Closest format: %d channels @ %lu kHz", + closestFormat->nChannels, static_cast< unsigned long >(closestFormat->nSamplesPerSec)); + CoTaskMemFree(pwfx); + + // Fall back to whatever the device offers. + + if (!getAndCheckMixFormat("WASAPIOutput", "Output", pAudioClient, &pwfx, &pwfxe, &eSampleFormat)) { + CoTaskMemFree(closestFormat); + goto cleanup; + } + } else if (FAILED(hr)) { + qWarning("WASAPIOutput: IsFormatSupported failed: hr=0x%08lx", hr); } - } else if (FAILED(hr)) { - qWarning("WASAPIOutput: IsFormatSupported failed: hr=0x%08lx", hr); + + CoTaskMemFree(closestFormat); } - CoTaskMemFree(closestFormat); + hr = pAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, bufferDuration, 0, + pwfx, nullptr); + if (FAILED(hr)) { + qWarning("WASAPIOutput: Initialize failed: hr=0x%08lx", hr); + goto cleanup; + } } - hr = pAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, bufferDuration, 0, - pwfx, nullptr); - if (FAILED(hr)) { - qWarning("WASAPIOutput: Initialize failed: hr=0x%08lx", hr); - goto cleanup; - } - } + qWarning() << "WASAPIOutput: Output stream format" << eSampleFormat; - qWarning() << "WASAPIOutput: Output stream format" << eSampleFormat; + pAudioClient->GetStreamLatency(&latency); + pAudioClient->GetBufferSize(&bufferFrameCount); + qWarning("WASAPIOutput: Stream Latency %lld (%d)", latency, bufferFrameCount); - pAudioClient->GetStreamLatency(&latency); - pAudioClient->GetBufferSize(&bufferFrameCount); - qWarning("WASAPIOutput: Stream Latency %lld (%d)", latency, bufferFrameCount); + iMixerFreq = pwfx->nSamplesPerSec; - iMixerFreq = pwfx->nSamplesPerSec; + qWarning("WASAPIOutput: Periods %lldus %lldus (latency %lldus)", def / 10LL, min / 10LL, latency / 10LL); + qWarning("WASAPIOutput: Buffer is %dus (%d)", (bufferFrameCount * 1000000) / iMixerFreq, + Global::get().s.iOutputDelay); - qWarning("WASAPIOutput: Periods %lldus %lldus (latency %lldus)", def / 10LL, min / 10LL, latency / 10LL); - qWarning("WASAPIOutput: Buffer is %dus (%d)", (bufferFrameCount * 1000000) / iMixerFreq, - Global::get().s.iOutputDelay); + hr = pAudioClient->GetService(__uuidof(IAudioRenderClient), (void **) &pRenderClient); + if (FAILED(hr)) { + qWarning("WASAPIOutput: GetService failed: hr=0x%08lx", hr); + goto cleanup; + } - hr = pAudioClient->GetService(__uuidof(IAudioRenderClient), (void **) &pRenderClient); - if (FAILED(hr)) { - qWarning("WASAPIOutput: GetService failed: hr=0x%08lx", hr); - goto cleanup; - } + pAudioClient->SetEventHandle(hEvent); + if (FAILED(hr)) { + qWarning("WASAPIOutput: Failed to set event: hr=0x%08lx", hr); + goto cleanup; + } - pAudioClient->SetEventHandle(hEvent); - if (FAILED(hr)) { - qWarning("WASAPIOutput: Failed to set event: hr=0x%08lx", hr); - goto cleanup; - } + hr = pAudioClient->Start(); + if (FAILED(hr)) { + qWarning("WASAPIOutput: Failed to start: hr=0x%08lx", hr); + goto cleanup; + } - hr = pAudioClient->Start(); - if (FAILED(hr)) { - qWarning("WASAPIOutput: Failed to start: hr=0x%08lx", hr); - goto cleanup; - } + if (pwfxe) { + for (int i = 0; i < 32; i++) { + if (pwfxe->dwChannelMask & (1 << i)) { + chanmasks[ns++] = 1 << i; + } + } + } else { + qWarning("WASAPIOutput: No chanmask available. Assigning in order."); - if (pwfxe) { - for (int i = 0; i < 32; i++) { - if (pwfxe->dwChannelMask & (1 << i)) { + for (int i = 0; i < pwfx->nChannels && i < 32; ++i) { chanmasks[ns++] = 1 << i; } } - } else { - qWarning("WASAPIOutput: No chanmask available. Assigning in order."); - for (int i = 0; i < pwfx->nChannels && i < 32; ++i) { - chanmasks[ns++] = 1 << i; + if (ns != pwfx->nChannels) { + qWarning("WASAPIOutput: Chanmask bits doesn't match number of channels."); } - } - if (ns != pwfx->nChannels) { - qWarning("WASAPIOutput: Chanmask bits doesn't match number of channels."); - } + iChannels = pwfx->nChannels; + initializeMixer(chanmasks); - iChannels = pwfx->nChannels; - initializeMixer(chanmasks); + numFramesAvailable = 0; - numFramesAvailable = 0; + while (bRunning && !FAILED(hr)) { + if (!exclusive) { + // Attenuate stream volumes. + if (lastspoke != (Global::get().bAttenuateOthers || mixed)) { + lastspoke = Global::get().bAttenuateOthers || mixed; + setVolumes(pDevice, lastspoke); + } - while (bRunning && !FAILED(hr)) { - if (!exclusive) { - // Attenuate stream volumes. - if (lastspoke != (Global::get().bAttenuateOthers || mixed)) { - lastspoke = Global::get().bAttenuateOthers || mixed; - setVolumes(pDevice, lastspoke); + hr = pAudioClient->GetCurrentPadding(&numFramesAvailable); + if (FAILED(hr)) { + qWarning("WASAPIOutput: GetCurrentPadding failed: hr=0x%08lx", hr); + goto cleanup; + } } - hr = pAudioClient->GetCurrentPadding(&numFramesAvailable); - if (FAILED(hr)) { - qWarning("WASAPIOutput: GetCurrentPadding failed: hr=0x%08lx", hr); - goto cleanup; - } - } + UINT32 packetLength = bufferFrameCount - numFramesAvailable; - UINT32 packetLength = bufferFrameCount - numFramesAvailable; + while (packetLength > 0) { + hr = pRenderClient->GetBuffer(packetLength, &pData); + if (FAILED(hr)) { + qWarning("WASAPIOutput: GetBuffer failed: hr=0x%08lx", hr); + goto cleanup; + } - while (packetLength > 0) { - hr = pRenderClient->GetBuffer(packetLength, &pData); - if (FAILED(hr)) { - qWarning("WASAPIOutput: GetBuffer failed: hr=0x%08lx", hr); - goto cleanup; - } + mixed = mix(reinterpret_cast< float * >(pData), packetLength); + if (mixed) + hr = pRenderClient->ReleaseBuffer(packetLength, 0); + else + hr = pRenderClient->ReleaseBuffer(packetLength, AUDCLNT_BUFFERFLAGS_SILENT); + if (FAILED(hr)) { + qWarning("WASAPIOutput: ReleaseBuffer failed: hr=0x%08lx", hr); + goto cleanup; + } - mixed = mix(reinterpret_cast< float * >(pData), packetLength); - if (mixed) - hr = pRenderClient->ReleaseBuffer(packetLength, 0); - else - hr = pRenderClient->ReleaseBuffer(packetLength, AUDCLNT_BUFFERFLAGS_SILENT); - if (FAILED(hr)) { - qWarning("WASAPIOutput: ReleaseBuffer failed: hr=0x%08lx", hr); - goto cleanup; - } + // Exclusive mode rendering ends here. + if (exclusive) + break; - // Exclusive mode rendering ends here. - if (exclusive) - break; + if (!Global::get().s.bAttenuateOthers && !Global::get().bAttenuateOthers) { + mixed = false; + } - if (!Global::get().s.bAttenuateOthers && !Global::get().bAttenuateOthers) { - mixed = false; - } + if (lastspoke != (Global::get().bAttenuateOthers || mixed)) { + lastspoke = Global::get().bAttenuateOthers || mixed; + setVolumes(pDevice, lastspoke); + } - if (lastspoke != (Global::get().bAttenuateOthers || mixed)) { - lastspoke = Global::get().bAttenuateOthers || mixed; - setVolumes(pDevice, lastspoke); - } + hr = pAudioClient->GetCurrentPadding(&numFramesAvailable); + if (FAILED(hr)) { + qWarning("WASAPIOutput: GetCurrentPadding failed: hr=0x%08lx", hr); + goto cleanup; + } - hr = pAudioClient->GetCurrentPadding(&numFramesAvailable); - if (FAILED(hr)) { - qWarning("WASAPIOutput: GetCurrentPadding failed: hr=0x%08lx", hr); - goto cleanup; + packetLength = bufferFrameCount - numFramesAvailable; } + if (!FAILED(hr)) + WaitForSingleObject(hEvent, exclusive ? 100 : 2000); + } - packetLength = bufferFrameCount - numFramesAvailable; + cleanup: + doOuterRetry = (hr == AUDCLNT_E_DEVICE_INVALIDATED); + if (doOuterRetry) { + qWarning("WASAPIOutput: Got AUDCLNT_E_DEVICE_INVALIDATED. Reinitializing"); } - if (!FAILED(hr)) - WaitForSingleObject(hEvent, exclusive ? 100 : 2000); - } -cleanup: - if (pwfx) - CoTaskMemFree(pwfx); + if (pwfx) { + CoTaskMemFree(pwfx); + pwfx = nullptr; + pwfxe = nullptr; + } - if (pDevice) { - setVolumes(pDevice, false); - } + if (pDevice) { + setVolumes(pDevice, false); + } + + if (pAudioClient) { + pAudioClient->Stop(); + pAudioClient->Release(); + pAudioClient = nullptr; + } + if (pRenderClient) { + pRenderClient->Release(); + pRenderClient = nullptr; + } + pDevice.ClearDevice(); - if (pAudioClient) { - pAudioClient->Stop(); - pAudioClient->Release(); + if (hEvent) { + CloseHandle(hEvent); + hEvent = nullptr; + } + } - if (pRenderClient) - pRenderClient->Release(); - if (pDevice) - pDevice->Release(); if (hMmThread) AvRevertMmThreadCharacteristics(hMmThread); - - if (hEvent) - CloseHandle(hEvent); } diff --git a/src/mumble/WASAPI.h b/src/mumble/WASAPI.h index 1bc487b925b..a814092ec66 100644 --- a/src/mumble/WASAPI.h +++ b/src/mumble/WASAPI.h @@ -38,6 +38,34 @@ class WASAPISystem : public QObject { static const QHash< QString, QString > getOutputDevices(); }; +struct WASAPIDevice { +public: + WASAPIDevice(); + WASAPIDevice(nullptr_t); + WASAPIDevice(WASAPIDevice&& other); + ~WASAPIDevice(); + void ClearDevice(); + void ClearUsage(); + + operator bool() const; + operator IMMDevice*() const; + IMMDevice* operator->() const; + WASAPIDevice& operator=(WASAPIDevice&& other); + + void OpenNamedOrDefaultDevice(const QString &name, EDataFlow dataFlow, ERole role); +private: + void Move(WASAPIDevice&& other); + + IMMDevice* pDevice = nullptr; + QString devName; + bool used = false; + bool wanted = false; + QString defaultDevName; + bool usedDefault = false; + bool wantDefault = false; +}; + + class WASAPIInput : public AudioInput { private: Q_OBJECT diff --git a/src/mumble/WASAPINotificationClient.cpp b/src/mumble/WASAPINotificationClient.cpp index 56713f1b4af..30edefac2ee 100644 --- a/src/mumble/WASAPINotificationClient.cpp +++ b/src/mumble/WASAPINotificationClient.cpp @@ -20,7 +20,7 @@ HRESULT STDMETHODCALLTYPE WASAPINotificationClient::OnDefaultDeviceChanged(EData << device; QMutexLocker lock(&listsMutex); - if (!usedDefaultDevices.empty() && role == eCommunications) { + if (((wantDefaultCount > 0) || !usedDefaultDevices.empty()) && role == eCommunications) { restartAudio(); } return S_OK; @@ -34,7 +34,7 @@ HRESULT STDMETHODCALLTYPE WASAPINotificationClient::OnPropertyValueChanged(LPCWS const bool channelConfigChanged = (key == PKEY_AudioEndpoint_PhysicalSpeakers); QMutexLocker lock(&listsMutex); - if ((formatChanged || channelConfigChanged) && usedDevices.contains(device)) { + if ((formatChanged || channelConfigChanged) && (usedDevices.contains(device) || usedDefaultDevices.contains(device))) { qDebug() << "WASAPINotificationClient: Property changed device=" << device << "formatChanged=" << formatChanged << "channelConfigChanged=" << channelConfigChanged; @@ -47,7 +47,7 @@ HRESULT STDMETHODCALLTYPE WASAPINotificationClient::OnDeviceAdded(LPCWSTR pwstrD const QString device = QString::fromWCharArray(pwstrDeviceId); qDebug() << "WASAPINotificationClient: Device added=" << device; - if (usedDevices.contains(device)) { + if (wantedDevices.contains(device)) { restartAudio(); } @@ -57,6 +57,9 @@ HRESULT STDMETHODCALLTYPE WASAPINotificationClient::OnDeviceAdded(LPCWSTR pwstrD HRESULT STDMETHODCALLTYPE WASAPINotificationClient::OnDeviceRemoved(LPCWSTR pwstrDeviceId) { const QString device = QString::fromWCharArray(pwstrDeviceId); qDebug() << "WASAPINotificationClient: Device removed=" << device; + if (usedDefaultDevices.contains(device) || usedDevices.contains(device)) { + restartAudio(); + } return S_OK; } @@ -103,8 +106,18 @@ ULONG STDMETHODCALLTYPE WASAPINotificationClient::Release() { return ulRef; } -void WASAPINotificationClient::enlistDefaultDeviceAsUsed(LPCWSTR pwstrDefaultDevice) { - const QString device = QString::fromWCharArray(pwstrDefaultDevice); + +void WASAPINotificationClient::incrementWantDefault() { + wantDefaultCount += 1; +} +void WASAPINotificationClient::decrementWantDefault() { + wantDefaultCount -= 1; +} +void WASAPINotificationClient::_clearWantDefaultDeviceCount() { + wantDefaultCount = 0; +} + +void WASAPINotificationClient::enlistDefaultDeviceAsUsed(const QString& device) { QMutexLocker lock(&listsMutex); if (!usedDefaultDevices.contains(device)) { usedDefaultDevices.append(device); @@ -112,10 +125,9 @@ void WASAPINotificationClient::enlistDefaultDeviceAsUsed(LPCWSTR pwstrDefaultDev } } -void WASAPINotificationClient::enlistDeviceAsUsed(LPCWSTR pwstrDevice) { - const QString device = QString::fromWCharArray(pwstrDevice); +void WASAPINotificationClient::unlistDefaultDeviceAsUsed(const QString &device) { QMutexLocker lock(&listsMutex); - _enlistDeviceAsUsed(device); + usedDefaultDevices.removeOne(device); } void WASAPINotificationClient::_enlistDeviceAsUsed(const QString &device) { @@ -129,26 +141,54 @@ void WASAPINotificationClient::enlistDeviceAsUsed(const QString &device) { _enlistDeviceAsUsed(device); } -void WASAPINotificationClient::unlistDevice(LPCWSTR pwstrDevice) { - const QString device = QString::fromWCharArray(pwstrDevice); +void WASAPINotificationClient::unlistDeviceAsUsed(const QString &device) { QMutexLocker lock(&listsMutex); usedDevices.removeOne(device); - usedDefaultDevices.removeOne(device); } -void WASAPINotificationClient::clearUsedDefaultDeviceList() { +void WASAPINotificationClient::_enlistDeviceAsWanted(const QString &device) { + if (!wantedDevices.contains(device)) { + wantedDevices.append(device); + } +} + +void WASAPINotificationClient::enlistDeviceAsWanted(const QString &device) { + qDebug() << "WASAPINotificationClient: Device wanted=" << device; QMutexLocker lock(&listsMutex); - usedDefaultDevices.clear(); + _enlistDeviceAsWanted(device); +} + +void WASAPINotificationClient::unlistDeviceAsWanted(const QString &device) { + qDebug() << "WASAPINotificationClient: Device unwanted=" << device; + QMutexLocker lock(&listsMutex); + wantedDevices.removeOne(device); } -void WASAPINotificationClient::_clearUsedDeviceLists() { +void WASAPINotificationClient::_clearUsedDefaultDeviceList() { usedDefaultDevices.clear(); +} + +void WASAPINotificationClient::clearUsedDefaultDeviceList() { + QMutexLocker lock(&listsMutex); + _clearUsedDefaultDeviceList(); +} + +void WASAPINotificationClient::_clearUsedDeviceList() { usedDevices.clear(); } -void WASAPINotificationClient::clearUsedDeviceLists() { +void WASAPINotificationClient::clearUsedDeviceList() { + QMutexLocker lock(&listsMutex); + _clearUsedDeviceList(); +} + +void WASAPINotificationClient::_clearWantedDeviceList() { + wantedDevices.clear(); +} + +void WASAPINotificationClient::clearWantedDeviceList() { QMutexLocker lock(&listsMutex); - _clearUsedDeviceLists(); + _clearWantedDeviceList(); } void WASAPINotificationClient::doGetOnce() { @@ -194,6 +234,9 @@ WASAPINotificationClient::~WASAPINotificationClient() { void WASAPINotificationClient::restartAudio() { qWarning("WASAPINotificationClient: Triggering audio reset"); - _clearUsedDeviceLists(); + _clearUsedDefaultDeviceList(); + _clearWantDefaultDeviceCount(); + _clearUsedDeviceList(); + _clearWantedDeviceList(); emit doResetAudio(); } diff --git a/src/mumble/WASAPINotificationClient.h b/src/mumble/WASAPINotificationClient.h index befa59357f3..34e71f2180b 100644 --- a/src/mumble/WASAPINotificationClient.h +++ b/src/mumble/WASAPINotificationClient.h @@ -27,15 +27,21 @@ class WASAPINotificationClient : public QObject, public IMMNotificationClient { ULONG STDMETHODCALLTYPE Release(); /* Enlist/Unlist functionality */ - void enlistDefaultDeviceAsUsed(LPCWSTR pwstrDefaultDevice); + void incrementWantDefault(); + void decrementWantDefault(); + + void enlistDefaultDeviceAsUsed(const QString &device); + void unlistDefaultDeviceAsUsed(const QString &device); - void enlistDeviceAsUsed(LPCWSTR pwstrDevice); void enlistDeviceAsUsed(const QString &device); + void unlistDeviceAsUsed(const QString &device); - void unlistDevice(LPCWSTR pwstrDevice); + void enlistDeviceAsWanted(const QString &device); + void unlistDeviceAsWanted(const QString &device); void clearUsedDefaultDeviceList(); - void clearUsedDeviceLists(); + void clearUsedDeviceList(); + void clearWantedDeviceList(); /** * @return Singleton instance reference. @@ -55,15 +61,20 @@ class WASAPINotificationClient : public QObject, public IMMNotificationClient { void restartAudio(); /* _fu = Non locking versions */ - void _clearUsedDeviceLists(); + void _clearWantDefaultDeviceCount(); + void _clearUsedDefaultDeviceList(); + void _clearUsedDeviceList(); + void _clearWantedDeviceList(); void _enlistDeviceAsUsed(const QString &device); + void _enlistDeviceAsWanted(const QString &device); QStringList usedDefaultDevices; QStringList usedDevices; + QStringList wantedDevices; IMMDeviceEnumerator *pEnumerator; LONG _cRef; QMutex listsMutex; - + std::atomic_int32_t wantDefaultCount = 0; signals: void doResetAudio(); }; From 7f6509f421cfddaafd1ee650e6e85ccf8145cc3e Mon Sep 17 00:00:00 2001 From: Matthew Fagan Date: Fri, 20 Mar 2026 23:10:22 +1100 Subject: [PATCH 2/4] Fix missing mutex calls --- src/mumble/WASAPINotificationClient.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mumble/WASAPINotificationClient.cpp b/src/mumble/WASAPINotificationClient.cpp index 30edefac2ee..5f4ca02134f 100644 --- a/src/mumble/WASAPINotificationClient.cpp +++ b/src/mumble/WASAPINotificationClient.cpp @@ -47,6 +47,7 @@ HRESULT STDMETHODCALLTYPE WASAPINotificationClient::OnDeviceAdded(LPCWSTR pwstrD const QString device = QString::fromWCharArray(pwstrDeviceId); qDebug() << "WASAPINotificationClient: Device added=" << device; + QMutexLocker lock(&listsMutex); if (wantedDevices.contains(device)) { restartAudio(); } @@ -57,6 +58,8 @@ HRESULT STDMETHODCALLTYPE WASAPINotificationClient::OnDeviceAdded(LPCWSTR pwstrD HRESULT STDMETHODCALLTYPE WASAPINotificationClient::OnDeviceRemoved(LPCWSTR pwstrDeviceId) { const QString device = QString::fromWCharArray(pwstrDeviceId); qDebug() << "WASAPINotificationClient: Device removed=" << device; + + QMutexLocker lock(&listsMutex); if (usedDefaultDevices.contains(device) || usedDevices.contains(device)) { restartAudio(); } From 619a6d30e7bfb05181686396e8ae04406d2f1828 Mon Sep 17 00:00:00 2001 From: Matthew Fagan Date: Fri, 20 Mar 2026 23:22:41 +1100 Subject: [PATCH 3/4] Fix issues --- src/mumble/WASAPI.cpp | 19 +++++++++++++------ src/mumble/WASAPI.h | 2 ++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/mumble/WASAPI.cpp b/src/mumble/WASAPI.cpp index a30917b7a1f..0d52278447e 100644 --- a/src/mumble/WASAPI.cpp +++ b/src/mumble/WASAPI.cpp @@ -384,6 +384,7 @@ WASAPIDevice& WASAPIDevice::operator=(WASAPIDevice&& other) { ClearDevice(); ClearUsage(); Move(std::move(other)); + return *this; } WASAPIDevice::operator bool() const { @@ -497,9 +498,9 @@ void WASAPIInput::run() { HANDLE hMmThread; float *tbuff = nullptr; short *sbuff = nullptr; - bool doecho = Global::get().s.doEcho(); + bool doecho; REFERENCE_TIME def, min, latency, want; - bool exclusive = false; + bool exclusive; CoInitialize(nullptr); @@ -512,6 +513,8 @@ void WASAPIInput::run() { bool doOuterRetry = true; while (doOuterRetry) { hr = 0; + doecho = Global::get().s.doEcho(); + exclusive = false; hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); @@ -1007,13 +1010,13 @@ void WASAPIOutput::run() { BYTE *pData; DWORD dwTaskIndex = 0; HANDLE hMmThread; - int ns = 0; + int ns; unsigned int chanmasks[32]; - bool lastspoke = false; + bool lastspoke; REFERENCE_TIME bufferDuration = (Global::get().s.iOutputDelay > 1) ? (Global::get().s.iOutputDelay + 1) * 100000 : 0; - bool exclusive = false; - bool mixed = false; + bool exclusive; + bool mixed; CoInitialize(nullptr); @@ -1026,6 +1029,10 @@ void WASAPIOutput::run() { bool doOuterRetry = true; while (doOuterRetry) { hr = 0; + ns = 0; + lastspoke = false; + exclusive = false; + mixed = false; hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); diff --git a/src/mumble/WASAPI.h b/src/mumble/WASAPI.h index a814092ec66..00720465f5b 100644 --- a/src/mumble/WASAPI.h +++ b/src/mumble/WASAPI.h @@ -43,6 +43,7 @@ struct WASAPIDevice { WASAPIDevice(); WASAPIDevice(nullptr_t); WASAPIDevice(WASAPIDevice&& other); + WASAPIDevice(const WASAPIDevice&) = delete; ~WASAPIDevice(); void ClearDevice(); void ClearUsage(); @@ -51,6 +52,7 @@ struct WASAPIDevice { operator IMMDevice*() const; IMMDevice* operator->() const; WASAPIDevice& operator=(WASAPIDevice&& other); + WASAPIDevice& operator=(const WASAPIDevice&) = delete; void OpenNamedOrDefaultDevice(const QString &name, EDataFlow dataFlow, ERole role); private: From ac383372833a9c7d6ac8dfa325df37a136d87423 Mon Sep 17 00:00:00 2001 From: Matthew Fagan Date: Sat, 21 Mar 2026 14:40:17 +1100 Subject: [PATCH 4/4] Update talking indicator on mic disconnect --- src/mumble/AudioInput.cpp | 8 ++++++++ src/mumble/AudioInput.h | 2 ++ src/mumble/WASAPI.cpp | 4 +++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/mumble/AudioInput.cpp b/src/mumble/AudioInput.cpp index a2265cbc574..992f7c2c355 100644 --- a/src/mumble/AudioInput.cpp +++ b/src/mumble/AudioInput.cpp @@ -672,6 +672,14 @@ void AudioInput::addEcho(const void *data, unsigned int nsamp) { } } +void AudioInput::micGone() { + ClientUser *p = ClientUser::get(Global::get().uiSession); + if (p) { + p->setTalking(Settings::Passive); + } +} + + void AudioInput::adjustBandwidth(int bitspersec, int &bitrate, int &frames, bool &allowLowDelay) { frames = Global::get().s.iFramesPerPacket; bitrate = Global::get().s.iQuality; diff --git a/src/mumble/AudioInput.h b/src/mumble/AudioInput.h index 5a87257090e..8f2202821ac 100644 --- a/src/mumble/AudioInput.h +++ b/src/mumble/AudioInput.h @@ -246,6 +246,8 @@ class AudioInput : public QThread { Resynchronizer resync; std::vector< short > opusBuffer; + void micGone(); + void encodeAudioFrame(AudioChunk chunk); void addMic(const void *data, unsigned int nsamp); void addEcho(const void *data, unsigned int nsamp); diff --git a/src/mumble/WASAPI.cpp b/src/mumble/WASAPI.cpp index 0d52278447e..7ae9b084b5f 100644 --- a/src/mumble/WASAPI.cpp +++ b/src/mumble/WASAPI.cpp @@ -520,8 +520,10 @@ void WASAPIInput::run() { // Open mic device. pMicDevice.OpenNamedOrDefaultDevice(Global::get().s.qsWASAPIInput, eCapture, WASAPIRoleFromSettings()); - if (!pMicDevice) + if (!pMicDevice) { + micGone(); goto cleanup; + } // Open echo capture device. if (doecho) {