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