Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwindowsaudiosink.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
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>
11
12#include <audioclient.h>
13#include <mmdeviceapi.h>
14
16
17namespace QtWASAPI {
18
20using namespace std::chrono_literals;
21
22namespace {
23
24QAudioFormat makeHostFormatForSink(const QAudioDevice &device, const QAudioFormat &format)
25{
26 const QWindowsAudioDevice *winDevice = QAudioDevicePrivate::handle<QWindowsAudioDevice>(device);
27
28 auto status = winDevice->m_probeDataFuture.wait_for(QAudioDevicePrivate::formatProbeTimeout);
29 switch (status) {
30 case std::future_status::ready:
31 case std::future_status::deferred:
32 break;
33 case std::future_status::timeout:
34 return QAudioFormat{};
35 default:
36 Q_UNREACHABLE();
37 }
38
39 auto [minProbedChannels, maxProbedChannels] = winDevice->m_probeDataFuture.get().channelCountRange;
40
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));
51 }
52
53 return hostFormat;
54}
55
56} // namespace
57
58QWASAPIAudioSinkStream::QWASAPIAudioSinkStream(QAudioDevice device, const QAudioFormat &format, std::optional<qsizetype> ringbufferSize,
59 QWindowsAudioSink *parent, float volume, std::optional<int32_t> hardwareBufferFrames, AudioEndpointRole role):
61 std::move(device),
62 format,
65 volume,
66 },
67 m_role{
68 role,
69 },
71 CreateEvent(0, false, false, nullptr),
72 },
73 m_parent{
74 parent
75 },
78 }
79{
80}
81
83{
84 return true;
85}
86
87bool QWASAPIAudioSinkStream::start(QIODevice *ioDevice)
88{
89 auto immDevice = QAudioDevicePrivate::handle<QWindowsAudioDevice>(m_audioDevice)->open();
90 if (!immDevice)
91 return false;
92
93 bool clientOpen = openAudioClient(std::move(immDevice), m_role);
94 if (!clientOpen)
95 return false;
96
97 setQIODevice(ioDevice);
98 createQIODeviceConnections(ioDevice);
99 pullFromQIODevice();
100
101 return startAudioClient(StreamType::Ringbuffer);
102}
103
105{
106 auto immDevice = QAudioDevicePrivate::handle<QWindowsAudioDevice>(m_audioDevice)->open();
107 if (!immDevice)
108 return nullptr;
109
110 bool clientOpen = openAudioClient(std::move(immDevice), m_role);
111 if (!clientOpen)
112 return nullptr;
113
114 QIODevice *ioDevice = createRingbufferWriterDevice();
115
116 m_parent->updateStreamIdle(true, QWindowsAudioSink::EmitStateSignal::False);
117
118 setQIODevice(ioDevice);
119 createQIODeviceConnections(ioDevice);
120
121 bool started = startAudioClient(StreamType::Ringbuffer);
122 return started ? ioDevice : nullptr;
123}
124
125bool QWASAPIAudioSinkStream::start(AudioCallback audioCallback)
126{
127 auto immDevice = QAudioDevicePrivate::handle<QWindowsAudioDevice>(m_audioDevice)->open();
128 if (!immDevice)
129 return false;
130
131 bool clientOpen = openAudioClient(std::move(immDevice), m_role);
132 if (!clientOpen)
133 return false;
134
135 m_audioCallback = std::move(audioCallback);
136
137 return startAudioClient(StreamType::Callback);
138}
139
141{
142 m_suspended = true;
143 QWindowsAudioUtils::audioClientStop(m_audioClient);
144}
145
147{
148 m_suspended = false;
149 QWindowsAudioUtils::audioClientStart(m_audioClient);
150}
151
152void QWASAPIAudioSinkStream::stop(ShutdownPolicy shutdownPolicy)
153{
154 using namespace QWindowsAudioUtils;
155
156 m_parent = nullptr;
157 m_shutdownPolicy = shutdownPolicy;
158
159 requestStop();
160 switch (shutdownPolicy) {
161 case ShutdownPolicy::DiscardRingbuffer: {
162 audioClientStop(m_audioClient);
163 joinWorkerThread();
164 audioClientReset(m_audioClient);
165
166 return;
167 }
168 case ShutdownPolicy::DrainRingbuffer: {
169 m_ringbufferDrained.callOnActivated([self = shared_from_this()]() mutable {
170 self->joinWorkerThread();
171 self = {};
172 });
173 return;
174 }
175 default:
176 Q_UNREACHABLE_RETURN();
177 }
178}
179
181{
182 if (m_parent)
183 m_parent->updateStreamIdle(streamIsIdle);
184}
185
186bool QWASAPIAudioSinkStream::openAudioClient(ComPtr<IMMDevice> device, AudioEndpointRole role)
187{
188 using namespace QWindowsAudioUtils;
189
190 std::optional<AudioClientCreationResult> clientData =
191 createAudioClient(device, m_hostFormat, m_hardwareBufferFrames, m_wasapiHandle, role);
192
193 if (!clientData)
194 return false;
195
196 m_audioClient = std::move(clientData->client);
197 m_periodSize = clientData->periodSize;
198 m_audioClientFrames = clientData->audioClientFrames;
199
200 HRESULT hr = m_audioClient->GetService(IID_PPV_ARGS(m_renderClient.GetAddressOf()));
201 if (FAILED(hr)) {
202 qWarning() << "IAudioClient3::GetService failed to obtain IAudioRenderClient"
203 << audioClientErrorString(hr);
204 return false;
205 }
206
207 if (m_audioDevice.preferredFormat().sampleRate() != m_hostFormat.sampleRate())
208 audioClientSetRate(m_audioClient, m_hostFormat.sampleRate());
209
210 return true;
211}
212
213bool QWASAPIAudioSinkStream::startAudioClient(StreamType streamType)
214{
215 using namespace QWindowsAudioUtils;
216 m_workerThread.reset(QThread::create([this, streamType] {
217 setMCSSForPeriodSize(m_periodSize);
218 fillInitialHostBuffer(streamType);
219 std::optional<QComHelper> m_comHelper;
220
221 if (m_hostFormat != m_format) {
222 m_comHelper.emplace();
223 m_resampler = std::make_unique<QWindowsResampler>();
224 m_resampler->setup(m_format, m_hostFormat);
225
226 m_memoryResource = std::make_unique<QTlsfMemoryResource>(512 * 1024);
227 }
228
229 switch (streamType) {
230 case StreamType::Ringbuffer:
231 return runProcessRingbufferLoop();
232 case StreamType::Callback:
233 return runProcessCallbackLoop();
234 }
235 }));
236 m_workerThread->setObjectName(u"QWASAPIAudioSinkStream");
237 m_workerThread->start();
238
239 bool started = QWindowsAudioUtils::audioClientStart(m_audioClient);
240 if (!started) {
241 joinWorkerThread();
242 return false;
243 }
244 return true;
245}
246
247void QWASAPIAudioSinkStream::fillInitialHostBuffer(StreamType streamType)
248{
249 switch (streamType) {
251 processRingbuffer();
252 return;
253 }
255 processCallback();
256 return;
257 }
258 default:
259 Q_UNREACHABLE_RETURN();
260 }
261}
262
263void QWASAPIAudioSinkStream::runProcessRingbufferLoop()
264{
265 using namespace QWindowsAudioUtils;
266
267 for (;;) {
268 constexpr std::chrono::milliseconds timeout = 2s;
269 DWORD retval = WaitForSingleObject(m_wasapiHandle.get(), timeout.count());
270 if (retval != WAIT_OBJECT_0) {
271 if (m_suspended)
272 continue;
273
274 handleAudioClientError();
275 return;
276 }
277
278 if (isStopRequested()) {
279 switch (m_shutdownPolicy.load(std::memory_order_relaxed)) {
280 case ShutdownPolicy::DiscardRingbuffer:
281 return;
282 case ShutdownPolicy::DrainRingbuffer: {
283 bool bufferDrained = visitRingbuffer([](const auto &ringbuffer) {
284 return ringbuffer.used() == 0;
285 });
286 if (bufferDrained) {
287 audioClientStop(m_audioClient);
288 audioClientReset(m_audioClient);
289
290 m_ringbufferDrained.set();
291 return;
292 }
293 break;
294 }
295 default:
296 Q_UNREACHABLE_RETURN();
297 }
298 }
299
300 bool success = processRingbuffer();
301 if (!success) {
302 handleAudioClientError();
303 return;
304 }
305 }
306}
307
308void QWASAPIAudioSinkStream::runProcessCallbackLoop()
309{
310 using namespace QWindowsAudioUtils;
311
312 for (;;) {
313 constexpr std::chrono::milliseconds timeout = 2s;
314 DWORD retval = WaitForSingleObject(m_wasapiHandle.get(), timeout.count());
315 if (retval != WAIT_OBJECT_0) {
316 if (m_suspended)
317 continue;
318
319 handleAudioClientError();
320 return;
321 }
322
323 if (isStopRequested())
324 return;
325
326 bool success = processCallback();
327 if (!success) {
328 handleAudioClientError();
329 return;
330 }
331 }
332}
333
334template <typename Functor>
335bool QWASAPIAudioSinkStream::visitAudioClientBuffer(Functor &&f)
336{
337 uint32_t numFramesPadding;
338 HRESULT hr = m_audioClient->GetCurrentPadding(&numFramesPadding);
339 if (FAILED(hr)) {
340 qWarning() << "IAudioClient3::GetCurrentPadding failed" << audioClientErrorString(hr);
341 return false;
342 }
343
344 const uint32_t requiredFrames = m_audioClientFrames - numFramesPadding;
345 if (requiredFrames == 0)
346 return true;
347
348 // Grab the next empty buffer from the audio device.
349 unsigned char *hostBuffer{};
350 hr = m_renderClient->GetBuffer(requiredFrames, &hostBuffer);
351 if (FAILED(hr)) {
352 qWarning() << "IAudioRenderClient::getBuffer failed" << audioClientErrorString(hr);
353 return false;
354 }
355
356 QSpan<std::byte> hostBufferSpan{
357 reinterpret_cast<std::byte *>(hostBuffer),
358 m_hostFormat.bytesForFrames(requiredFrames),
359 };
360
361 uint64_t consumedFrames;
362 if (m_resampler) {
363 Q_UNLIKELY_BRANCH;
364
365 std::pmr::vector<std::byte> resampleBuffer{
366 size_t(m_format.bytesForFrames(requiredFrames)),
367 m_memoryResource.get(),
368 };
369 consumedFrames = f(as_writable_bytes(QSpan{ resampleBuffer }), requiredFrames);
370
371 auto resampledBuffer = m_resampler->resample(resampleBuffer, m_memoryResource.get());
372
373 Q_ASSERT(resampledBuffer.size() == size_t(hostBufferSpan.size()));
374 std::copy_n(resampledBuffer.data(), resampledBuffer.size(), hostBufferSpan.data());
375 } else {
376 consumedFrames = f(hostBufferSpan, requiredFrames);
377 }
378
379 const DWORD flags = consumedFrames != 0 ? 0 : AUDCLNT_BUFFERFLAGS_SILENT;
380
381 hr = m_renderClient->ReleaseBuffer(requiredFrames, flags);
382 if (FAILED(hr)) {
383 qWarning() << "IAudioRenderClient::ReleaseBuffer failed" << audioClientErrorString(hr);
384 return false;
385 }
386
387 return true;
388}
389
391{
394 return consumedFrames;
395 });
396}
397
399{
402 return requiredFrames;
403 });
404}
405
407{
408 requestStop();
409 ::SetEvent(m_wasapiHandle.get()); // force wakeup
411 m_workerThread = {};
412}
413
415{
416 using namespace QWindowsAudioUtils;
417 requestStop();
420
421 invokeOnAppThread([this] {
423 });
424}
425
431
433 = default;
434
435} // namespace QtWASAPI
436
437QT_END_NAMESPACE
Combined button and popup list for selecting options.
QWASAPIAudioSinkStream(QAudioDevice, const QAudioFormat &, std::optional< qsizetype > ringbufferSize, QWindowsAudioSink *parent, float volume, std::optional< int32_t > hardwareBufferSize, AudioEndpointRole)
QAudioFormat::SampleFormat SampleFormat