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
68QWASAPIAudioSourceStream::QWASAPIAudioSourceStream(QAudioDevice device, const QAudioFormat &format,
69 std::optional<qsizetype> ringbufferSize,
70 QWindowsAudioSource *parent,
71 float volume,
72 std::optional<int32_t> hardwareBufferFrames):
74 std::move(device),
75 format,
78 volume,
79 },
81 CreateEvent(0, false, false, nullptr),
82 },
83 m_parent{
84 parent
85 },
88 }
89{
90}
91
92QWASAPIAudioSourceStream::~QWASAPIAudioSourceStream() = default;
93
94bool QWASAPIAudioSourceStream::start(QIODevice *ioDevice)
95{
96 auto immDevice = QAudioDevicePrivate::handle<QWindowsAudioDevice>(m_audioDevice)->open();
97 if (!immDevice)
98 return false;
99
100 bool clientOpen = openAudioClient(std::move(immDevice));
101 if (!clientOpen)
102 return false;
103
104 setQIODevice(ioDevice);
105 createQIODeviceConnections(ioDevice);
106
107 return startAudioClient();
108}
109
111{
112 auto immDevice = QAudioDevicePrivate::handle<QWindowsAudioDevice>(m_audioDevice)->open();
113 if (!immDevice)
114 return nullptr;
115
116 bool clientOpen = openAudioClient(std::move(immDevice));
117 if (!clientOpen)
118 return nullptr;
119
120 QIODevice *ioDevice = createRingbufferReaderDevice();
121
122 m_parent->updateStreamIdle(true, QWindowsAudioSource::EmitStateSignal::False);
123
124 setQIODevice(ioDevice);
125 createQIODeviceConnections(ioDevice);
126
127 bool started = startAudioClient();
128 return started ? ioDevice : nullptr;
129}
130
131bool QWASAPIAudioSourceStream::start(AudioCallback &&cb)
132{
133 auto immDevice = QAudioDevicePrivate::handle<QWindowsAudioDevice>(m_audioDevice)->open();
134 if (!immDevice)
135 return false;
136
137 bool clientOpen = openAudioClient(std::move(immDevice));
138 if (!clientOpen)
139 return false;
140
141 m_audioCallback = std::move(cb);
142
143 return startAudioClient();
144}
145
147{
148 m_suspended = true;
149 QWindowsAudioUtils::audioClientStop(m_audioClient);
150}
151
153{
154 m_suspended = false;
155 QWindowsAudioUtils::audioClientStart(m_audioClient);
156}
157
158void QWASAPIAudioSourceStream::stop(ShutdownPolicy shutdownPolicy)
159{
160 m_parent = nullptr;
161 m_shutdownPolicy = shutdownPolicy;
162
163 requestStop();
164 disconnectQIODeviceConnections();
165 QWindowsAudioUtils::audioClientStop(m_audioClient);
166
167 joinWorkerThread();
168 QWindowsAudioUtils::audioClientReset(m_audioClient);
169
170 finalizeQIODevice(shutdownPolicy);
171 if (shutdownPolicy == ShutdownPolicy::DiscardRingbuffer)
172 emptyRingbuffer();
173}
174
176{
177 if (m_parent)
178 m_parent->updateStreamIdle(streamIsIdle);
179}
180
181bool QWASAPIAudioSourceStream::openAudioClient(ComPtr<IMMDevice> device)
182{
183 using namespace QWindowsAudioUtils;
184
185 std::optional<AudioClientCreationResult> clientData =
186 createAudioClient(device, m_hostFormat, m_hardwareBufferFrames, m_wasapiHandle);
187
188 if (!clientData)
189 return false;
190
191 m_audioClient = std::move(clientData->client);
192 m_periodSize = clientData->periodSize;
193 m_audioClientFrames = clientData->audioClientFrames;
194
195 HRESULT hr = m_audioClient->GetService(IID_PPV_ARGS(m_captureClient.GetAddressOf()));
196 if (FAILED(hr)) {
197 qWarning() << "IAudioClient3::GetService failed to obtain IAudioCaptureClient"
198 << audioClientErrorString(hr);
199 return false;
200 }
201
202 return true;
203}
204
205bool QWASAPIAudioSourceStream::startAudioClient()
206{
207 using namespace QWindowsAudioUtils;
208 m_workerThread.reset(QThread::create([this] {
209 setMCSSForPeriodSize(m_periodSize);
210
211 std::optional<QComHelper> m_comHelper;
212
213 if (m_hostFormat != m_format) {
214 m_comHelper.emplace();
215 m_resampler = std::make_unique<QWindowsResampler>();
216 m_resampler->setup(m_hostFormat, m_format);
217
218 m_memoryResource = std::make_unique<QTlsfMemoryResource>(512 * 1024);
219 }
220
221 runProcessLoop();
222 }));
223
224 m_workerThread->setObjectName(u"QWASAPIAudioSourceStream");
225 m_workerThread->start();
226
227 bool clientStarted = audioClientStart(m_audioClient);
228 if (!clientStarted) {
229 joinWorkerThread();
230 return false;
231 }
232
233 return true;
234}
235
236void QWASAPIAudioSourceStream::runProcessLoop()
237{
238 using namespace QWindowsAudioUtils;
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 return; // TODO: distinguish between stop/reset?
252
253 bool success = m_audioCallback ? processCallback() : processRingbuffer();
254 if (!success) {
255 handleAudioClientError();
256 return;
257 }
258 }
259}
260
261template <typename Functor>
262bool QWASAPIAudioSourceStream::visitAudioClientBuffer(Functor &&f)
263{
264 for (;;) {
265 unsigned char *hostBuffer;
266 uint32_t hostBufferFrames;
267 DWORD flags;
268 uint64_t devicePosition;
269 uint64_t QPCPosition;
270 HRESULT hr = m_captureClient->GetBuffer(&hostBuffer, &hostBufferFrames, &flags,
271 &devicePosition, &QPCPosition);
272 if (FAILED(hr)) {
273 qWarning() << "IAudioCaptureClient::GetBuffer failed" << audioClientErrorString(hr);
274 return false;
275 }
276
277 // Note: when starting the capture client, we an initial wakeup, despite no buffer being
278 // available
279 if (hostBufferFrames == 0)
280 return true;
281
282 QSpan hostBufferSpan{
283 hostBuffer,
284 m_hostFormat.bytesForFrames(hostBufferFrames),
285 };
286
287 if (m_resampler) {
288 Q_UNLIKELY_BRANCH;
289 auto resampledBuffer =
290 m_resampler->resample(as_bytes(hostBufferSpan), m_memoryResource.get());
291 QPlatformAudioSourceStream::process(resampledBuffer,
292 m_format.framesForBytes(resampledBuffer.size()));
293 } else {
294 f(as_bytes(hostBufferSpan), hostBufferFrames);
295 }
296
297 hr = m_captureClient->ReleaseBuffer(hostBufferFrames);
298
299 if (FAILED(hr)) {
300 qWarning() << "IAudioCaptureClient::ReleaseBuffer failed" << audioClientErrorString(hr);
301 return false;
302 }
303 }
304}
305
307{
312 updateStreamIdle(true);
313 });
314}
315
317{
320 });
321}
322
324{
325 using namespace QWindowsAudioUtils;
328 requestStop();
329
330 invokeOnAppThread([this] {
332 });
333}
334
336{
337 requestStop();
338 ::SetEvent(m_wasapiHandle.get()); // force wakeup
340 m_workerThread = {};
341}
342
343///////////////////////////////////////////////////////////////////////////////////////////////////
344
350
352 = default;
353
354} // namespace QtWASAPI
355
356QT_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< int32_t > hardwareBufferFrames)