6#include <QtCore/private/qsystemerror_p.h>
7#include <QtCore/private/qfunctions_win_p.h>
8#include <QtMultimedia/private/qmemory_resource_tlsf_p.h>
9#include <QtMultimedia/private/qwindowsaudiodevice_p.h>
10#include <QtMultimedia/private/qwindowsresampler_p.h>
12#include <audioclient.h>
13#include <mmdeviceapi.h>
20using namespace std::chrono_literals;
24QAudioFormat makeHostFormatForSink(
const QAudioDevice &device,
const QAudioFormat &format)
26 const QWindowsAudioDevice *winDevice = QAudioDevicePrivate::handle<QWindowsAudioDevice>(device);
28 auto status = winDevice->m_probeDataFuture.wait_for(QAudioDevicePrivate::formatProbeTimeout);
30 case std::future_status::ready:
31 case std::future_status::deferred:
33 case std::future_status::timeout:
34 return QAudioFormat{};
39 auto [minProbedChannels, maxProbedChannels] = winDevice->m_probeDataFuture.get().channelCountRange;
41 QAudioFormat hostFormat = format;
42 const int requestedChannelCount = format.channelCount();
43 if (requestedChannelCount < device.minimumChannelCount()) {
44 hostFormat.setChannelCount(minProbedChannels);
45 hostFormat.setChannelConfig(
46 QAudioFormat::defaultChannelConfigForChannelCount(minProbedChannels));
47 }
else if (requestedChannelCount > device.maximumChannelCount()) {
48 hostFormat.setChannelCount(maxProbedChannels);
49 hostFormat.setChannelConfig(
50 QAudioFormat::defaultChannelConfigForChannelCount(maxProbedChannels));
59 std::optional<qsizetype> ringbufferSize,
60 QWindowsAudioSink *parent,
float volume,
61 std::optional<NativePeriodFrames> nativePeriodFrames,
62 AudioEndpointRole role)
86 auto immDevice = QAudioDevicePrivate::handle<QWindowsAudioDevice>(m_audioDevice)->open();
90 bool clientOpen = openAudioClient(std::move(immDevice), m_role);
94 setQIODevice(ioDevice);
95 createQIODeviceConnections(ioDevice);
103 auto immDevice = QAudioDevicePrivate::handle<QWindowsAudioDevice>(m_audioDevice)->open();
107 bool clientOpen = openAudioClient(std::move(immDevice), m_role);
111 QIODevice *ioDevice = createRingbufferWriterDevice();
113 m_parent->updateStreamIdle(
true, QWindowsAudioSink::EmitStateSignal::False);
115 setQIODevice(ioDevice);
116 createQIODeviceConnections(ioDevice);
119 return started ? ioDevice :
nullptr;
124 auto immDevice = QAudioDevicePrivate::handle<QWindowsAudioDevice>(m_audioDevice)->open();
128 bool clientOpen = openAudioClient(std::move(immDevice), m_role);
132 m_audioCallback = std::move(audioCallback);
140 QWindowsAudioUtils::audioClientStop(m_audioClient);
146 QWindowsAudioUtils::audioClientStart(m_audioClient);
151 using namespace QWindowsAudioUtils;
154 m_shutdownPolicy = shutdownPolicy;
157 switch (shutdownPolicy) {
158 case ShutdownPolicy::DiscardRingbuffer: {
159 audioClientStop(m_audioClient);
161 audioClientReset(m_audioClient);
165 case ShutdownPolicy::DrainRingbuffer: {
166 m_ringbufferDrained.callOnActivated([self = shared_from_this()]()
mutable {
167 self->joinWorkerThread();
173 Q_UNREACHABLE_RETURN();
180 m_parent->updateStreamIdle(streamIsIdle);
185 using namespace QWindowsAudioUtils;
187 std::optional<AudioClientCreationResult> clientData = createAudioClient(
188 device, m_hostFormat, m_nativePeriodFrames, m_audioClientEventHandle, role);
193 m_audioClient = std::move(clientData->client);
194 m_periodSize = clientData->periodSize;
195 m_audioClientFrames = clientData->audioClientFrames;
197 HRESULT hr = m_audioClient->GetService(IID_PPV_ARGS(m_renderClient.GetAddressOf()));
199 qWarning() <<
"IAudioClient3::GetService failed to obtain IAudioRenderClient"
200 << audioClientErrorString(hr);
204 if (m_audioDevice.preferredFormat().sampleRate() != m_hostFormat.sampleRate())
205 audioClientSetRate(m_audioClient, m_hostFormat.sampleRate());
212 using namespace QWindowsAudioUtils;
213 m_workerThread.reset(QThread::create([
this, streamType] {
214 const auto mmcssRegistration = registerMMCSSThreadForPeriod(m_periodSize);
215 fillInitialHostBuffer(streamType);
216 std::optional<QComHelper> m_comHelper;
218 if (m_hostFormat != m_format) {
219 m_comHelper.emplace();
220 m_resampler = std::make_unique<QWindowsResampler>();
221 m_resampler->setup(m_format, m_hostFormat);
223 m_memoryResource = std::make_unique<QTlsfMemoryResource>(512 * 1024);
226 switch (streamType) {
227 case StreamType::Ringbuffer:
228 return runProcessRingbufferLoop();
229 case StreamType::Callback:
230 return runProcessCallbackLoop();
233 m_workerThread->setObjectName(u"QWASAPIAudioSinkStream");
234 m_workerThread->start();
236 bool started = QWindowsAudioUtils::audioClientStart(m_audioClient);
246 switch (streamType) {
256 Q_UNREACHABLE_RETURN();
262 using namespace QWindowsAudioUtils;
265 constexpr std::chrono::milliseconds timeout = 2s;
266 const bool wasSuspended = m_suspended;
267 DWORD retval = WaitForSingleObject(m_audioClientEventHandle.get(), timeout.count());
268 if (retval != WAIT_OBJECT_0) {
269 if (wasSuspended || m_suspended)
272 handleAudioClientError();
276 if (isStopRequested()) {
277 switch (m_shutdownPolicy.load(std::memory_order_relaxed)) {
278 case ShutdownPolicy::DiscardRingbuffer:
280 case ShutdownPolicy::DrainRingbuffer: {
281 bool bufferDrained = visitRingbuffer([](
const auto &ringbuffer) {
282 return ringbuffer.used() == 0;
285 audioClientStop(m_audioClient);
286 audioClientReset(m_audioClient);
288 m_ringbufferDrained.set();
294 Q_UNREACHABLE_RETURN();
298 bool success = processRingbuffer();
300 handleAudioClientError();
308 using namespace QWindowsAudioUtils;
311 constexpr std::chrono::milliseconds timeout = 2s;
312 const bool wasSuspended = m_suspended;
313 DWORD retval = WaitForSingleObject(m_audioClientEventHandle.get(), timeout.count());
314 if (retval != WAIT_OBJECT_0) {
315 if (wasSuspended || m_suspended)
318 handleAudioClientError();
322 if (isStopRequested())
325 bool success = processCallback();
327 handleAudioClientError();
333template <
typename Functor>
336 uint32_t numFramesPadding;
337 HRESULT hr = m_audioClient->GetCurrentPadding(&numFramesPadding);
339 qWarning() <<
"IAudioClient3::GetCurrentPadding failed" << audioClientErrorString(hr);
343 const uint32_t requiredFrames = m_audioClientFrames - numFramesPadding;
344 if (requiredFrames == 0)
348 unsigned char *hostBuffer{};
349 hr = m_renderClient->GetBuffer(requiredFrames, &hostBuffer);
351 qWarning() <<
"IAudioRenderClient::getBuffer failed" << audioClientErrorString(hr);
355 QSpan<std::byte> hostBufferSpan{
356 reinterpret_cast<std::byte *>(hostBuffer),
357 m_hostFormat.bytesForFrames(requiredFrames),
360 uint64_t consumedFrames;
364 std::pmr::vector<std::byte> resampleBuffer{
365 size_t(m_format.bytesForFrames(requiredFrames)),
366 m_memoryResource.get(),
368 consumedFrames = f(as_writable_bytes(QSpan{ resampleBuffer }), requiredFrames);
370 auto resampledBuffer = m_resampler->resample(resampleBuffer, m_memoryResource.get());
372 Q_ASSERT(resampledBuffer.size() == size_t(hostBufferSpan.size()));
373 std::copy_n(resampledBuffer.data(), resampledBuffer.size(), hostBufferSpan.data());
375 consumedFrames = f(hostBufferSpan, requiredFrames);
378 const DWORD flags = consumedFrames != 0 ? 0 : AUDCLNT_BUFFERFLAGS_SILENT;
380 hr = m_renderClient->ReleaseBuffer(requiredFrames, flags);
382 qWarning() <<
"IAudioRenderClient::ReleaseBuffer failed" << audioClientErrorString(hr);
Combined button and popup list for selecting options.
void updateStreamIdle(bool) override
QWASAPIAudioSinkStream(QAudioDevice, const QAudioFormat &, std::optional< qsizetype > ringbufferSize, QWindowsAudioSink *parent, float volume, std::optional< NativePeriodFrames > nativePeriodFrames, AudioEndpointRole)
QAudioFormat::SampleFormat SampleFormat
bool start(AudioCallback)
void stop(ShutdownPolicy)