1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
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 <QtCore/qcoreapplication.h>
41 #include <QtCore/qdebug.h>
42 #include <QtCore/qmath.h>
43 #include <private/qaudiohelpers_p.h>
44 
45 #include "qaudiooutput_pulse.h"
46 #include "qaudiodeviceinfo_pulse.h"
47 #include "qpulseaudioengine.h"
48 #include "qpulsehelpers.h"
49 #include <sys/types.h>
50 #include <unistd.h>
51 
52 QT_BEGIN_NAMESPACE
53 
54 const int PeriodTimeMs = 20;
55 const int LowLatencyPeriodTimeMs = 10;
56 const int LowLatencyBufferSizeMs = 40;
57 
58 #define LOW_LATENCY_CATEGORY_NAME "game"
59 
outputStreamWriteCallback(pa_stream * stream,size_t length,void * userdata)60 static void  outputStreamWriteCallback(pa_stream *stream, size_t length, void *userdata)
61 {
62     Q_UNUSED(stream);
63     Q_UNUSED(length);
64     Q_UNUSED(userdata);
65     QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
66     pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
67 }
68 
outputStreamStateCallback(pa_stream * stream,void * userdata)69 static void outputStreamStateCallback(pa_stream *stream, void *userdata)
70 {
71     Q_UNUSED(userdata)
72     pa_stream_state_t state = pa_stream_get_state(stream);
73 #ifdef DEBUG_PULSE
74     qDebug() << "Stream state: " << QPulseAudioInternal::stateToQString(state);
75 #endif
76     switch (state) {
77         case PA_STREAM_CREATING:
78         case PA_STREAM_READY:
79         case PA_STREAM_TERMINATED:
80             break;
81 
82         case PA_STREAM_FAILED:
83         default:
84             qWarning() << QString("Stream error: %1").arg(pa_strerror(pa_context_errno(pa_stream_get_context(stream))));
85             QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
86             pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
87             break;
88     }
89 }
90 
outputStreamUnderflowCallback(pa_stream * stream,void * userdata)91 static void outputStreamUnderflowCallback(pa_stream *stream, void *userdata)
92 {
93     Q_UNUSED(stream)
94     ((QPulseAudioOutput*)userdata)->streamUnderflowCallback();
95 }
96 
outputStreamOverflowCallback(pa_stream * stream,void * userdata)97 static void outputStreamOverflowCallback(pa_stream *stream, void *userdata)
98 {
99     Q_UNUSED(stream)
100     Q_UNUSED(userdata)
101     qWarning() << "Got a buffer overflow!";
102 }
103 
outputStreamLatencyCallback(pa_stream * stream,void * userdata)104 static void outputStreamLatencyCallback(pa_stream *stream, void *userdata)
105 {
106     Q_UNUSED(stream)
107     Q_UNUSED(userdata)
108 
109 #ifdef DEBUG_PULSE
110     const pa_timing_info *info = pa_stream_get_timing_info(stream);
111 
112     qDebug() << "Write index corrupt: " << info->write_index_corrupt;
113     qDebug() << "Write index: " << info->write_index;
114     qDebug() << "Read index corrupt: " << info->read_index_corrupt;
115     qDebug() << "Read index: " << info->read_index;
116     qDebug() << "Sink usec: " << info->sink_usec;
117     qDebug() << "Configured sink usec: " << info->configured_sink_usec;
118 #endif
119 }
120 
outputStreamSuccessCallback(pa_stream * stream,int success,void * userdata)121 static void outputStreamSuccessCallback(pa_stream *stream, int success, void *userdata)
122 {
123     Q_UNUSED(stream);
124     Q_UNUSED(success);
125     Q_UNUSED(userdata);
126 
127     QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
128     pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
129 }
130 
outputStreamDrainComplete(pa_stream * stream,int success,void * userdata)131 static void outputStreamDrainComplete(pa_stream *stream, int success, void *userdata)
132 {
133     Q_UNUSED(stream);
134     Q_UNUSED(success);
135     Q_UNUSED(userdata);
136 
137 #ifdef DEBUG_PULSE
138     qDebug() << "Draining completed successfully: " << (bool)success;
139 #endif
140 }
141 
streamAdjustPrebufferCallback(pa_stream * stream,int success,void * userdata)142 static void streamAdjustPrebufferCallback(pa_stream *stream, int success, void *userdata)
143 {
144     Q_UNUSED(stream);
145     Q_UNUSED(success);
146     Q_UNUSED(userdata);
147 
148 #ifdef DEBUG_PULSE
149     qDebug() << "Adjust prebuffer completed successfully: " << (bool)success;
150 #endif
151 }
152 
153 
QPulseAudioOutput(const QByteArray & device)154 QPulseAudioOutput::QPulseAudioOutput(const QByteArray &device)
155     : m_device(device)
156     , m_errorState(QAudio::NoError)
157     , m_deviceState(QAudio::StoppedState)
158     , m_pullMode(true)
159     , m_opened(false)
160     , m_audioSource(0)
161     , m_periodTime(0)
162     , m_stream(0)
163     , m_notifyInterval(1000)
164     , m_periodSize(0)
165     , m_bufferSize(0)
166     , m_maxBufferSize(0)
167     , m_totalTimeValue(0)
168     , m_tickTimer(new QTimer(this))
169     , m_audioBuffer(0)
170     , m_resuming(false)
171     , m_volume(1.0)
172 {
173     connect(m_tickTimer, SIGNAL(timeout()), SLOT(userFeed()));
174 }
175 
~QPulseAudioOutput()176 QPulseAudioOutput::~QPulseAudioOutput()
177 {
178     close();
179     disconnect(m_tickTimer, SIGNAL(timeout()));
180     QCoreApplication::processEvents();
181 }
182 
setError(QAudio::Error error)183 void QPulseAudioOutput::setError(QAudio::Error error)
184 {
185     if (m_errorState == error)
186         return;
187 
188     m_errorState = error;
189     emit errorChanged(error);
190 }
191 
error() const192 QAudio::Error QPulseAudioOutput::error() const
193 {
194     return m_errorState;
195 }
196 
setState(QAudio::State state)197 void QPulseAudioOutput::setState(QAudio::State state)
198 {
199     if (m_deviceState == state)
200         return;
201 
202     m_deviceState = state;
203     emit stateChanged(state);
204 }
205 
state() const206 QAudio::State QPulseAudioOutput::state() const
207 {
208     return m_deviceState;
209 }
210 
streamUnderflowCallback()211 void QPulseAudioOutput::streamUnderflowCallback()
212 {
213     if (m_deviceState != QAudio::IdleState && !m_resuming) {
214         setError(QAudio::UnderrunError);
215         setState(QAudio::IdleState);
216     }
217 }
218 
start(QIODevice * device)219 void QPulseAudioOutput::start(QIODevice *device)
220 {
221     setState(QAudio::StoppedState);
222     setError(QAudio::NoError);
223 
224     // Handle change of mode
225     if (m_audioSource && !m_pullMode) {
226         delete m_audioSource;
227     }
228     m_audioSource = 0;
229 
230     close();
231 
232     m_pullMode = true;
233     m_audioSource = device;
234 
235     if (!open()) {
236         m_audioSource = 0;
237         return;
238     }
239 
240     setState(QAudio::ActiveState);
241 }
242 
start()243 QIODevice *QPulseAudioOutput::start()
244 {
245     setState(QAudio::StoppedState);
246     setError(QAudio::NoError);
247 
248     // Handle change of mode
249     if (m_audioSource && !m_pullMode) {
250         delete m_audioSource;
251     }
252     m_audioSource = 0;
253 
254     close();
255 
256     m_pullMode = false;
257 
258     if (!open())
259         return nullptr;
260 
261     m_audioSource = new PulseOutputPrivate(this);
262     m_audioSource->open(QIODevice::WriteOnly|QIODevice::Unbuffered);
263 
264     setState(QAudio::IdleState);
265 
266     return m_audioSource;
267 }
268 
open()269 bool QPulseAudioOutput::open()
270 {
271     if (m_opened)
272         return true;
273 
274     QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
275 
276     if (!pulseEngine->context() || pa_context_get_state(pulseEngine->context()) != PA_CONTEXT_READY) {
277         setError(QAudio::FatalError);
278         setState(QAudio::StoppedState);
279         emit stateChanged(m_deviceState);
280         return false;
281     }
282 
283     pa_sample_spec spec = QPulseAudioInternal::audioFormatToSampleSpec(m_format);
284 
285     if (!pa_sample_spec_valid(&spec)) {
286         setError(QAudio::OpenError);
287         setState(QAudio::StoppedState);
288         emit stateChanged(m_deviceState);
289         return false;
290     }
291 
292     m_spec = spec;
293     m_totalTimeValue = 0;
294 
295     if (m_streamName.isNull())
296         m_streamName = QString(QLatin1String("QtmPulseStream-%1-%2")).arg(::getpid()).arg(quintptr(this)).toUtf8();
297 
298 #ifdef DEBUG_PULSE
299     qDebug() << "Format: " << QPulseAudioInternal::sampleFormatToQString(spec.format);
300     qDebug() << "Rate: " << spec.rate;
301     qDebug() << "Channels: " << spec.channels;
302     qDebug() << "Frame size: " << pa_frame_size(&spec);
303 #endif
304 
305     pulseEngine->lock();
306 
307     qint64 bytesPerSecond = m_format.sampleRate() * m_format.channelCount() * m_format.sampleSize() / 8;
308 
309     pa_proplist *propList = pa_proplist_new();
310     if (!m_category.isNull())
311         pa_proplist_sets(propList, PA_PROP_MEDIA_ROLE, m_category.toLatin1().constData());
312 
313     static const auto mapName = qEnvironmentVariable("QT_PA_CHANNEL_MAP");
314     pa_channel_map_def_t mapDef = PA_CHANNEL_MAP_DEFAULT;
315     if (mapName == QLatin1String("ALSA"))
316         mapDef = PA_CHANNEL_MAP_ALSA;
317     else if (mapName == QLatin1String("AUX"))
318         mapDef = PA_CHANNEL_MAP_AUX;
319     else if (mapName == QLatin1String("WAVEEX"))
320         mapDef = PA_CHANNEL_MAP_WAVEEX;
321     else if (mapName == QLatin1String("OSS"))
322         mapDef = PA_CHANNEL_MAP_OSS;
323     else if (!mapName.isEmpty())
324         qWarning() << "Unknown pulse audio channel mapping definition:" << mapName;
325 
326     pa_channel_map m;
327     auto channelMap = pa_channel_map_init_extend(&m, m_spec.channels, mapDef);
328     if (!channelMap)
329         qWarning() << "QAudioOutput: pa_channel_map_init_extend() Could not initialize channel map";
330 
331     m_stream = pa_stream_new_with_proplist(pulseEngine->context(), m_streamName.constData(), &m_spec, channelMap, propList);
332     if (!m_stream) {
333         qWarning() << "QAudioOutput: pa_stream_new_with_proplist() failed!";
334         pulseEngine->unlock();
335         setError(QAudio::OpenError);
336         setState(QAudio::StoppedState);
337         emit stateChanged(m_deviceState);
338         return false;
339     }
340 
341     pa_proplist_free(propList);
342 
343     pa_stream_set_state_callback(m_stream, outputStreamStateCallback, this);
344     pa_stream_set_write_callback(m_stream, outputStreamWriteCallback, this);
345 
346     pa_stream_set_underflow_callback(m_stream, outputStreamUnderflowCallback, this);
347     pa_stream_set_overflow_callback(m_stream, outputStreamOverflowCallback, this);
348     pa_stream_set_latency_update_callback(m_stream, outputStreamLatencyCallback, this);
349 
350     if (m_bufferSize <= 0 && m_category == LOW_LATENCY_CATEGORY_NAME) {
351         m_bufferSize = bytesPerSecond * LowLatencyBufferSizeMs / qint64(1000);
352     }
353 
354     pa_buffer_attr requestedBuffer;
355     requestedBuffer.fragsize = (uint32_t)-1;
356     requestedBuffer.maxlength = (uint32_t)-1;
357     requestedBuffer.minreq = (uint32_t)-1;
358     requestedBuffer.prebuf = (uint32_t)-1;
359     requestedBuffer.tlength = m_bufferSize;
360 
361     if (pa_stream_connect_playback(m_stream, m_device.data(), (m_bufferSize > 0) ? &requestedBuffer : NULL, (pa_stream_flags_t)0, NULL, NULL) < 0) {
362         qWarning() << "pa_stream_connect_playback() failed!";
363         pa_stream_unref(m_stream);
364         m_stream = 0;
365         pulseEngine->unlock();
366         setError(QAudio::OpenError);
367         setState(QAudio::StoppedState);
368         emit stateChanged(m_deviceState);
369         return false;
370     }
371 
372     while (pa_stream_get_state(m_stream) != PA_STREAM_READY)
373         pa_threaded_mainloop_wait(pulseEngine->mainloop());
374 
375     const pa_buffer_attr *buffer = pa_stream_get_buffer_attr(m_stream);
376     m_periodTime = (m_category == LOW_LATENCY_CATEGORY_NAME) ? LowLatencyPeriodTimeMs : PeriodTimeMs;
377     m_periodSize = pa_usec_to_bytes(m_periodTime*1000, &m_spec);
378     m_bufferSize = buffer->tlength;
379     m_maxBufferSize = buffer->maxlength;
380     m_audioBuffer = new char[m_maxBufferSize];
381 
382     const qint64 streamSize = m_audioSource ? m_audioSource->size() : 0;
383     if (m_pullMode && streamSize > 0 && static_cast<qint64>(buffer->prebuf) > streamSize) {
384         pa_buffer_attr newBufferAttr;
385         newBufferAttr = *buffer;
386         newBufferAttr.prebuf = streamSize;
387         pa_operation *o = pa_stream_set_buffer_attr(m_stream, &newBufferAttr, streamAdjustPrebufferCallback, NULL);
388         if (o)
389             pa_operation_unref(o);
390     }
391 
392 #ifdef DEBUG_PULSE
393     qDebug() << "Buffering info:";
394     qDebug() << "\tMax length: " << buffer->maxlength;
395     qDebug() << "\tTarget length: " << buffer->tlength;
396     qDebug() << "\tPre-buffering: " << buffer->prebuf;
397     qDebug() << "\tMinimum request: " << buffer->minreq;
398     qDebug() << "\tFragment size: " << buffer->fragsize;
399 #endif
400 
401     pulseEngine->unlock();
402 
403     connect(pulseEngine, &QPulseAudioEngine::contextFailed, this, &QPulseAudioOutput::onPulseContextFailed);
404 
405     m_opened = true;
406 
407     m_tickTimer->start(m_periodTime);
408 
409     m_elapsedTimeOffset = 0;
410     m_timeStamp.restart();
411     m_clockStamp.restart();
412 
413     return true;
414 }
415 
close()416 void QPulseAudioOutput::close()
417 {
418     if (!m_opened)
419         return;
420 
421     m_tickTimer->stop();
422 
423     QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
424 
425     if (m_stream) {
426         pulseEngine->lock();
427 
428         pa_stream_set_state_callback(m_stream, 0, 0);
429         pa_stream_set_write_callback(m_stream, 0, 0);
430         pa_stream_set_underflow_callback(m_stream, 0, 0);
431         pa_stream_set_overflow_callback(m_stream, 0, 0);
432         pa_stream_set_latency_update_callback(m_stream, 0, 0);
433 
434         pa_operation *o = pa_stream_drain(m_stream, outputStreamDrainComplete, NULL);
435         if (o)
436             pa_operation_unref(o);
437 
438         pa_stream_disconnect(m_stream);
439         pa_stream_unref(m_stream);
440         m_stream = NULL;
441 
442         pulseEngine->unlock();
443     }
444 
445     disconnect(pulseEngine, &QPulseAudioEngine::contextFailed, this, &QPulseAudioOutput::onPulseContextFailed);
446 
447     if (!m_pullMode && m_audioSource) {
448         delete m_audioSource;
449         m_audioSource = 0;
450     }
451     m_opened = false;
452     if (m_audioBuffer) {
453         delete[] m_audioBuffer;
454         m_audioBuffer = 0;
455     }
456 }
457 
userFeed()458 void QPulseAudioOutput::userFeed()
459 {
460     if (m_deviceState == QAudio::StoppedState || m_deviceState == QAudio::SuspendedState)
461         return;
462 
463     m_resuming = false;
464 
465     if (m_pullMode) {
466         int writableSize = bytesFree();
467         int chunks = writableSize / m_periodSize;
468         if (chunks == 0)
469             return;
470 
471         int input = m_periodSize; // always request 1 chunk of data from user
472         if (input > m_maxBufferSize)
473             input = m_maxBufferSize;
474 
475         int audioBytesPulled = m_audioSource->read(m_audioBuffer, input);
476         Q_ASSERT(audioBytesPulled <= input);
477         if (m_audioBuffer && audioBytesPulled > 0) {
478             if (audioBytesPulled > input) {
479                 qWarning() << "QPulseAudioOutput::userFeed() - Invalid audio data size provided from user:"
480                            << audioBytesPulled << "should be less than" << input;
481                 audioBytesPulled = input;
482             }
483             qint64 bytesWritten = write(m_audioBuffer, audioBytesPulled);
484             Q_ASSERT(bytesWritten == audioBytesPulled); //unfinished write should not happen since the data provided is less than writableSize
485             Q_UNUSED(bytesWritten);
486 
487             if (chunks > 1) {
488                 // PulseAudio needs more data. Ask for it immediately.
489                 QMetaObject::invokeMethod(this, "userFeed", Qt::QueuedConnection);
490             }
491         }
492     }
493 
494     if (m_deviceState != QAudio::ActiveState)
495         return;
496 
497     if (m_notifyInterval && (m_timeStamp.elapsed() + m_elapsedTimeOffset) > m_notifyInterval) {
498         emit notify();
499         m_elapsedTimeOffset = m_timeStamp.restart() + m_elapsedTimeOffset - m_notifyInterval;
500     }
501 }
502 
write(const char * data,qint64 len)503 qint64 QPulseAudioOutput::write(const char *data, qint64 len)
504 {
505     QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
506 
507     pulseEngine->lock();
508 
509     len = qMin(len, static_cast<qint64>(pa_stream_writable_size(m_stream)));
510 
511     if (m_volume < 1.0f) {
512         // Don't use PulseAudio volume, as it might affect all other streams of the same category
513         // or even affect the system volume if flat volumes are enabled
514         void *dest = NULL;
515         size_t nbytes = len;
516         if (pa_stream_begin_write(m_stream, &dest, &nbytes) < 0) {
517             qWarning("QAudioOutput(pulseaudio): pa_stream_begin_write, error = %s",
518                      pa_strerror(pa_context_errno(pulseEngine->context())));
519             setError(QAudio::IOError);
520             return 0;
521         }
522 
523         len = int(nbytes);
524         QAudioHelperInternal::qMultiplySamples(m_volume, m_format, data, dest, len);
525         data = reinterpret_cast<char *>(dest);
526     }
527 
528     if (pa_stream_write(m_stream, data, len, NULL, 0, PA_SEEK_RELATIVE) < 0) {
529         qWarning("QAudioOutput(pulseaudio): pa_stream_write, error = %s",
530                  pa_strerror(pa_context_errno(pulseEngine->context())));
531         setError(QAudio::IOError);
532         return 0;
533     }
534 
535     pulseEngine->unlock();
536     m_totalTimeValue += len;
537 
538     setError(QAudio::NoError);
539     setState(QAudio::ActiveState);
540 
541     return len;
542 }
543 
stop()544 void QPulseAudioOutput::stop()
545 {
546     if (m_deviceState == QAudio::StoppedState)
547         return;
548 
549     close();
550 
551     setError(QAudio::NoError);
552     setState(QAudio::StoppedState);
553 }
554 
bytesFree() const555 int QPulseAudioOutput::bytesFree() const
556 {
557     if (m_deviceState != QAudio::ActiveState && m_deviceState != QAudio::IdleState)
558         return 0;
559 
560     QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
561     pulseEngine->lock();
562     int writableSize = pa_stream_writable_size(m_stream);
563     pulseEngine->unlock();
564     return writableSize;
565 }
566 
periodSize() const567 int QPulseAudioOutput::periodSize() const
568 {
569     return m_periodSize;
570 }
571 
setBufferSize(int value)572 void QPulseAudioOutput::setBufferSize(int value)
573 {
574     m_bufferSize = value;
575 }
576 
bufferSize() const577 int QPulseAudioOutput::bufferSize() const
578 {
579     return m_bufferSize;
580 }
581 
setNotifyInterval(int ms)582 void QPulseAudioOutput::setNotifyInterval(int ms)
583 {
584     m_notifyInterval = qMax(0, ms);
585 }
586 
notifyInterval() const587 int QPulseAudioOutput::notifyInterval() const
588 {
589     return m_notifyInterval;
590 }
591 
processedUSecs() const592 qint64 QPulseAudioOutput::processedUSecs() const
593 {
594     qint64 result = qint64(1000000) * m_totalTimeValue /
595         (m_format.channelCount() * (m_format.sampleSize() / 8)) /
596         m_format.sampleRate();
597 
598     return result;
599 }
600 
resume()601 void QPulseAudioOutput::resume()
602 {
603     if (m_deviceState == QAudio::SuspendedState) {
604         m_resuming = true;
605 
606         QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
607 
608         pulseEngine->lock();
609 
610         pa_operation *operation = pa_stream_cork(m_stream, 0, outputStreamSuccessCallback, NULL);
611         pulseEngine->wait(operation);
612         pa_operation_unref(operation);
613 
614         operation = pa_stream_trigger(m_stream, outputStreamSuccessCallback, NULL);
615         pulseEngine->wait(operation);
616         pa_operation_unref(operation);
617 
618         pulseEngine->unlock();
619 
620         m_tickTimer->start(m_periodTime);
621 
622         setState(m_pullMode ? QAudio::ActiveState : QAudio::IdleState);
623         setError(QAudio::NoError);
624     }
625 }
626 
setFormat(const QAudioFormat & format)627 void QPulseAudioOutput::setFormat(const QAudioFormat &format)
628 {
629     m_format = format;
630 }
631 
format() const632 QAudioFormat QPulseAudioOutput::format() const
633 {
634     return m_format;
635 }
636 
suspend()637 void QPulseAudioOutput::suspend()
638 {
639     if (m_deviceState == QAudio::ActiveState || m_deviceState == QAudio::IdleState) {
640         setError(QAudio::NoError);
641         setState(QAudio::SuspendedState);
642 
643         m_tickTimer->stop();
644 
645         QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
646         pa_operation *operation;
647 
648         pulseEngine->lock();
649 
650         operation = pa_stream_cork(m_stream, 1, outputStreamSuccessCallback, NULL);
651         pulseEngine->wait(operation);
652         pa_operation_unref(operation);
653 
654         pulseEngine->unlock();
655     }
656 }
657 
elapsedUSecs() const658 qint64 QPulseAudioOutput::elapsedUSecs() const
659 {
660     if (m_deviceState == QAudio::StoppedState)
661         return 0;
662 
663     return m_clockStamp.elapsed() * qint64(1000);
664 }
665 
reset()666 void QPulseAudioOutput::reset()
667 {
668     stop();
669 }
670 
PulseOutputPrivate(QPulseAudioOutput * audio)671 PulseOutputPrivate::PulseOutputPrivate(QPulseAudioOutput *audio)
672 {
673     m_audioDevice = qobject_cast<QPulseAudioOutput*>(audio);
674 }
675 
readData(char * data,qint64 len)676 qint64 PulseOutputPrivate::readData(char *data, qint64 len)
677 {
678     Q_UNUSED(data)
679     Q_UNUSED(len)
680 
681     return 0;
682 }
683 
writeData(const char * data,qint64 len)684 qint64 PulseOutputPrivate::writeData(const char *data, qint64 len)
685 {
686     int retry = 0;
687     qint64 written = 0;
688 
689     if ((m_audioDevice->m_deviceState == QAudio::ActiveState
690          || m_audioDevice->m_deviceState == QAudio::IdleState)) {
691          while(written < len) {
692             int chunk = m_audioDevice->write(data+written, (len-written));
693             if (chunk <= 0)
694                 retry++;
695             written+=chunk;
696             if (retry > 10)
697                 return written;
698         }
699     }
700 
701     return written;
702 }
703 
setVolume(qreal vol)704 void QPulseAudioOutput::setVolume(qreal vol)
705 {
706     if (qFuzzyCompare(m_volume, vol))
707         return;
708 
709     m_volume = qBound(qreal(0), vol, qreal(1));
710 }
711 
volume() const712 qreal QPulseAudioOutput::volume() const
713 {
714     return m_volume;
715 }
716 
setCategory(const QString & category)717 void QPulseAudioOutput::setCategory(const QString &category)
718 {
719     if (m_category != category) {
720         m_category = category;
721     }
722 }
723 
category() const724 QString QPulseAudioOutput::category() const
725 {
726     return m_category;
727 }
728 
onPulseContextFailed()729 void QPulseAudioOutput::onPulseContextFailed()
730 {
731     close();
732 
733     setError(QAudio::FatalError);
734     setState(QAudio::StoppedState);
735 }
736 
737 QT_END_NAMESPACE
738 
739 #include "moc_qaudiooutput_pulse.cpp"
740