Qt 6.x
The Qt SDK
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Pages
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
29{
30 m_device = device;
31
32 timer = new QTimer(this);
33 connect(timer,SIGNAL(timeout()),SLOT(userFeed()));
34}
35
37{
38 close();
39 disconnect(timer, SIGNAL(timeout()));
41 delete timer;
42}
43
45{
46 m_volume = vol;
47}
48
50{
51 return m_volume;
52}
53
55{
56 return errorState;
57}
58
60{
61 return deviceState;
62}
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_LE;
119 else
120 pcmformat = SND_PCM_FORMAT_S16_BE;
121 break;
124 pcmformat = SND_PCM_FORMAT_S32_LE;
125 else
126 pcmformat = SND_PCM_FORMAT_S32_BE;
127 break;
130 pcmformat = SND_PCM_FORMAT_FLOAT_LE;
131 else
132 pcmformat = SND_PCM_FORMAT_FLOAT_BE;
133 default:
134 break;
135 }
136
137 return pcmformat != SND_PCM_FORMAT_UNKNOWN
138 ? snd_pcm_hw_params_set_format( handle, hwparams, pcmformat)
139 : -1;
140}
141
143{
146
148
149 // Handle change of mode
150 if(audioSource && !pullMode) {
151 delete audioSource;
152 audioSource = 0;
153 }
154
155 close();
156
157 pullMode = true;
159
161
162 open();
163
165}
166
168{
171
173
174 // Handle change of mode
175 if(audioSource && !pullMode) {
176 delete audioSource;
177 audioSource = 0;
178 }
179
180 close();
181
182 audioSource = new AlsaOutputPrivate(this);
184 pullMode = false;
185
187
188 open();
189
191
192 return audioSource;
193}
194
196{
198 return;
201 close();
203}
204
205bool QAlsaAudioSink::open()
206{
207 if(opened)
208 return true;
209
210#ifdef DEBUG_AUDIO
212 qDebug()<<now.second()<<"s "<<now.msec()<<"ms :open()";
213#endif
214 elapsedTimeOffset = 0;
215
216 int dir;
217 int err = 0;
218 int count=0;
219 unsigned int sampleRate = settings.sampleRate();
220
221 if (!settings.isValid()) {
222 qWarning("QAudioSink: open error, invalid format.");
223 } else if (settings.sampleRate() <= 0) {
224 qWarning("QAudioSink: open error, invalid sample rate (%d).",
226 } else {
227 err = -1;
228 }
229
230 if (err == 0) {
234 return false;
235 }
236
237 // Step 1: try and open the device
238 while((count < 5) && (err < 0)) {
239 err=snd_pcm_open(&handle, m_device.constData(),SND_PCM_STREAM_PLAYBACK,0);
240 if(err < 0)
241 count++;
242 }
243 if (( err < 0)||(handle == 0)) {
247 return false;
248 }
249 snd_pcm_nonblock( handle, 0 );
250
251 // Step 2: Set the desired HW parameters.
252 snd_pcm_hw_params_alloca( &hwparams );
253
254 bool fatal = false;
255 QString errMessage;
256 unsigned int chunks = 8;
257
258 err = snd_pcm_hw_params_any( handle, hwparams );
259 if ( err < 0 ) {
260 fatal = true;
261 errMessage = QString::fromLatin1("QAudioSink: snd_pcm_hw_params_any: err = %1").arg(err);
262 }
263 if ( !fatal ) {
264 err = snd_pcm_hw_params_set_rate_resample( handle, hwparams, 1 );
265 if ( err < 0 ) {
266 fatal = true;
267 errMessage = QString::fromLatin1("QAudioSink: snd_pcm_hw_params_set_rate_resample: err = %1").arg(err);
268 }
269 }
270 if ( !fatal ) {
271 err = snd_pcm_hw_params_set_access( handle, hwparams, access );
272 if ( err < 0 ) {
273 fatal = true;
274 errMessage = QString::fromLatin1("QAudioSink: snd_pcm_hw_params_set_access: err = %1").arg(err);
275 }
276 }
277 if ( !fatal ) {
278 err = setFormat();
279 if ( err < 0 ) {
280 fatal = true;
281 errMessage = QString::fromLatin1("QAudioSink: snd_pcm_hw_params_set_format: err = %1").arg(err);
282 }
283 }
284 if ( !fatal ) {
285 err = snd_pcm_hw_params_set_channels( handle, hwparams, (unsigned int)settings.channelCount() );
286 if ( err < 0 ) {
287 fatal = true;
288 errMessage = QString::fromLatin1("QAudioSink: snd_pcm_hw_params_set_channels: err = %1").arg(err);
289 }
290 }
291 if ( !fatal ) {
292 err = snd_pcm_hw_params_set_rate_near( handle, hwparams, &sampleRate, 0 );
293 if ( err < 0 ) {
294 fatal = true;
295 errMessage = QString::fromLatin1("QAudioSink: snd_pcm_hw_params_set_rate_near: err = %1").arg(err);
296 }
297 }
298 if ( !fatal ) {
299 unsigned int maxBufferTime = 0;
300 unsigned int minBufferTime = 0;
301 unsigned int maxPeriodTime = 0;
302 unsigned int minPeriodTime = 0;
303
304 err = snd_pcm_hw_params_get_buffer_time_max(hwparams, &maxBufferTime, &dir);
305 if ( err >= 0)
306 err = snd_pcm_hw_params_get_buffer_time_min(hwparams, &minBufferTime, &dir);
307 if ( err >= 0)
308 err = snd_pcm_hw_params_get_period_time_max(hwparams, &maxPeriodTime, &dir);
309 if ( err >= 0)
310 err = snd_pcm_hw_params_get_period_time_min(hwparams, &minPeriodTime, &dir);
311
312 if ( err < 0 ) {
313 fatal = true;
314 errMessage = QString::fromLatin1("QAudioSink: buffer/period min and max: err = %1").arg(err);
315 } else {
316 static unsigned user_buffer_time = qEnvironmentVariableIntValue("QT_ALSA_OUTPUT_BUFFER_TIME");
317 static unsigned user_period_time = qEnvironmentVariableIntValue("QT_ALSA_OUTPUT_PERIOD_TIME");
318 const bool outOfRange = maxBufferTime < buffer_time || buffer_time < minBufferTime || maxPeriodTime < period_time || minPeriodTime > period_time;
319 if (outOfRange || user_period_time || user_buffer_time) {
320 period_time = user_period_time ? user_period_time : minPeriodTime;
321 if (!user_buffer_time) {
322 chunks = maxBufferTime / period_time;
323 buffer_time = period_time * chunks;
324 } else {
325 buffer_time = user_buffer_time;
326 chunks = buffer_time / period_time;
327 }
328 }
329 qCDebug(lcAlsaOutput) << "buffer time: [" << minBufferTime << "-" << maxBufferTime << "] =" << buffer_time;
330 qCDebug(lcAlsaOutput) << "period time: [" << minPeriodTime << "-" << maxPeriodTime << "] =" << period_time;
331 qCDebug(lcAlsaOutput) << "chunks =" << chunks;
332 }
333 }
334 if ( !fatal ) {
335 err = snd_pcm_hw_params_set_buffer_time_near(handle, hwparams, &buffer_time, &dir);
336 if ( err < 0 ) {
337 fatal = true;
338 errMessage = QString::fromLatin1("QAudioSink: snd_pcm_hw_params_set_buffer_time_near: err = %1").arg(err);
339 }
340 }
341 if ( !fatal ) {
342 err = snd_pcm_hw_params_set_period_time_near(handle, hwparams, &period_time, &dir);
343 if ( err < 0 ) {
344 fatal = true;
345 errMessage = QString::fromLatin1("QAudioSink: snd_pcm_hw_params_set_period_time_near: err = %1").arg(err);
346 }
347 }
348 if ( !fatal ) {
349 err = snd_pcm_hw_params_set_periods_near(handle, hwparams, &chunks, &dir);
350 if ( err < 0 ) {
351 fatal = true;
352 errMessage = QString::fromLatin1("QAudioSink: snd_pcm_hw_params_set_periods_near: err = %1").arg(err);
353 }
354 }
355 if ( !fatal ) {
356 err = snd_pcm_hw_params(handle, hwparams);
357 if ( err < 0 ) {
358 fatal = true;
359 errMessage = QString::fromLatin1("QAudioSink: snd_pcm_hw_params: err = %1").arg(err);
360 }
361 }
362 if( err < 0) {
363 qWarning()<<errMessage;
367 return false;
368 }
369 snd_pcm_hw_params_get_buffer_size(hwparams,&buffer_frames);
370 buffer_size = snd_pcm_frames_to_bytes(handle,buffer_frames);
371 snd_pcm_hw_params_get_period_size(hwparams,&period_frames, &dir);
372 period_size = snd_pcm_frames_to_bytes(handle,period_frames);
373 snd_pcm_hw_params_get_buffer_time(hwparams,&buffer_time, &dir);
374 snd_pcm_hw_params_get_period_time(hwparams,&period_time, &dir);
375
376 // Step 3: Set the desired SW parameters.
377 snd_pcm_sw_params_t *swparams;
378 snd_pcm_sw_params_alloca(&swparams);
379 snd_pcm_sw_params_current(handle, swparams);
380 snd_pcm_sw_params_set_start_threshold(handle,swparams,period_frames);
381 snd_pcm_sw_params_set_stop_threshold(handle,swparams,buffer_frames);
382 snd_pcm_sw_params_set_avail_min(handle, swparams,period_frames);
383 snd_pcm_sw_params(handle, swparams);
384
385 // Step 4: Prepare audio
386 if(audioBuffer == 0)
387 audioBuffer = new char[snd_pcm_frames_to_bytes(handle,buffer_frames)];
388 snd_pcm_prepare( handle );
389 snd_pcm_start(handle);
390
391 // Step 5: Setup timer
392 bytesAvailable = bytesFree();
393
394 // Step 6: Start audio processing
395 timer->start(period_time/1000);
396
397 elapsedTimeOffset = 0;
399 totalTimeValue = 0;
400 opened = true;
401
402 return true;
403}
404
405void QAlsaAudioSink::close()
406{
407 timer->stop();
408
409 if ( handle ) {
410 snd_pcm_drain( handle );
411 snd_pcm_close( handle );
412 handle = 0;
413 delete [] audioBuffer;
414 audioBuffer=0;
415 }
416 if(!pullMode && audioSource) {
417 delete audioSource;
418 audioSource = 0;
419 }
420 opened = false;
421}
422
424{
425 if(resuming)
426 return period_size;
427
429 return 0;
430
431 int frames = snd_pcm_avail_update(handle);
432 if (frames == -EPIPE) {
433 // Try and handle buffer underrun
434 int err = snd_pcm_recover(handle, frames, 0);
435 if (err < 0)
436 return 0;
437 else
438 frames = snd_pcm_avail_update(handle);
439 } else if (frames < 0) {
440 return 0;
441 }
442
443 if ((int)frames > (int)buffer_frames)
444 frames = buffer_frames;
445
446 return snd_pcm_frames_to_bytes(handle, frames);
447}
448
450{
451 // Write out some audio data
452 if ( !handle )
453 return 0;
454#ifdef DEBUG_AUDIO
455 qDebug()<<"frames to write out = "<<
456 snd_pcm_bytes_to_frames( handle, (int)len )<<" ("<<len<<") bytes";
457#endif
458 int frames, err;
459 int space = bytesFree();
460
461 if (!space)
462 return 0;
463
464 if (len < space)
465 space = len;
466
467 frames = snd_pcm_bytes_to_frames(handle, space);
468
469 if (m_volume < 1.0f) {
471 QAudioHelperInternal::qMultiplySamples(m_volume, settings, data, out.data(), space);
472 err = snd_pcm_writei(handle, out.constData(), frames);
473 } else {
474 err = snd_pcm_writei(handle, data, frames);
475 }
476
477 if(err > 0) {
478 totalTimeValue += err;
479 resuming = false;
484 }
485 return snd_pcm_frames_to_bytes( handle, err );
486 } else
487 err = xrun_recovery(err);
488
489 if(err < 0) {
490 close();
495 }
496 return 0;
497}
498
500{
502 buffer_size = value;
503}
504
506{
507 return buffer_size;
508}
509
511{
512 return qint64(1000000) * totalTimeValue / settings.sampleRate();
513}
514
516{
518 int err = 0;
519
520 if(handle) {
521 err = snd_pcm_prepare( handle );
522 if(err < 0)
523 xrun_recovery(err);
524
525 err = snd_pcm_start(handle);
526 if(err < 0)
527 xrun_recovery(err);
528
529 bytesAvailable = (int)snd_pcm_frames_to_bytes(handle, buffer_frames);
530 }
531 resuming = true;
532
535 timer->start(period_time/1000);
537 }
538}
539
541{
542 settings = fmt;
543}
544
546{
547 return settings;
548}
549
551{
554 snd_pcm_drain(handle);
555 timer->stop();
559 }
560}
561
562void QAlsaAudioSink::userFeed()
563{
565 return;
566#ifdef DEBUG_AUDIO
568 qDebug()<<now.second()<<"s "<<now.msec()<<"ms :userFeed() OUT";
569#endif
571 bytesAvailable = bytesFree();
572
573 deviceReady();
574}
575
576bool QAlsaAudioSink::deviceReady()
577{
578 if(pullMode) {
579 int l = 0;
580 int chunks = bytesAvailable/period_size;
581 if(chunks==0) {
582 bytesAvailable = bytesFree();
583 return false;
584 }
585#ifdef DEBUG_AUDIO
586 qDebug()<<"deviceReady() avail="<<bytesAvailable<<" bytes, period size="<<period_size<<" bytes";
587 qDebug()<<"deviceReady() no. of chunks that can fit ="<<chunks<<", chunks in bytes ="<<period_size*chunks;
588#endif
589 int input = period_frames*chunks;
590 if(input > (int)buffer_frames)
591 input = buffer_frames;
592 l = audioSource->read(audioBuffer,snd_pcm_frames_to_bytes(handle, input));
593
594 // reading can take a while and stream may have been stopped
595 if (!handle)
596 return false;
597
598 if(l > 0) {
599 // Got some data to output
601 return true;
602 qint64 bytesWritten = write(audioBuffer,l);
603 if (bytesWritten != l)
605 bytesAvailable = bytesFree();
606
607 } else if(l == 0) {
608 // Did not get any data to output
609 bytesAvailable = bytesFree();
610 if(bytesAvailable > snd_pcm_frames_to_bytes(handle, buffer_frames-period_frames)) {
611 // Underrun
617 }
618 }
619
620 } else if(l < 0) {
621 close();
626 }
627 } else {
628 bytesAvailable = bytesFree();
629 if(bytesAvailable > snd_pcm_frames_to_bytes(handle, buffer_frames-period_frames)) {
630 // Underrun
636 }
637 }
638 }
639
641 return true;
642
643 return true;
644}
645
647{
648 if(handle)
649 snd_pcm_reset(handle);
650
651 stop();
652}
653
655{
656 audioDevice = qobject_cast<QAlsaAudioSink*>(audio);
657}
658
660
662{
663 Q_UNUSED(data);
664 Q_UNUSED(len);
665
666 return 0;
667}
668
670{
671 int retry = 0;
672 qint64 written = 0;
673 if((audioDevice->deviceState == QAudio::ActiveState)
674 ||(audioDevice->deviceState == QAudio::IdleState)) {
675 while(written < len) {
676 int chunk = audioDevice->write(data+written,(len-written));
677 if(chunk <= 0)
678 retry++;
679 written+=chunk;
680 if(retry > 10)
681 return written;
682 }
683 }
684 return written;
685
686}
687
689
690#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.
\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:122
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.
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,...
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:90
void errorChanged(QAudio::Error error)
void stateChanged(QAudio::State state)
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5710
QString arg(qlonglong a, int fieldwidth=0, int base=10, QChar fillChar=u' ') const
Definition qstring.cpp:8606
@ BigEndian
Definition qsysinfo.h:29
@ ByteOrder
Definition qsysinfo.h:34
\inmodule QtCore \reentrant
Definition qdatetime.h:189
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:208
void stop()
Stops the timer.
Definition qtimer.cpp:226
void qMultiplySamples(qreal factor, const QAudioFormat &format, const void *src, void *dest, int len)
State
\value ActiveState Audio data is being processed, this state is set after start() is called and while...
Definition qaudio.h:25
@ StoppedState
Definition qaudio.h:25
@ SuspendedState
Definition qaudio.h:25
@ IdleState
Definition qaudio.h:25
@ ActiveState
Definition qaudio.h:25
Error
\value NoError No errors have occurred \value OpenError An error occurred opening the audio device \v...
Definition qaudio.h:24
@ UnderrunError
Definition qaudio.h:24
@ FatalError
Definition qaudio.h:24
@ OpenError
Definition qaudio.h:24
@ NoError
Definition qaudio.h:24
@ IOError
Definition qaudio.h:24
Combined button and popup list for selecting options.
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
#define qDebug
[1]
Definition qlogging.h:160
#define qWarning
Definition qlogging.h:162
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
#define SLOT(a)
Definition qobjectdefs.h:51
#define SIGNAL(a)
Definition qobjectdefs.h:52
GLenum GLsizei GLuint GLint * bytesWritten
GLuint64 GLenum void * handle
GLenum GLenum GLsizei count
GLbitfield GLuint64 timeout
[4]
GLenum access
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
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:70
long long qint64
Definition qtypes.h:55
double qreal
Definition qtypes.h:92
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]
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent