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 QAudioFormat hostFormat = format;
29 const int requestedChannelCount = format.channelCount();
30 auto [minProbedChannels, maxProbedChannels] = winDevice->m_probedChannelCountRange;
31
32 if (requestedChannelCount < device.minimumChannelCount()) {
33 hostFormat.setChannelCount(minProbedChannels);
34 hostFormat.setChannelConfig(
35 QAudioFormat::defaultChannelConfigForChannelCount(minProbedChannels));
36 } else if (requestedChannelCount > device.maximumChannelCount()) {
37 hostFormat.setChannelCount(maxProbedChannels);
38 hostFormat.setChannelConfig(
39 QAudioFormat::defaultChannelConfigForChannelCount(maxProbedChannels));
40 }
41
42 return hostFormat;
43}
44
45} // namespace
46
47QWASAPIAudioSinkStream::QWASAPIAudioSinkStream(QAudioDevice device, const QAudioFormat &format, std::optional<qsizetype> ringbufferSize,
48 QWindowsAudioSink *parent, float volume, std::optional<int32_t> hardwareBufferFrames, AudioEndpointRole role):
50 std::move(device),
51 format,
54 volume,
55 },
56 m_role{
57 role,
58 },
60 CreateEvent(0, false, false, nullptr),
61 },
62 m_parent{
63 parent
64 },
67 }
68{
69}
70
72{
73 return true;
74}
75
76bool QWASAPIAudioSinkStream::start(QIODevice *ioDevice)
77{
78 auto immDevice = QAudioDevicePrivate::handle<QWindowsAudioDevice>(m_audioDevice)->open();
79 bool clientOpen = openAudioClient(std::move(immDevice), m_role);
80 if (!clientOpen)
81 return false;
82
83 setQIODevice(ioDevice);
84 createQIODeviceConnections(ioDevice);
85 pullFromQIODevice();
86
87 bool started = startAudioClient(StreamType::Ringbuffer);
88 if (!started)
89 return false;
90
91 return true;
92}
93
95{
96 auto immDevice = QAudioDevicePrivate::handle<QWindowsAudioDevice>(m_audioDevice)->open();
97 bool clientOpen = openAudioClient(std::move(immDevice), m_role);
98 if (!clientOpen)
99 return nullptr;
100
101 QIODevice *ioDevice = createRingbufferWriterDevice();
102
103 m_parent->updateStreamIdle(true, QWindowsAudioSink::EmitStateSignal::False);
104
105 setQIODevice(ioDevice);
106 createQIODeviceConnections(ioDevice);
107
108 bool started = startAudioClient(StreamType::Ringbuffer);
109 if (!started)
110 return nullptr;
111
112 return ioDevice;
113}
114
115bool QWASAPIAudioSinkStream::start(AudioCallback audioCallback)
116{
117 auto immDevice = QAudioDevicePrivate::handle<QWindowsAudioDevice>(m_audioDevice)->open();
118 bool clientOpen = openAudioClient(std::move(immDevice), m_role);
119 if (!clientOpen)
120 return false;
121
122 m_audioCallback = std::move(audioCallback);
123
124 return startAudioClient(StreamType::Callback);
125}
126
128{
129 m_suspended = true;
130 QWindowsAudioUtils::audioClientStop(m_audioClient);
131}
132
134{
135 m_suspended = false;
136 QWindowsAudioUtils::audioClientStart(m_audioClient);
137}
138
139void QWASAPIAudioSinkStream::stop(ShutdownPolicy shutdownPolicy)
140{
141 using namespace QWindowsAudioUtils;
142
143 m_parent = nullptr;
144 m_shutdownPolicy = shutdownPolicy;
145
146 switch (shutdownPolicy) {
147 case ShutdownPolicy::DiscardRingbuffer: {
148 requestStop();
149 audioClientStop(m_audioClient);
150 m_workerThread->wait();
151 m_workerThread = {};
152 audioClientReset(m_audioClient);
153
154 return;
155 }
156 case ShutdownPolicy::DrainRingbuffer: {
157 m_ringbufferDrained.callOnActivated([self = shared_from_this()]() mutable {
158 self->m_workerThread->wait();
159 self = {};
160 });
161 return;
162 }
163 default:
164 Q_UNREACHABLE_RETURN();
165 }
166}
167
169{
170 if (m_parent)
171 m_parent->updateStreamIdle(streamIsIdle);
172}
173
174bool QWASAPIAudioSinkStream::openAudioClient(ComPtr<IMMDevice> device, AudioEndpointRole role)
175{
176 using namespace QWindowsAudioUtils;
177
178 std::optional<AudioClientCreationResult> clientData =
179 createAudioClient(device, m_hostFormat, m_hardwareBufferFrames, m_wasapiHandle, role);
180
181 if (!clientData)
182 return false;
183
184 m_audioClient = std::move(clientData->client);
185 m_periodSize = clientData->periodSize;
186 m_audioClientFrames = clientData->audioClientFrames;
187
188 HRESULT hr = m_audioClient->GetService(IID_PPV_ARGS(m_renderClient.GetAddressOf()));
189 if (FAILED(hr)) {
190 qWarning() << "IAudioClient3::GetService failed to obtain IAudioRenderClient"
191 << audioClientErrorString(hr);
192 return false;
193 }
194
195 if (m_audioDevice.preferredFormat().sampleRate() != m_hostFormat.sampleRate())
196 audioClientSetRate(m_audioClient, m_hostFormat.sampleRate());
197
198 return true;
199}
200
201bool QWASAPIAudioSinkStream::startAudioClient(StreamType streamType)
202{
203 using namespace QWindowsAudioUtils;
204 m_workerThread.reset(QThread::create([this, streamType] {
205 setMCSSForPeriodSize(m_periodSize);
206 fillInitialHostBuffer();
207 std::optional<QComHelper> m_comHelper;
208
209 if (m_hostFormat != m_format) {
210 m_comHelper.emplace();
211 m_resampler = std::make_unique<QWindowsResampler>();
212 m_resampler->setup(m_format, m_hostFormat);
213
214 m_memoryResource = std::make_unique<QTlsfMemoryResource>(512 * 1024);
215 }
216
217 switch (streamType) {
218 case StreamType::Ringbuffer:
219 return runProcessRingbufferLoop();
220 case StreamType::Callback:
221 return runProcessCallbackLoop();
222 }
223 }));
224 m_workerThread->setObjectName(u"QWASAPIAudioSinkStream");
225 m_workerThread->start();
226
227 return QWindowsAudioUtils::audioClientStart(m_audioClient);
228}
229
230void QWASAPIAudioSinkStream::fillInitialHostBuffer()
231{
232 processRingbuffer();
233}
234
235void QWASAPIAudioSinkStream::runProcessRingbufferLoop()
236{
237 using namespace QWindowsAudioUtils;
238
239 for (;;) {
240 constexpr std::chrono::milliseconds timeout = 2s;
241 DWORD retval = WaitForSingleObject(m_wasapiHandle.get(), timeout.count());
242 if (retval != WAIT_OBJECT_0) {
243 if (m_suspended)
244 continue;
245
246 handleAudioClientError();
247 return;
248 }
249
250 if (isStopRequested()) {
251 switch (m_shutdownPolicy.load(std::memory_order_relaxed)) {
252 case ShutdownPolicy::DiscardRingbuffer:
253 return;
254 case ShutdownPolicy::DrainRingbuffer: {
255 bool bufferDrained = visitRingbuffer([](const auto &ringbuffer) {
256 return ringbuffer.used() == 0;
257 });
258 if (bufferDrained) {
259 audioClientStop(m_audioClient);
260 audioClientReset(m_audioClient);
261
262 m_ringbufferDrained.set();
263 return;
264 }
265 break;
266 }
267 default:
268 Q_UNREACHABLE_RETURN();
269 }
270 }
271
272 bool success = processRingbuffer();
273 if (!success) {
274 handleAudioClientError();
275 return;
276 }
277 }
278}
279
280void QWASAPIAudioSinkStream::runProcessCallbackLoop()
281{
282 using namespace QWindowsAudioUtils;
283
284 for (;;) {
285 constexpr std::chrono::milliseconds timeout = 2s;
286 DWORD retval = WaitForSingleObject(m_wasapiHandle.get(), timeout.count());
287 if (retval != WAIT_OBJECT_0) {
288 if (m_suspended)
289 continue;
290
291 handleAudioClientError();
292 return;
293 }
294
295 if (isStopRequested())
296 return;
297
298 bool success = processCallback();
299 if (!success) {
300 handleAudioClientError();
301 return;
302 }
303 }
304}
305
306template <typename Functor>
307bool QWASAPIAudioSinkStream::visitAudioClientBuffer(Functor &&f)
308{
309 uint32_t numFramesPadding;
310 HRESULT hr = m_audioClient->GetCurrentPadding(&numFramesPadding);
311 if (FAILED(hr)) {
312 qWarning() << "IAudioClient3::GetCurrentPadding failed" << audioClientErrorString(hr);
313 return false;
314 }
315
316 const uint32_t requiredFrames = m_audioClientFrames - numFramesPadding;
317 if (requiredFrames == 0)
318 return true;
319
320 // Grab the next empty buffer from the audio device.
321 unsigned char *hostBuffer{};
322 hr = m_renderClient->GetBuffer(requiredFrames, &hostBuffer);
323 if (FAILED(hr)) {
324 qWarning() << "IAudioRenderClient::getBuffer failed" << audioClientErrorString(hr);
325 return false;
326 }
327
328 QSpan<std::byte> hostBufferSpan{
329 reinterpret_cast<std::byte *>(hostBuffer),
330 m_hostFormat.bytesForFrames(requiredFrames),
331 };
332
333 uint64_t consumedFrames;
334 if (m_resampler) {
335 Q_UNLIKELY_BRANCH;
336
337 std::pmr::vector<std::byte> resampleBuffer{
338 size_t(m_format.bytesForFrames(requiredFrames)),
339 m_memoryResource.get(),
340 };
341 consumedFrames = f(as_writable_bytes(QSpan{ resampleBuffer }), requiredFrames);
342
343 auto resampledBuffer = m_resampler->resample(resampleBuffer, m_memoryResource.get());
344
345 Q_ASSERT(resampledBuffer.size() == size_t(hostBufferSpan.size()));
346 std::copy_n(resampledBuffer.data(), resampledBuffer.size(), hostBufferSpan.data());
347 } else {
348 consumedFrames = f(hostBufferSpan, requiredFrames);
349 }
350
351 const DWORD flags = consumedFrames != 0 ? 0 : AUDCLNT_BUFFERFLAGS_SILENT;
352
353 hr = m_renderClient->ReleaseBuffer(requiredFrames, flags);
354 if (FAILED(hr)) {
355 qWarning() << "IAudioRenderClient::ReleaseBuffer failed" << audioClientErrorString(hr);
356 return false;
357 }
358
359 return true;
360}
361
363{
366 return consumedFrames;
367 });
368}
369
371{
374 return requiredFrames;
375 });
376}
377
379{
380 using namespace QWindowsAudioUtils;
383
384 invokeOnAppThread([this] {
386 });
387}
388
394
395} // namespace QtWASAPI
396
397QT_END_NAMESPACE
QWASAPIAudioSinkStream(QAudioDevice, const QAudioFormat &, std::optional< qsizetype > ringbufferSize, QWindowsAudioSink *parent, float volume, std::optional< int32_t > hardwareBufferSize, AudioEndpointRole)
QAudioFormat::SampleFormat SampleFormat