4#include <QtCore/qcoreapplication.h>
5#include <QtCore/qdebug.h>
6#include <QtCore/qmath.h>
7#include <private/qaudiohelpers_p.h>
21#define LOW_LATENCY_CATEGORY_NAME "game"
29 pa_threaded_mainloop_signal(pulseEngine->
mainloop(), 0);
35 pa_stream_state_t
state = pa_stream_get_state(
stream);
36 qCDebug(qLcPulseAudioOut) <<
"Stream state callback:" <<
state;
38 case PA_STREAM_CREATING:
40 case PA_STREAM_TERMINATED:
43 case PA_STREAM_FAILED:
47 pa_threaded_mainloop_signal(pulseEngine->
mainloop(), 0);
55 qCDebug(qLcPulseAudioOut) <<
"Buffer underflow";
64 qCDebug(qLcPulseAudioOut) <<
"Buffer overflow";
73 const pa_timing_info *
info = pa_stream_get_timing_info(
stream);
75 qCDebug(qLcPulseAudioOut) <<
"Latency callback:";
76 qCDebug(qLcPulseAudioOut) <<
"\tWrite index corrupt: " <<
info->write_index_corrupt;
77 qCDebug(qLcPulseAudioOut) <<
"\tWrite index: " <<
info->write_index;
78 qCDebug(qLcPulseAudioOut) <<
"\tRead index corrupt: " <<
info->read_index_corrupt;
79 qCDebug(qLcPulseAudioOut) <<
"\tRead index: " <<
info->read_index;
80 qCDebug(qLcPulseAudioOut) <<
"\tSink usec: " <<
info->sink_usec;
81 qCDebug(qLcPulseAudioOut) <<
"\tConfigured sink usec: " <<
info->configured_sink_usec;
90 qCDebug(qLcPulseAudioOut) <<
"Stream successful:" << success;
92 pa_threaded_mainloop_signal(pulseEngine->
mainloop(), 0);
99 qCDebug(qLcPulseAudioOut) <<
"Stream drained:" << bool(success) << userdata;
101 if (userdata && success)
111 qCDebug(qLcPulseAudioOut) <<
"Prebuffer adjusted:" << bool(success);
137 const auto isStateChanged = m_deviceState.exchange(
state) !=
state;
138 const auto isErrorChanged = m_errorState.exchange(
error) !=
error;
140 if (isStateChanged || forceEmitState)
149 return m_deviceState;
154 if (m_audioSource && m_audioSource->
atEnd()) {
155 qCDebug(qLcPulseAudioOut) <<
"Draining stream at end of buffer";
165 if (!exchangeDrainOperation(
nullptr))
181 m_audioSource =
nullptr;
186 gettimeofday(&lastTimingInfo,
nullptr);
187 lastProcessedUSecs = 0;
193void QPulseAudioSink::startReading()
196 m_tickTimer.
start(m_periodTime,
this);
214 gettimeofday(&lastTimingInfo,
nullptr);
215 lastProcessedUSecs = 0;
219 return m_audioSource;
222bool QPulseAudioSink::open()
229 if (!pulseEngine->
context() || pa_context_get_state(pulseEngine->
context()) != PA_CONTEXT_READY) {
236 Q_ASSERT(spec.channels == channel_map.channels);
238 if (!pa_sample_spec_valid(&spec)) {
244 m_totalTimeValue = 0;
246 if (m_streamName.
isNull())
250 qCDebug(qLcPulseAudioOut) <<
"Opening stream with.";
252 qCDebug(qLcPulseAudioOut) <<
"\tRate: " << spec.rate;
253 qCDebug(qLcPulseAudioOut) <<
"\tChannels: " << spec.channels;
254 qCDebug(qLcPulseAudioOut) <<
"\tFrame size: " << pa_frame_size(&spec);
260 pa_proplist *propList = pa_proplist_new();
263 static const char *mediaRoleFromAudioRole[] = {
276 const char *
r = mediaRoleFromAudioRole[m_role];
278 pa_proplist_sets(propList, PA_PROP_MEDIA_ROLE,
r);
281 m_stream = pa_stream_new_with_proplist(pulseEngine->
context(), m_streamName.
constData(), &m_spec, &channel_map, propList);
282 pa_proplist_free(propList);
285 qCWarning(qLcPulseAudioOut) <<
"QAudioSink: pa_stream_new_with_proplist() failed!";
299 pa_buffer_attr requestedBuffer;
300 requestedBuffer.fragsize = (uint32_t)-1;
301 requestedBuffer.maxlength = (uint32_t)-1;
302 requestedBuffer.minreq = (uint32_t)-1;
303 requestedBuffer.prebuf = (uint32_t)-1;
304 requestedBuffer.tlength = m_bufferSize;
306 pa_stream_flags
flags = pa_stream_flags(PA_STREAM_AUTO_TIMING_UPDATE|PA_STREAM_ADJUST_LATENCY);
308 qCWarning(qLcPulseAudioOut) <<
"pa_stream_connect_playback() failed!";
309 pa_stream_unref(m_stream);
316 while (pa_stream_get_state(m_stream) != PA_STREAM_READY)
317 pa_threaded_mainloop_wait(pulseEngine->
mainloop());
319 const pa_buffer_attr *
buffer = pa_stream_get_buffer_attr(m_stream);
321 m_periodSize = pa_usec_to_bytes(m_periodTime * 1000, &m_spec);
322 m_bufferSize =
buffer->tlength;
323 m_audioBuffer.resize(
buffer->maxlength);
325 const qint64 streamSize = m_audioSource ? m_audioSource->
size() : 0;
326 if (m_pullMode && streamSize > 0 &&
static_cast<qint64>(
buffer->prebuf) > streamSize) {
327 pa_buffer_attr newBufferAttr;
329 newBufferAttr.prebuf = streamSize;
335 qCDebug(qLcPulseAudioOut) <<
"Buffering info:";
336 qCDebug(qLcPulseAudioOut) <<
"\tMax length: " <<
buffer->maxlength;
337 qCDebug(qLcPulseAudioOut) <<
"\tTarget length: " <<
buffer->tlength;
338 qCDebug(qLcPulseAudioOut) <<
"\tPre-buffering: " <<
buffer->prebuf;
339 qCDebug(qLcPulseAudioOut) <<
"\tMinimum request: " <<
buffer->minreq;
340 qCDebug(qLcPulseAudioOut) <<
"\tFragment size: " <<
buffer->fragsize;
351 m_elapsedTimeOffset = 0;
356void QPulseAudioSink::close()
366 std::lock_guard
lock(*pulseEngine);
368 pa_stream_set_state_callback(m_stream,
nullptr,
nullptr);
369 pa_stream_set_write_callback(m_stream,
nullptr,
nullptr);
370 pa_stream_set_underflow_callback(m_stream,
nullptr,
nullptr);
371 pa_stream_set_overflow_callback(m_stream,
nullptr,
nullptr);
372 pa_stream_set_latency_update_callback(m_stream,
nullptr,
nullptr);
374 if (
auto prevOp = exchangeDrainOperation(
nullptr))
376 pa_operation_cancel(prevOp.get());
380 pa_stream_disconnect(m_stream);
381 pa_stream_unref(m_stream);
391 delete m_audioSource;
392 m_audioSource =
nullptr;
397 m_audioBuffer.clear();
408void QPulseAudioSink::userFeed()
419 int chunks = writableSize / m_periodSize;
423 const int input = std::min(
425 static_cast<int>(m_audioBuffer.size()));
428 int audioBytesPulled = m_audioSource->
read(m_audioBuffer.data(),
input);
430 if (audioBytesPulled > 0) {
431 if (audioBytesPulled >
input) {
432 qCWarning(qLcPulseAudioOut) <<
"Invalid audio data size provided by pull source:"
433 << audioBytesPulled <<
"should be less than" <<
input;
434 audioBytesPulled =
input;
444 }
else if (audioBytesPulled == 0) {
446 const auto atEnd = m_audioSource->
atEnd();
447 qCDebug(qLcPulseAudioOut) <<
"No more data available, source is done:" << atEnd;
463 void *dest =
nullptr;
465 if (pa_stream_begin_write(m_stream, &dest, &nbytes) < 0) {
467 qCWarning(qLcPulseAudioOut) <<
"pa_stream_begin_write error:"
468 << pa_strerror(pa_context_errno(pulseEngine->
context()));
475 if (m_volume < 1.0f) {
483 data =
reinterpret_cast<char *
>(dest);
485 if ((pa_stream_write(m_stream,
data,
len,
nullptr, 0, PA_SEEK_RELATIVE)) < 0) {
487 qCWarning(qLcPulseAudioOut) <<
"pa_stream_write error:"
488 << pa_strerror(pa_context_errno(pulseEngine->
context()));
494 m_totalTimeValue +=
len;
518 return pa_stream_writable_size(m_stream);
523 m_bufferSize =
value;
533 constexpr qint64 secsToUSecs = 1000000;
534 return (
t1.tv_sec -
t2.tv_sec)*secsToUSecs + (
t1.tv_usec -
t2.tv_usec);
543 return lastProcessedUSecs;
545 auto info = pa_stream_get_timing_info(m_stream);
547 return lastProcessedUSecs;
550 if (
info->timestamp - lastTimingInfo > 0) {
551 lastTimingInfo.tv_sec =
info->timestamp.tv_sec;
552 lastTimingInfo.tv_usec =
info->timestamp.tv_usec;
556 if (
info->since_underrun >= 0 && pa_bytes_to_usec(
info->since_underrun, &m_spec) >
info->sink_usec) {
560 const int latencyListMaxSize = 10;
561 if (latencyList.
size() > latencyListMaxSize)
563 for (
const auto l : latencyList)
565 averageLatency /= latencyList.
size();
566 if (averageLatency < 0)
571 const qint64 usecsRead =
info->read_index < 0 ? 0 : pa_bytes_to_usec(
info->read_index, &m_spec);
572 const qint64 usecsWritten =
info->write_index < 0 ? 0 : pa_bytes_to_usec(
info->write_index, &m_spec);
575 qint64 usecs = usecsRead - averageLatency;
578 gettimeofday(&tv,
nullptr);
581 qint64 timeSinceUpdate = tv -
info->timestamp;
582 if (timeSinceUpdate > 0)
583 usecs += timeSinceUpdate;
586 if (usecs > usecsWritten)
587 usecs = usecsWritten;
590 if (usecs < lastProcessedUSecs)
591 usecs = lastProcessedUSecs;
593 lastProcessedUSecs = usecs;
606 std::lock_guard
lock(*pulseEngine);
610 pulseEngine->
wait(operation.get());
613 pulseEngine->
wait(operation.get());
616 m_tickTimer.
start(m_periodTime,
this);
636 m_suspendedInState =
state;
643 std::lock_guard
lock(*pulseEngine);
647 pulseEngine->
wait(operation.get());
658 m_audioDevice = qobject_cast<QPulseAudioSink*>(audio);
675 while (written <
len) {
676 int chunk = m_audioDevice->write(
data+written, (
len-written));
699void QPulseAudioSink::onPulseContextFailed()
706PAOperationUPtr QPulseAudioSink::exchangeDrainOperation(pa_operation *newOperation)
713#include "moc_qpulseaudiosink_p.cpp"
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.
void start(int msec, QObject *obj)
\obsolete Use chrono overload instead.
int timerId() const noexcept
Returns the timer's ID.
void stop()
Stops the timer.
bool isActive() const noexcept
Returns true if the timer is running and has not been stopped; otherwise returns false.
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
bool isNull() const noexcept
Returns true if this byte array is null; otherwise returns false.
static void processEvents(QEventLoop::ProcessEventsFlags flags=QEventLoop::AllEvents)
Processes some pending events for the calling thread according to the specified flags.
\inmodule QtCore \reentrant
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 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
void pop_front() noexcept
void append(parameter_type t)
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
virtual void timerEvent(QTimerEvent *event)
This event handler can be reimplemented in a subclass to receive timer events for the object.
static QPulseAudioEngine * instance()
pa_threaded_mainloop * mainloop()
void wait(pa_operation *op)
void streamDrainedCallback()
void streamUnderflowCallback()
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 setBufferSize(qsizetype value) 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
qsizetype bufferSize() const override
\macro QT_RESTRICTED_CAST_FROM_ASCII
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
QString arg(qlonglong a, int fieldwidth=0, int base=10, QChar fillChar=u' ') const
QByteArray toUtf8() const &
void qMultiplySamples(qreal factor, const QAudioFormat &format, const void *src, void *dest, int len)
The QAudio namespace contains enums used by the audio classes.
State
\value ActiveState Audio data is being processed, this state is set after start() is called and while...
Error
\value NoError No errors have occurred \value OpenError An error occurred opening the audio device \v...
static QString sampleFormatToQString(pa_sample_format format)
pa_channel_map channelMapForAudioFormat(const QAudioFormat &format)
pa_sample_spec audioFormatToSampleSpec(const QAudioFormat &format)
Combined button and popup list for selecting options.
DBusConnection const char DBusError * error
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
#define qCWarning(category,...)
#define qCDebug(category,...)
constexpr const T & qMin(const T &a, const T &b)
constexpr const T & qBound(const T &min, const T &val, const T &max)
GLenum GLsizei GLuint GLint * bytesWritten
GLenum GLuint GLenum GLsizei length
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat t1
[4]
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLint GLsizei GLsizei GLenum format
GLenum GLenum GLenum input
static void outputStreamStateCallback(pa_stream *stream, void *userdata)
static void outputStreamOverflowCallback(pa_stream *stream, void *userdata)
QT_BEGIN_NAMESPACE const int 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)
std::unique_ptr< pa_operation, PAOperationDeleter > PAOperationUPtr
QLatin1StringView QLatin1String
QFileInfo info(fileName)
[8]
myObject disconnect()
[26]
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent