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 QAudioFormat hostFormat = format;
32 const int requestedChannelCount = format.channelCount();
33 auto [minProbedChannels, maxProbedChannels] = winDevice->m_probedChannelCountRange;
34
35 if (requestedChannelCount < device.minimumChannelCount()) {
36 hostFormat.setChannelCount(minProbedChannels);
37 hostFormat.setChannelConfig(
38 QAudioFormat::defaultChannelConfigForChannelCount(minProbedChannels));
39 } else if (requestedChannelCount > device.maximumChannelCount()) {
40 hostFormat.setChannelCount(maxProbedChannels);
41 hostFormat.setChannelConfig(
42 QAudioFormat::defaultChannelConfigForChannelCount(maxProbedChannels));
43 }
44
45 const int requestedSampleRate = format.sampleRate();
46 auto [minProbedSampleRate, maxProbedSampleRate] = winDevice->m_probedSampleRateRange;
47
48 if (requestedSampleRate < device.minimumSampleRate())
49 hostFormat.setSampleRate(minProbedSampleRate);
50 else if (requestedSampleRate > device.maximumSampleRate())
51 hostFormat.setSampleRate(maxProbedSampleRate);
52
53 return hostFormat;
54}
55} // namespace
56
57QWASAPIAudioSourceStream::QWASAPIAudioSourceStream(QAudioDevice device, const QAudioFormat &format,
58 std::optional<qsizetype> ringbufferSize,
59 QWindowsAudioSource *parent,
60 float volume,
61 std::optional<int32_t> hardwareBufferFrames):
63 std::move(device),
64 format,
67 volume,
68 },
70 CreateEvent(0, false, false, nullptr),
71 },
72 m_parent{
73 parent
74 },
77 }
78{
79}
80
81QWASAPIAudioSourceStream::~QWASAPIAudioSourceStream() = default;
82
83bool QWASAPIAudioSourceStream::start(QIODevice *ioDevice)
84{
85 auto immDevice = QAudioDevicePrivate::handle<QWindowsAudioDevice>(m_audioDevice)->open();
86
87 bool clientOpen = openAudioClient(std::move(immDevice));
88 if (!clientOpen)
89 return false;
90
91 setQIODevice(ioDevice);
92 createQIODeviceConnections(ioDevice);
93
94 bool started = startAudioClient();
95 if (!started)
96 return false;
97
98 return true;
99}
100
102{
103 auto immDevice = QAudioDevicePrivate::handle<QWindowsAudioDevice>(m_audioDevice)->open();
104
105 bool clientOpen = openAudioClient(std::move(immDevice));
106 if (!clientOpen)
107 return nullptr;
108
109 QIODevice *ioDevice = createRingbufferReaderDevice();
110
111 m_parent->updateStreamIdle(true, QWindowsAudioSource::EmitStateSignal::False);
112
113 setQIODevice(ioDevice);
114 createQIODeviceConnections(ioDevice);
115
116 bool started = startAudioClient();
117 if (!started)
118 return nullptr;
119
120 return ioDevice;
121}
122
123bool QWASAPIAudioSourceStream::start(AudioCallback &&cb)
124{
125 auto immDevice = QAudioDevicePrivate::handle<QWindowsAudioDevice>(m_audioDevice)->open();
126
127 bool clientOpen = openAudioClient(std::move(immDevice));
128 if (!clientOpen)
129 return false;
130
131 m_audioCallback = std::move(cb);
132
133 return startAudioClient();
134}
135
137{
138 m_suspended = true;
139 QWindowsAudioUtils::audioClientStop(m_audioClient);
140}
141
143{
144 m_suspended = false;
145 QWindowsAudioUtils::audioClientStart(m_audioClient);
146}
147
148void QWASAPIAudioSourceStream::stop(ShutdownPolicy shutdownPolicy)
149{
150 m_parent = nullptr;
151 m_shutdownPolicy = shutdownPolicy;
152
153 requestStop();
154 disconnectQIODeviceConnections();
155
156 QWindowsAudioUtils::audioClientStop(m_audioClient);
157 m_workerThread->wait();
158 QWindowsAudioUtils::audioClientReset(m_audioClient);
159
160 finalizeQIODevice(shutdownPolicy);
161 if (shutdownPolicy == ShutdownPolicy::DiscardRingbuffer)
162 emptyRingbuffer();
163}
164
166{
167 if (m_parent)
168 m_parent->updateStreamIdle(streamIsIdle);
169}
170
171bool QWASAPIAudioSourceStream::openAudioClient(ComPtr<IMMDevice> device)
172{
173 using namespace QWindowsAudioUtils;
174
175 std::optional<AudioClientCreationResult> clientData =
176 createAudioClient(device, m_hostFormat, m_hardwareBufferFrames, m_wasapiHandle);
177
178 if (!clientData)
179 return false;
180
181 m_audioClient = std::move(clientData->client);
182 m_periodSize = clientData->periodSize;
183 m_audioClientFrames = clientData->audioClientFrames;
184
185 HRESULT hr = m_audioClient->GetService(IID_PPV_ARGS(m_captureClient.GetAddressOf()));
186 if (FAILED(hr)) {
187 qWarning() << "IAudioClient3::GetService failed to obtain IAudioCaptureClient"
188 << audioClientErrorString(hr);
189 return false;
190 }
191
192 return true;
193}
194
195bool QWASAPIAudioSourceStream::startAudioClient()
196{
197 using namespace QWindowsAudioUtils;
198 m_workerThread.reset(QThread::create([this] {
199 setMCSSForPeriodSize(m_periodSize);
200
201 std::optional<QComHelper> m_comHelper;
202
203 if (m_hostFormat != m_format) {
204 m_comHelper.emplace();
205 m_resampler = std::make_unique<QWindowsResampler>();
206 m_resampler->setup(m_hostFormat, m_format);
207
208 m_memoryResource = std::make_unique<QTlsfMemoryResource>(512 * 1024);
209 }
210
211 runProcessLoop();
212 }));
213
214 m_workerThread->setObjectName(u"QWASAPIAudioSourceStream");
215 m_workerThread->start();
216
217 return audioClientStart(m_audioClient);
218}
219
220void QWASAPIAudioSourceStream::runProcessLoop()
221{
222 using namespace QWindowsAudioUtils;
223 for (;;) {
224 constexpr std::chrono::milliseconds timeout = 2s;
225 DWORD retval = WaitForSingleObject(m_wasapiHandle.get(), timeout.count());
226 if (retval != WAIT_OBJECT_0) {
227 if (m_suspended)
228 continue;
229
230 handleAudioClientError();
231 return;
232 }
233
234 if (isStopRequested())
235 return; // TODO: distinguish between stop/reset?
236
237 bool success = m_audioCallback ? processCallback() : processRingbuffer();
238 if (!success) {
239 handleAudioClientError();
240 return;
241 }
242 }
243}
244
245template <typename Functor>
246bool QWASAPIAudioSourceStream::visitAudioClientBuffer(Functor &&f)
247{
248 for (;;) {
249 unsigned char *hostBuffer;
250 uint32_t hostBufferFrames;
251 DWORD flags;
252 uint64_t devicePosition;
253 uint64_t QPCPosition;
254 HRESULT hr = m_captureClient->GetBuffer(&hostBuffer, &hostBufferFrames, &flags,
255 &devicePosition, &QPCPosition);
256 if (FAILED(hr)) {
257 qWarning() << "IAudioCaptureClient::GetBuffer failed" << audioClientErrorString(hr);
258 return false;
259 }
260
261 // Note: when starting the capture client, we an initial wakeup, despite no buffer being
262 // available
263 if (hostBufferFrames == 0)
264 return true;
265
266 QSpan hostBufferSpan{
267 hostBuffer,
268 m_hostFormat.bytesForFrames(hostBufferFrames),
269 };
270
271 if (m_resampler) {
272 Q_UNLIKELY_BRANCH;
273 auto resampledBuffer =
274 m_resampler->resample(as_bytes(hostBufferSpan), m_memoryResource.get());
275 QPlatformAudioSourceStream::process(resampledBuffer,
276 m_format.framesForBytes(resampledBuffer.size()));
277 } else {
278 f(as_bytes(hostBufferSpan), hostBufferFrames);
279 }
280
281 hr = m_captureClient->ReleaseBuffer(hostBufferFrames);
282
283 if (FAILED(hr)) {
284 qWarning() << "IAudioCaptureClient::ReleaseBuffer failed" << audioClientErrorString(hr);
285 return false;
286 }
287 }
288}
289
291{
296 updateStreamIdle(true);
297 });
298}
299
301{
304 });
305}
306
308{
309 using namespace QWindowsAudioUtils;
312 requestStop();
313
314 invokeOnAppThread([this] {
316 });
317}
318
319///////////////////////////////////////////////////////////////////////////////////////////////////
320
326
327} // namespace QtWASAPI
328
329QT_END_NAMESPACE
QAudioFormat::SampleFormat SampleFormat
QWASAPIAudioSourceStream(QAudioDevice, const QAudioFormat &, std::optional< qsizetype > ringbufferSize, QWindowsAudioSource *parent, float volume, std::optional< int32_t > hardwareBufferFrames)