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