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
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
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#include "qwindowsaudiosink_p.h"
18#include "qcomtaskresource_p.h"
19
20#include <QtCore/QDataStream>
21#include <QtCore/qtimer.h>
22#include <QtCore/qloggingcategory.h>
23#include <QtCore/qpointer.h>
24
25#include <private/qaudiohelpers_p.h>
26
27#include <audioclient.h>
28#include <mmdeviceapi.h>
29
31
32static Q_LOGGING_CATEGORY(qLcAudioOutput, "qt.multimedia.audiooutput")
33
34using namespace QWindowsMultimediaUtils;
35
37{
39public:
40 OutputPrivate(QWindowsAudioSink &audio) : QIODevice(&audio), audioDevice(audio) {}
41 ~OutputPrivate() override = default;
42
43 qint64 readData(char *, qint64) override { return 0; }
44 qint64 writeData(const char *data, qint64 len) override { return audioDevice.push(data, len); }
45
46private:
47 QWindowsAudioSink &audioDevice;
48};
49
50std::optional<quint32> audioClientFramesAvailable(IAudioClient *client)
51{
52 auto framesAllocated = QWindowsAudioUtils::audioClientFramesAllocated(client);
53 auto framesInUse = QWindowsAudioUtils::audioClientFramesInUse(client);
54
55 if (framesAllocated && framesInUse)
56 return *framesAllocated - *framesInUse;
57 return {};
58}
59
61 QPlatformAudioSink(parent),
62 m_timer(new QTimer(this)),
63 m_pushSource(new OutputPrivate(*this)),
64 m_device(std::move(device))
65{
67 m_timer->setSingleShot(true);
69}
70
75
76qint64 QWindowsAudioSink::remainingPlayTimeUs()
77{
78 auto framesInUse = QWindowsAudioUtils::audioClientFramesInUse(m_audioClient.Get());
79 return m_resampler.outputFormat().durationForFrames(framesInUse ? *framesInUse : 0);
80}
81
82void QWindowsAudioSink::deviceStateChange(QAudio::State state, QAudio::Error error)
83{
84 if (state != deviceState) {
86 m_audioClient->Start();
87 qCDebug(qLcAudioOutput) << "Audio client started";
88
89 } else if (deviceState == QAudio::ActiveState) {
90 m_timer->stop();
91 m_audioClient->Stop();
92 qCDebug(qLcAudioOutput) << "Audio client stopped";
93 }
94
95 QPointer<QWindowsAudioSink> thisGuard(this);
96 deviceState = state;
97 emit stateChanged(deviceState);
98 if (!thisGuard)
99 return;
100 }
101
102 if (error != errorState) {
103 errorState = error;
105 }
106}
107
109{
110 return m_format;
111}
112
114{
115 if (deviceState == QAudio::StoppedState)
116 m_format = fmt;
117}
118
119void QWindowsAudioSink::pullSource()
120{
121 qCDebug(qLcAudioOutput) << "Pull source";
122 if (!m_pullSource)
123 return;
124
125 auto bytesAvailable = m_pullSource->isOpen() ? qsizetype(m_pullSource->bytesAvailable()) : 0;
126 auto readLen = qMin(bytesFree(), bytesAvailable);
127 if (readLen > 0) {
128 QByteArray samples = m_pullSource->read(readLen);
129 if (samples.size() == 0) {
130 deviceStateChange(QAudio::IdleState, QAudio::IOError);
131 return;
132 } else {
133 write(samples.data(), samples.size());
134 }
135 }
136
137 auto playTimeUs = remainingPlayTimeUs();
138 if (playTimeUs == 0) {
139 deviceStateChange(QAudio::IdleState, m_pullSource->atEnd() ? QAudio::NoError : QAudio::UnderrunError);
140 } else {
141 deviceStateChange(QAudio::ActiveState, QAudio::NoError);
142 m_timer->start(playTimeUs / 2000);
143 }
144}
145
147{
148 qCDebug(qLcAudioOutput) << "start(ioDevice)" << deviceState;
149 if (deviceState != QAudio::StoppedState)
150 close();
151
152 if (device == nullptr)
153 return;
154
155 if (!open()) {
156 errorState = QAudio::OpenError;
158 return;
159 }
160
161 m_pullSource = device;
162
163 connect(device, &QIODevice::readyRead, this, &QWindowsAudioSink::pullSource);
164 m_timer->disconnect();
165 m_timer->callOnTimeout(this, &QWindowsAudioSink::pullSource);
166 pullSource();
167}
168
169qint64 QWindowsAudioSink::push(const char *data, qint64 len)
170{
171 if (deviceState == QAudio::StoppedState)
172 return -1;
173
174 qint64 written = write(data, len);
175 if (written > 0) {
176 deviceStateChange(QAudio::ActiveState, QAudio::NoError);
177 m_timer->start(remainingPlayTimeUs() /1000);
178 }
179
180 return written;
181}
182
184{
185 qCDebug(qLcAudioOutput) << "start()";
186 if (deviceState != QAudio::StoppedState)
187 close();
188
189 if (!open()) {
190 errorState = QAudio::OpenError;
192 return nullptr;
193 }
194
195 deviceStateChange(QAudio::IdleState, QAudio::NoError);
196
197 m_timer->disconnect();
198 m_timer->callOnTimeout(this, [this](){
199 deviceStateChange(QAudio::IdleState, QAudio::UnderrunError);
200 });
201
202 return m_pushSource.get();
203}
204
205bool QWindowsAudioSink::open()
206{
207 if (m_audioClient)
208 return true;
209
210 HRESULT hr = m_device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER,
211 nullptr, (void**)m_audioClient.GetAddressOf());
212 if (FAILED(hr)) {
213 qCWarning(qLcAudioOutput) << "Failed to activate audio device" << errorString(hr);
214 return false;
215 }
216
217 auto resetClient = qScopeGuard([this](){ m_audioClient.Reset(); });
218
219 QComTaskResource<WAVEFORMATEX> pwfx;
220 hr = m_audioClient->GetMixFormat(pwfx.address());
221 if (FAILED(hr)) {
222 qCWarning(qLcAudioOutput) << "Format unsupported" << errorString(hr);
223 return false;
224 }
225
226 if (!m_resampler.setup(m_format, QWindowsAudioUtils::waveFormatExToFormat(*pwfx))) {
227 qCWarning(qLcAudioOutput) << "Failed to set up resampler";
228 return false;
229 }
230
231 if (m_bufferSize == 0)
232 m_bufferSize = m_format.sampleRate() * m_format.bytesPerFrame() / 2;
233
234 REFERENCE_TIME requestedDuration = m_format.durationForBytes(m_bufferSize) * 10;
235
236 hr = m_audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, requestedDuration, 0, pwfx.get(),
237 nullptr);
238
239 if (FAILED(hr)) {
240 qCWarning(qLcAudioOutput) << "Failed to initialize audio client" << errorString(hr);
241 return false;
242 }
243
244 auto framesAllocated = QWindowsAudioUtils::audioClientFramesAllocated(m_audioClient.Get());
245 if (!framesAllocated) {
246 qCWarning(qLcAudioOutput) << "Failed to get audio client buffer size";
247 return false;
248 }
249
250 m_bufferSize = m_format.bytesForDuration(
251 m_resampler.outputFormat().durationForFrames(*framesAllocated));
252
253 hr = m_audioClient->GetService(__uuidof(IAudioRenderClient), (void**)m_renderClient.GetAddressOf());
254 if (FAILED(hr)) {
255 qCWarning(qLcAudioOutput) << "Failed to obtain audio client rendering service"
256 << errorString(hr);
257 return false;
258 }
259
260 resetClient.dismiss();
261
262 return true;
263}
264
265void QWindowsAudioSink::close()
266{
267 qCDebug(qLcAudioOutput) << "close()";
268 if (deviceState == QAudio::StoppedState)
269 return;
270
271 deviceStateChange(QAudio::StoppedState, QAudio::NoError);
272
273 if (m_pullSource)
274 disconnect(m_pullSource, &QIODevice::readyRead, this, &QWindowsAudioSink::pullSource);
275 m_audioClient.Reset();
276 m_renderClient.Reset();
277 m_pullSource = nullptr;
278}
279
281{
282 if (!m_audioClient)
283 return 0;
284
285 auto framesAvailable = audioClientFramesAvailable(m_audioClient.Get());
286 if (framesAvailable)
287 return m_resampler.inputBufferSize(*framesAvailable * m_resampler.outputFormat().bytesPerFrame());
288 return 0;
289}
290
292{
293 if (!m_audioClient)
294 m_bufferSize = value;
295}
296
298{
299 if (!m_audioClient)
300 return 0;
301
302 return m_format.durationForBytes(m_resampler.totalInputBytes());
303}
304
305qint64 QWindowsAudioSink::write(const char *data, qint64 len)
306{
307 Q_ASSERT(m_audioClient);
308 Q_ASSERT(m_renderClient);
309
310 qCDebug(qLcAudioOutput) << "write()" << len;
311
312 auto framesAvailable = audioClientFramesAvailable(m_audioClient.Get());
313 if (!framesAvailable)
314 return -1;
315
316 auto maxBytesCanWrite = m_format.bytesForDuration(
317 m_resampler.outputFormat().durationForFrames(*framesAvailable));
318 qsizetype writeSize = qMin(maxBytesCanWrite, len);
319
320 QByteArray writeBytes = m_resampler.resample({data, writeSize});
321 qint32 writeFramesNum = m_resampler.outputFormat().framesForBytes(writeBytes.size());
322
323 quint8 *buffer = nullptr;
324 HRESULT hr = m_renderClient->GetBuffer(writeFramesNum, &buffer);
325 if (FAILED(hr)) {
326 qCWarning(qLcAudioOutput) << "Failed to get buffer" << errorString(hr);
327 return -1;
328 }
329
330 if (m_volume < qreal(1.0))
331 QAudioHelperInternal::qMultiplySamples(m_volume, m_resampler.outputFormat(), writeBytes.data(), buffer, writeBytes.size());
332 else
333 std::memcpy(buffer, writeBytes.data(), writeBytes.size());
334
335 DWORD flags = writeBytes.isEmpty() ? AUDCLNT_BUFFERFLAGS_SILENT : 0;
336 hr = m_renderClient->ReleaseBuffer(writeFramesNum, flags);
337 if (FAILED(hr)) {
338 qCWarning(qLcAudioOutput) << "Failed to return buffer" << errorString(hr);
339 return -1;
340 }
341
342 return writeSize;
343}
344
346{
347 qCDebug(qLcAudioOutput) << "resume()";
348 if (deviceState == QAudio::SuspendedState) {
349 if (m_pullSource) {
350 pullSource();
351 } else {
352 deviceStateChange(suspendedInState, QAudio::NoError);
353 if (remainingPlayTimeUs() > 0)
354 m_audioClient->Start();
355 }
356 }
357}
358
360{
361 qCDebug(qLcAudioOutput) << "suspend()";
362 if (deviceState == QAudio::ActiveState || deviceState == QAudio::IdleState) {
363 suspendedInState = deviceState;
364 deviceStateChange(QAudio::SuspendedState, QAudio::NoError);
365 }
366}
367
369{
370 if (qFuzzyCompare(m_volume, v))
371 return;
372
373 m_volume = qBound(qreal(0), v, qreal(1));
374}
375
377 // TODO: investigate and find a way to drain and stop instead of closing
378 close();
379}
380
382{
383 close();
384}
385
387
388#include "qwindowsaudiosink.moc"
IOBluetoothDevice * device
qint64 writeData(const char *data, qint64 len) override
Writes up to maxSize bytes from data to the device.
qint64 readData(char *, qint64) override
Reads up to maxSize bytes from the device into data, and returns the number of bytes read or -1 if an...
~OutputPrivate() override=default
OutputPrivate(QWindowsAudioSink &audio)
The QAudioFormat class stores audio stream parameter information.
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.
Q_MULTIMEDIA_EXPORT qint32 framesForBytes(qint32 byteCount) const
Returns the number of frames represented by byteCount 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
\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...
bool isOpen() const
Returns true if the device is open; otherwise returns false.
virtual qint64 bytesAvailable() const
Returns the number of bytes that are available for reading.
virtual bool atEnd() const
Returns true if the current read and write position is at the end of the device (i....
qint64 read(char *data, qint64 maxlen)
Reads at most maxSize bytes from the device into data, and returns the number of bytes read.
\inmodule QtCore
Definition qobject.h:103
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
static bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *member)
\threadsafe
Definition qobject.cpp:3236
T * get() const noexcept
\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
void setFormat(const QAudioFormat &fmt) override
void suspend() override
QAudioFormat format() const override
QAudio::State state() const override
void setVolume(qreal) override
qsizetype bytesFree() const override
void setBufferSize(qsizetype value) override
QAudio::Error error() const override
QWindowsAudioSink(ComPtr< IMMDevice > device, QObject *parent)
QIODevice * start() override
quint64 totalInputBytes() const
bool setup(const QAudioFormat &in, const QAudioFormat &out)
QByteArray resample(const QByteArrayView &in)
quint64 inputBufferSize(quint64 outputBufferSize) 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
@ UnderrunError
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]
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:333
#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
GLsizei const GLfloat * v
[13]
GLsizei samples
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint buffer
GLbitfield flags
GLenum GLsizei len
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QScopeGuard< typename std::decay< F >::type > qScopeGuard(F &&f)
[qScopeGuard]
Definition qscopeguard.h:60
#define Q_OBJECT
#define emit
int qint32
Definition qtypes.h:49
ptrdiff_t qsizetype
Definition qtypes.h:165
long long qint64
Definition qtypes.h:60
double qreal
Definition qtypes.h:187
unsigned char quint8
Definition qtypes.h:46
QVideoFrameFormat::PixelFormat fmt
long HRESULT
std::optional< quint32 > audioClientFramesAvailable(IAudioClient *client)
myObject disconnect()
[26]