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
qwindowsaudiosource.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/qthread.h>
7#include <QtCore/qloggingcategory.h>
8#include <QtCore/private/qfunctions_win_p.h>
9#include <QtCore/private/quniquehandle_types_p.h>
10#include <QtMultimedia/private/qaudioformat_p.h>
11#include <QtMultimedia/private/qaudiosystem_platform_stream_support_p.h>
12#include <QtMultimedia/private/qmemory_resource_tlsf_p.h>
13#include <QtMultimedia/private/qwindowsaudiodevice_p.h>
14#include <QtMultimedia/private/qwindowsaudioutils_p.h>
15
16#include <audioclient.h>
17#include <mmdeviceapi.h>
18
20
21namespace QtWASAPI {
22
24using namespace std::chrono_literals;
25
26namespace {
27QAudioFormat makeHostFormatForSource(const QAudioDevice &device, const QAudioFormat &format)
28{
29 const QWindowsAudioDevice *winDevice = QAudioDevicePrivate::handle<QWindowsAudioDevice>(device);
30
31 auto status = winDevice->m_probeDataFuture.wait_for(QAudioDevicePrivate::formatProbeTimeout);
32 switch (status) {
33 case std::future_status::ready:
34 case std::future_status::deferred:
35 // proceed
36 break;
37 case std::future_status::timeout:
38 return QAudioFormat{};
39 default:
40 Q_UNREACHABLE();
41 }
42
43 auto [minProbedChannels, maxProbedChannels] = winDevice->m_probeDataFuture.get().channelCountRange;
44 auto [minProbedSampleRate, maxProbedSampleRate] = winDevice->m_probeDataFuture.get().sampleRateRange;
45
46 QAudioFormat hostFormat = format;
47 const int requestedChannelCount = format.channelCount();
48 if (requestedChannelCount < device.minimumChannelCount()) {
49 hostFormat.setChannelCount(minProbedChannels);
50 hostFormat.setChannelConfig(
51 QAudioFormat::defaultChannelConfigForChannelCount(minProbedChannels));
52 } else if (requestedChannelCount > device.maximumChannelCount()) {
53 hostFormat.setChannelCount(maxProbedChannels);
54 hostFormat.setChannelConfig(
55 QAudioFormat::defaultChannelConfigForChannelCount(maxProbedChannels));
56 }
57
58 const int requestedSampleRate = format.sampleRate();
59 if (requestedSampleRate < device.minimumSampleRate())
60 hostFormat.setSampleRate(minProbedSampleRate);
61 else if (requestedSampleRate > device.maximumSampleRate())
62 hostFormat.setSampleRate(maxProbedSampleRate);
63
64 return hostFormat;
65}
66} // namespace
67
69 QAudioDevice device, const QAudioFormat &format, std::optional<qsizetype> ringbufferSize,
70 QWindowsAudioSource *parent, float volume,
71 std::optional<NativePeriodFrames> nativePeriodFrames)
74 },
76 CreateEvent(0, false, false, nullptr),
77 },
78 m_parent{ parent },
81 }
82{
83}
84
85QWASAPIAudioSourceStream::~QWASAPIAudioSourceStream() = default;
86
87bool QWASAPIAudioSourceStream::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));
94 if (!clientOpen)
95 return false;
96
97 setQIODevice(ioDevice);
98 createQIODeviceConnections(ioDevice);
99
100 return startAudioClient();
101}
102
104{
105 auto immDevice = QAudioDevicePrivate::handle<QWindowsAudioDevice>(m_audioDevice)->open();
106 if (!immDevice)
107 return nullptr;
108
109 bool clientOpen = openAudioClient(std::move(immDevice));
110 if (!clientOpen)
111 return nullptr;
112
113 QIODevice *ioDevice = createRingbufferReaderDevice();
114
115 m_parent->updateStreamIdle(true, QWindowsAudioSource::EmitStateSignal::False);
116
117 setQIODevice(ioDevice);
118 createQIODeviceConnections(ioDevice);
119
120 bool started = startAudioClient();
121 return started ? ioDevice : nullptr;
122}
123
124bool QWASAPIAudioSourceStream::start(AudioCallback &&cb)
125{
126 auto immDevice = QAudioDevicePrivate::handle<QWindowsAudioDevice>(m_audioDevice)->open();
127 if (!immDevice)
128 return false;
129
130 bool clientOpen = openAudioClient(std::move(immDevice));
131 if (!clientOpen)
132 return false;
133
134 m_audioCallback = std::move(cb);
135
136 return startAudioClient();
137}
138
140{
141 m_suspended = true;
142 QWindowsAudioUtils::audioClientStop(m_audioClient);
143}
144
146{
147 m_suspended = false;
148 QWindowsAudioUtils::audioClientStart(m_audioClient);
149}
150
151void QWASAPIAudioSourceStream::stop(ShutdownPolicy shutdownPolicy)
152{
153 m_parent = nullptr;
154 m_shutdownPolicy = shutdownPolicy;
155
156 requestStop();
157 disconnectQIODeviceConnections();
158 QWindowsAudioUtils::audioClientStop(m_audioClient);
159
160 joinWorkerThread();
161 QWindowsAudioUtils::audioClientReset(m_audioClient);
162
163 finalizeQIODevice(shutdownPolicy);
164 if (shutdownPolicy == ShutdownPolicy::DiscardRingbuffer)
165 emptyRingbuffer();
166}
167
169{
170 if (m_parent)
171 m_parent->updateStreamIdle(streamIsIdle);
172}
173
174bool QWASAPIAudioSourceStream::openAudioClient(ComPtr<IMMDevice> device)
175{
176 using namespace QWindowsAudioUtils;
177
178 std::optional<AudioClientCreationResult> clientData =
179 createAudioClient(device, m_hostFormat, m_nativePeriodFrames, m_audioClientEventHandle);
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_captureClient.GetAddressOf()));
189 if (FAILED(hr)) {
190 qWarning() << "IAudioClient3::GetService failed to obtain IAudioCaptureClient"
191 << audioClientErrorString(hr);
192 return false;
193 }
194
195 return true;
196}
197
198bool QWASAPIAudioSourceStream::startAudioClient()
199{
200 using namespace QWindowsAudioUtils;
201 m_workerThread.reset(QThread::create([this] {
202 const auto mmcssRegistration = registerMMCSSThreadForPeriod(m_periodSize);
203
204 std::optional<QComHelper> m_comHelper;
205
206 if (m_hostFormat != m_format) {
207 m_comHelper.emplace();
208 m_resampler = std::make_unique<QWindowsResampler>();
209 m_resampler->setup(m_hostFormat, m_format);
210
211 m_memoryResource = std::make_unique<QTlsfMemoryResource>(512 * 1024);
212 }
213
214 runProcessLoop();
215 }));
216
217 m_workerThread->setObjectName(u"QWASAPIAudioSourceStream");
218 m_workerThread->start();
219
220 bool clientStarted = audioClientStart(m_audioClient);
221 if (!clientStarted) {
222 joinWorkerThread();
223 return false;
224 }
225
226 return true;
227}
228
229void QWASAPIAudioSourceStream::runProcessLoop()
230{
231 using namespace QWindowsAudioUtils;
232 for (;;) {
233 constexpr std::chrono::milliseconds timeout = 2s;
234 const bool wasSuspended = m_suspended;
235 DWORD retval = WaitForSingleObject(m_audioClientEventHandle.get(), timeout.count());
236 if (retval != WAIT_OBJECT_0) {
237 if (wasSuspended || m_suspended)
238 continue;
239
240 handleAudioClientError();
241 return;
242 }
243
244 if (isStopRequested())
245 return; // TODO: distinguish between stop/reset?
246
247 bool success = m_audioCallback ? processCallback() : processRingbuffer();
248 if (!success) {
249 handleAudioClientError();
250 return;
251 }
252 }
253}
254
255template <typename Functor>
256bool QWASAPIAudioSourceStream::visitAudioClientBuffer(Functor &&f)
257{
258 for (;;) {
259 unsigned char *hostBuffer;
260 uint32_t hostBufferFrames;
261 DWORD flags;
262 uint64_t devicePosition;
263 uint64_t QPCPosition;
264 HRESULT hr = m_captureClient->GetBuffer(&hostBuffer, &hostBufferFrames, &flags,
265 &devicePosition, &QPCPosition);
266 if (FAILED(hr)) {
267 qWarning() << "IAudioCaptureClient::GetBuffer failed" << audioClientErrorString(hr);
268 return false;
269 }
270
271 // Note: when starting the capture client, we an initial wakeup, despite no buffer being
272 // available
273 if (hostBufferFrames == 0)
274 return true;
275
276 QSpan hostBufferSpan{
277 hostBuffer,
278 m_hostFormat.bytesForFrames(hostBufferFrames),
279 };
280
281 if (m_resampler) {
282 Q_UNLIKELY_BRANCH;
283 auto resampledBuffer =
284 m_resampler->resample(as_bytes(hostBufferSpan), m_memoryResource.get());
285 QPlatformAudioSourceStream::process(resampledBuffer,
286 m_format.framesForBytes(resampledBuffer.size()));
287 } else {
288 f(as_bytes(hostBufferSpan), hostBufferFrames);
289 }
290
291 hr = m_captureClient->ReleaseBuffer(hostBufferFrames);
292
293 if (FAILED(hr)) {
294 qWarning() << "IAudioCaptureClient::ReleaseBuffer failed" << audioClientErrorString(hr);
295 return false;
296 }
297 }
298}
299
301{
306 updateStreamIdle(true);
307 });
308}
309
311{
314 });
315}
316
318{
319 using namespace QWindowsAudioUtils;
322 requestStop();
323
325 if (self->m_parent && self->m_parent->m_stream == self)
327 });
328}
329
331{
332 requestStop();
333 ::SetEvent(m_audioClientEventHandle.get()); // force wakeup
335 m_workerThread = {};
336}
337
338///////////////////////////////////////////////////////////////////////////////////////////////////
339
345
347 = default;
348
349} // namespace QtWASAPI
350
351QT_END_NAMESPACE
Combined button and popup list for selecting options.
QAudioFormat::SampleFormat SampleFormat
QWASAPIAudioSourceStream(QAudioDevice, const QAudioFormat &, std::optional< qsizetype > ringbufferSize, QWindowsAudioSource *parent, float volume, std::optional< NativePeriodFrames > nativePeriodFrames)