1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39
40 #include "qwasapiaudiooutput.h"
41 #include "qwasapiutils.h"
42 #include <QtCore/qfunctions_winrt.h>
43 #include <QtCore/QMutexLocker>
44 #include <QtCore/QThread>
45 #include <QtCore/QTimer>
46
47 #include <Audioclient.h>
48 #include <functional>
49
50 using namespace Microsoft::WRL;
51
52 QT_BEGIN_NAMESPACE
53
54 Q_LOGGING_CATEGORY(lcMmAudioOutput, "qt.multimedia.audiooutput")
55
56 class WasapiOutputDevicePrivate : public QIODevice
57 {
58 Q_OBJECT
59 public:
60 WasapiOutputDevicePrivate(QWasapiAudioOutput* output);
61 ~WasapiOutputDevicePrivate();
62
63 qint64 readData(char* data, qint64 len);
64 qint64 writeData(const char* data, qint64 len);
65
66 private:
67 QWasapiAudioOutput *m_output;
68 QTimer m_timer;
69 };
70
WasapiOutputDevicePrivate(QWasapiAudioOutput * output)71 WasapiOutputDevicePrivate::WasapiOutputDevicePrivate(QWasapiAudioOutput* output)
72 : m_output(output)
73 {
74 qCDebug(lcMmAudioOutput) << __FUNCTION__;
75
76 m_timer.setSingleShot(true);
77 connect(&m_timer, &QTimer::timeout, [=](){
78 if (m_output->m_currentState == QAudio::ActiveState) {
79 m_output->m_currentState = QAudio::IdleState;
80 emit m_output->stateChanged(m_output->m_currentState);
81 m_output->m_currentError = QAudio::UnderrunError;
82 emit m_output->errorChanged(m_output->m_currentError);
83 }
84 });
85 }
86
~WasapiOutputDevicePrivate()87 WasapiOutputDevicePrivate::~WasapiOutputDevicePrivate()
88 {
89 qCDebug(lcMmAudioOutput) << __FUNCTION__;
90 }
91
readData(char * data,qint64 len)92 qint64 WasapiOutputDevicePrivate::readData(char* data, qint64 len)
93 {
94 qCDebug(lcMmAudioOutput) << __FUNCTION__;
95 Q_UNUSED(data)
96 Q_UNUSED(len)
97
98 return 0;
99 }
100
writeData(const char * data,qint64 len)101 qint64 WasapiOutputDevicePrivate::writeData(const char* data, qint64 len)
102 {
103 if (m_output->state() != QAudio::ActiveState && m_output->state() != QAudio::IdleState)
104 return 0;
105
106 QMutexLocker locker(&m_output->m_mutex);
107 m_timer.stop();
108
109 const quint32 channelCount = m_output->m_currentFormat.channelCount();
110 const quint32 sampleBytes = m_output->m_currentFormat.sampleSize() / 8;
111 const quint32 freeBytes = static_cast<quint32>(m_output->bytesFree());
112 const quint32 bytesToWrite = qMin(freeBytes, static_cast<quint32>(len));
113 const quint32 framesToWrite = bytesToWrite / (channelCount * sampleBytes);
114
115 BYTE *buffer;
116 HRESULT hr;
117 hr = m_output->m_renderer->GetBuffer(framesToWrite, &buffer);
118 if (hr != S_OK) {
119 m_output->m_currentError = QAudio::UnderrunError;
120 QMetaObject::invokeMethod(m_output, "errorChanged", Qt::QueuedConnection,
121 Q_ARG(QAudio::Error, QAudio::UnderrunError));
122 // Also Error Buffers need to be released
123 hr = m_output->m_renderer->ReleaseBuffer(framesToWrite, 0);
124 return 0;
125 }
126
127 memcpy_s(buffer, bytesToWrite, data, bytesToWrite);
128
129 hr = m_output->m_renderer->ReleaseBuffer(framesToWrite, 0);
130 if (hr != S_OK)
131 qFatal("Could not release buffer");
132
133 if (m_output->m_interval && m_output->m_openTime.elapsed() - m_output->m_openTimeOffset > m_output->m_interval) {
134 QMetaObject::invokeMethod(m_output, "notify", Qt::QueuedConnection);
135 m_output->m_openTimeOffset = m_output->m_openTime.elapsed();
136 }
137
138 m_output->m_bytesProcessed += bytesToWrite;
139
140 if (m_output->m_currentState != QAudio::ActiveState) {
141 m_output->m_currentState = QAudio::ActiveState;
142 emit m_output->stateChanged(m_output->m_currentState);
143 }
144 if (m_output->m_currentError != QAudio::NoError) {
145 m_output->m_currentError = QAudio::NoError;
146 emit m_output->errorChanged(m_output->m_currentError);
147 }
148
149 quint32 paddingFrames;
150 hr = m_output->m_interface->m_client->GetCurrentPadding(&paddingFrames);
151 const quint32 paddingBytes = paddingFrames * m_output->m_currentFormat.channelCount() * m_output->m_currentFormat.sampleSize() / 8;
152
153 m_timer.setInterval(m_output->m_currentFormat.durationForBytes(paddingBytes) / 1000);
154 m_timer.start();
155 return bytesToWrite;
156 }
157
QWasapiAudioOutput(const QByteArray & device)158 QWasapiAudioOutput::QWasapiAudioOutput(const QByteArray &device)
159 : m_deviceName(device)
160 , m_volumeCache(qreal(1.))
161 , m_currentState(QAudio::StoppedState)
162 , m_currentError(QAudio::NoError)
163 , m_interval(1000)
164 , m_pullMode(true)
165 , m_bufferFrames(0)
166 , m_bufferBytes(4096)
167 , m_eventThread(0)
168 {
169 qCDebug(lcMmAudioOutput) << __FUNCTION__ << device;
170 }
171
~QWasapiAudioOutput()172 QWasapiAudioOutput::~QWasapiAudioOutput()
173 {
174 qCDebug(lcMmAudioOutput) << __FUNCTION__;
175 stop();
176 }
177
setVolume(qreal vol)178 void QWasapiAudioOutput::setVolume(qreal vol)
179 {
180 qCDebug(lcMmAudioOutput) << __FUNCTION__ << vol;
181 m_volumeCache = vol;
182 if (m_volumeControl) {
183 quint32 channelCount;
184 HRESULT hr = m_volumeControl->GetChannelCount(&channelCount);
185 for (quint32 i = 0; i < channelCount; ++i) {
186 hr = m_volumeControl->SetChannelVolume(i, vol);
187 RETURN_VOID_IF_FAILED("Could not set audio volume.");
188 }
189 }
190 }
191
volume() const192 qreal QWasapiAudioOutput::volume() const
193 {
194 qCDebug(lcMmAudioOutput) << __FUNCTION__;
195 return m_volumeCache;
196 }
197
process()198 void QWasapiAudioOutput::process()
199 {
200 qCDebug(lcMmAudioOutput) << __FUNCTION__;
201 DWORD waitRet;
202
203 m_processing = true;
204 do {
205 waitRet = WaitForSingleObjectEx(m_event, 2000, FALSE);
206 if (waitRet != WAIT_OBJECT_0) {
207 qFatal("Returned while waiting for event.");
208 return;
209 }
210
211 QMutexLocker locker(&m_mutex);
212
213 if (m_currentState != QAudio::ActiveState && m_currentState != QAudio::IdleState)
214 break;
215 QMetaObject::invokeMethod(this, "processBuffer", Qt::QueuedConnection);
216 } while (m_processing);
217 }
218
processBuffer()219 void QWasapiAudioOutput::processBuffer()
220 {
221 QMutexLocker locker(&m_mutex);
222
223 const quint32 channelCount = m_currentFormat.channelCount();
224 const quint32 sampleBytes = m_currentFormat.sampleSize() / 8;
225 BYTE* buffer;
226 HRESULT hr;
227
228 quint32 paddingFrames;
229 hr = m_interface->m_client->GetCurrentPadding(&paddingFrames);
230
231 const quint32 availableFrames = m_bufferFrames - paddingFrames;
232 hr = m_renderer->GetBuffer(availableFrames, &buffer);
233 if (hr != S_OK) {
234 m_currentError = QAudio::UnderrunError;
235 emit errorChanged(m_currentError);
236 // Also Error Buffers need to be released
237 hr = m_renderer->ReleaseBuffer(availableFrames, 0);
238 ResetEvent(m_event);
239 return;
240 }
241
242 const quint32 readBytes = availableFrames * channelCount * sampleBytes;
243 const qint64 read = m_eventDevice->read((char*)buffer, readBytes);
244 if (read < static_cast<qint64>(readBytes)) {
245 // Fill the rest of the buffer with zero to avoid audio glitches
246 if (m_currentError != QAudio::UnderrunError) {
247 m_currentError = QAudio::UnderrunError;
248 emit errorChanged(m_currentError);
249 }
250 if (m_currentState != QAudio::IdleState) {
251 m_currentState = QAudio::IdleState;
252 emit stateChanged(m_currentState);
253 }
254 }
255
256 hr = m_renderer->ReleaseBuffer(availableFrames, 0);
257 if (hr != S_OK)
258 qFatal("Could not release buffer");
259 ResetEvent(m_event);
260
261 if (m_interval && m_openTime.elapsed() - m_openTimeOffset > m_interval) {
262 emit notify();
263 m_openTimeOffset = m_openTime.elapsed();
264 }
265
266 m_bytesProcessed += read;
267 m_processing = m_currentState == QAudio::ActiveState || m_currentState == QAudio::IdleState;
268 }
269
initStart(bool pull)270 bool QWasapiAudioOutput::initStart(bool pull)
271 {
272 if (m_currentState == QAudio::ActiveState || m_currentState == QAudio::IdleState)
273 stop();
274
275 QMutexLocker locker(&m_mutex);
276
277 m_interface = QWasapiUtils::createOrGetInterface(m_deviceName, QAudio::AudioOutput);
278 Q_ASSERT(m_interface);
279
280 m_pullMode = pull;
281 WAVEFORMATEX nFmt;
282 WAVEFORMATEX closest;
283 WAVEFORMATEX *pClose = &closest;
284
285 if (!m_currentFormat.isValid() || !QWasapiUtils::convertToNativeFormat(m_currentFormat, &nFmt)) {
286 m_currentError = QAudio::OpenError;
287 emit errorChanged(m_currentError);
288 return false;
289 }
290
291 HRESULT hr;
292
293 hr = m_interface->m_client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &nFmt, &pClose);
294 if (hr != S_OK) {
295 m_currentError = QAudio::OpenError;
296 emit errorChanged(m_currentError);
297 return false;
298 }
299
300 REFERENCE_TIME t = ((10000.0 * 10000 / nFmt.nSamplesPerSec * 1024) + 0.5);
301 if (m_bufferBytes)
302 t = m_currentFormat.durationForBytes(m_bufferBytes) * 100;
303
304 DWORD flags = pull ? AUDCLNT_STREAMFLAGS_EVENTCALLBACK : 0;
305 hr = m_interface->m_client->Initialize(AUDCLNT_SHAREMODE_SHARED, flags, t, 0, &nFmt, NULL);
306 EMIT_RETURN_FALSE_IF_FAILED("Could not initialize audio client.", QAudio::OpenError)
307
308 hr = m_interface->m_client->GetService(IID_PPV_ARGS(&m_renderer));
309 EMIT_RETURN_FALSE_IF_FAILED("Could not acquire render service.", QAudio::OpenError)
310
311 hr = m_interface->m_client->GetService(IID_PPV_ARGS(&m_volumeControl));
312 if (FAILED(hr))
313 qCDebug(lcMmAudioOutput) << "Could not acquire volume control.";
314
315 hr = m_interface->m_client->GetBufferSize(&m_bufferFrames);
316 EMIT_RETURN_FALSE_IF_FAILED("Could not access buffer size.", QAudio::OpenError)
317
318 if (m_pullMode) {
319 m_eventThread = new QWasapiProcessThread(this);
320 m_event = CreateEventEx(NULL, NULL, 0, EVENT_ALL_ACCESS);
321 m_eventThread->m_event = m_event;
322
323 hr = m_interface->m_client->SetEventHandle(m_event);
324 EMIT_RETURN_FALSE_IF_FAILED("Could not set event handle.", QAudio::OpenError)
325 } else {
326 m_eventDevice = new WasapiOutputDevicePrivate(this);
327 m_eventDevice->open(QIODevice::WriteOnly|QIODevice::Unbuffered);
328 }
329 // Send some initial data, do not exit on failure, latest in process
330 // those an error will be caught
331 BYTE *pdata = nullptr;
332 hr = m_renderer->GetBuffer(m_bufferFrames, &pdata);
333 hr = m_renderer->ReleaseBuffer(m_bufferFrames, AUDCLNT_BUFFERFLAGS_SILENT);
334
335 hr = m_interface->m_client->Start();
336 EMIT_RETURN_FALSE_IF_FAILED("Could not start audio render client.", QAudio::OpenError)
337
338 m_openTime.restart();
339 m_openTimeOffset = 0;
340 m_bytesProcessed = 0;
341 return true;
342 }
343
error() const344 QAudio::Error QWasapiAudioOutput::error() const
345 {
346 qCDebug(lcMmAudioOutput) << __FUNCTION__ << m_currentError;
347 return m_currentError;
348 }
349
state() const350 QAudio::State QWasapiAudioOutput::state() const
351 {
352 qCDebug(lcMmAudioOutput) << __FUNCTION__;
353 return m_currentState;
354 }
355
start(QIODevice * device)356 void QWasapiAudioOutput::start(QIODevice *device)
357 {
358 qCDebug(lcMmAudioOutput) << __FUNCTION__ << device;
359 if (!initStart(true)) {
360 qCDebug(lcMmAudioOutput) << __FUNCTION__ << "failed";
361 return;
362 }
363 m_eventDevice = device;
364
365 m_mutex.lock();
366 m_currentState = QAudio::ActiveState;
367 m_mutex.unlock();
368 emit stateChanged(m_currentState);
369 m_eventThread->start();
370 }
371
start()372 QIODevice *QWasapiAudioOutput::start()
373 {
374 qCDebug(lcMmAudioOutput) << __FUNCTION__;
375
376 if (!initStart(false)) {
377 qCDebug(lcMmAudioOutput) << __FUNCTION__ << "failed";
378 return nullptr;
379 }
380
381 m_mutex.lock();
382 m_currentState = QAudio::IdleState;
383 m_mutex.unlock();
384 emit stateChanged(m_currentState);
385
386 return m_eventDevice;
387 }
388
stop()389 void QWasapiAudioOutput::stop()
390 {
391 qCDebug(lcMmAudioOutput) << __FUNCTION__;
392 if (m_currentState == QAudio::StoppedState)
393 return;
394
395 if (!m_pullMode) {
396 HRESULT hr;
397 hr = m_interface->m_client->Stop();
398 hr = m_interface->m_client->Reset();
399 }
400
401 m_mutex.lock();
402 m_currentState = QAudio::StoppedState;
403 m_mutex.unlock();
404 emit stateChanged(m_currentState);
405 if (m_currentError != QAudio::NoError) {
406 m_mutex.lock();
407 m_currentError = QAudio::NoError;
408 m_mutex.unlock();
409 emit errorChanged(m_currentError);
410 }
411
412 HRESULT hr = m_interface->m_client->Stop();
413 if (m_currentState == QAudio::StoppedState) {
414 hr = m_interface->m_client->Reset();
415 }
416
417 if (m_eventThread) {
418 SetEvent(m_eventThread->m_event);
419 while (m_eventThread->isRunning())
420 QThread::yieldCurrentThread();
421 m_eventThread->deleteLater();
422 m_eventThread = 0;
423 }
424 }
425
bytesFree() const426 int QWasapiAudioOutput::bytesFree() const
427 {
428 qCDebug(lcMmAudioOutput) << __FUNCTION__;
429 if (m_currentState != QAudio::ActiveState && m_currentState != QAudio::IdleState)
430 return 0;
431
432 quint32 paddingFrames;
433 HRESULT hr = m_interface->m_client->GetCurrentPadding(&paddingFrames);
434 if (FAILED(hr)) {
435 qCDebug(lcMmAudioOutput) << __FUNCTION__ << "Could not query padding frames.";
436 return bufferSize();
437 }
438
439 const quint32 availableFrames = m_bufferFrames - paddingFrames;
440 const quint32 res = availableFrames * m_currentFormat.channelCount() * m_currentFormat.sampleSize() / 8;
441 return res;
442 }
443
periodSize() const444 int QWasapiAudioOutput::periodSize() const
445 {
446 qCDebug(lcMmAudioOutput) << __FUNCTION__;
447 REFERENCE_TIME defaultDevicePeriod;
448 HRESULT hr = m_interface->m_client->GetDevicePeriod(&defaultDevicePeriod, NULL);
449 if (FAILED(hr))
450 return 0;
451 const QAudioFormat f = m_currentFormat.isValid() ? m_currentFormat : m_interface->m_mixFormat;
452 const int res = m_currentFormat.bytesForDuration(defaultDevicePeriod / 10);
453 return res;
454 }
455
setBufferSize(int value)456 void QWasapiAudioOutput::setBufferSize(int value)
457 {
458 qCDebug(lcMmAudioOutput) << __FUNCTION__ << value;
459 if (m_currentState == QAudio::ActiveState || m_currentState == QAudio::IdleState)
460 return;
461 m_bufferBytes = value;
462 }
463
bufferSize() const464 int QWasapiAudioOutput::bufferSize() const
465 {
466 qCDebug(lcMmAudioOutput) << __FUNCTION__;
467 if (m_currentState == QAudio::ActiveState || m_currentState == QAudio::IdleState)
468 return m_bufferFrames * m_currentFormat.channelCount()* m_currentFormat.sampleSize() / 8;
469
470 return m_bufferBytes;
471 }
472
setNotifyInterval(int ms)473 void QWasapiAudioOutput::setNotifyInterval(int ms)
474 {
475 qCDebug(lcMmAudioOutput) << __FUNCTION__ << ms;
476 m_interval = qMax(0, ms);
477 }
478
notifyInterval() const479 int QWasapiAudioOutput::notifyInterval() const
480 {
481 qCDebug(lcMmAudioOutput) << __FUNCTION__;
482 return m_interval;
483 }
484
processedUSecs() const485 qint64 QWasapiAudioOutput::processedUSecs() const
486 {
487 qCDebug(lcMmAudioOutput) << __FUNCTION__;
488 if (m_currentState == QAudio::StoppedState)
489 return 0;
490 qint64 res = qint64(1000000) * m_bytesProcessed /
491 (m_currentFormat.channelCount() * (m_currentFormat.sampleSize() / 8)) /
492 m_currentFormat.sampleRate();
493
494 return res;
495 }
496
resume()497 void QWasapiAudioOutput::resume()
498 {
499 qCDebug(lcMmAudioOutput) << __FUNCTION__;
500
501 if (m_currentState != QAudio::SuspendedState)
502 return;
503
504 HRESULT hr = m_interface->m_client->Start();
505 EMIT_RETURN_VOID_IF_FAILED("Could not start audio render client.", QAudio::FatalError)
506
507 m_mutex.lock();
508 m_currentError = QAudio::NoError;
509 m_currentState = m_pullMode ? QAudio::ActiveState : QAudio::IdleState;
510 m_mutex.unlock();
511 emit stateChanged(m_currentState);
512 if (m_eventThread)
513 m_eventThread->start();
514 }
515
setFormat(const QAudioFormat & fmt)516 void QWasapiAudioOutput::setFormat(const QAudioFormat& fmt)
517 {
518 qCDebug(lcMmAudioOutput) << __FUNCTION__ << fmt;
519 if (m_currentState != QAudio::StoppedState)
520 return;
521 m_currentFormat = fmt;
522 }
523
format() const524 QAudioFormat QWasapiAudioOutput::format() const
525 {
526 qCDebug(lcMmAudioOutput) << __FUNCTION__;
527 return m_currentFormat;
528 }
529
suspend()530 void QWasapiAudioOutput::suspend()
531 {
532 qCDebug(lcMmAudioOutput) << __FUNCTION__;
533
534 if (m_currentState != QAudio::ActiveState && m_currentState != QAudio::IdleState)
535 return;
536
537 m_mutex.lock();
538 m_currentState = QAudio::SuspendedState;
539 m_mutex.unlock();
540 emit stateChanged(m_currentState);
541
542 HRESULT hr = m_interface->m_client->Stop();
543 EMIT_RETURN_VOID_IF_FAILED("Could not suspend audio render client.", QAudio::FatalError);
544 if (m_eventThread) {
545 SetEvent(m_eventThread->m_event);
546 while (m_eventThread->isRunning())
547 QThread::yieldCurrentThread();
548 qCDebug(lcMmAudioOutput) << __FUNCTION__ << "Thread has stopped";
549 }
550 }
551
elapsedUSecs() const552 qint64 QWasapiAudioOutput::elapsedUSecs() const
553 {
554 qCDebug(lcMmAudioOutput) << __FUNCTION__;
555 if (m_currentState == QAudio::StoppedState)
556 return 0;
557 return m_openTime.elapsed() * qint64(1000);
558 }
559
reset()560 void QWasapiAudioOutput::reset()
561 {
562 qCDebug(lcMmAudioOutput) << __FUNCTION__;
563 stop();
564 }
565
566 QT_END_NAMESPACE
567
568 #include "qwasapiaudiooutput.moc"
569