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
qpulseaudiosink.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#include <QtCore/qcoreapplication.h>
5#include <QtCore/qdebug.h>
6#include <QtCore/qmath.h>
7#include <private/qaudiohelpers_p.h>
8
9#include "qpulseaudiosink_p.h"
11#include "qpulsehelpers_p.h"
12#include <sys/types.h>
13#include <unistd.h>
14#include <mutex> // for std::lock_guard
15
17
18static constexpr uint SinkPeriodTimeMs = 20;
19static constexpr uint DefaultBufferLengthMs = 100;
20
21#define LOW_LATENCY_CATEGORY_NAME "game"
22
23static void outputStreamWriteCallback(pa_stream *stream, size_t length, void *userdata)
24{
26 Q_UNUSED(userdata);
27 qCDebug(qLcPulseAudioOut) << "Write callback:" << length;
29 pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
30}
31
32static void outputStreamStateCallback(pa_stream *stream, void *userdata)
33{
34 Q_UNUSED(userdata);
35 pa_stream_state_t state = pa_stream_get_state(stream);
36 qCDebug(qLcPulseAudioOut) << "Stream state callback:" << state;
37 switch (state) {
38 case PA_STREAM_CREATING:
39 case PA_STREAM_READY:
40 case PA_STREAM_TERMINATED:
41 break;
42
43 case PA_STREAM_FAILED:
44 default:
45 qWarning() << QStringLiteral("Stream error: %1")
46 .arg(QString::fromUtf8(pa_strerror(
47 pa_context_errno(pa_stream_get_context(stream)))));
49 pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
50 break;
51 }
52}
53
54static void outputStreamUnderflowCallback(pa_stream *stream, void *userdata)
55{
57 qCDebug(qLcPulseAudioOut) << "Buffer underflow";
58 if (userdata)
59 static_cast<QPulseAudioSink *>(userdata)->streamUnderflowCallback();
60}
61
62static void outputStreamOverflowCallback(pa_stream *stream, void *userdata)
63{
65 Q_UNUSED(userdata);
66 qCDebug(qLcPulseAudioOut) << "Buffer overflow";
67}
68
69static void outputStreamLatencyCallback(pa_stream *stream, void *userdata)
70{
72 Q_UNUSED(userdata);
73
74 if (Q_UNLIKELY(qLcPulseAudioOut().isEnabled(QtDebugMsg))) {
75 const pa_timing_info *info = pa_stream_get_timing_info(stream);
76
77 qCDebug(qLcPulseAudioOut) << "Latency callback:";
78 qCDebug(qLcPulseAudioOut) << "\tWrite index corrupt: " << info->write_index_corrupt;
79 qCDebug(qLcPulseAudioOut) << "\tWrite index: " << info->write_index;
80 qCDebug(qLcPulseAudioOut) << "\tRead index corrupt: " << info->read_index_corrupt;
81 qCDebug(qLcPulseAudioOut) << "\tRead index: " << info->read_index;
82 qCDebug(qLcPulseAudioOut) << "\tSink usec: " << info->sink_usec;
83 qCDebug(qLcPulseAudioOut) << "\tConfigured sink usec: " << info->configured_sink_usec;
84 }
85}
86
87static void outputStreamSuccessCallback(pa_stream *stream, int success, void *userdata)
88{
90 Q_UNUSED(userdata);
91
92 qCDebug(qLcPulseAudioOut) << "Stream successful:" << success;
94 pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
95}
96
97static void outputStreamDrainComplete(pa_stream *stream, int success, void *userdata)
98{
100
101 qCDebug(qLcPulseAudioOut) << "Stream drained:" << static_cast<bool>(success) << userdata;
102
104 pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
105
106 if (userdata && success)
107 static_cast<QPulseAudioSink *>(userdata)->streamDrainedCallback();
108}
109
110static void outputStreamFlushComplete(pa_stream *stream, int success, void *userdata)
111{
113
114 qCDebug(qLcPulseAudioOut) << "Stream flushed:" << static_cast<bool>(success) << userdata;
115}
116
117static void streamAdjustPrebufferCallback(pa_stream *stream, int success, void *userdata)
118{
120 Q_UNUSED(success);
121 Q_UNUSED(userdata);
122
123 qCDebug(qLcPulseAudioOut) << "Prebuffer adjusted:" << static_cast<bool>(success);
124}
125
127 : QPlatformAudioSink(parent), m_device(device), m_stateMachine(*this)
128{
129}
130
132{
133 if (auto notifier = m_stateMachine.stop())
134 close();
135}
136
138{
139 return m_stateMachine.error();
140}
141
143{
144 return m_stateMachine.state();
145}
146
148{
149 bool atEnd = m_audioSource && m_audioSource->atEnd();
150 if (atEnd && m_stateMachine.state() != QAudio::StoppedState) {
151 qCDebug(qLcPulseAudioOut) << "Draining stream at end of buffer";
152 exchangeDrainOperation(pa_stream_drain(m_stream, outputStreamDrainComplete, this));
153 }
154
155 m_stateMachine.updateActiveOrIdle(
156 false, (m_pullMode && atEnd) ? QAudio::NoError : QAudio::UnderrunError);
157}
158
160{
161 if (!exchangeDrainOperation(nullptr))
162 return;
163}
164
166{
167 reset();
168
169 m_pullMode = true;
170 m_audioSource = device;
171
172 if (!open()) {
173 m_audioSource = nullptr;
174 return;
175 }
176
177 // ensure we only process timing infos that are up to date
178 gettimeofday(&lastTimingInfo, nullptr);
179 lastProcessedUSecs = 0;
180
181 connect(m_audioSource, &QIODevice::readyRead, this, &QPulseAudioSink::startPulling);
182
183 m_stateMachine.start();
184}
185
186void QPulseAudioSink::startPulling()
187{
188 Q_ASSERT(m_pullMode);
189 if (m_tickTimer.isActive())
190 return;
191
192 m_tickTimer.start(m_pullingPeriodTime, this);
193}
194
195void QPulseAudioSink::stopTimer()
196{
197 if (m_tickTimer.isActive())
198 m_tickTimer.stop();
199}
200
202{
203 reset();
204
205 m_pullMode = false;
206
207 if (!open())
208 return nullptr;
209
210 m_audioSource = new PulseOutputPrivate(this);
212
213 // ensure we only process timing infos that are up to date
214 gettimeofday(&lastTimingInfo, nullptr);
215 lastProcessedUSecs = 0;
216
217 m_stateMachine.start(false);
218
219 return m_audioSource;
220}
221
222bool QPulseAudioSink::open()
223{
224 if (m_opened)
225 return true;
226
228
229 if (!pulseEngine->context()
230 || pa_context_get_state(pulseEngine->context()) != PA_CONTEXT_READY) {
232 return false;
233 }
234
235 pa_sample_spec spec = QPulseAudioInternal::audioFormatToSampleSpec(m_format);
236 pa_channel_map channel_map = QPulseAudioInternal::channelMapForAudioFormat(m_format);
237 Q_ASSERT(spec.channels == channel_map.channels);
238
239 if (!pa_sample_spec_valid(&spec)) {
240 m_stateMachine.stopOrUpdateError(QAudio::OpenError);
241 return false;
242 }
243
244 m_spec = spec;
245 m_totalTimeValue = 0;
246
247 if (m_streamName.isNull())
248 m_streamName =
249 QStringLiteral("QtmPulseStream-%1-%2").arg(::getpid()).arg(quintptr(this)).toUtf8();
250
251 if (Q_UNLIKELY(qLcPulseAudioOut().isEnabled(QtDebugMsg))) {
252 qCDebug(qLcPulseAudioOut) << "Opening stream with.";
253 qCDebug(qLcPulseAudioOut) << "\tFormat: " << spec.format;
254 qCDebug(qLcPulseAudioOut) << "\tRate: " << spec.rate;
255 qCDebug(qLcPulseAudioOut) << "\tChannels: " << spec.channels;
256 qCDebug(qLcPulseAudioOut) << "\tFrame size: " << pa_frame_size(&spec);
257 }
258
259 pulseEngine->lock();
260
261 pa_proplist *propList = pa_proplist_new();
262#if 0
263 qint64 bytesPerSecond = m_format.sampleRate() * m_format.bytesPerFrame();
264 static const char *mediaRoleFromAudioRole[] = {
265 nullptr, // UnknownRole
266 "music", // MusicRole
267 "video", // VideoRole
268 "phone", // VoiceCommunicationRole
269 "event", // AlarmRole
270 "event", // NotificationRole
271 "phone", // RingtoneRole
272 "a11y", // AccessibilityRole
273 nullptr, // SonificationRole
274 "game" // GameRole
275 };
276
277 const char *r = mediaRoleFromAudioRole[m_role];
278 if (r)
279 pa_proplist_sets(propList, PA_PROP_MEDIA_ROLE, r);
280#endif
281
282 m_stream = pa_stream_new_with_proplist(pulseEngine->context(), m_streamName.constData(),
283 &m_spec, &channel_map, propList);
284 pa_proplist_free(propList);
285
286 if (!m_stream) {
287 qCWarning(qLcPulseAudioOut) << "QAudioSink: pa_stream_new_with_proplist() failed!";
288 pulseEngine->unlock();
289
290 m_stateMachine.stopOrUpdateError(QAudio::OpenError);
291 return false;
292 }
293
294 pa_stream_set_state_callback(m_stream, outputStreamStateCallback, this);
295 pa_stream_set_write_callback(m_stream, outputStreamWriteCallback, this);
296
297 pa_stream_set_underflow_callback(m_stream, outputStreamUnderflowCallback, this);
298 pa_stream_set_overflow_callback(m_stream, outputStreamOverflowCallback, this);
299 pa_stream_set_latency_update_callback(m_stream, outputStreamLatencyCallback, this);
300
301 pa_buffer_attr requestedBuffer;
302 // Request a target buffer size
303 auto targetBufferSize = m_userBufferSize ? *m_userBufferSize : defaultBufferSize();
304 requestedBuffer.tlength =
305 targetBufferSize ? static_cast<uint32_t>(targetBufferSize) : static_cast<uint32_t>(-1);
306 // Rest should be determined by PulseAudio
307 requestedBuffer.fragsize = static_cast<uint32_t>(-1);
308 requestedBuffer.maxlength = static_cast<uint32_t>(-1);
309 requestedBuffer.minreq = static_cast<uint32_t>(-1);
310 requestedBuffer.prebuf = static_cast<uint32_t>(-1);
311
312 pa_stream_flags flags =
313 pa_stream_flags(PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY);
314 if (pa_stream_connect_playback(m_stream, m_device.data(), &requestedBuffer, flags, nullptr,
315 nullptr)
316 < 0) {
317 qCWarning(qLcPulseAudioOut) << "pa_stream_connect_playback() failed!";
318 pa_stream_unref(m_stream);
319 m_stream = nullptr;
320 pulseEngine->unlock();
321 m_stateMachine.stopOrUpdateError(QAudio::OpenError);
322 return false;
323 }
324
325 while (pa_stream_get_state(m_stream) != PA_STREAM_READY)
326 pa_threaded_mainloop_wait(pulseEngine->mainloop());
327
328 const pa_buffer_attr *buffer = pa_stream_get_buffer_attr(m_stream);
329 m_bufferSize = buffer->tlength;
330
331 if (m_pullMode) {
332 // Adjust period time to reduce chance of it being higher than amount of bytes requested by
333 // PulseAudio server
334 m_pullingPeriodTime =
335 qMin(SinkPeriodTimeMs, pa_bytes_to_usec(m_bufferSize, &m_spec) / 1000 / 2);
336 m_pullingPeriodSize = pa_usec_to_bytes(m_pullingPeriodTime * 1000, &m_spec);
337 }
338
339 m_audioBuffer.resize(buffer->maxlength);
340
341 const qint64 streamSize = m_audioSource ? m_audioSource->size() : 0;
342 if (m_pullMode && streamSize > 0 && static_cast<qint64>(buffer->prebuf) > streamSize) {
343 pa_buffer_attr newBufferAttr;
344 newBufferAttr = *buffer;
345 newBufferAttr.prebuf = streamSize;
346 PAOperationUPtr(pa_stream_set_buffer_attr(m_stream, &newBufferAttr,
348 }
349
350 if (Q_UNLIKELY(qLcPulseAudioOut().isEnabled(QtDebugMsg))) {
351 qCDebug(qLcPulseAudioOut) << "Buffering info:";
352 qCDebug(qLcPulseAudioOut) << "\tMax length: " << buffer->maxlength;
353 qCDebug(qLcPulseAudioOut) << "\tTarget length: " << buffer->tlength;
354 qCDebug(qLcPulseAudioOut) << "\tPre-buffering: " << buffer->prebuf;
355 qCDebug(qLcPulseAudioOut) << "\tMinimum request: " << buffer->minreq;
356 qCDebug(qLcPulseAudioOut) << "\tFragment size: " << buffer->fragsize;
357 }
358
359 pulseEngine->unlock();
360
361 connect(pulseEngine, &QPulseAudioEngine::contextFailed, this,
362 &QPulseAudioSink::onPulseContextFailed);
363
364 m_opened = true;
365
366 if (m_pullMode)
367 startPulling();
368
369 m_elapsedTimeOffset = 0;
370
371 return true;
372}
373
374void QPulseAudioSink::close()
375{
376 if (!m_opened)
377 return;
378
379 stopTimer();
380
382
383 if (m_stream) {
384 std::lock_guard lock(*pulseEngine);
385
386 pa_stream_set_state_callback(m_stream, nullptr, nullptr);
387 pa_stream_set_write_callback(m_stream, nullptr, nullptr);
388 pa_stream_set_underflow_callback(m_stream, nullptr, nullptr);
389 pa_stream_set_overflow_callback(m_stream, nullptr, nullptr);
390 pa_stream_set_latency_update_callback(m_stream, nullptr, nullptr);
391
392 if (auto prevOp = exchangeDrainOperation(nullptr))
393 // cancel draining operation to prevent calling draining callback after closing.
394 pa_operation_cancel(prevOp.get());
395
396 PAOperationUPtr operation(pa_stream_flush(m_stream, outputStreamFlushComplete, nullptr));
397
398 pa_stream_disconnect(m_stream);
399 pa_stream_unref(m_stream);
400 m_stream = nullptr;
401 }
402
404 &QPulseAudioSink::onPulseContextFailed);
405
406 if (m_audioSource) {
407 if (m_pullMode) {
408 disconnect(m_audioSource, &QIODevice::readyRead, this, nullptr);
409 m_audioSource->reset();
410 } else {
411 delete m_audioSource;
412 m_audioSource = nullptr;
413 }
414 }
415
416 m_opened = false;
417 m_audioBuffer.clear();
418}
419
421{
422 if (event->timerId() == m_tickTimer.timerId() && m_pullMode)
423 userFeed();
424
426}
427
428void QPulseAudioSink::userFeed()
429{
430 int writableSize = bytesFree();
431
432 if (writableSize == 0) {
433 // PulseAudio server doesn't want any more data
434 m_stateMachine.activateFromIdle();
435 return;
436 }
437
438 // Write up to writableSize
439 const int inputSize =
440 std::min({ m_pullingPeriodSize, static_cast<int>(m_audioBuffer.size()), writableSize });
441
442 Q_ASSERT(!m_audioBuffer.empty());
443 int audioBytesPulled = m_audioSource->read(m_audioBuffer.data(), inputSize);
444 Q_ASSERT(audioBytesPulled <= inputSize);
445
446 if (audioBytesPulled > 0) {
447 if (audioBytesPulled > inputSize) {
448 qCWarning(qLcPulseAudioOut)
449 << "Invalid audio data size provided by pull source:" << audioBytesPulled
450 << "should be less than" << inputSize;
451 audioBytesPulled = inputSize;
452 }
453 auto bytesWritten = write(m_audioBuffer.data(), audioBytesPulled);
454 if (bytesWritten != audioBytesPulled)
455 qWarning() << "Unfinished write:" << bytesWritten << "vs" << audioBytesPulled;
456
457 m_stateMachine.activateFromIdle();
458
459 if (inputSize < writableSize) // PulseAudio needs more data.
460 QMetaObject::invokeMethod(this, &QPulseAudioSink::userFeed, Qt::QueuedConnection);
461 } else if (audioBytesPulled == 0) {
462 stopTimer();
463 const auto atEnd = m_audioSource->atEnd();
464 qCDebug(qLcPulseAudioOut) << "No more data available, source is done:" << atEnd;
465 }
466}
467
468qint64 QPulseAudioSink::write(const char *data, qint64 len)
469{
470 using namespace QPulseAudioInternal;
471
473
474 pulseEngine->lock();
475
476 size_t nbytes = len;
477 void *dest = nullptr;
478
479 if (pa_stream_begin_write(m_stream, &dest, &nbytes) < 0) {
480 pulseEngine->unlock();
481 qCWarning(qLcPulseAudioOut)
482 << "pa_stream_begin_write error:" << currentError(pulseEngine->context());
483 m_stateMachine.updateActiveOrIdle(false, QAudio::IOError);
484 return 0;
485 }
486
487 len = qMin(len, qint64(nbytes));
488
489 if (m_volume < 1.0f) {
490 // Don't use PulseAudio volume, as it might affect all other streams of the same category
491 // or even affect the system volume if flat volumes are enabled
492 QAudioHelperInternal::qMultiplySamples(m_volume, m_format, data, dest, len);
493 } else {
494 memcpy(dest, data, len);
495 }
496
497 data = reinterpret_cast<char *>(dest);
498
499 if ((pa_stream_write(m_stream, data, len, nullptr, 0, PA_SEEK_RELATIVE)) < 0) {
500 pulseEngine->unlock();
501 qCWarning(qLcPulseAudioOut)
502 << "pa_stream_write error:" << currentError(pulseEngine->context());
503 m_stateMachine.updateActiveOrIdle(false, QAudio::IOError);
504 return 0;
505 }
506
507 pulseEngine->unlock();
508 m_totalTimeValue += len;
509
510 m_stateMachine.updateActiveOrIdle(true);
511 return len;
512}
513
515{
516 if (auto notifier = m_stateMachine.stop()) {
517 {
519 std::lock_guard lock(*pulseEngine);
520
521 if (auto prevOp = exchangeDrainOperation(nullptr))
522 // cancel the draining callback that is not relevant already
523 pa_operation_cancel(prevOp.get());
524
525 PAOperationUPtr drainOp(pa_stream_drain(m_stream, outputStreamDrainComplete, nullptr));
526 pulseEngine->wait(drainOp.get());
527 }
528
529 close();
530 }
531}
532
534{
535 if (!m_stateMachine.isActiveOrIdle())
536 return 0;
537
538 std::lock_guard lock(*QPulseAudioEngine::instance());
539 return pa_stream_writable_size(m_stream);
540}
541
543{
544 m_userBufferSize = value;
545}
546
548{
549 if (m_bufferSize)
550 return m_bufferSize;
551
552 if (m_userBufferSize)
553 return *m_userBufferSize;
554
555 return defaultBufferSize();
556}
557
558static qint64 operator-(timeval t1, timeval t2)
559{
560 constexpr qint64 secsToUSecs = 1000000;
561 return (t1.tv_sec - t2.tv_sec) * secsToUSecs + (t1.tv_usec - t2.tv_usec);
562}
563
565{
566 const auto state = this->state();
567 if (!m_stream || state == QAudio::StoppedState)
568 return 0;
570 return lastProcessedUSecs;
571
572 auto info = pa_stream_get_timing_info(m_stream);
573 if (!info)
574 return lastProcessedUSecs;
575
576 // if the info changed, update our cached data, and recalculate the average latency
577 if (info->timestamp - lastTimingInfo > 0) {
578 lastTimingInfo.tv_sec = info->timestamp.tv_sec;
579 lastTimingInfo.tv_usec = info->timestamp.tv_usec;
580 averageLatency =
581 0; // also use that as long as we don't have valid data from the timing info
582
583 // Only use timing values when playing, otherwise the latency numbers can be way off
584 if (info->since_underrun >= 0
585 && pa_bytes_to_usec(info->since_underrun, &m_spec) > info->sink_usec) {
586 latencyList.append(info->sink_usec);
587 // Average over the last X timing infos to keep numbers more stable.
588 // 10 seems to be a decent number that keeps values relatively stable but doesn't make
589 // the list too big
590 const int latencyListMaxSize = 10;
591 if (latencyList.size() > latencyListMaxSize)
592 latencyList.pop_front();
593 for (const auto l : latencyList)
594 averageLatency += l;
595 averageLatency /= latencyList.size();
596 if (averageLatency < 0)
597 averageLatency = 0;
598 }
599 }
600
601 const qint64 usecsRead = info->read_index < 0 ? 0 : pa_bytes_to_usec(info->read_index, &m_spec);
602 const qint64 usecsWritten =
603 info->write_index < 0 ? 0 : pa_bytes_to_usec(info->write_index, &m_spec);
604
605 // processed data is the amount read by the server minus its latency
606 qint64 usecs = usecsRead - averageLatency;
607
608 timeval tv;
609 gettimeofday(&tv, nullptr);
610
611 // and now adjust for the time since the last update
612 qint64 timeSinceUpdate = tv - info->timestamp;
613 if (timeSinceUpdate > 0)
614 usecs += timeSinceUpdate;
615
616 // We can never have processed more than we've written to the sink
617 if (usecs > usecsWritten)
618 usecs = usecsWritten;
619
620 // make sure timing is monotonic
621 if (usecs < lastProcessedUSecs)
622 usecs = lastProcessedUSecs;
623 else
624 lastProcessedUSecs = usecs;
625
626 return usecs;
627}
628
630{
631 if (auto notifier = m_stateMachine.resume()) {
632 {
634
635 std::lock_guard lock(*pulseEngine);
636
637 PAOperationUPtr operation(
638 pa_stream_cork(m_stream, 0, outputStreamSuccessCallback, nullptr));
639 pulseEngine->wait(operation.get());
640
641 operation.reset(pa_stream_trigger(m_stream, outputStreamSuccessCallback, nullptr));
642 pulseEngine->wait(operation.get());
643 }
644
645 if (m_pullMode)
646 startPulling();
647 }
648}
649
651{
652 m_format = format;
653}
654
656{
657 return m_format;
658}
659
661{
662 if (auto notifier = m_stateMachine.suspend()) {
663 stopTimer();
664
666
667 std::lock_guard lock(*pulseEngine);
668
669 PAOperationUPtr operation(
670 pa_stream_cork(m_stream, 1, outputStreamSuccessCallback, nullptr));
671 pulseEngine->wait(operation.get());
672 }
673}
674
676{
677 if (auto notifier = m_stateMachine.stopOrUpdateError())
678 close();
679}
680
682{
683 m_audioDevice = qobject_cast<QPulseAudioSink *>(audio);
684}
685
687{
688 Q_UNUSED(data);
689 Q_UNUSED(len);
690
691 return 0;
692}
693
695{
696 qint64 written = 0;
697
698 const auto state = m_audioDevice->state();
700 while (written < len) {
701 int chunk = m_audioDevice->write(data + written, (len - written));
702 if (chunk <= 0)
703 return written;
704 written += chunk;
705 }
706 }
707
708 return written;
709}
710
712{
713 if (qFuzzyCompare(m_volume, vol))
714 return;
715
716 m_volume = qBound(qreal(0), vol, qreal(1));
717}
718
720{
721 return m_volume;
722}
723
724void QPulseAudioSink::onPulseContextFailed()
725{
726 if (auto notifier = m_stateMachine.stop(QAudio::FatalError))
727 close();
728}
729
730PAOperationUPtr QPulseAudioSink::exchangeDrainOperation(pa_operation *newOperation)
731{
732 return PAOperationUPtr(m_drainOperation.exchange(newOperation));
733}
734
735qsizetype QPulseAudioSink::defaultBufferSize() const
736{
737 if (m_spec.rate > 0)
738 return pa_usec_to_bytes(DefaultBufferLengthMs * 1000, &m_spec);
739
741 if (pa_sample_spec_valid(&spec))
742 return pa_usec_to_bytes(DefaultBufferLengthMs * 1000, &spec);
743
744 return 0;
745}
746
748
749#include "moc_qpulseaudiosink_p.cpp"
DarwinBluetooth::LECBManagerNotifier * notifier
IOBluetoothDevice * device
PulseOutputPrivate(QPulseAudioSink *audio)
qint64 readData(char *data, qint64 len) override
Reads up to maxSize bytes from the device into data, and returns the number of bytes read or -1 if an...
qint64 writeData(const char *data, qint64 len) override
Writes up to maxSize bytes from data to the device.
The QAudioFormat class stores audio stream parameter information.
constexpr int sampleRate() const noexcept
Returns the current sample rate in Hertz.
constexpr int bytesPerFrame() const
Returns the number of bytes required to represent one frame (a sample in each channel) in this format...
QAudio::Error error() const
Notifier start(bool isActive=true)
Notifier updateActiveOrIdle(bool isActive, QAudio::Error error=QAudio::NoError)
QAudio::State state() const
Notifier stopOrUpdateError(QAudio::Error error=QAudio::NoError)
Notifier stop(QAudio::Error error=QAudio::NoError, bool shouldDrain=false, bool forceUpdateError=false)
void start(int msec, QObject *obj)
\obsolete Use chrono overload instead.
int timerId() const noexcept
Returns the timer's ID.
Definition qbasictimer.h:35
void stop()
Stops the timer.
bool isActive() const noexcept
Returns true if the timer is running and has not been stopped; otherwise returns false.
Definition qbasictimer.h:34
\inmodule QtCore
Definition qbytearray.h:57
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
Definition qbytearray.h:611
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:124
bool isNull() const noexcept
Returns true if this byte array is null; otherwise returns false.
\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...
virtual qint64 size() const
For open random-access devices, this function returns the size of the device.
virtual bool reset()
Seeks to the start of input for random-access devices.
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.
qsizetype size() const noexcept
Definition qlist.h:397
void pop_front() noexcept
Definition qlist.h:680
void append(parameter_type t)
Definition qlist.h:458
\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
virtual void timerEvent(QTimerEvent *event)
This event handler can be reimplemented in a subclass to receive timer events for the object.
Definition qobject.cpp:1470
static QPulseAudioEngine * instance()
pa_threaded_mainloop * mainloop()
void wait(pa_operation *op)
QAudio::Error error() const override
qreal volume() const override
QIODevice * start() override
QPulseAudioSink(const QByteArray &device, QObject *parent)
void setVolume(qreal volume) override
qint64 processedUSecs() const override
void suspend() override
void resume() override
void setBufferSize(qsizetype value) override
void reset() override
QAudio::State state() const override
friend class PulseOutputPrivate
void setFormat(const QAudioFormat &format) override
void timerEvent(QTimerEvent *event) override
This event handler can be reimplemented in a subclass to receive timer events for the object.
QAudioFormat format() const override
qsizetype bytesFree() const override
void stop() override
qsizetype bufferSize() const override
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:6018
\inmodule QtCore
Definition qcoreevent.h:366
#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
@ FatalError
Definition qaudio.h:28
@ OpenError
Definition qaudio.h:28
@ NoError
Definition qaudio.h:28
@ IOError
Definition qaudio.h:28
QUtf8StringView currentError(const pa_context *context)
pa_channel_map channelMapForAudioFormat(const QAudioFormat &format)
pa_sample_spec audioFormatToSampleSpec(const QAudioFormat &format)
Combined button and popup list for selecting options.
bool isEnabled()
@ QueuedConnection
#define Q_UNLIKELY(x)
EGLStreamKHR stream
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:333
@ QtDebugMsg
Definition qlogging.h:30
#define qWarning
Definition qlogging.h:166
#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
GLenum GLsizei GLuint GLint * bytesWritten
GLboolean r
[2]
GLenum GLuint GLenum GLsizei length
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat t1
[4]
GLenum GLuint buffer
GLbitfield flags
GLint GLsizei GLsizei GLenum format
struct _cl_event * event
GLenum GLsizei len
static void outputStreamStateCallback(pa_stream *stream, void *userdata)
static void outputStreamOverflowCallback(pa_stream *stream, void *userdata)
static QT_BEGIN_NAMESPACE constexpr uint SinkPeriodTimeMs
static void outputStreamSuccessCallback(pa_stream *stream, int success, void *userdata)
static void outputStreamUnderflowCallback(pa_stream *stream, void *userdata)
static void streamAdjustPrebufferCallback(pa_stream *stream, int success, void *userdata)
static void outputStreamDrainComplete(pa_stream *stream, int success, void *userdata)
static qint64 operator-(timeval t1, timeval t2)
static void outputStreamLatencyCallback(pa_stream *stream, void *userdata)
static void outputStreamWriteCallback(pa_stream *stream, size_t length, void *userdata)
static void outputStreamFlushComplete(pa_stream *stream, int success, void *userdata)
static constexpr uint DefaultBufferLengthMs
std::unique_ptr< pa_operation, PAOperationDeleter > PAOperationUPtr
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define QStringLiteral(str)
#define t2
#define Q_UNUSED(x)
size_t quintptr
Definition qtypes.h:167
ptrdiff_t qsizetype
Definition qtypes.h:165
unsigned int uint
Definition qtypes.h:34
long long qint64
Definition qtypes.h:60
double qreal
Definition qtypes.h:187
myObject disconnect()
[26]
QReadWriteLock lock
[0]
QHostInfo info
[0]
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...