/* Webcamoid, webcam capture application. * Copyright (C) 2016 Gonzalo Exequiel Pedone * * Webcamoid 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, either version 3 of the License, or * (at your option) any later version. * * Webcamoid 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 Webcamoid. If not, see . * * Web-Site: http://webcamoid.github.io/ */ #include #include #include #include #include #include #include #include "audiodevwasapi.h" DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); #define MAX_ERRORS_READ_WRITE 5 #define EVENT_TIMEOUT 1000 using ErrorCodesMap = QMap; inline ErrorCodesMap initErrorCodesMap() { ErrorCodesMap errorCodes = { // COM library errors. {REGDB_E_CLASSNOTREG , "REGDB_E_CLASSNOTREG" }, {CLASS_E_NOAGGREGATION, "CLASS_E_NOAGGREGATION"}, {E_NOINTERFACE , "E_NOINTERFACE" }, {E_POINTER , "E_POINTER" }, // IMMDeviceEnumerator errors. {E_INVALIDARG , "E_INVALIDARG" }, {E_NOTFOUND , "E_NOTFOUND" }, {E_OUTOFMEMORY, "E_OUTOFMEMORY"}, // IAudioClient errors. {AUDCLNT_E_ALREADY_INITIALIZED , "AUDCLNT_E_ALREADY_INITIALIZED" }, {AUDCLNT_E_WRONG_ENDPOINT_TYPE , "AUDCLNT_E_WRONG_ENDPOINT_TYPE" }, {AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED , "AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED" }, {AUDCLNT_E_BUFFER_SIZE_ERROR , "AUDCLNT_E_BUFFER_SIZE_ERROR" }, {AUDCLNT_E_CPUUSAGE_EXCEEDED , "AUDCLNT_E_CPUUSAGE_EXCEEDED" }, {AUDCLNT_E_DEVICE_INVALIDATED , "AUDCLNT_E_DEVICE_INVALIDATED" }, {AUDCLNT_E_DEVICE_IN_USE , "AUDCLNT_E_DEVICE_IN_USE" }, {AUDCLNT_E_ENDPOINT_CREATE_FAILED , "AUDCLNT_E_ENDPOINT_CREATE_FAILED" }, {AUDCLNT_E_INVALID_DEVICE_PERIOD , "AUDCLNT_E_INVALID_DEVICE_PERIOD" }, {AUDCLNT_E_UNSUPPORTED_FORMAT , "AUDCLNT_E_UNSUPPORTED_FORMAT" }, {AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED , "AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED" }, {AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL, "AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL"}, {AUDCLNT_E_SERVICE_NOT_RUNNING , "AUDCLNT_E_SERVICE_NOT_RUNNING" }, {AUDCLNT_E_NOT_INITIALIZED , "AUDCLNT_E_NOT_INITIALIZED" }, {AUDCLNT_E_NOT_STOPPED , "AUDCLNT_E_NOT_STOPPED" }, {AUDCLNT_E_EVENTHANDLE_NOT_SET , "AUDCLNT_E_EVENTHANDLE_NOT_SET" }, {AUDCLNT_E_INVALID_SIZE , "AUDCLNT_E_INVALID_SIZE" }, {AUDCLNT_E_OUT_OF_ORDER , "AUDCLNT_E_OUT_OF_ORDER" }, {AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED , "AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED" }, {AUDCLNT_E_BUFFER_ERROR , "AUDCLNT_E_BUFFER_ERROR" }, {AUDCLNT_E_BUFFER_TOO_LARGE , "AUDCLNT_E_BUFFER_TOO_LARGE" }, {AUDCLNT_E_BUFFER_OPERATION_PENDING , "AUDCLNT_E_BUFFER_OPERATION_PENDING" } }; return errorCodes; } Q_GLOBAL_STATIC_WITH_ARGS(ErrorCodesMap, errorCodes, (initErrorCodesMap())) class AudioDevWasapiPrivate { public: AudioDevWasapi *self; QString m_error; QStringList m_sources; QStringList m_sinks; QString m_defaultSink; QString m_defaultSource; QMap m_descriptionMap; QMap> m_supportedFormats; QMap> m_supportedLayouts; QMap> m_supportedSampleRates; QMap m_preferredInputCaps; QMap m_preferredOutputCaps; QByteArray m_audioBuffer; AkAudioCaps m_curCaps; QString m_curDevice; IMMDeviceEnumerator *m_deviceEnumerator {nullptr}; IMMDevice *m_pDevice {nullptr}; IAudioClient *m_pAudioClient {nullptr}; IAudioCaptureClient *m_pCaptureClient {nullptr}; IAudioRenderClient *m_pRenderClient {nullptr}; HANDLE m_hEvent {nullptr}; ULONG m_cRef {1}; int m_samples {0}; explicit AudioDevWasapiPrivate(AudioDevWasapi *self); bool waveFormatFromCaps(WAVEFORMATEX *wfx, const AkAudioCaps &caps) const; AkAudioCaps capsFromWaveFormat(WAVEFORMATEX *wfx) const; void fillDeviceInfo(const QString &device, QList *supportedFormats, QList *supportedLayouts, QList *supportedSampleRates) const; AkAudioCaps preferredCaps(const QString &device, EDataFlow dataFlow) const; }; AudioDevWasapi::AudioDevWasapi(QObject *parent): AudioDev(parent) { this->d = new AudioDevWasapiPrivate(this); // Create DeviceEnumerator HRESULT hr; // Get device enumerator. if (FAILED(hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), reinterpret_cast(&this->d->m_deviceEnumerator)))) { return; } if (FAILED(hr = this->d->m_deviceEnumerator->RegisterEndpointNotificationCallback(this))) { this->d->m_deviceEnumerator->Release(); this->d->m_deviceEnumerator = nullptr; return; } this->updateDevices(); } AudioDevWasapi::~AudioDevWasapi() { this->uninit(); this->d->m_deviceEnumerator->UnregisterEndpointNotificationCallback(this); this->d->m_deviceEnumerator->Release(); delete this->d; } QString AudioDevWasapi::error() const { return this->d->m_error; } QString AudioDevWasapi::defaultInput() { return this->d->m_defaultSource; } QString AudioDevWasapi::defaultOutput() { return this->d->m_defaultSink; } QStringList AudioDevWasapi::inputs() { return this->d->m_sources; } QStringList AudioDevWasapi::outputs() { return this->d->m_sinks; } QString AudioDevWasapi::description(const QString &device) { return this->d->m_descriptionMap.value(device); } // Get native format for the default audio device. AkAudioCaps AudioDevWasapi::preferredFormat(const QString &device) { if (this->d->m_preferredOutputCaps.contains(device)) return this->d->m_preferredOutputCaps[device]; if (this->d->m_preferredInputCaps.contains(device)) return this->d->m_preferredInputCaps[device]; return AkAudioCaps(); } QList AudioDevWasapi::supportedFormats(const QString &device) { return this->d->m_supportedFormats.value(device); } QList AudioDevWasapi::supportedChannelLayouts(const QString &device) { return this->d->m_supportedLayouts.value(device); } QList AudioDevWasapi::supportedSampleRates(const QString &device) { return this->d->m_supportedSampleRates.value(device); } bool AudioDevWasapi::init(const QString &device, const AkAudioCaps &caps) { return this->init(device, caps, false); } bool AudioDevWasapi::init(const QString &device, const AkAudioCaps &caps, bool justActivate) { if (!this->d->m_deviceEnumerator) { this->d->m_error = "Device enumerator not created."; emit this->errorChanged(this->d->m_error); return false; } // Clear audio buffer. this->d->m_audioBuffer.clear(); HRESULT hr; // Get audio device. if (FAILED(hr = this->d->m_deviceEnumerator->GetDevice(device.toStdWString().c_str(), &this->d->m_pDevice))) { this->d->m_error = "GetDevice: " + errorCodes->value(hr); emit this->errorChanged(this->d->m_error); this->uninit(); return false; } // Get an instance for the audio client. if (FAILED(hr = this->d->m_pDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, reinterpret_cast(&this->d->m_pAudioClient)))) { this->d->m_error = "Activate: " + errorCodes->value(hr); emit this->errorChanged(this->d->m_error); this->uninit(); return false; } // Just get the audio client instance. if (justActivate) return true; // Get the minimum size of the buffer in 100-nanosecond units, // this means you must do: // // bufferSize(seconds) = 100e-9 * hnsRequestedDuration // // to get the size of the buffer in seconds. // REFERENCE_TIME hnsRequestedDuration; this->d->m_pAudioClient->GetDevicePeriod(nullptr, &hnsRequestedDuration); // Accumulate a minimum of 1 sec. of audio in the buffer. REFERENCE_TIME minDuration = 10e6; if (hnsRequestedDuration < minDuration) hnsRequestedDuration = minDuration; // Set audio device format. WAVEFORMATEX wfx; WAVEFORMATEX *ptrWfx = &wfx; WAVEFORMATEX *closestWfx = nullptr; this->d->waveFormatFromCaps(&wfx, caps); if (FAILED(this->d->m_pAudioClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &wfx, &closestWfx))) { this->uninit(); return false; } if (closestWfx) ptrWfx = closestWfx; this->d->m_curCaps = this->d->capsFromWaveFormat(ptrWfx); if (FAILED(hr = this->d->m_pAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, hnsRequestedDuration, hnsRequestedDuration, ptrWfx, nullptr))) { if (closestWfx) CoTaskMemFree(closestWfx); this->d->m_error = "Initialize: " + errorCodes->value(hr); emit this->errorChanged(this->d->m_error); this->uninit(); return false; } if (closestWfx) CoTaskMemFree(closestWfx); // Create an event handler for checking when an aundio frame is required // for reading or writing. this->d->m_hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); if (!this->d->m_hEvent) { this->d->m_error = "CreateEvent: Error creating event handler"; emit this->errorChanged(this->d->m_error); this->uninit(); return false; } // Set event handler. if (FAILED(this->d->m_pAudioClient->SetEventHandle(this->d->m_hEvent))) { this->d->m_error = "SetEventHandle: " + errorCodes->value(hr); emit this->errorChanged(this->d->m_error); this->uninit(); return false; } // Get audio capture/render client. if (this->inputs().contains(device)) hr = this->d->m_pAudioClient->GetService(__uuidof(IAudioCaptureClient), reinterpret_cast(&this->d->m_pCaptureClient)); else hr = this->d->m_pAudioClient->GetService(__uuidof(IAudioRenderClient), reinterpret_cast(&this->d->m_pRenderClient)); if (FAILED(hr)) { this->d->m_error = "GetService: " + errorCodes->value(hr); emit this->errorChanged(this->d->m_error); this->uninit(); return false; } // Start audio client. if (FAILED(hr = this->d->m_pAudioClient->Start())) { this->d->m_error = "Start: " + errorCodes->value(hr); emit this->errorChanged(this->d->m_error); this->uninit(); return false; } this->d->m_curDevice = device; this->d->m_samples = qMax(this->latency() * caps.rate() / 1000, 1); return true; } QByteArray AudioDevWasapi::read() { int bufferSize = this->d->m_samples * this->d->m_curCaps.bps() * this->d->m_curCaps.channels() / 8; int nErrors = 0; // Read audio samples until audio buffer is full. while (this->d->m_audioBuffer.size() < bufferSize && nErrors < MAX_ERRORS_READ_WRITE) { // Wait until an audio frame can be read. if (WaitForSingleObject(this->d->m_hEvent, EVENT_TIMEOUT) != WAIT_OBJECT_0) { nErrors++; continue; } HRESULT hr; UINT32 samplesCount = 0; // Get the size in samples of the captured audio frame. if (FAILED(hr = this->d->m_pCaptureClient->GetNextPacketSize(&samplesCount))) { this->d->m_error = "GetNextPacketSize: " + errorCodes->value(hr); emit this->errorChanged(this->d->m_error); return QByteArray(); } // Check if empty. if (samplesCount < 1) continue; BYTE *pData = nullptr; DWORD flags = 0; // Read audio buffer. if (FAILED(hr = this->d->m_pCaptureClient->GetBuffer(&pData, &samplesCount, &flags, nullptr, nullptr))) { this->d->m_error = "GetBuffer: " + errorCodes->value(hr); emit this->errorChanged(this->d->m_error); return QByteArray(); } size_t bufferSize = samplesCount * size_t(this->d->m_curCaps.bps() * this->d->m_curCaps.channels()) / 8; // This flag means we must ignore the incoming buffer and write zeros // to it. if (flags & AUDCLNT_BUFFERFLAGS_SILENT) { pData = new BYTE[bufferSize]; memset(pData, 0, bufferSize); } // Copy audio frame to the audio buffer. QByteArray buffer(reinterpret_cast(pData), int(bufferSize)); if (flags & AUDCLNT_BUFFERFLAGS_SILENT) delete [] pData; this->d->m_audioBuffer.append(buffer); // Remove read samples from the audio device. if (FAILED(hr = this->d->m_pCaptureClient->ReleaseBuffer(samplesCount))) { this->d->m_error = "ReleaseBuffer: " + errorCodes->value(hr); emit this->errorChanged(this->d->m_error); return QByteArray(); } } // In case of error and if the buffer is empty, return. if (this->d->m_audioBuffer.isEmpty()) return QByteArray(); QByteArray buffer = this->d->m_audioBuffer.mid(0, bufferSize); this->d->m_audioBuffer.remove(0, bufferSize); return buffer; } bool AudioDevWasapi::write(const AkAudioPacket &packet) { this->d->m_audioBuffer.append(packet.buffer()); int nErrors = 0; while (!this->d->m_audioBuffer.isEmpty() && nErrors < MAX_ERRORS_READ_WRITE) { // Wait until an audio frame can be writen. if (WaitForSingleObject(this->d->m_hEvent, EVENT_TIMEOUT) != WAIT_OBJECT_0) { nErrors++; continue; } HRESULT hr; UINT32 samplesCount; // Get audio buffer size in samples. if (FAILED(hr = this->d->m_pAudioClient->GetBufferSize(&samplesCount))) { this->d->m_error = "GetBufferSize: " + errorCodes->value(hr); emit this->errorChanged(this->d->m_error); return false; } UINT32 numSamplesPadding; // Get the number of samples already present in the audio buffer. if (FAILED(hr = this->d->m_pAudioClient->GetCurrentPadding(&numSamplesPadding))) { this->d->m_error = "GetCurrentPadding: " + errorCodes->value(hr); emit this->errorChanged(this->d->m_error); return false; } // Calculate the difference. This is the number of samples we can write // to the audio buffer. UINT32 availableSamples = samplesCount - numSamplesPadding; // This is probably an impossible but well... check it. if (availableSamples < 1) continue; // Check how many samples we can write to the audio buffer. UINT32 samplesInBuffer = UINT32(this->d->m_audioBuffer.size() * 8 / this->d->m_curCaps.bps() / this->d->m_curCaps.channels()); UINT32 samplesToWrite = qMin(availableSamples, samplesInBuffer); BYTE *pData = nullptr; // Get the audio buffer. if (FAILED(hr = this->d->m_pRenderClient->GetBuffer(samplesToWrite, &pData))) { this->d->m_error = "GetBuffer: " + errorCodes->value(hr); emit this->errorChanged(this->d->m_error); return false; } // Copy the maximum number of audio samples we can write to the audio // buffer. size_t bufferSize = samplesToWrite * size_t(this->d->m_curCaps.bps() * this->d->m_curCaps.channels()) / 8; memcpy(pData, this->d->m_audioBuffer.constData(), bufferSize); this->d->m_audioBuffer.remove(0, int(bufferSize)); // Tell audio device how many samples we had written. if (FAILED(hr = this->d->m_pRenderClient->ReleaseBuffer(samplesToWrite, 0))) { this->d->m_error = "ReleaseBuffer: " + errorCodes->value(hr); emit this->errorChanged(this->d->m_error); return false; } } return true; } bool AudioDevWasapi::uninit() { bool ok = true; HRESULT hr; // Stop audio device. if (this->d->m_pAudioClient && FAILED(hr = this->d->m_pAudioClient->Stop())) { this->d->m_error = "Stop: " + errorCodes->value(hr); emit this->errorChanged(this->d->m_error); ok = false; } // Release interfaces. if (this->d->m_pCaptureClient) { this->d->m_pCaptureClient->Release(); this->d->m_pCaptureClient = nullptr; } if (this->d->m_pRenderClient) { this->d->m_pRenderClient->Release(); this->d->m_pRenderClient = nullptr; } if (this->d->m_pAudioClient) { this->d->m_pAudioClient->Release(); this->d->m_pAudioClient = nullptr; } if (this->d->m_pDevice) { this->d->m_pDevice->Release(); this->d->m_pDevice = nullptr; } if (this->d->m_hEvent) { CloseHandle(this->d->m_hEvent); this->d->m_hEvent = nullptr; } this->d->m_curDevice.clear(); return ok; } HRESULT AudioDevWasapi::QueryInterface(const IID &riid, void **ppvObject) { if (riid == __uuidof(IUnknown) || riid == __uuidof(IMMNotificationClient)) *ppvObject = static_cast(this); else { *ppvObject = nullptr; return E_NOINTERFACE; } this->AddRef(); return S_OK; } ULONG AudioDevWasapi::AddRef() { return InterlockedIncrement(&this->d->m_cRef); } ULONG AudioDevWasapi::Release() { ULONG lRef = InterlockedDecrement(&this->d->m_cRef); if (lRef == 0) delete this; return lRef; } AudioDevWasapiPrivate::AudioDevWasapiPrivate(AudioDevWasapi *self): self(self) { } bool AudioDevWasapiPrivate::waveFormatFromCaps(WAVEFORMATEX *wfx, const AkAudioCaps &caps) const { if (!wfx) return false; wfx->wFormatTag = caps.format() == AkAudioCaps::SampleFormat_flt? WAVE_FORMAT_IEEE_FLOAT: WAVE_FORMAT_PCM; wfx->nChannels = WORD(caps.channels()); wfx->nSamplesPerSec = DWORD(caps.rate()); wfx->wBitsPerSample = WORD(caps.bps()); wfx->nBlockAlign = wfx->nChannels * wfx->wBitsPerSample / 8; wfx->nAvgBytesPerSec = wfx->nSamplesPerSec * wfx->nBlockAlign; wfx->cbSize = 0; return true; } AkAudioCaps AudioDevWasapiPrivate::capsFromWaveFormat(WAVEFORMATEX *wfx) const { if (!wfx) return AkAudioCaps(); auto sampleType = wfx->wFormatTag == WAVE_FORMAT_IEEE_FLOAT? AkAudioCaps::SampleType_float: AkAudioCaps::SampleType_int; auto sampleFormat = AkAudioCaps::sampleFormatFromProperties(sampleType, int(wfx->wBitsPerSample), Q_BYTE_ORDER); return AkAudioCaps(sampleFormat, AkAudioCaps::defaultChannelLayout(int(wfx->nChannels)), int(wfx->nSamplesPerSec)); } void AudioDevWasapiPrivate::fillDeviceInfo(const QString &device, QList *supportedFormats, QList *supportedLayouts, QList *supportedSampleRates) const { if (!this->m_deviceEnumerator) return; IMMDevice *pDevice = nullptr; IAudioClient *pAudioClient = nullptr; // Test if the device is already running, if (this->m_curDevice != device) { // Get audio device. if (FAILED(this->m_deviceEnumerator->GetDevice(device.toStdWString().c_str(), &pDevice))) return; // Get an instance for the audio client. if (FAILED(pDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, reinterpret_cast(&pAudioClient)))) { pDevice->Release(); return; } } else { pDevice = this->m_pDevice; pAudioClient = this->m_pAudioClient; } static const QVector preferredFormats { AkAudioCaps::SampleFormat_flt, AkAudioCaps::SampleFormat_s32, AkAudioCaps::SampleFormat_s16, AkAudioCaps::SampleFormat_u8, }; for (auto &format: preferredFormats) for (int channels = 1; channels < 3; channels++) for (auto &rate: self->commonSampleRates()) { WAVEFORMATEX wfx; WAVEFORMATEX *closestWfx = nullptr; AkAudioCaps audioCaps(format, AkAudioCaps::defaultChannelLayout(channels), rate); this->waveFormatFromCaps(&wfx, audioCaps); if (SUCCEEDED(pAudioClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &wfx, &closestWfx))) { AkAudioCaps::SampleFormat sampleFormat; AkAudioCaps::ChannelLayout layout; int sampleRate; if (closestWfx) { auto sampleType = closestWfx->wFormatTag == WAVE_FORMAT_IEEE_FLOAT? AkAudioCaps::SampleType_float: AkAudioCaps::SampleType_int; sampleFormat = AkAudioCaps::sampleFormatFromProperties(sampleType, int(closestWfx->wBitsPerSample), Q_BYTE_ORDER); layout = AkAudioCaps::defaultChannelLayout(int(closestWfx->nChannels)); sampleRate = int(closestWfx->nSamplesPerSec); CoTaskMemFree(closestWfx); } else { sampleFormat = format; layout = AkAudioCaps::defaultChannelLayout(channels); sampleRate = rate; } if (!supportedFormats->contains(sampleFormat)) supportedFormats->append(sampleFormat); if (layout != AkAudioCaps::Layout_none && !supportedLayouts->contains(layout)) supportedLayouts->append(layout); if (!supportedSampleRates->contains(sampleRate)) supportedSampleRates->append(sampleRate); } } if (this->m_curDevice != device) { pAudioClient->Release(); pDevice->Release(); } std::sort(supportedFormats->begin(), supportedFormats->end()); } AkAudioCaps AudioDevWasapiPrivate::preferredCaps(const QString &device, EDataFlow dataFlow) const { if (!this->m_deviceEnumerator) return AkAudioCaps(); IMMDevice *pDevice = nullptr; IAudioClient *pAudioClient = nullptr; // Test if the device is already running, if (this->m_curDevice != device) { // Get audio device. if (FAILED(this->m_deviceEnumerator->GetDevice(device.toStdWString().c_str(), &pDevice))) return AkAudioCaps(); // Get an instance for the audio client. if (FAILED(pDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, reinterpret_cast(&pAudioClient)))) { pDevice->Release(); return AkAudioCaps(); } } else { pDevice = this->m_pDevice; pAudioClient = this->m_pAudioClient; } AkAudioCaps caps = dataFlow == eCapture? AkAudioCaps(AkAudioCaps::SampleFormat_u8, AkAudioCaps::Layout_mono, 8000): AkAudioCaps(AkAudioCaps::SampleFormat_s16, AkAudioCaps::Layout_stereo, 44100); WAVEFORMATEX wfx; WAVEFORMATEX *closestWfx = nullptr; this->waveFormatFromCaps(&wfx, caps); if (SUCCEEDED(pAudioClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &wfx, &closestWfx))) { if (closestWfx) { caps = this->capsFromWaveFormat(closestWfx); CoTaskMemFree(closestWfx); } } if (this->m_curDevice != device) { pAudioClient->Release(); pDevice->Release(); } return caps; } HRESULT AudioDevWasapi::OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) { Q_UNUSED(pwstrDeviceId) Q_UNUSED(dwNewState) return S_OK; } HRESULT AudioDevWasapi::OnDeviceAdded(LPCWSTR pwstrDeviceId) { Q_UNUSED(pwstrDeviceId) // Device was installed return S_OK; } HRESULT AudioDevWasapi::OnDeviceRemoved(LPCWSTR pwstrDeviceId) { Q_UNUSED(pwstrDeviceId) // Device was uninstalled return S_OK; } HRESULT AudioDevWasapi::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId) { if (role != eMultimedia) return S_OK; QString deviceId = QString::fromWCharArray(pwstrDeviceId); if (flow == eCapture) { this->d->m_defaultSource = deviceId; emit this->defaultInputChanged(deviceId); } else if (flow == eRender) { this->d->m_defaultSink = deviceId; emit this->defaultOutputChanged(deviceId); } return S_OK; } HRESULT AudioDevWasapi::OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key) { Q_UNUSED(pwstrDeviceId) Q_UNUSED(key) this->updateDevices(); return S_OK; } void AudioDevWasapi::updateDevices() { if (!this->d->m_deviceEnumerator) { this->d->m_error = "Device enumerator not created."; emit this->errorChanged(this->d->m_error); return; } decltype(this->d->m_sources) inputs; decltype(this->d->m_sinks) outputs; decltype(this->d->m_defaultSink) defaultSink; decltype(this->d->m_defaultSource) defaultSource; decltype(this->d->m_descriptionMap) descriptionMap; decltype(this->d->m_supportedFormats) supportedFormats; decltype(this->d->m_supportedLayouts) supportedChannels; decltype(this->d->m_supportedSampleRates) supportedSampleRates; decltype(this->d->m_preferredInputCaps) preferredInputCaps; decltype(this->d->m_preferredOutputCaps) preferredOutputCaps; for (auto &dataFlow: QVector {eCapture, eRender}) { HRESULT hr; IMMDevice *defaultDevice = nullptr; if (SUCCEEDED(hr = this->d->m_deviceEnumerator->GetDefaultAudioEndpoint(dataFlow, eMultimedia, &defaultDevice))) { LPWSTR deviceId; if (SUCCEEDED(hr = defaultDevice->GetId(&deviceId))) { if (dataFlow == eCapture) defaultSource = QString::fromWCharArray(deviceId); else defaultSink = QString::fromWCharArray(deviceId); CoTaskMemFree(deviceId); } defaultDevice->Release(); } IMMDeviceCollection *endPoints = nullptr; if (SUCCEEDED(hr = this->d->m_deviceEnumerator->EnumAudioEndpoints(dataFlow, eMultimedia, &endPoints))) { UINT nDevices = 0; if (SUCCEEDED(hr = endPoints->GetCount(&nDevices))) for (UINT i = 0; i < nDevices; i++) { IMMDevice *device = nullptr; if (SUCCEEDED(endPoints->Item(i, &device))) { LPWSTR deviceId; if (SUCCEEDED(hr = device->GetId(&deviceId))) { IPropertyStore *properties = nullptr; if (SUCCEEDED(hr = device->OpenPropertyStore(STGM_READ, &properties))) { PROPVARIANT friendlyName; PropVariantInit(&friendlyName); if (SUCCEEDED(hr = properties->GetValue(PKEY_Device_FriendlyName, &friendlyName))) { auto devId = QString::fromWCharArray(deviceId); QList _supportedFormats; QList _supportedLayouts; QList _supportedSampleRates; this->d->fillDeviceInfo(devId, &_supportedFormats, &_supportedLayouts, &_supportedSampleRates); if (_supportedFormats.isEmpty()) _supportedFormats = this->d->m_supportedFormats.value(devId); if (_supportedLayouts.isEmpty()) _supportedLayouts = this->d->m_supportedLayouts.value(devId); if (_supportedSampleRates.isEmpty()) _supportedSampleRates = this->d->m_supportedSampleRates.value(devId); if (!_supportedFormats.isEmpty() && !_supportedLayouts.isEmpty() && !_supportedSampleRates.isEmpty()) { if (dataFlow == eCapture) { inputs << devId; preferredInputCaps[devId] = this->d->preferredCaps(devId, dataFlow); } else { outputs << devId; preferredOutputCaps[devId] = this->d->preferredCaps(devId, dataFlow); } descriptionMap[devId] = QString::fromWCharArray(friendlyName.pwszVal); supportedFormats[devId] = _supportedFormats; supportedChannels[devId] = _supportedLayouts; supportedSampleRates[devId] = _supportedSampleRates; } PropVariantClear(&friendlyName); } properties->Release(); } CoTaskMemFree(deviceId); } device->Release(); } } endPoints->Release(); } } if (this->d->m_supportedFormats != supportedFormats) this->d->m_supportedFormats = supportedFormats; if (this->d->m_supportedLayouts != supportedChannels) this->d->m_supportedLayouts = supportedChannels; if (this->d->m_supportedSampleRates != supportedSampleRates) this->d->m_supportedSampleRates = supportedSampleRates; if (this->d->m_descriptionMap != descriptionMap) this->d->m_descriptionMap = descriptionMap; this->d->m_preferredInputCaps = preferredInputCaps; this->d->m_preferredOutputCaps = preferredOutputCaps; if (this->d->m_sources != inputs) { this->d->m_sources = inputs; emit this->inputsChanged(inputs); } if (this->d->m_sinks != outputs) { this->d->m_sinks = outputs; emit this->outputsChanged(outputs); } if (defaultSource.isEmpty() && !inputs.isEmpty()) defaultSource = inputs.first(); if (defaultSink.isEmpty() && !outputs.isEmpty()) defaultSink = outputs.first(); if (this->d->m_defaultSource != defaultSource) { this->d->m_defaultSource = defaultSource; emit this->defaultInputChanged(defaultSource); } if (this->d->m_defaultSink != defaultSink) { this->d->m_defaultSink = defaultSink; emit this->defaultOutputChanged(defaultSink); } } #include "moc_audiodevwasapi.cpp"