Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
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#include <QtCore/qcoreapplication.h>
5#include <QtCore/qvarlengtharray.h>
6#include <QtMultimedia/private/qaudiohelpers_p.h>
8
10
11//#define DEBUG_AUDIO 1
12
14 : QPlatformAudioSource(std::move(device), fmt, parent)
15{
16 bytesAvailable = 0;
17 handle = 0;
18 access = SND_PCM_ACCESS_RW_INTERLEAVED;
19 pcmformat = SND_PCM_FORMAT_S16;
20 buffer_size = 0;
21 period_size = 0;
22 buffer_time = 100000;
23 period_time = 20000;
24 totalTimeValue = 0;
25 deviceState = QAudio::StoppedState;
26 audioSource = 0;
27 pullMode = true;
28 resuming = false;
29
30 timer = new QTimer(this);
31 connect(timer, &QTimer::timeout, this, &QAlsaAudioSource::userFeed);
32}
33
35{
36 close();
37 disconnect(timer, &QTimer::timeout, this, &QAlsaAudioSource::userFeed);
38 delete timer;
39}
40
42{
43 return deviceState;
44}
45
46int QAlsaAudioSource::xrun_recovery(int err)
47{
48 int count = 0;
49 bool reset = false;
50
51 // ESTRPIPE is not available in all OSes where ALSA is available
52 int estrpipe = EIO;
53#ifdef ESTRPIPE
54 estrpipe = ESTRPIPE;
55#endif
56
57 if(err == -EPIPE) {
58 setError(QAudio::UnderrunError);
59 err = snd_pcm_prepare(handle);
60 if(err < 0)
61 reset = true;
62 else {
63 bytesAvailable = checkBytesReady();
64 if (bytesAvailable <= 0)
65 reset = true;
66 }
67 } else if ((err == -estrpipe)||(err == -EIO)) {
68 setError(QAudio::IOError);
69 while((err = snd_pcm_resume(handle)) == -EAGAIN){
70 usleep(100);
71 count++;
72 if(count > 5) {
73 reset = true;
74 break;
75 }
76 }
77 if(err < 0) {
78 err = snd_pcm_prepare(handle);
79 if(err < 0)
80 reset = true;
81 }
82 }
83 if(reset) {
84 close();
85 open();
86 snd_pcm_prepare(handle);
87 return 0;
88 }
89 return err;
90}
91
92int QAlsaAudioSource::setFormat()
93{
94 snd_pcm_format_t pcmformat = SND_PCM_FORMAT_UNKNOWN;
95
96 switch (m_format.sampleFormat()) {
97 case QAudioFormat::UInt8:
98 pcmformat = SND_PCM_FORMAT_U8;
99 break;
100 case QAudioFormat::Int16:
101 if constexpr (QSysInfo::ByteOrder == QSysInfo::BigEndian)
102 pcmformat = SND_PCM_FORMAT_S16_BE;
103 else
104 pcmformat = SND_PCM_FORMAT_S16_LE;
105 break;
106 case QAudioFormat::Int32:
107 if constexpr (QSysInfo::ByteOrder == QSysInfo::BigEndian)
108 pcmformat = SND_PCM_FORMAT_S32_BE;
109 else
110 pcmformat = SND_PCM_FORMAT_S32_LE;
111 break;
112 case QAudioFormat::Float:
113 if constexpr (QSysInfo::ByteOrder == QSysInfo::BigEndian)
114 pcmformat = SND_PCM_FORMAT_FLOAT_BE;
115 else
116 pcmformat = SND_PCM_FORMAT_FLOAT_LE;
117 break;
118 default:
119 break;
120 }
121
122 return pcmformat != SND_PCM_FORMAT_UNKNOWN
123 ? snd_pcm_hw_params_set_format( handle, hwparams, pcmformat)
124 : -1;
125}
126
127void QAlsaAudioSource::start(QIODevice* device)
128{
129 if(deviceState != QAudio::StoppedState)
130 close();
131
132 if(!pullMode && audioSource)
133 delete audioSource;
134
135 pullMode = true;
136 audioSource = device;
137
138 deviceState = QAudio::ActiveState;
139
140 if( !open() )
141 return;
142
143 emit stateChanged(deviceState);
144}
145
147{
148 if(deviceState != QAudio::StoppedState)
149 close();
150
151 if(!pullMode && audioSource)
152 delete audioSource;
153
154 pullMode = false;
155 audioSource = new AlsaInputPrivate(this);
156 audioSource->open(QIODevice::ReadOnly | QIODevice::Unbuffered);
157
158 deviceState = QAudio::IdleState;
159
160 if( !open() )
161 return 0;
162
163 emit stateChanged(deviceState);
164
165 return audioSource;
166}
167
169{
170 if(deviceState == QAudio::StoppedState)
171 return;
172
173 deviceState = QAudio::StoppedState;
174
175 close();
176 emit stateChanged(deviceState);
177}
178
179bool QAlsaAudioSource::open()
180{
181#ifdef DEBUG_AUDIO
182 QTime now(QTime::currentTime());
183 qDebug()<<now.second()<<"s "<<now.msec()<<"ms :open()";
184#endif
185 elapsedTimeOffset = 0;
186
187 int dir;
188 int err = -1;
189 int count=0;
190 unsigned int sampleRate=m_format.sampleRate();
191
192 // Step 1: try and open the device
193 while((count < 5) && (err < 0)) {
194 err = snd_pcm_open(&handle, m_audioDevice.id().constData(), SND_PCM_STREAM_CAPTURE, 0);
195 if(err < 0)
196 count++;
197 }
198 if (( err < 0)||(handle == 0)) {
199 setError(QAudio::OpenError);
200 deviceState = QAudio::StoppedState;
201 emit stateChanged(deviceState);
202 return false;
203 }
204 snd_pcm_nonblock( handle, 0 );
205
206 // Step 2: Set the desired HW parameters.
207 snd_pcm_hw_params_alloca( &hwparams );
208
209 bool fatal = false;
210 QString errMessage;
211 unsigned int chunks = 8;
212
213 err = snd_pcm_hw_params_any( handle, hwparams );
214 if ( err < 0 ) {
215 fatal = true;
216 errMessage = QString::fromLatin1("QAudioSource: snd_pcm_hw_params_any: err = %1").arg(err);
217 }
218 if ( !fatal ) {
219 err = snd_pcm_hw_params_set_rate_resample( handle, hwparams, 1 );
220 if ( err < 0 ) {
221 fatal = true;
222 errMessage = QString::fromLatin1("QAudioSource: snd_pcm_hw_params_set_rate_resample: err = %1").arg(err);
223 }
224 }
225 if ( !fatal ) {
226 err = snd_pcm_hw_params_set_access( handle, hwparams, access );
227 if ( err < 0 ) {
228 fatal = true;
229 errMessage = QString::fromLatin1("QAudioSource: snd_pcm_hw_params_set_access: err = %1").arg(err);
230 }
231 }
232 if ( !fatal ) {
233 err = setFormat();
234 if ( err < 0 ) {
235 fatal = true;
236 errMessage = QString::fromLatin1("QAudioSource: snd_pcm_hw_params_set_format: err = %1").arg(err);
237 }
238 }
239 if ( !fatal ) {
240 err = snd_pcm_hw_params_set_channels( handle, hwparams, (unsigned int)m_format.channelCount() );
241 if ( err < 0 ) {
242 fatal = true;
243 errMessage = QString::fromLatin1("QAudioSource: snd_pcm_hw_params_set_channels: err = %1").arg(err);
244 }
245 }
246 if ( !fatal ) {
247 err = snd_pcm_hw_params_set_rate_near( handle, hwparams, &sampleRate, 0 );
248 if ( err < 0 ) {
249 fatal = true;
250 errMessage = QString::fromLatin1("QAudioSource: snd_pcm_hw_params_set_rate_near: err = %1").arg(err);
251 }
252 }
253 if ( !fatal ) {
254 err = snd_pcm_hw_params_set_buffer_time_near(handle, hwparams, &buffer_time, &dir);
255 if ( err < 0 ) {
256 fatal = true;
257 errMessage = QString::fromLatin1("QAudioSource: snd_pcm_hw_params_set_buffer_time_near: err = %1").arg(err);
258 }
259 }
260 if ( !fatal ) {
261 err = snd_pcm_hw_params_set_period_time_near(handle, hwparams, &period_time, &dir);
262 if ( err < 0 ) {
263 fatal = true;
264 errMessage = QString::fromLatin1("QAudioSource: snd_pcm_hw_params_set_period_time_near: err = %1").arg(err);
265 }
266 }
267 if ( !fatal ) {
268 err = snd_pcm_hw_params_set_periods_near(handle, hwparams, &chunks, &dir);
269 if ( err < 0 ) {
270 fatal = true;
271 errMessage = QString::fromLatin1("QAudioSource: snd_pcm_hw_params_set_periods_near: err = %1").arg(err);
272 }
273 }
274 if ( !fatal ) {
275 err = snd_pcm_hw_params(handle, hwparams);
276 if ( err < 0 ) {
277 fatal = true;
278 errMessage = QString::fromLatin1("QAudioSource: snd_pcm_hw_params: err = %1").arg(err);
279 }
280 }
281 if( err < 0) {
282 qWarning()<<errMessage;
283 setError(QAudio::OpenError);
284 deviceState = QAudio::StoppedState;
285 emit stateChanged(deviceState);
286 return false;
287 }
288 snd_pcm_hw_params_get_buffer_size(hwparams,&buffer_frames);
289 buffer_size = snd_pcm_frames_to_bytes(handle,buffer_frames);
290 snd_pcm_hw_params_get_period_size(hwparams,&period_frames, &dir);
291 period_size = snd_pcm_frames_to_bytes(handle,period_frames);
292 snd_pcm_hw_params_get_buffer_time(hwparams,&buffer_time, &dir);
293 snd_pcm_hw_params_get_period_time(hwparams,&period_time, &dir);
294
295 // Step 3: Set the desired SW parameters.
296 snd_pcm_sw_params_t *swparams;
297 snd_pcm_sw_params_alloca(&swparams);
298 snd_pcm_sw_params_current(handle, swparams);
299 snd_pcm_sw_params_set_start_threshold(handle,swparams,period_frames);
300 snd_pcm_sw_params_set_stop_threshold(handle,swparams,buffer_frames);
301 snd_pcm_sw_params_set_avail_min(handle, swparams,period_frames);
302 snd_pcm_sw_params(handle, swparams);
303
304 // Step 4: Prepare audio
305 ringBuffer.resize(buffer_size);
306 snd_pcm_prepare( handle );
307 snd_pcm_start(handle);
308
309 // Step 5: Setup timer
310 bytesAvailable = checkBytesReady();
311
312 if(pullMode)
313 connect(audioSource, &QIODevice::readyRead, this, &QAlsaAudioSource::userFeed);
314
315 // Step 6: Start audio processing
316 chunks = buffer_size/period_size;
317 timer->start(period_time*chunks/2000);
318
319 setError(QAudio::NoError);
320
321 totalTimeValue = 0;
322
323 return true;
324}
325
326void QAlsaAudioSource::close()
327{
328 timer->stop();
329
330 if ( handle ) {
331 snd_pcm_drop( handle );
332 snd_pcm_close( handle );
333 handle = 0;
334 }
335}
336
337int QAlsaAudioSource::checkBytesReady()
338{
339 if(resuming)
340 bytesAvailable = period_size;
341 else if(deviceState != QAudio::ActiveState
342 && deviceState != QAudio::IdleState)
343 bytesAvailable = 0;
344 else {
345 int frames = snd_pcm_avail_update(handle);
346 if (frames < 0) {
347 bytesAvailable = frames;
348 } else {
349 if((int)frames > (int)buffer_frames)
350 frames = buffer_frames;
351 bytesAvailable = snd_pcm_frames_to_bytes(handle, frames);
352 }
353 }
354 return bytesAvailable;
355}
356
358{
359 return qMax(bytesAvailable, 0);
360}
361
362qint64 QAlsaAudioSource::read(char* data, qint64 len)
363{
364 // Read in some audio data and write it to QIODevice, pull mode
365 if ( !handle )
366 return 0;
367
368 int bytesRead = 0;
369 int bytesInRingbufferBeforeRead = ringBuffer.bytesOfDataInBuffer();
370
371 if (ringBuffer.bytesOfDataInBuffer() < len) {
372
373 // bytesAvaiable is saved as a side effect of checkBytesReady().
374 int bytesToRead = checkBytesReady();
375
376 if (bytesToRead < 0) {
377 // bytesAvailable as negative is error code, try to recover from it.
378 xrun_recovery(bytesToRead);
379 bytesToRead = checkBytesReady();
380 if (bytesToRead < 0) {
381 // recovery failed must stop and set error.
382 close();
383 setError(QAudio::IOError);
384 deviceState = QAudio::StoppedState;
385 emit stateChanged(deviceState);
386 return 0;
387 }
388 }
389
390 bytesToRead = qMin<qint64>(len, bytesToRead);
391 bytesToRead = qMin<qint64>(ringBuffer.freeBytes(), bytesToRead);
392 bytesToRead -= bytesToRead % period_size;
393
394 int count=0;
395 int err = 0;
396 QVarLengthArray<char, 4096> buffer(bytesToRead);
397 while(count < 5 && bytesToRead > 0) {
398 int chunks = bytesToRead / period_size;
399 int frames = chunks * period_frames;
400 if (frames > (int)buffer_frames)
401 frames = buffer_frames;
402
403 int readFrames = snd_pcm_readi(handle, buffer.data(), frames);
404 bytesRead = snd_pcm_frames_to_bytes(handle, readFrames);
405 if (volume() < 1.0f)
406 QAudioHelperInternal::qMultiplySamples(volume(), m_format, buffer.constData(),
407 buffer.data(), bytesRead);
408
409 if (readFrames >= 0) {
410 ringBuffer.write(buffer.data(), bytesRead);
411#ifdef DEBUG_AUDIO
412 qDebug() << QString::fromLatin1("read in bytes = %1 (frames=%2)").arg(bytesRead).arg(readFrames).toLatin1().constData();
413#endif
414 break;
415 } else if((readFrames == -EAGAIN) || (readFrames == -EINTR)) {
416 setError(QAudio::IOError);
417 err = 0;
418 break;
419 } else {
420 if(readFrames == -EPIPE) {
421 setError(QAudio::UnderrunError);
422 err = snd_pcm_prepare(handle);
423#ifdef ESTRPIPE
424 } else if(readFrames == -ESTRPIPE) {
425 err = snd_pcm_prepare(handle);
426#endif
427 }
428 if(err != 0) break;
429 }
430 count++;
431 }
432
433 }
434
435 bytesRead += bytesInRingbufferBeforeRead;
436
437 if (bytesRead > 0) {
438 // got some send it onward
439#ifdef DEBUG_AUDIO
440 qDebug() << "frames to write to QIODevice = " <<
441 snd_pcm_bytes_to_frames( handle, (int)bytesRead ) << " (" << bytesRead << ") bytes";
442#endif
443 if (deviceState != QAudio::ActiveState && deviceState != QAudio::IdleState)
444 return 0;
445
446 if (pullMode) {
447 qint64 l = 0;
448 qint64 bytesWritten = 0;
449 while (ringBuffer.bytesOfDataInBuffer() > 0) {
450 l = audioSource->write(ringBuffer.availableData(), ringBuffer.availableDataBlockSize());
451 if (l > 0) {
452 ringBuffer.readBytes(l);
453 bytesWritten += l;
454 } else {
455 break;
456 }
457 }
458
459 if (l < 0) {
460 close();
461 setError(QAudio::IOError);
462 deviceState = QAudio::StoppedState;
463 emit stateChanged(deviceState);
464 } else if (l == 0 && bytesWritten == 0) {
465 if (deviceState != QAudio::IdleState) {
466 setError(QAudio::NoError);
467 deviceState = QAudio::IdleState;
468 emit stateChanged(deviceState);
469 }
470 } else {
471 bytesAvailable -= bytesWritten;
472 totalTimeValue += bytesWritten;
473 resuming = false;
474 if (deviceState != QAudio::ActiveState) {
475 setError(QAudio::NoError);
476 deviceState = QAudio::ActiveState;
477 emit stateChanged(deviceState);
478 }
479 }
480
481 return bytesWritten;
482 } else {
483 while (ringBuffer.bytesOfDataInBuffer() > 0) {
484 int size = ringBuffer.availableDataBlockSize();
485 memcpy(data, ringBuffer.availableData(), size);
486 data += size;
487 ringBuffer.readBytes(size);
488 }
489
490 bytesAvailable -= bytesRead;
491 totalTimeValue += bytesRead;
492 resuming = false;
493 if (deviceState != QAudio::ActiveState) {
494 setError(QAudio::NoError);
495 deviceState = QAudio::ActiveState;
496 emit stateChanged(deviceState);
497 }
498
499 return bytesRead;
500 }
501 }
502
503 return 0;
504}
505
507{
508 if(deviceState == QAudio::SuspendedState) {
509 int err = 0;
510
511 if(handle) {
512 err = snd_pcm_prepare( handle );
513 if(err < 0)
514 xrun_recovery(err);
515
516 err = snd_pcm_start(handle);
517 if(err < 0)
518 xrun_recovery(err);
519
520 bytesAvailable = buffer_size;
521 }
522 resuming = true;
523 deviceState = QAudio::ActiveState;
524 int chunks = buffer_size/period_size;
525 timer->start(period_time*chunks/2000);
526 emit stateChanged(deviceState);
527 }
528}
529
530void QAlsaAudioSource::setBufferSize(qsizetype value)
531{
532 buffer_size = value;
533}
534
536{
537 return buffer_size;
538}
539
541{
542 qint64 result = qint64(1000000) * totalTimeValue /
543 m_format.bytesPerFrame() /
544 m_format.sampleRate();
545
546 return result;
547}
548
550{
551 if(deviceState == QAudio::ActiveState||resuming) {
552 snd_pcm_drain(handle);
553 timer->stop();
554 deviceState = QAudio::SuspendedState;
555 emit stateChanged(deviceState);
556 }
557}
558
559void QAlsaAudioSource::userFeed()
560{
561 if(deviceState == QAudio::StoppedState || deviceState == QAudio::SuspendedState)
562 return;
563#ifdef DEBUG_AUDIO
564 QTime now(QTime::currentTime());
565 qDebug()<<now.second()<<"s "<<now.msec()<<"ms :userFeed() IN";
566#endif
567 deviceReady();
568}
569
570bool QAlsaAudioSource::deviceReady()
571{
572 if(pullMode) {
573 // reads some audio data and writes it to QIODevice
574 read(0, buffer_size);
575 } else {
576 // emits readyRead() so user will call read() on QIODevice to get some audio data
577 AlsaInputPrivate* a = qobject_cast<AlsaInputPrivate*>(audioSource);
579 }
580 bytesAvailable = checkBytesReady();
581
582 if(deviceState != QAudio::ActiveState)
583 return true;
584
585 if (bytesAvailable < 0) {
586 // bytesAvailable as negative is error code, try to recover from it.
587 xrun_recovery(bytesAvailable);
588 bytesAvailable = checkBytesReady();
589 if (bytesAvailable < 0) {
590 // recovery failed must stop and set error.
591 close();
592 setError(QAudio::IOError);
593 deviceState = QAudio::StoppedState;
594 emit stateChanged(deviceState);
595 return 0;
596 }
597 }
598
599 return true;
600}
601
603{
604 if(handle)
605 snd_pcm_reset(handle);
606 stop();
607 bytesAvailable = 0;
608}
609
610void QAlsaAudioSource::drain()
611{
612 if(handle)
613 snd_pcm_drain(handle);
614}
615
616AlsaInputPrivate::AlsaInputPrivate(QAlsaAudioSource* audio)
617{
618 audioDevice = qobject_cast<QAlsaAudioSource*>(audio);
619}
620
624
625qint64 AlsaInputPrivate::readData( char* data, qint64 len)
626{
627 return audioDevice->read(data,len);
628}
629
630qint64 AlsaInputPrivate::writeData(const char* data, qint64 len)
631{
632 Q_UNUSED(data);
633 Q_UNUSED(len);
634 return 0;
635}
636
638{
639 emit readyRead();
640}
641
643 m_head(0),
644 m_tail(0)
645{
646}
647
648void RingBuffer::resize(int size)
649{
650 m_data.resize(size);
651}
652
654{
655 if (m_head < m_tail)
656 return m_tail - m_head;
657 else if (m_tail < m_head)
658 return m_data.size() + m_tail - m_head;
659 else
660 return 0;
661}
662
664{
665 if (m_head > m_tail)
666 return m_head - m_tail - 1;
667 else if (m_tail > m_head)
668 return m_data.size() - m_tail + m_head - 1;
669 else
670 return m_data.size() - 1;
671}
672
673const char *RingBuffer::availableData() const
674{
675 return (m_data.constData() + m_head);
676}
677
679{
680 if (m_head > m_tail)
681 return m_data.size() - m_head;
682 else if (m_tail > m_head)
683 return m_tail - m_head;
684 else
685 return 0;
686}
687
688void RingBuffer::readBytes(int bytes)
689{
690 m_head = (m_head + bytes) % m_data.size();
691}
692
693void RingBuffer::write(char *data, int len)
694{
695 if (m_tail + len < m_data.size()) {
696 memcpy(m_data.data() + m_tail, data, len);
697 m_tail += len;
698 } else {
699 int bytesUntilEnd = m_data.size() - m_tail;
700 memcpy(m_data.data() + m_tail, data, bytesUntilEnd);
701 if (len - bytesUntilEnd > 0)
702 memcpy(m_data.data(), data + bytesUntilEnd, len - bytesUntilEnd);
703 m_tail = len - bytesUntilEnd;
704 }
705}
706
707QT_END_NAMESPACE
708
709#include "moc_qalsaaudiosource_p.cpp"
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 resume() override
void reset() override
void stop() override
qsizetype bytesReady() const override
QIODevice * start() override
void setBufferSize(qsizetype value) override
QAudio::State state() const override
qsizetype bufferSize() const override
qint64 processedUSecs() const override
void start(QIODevice *device) override
qint64 read(char *data, qint64 len)
void suspend() override
The QAudioDevice class provides an information about audio devices and their functionality.
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)