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
qalsaaudiosink.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//
5// W A R N I N G
6// -------------
7//
8// This file is not part of the Qt API. It exists for the convenience
9// of other Qt classes. This header file may change from version to
10// version without notice, or even be removed.
11//
12// INTERNAL USE ONLY: Do NOT use for any other purpose.
13//
14
15#include <QtCore/qcoreapplication.h>
16#include <QtCore/qvarlengtharray.h>
17#include <QtMultimedia/private/qaudiohelpers_p.h>
18#include "qalsaaudiosink_p.h"
19#include "qalsaaudiodevice_p.h"
20#include <QLoggingCategory>
21
23
24static Q_LOGGING_CATEGORY(lcAlsaOutput, "qt.multimedia.alsa.output")
25//#define DEBUG_AUDIO 1
26
28 : QPlatformAudioSink(parent)
29{
30 m_device = device;
31
32 timer = new QTimer(this);
33 connect(timer, &QTimer::timeout, this, &QAlsaAudioSink::userFeed);
34}
35
37{
38 close();
39 disconnect(timer, &QTimer::timeout, this, &QAlsaAudioSink::userFeed);
41 delete timer;
42}
43
45{
46 m_volume = vol;
47}
48
50{
51 return m_volume;
52}
53
58
63
64int QAlsaAudioSink::xrun_recovery(int err)
65{
66 int count = 0;
67 bool reset = false;
68
69 // ESTRPIPE is not available in all OSes where ALSA is available
70 int estrpipe = EIO;
71#ifdef ESTRPIPE
72 estrpipe = ESTRPIPE;
73#endif
74
75 if(err == -EPIPE) {
78 err = snd_pcm_prepare(handle);
79 if(err < 0)
80 reset = true;
81
82 } else if ((err == -estrpipe)||(err == -EIO)) {
85 while((err = snd_pcm_resume(handle)) == -EAGAIN){
86 usleep(100);
87 count++;
88 if(count > 5) {
89 reset = true;
90 break;
91 }
92 }
93 if(err < 0) {
94 err = snd_pcm_prepare(handle);
95 if(err < 0)
96 reset = true;
97 }
98 }
99 if(reset) {
100 close();
101 open();
102 snd_pcm_prepare(handle);
103 return 0;
104 }
105 return err;
106}
107
109{
110 snd_pcm_format_t pcmformat = SND_PCM_FORMAT_UNKNOWN;
111
112 switch (settings.sampleFormat()) {
114 pcmformat = SND_PCM_FORMAT_U8;
115 break;
118 pcmformat = SND_PCM_FORMAT_S16_BE;
119 else
120 pcmformat = SND_PCM_FORMAT_S16_LE;
121 break;
124 pcmformat = SND_PCM_FORMAT_S32_BE;
125 else
126 pcmformat = SND_PCM_FORMAT_S32_LE;
127 break;
130 pcmformat = SND_PCM_FORMAT_FLOAT_BE;
131 else
132 pcmformat = SND_PCM_FORMAT_FLOAT_LE;
133 break;
134 default:
135 break;
136 }
137
138 return pcmformat != SND_PCM_FORMAT_UNKNOWN
139 ? snd_pcm_hw_params_set_format( handle, hwparams, pcmformat)
140 : -1;
141}
142
144{
147
149
150 // Handle change of mode
151 if(audioSource && !pullMode) {
152 delete audioSource;
153 audioSource = 0;
154 }
155
156 close();
157
158 pullMode = true;
160
161 connect(audioSource, &QIODevice::readyRead, timer, [this] {
162 if (!timer->isActive()) {
163 timer->start(period_time / 1000);
164 }
165 });
167
168 open();
169
171}
172
174{
177
179
180 // Handle change of mode
181 if(audioSource && !pullMode) {
182 delete audioSource;
183 audioSource = 0;
184 }
185
186 close();
187
188 audioSource = new AlsaOutputPrivate(this);
190 pullMode = false;
191
193
194 open();
195
197
198 return audioSource;
199}
200
210
211bool QAlsaAudioSink::open()
212{
213 if(opened)
214 return true;
215
216#ifdef DEBUG_AUDIO
218 qDebug()<<now.second()<<"s "<<now.msec()<<"ms :open()";
219#endif
220 elapsedTimeOffset = 0;
221
222 int dir;
223 int err = 0;
224 int count=0;
225 unsigned int sampleRate = settings.sampleRate();
226
227 if (!settings.isValid()) {
228 qWarning("QAudioSink: open error, invalid format.");
229 } else if (settings.sampleRate() <= 0) {
230 qWarning("QAudioSink: open error, invalid sample rate (%d).",
232 } else {
233 err = -1;
234 }
235
236 if (err == 0) {
240 return false;
241 }
242
243 // Step 1: try and open the device
244 while((count < 5) && (err < 0)) {
245 err=snd_pcm_open(&handle, m_device.constData(),SND_PCM_STREAM_PLAYBACK,0);
246 if(err < 0)
247 count++;
248 }
249 if (( err < 0)||(handle == 0)) {
253 return false;
254 }
255 snd_pcm_nonblock( handle, 0 );
256
257 // Step 2: Set the desired HW parameters.
258 snd_pcm_hw_params_alloca( &hwparams );
259
260 bool fatal = false;
261 QString errMessage;
262 unsigned int chunks = 8;
263
264 err = snd_pcm_hw_params_any( handle, hwparams );
265 if ( err < 0 ) {
266 fatal = true;
267 errMessage = QString::fromLatin1("QAudioSink: snd_pcm_hw_params_any: err = %1").arg(err);
268 }
269 if ( !fatal ) {
270 err = snd_pcm_hw_params_set_rate_resample( handle, hwparams, 1 );
271 if ( err < 0 ) {
272 fatal = true;
273 errMessage = QString::fromLatin1("QAudioSink: snd_pcm_hw_params_set_rate_resample: err = %1").arg(err);
274 }
275 }
276 if ( !fatal ) {
277 err = snd_pcm_hw_params_set_access( handle, hwparams, access );
278 if ( err < 0 ) {
279 fatal = true;
280 errMessage = QString::fromLatin1("QAudioSink: snd_pcm_hw_params_set_access: err = %1").arg(err);
281 }
282 }
283 if ( !fatal ) {
284 err = setFormat();
285 if ( err < 0 ) {
286 fatal = true;
287 errMessage = QString::fromLatin1("QAudioSink: snd_pcm_hw_params_set_format: err = %1").arg(err);
288 }
289 }
290 if ( !fatal ) {
291 err = snd_pcm_hw_params_set_channels( handle, hwparams, (unsigned int)settings.channelCount() );
292 if ( err < 0 ) {
293 fatal = true;
294 errMessage = QString::fromLatin1("QAudioSink: snd_pcm_hw_params_set_channels: err = %1").arg(err);
295 }
296 }
297 if ( !fatal ) {
298 err = snd_pcm_hw_params_set_rate_near( handle, hwparams, &sampleRate, 0 );
299 if ( err < 0 ) {
300 fatal = true;
301 errMessage = QString::fromLatin1("QAudioSink: snd_pcm_hw_params_set_rate_near: err = %1").arg(err);
302 }
303 }
304 if ( !fatal ) {
305 unsigned int maxBufferTime = 0;
306 unsigned int minBufferTime = 0;
307 unsigned int maxPeriodTime = 0;
308 unsigned int minPeriodTime = 0;
309
310 err = snd_pcm_hw_params_get_buffer_time_max(hwparams, &maxBufferTime, &dir);
311 if ( err >= 0)
312 err = snd_pcm_hw_params_get_buffer_time_min(hwparams, &minBufferTime, &dir);
313 if ( err >= 0)
314 err = snd_pcm_hw_params_get_period_time_max(hwparams, &maxPeriodTime, &dir);
315 if ( err >= 0)
316 err = snd_pcm_hw_params_get_period_time_min(hwparams, &minPeriodTime, &dir);
317
318 if ( err < 0 ) {
319 fatal = true;
320 errMessage = QString::fromLatin1("QAudioSink: buffer/period min and max: err = %1").arg(err);
321 } else {
322 static unsigned user_buffer_time = qEnvironmentVariableIntValue("QT_ALSA_OUTPUT_BUFFER_TIME");
323 static unsigned user_period_time = qEnvironmentVariableIntValue("QT_ALSA_OUTPUT_PERIOD_TIME");
324 const bool outOfRange = maxBufferTime < buffer_time || buffer_time < minBufferTime || maxPeriodTime < period_time || minPeriodTime > period_time;
325 if (outOfRange || user_period_time || user_buffer_time) {
326 period_time = user_period_time ? user_period_time : minPeriodTime;
327 if (!user_buffer_time) {
328 chunks = maxBufferTime / period_time;
329 buffer_time = period_time * chunks;
330 } else {
331 buffer_time = user_buffer_time;
332 chunks = buffer_time / period_time;
333 }
334 }
335 qCDebug(lcAlsaOutput) << "buffer time: [" << minBufferTime << "-" << maxBufferTime << "] =" << buffer_time;
336 qCDebug(lcAlsaOutput) << "period time: [" << minPeriodTime << "-" << maxPeriodTime << "] =" << period_time;
337 qCDebug(lcAlsaOutput) << "chunks =" << chunks;
338 }
339 }
340 if ( !fatal ) {
341 err = snd_pcm_hw_params_set_buffer_time_near(handle, hwparams, &buffer_time, &dir);
342 if ( err < 0 ) {
343 fatal = true;
344 errMessage = QString::fromLatin1("QAudioSink: snd_pcm_hw_params_set_buffer_time_near: err = %1").arg(err);
345 }
346 }
347 if ( !fatal ) {
348 err = snd_pcm_hw_params_set_period_time_near(handle, hwparams, &period_time, &dir);
349 if ( err < 0 ) {
350 fatal = true;
351 errMessage = QString::fromLatin1("QAudioSink: snd_pcm_hw_params_set_period_time_near: err = %1").arg(err);
352 }
353 }
354 if ( !fatal ) {
355 err = snd_pcm_hw_params_set_periods_near(handle, hwparams, &chunks, &dir);
356 if ( err < 0 ) {
357 fatal = true;
358 errMessage = QString::fromLatin1("QAudioSink: snd_pcm_hw_params_set_periods_near: err = %1").arg(err);
359 }
360 }
361 if ( !fatal ) {
362 err = snd_pcm_hw_params(handle, hwparams);
363 if ( err < 0 ) {
364 fatal = true;
365 errMessage = QString::fromLatin1("QAudioSink: snd_pcm_hw_params: err = %1").arg(err);
366 }
367 }
368 if( err < 0) {
369 qWarning()<<errMessage;
373 return false;
374 }
375 snd_pcm_hw_params_get_buffer_size(hwparams,&buffer_frames);
376 buffer_size = snd_pcm_frames_to_bytes(handle,buffer_frames);
377 snd_pcm_hw_params_get_period_size(hwparams,&period_frames, &dir);
378 period_size = snd_pcm_frames_to_bytes(handle,period_frames);
379 snd_pcm_hw_params_get_buffer_time(hwparams,&buffer_time, &dir);
380 snd_pcm_hw_params_get_period_time(hwparams,&period_time, &dir);
381
382 // Step 3: Set the desired SW parameters.
383 snd_pcm_sw_params_t *swparams;
384 snd_pcm_sw_params_alloca(&swparams);
385 snd_pcm_sw_params_current(handle, swparams);
386 snd_pcm_sw_params_set_start_threshold(handle,swparams,period_frames);
387 snd_pcm_sw_params_set_stop_threshold(handle,swparams,buffer_frames);
388 snd_pcm_sw_params_set_avail_min(handle, swparams,period_frames);
389 snd_pcm_sw_params(handle, swparams);
390
391 // Step 4: Prepare audio
392 if(audioBuffer == 0)
393 audioBuffer = new char[snd_pcm_frames_to_bytes(handle,buffer_frames)];
394 snd_pcm_prepare( handle );
395 snd_pcm_start(handle);
396
397 // Step 5: Setup timer
398 bytesAvailable = bytesFree();
399
400 // Step 6: Start audio processing
401 timer->start(period_time/1000);
402
403 elapsedTimeOffset = 0;
405 totalTimeValue = 0;
406 opened = true;
407
408 return true;
409}
410
411void QAlsaAudioSink::close()
412{
413 timer->stop();
414
415 if ( handle ) {
416 snd_pcm_drain( handle );
417 snd_pcm_close( handle );
418 handle = 0;
419 delete [] audioBuffer;
420 audioBuffer=0;
421 }
422 if(!pullMode && audioSource) {
423 delete audioSource;
424 audioSource = 0;
425 }
426 opened = false;
427}
428
430{
431 if(resuming)
432 return period_size;
433
435 return 0;
436
437 int frames = snd_pcm_avail_update(handle);
438 if (frames == -EPIPE) {
439 // Try and handle buffer underrun
440 int err = snd_pcm_recover(handle, frames, 0);
441 if (err < 0)
442 return 0;
443 else
444 frames = snd_pcm_avail_update(handle);
445 } else if (frames < 0) {
446 return 0;
447 }
448
449 if ((int)frames > (int)buffer_frames)
450 frames = buffer_frames;
451
452 return snd_pcm_frames_to_bytes(handle, frames);
453}
454
456{
457 // Write out some audio data
458 if ( !handle )
459 return 0;
460#ifdef DEBUG_AUDIO
461 qDebug()<<"frames to write out = "<<
462 snd_pcm_bytes_to_frames( handle, (int)len )<<" ("<<len<<") bytes";
463#endif
464 int frames, err;
465 int space = bytesFree();
466
467 if (!space)
468 return 0;
469
470 if (len < space)
471 space = len;
472
473 frames = snd_pcm_bytes_to_frames(handle, space);
474
475 if (m_volume < 1.0f) {
476 QVarLengthArray<char, 4096> out(space);
477 QAudioHelperInternal::qMultiplySamples(m_volume, settings, data, out.data(), space);
478 err = snd_pcm_writei(handle, out.constData(), frames);
479 } else {
480 err = snd_pcm_writei(handle, data, frames);
481 }
482
483 if(err > 0) {
484 totalTimeValue += err;
485 resuming = false;
490 }
491 return snd_pcm_frames_to_bytes( handle, err );
492 } else
493 err = xrun_recovery(err);
494
495 if(err < 0) {
496 close();
501 }
502 return 0;
503}
504
510
512{
513 return buffer_size;
514}
515
517{
518 return qint64(1000000) * totalTimeValue / settings.sampleRate();
519}
520
522{
524 int err = 0;
525
526 if(handle) {
527 err = snd_pcm_prepare( handle );
528 if(err < 0)
529 xrun_recovery(err);
530
531 err = snd_pcm_start(handle);
532 if(err < 0)
533 xrun_recovery(err);
534
535 bytesAvailable = (int)snd_pcm_frames_to_bytes(handle, buffer_frames);
536 }
537 resuming = true;
538
541 timer->start(period_time/1000);
543 }
544}
545
550
552{
553 return settings;
554}
555
567
568void QAlsaAudioSink::userFeed()
569{
571 return;
572#ifdef DEBUG_AUDIO
574 qDebug()<<now.second()<<"s "<<now.msec()<<"ms :userFeed() OUT";
575#endif
577 bytesAvailable = bytesFree();
578
579 deviceReady();
580}
581
582bool QAlsaAudioSink::deviceReady()
583{
584 if(pullMode) {
585 int l = 0;
586 int chunks = bytesAvailable/period_size;
587 if(chunks==0) {
588 bytesAvailable = bytesFree();
589 return false;
590 }
591#ifdef DEBUG_AUDIO
592 qDebug()<<"deviceReady() avail="<<bytesAvailable<<" bytes, period size="<<period_size<<" bytes";
593 qDebug()<<"deviceReady() no. of chunks that can fit ="<<chunks<<", chunks in bytes ="<<period_size*chunks;
594#endif
595 int input = period_frames*chunks;
596 if(input > (int)buffer_frames)
597 input = buffer_frames;
598 l = audioSource->read(audioBuffer,snd_pcm_frames_to_bytes(handle, input));
599
600 // reading can take a while and stream may have been stopped
601 if (!handle)
602 return false;
603
604 if(l > 0) {
605 // Got some data to output
607 return true;
608 qint64 bytesWritten = write(audioBuffer,l);
609 if (bytesWritten != l)
611 bytesAvailable = bytesFree();
612
613 } else if(l == 0) {
614 // Did not get any data to output
615 timer->stop();
616 snd_pcm_drain(handle);
617 bytesAvailable = bytesFree();
618 if(bytesAvailable > snd_pcm_frames_to_bytes(handle, buffer_frames-period_frames)) {
619 // Underrun
625 }
626 }
627
628 } else if(l < 0) {
629 close();
634 }
635 } else {
636 bytesAvailable = bytesFree();
637 if(bytesAvailable > snd_pcm_frames_to_bytes(handle, buffer_frames-period_frames)) {
638 // Underrun
644 }
645 }
646 }
647
649 return true;
650
651 return true;
652}
653
655{
656 if(handle)
657 snd_pcm_reset(handle);
658
659 stop();
660}
661
663{
664 audioDevice = qobject_cast<QAlsaAudioSink*>(audio);
665}
666
668
670{
671 Q_UNUSED(data);
672 Q_UNUSED(len);
673
674 return 0;
675}
676
678{
679 int retry = 0;
680 qint64 written = 0;
681 if((audioDevice->deviceState == QAudio::ActiveState)
682 ||(audioDevice->deviceState == QAudio::IdleState)) {
683 while(written < len) {
684 int chunk = audioDevice->write(data+written,(len-written));
685 if(chunk <= 0)
686 retry++;
687 written+=chunk;
688 if(retry > 10)
689 return written;
690 }
691 }
692 return written;
693
694}
695
697
698#include "moc_qalsaaudiosink_p.cpp"
IOBluetoothDevice * device
qint64 writeData(const char *data, qint64 len) override
Writes up to maxSize bytes from data to the device.
AlsaOutputPrivate(QAlsaAudioSink *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...
friend class AlsaOutputPrivate
QIODevice * start() override
void setBufferSize(qsizetype value) override
QAudio::State suspendedInState
QIODevice * audioSource
qint64 processedUSecs() const override
void setFormat(const QAudioFormat &fmt) override
void setVolume(qreal) override
void suspend() override
QAudio::State deviceState
QAudio::State state() const override
qreal volume() const override
void resume() override
qsizetype bytesFree() const override
QAudioFormat format() const override
QAudioFormat settings
qsizetype bufferSize() const override
QAudio::Error errorState
void stop() override
QAudio::Error error() const override
qint64 write(const char *data, qint64 len)
void reset() override
The QAudioFormat class stores audio stream parameter information.
constexpr int channelCount() const noexcept
Returns the current channel count value.
constexpr int sampleRate() const noexcept
Returns the current sample rate in Hertz.
constexpr SampleFormat sampleFormat() const noexcept
Returns the current sample format.
constexpr bool isValid() const noexcept
Returns true if all of the parameters are valid.
void stateChanged(QAudio::State state)
void errorChanged(QAudio::Error error)
\inmodule QtCore
Definition qbytearray.h:57
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:124
static void processEvents(QEventLoop::ProcessEventsFlags flags=QEventLoop::AllEvents)
Processes some pending events for the calling thread according to the specified flags.
\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 pos() const
For random-access devices, this function returns the position that data is written to or read from.
virtual bool seek(qint64 pos)
For random-access devices, this function sets the current position to pos, returning true on success,...
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.
\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
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5871
@ BigEndian
Definition qsysinfo.h:29
@ ByteOrder
Definition qsysinfo.h:34
\inmodule QtCore \reentrant
Definition qdatetime.h:215
static QTime currentTime()
Returns the current time as reported by the system clock.
\inmodule QtCore
Definition qtimer.h:20
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
Definition qtimer.cpp:241
bool isActive() const
Returns true if the timer is running (pending); otherwise returns false.
Definition qtimer.cpp:167
void stop()
Stops the timer.
Definition qtimer.cpp:267
void timeout(QPrivateSignal)
This signal is emitted when the timer times out.
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
Combined button and popup list for selecting options.
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
#define qDebug
[1]
Definition qlogging.h:164
#define qWarning
Definition qlogging.h:166
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
GLenum GLsizei GLuint GLint * bytesWritten
GLuint64 GLenum void * handle
GLenum GLenum GLsizei count
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum access
GLboolean reset
GLenum GLsizei len
GLenum GLenum GLenum input
Q_CORE_EXPORT int qEnvironmentVariableIntValue(const char *varName, bool *ok=nullptr) noexcept
#define emit
#define Q_UNUSED(x)
ptrdiff_t qsizetype
Definition qtypes.h:165
long long qint64
Definition qtypes.h:60
double qreal
Definition qtypes.h:187
QVideoFrameFormat::PixelFormat fmt
QTextStream out(stdout)
[7]
connect(quitButton, &QPushButton::clicked, &app, &QCoreApplication::quit, Qt::QueuedConnection)
myObject disconnect()
[26]
QTimer * timer
[3]
QString dir
[11]