4#include <QtCore/qcoreapplication.h>
5#include <QtCore/qvarlengtharray.h>
6#include <QtMultimedia/private/qaudiohelpers_p.h>
8#include <QLoggingCategory>
12Q_STATIC_LOGGING_CATEGORY(lcAlsaOutput,
"qt.multimedia.alsa.output");
15QAlsaAudioSink::QAlsaAudioSink(QAudioDevice device,
const QAudioFormat &format, QObject *parent)
16 : QPlatformAudioSink(std::move(device), format, parent)
18 timer =
new QTimer(
this);
19 connect(timer, &QTimer::timeout,
this, &QAlsaAudioSink::userFeed);
22QAlsaAudioSink::~QAlsaAudioSink()
25 disconnect(timer, &QTimer::timeout,
this, &QAlsaAudioSink::userFeed);
29QAudio::State QAlsaAudioSink::state()
const
34int QAlsaAudioSink::xrun_recovery(
int err)
46 errorState = QAudio::UnderrunError;
48 err = snd_pcm_prepare(handle);
52 }
else if ((err == -estrpipe)||(err == -EIO)) {
53 errorState = QAudio::IOError;
55 while ((err = snd_pcm_resume(handle)) == -EAGAIN) {
64 err = snd_pcm_prepare(handle);
72 snd_pcm_prepare(handle);
78int QAlsaAudioSink::setFormat()
80 snd_pcm_format_t pcmformat = SND_PCM_FORMAT_UNKNOWN;
82 switch (m_format.sampleFormat()) {
83 case QAudioFormat::UInt8:
84 pcmformat = SND_PCM_FORMAT_U8;
86 case QAudioFormat::Int16:
87 if constexpr (QSysInfo::ByteOrder == QSysInfo::BigEndian)
88 pcmformat = SND_PCM_FORMAT_S16_BE;
90 pcmformat = SND_PCM_FORMAT_S16_LE;
92 case QAudioFormat::Int32:
93 if constexpr (QSysInfo::ByteOrder == QSysInfo::BigEndian)
94 pcmformat = SND_PCM_FORMAT_S32_BE;
96 pcmformat = SND_PCM_FORMAT_S32_LE;
98 case QAudioFormat::Float:
99 if constexpr (QSysInfo::ByteOrder == QSysInfo::BigEndian)
100 pcmformat = SND_PCM_FORMAT_FLOAT_BE;
102 pcmformat = SND_PCM_FORMAT_FLOAT_LE;
108 return pcmformat != SND_PCM_FORMAT_UNKNOWN
109 ? snd_pcm_hw_params_set_format( handle, hwparams, pcmformat)
113void QAlsaAudioSink::start(QIODevice* device)
115 if(deviceState != QAudio::StoppedState)
116 deviceState = QAudio::StoppedState;
118 errorState = QAudio::NoError;
121 if(audioSource && !pullMode) {
129 audioSource = device;
131 connect(audioSource, &QIODevice::readyRead, timer, [
this] {
132 if (!timer->isActive()) {
133 timer->start(period_time / 1000);
136 deviceState = QAudio::ActiveState;
140 emit stateChanged(deviceState);
143QIODevice* QAlsaAudioSink::start()
145 if(deviceState != QAudio::StoppedState)
146 deviceState = QAudio::StoppedState;
148 errorState = QAudio::NoError;
151 if(audioSource && !pullMode) {
158 audioSource =
new AlsaOutputPrivate(
this);
159 audioSource->open(QIODevice::WriteOnly|QIODevice::Unbuffered);
162 deviceState = QAudio::IdleState;
166 emit stateChanged(deviceState);
171void QAlsaAudioSink::stop()
173 if(deviceState == QAudio::StoppedState)
175 errorState = QAudio::NoError;
176 deviceState = QAudio::StoppedState;
178 emit stateChanged(deviceState);
181bool QAlsaAudioSink::open()
187 QTime now(QTime::currentTime());
188 qDebug()<<now.second()<<
"s "<<now.msec()<<
"ms :open()";
190 elapsedTimeOffset = 0;
195 unsigned int sampleRate = m_format.sampleRate();
198 while((count < 5) && (err < 0)) {
199 err = snd_pcm_open(&handle, m_audioDevice.id().constData(), SND_PCM_STREAM_PLAYBACK, 0);
203 if (( err < 0)||(handle == 0)) {
204 errorState = QAudio::OpenError;
205 setError(errorState);
206 deviceState = QAudio::StoppedState;
209 snd_pcm_nonblock( handle, 0 );
212 snd_pcm_hw_params_alloca( &hwparams );
216 unsigned int chunks = 8;
218 err = snd_pcm_hw_params_any( handle, hwparams );
221 errMessage = QString::fromLatin1(
"QAudioSink: snd_pcm_hw_params_any: err = %1").arg(err);
224 err = snd_pcm_hw_params_set_rate_resample( handle, hwparams, 1 );
227 errMessage = QString::fromLatin1(
"QAudioSink: snd_pcm_hw_params_set_rate_resample: err = %1").arg(err);
231 err = snd_pcm_hw_params_set_access( handle, hwparams, access );
234 errMessage = QString::fromLatin1(
"QAudioSink: snd_pcm_hw_params_set_access: err = %1").arg(err);
241 errMessage = QString::fromLatin1(
"QAudioSink: snd_pcm_hw_params_set_format: err = %1").arg(err);
245 err = snd_pcm_hw_params_set_channels(handle, hwparams,
246 (
unsigned int)m_format.channelCount());
249 errMessage = QString::fromLatin1(
"QAudioSink: snd_pcm_hw_params_set_channels: err = %1").arg(err);
253 err = snd_pcm_hw_params_set_rate_near( handle, hwparams, &sampleRate, 0 );
256 errMessage = QString::fromLatin1(
"QAudioSink: snd_pcm_hw_params_set_rate_near: err = %1").arg(err);
260 unsigned int maxBufferTime = 0;
261 unsigned int minBufferTime = 0;
262 unsigned int maxPeriodTime = 0;
263 unsigned int minPeriodTime = 0;
265 err = snd_pcm_hw_params_get_buffer_time_max(hwparams, &maxBufferTime, &dir);
267 err = snd_pcm_hw_params_get_buffer_time_min(hwparams, &minBufferTime, &dir);
269 err = snd_pcm_hw_params_get_period_time_max(hwparams, &maxPeriodTime, &dir);
271 err = snd_pcm_hw_params_get_period_time_min(hwparams, &minPeriodTime, &dir);
275 errMessage = QString::fromLatin1(
"QAudioSink: buffer/period min and max: err = %1").arg(err);
277 static unsigned user_buffer_time = qEnvironmentVariableIntValue(
"QT_ALSA_OUTPUT_BUFFER_TIME");
278 static unsigned user_period_time = qEnvironmentVariableIntValue(
"QT_ALSA_OUTPUT_PERIOD_TIME");
279 const bool outOfRange = maxBufferTime < buffer_time || buffer_time < minBufferTime || maxPeriodTime < period_time || minPeriodTime > period_time;
280 if (outOfRange || user_period_time || user_buffer_time) {
281 period_time = user_period_time ? user_period_time : minPeriodTime;
282 if (!user_buffer_time) {
283 chunks = maxBufferTime / period_time;
284 buffer_time = period_time * chunks;
286 buffer_time = user_buffer_time;
287 chunks = buffer_time / period_time;
290 qCDebug(lcAlsaOutput) <<
"buffer time: [" << minBufferTime <<
"-" << maxBufferTime <<
"] =" << buffer_time;
291 qCDebug(lcAlsaOutput) <<
"period time: [" << minPeriodTime <<
"-" << maxPeriodTime <<
"] =" << period_time;
292 qCDebug(lcAlsaOutput) <<
"chunks =" << chunks;
296 err = snd_pcm_hw_params_set_buffer_time_near(handle, hwparams, &buffer_time, &dir);
299 errMessage = QString::fromLatin1(
"QAudioSink: snd_pcm_hw_params_set_buffer_time_near: err = %1").arg(err);
303 err = snd_pcm_hw_params_set_period_time_near(handle, hwparams, &period_time, &dir);
306 errMessage = QString::fromLatin1(
"QAudioSink: snd_pcm_hw_params_set_period_time_near: err = %1").arg(err);
310 err = snd_pcm_hw_params_set_periods_near(handle, hwparams, &chunks, &dir);
313 errMessage = QString::fromLatin1(
"QAudioSink: snd_pcm_hw_params_set_periods_near: err = %1").arg(err);
317 err = snd_pcm_hw_params(handle, hwparams);
320 errMessage = QString::fromLatin1(
"QAudioSink: snd_pcm_hw_params: err = %1").arg(err);
324 qWarning()<<errMessage;
325 errorState = QAudio::OpenError;
326 setError(errorState);
327 deviceState = QAudio::StoppedState;
330 snd_pcm_hw_params_get_buffer_size(hwparams,&buffer_frames);
331 buffer_size = snd_pcm_frames_to_bytes(handle,buffer_frames);
332 snd_pcm_hw_params_get_period_size(hwparams,&period_frames, &dir);
333 period_size = snd_pcm_frames_to_bytes(handle,period_frames);
334 snd_pcm_hw_params_get_buffer_time(hwparams,&buffer_time, &dir);
335 snd_pcm_hw_params_get_period_time(hwparams,&period_time, &dir);
338 snd_pcm_sw_params_t *swparams;
339 snd_pcm_sw_params_alloca(&swparams);
340 snd_pcm_sw_params_current(handle, swparams);
341 snd_pcm_sw_params_set_start_threshold(handle,swparams,period_frames);
342 snd_pcm_sw_params_set_stop_threshold(handle,swparams,buffer_frames);
343 snd_pcm_sw_params_set_avail_min(handle, swparams,period_frames);
344 snd_pcm_sw_params(handle, swparams);
348 audioBuffer =
new char[snd_pcm_frames_to_bytes(handle,buffer_frames)];
349 snd_pcm_prepare( handle );
350 snd_pcm_start(handle);
353 bytesAvailable = bytesFree();
356 timer->start(period_time/1000);
358 elapsedTimeOffset = 0;
359 errorState = QAudio::NoError;
366void QAlsaAudioSink::close()
371 snd_pcm_drain( handle );
372 snd_pcm_close( handle );
374 delete [] audioBuffer;
377 if(!pullMode && audioSource) {
384qsizetype QAlsaAudioSink::bytesFree()
const
389 if(deviceState != QAudio::ActiveState && deviceState != QAudio::IdleState)
392 int frames = snd_pcm_avail_update(handle);
393 if (frames == -EPIPE) {
395 int err = snd_pcm_recover(handle, frames, 0);
399 frames = snd_pcm_avail_update(handle);
400 }
else if (frames < 0) {
404 if ((
int)frames > (
int)buffer_frames)
405 frames = buffer_frames;
407 return snd_pcm_frames_to_bytes(handle, frames);
410qint64 QAlsaAudioSink::write(
const char *data, qint64 len )
416 qDebug()<<
"frames to write out = "<<
417 snd_pcm_bytes_to_frames( handle, (
int)len )<<
" ("<<len<<
") bytes";
420 int space = bytesFree();
428 frames = snd_pcm_bytes_to_frames(handle, space);
430 if (volume() < 1.0f) {
431 QVarLengthArray<
char, 4096> out(space);
432 QAudioHelperInternal::qMultiplySamples(volume(), m_format, data, out.data(), space);
433 err = snd_pcm_writei(handle, out.constData(), frames);
435 err = snd_pcm_writei(handle, data, frames);
439 totalTimeValue += err;
441 errorState = QAudio::NoError;
442 if (deviceState != QAudio::ActiveState) {
443 deviceState = QAudio::ActiveState;
444 emit stateChanged(deviceState);
446 return snd_pcm_frames_to_bytes( handle, err );
448 err = xrun_recovery(err);
452 errorState = QAudio::FatalError;
453 setError(errorState);
454 deviceState = QAudio::StoppedState;
455 emit stateChanged(deviceState);
460void QAlsaAudioSink::setBufferSize(qsizetype value)
462 if(deviceState == QAudio::StoppedState)
466qsizetype QAlsaAudioSink::bufferSize()
const
471qint64 QAlsaAudioSink::processedUSecs()
const
473 return qint64(1000000) * totalTimeValue / m_format.sampleRate();
476void QAlsaAudioSink::resume()
478 if(deviceState == QAudio::SuspendedState) {
482 err = snd_pcm_prepare( handle );
486 err = snd_pcm_start(handle);
490 bytesAvailable = (
int)snd_pcm_frames_to_bytes(handle, buffer_frames);
494 deviceState = suspendedInState;
495 errorState = QAudio::NoError;
496 timer->start(period_time/1000);
497 emit stateChanged(deviceState);
501void QAlsaAudioSink::suspend()
503 if(deviceState == QAudio::ActiveState || deviceState == QAudio::IdleState || resuming) {
504 suspendedInState = deviceState;
505 snd_pcm_drain(handle);
507 deviceState = QAudio::SuspendedState;
508 errorState = QAudio::NoError;
509 emit stateChanged(deviceState);
513void QAlsaAudioSink::userFeed()
515 if(deviceState == QAudio::StoppedState || deviceState == QAudio::SuspendedState)
518 QTime now(QTime::currentTime());
519 qDebug()<<now.second()<<
"s "<<now.msec()<<
"ms :userFeed() OUT";
521 if(deviceState == QAudio::IdleState)
522 bytesAvailable = bytesFree();
527bool QAlsaAudioSink::deviceReady()
531 int chunks = bytesAvailable/period_size;
533 bytesAvailable = bytesFree();
537 qDebug()<<
"deviceReady() avail="<<bytesAvailable<<
" bytes, period size="<<period_size<<
" bytes";
538 qDebug()<<
"deviceReady() no. of chunks that can fit ="<<chunks<<
", chunks in bytes ="<<period_size*chunks;
540 int input = period_frames*chunks;
541 if(input > (
int)buffer_frames)
542 input = buffer_frames;
543 l = audioSource->read(audioBuffer,snd_pcm_frames_to_bytes(handle, input));
551 if (deviceState != QAudio::ActiveState && deviceState != QAudio::IdleState)
553 qint64 bytesWritten = write(audioBuffer,l);
554 if (bytesWritten != l)
555 audioSource->seek(audioSource->pos()-(l-bytesWritten));
556 bytesAvailable = bytesFree();
561 snd_pcm_drain(handle);
562 bytesAvailable = bytesFree();
563 if(bytesAvailable > snd_pcm_frames_to_bytes(handle, buffer_frames-period_frames)) {
565 if (deviceState != QAudio::IdleState) {
566 errorState = audioSource->atEnd() ? QAudio::NoError : QAudio::UnderrunError;
567 setError(errorState);
568 deviceState = QAudio::IdleState;
569 emit stateChanged(deviceState);
575 deviceState = QAudio::StoppedState;
576 errorState = QAudio::IOError;
577 setError(errorState);
578 emit stateChanged(deviceState);
581 bytesAvailable = bytesFree();
582 if(bytesAvailable > snd_pcm_frames_to_bytes(handle, buffer_frames-period_frames)) {
584 if (deviceState != QAudio::IdleState) {
585 errorState = QAudio::UnderrunError;
586 setError(errorState);
587 deviceState = QAudio::IdleState;
588 emit stateChanged(deviceState);
593 if(deviceState != QAudio::ActiveState)
599void QAlsaAudioSink::reset()
602 snd_pcm_reset(handle);
609 audioDevice = qobject_cast<QAlsaAudioSink*>(audio);
626 if((audioDevice->deviceState == QAudio::ActiveState)
627 ||(audioDevice->deviceState == QAudio::IdleState)) {
628 while(written < len) {
629 int chunk = audioDevice->write(data+written,(len-written));
643#include "moc_qalsaaudiosink_p.cpp"
qint64 writeData(const char *data, qint64 len) override
Writes up to maxSize bytes from data to the 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...