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
qalsaaudiosource.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 "qalsaaudiosource_p.h"
19
21
22//#define DEBUG_AUDIO 1
23
25 : QPlatformAudioSource(parent)
26{
27 bytesAvailable = 0;
28 handle = 0;
29 access = SND_PCM_ACCESS_RW_INTERLEAVED;
30 pcmformat = SND_PCM_FORMAT_S16;
31 buffer_size = 0;
32 period_size = 0;
33 buffer_time = 100000;
34 period_time = 20000;
38 audioSource = 0;
39 pullMode = true;
40 resuming = false;
41
42 m_volume = 1.0f;
43
44 m_device = device;
45
46 timer = new QTimer(this);
47 connect(timer, &QTimer::timeout, this, &QAlsaAudioSource::userFeed);
48}
49
51{
52 close();
53 disconnect(timer, &QTimer::timeout, this, &QAlsaAudioSource::userFeed);
55 delete timer;
56}
57
59{
60 m_volume = vol;
61}
62
64{
65 return m_volume;
66}
67
72
77
83
88
89int QAlsaAudioSource::xrun_recovery(int err)
90{
91 int count = 0;
92 bool reset = false;
93
94 // ESTRPIPE is not available in all OSes where ALSA is available
95 int estrpipe = EIO;
96#ifdef ESTRPIPE
97 estrpipe = ESTRPIPE;
98#endif
99
100 if(err == -EPIPE) {
102 err = snd_pcm_prepare(handle);
103 if(err < 0)
104 reset = true;
105 else {
106 bytesAvailable = checkBytesReady();
107 if (bytesAvailable <= 0)
108 reset = true;
109 }
110 } else if ((err == -estrpipe)||(err == -EIO)) {
112 while((err = snd_pcm_resume(handle)) == -EAGAIN){
113 usleep(100);
114 count++;
115 if(count > 5) {
116 reset = true;
117 break;
118 }
119 }
120 if(err < 0) {
121 err = snd_pcm_prepare(handle);
122 if(err < 0)
123 reset = true;
124 }
125 }
126 if(reset) {
127 close();
128 open();
129 snd_pcm_prepare(handle);
130 return 0;
131 }
132 return err;
133}
134
136{
137 snd_pcm_format_t pcmformat = SND_PCM_FORMAT_UNKNOWN;
138
139 switch (settings.sampleFormat()) {
141 pcmformat = SND_PCM_FORMAT_U8;
142 break;
145 pcmformat = SND_PCM_FORMAT_S16_BE;
146 else
147 pcmformat = SND_PCM_FORMAT_S16_LE;
148 break;
151 pcmformat = SND_PCM_FORMAT_S32_BE;
152 else
153 pcmformat = SND_PCM_FORMAT_S32_LE;
154 break;
157 pcmformat = SND_PCM_FORMAT_FLOAT_BE;
158 else
159 pcmformat = SND_PCM_FORMAT_FLOAT_LE;
160 break;
161 default:
162 break;
163 }
164
165 return pcmformat != SND_PCM_FORMAT_UNKNOWN
166 ? snd_pcm_hw_params_set_format( handle, hwparams, pcmformat)
167 : -1;
168}
169
171{
173 close();
174
175 if(!pullMode && audioSource)
176 delete audioSource;
177
178 pullMode = true;
180
182
183 if( !open() )
184 return;
185
187}
188
190{
192 close();
193
194 if(!pullMode && audioSource)
195 delete audioSource;
196
197 pullMode = false;
198 audioSource = new AlsaInputPrivate(this);
200
202
203 if( !open() )
204 return 0;
205
207
208 return audioSource;
209}
210
212{
214 return;
215
217
218 close();
220}
221
222bool QAlsaAudioSource::open()
223{
224#ifdef DEBUG_AUDIO
226 qDebug()<<now.second()<<"s "<<now.msec()<<"ms :open()";
227#endif
228 elapsedTimeOffset = 0;
229
230 int dir;
231 int err = 0;
232 int count=0;
233 unsigned int sampleRate=settings.sampleRate();
234
235 if (!settings.isValid()) {
236 qWarning("QAudioSource: open error, invalid format.");
237 } else if (settings.sampleRate() <= 0) {
238 qWarning("QAudioSource: open error, invalid sample rate (%d).",
240 } else {
241 err = -1;
242 }
243
244 if (err == 0) {
248 return false;
249 }
250
251
252 // Step 1: try and open the device
253 while((count < 5) && (err < 0)) {
254 err = snd_pcm_open(&handle, m_device.constData(), SND_PCM_STREAM_CAPTURE,0);
255 if(err < 0)
256 count++;
257 }
258 if (( err < 0)||(handle == 0)) {
262 return false;
263 }
264 snd_pcm_nonblock( handle, 0 );
265
266 // Step 2: Set the desired HW parameters.
267 snd_pcm_hw_params_alloca( &hwparams );
268
269 bool fatal = false;
270 QString errMessage;
271 unsigned int chunks = 8;
272
273 err = snd_pcm_hw_params_any( handle, hwparams );
274 if ( err < 0 ) {
275 fatal = true;
276 errMessage = QString::fromLatin1("QAudioSource: snd_pcm_hw_params_any: err = %1").arg(err);
277 }
278 if ( !fatal ) {
279 err = snd_pcm_hw_params_set_rate_resample( handle, hwparams, 1 );
280 if ( err < 0 ) {
281 fatal = true;
282 errMessage = QString::fromLatin1("QAudioSource: snd_pcm_hw_params_set_rate_resample: err = %1").arg(err);
283 }
284 }
285 if ( !fatal ) {
286 err = snd_pcm_hw_params_set_access( handle, hwparams, access );
287 if ( err < 0 ) {
288 fatal = true;
289 errMessage = QString::fromLatin1("QAudioSource: snd_pcm_hw_params_set_access: err = %1").arg(err);
290 }
291 }
292 if ( !fatal ) {
293 err = setFormat();
294 if ( err < 0 ) {
295 fatal = true;
296 errMessage = QString::fromLatin1("QAudioSource: snd_pcm_hw_params_set_format: err = %1").arg(err);
297 }
298 }
299 if ( !fatal ) {
300 err = snd_pcm_hw_params_set_channels( handle, hwparams, (unsigned int)settings.channelCount() );
301 if ( err < 0 ) {
302 fatal = true;
303 errMessage = QString::fromLatin1("QAudioSource: snd_pcm_hw_params_set_channels: err = %1").arg(err);
304 }
305 }
306 if ( !fatal ) {
307 err = snd_pcm_hw_params_set_rate_near( handle, hwparams, &sampleRate, 0 );
308 if ( err < 0 ) {
309 fatal = true;
310 errMessage = QString::fromLatin1("QAudioSource: snd_pcm_hw_params_set_rate_near: err = %1").arg(err);
311 }
312 }
313 if ( !fatal ) {
314 err = snd_pcm_hw_params_set_buffer_time_near(handle, hwparams, &buffer_time, &dir);
315 if ( err < 0 ) {
316 fatal = true;
317 errMessage = QString::fromLatin1("QAudioSource: snd_pcm_hw_params_set_buffer_time_near: err = %1").arg(err);
318 }
319 }
320 if ( !fatal ) {
321 err = snd_pcm_hw_params_set_period_time_near(handle, hwparams, &period_time, &dir);
322 if ( err < 0 ) {
323 fatal = true;
324 errMessage = QString::fromLatin1("QAudioSource: snd_pcm_hw_params_set_period_time_near: err = %1").arg(err);
325 }
326 }
327 if ( !fatal ) {
328 err = snd_pcm_hw_params_set_periods_near(handle, hwparams, &chunks, &dir);
329 if ( err < 0 ) {
330 fatal = true;
331 errMessage = QString::fromLatin1("QAudioSource: snd_pcm_hw_params_set_periods_near: err = %1").arg(err);
332 }
333 }
334 if ( !fatal ) {
335 err = snd_pcm_hw_params(handle, hwparams);
336 if ( err < 0 ) {
337 fatal = true;
338 errMessage = QString::fromLatin1("QAudioSource: snd_pcm_hw_params: err = %1").arg(err);
339 }
340 }
341 if( err < 0) {
342 qWarning()<<errMessage;
346 return false;
347 }
348 snd_pcm_hw_params_get_buffer_size(hwparams,&buffer_frames);
349 buffer_size = snd_pcm_frames_to_bytes(handle,buffer_frames);
350 snd_pcm_hw_params_get_period_size(hwparams,&period_frames, &dir);
351 period_size = snd_pcm_frames_to_bytes(handle,period_frames);
352 snd_pcm_hw_params_get_buffer_time(hwparams,&buffer_time, &dir);
353 snd_pcm_hw_params_get_period_time(hwparams,&period_time, &dir);
354
355 // Step 3: Set the desired SW parameters.
356 snd_pcm_sw_params_t *swparams;
357 snd_pcm_sw_params_alloca(&swparams);
358 snd_pcm_sw_params_current(handle, swparams);
359 snd_pcm_sw_params_set_start_threshold(handle,swparams,period_frames);
360 snd_pcm_sw_params_set_stop_threshold(handle,swparams,buffer_frames);
361 snd_pcm_sw_params_set_avail_min(handle, swparams,period_frames);
362 snd_pcm_sw_params(handle, swparams);
363
364 // Step 4: Prepare audio
365 ringBuffer.resize(buffer_size);
366 snd_pcm_prepare( handle );
367 snd_pcm_start(handle);
368
369 // Step 5: Setup timer
370 bytesAvailable = checkBytesReady();
371
372 if(pullMode)
373 connect(audioSource, &QIODevice::readyRead, this, &QAlsaAudioSource::userFeed);
374
375 // Step 6: Start audio processing
376 chunks = buffer_size/period_size;
377 timer->start(period_time*chunks/2000);
378
380
381 totalTimeValue = 0;
382
383 return true;
384}
385
386void QAlsaAudioSource::close()
387{
388 timer->stop();
389
390 if ( handle ) {
391 snd_pcm_drop( handle );
392 snd_pcm_close( handle );
393 handle = 0;
394 }
395}
396
397int QAlsaAudioSource::checkBytesReady()
398{
399 if(resuming)
400 bytesAvailable = period_size;
403 bytesAvailable = 0;
404 else {
405 int frames = snd_pcm_avail_update(handle);
406 if (frames < 0) {
407 bytesAvailable = frames;
408 } else {
409 if((int)frames > (int)buffer_frames)
410 frames = buffer_frames;
411 bytesAvailable = snd_pcm_frames_to_bytes(handle, frames);
412 }
413 }
414 return bytesAvailable;
415}
416
418{
419 return qMax(bytesAvailable, 0);
420}
421
423{
424 // Read in some audio data and write it to QIODevice, pull mode
425 if ( !handle )
426 return 0;
427
428 int bytesRead = 0;
429 int bytesInRingbufferBeforeRead = ringBuffer.bytesOfDataInBuffer();
430
431 if (ringBuffer.bytesOfDataInBuffer() < len) {
432
433 // bytesAvaiable is saved as a side effect of checkBytesReady().
434 int bytesToRead = checkBytesReady();
435
436 if (bytesToRead < 0) {
437 // bytesAvailable as negative is error code, try to recover from it.
438 xrun_recovery(bytesToRead);
439 bytesToRead = checkBytesReady();
440 if (bytesToRead < 0) {
441 // recovery failed must stop and set error.
442 close();
446 return 0;
447 }
448 }
449
450 bytesToRead = qMin<qint64>(len, bytesToRead);
451 bytesToRead = qMin<qint64>(ringBuffer.freeBytes(), bytesToRead);
452 bytesToRead -= bytesToRead % period_size;
453
454 int count=0;
455 int err = 0;
456 QVarLengthArray<char, 4096> buffer(bytesToRead);
457 while(count < 5 && bytesToRead > 0) {
458 int chunks = bytesToRead / period_size;
459 int frames = chunks * period_frames;
460 if (frames > (int)buffer_frames)
461 frames = buffer_frames;
462
463 int readFrames = snd_pcm_readi(handle, buffer.data(), frames);
464 bytesRead = snd_pcm_frames_to_bytes(handle, readFrames);
465 if (m_volume < 1.0f)
467 buffer.constData(),
468 buffer.data(), bytesRead);
469
470 if (readFrames >= 0) {
471 ringBuffer.write(buffer.data(), bytesRead);
472#ifdef DEBUG_AUDIO
473 qDebug() << QString::fromLatin1("read in bytes = %1 (frames=%2)").arg(bytesRead).arg(readFrames).toLatin1().constData();
474#endif
475 break;
476 } else if((readFrames == -EAGAIN) || (readFrames == -EINTR)) {
478 err = 0;
479 break;
480 } else {
481 if(readFrames == -EPIPE) {
483 err = snd_pcm_prepare(handle);
484#ifdef ESTRPIPE
485 } else if(readFrames == -ESTRPIPE) {
486 err = snd_pcm_prepare(handle);
487#endif
488 }
489 if(err != 0) break;
490 }
491 count++;
492 }
493
494 }
495
496 bytesRead += bytesInRingbufferBeforeRead;
497
498 if (bytesRead > 0) {
499 // got some send it onward
500#ifdef DEBUG_AUDIO
501 qDebug() << "frames to write to QIODevice = " <<
502 snd_pcm_bytes_to_frames( handle, (int)bytesRead ) << " (" << bytesRead << ") bytes";
503#endif
505 return 0;
506
507 if (pullMode) {
508 qint64 l = 0;
510 while (ringBuffer.bytesOfDataInBuffer() > 0) {
511 l = audioSource->write(ringBuffer.availableData(), ringBuffer.availableDataBlockSize());
512 if (l > 0) {
513 ringBuffer.readBytes(l);
514 bytesWritten += l;
515 } else {
516 break;
517 }
518 }
519
520 if (l < 0) {
521 close();
525 } else if (l == 0 && bytesWritten == 0) {
530 }
531 } else {
532 bytesAvailable -= bytesWritten;
534 resuming = false;
539 }
540 }
541
542 return bytesWritten;
543 } else {
544 while (ringBuffer.bytesOfDataInBuffer() > 0) {
545 int size = ringBuffer.availableDataBlockSize();
546 memcpy(data, ringBuffer.availableData(), size);
547 data += size;
548 ringBuffer.readBytes(size);
549 }
550
551 bytesAvailable -= bytesRead;
552 totalTimeValue += bytesRead;
553 resuming = false;
558 }
559
560 return bytesRead;
561 }
562 }
563
564 return 0;
565}
566
568{
570 int err = 0;
571
572 if(handle) {
573 err = snd_pcm_prepare( handle );
574 if(err < 0)
575 xrun_recovery(err);
576
577 err = snd_pcm_start(handle);
578 if(err < 0)
579 xrun_recovery(err);
580
581 bytesAvailable = buffer_size;
582 }
583 resuming = true;
585 int chunks = buffer_size/period_size;
586 timer->start(period_time*chunks/2000);
588 }
589}
590
592{
593 buffer_size = value;
594}
595
597{
598 return buffer_size;
599}
600
609
611{
613 snd_pcm_drain(handle);
614 timer->stop();
617 }
618}
619
620void QAlsaAudioSource::userFeed()
621{
623 return;
624#ifdef DEBUG_AUDIO
626 qDebug()<<now.second()<<"s "<<now.msec()<<"ms :userFeed() IN";
627#endif
628 deviceReady();
629}
630
631bool QAlsaAudioSource::deviceReady()
632{
633 if(pullMode) {
634 // reads some audio data and writes it to QIODevice
635 read(0, buffer_size);
636 } else {
637 // emits readyRead() so user will call read() on QIODevice to get some audio data
638 AlsaInputPrivate* a = qobject_cast<AlsaInputPrivate*>(audioSource);
639 a->trigger();
640 }
641 bytesAvailable = checkBytesReady();
642
644 return true;
645
646 if (bytesAvailable < 0) {
647 // bytesAvailable as negative is error code, try to recover from it.
648 xrun_recovery(bytesAvailable);
649 bytesAvailable = checkBytesReady();
650 if (bytesAvailable < 0) {
651 // recovery failed must stop and set error.
652 close();
656 return 0;
657 }
658 }
659
660 return true;
661}
662
664{
665 if(handle)
666 snd_pcm_reset(handle);
667 stop();
668 bytesAvailable = 0;
669}
670
671void QAlsaAudioSource::drain()
672{
673 if(handle)
674 snd_pcm_drain(handle);
675}
676
678{
679 audioDevice = qobject_cast<QAlsaAudioSource*>(audio);
680}
681
685
687{
688 return audioDevice->read(data,len);
689}
690
692{
693 Q_UNUSED(data);
694 Q_UNUSED(len);
695 return 0;
696}
697
702
704 m_head(0),
705 m_tail(0)
706{
707}
708
710{
711 m_data.resize(size);
712}
713
715{
716 if (m_head < m_tail)
717 return m_tail - m_head;
718 else if (m_tail < m_head)
719 return m_data.size() + m_tail - m_head;
720 else
721 return 0;
722}
723
725{
726 if (m_head > m_tail)
727 return m_head - m_tail - 1;
728 else if (m_tail > m_head)
729 return m_data.size() - m_tail + m_head - 1;
730 else
731 return m_data.size() - 1;
732}
733
734const char *RingBuffer::availableData() const
735{
736 return (m_data.constData() + m_head);
737}
738
740{
741 if (m_head > m_tail)
742 return m_data.size() - m_head;
743 else if (m_tail > m_head)
744 return m_tail - m_head;
745 else
746 return 0;
747}
748
750{
751 m_head = (m_head + bytes) % m_data.size();
752}
753
754void RingBuffer::write(char *data, int len)
755{
756 if (m_tail + len < m_data.size()) {
757 memcpy(m_data.data() + m_tail, data, len);
758 m_tail += len;
759 } else {
760 int bytesUntilEnd = m_data.size() - m_tail;
761 memcpy(m_data.data() + m_tail, data, bytesUntilEnd);
762 if (len - bytesUntilEnd > 0)
763 memcpy(m_data.data(), data + bytesUntilEnd, len - bytesUntilEnd);
764 m_tail = len - bytesUntilEnd;
765 }
766}
767
769
770#include "moc_qalsaaudiosource_p.cpp"
IOBluetoothDevice * device
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.
AlsaInputPrivate(QAlsaAudioSource *audio)
void resume() override
void reset() override
void stop() override
QAlsaAudioSource(const QByteArray &device, QObject *parent)
qsizetype bytesReady() const override
QAudio::State deviceState
QAudioFormat format() const override
QIODevice * start() override
void setFormat(const QAudioFormat &fmt) override
void setVolume(qreal) override
qreal volume() const override
void setBufferSize(qsizetype value) override
QAudio::Error error() const override
QAudio::State state() const override
qsizetype bufferSize() const override
qint64 processedUSecs() const override
qint64 read(char *data, qint64 len)
QAudio::Error errorState
void suspend() 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 int bytesPerFrame() const
Returns the number of bytes required to represent one frame (a sample in each channel) in this 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
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
Definition qbytearray.h:611
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:494
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:124
void resize(qsizetype size)
Sets the size of the byte array to size bytes.
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...
qint64 write(const char *data, qint64 len)
Writes at most maxSize bytes of data from data to the device.
\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
void stop()
Stops the timer.
Definition qtimer.cpp:267
void timeout(QPrivateSignal)
This signal is emitted when the timer times out.
const char * availableData() const
int availableDataBlockSize() const
void write(char *data, int len)
int freeBytes() const
void readBytes(int bytes)
int bytesOfDataInBuffer() const
void resize(int size)
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
@ 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
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
GLenum GLsizei GLuint GLint * bytesWritten
GLuint64 GLenum void * handle
GLboolean GLboolean GLboolean GLboolean a
[7]
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLenum GLenum GLsizei count
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint buffer
GLenum access
GLboolean reset
GLuint64EXT * result
[6]
GLenum GLsizei len
#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
myObject disconnect()
[26]
QString dir
[11]