summaryrefslogtreecommitdiffstats
path: root/pica-client/capturegain_wasapi.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'pica-client/capturegain_wasapi.cpp')
-rw-r--r--pica-client/capturegain_wasapi.cpp153
1 files changed, 153 insertions, 0 deletions
diff --git a/pica-client/capturegain_wasapi.cpp b/pica-client/capturegain_wasapi.cpp
new file mode 100644
index 0000000..15dec57
--- /dev/null
+++ b/pica-client/capturegain_wasapi.cpp
@@ -0,0 +1,153 @@
+/*
+ (c) Copyright 2012 - 2026 Anton Sviridenko
+ https://picapica.im
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, version 3.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+#include "capturegain.h"
+
+#ifdef Q_OS_WIN
+
+#include <QDebug>
+
+// Deliberately NOT defining INITGUID here, unlike nativeaudio_wasapi.cpp: that
+// file already emits the definitions for the endpoint GUIDs, and a second copy
+// in this translation unit would collide at link time. The interface IDs below
+// come from __uuidof() instead, which mingw-w64 supports because its headers
+// declare these interfaces with __CRT_UUID_DECL.
+#include <windows.h>
+#include <objbase.h>
+#include <mmdeviceapi.h>
+#include <endpointvolume.h>
+
+static QString hrText(HRESULT hr)
+{
+ return QString("0x%1").arg((quint32)hr, 8, 16, QLatin1Char('0'));
+}
+
+// The endpoint volume, which is the slider under Settings > Sound > Input.
+//
+// Note this is not the same control as "Microphone Boost" - that one is a
+// driver specific APO with no uniform API, and is usually the thing actually
+// responsible when a mic clips at every level. Turning the endpoint volume
+// down still reduces what reaches the converter on the great majority of
+// devices, so it is the right control to reach for; it just may not have as
+// much travel as the ALSA case.
+class WasapiCaptureGain : public CaptureGainControl
+{
+public:
+ WasapiCaptureGain() : m_vol(nullptr), m_uninitCom(false) {}
+
+ ~WasapiCaptureGain() override
+ {
+ if (m_vol)
+ m_vol->Release();
+ if (m_uninitCom)
+ CoUninitialize();
+ }
+
+ bool open(const QString &device)
+ {
+ HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
+ // S_FALSE means this thread was already in an apartment, and
+ // RPC_E_CHANGED_MODE that it is in a different one - in both cases COM
+ // is usable and we must not be the one to tear it down.
+ if (SUCCEEDED(hr))
+ m_uninitCom = true;
+ else if (hr != RPC_E_CHANGED_MODE)
+ return false;
+
+ IMMDeviceEnumerator *devEnum = nullptr;
+ hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL,
+ __uuidof(IMMDeviceEnumerator), (void **)&devEnum);
+ if (FAILED(hr) || !devEnum)
+ return false;
+
+ IMMDevice *dev = nullptr;
+ if (device.isEmpty() || device == QLatin1String("default"))
+ {
+ // eCommunications to match what the capture stream itself opens in
+ // WasapiBackend::resolveDevice() - otherwise on a machine where the
+ // call endpoint and the console endpoint differ we would be turning
+ // down a device nobody is recording from.
+ hr = devEnum->GetDefaultAudioEndpoint(eCapture, eCommunications, &dev);
+ }
+ else
+ {
+ hr = devEnum->GetDevice((LPCWSTR)device.utf16(), &dev);
+ }
+
+ devEnum->Release();
+
+ if (FAILED(hr) || !dev)
+ return false;
+
+ hr = dev->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_ALL, nullptr, (void **)&m_vol);
+ dev->Release();
+
+ if (FAILED(hr) || !m_vol)
+ {
+ qWarning() << "WASAPI: capture endpoint has no volume control:" << hrText(hr);
+ m_vol = nullptr;
+ return false;
+ }
+
+ m_device = device.isEmpty() ? QStringLiteral("default") : device;
+ return true;
+ }
+
+ bool gain(double *out) override
+ {
+ if (!m_vol || !out)
+ return false;
+
+ float level = 0.0f;
+ if (FAILED(m_vol->GetMasterVolumeLevelScalar(&level)))
+ return false;
+
+ *out = level;
+ return true;
+ }
+
+ bool setGain(double value) override
+ {
+ if (!m_vol)
+ return false;
+
+ float level = (float)(value < 0.0 ? 0.0 : (value > 1.0 ? 1.0 : value));
+ return SUCCEEDED(m_vol->SetMasterVolumeLevelScalar(level, nullptr));
+ }
+
+ QString description() const override
+ {
+ return m_device + QStringLiteral(" (WASAPI endpoint volume)");
+ }
+
+private:
+ IAudioEndpointVolume *m_vol;
+ bool m_uninitCom;
+ QString m_device;
+};
+
+CaptureGainControl *pica_create_wasapi_capture_gain(const QString &device)
+{
+ WasapiCaptureGain *g = new WasapiCaptureGain();
+
+ if (g->open(device))
+ return g;
+
+ delete g;
+ return nullptr;
+}
+
+#endif // Q_OS_WIN