Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
4//
5// W A R N I N G
6// -------------
7//
8// This file is not part of the Qt API. It exists for the convenience
9// of other Qt classes. This header file may change from version to
10// version without notice, or even be removed.
11//
12// INTERNAL USE ONLY: Do NOT use for any other purpose.
13//
14
15
18#include "qcomtaskresource_p.h"
19
20#include <QtCore/QDataStream>
21#include <QtCore/qtimer.h>
22
23#include <private/qaudiohelpers_p.h>
24
25#include <qloggingcategory.h>
26#include <qdebug.h>
27#include <audioclient.h>
28#include <mmdeviceapi.h>
29
31
32static Q_LOGGING_CATEGORY(qLcAudioSource, "qt.multimedia.audiosource")
33
34using namespace QWindowsMultimediaUtils;
35
36class OurSink : public QIODevice
37{
38public:
40
41 qint64 bytesAvailable() const override { return m_audioSource.bytesReady(); }
42 qint64 readData(char* data, qint64 len) override { return m_audioSource.read(data, len); }
43 qint64 writeData(const char*, qint64) override { return 0; }
44
45private:
46 QWindowsAudioSource &m_audioSource;
47};
48
50 : QPlatformAudioSource(parent),
51 m_timer(new QTimer(this)),
52 m_device(std::move(device)),
53 m_ourSink(new OurSink(*this))
54{
57 m_timer->setSingleShot(true);
58 m_timer->callOnTimeout(this, &QWindowsAudioSource::pullCaptureClient);
59}
60
62{
63 m_volume = qBound(qreal(0), volume, qreal(1));
64}
65
67{
68 return m_volume;
69}
70
75
77{
78 return m_errorState;
79}
80
82{
83 return m_deviceState;
84}
85
87{
88 if (m_deviceState == QAudio::StoppedState) {
89 m_format = fmt;
90 } else {
91 if (m_format != fmt) {
92 qWarning() << "Cannot set a new audio format, in the current state ("
93 << m_deviceState << ")";
94 }
95 }
96}
97
99{
100 return m_format;
101}
102
103void QWindowsAudioSource::deviceStateChange(QAudio::State state, QAudio::Error error)
104{
105 if (state != m_deviceState) {
106 bool wasActive = m_deviceState == QAudio::ActiveState || m_deviceState == QAudio::IdleState;
108
109 if (isActive && !wasActive) {
110 m_audioClient->Start();
111 qCDebug(qLcAudioSource) << "Audio client started";
112
113 } else if (wasActive && !isActive) {
114 m_timer->stop();
115 m_audioClient->Stop();
116 qCDebug(qLcAudioSource) << "Audio client stopped";
117 }
118
119 m_deviceState = state;
120 emit stateChanged(m_deviceState);
121 }
122
123 if (error != m_errorState) {
124 m_errorState = error;
126 }
127}
128
129QByteArray QWindowsAudioSource::readCaptureClientBuffer()
130{
131 UINT32 actualFrames = 0;
132 BYTE *data = nullptr;
133 DWORD flags = 0;
134 HRESULT hr = m_captureClient->GetBuffer(&data, &actualFrames, &flags, nullptr, nullptr);
135 if (FAILED(hr)) {
136 qWarning() << "IAudioCaptureClient::GetBuffer failed" << errorString(hr);
137 deviceStateChange(QAudio::IdleState, QAudio::IOError);
138 return {};
139 }
140
141 if (actualFrames == 0)
142 return {};
143
145 if (flags & AUDCLNT_BUFFERFLAGS_SILENT) {
146 out.resize(m_resampler.outputFormat().bytesForDuration(
147 m_resampler.inputFormat().framesForDuration(actualFrames)),
148 0);
149 } else {
150 out = m_resampler.resample(
151 { data, m_resampler.inputFormat().bytesForFrames(actualFrames) });
152 QAudioHelperInternal::qMultiplySamples(m_volume, m_resampler.outputFormat(), out.data(), out.data(), out.size());
153 }
154
155 hr = m_captureClient->ReleaseBuffer(actualFrames);
156 if (FAILED(hr)) {
157 qWarning() << "IAudioCaptureClient::ReleaseBuffer failed" << errorString(hr);
158 deviceStateChange(QAudio::IdleState, QAudio::IOError);
159 return {};
160 }
161
162 return out;
163}
164
165void QWindowsAudioSource::schedulePull()
166{
167 auto allocated = QWindowsAudioUtils::audioClientFramesAllocated(m_audioClient.Get());
168 auto inUse = QWindowsAudioUtils::audioClientFramesInUse(m_audioClient.Get());
169
170 if (!allocated || !inUse) {
171 deviceStateChange(QAudio::IdleState, QAudio::IOError);
172 } else {
173 // Schedule the next audio pull immediately if the audio buffer is more
174 // than half-full or wait until the audio source fills at least half of it.
175 if (*inUse > *allocated / 2) {
176 m_timer->start(0);
177 } else {
178 auto timeToHalfBuffer = m_resampler.inputFormat().durationForFrames(*allocated / 2 - *inUse);
179 m_timer->start(timeToHalfBuffer / 1000);
180 }
181 }
182}
183
184void QWindowsAudioSource::pullCaptureClient()
185{
186 qCDebug(qLcAudioSource) << "Pull captureClient";
187 while (true) {
188 auto out = readCaptureClientBuffer();
189 if (out.isEmpty())
190 break;
191
192 if (m_clientSink) {
193 qint64 written = m_clientSink->write(out.data(), out.size());
194 if (written != out.size())
195 qCDebug(qLcAudioSource) << "Did not write all data to the output";
196
197 } else {
198 m_clientBufferResidue += out;
199 emit m_ourSink->readyRead();
200 }
201 }
202
203 schedulePull();
204}
205
207{
208 qCDebug(qLcAudioSource) << "start(ioDevice)";
209 if (m_deviceState != QAudio::StoppedState)
210 close();
211
212 if (device == nullptr)
213 return;
214
215 if (!open()) {
216 m_errorState = QAudio::OpenError;
218 return;
219 }
220
221 m_clientSink = device;
222 schedulePull();
223 deviceStateChange(QAudio::ActiveState, QAudio::NoError);
224}
225
227{
228 qCDebug(qLcAudioSource) << "start()";
229 if (m_deviceState != QAudio::StoppedState)
230 close();
231
232 if (!open()) {
233 m_errorState = QAudio::OpenError;
235 return nullptr;
236 }
237
238 schedulePull();
239 deviceStateChange(QAudio::IdleState, QAudio::NoError);
240 return m_ourSink;
241}
242
244{
245 if (m_deviceState == QAudio::StoppedState)
246 return;
247
248 close();
249}
250
251bool QWindowsAudioSource::open()
252{
253 HRESULT hr = m_device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER,
254 nullptr, (void**)m_audioClient.GetAddressOf());
255 if (FAILED(hr)) {
256 qCWarning(qLcAudioSource) << "Failed to activate audio device" << errorString(hr);
257 return false;
258 }
259
260 QComTaskResource<WAVEFORMATEX> pwfx;
261 hr = m_audioClient->GetMixFormat(pwfx.address());
262 if (FAILED(hr)) {
263 qCWarning(qLcAudioSource) << "Format unsupported" << errorString(hr);
264 return false;
265 }
266
267 if (!m_resampler.setup(QWindowsAudioUtils::waveFormatExToFormat(*pwfx), m_format)) {
268 qCWarning(qLcAudioSource) << "Failed to set up resampler";
269 return false;
270 }
271
272 if (m_bufferSize == 0)
273 m_bufferSize = m_format.sampleRate() * m_format.bytesPerFrame() / 5; // 200ms
274
275 REFERENCE_TIME requestedDuration = m_format.durationForBytes(m_bufferSize);
276
277 hr = m_audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, requestedDuration, 0, pwfx.get(),
278 nullptr);
279
280 if (FAILED(hr)) {
281 qCWarning(qLcAudioSource) << "Failed to initialize audio client" << errorString(hr);
282 return false;
283 }
284
285 auto framesAllocated = QWindowsAudioUtils::audioClientFramesAllocated(m_audioClient.Get());
286 if (!framesAllocated) {
287 qCWarning(qLcAudioSource) << "Failed to get audio client buffer size";
288 return false;
289 }
290
291 m_bufferSize = m_format.bytesForDuration(
292 m_resampler.inputFormat().durationForFrames(*framesAllocated));
293
294 hr = m_audioClient->GetService(__uuidof(IAudioCaptureClient), (void**)m_captureClient.GetAddressOf());
295 if (FAILED(hr)) {
296 qCWarning(qLcAudioSource) << "Failed to obtain audio client rendering service" << errorString(hr);
297 return false;
298 }
299
300 return true;
301}
302
303void QWindowsAudioSource::close()
304{
305 qCDebug(qLcAudioSource) << "close()";
306 if (m_deviceState == QAudio::StoppedState)
307 return;
308
309 deviceStateChange(QAudio::StoppedState, QAudio::NoError);
310
311 m_clientBufferResidue.clear();
312 m_captureClient.Reset();
313 m_audioClient.Reset();
314 m_clientSink = nullptr;
315}
316
318{
319 if (m_deviceState == QAudio::StoppedState || m_deviceState == QAudio::SuspendedState)
320 return 0;
321
322 auto frames = QWindowsAudioUtils::audioClientFramesInUse(m_audioClient.Get());
323 if (frames) {
324 auto clientBufferSize = m_resampler.outputFormat().bytesForDuration(
325 m_resampler.inputFormat().durationForFrames(*frames));
326
327 return clientBufferSize + m_clientBufferResidue.size();
328
329 } else {
330 return 0;
331 }
332}
333
335{
336 deviceStateChange(QAudio::ActiveState, QAudio::NoError);
337 schedulePull();
338
339 if (data == nullptr || len < 0)
340 return -1;
341
342 auto offset = 0;
343 if (!m_clientBufferResidue.isEmpty()) {
344 auto copyLen = qMin(m_clientBufferResidue.size(), len);
345 memcpy(data, m_clientBufferResidue.data(), copyLen);
346 len -= copyLen;
347 offset += copyLen;
348 }
349
350 m_clientBufferResidue = QByteArray{ m_clientBufferResidue.data() + offset,
351 m_clientBufferResidue.size() - offset };
352
353 if (len > 0) {
354 auto out = readCaptureClientBuffer();
355 if (!out.isEmpty()) {
356 qsizetype copyLen = qMin(out.size(), len);
357 memcpy(data + offset, out.data(), copyLen);
358 offset += copyLen;
359
360 m_clientBufferResidue = QByteArray{ out.data() + copyLen, out.size() - copyLen };
361 }
362 }
363
364 return offset;
365}
366
368{
369 qCDebug(qLcAudioSource) << "resume()";
370 if (m_deviceState == QAudio::SuspendedState) {
371 deviceStateChange(QAudio::ActiveState, QAudio::NoError);
372 pullCaptureClient();
373 }
374}
375
377{
378 m_bufferSize = value;
379}
380
382{
383 return m_bufferSize;
384}
385
387{
388 if (m_deviceState == QAudio::StoppedState)
389 return 0;
390
391 return m_resampler.outputFormat().durationForBytes(m_resampler.totalOutputBytes());
392}
393
395{
396 qCDebug(qLcAudioSource) << "suspend";
397 if (m_deviceState == QAudio::ActiveState || m_deviceState == QAudio::IdleState)
398 deviceStateChange(QAudio::SuspendedState, QAudio::NoError);
399}
400
402{
403 stop();
404}
405
IOBluetoothDevice * device
bool isActive
qint64 readData(char *data, qint64 len) override
Reads up to maxSize bytes from the device into data, and returns the number of bytes read or -1 if an...
qint64 bytesAvailable() const override
Returns the number of bytes that are available for reading.
qint64 writeData(const char *, qint64) override
Writes up to maxSize bytes from data to the device.
OurSink(QWindowsAudioSource &source)
The QAudioFormat class stores audio stream parameter information.
Q_MULTIMEDIA_EXPORT qint32 framesForDuration(qint64 microseconds) const
Returns the number of frames required to represent microseconds in this format.
constexpr int sampleRate() const noexcept
Returns the current sample rate in Hertz.
Q_MULTIMEDIA_EXPORT qint32 bytesForDuration(qint64 microseconds) const
Returns the number of bytes required for this audio format for microseconds.
Q_MULTIMEDIA_EXPORT qint64 durationForFrames(qint32 frameCount) const
Return the number of microseconds represented by frameCount frames in this format.
constexpr int bytesPerFrame() const
Returns the number of bytes required to represent one frame (a sample in each channel) in this format...
Q_MULTIMEDIA_EXPORT qint64 durationForBytes(qint32 byteCount) const
Returns the number of microseconds represented by bytes in this format.
void stateChanged(QAudio::State state)
void errorChanged(QAudio::Error error)
\inmodule QtCore
Definition qbytearray.h:57
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
Definition qbytearray.h:611
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:494
bool isEmpty() const noexcept
Returns true if the byte array has size 0; otherwise returns false.
Definition qbytearray.h:107
void clear()
Clears the contents of the byte array and makes it null.
\inmodule QtCore \reentrant
Definition qiodevice.h:34
virtual bool open(QIODeviceBase::OpenMode mode)
Opens the device and sets its OpenMode to mode.
void readyRead()
This signal is emitted once every time new data is available for reading from the device's current re...
qint64 write(const char *data, qint64 len)
Writes at most maxSize bytes of data from data to the device.
\inmodule QtCore
Definition qobject.h:103
\inmodule QtCore
Definition qtimer.h:20
void setSingleShot(bool singleShot)
Definition qtimer.cpp:552
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
Definition qtimer.cpp:241
void stop()
Stops the timer.
Definition qtimer.cpp:267
QMetaObject::Connection callOnTimeout(Args &&...args)
Definition qtimer.h:101
void setTimerType(Qt::TimerType atype)
Definition qtimer.cpp:651
qint64 processedUSecs() const override
qsizetype bufferSize() const override
qreal volume() const override
qint64 read(char *data, qint64 len)
void setVolume(qreal volume) override
QWindowsAudioSource(ComPtr< IMMDevice > device, QObject *parent)
void setFormat(const QAudioFormat &fmt) override
QAudio::State state() const override
qsizetype bytesReady() const override
void setBufferSize(qsizetype value) override
QAudio::Error error() const override
QAudioFormat format() const override
QIODevice * start() override
bool setup(const QAudioFormat &in, const QAudioFormat &out)
QAudioFormat inputFormat() const
QByteArray resample(const QByteArrayView &in)
quint64 totalOutputBytes() const
QAudioFormat outputFormat() const
#define this
Definition dialogs.cpp:9
else opt state
[0]
void qMultiplySamples(qreal factor, const QAudioFormat &format, const void *src, void *dest, int len)
State
Definition qaudio.h:29
@ StoppedState
Definition qaudio.h:29
@ SuspendedState
Definition qaudio.h:29
@ IdleState
Definition qaudio.h:29
@ ActiveState
Definition qaudio.h:29
Error
Definition qaudio.h:28
@ OpenError
Definition qaudio.h:28
@ NoError
Definition qaudio.h:28
@ IOError
Definition qaudio.h:28
Combined button and popup list for selecting options.
std::optional< quint32 > audioClientFramesInUse(IAudioClient *client)
std::optional< quint32 > audioClientFramesAllocated(IAudioClient *client)
QAudioFormat waveFormatExToFormat(const WAVEFORMATEX &in)
Q_MULTIMEDIA_EXPORT QString errorString(HRESULT hr)
@ PreciseTimer
DBusConnection const char DBusError * error
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
#define qWarning
Definition qlogging.h:166
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLbitfield flags
GLenum GLuint GLintptr offset
GLsizei GLsizei GLchar * source
GLenum GLsizei len
#define emit
ptrdiff_t qsizetype
Definition qtypes.h:165
long long qint64
Definition qtypes.h:60
double qreal
Definition qtypes.h:187
QVideoFrameFormat::PixelFormat fmt
long HRESULT
QTextStream out(stdout)
[7]