// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qwindowsaudiosource_p.h" #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE namespace QtWASAPI { using QWindowsAudioUtils::audioClientErrorString; using namespace std::chrono_literals; QWASAPIAudioSourceStream::QWASAPIAudioSourceStream(QAudioDevice device, const QAudioFormat &format, std::optional ringbufferSize, QWindowsAudioSource *parent, float volume, std::optional hardwareBufferFrames): QPlatformAudioSourceStream{ std::move(device), format, ringbufferSize, hardwareBufferFrames, volume, }, m_wasapiHandle { CreateEvent(0, false, false, nullptr), }, m_parent{ parent } { } QWASAPIAudioSourceStream::~QWASAPIAudioSourceStream() = default; bool QWASAPIAudioSourceStream::start(QIODevice *ioDevice) { auto immDevice = QAudioDevicePrivate::handle(m_audioDevice)->open(); bool clientOpen = openAudioClient(std::move(immDevice)); if (!clientOpen) return false; setQIODevice(ioDevice); createQIODeviceConnections(ioDevice); bool started = startAudioClient(); if (!started) return false; return true; } QIODevice *QWASAPIAudioSourceStream::start() { auto immDevice = QAudioDevicePrivate::handle(m_audioDevice)->open(); bool clientOpen = openAudioClient(std::move(immDevice)); if (!clientOpen) return nullptr; QIODevice *ioDevice = createRingbufferReaderDevice(); m_parent->updateStreamIdle(true, QWindowsAudioSource::EmitStateSignal::False); setQIODevice(ioDevice); createQIODeviceConnections(ioDevice); bool started = startAudioClient(); if (!started) return nullptr; return ioDevice; } void QWASAPIAudioSourceStream::suspend() { m_suspended = true; QWindowsAudioUtils::audioClientStop(m_audioClient); } void QWASAPIAudioSourceStream::resume() { m_suspended = false; QWindowsAudioUtils::audioClientStart(m_audioClient); } void QWASAPIAudioSourceStream::stop(ShutdownPolicy shutdownPolicy) { m_parent = nullptr; m_shutdownPolicy = shutdownPolicy; requestStop(); disconnectQIODeviceConnections(); QWindowsAudioUtils::audioClientStop(m_audioClient); m_workerThread->wait(); QWindowsAudioUtils::audioClientReset(m_audioClient); finalizeQIODevice(shutdownPolicy); if (shutdownPolicy == ShutdownPolicy::DiscardRingbuffer) emptyRingbuffer(); } void QWASAPIAudioSourceStream::updateStreamIdle(bool streamIsIdle) { if (m_parent) m_parent->updateStreamIdle(streamIsIdle); } bool QWASAPIAudioSourceStream::openAudioClient(ComPtr device) { using namespace QWindowsAudioUtils; std::optional clientData = createAudioClient(device, m_format, m_hardwareBufferFrames, m_wasapiHandle); if (!clientData) return false; m_audioClient = std::move(clientData->client); m_periodSize = clientData->periodSize; m_audioClientFrames = clientData->audioClientFrames; HRESULT hr = m_audioClient->GetService(IID_PPV_ARGS(m_captureClient.GetAddressOf())); if (FAILED(hr)) { qWarning() << "IAudioClient3::GetService failed to obtain IAudioCaptureClient" << audioClientErrorString(hr); return false; } if (m_audioDevice.preferredFormat().sampleRate() != m_format.sampleRate()) audioClientSetRate(m_audioClient, m_format.sampleRate()); return true; } bool QWASAPIAudioSourceStream::startAudioClient() { using namespace QWindowsAudioUtils; m_workerThread.reset(QThread::create([this] { setMCSSForPeriodSize(m_periodSize); runProcessLoop(); })); m_workerThread->setObjectName(u"QWASAPIAudioSourceStream"); m_workerThread->start(); return audioClientStart(m_audioClient); } void QWASAPIAudioSourceStream::runProcessLoop() { using namespace QWindowsAudioUtils; for (;;) { constexpr std::chrono::milliseconds timeout = 2s; DWORD retval = WaitForSingleObject(m_wasapiHandle.get(), timeout.count()); if (retval != WAIT_OBJECT_0) { if (m_suspended) continue; handleAudioClientError(); return; } if (isStopRequested()) return; // TODO: distinguish between stop/reset? bool success = process(); if (!success) { handleAudioClientError(); return; } } } bool QWASAPIAudioSourceStream::process() noexcept QT_MM_NONBLOCKING { for (;;) { unsigned char *hostBuffer; uint32_t hostBufferFrames; DWORD flags; uint64_t devicePosition; uint64_t QPCPosition; HRESULT hr = m_captureClient->GetBuffer(&hostBuffer, &hostBufferFrames, &flags, &devicePosition, &QPCPosition); if (FAILED(hr)) { qWarning() << "IAudioCaptureClient::GetBuffer failed" << audioClientErrorString(hr); return false; } QSpan hostBufferSpan{ hostBuffer, m_format.bytesForFrames(hostBufferFrames), }; uint64_t framesWritten = QPlatformAudioSourceStream::process(as_bytes(hostBufferSpan), hostBufferFrames); if (framesWritten != hostBufferFrames) updateStreamIdle(true); hr = m_captureClient->ReleaseBuffer(hostBufferFrames); if (FAILED(hr)) { qWarning() << "IAudioCaptureClient::ReleaseBuffer failed" << audioClientErrorString(hr); return false; } uint32_t framesInNextPacket; hr = m_captureClient->GetNextPacketSize(&framesInNextPacket); if (FAILED(hr)) { qWarning() << "IAudioCaptureClient::GetNextPacketSize failed" << audioClientErrorString(hr); return false; } if (framesInNextPacket == 0) return true; } } void QWASAPIAudioSourceStream::handleAudioClientError() { using namespace QWindowsAudioUtils; audioClientStop(m_audioClient); audioClientReset(m_audioClient); QMetaObject::invokeMethod(&m_ringbufferDrained, [this] { handleIOError(m_parent); }); } /////////////////////////////////////////////////////////////////////////////////////////////////// QWindowsAudioSource::QWindowsAudioSource(QAudioDevice audioDevice, const QAudioFormat &fmt, QObject *parent) : BaseClass(std::move(audioDevice), fmt, parent) { } } // namespace QtWASAPI QT_END_NAMESPACE