/**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ // // W A R N I N G // ------------- // // This file is not part of the Qt API. It exists for the convenience // of other Qt classes. This header file may change from version to // version without notice, or even be removed. // // INTERNAL USE ONLY: Do NOT use for any other purpose. // #include #include #include "qalsaaudiooutput.h" #include "qalsaaudiodeviceinfo.h" QT_BEGIN_NAMESPACE //#define DEBUG_AUDIO 1 QAlsaAudioOutput::QAlsaAudioOutput(const QByteArray &device) { bytesAvailable = 0; handle = 0; ahandler = 0; access = SND_PCM_ACCESS_RW_INTERLEAVED; pcmformat = SND_PCM_FORMAT_S16; buffer_frames = 0; period_frames = 0; buffer_size = 0; period_size = 0; buffer_time = 100000; period_time = 20000; totalTimeValue = 0; intervalTime = 1000; audioBuffer = 0; errorState = QAudio::NoError; deviceState = QAudio::StoppedState; audioSource = 0; pullMode = true; resuming = false; opened = false; m_volume = 1.0f; m_device = device; timer = new QTimer(this); connect(timer,SIGNAL(timeout()),SLOT(userFeed())); } QAlsaAudioOutput::~QAlsaAudioOutput() { close(); disconnect(timer, SIGNAL(timeout())); QCoreApplication::processEvents(); delete timer; } void QAlsaAudioOutput::setVolume(qreal vol) { m_volume = vol; } qreal QAlsaAudioOutput::volume() const { return m_volume; } QAudio::Error QAlsaAudioOutput::error() const { return errorState; } QAudio::State QAlsaAudioOutput::state() const { return deviceState; } void QAlsaAudioOutput::async_callback(snd_async_handler_t *ahandler) { QAlsaAudioOutput* audioOut; audioOut = static_cast (snd_async_handler_get_callback_private(ahandler)); if (audioOut && (audioOut->deviceState == QAudio::ActiveState || audioOut->resuming)) audioOut->feedback(); } int QAlsaAudioOutput::xrun_recovery(int err) { int count = 0; bool reset = false; if(err == -EPIPE) { errorState = QAudio::UnderrunError; emit errorChanged(errorState); err = snd_pcm_prepare(handle); if(err < 0) reset = true; } else if((err == -ESTRPIPE)||(err == -EIO)) { errorState = QAudio::IOError; emit errorChanged(errorState); while((err = snd_pcm_resume(handle)) == -EAGAIN){ usleep(100); count++; if(count > 5) { reset = true; break; } } if(err < 0) { err = snd_pcm_prepare(handle); if(err < 0) reset = true; } } if(reset) { close(); open(); snd_pcm_prepare(handle); return 0; } return err; } int QAlsaAudioOutput::setFormat() { snd_pcm_format_t pcmformat = SND_PCM_FORMAT_UNKNOWN; if(settings.sampleSize() == 8) { pcmformat = SND_PCM_FORMAT_U8; } else if(settings.sampleSize() == 16) { if(settings.sampleType() == QAudioFormat::SignedInt) { if(settings.byteOrder() == QAudioFormat::LittleEndian) pcmformat = SND_PCM_FORMAT_S16_LE; else pcmformat = SND_PCM_FORMAT_S16_BE; } else if(settings.sampleType() == QAudioFormat::UnSignedInt) { if(settings.byteOrder() == QAudioFormat::LittleEndian) pcmformat = SND_PCM_FORMAT_U16_LE; else pcmformat = SND_PCM_FORMAT_U16_BE; } } else if(settings.sampleSize() == 24) { if(settings.sampleType() == QAudioFormat::SignedInt) { if(settings.byteOrder() == QAudioFormat::LittleEndian) pcmformat = SND_PCM_FORMAT_S24_LE; else pcmformat = SND_PCM_FORMAT_S24_BE; } else if(settings.sampleType() == QAudioFormat::UnSignedInt) { if(settings.byteOrder() == QAudioFormat::LittleEndian) pcmformat = SND_PCM_FORMAT_U24_LE; else pcmformat = SND_PCM_FORMAT_U24_BE; } } else if(settings.sampleSize() == 32) { if(settings.sampleType() == QAudioFormat::SignedInt) { if(settings.byteOrder() == QAudioFormat::LittleEndian) pcmformat = SND_PCM_FORMAT_S32_LE; else pcmformat = SND_PCM_FORMAT_S32_BE; } else if(settings.sampleType() == QAudioFormat::UnSignedInt) { if(settings.byteOrder() == QAudioFormat::LittleEndian) pcmformat = SND_PCM_FORMAT_U32_LE; else pcmformat = SND_PCM_FORMAT_U32_BE; } else if(settings.sampleType() == QAudioFormat::Float) { if(settings.byteOrder() == QAudioFormat::LittleEndian) pcmformat = SND_PCM_FORMAT_FLOAT_LE; else pcmformat = SND_PCM_FORMAT_FLOAT_BE; } } else if(settings.sampleSize() == 64) { if(settings.byteOrder() == QAudioFormat::LittleEndian) pcmformat = SND_PCM_FORMAT_FLOAT64_LE; else pcmformat = SND_PCM_FORMAT_FLOAT64_BE; } return pcmformat != SND_PCM_FORMAT_UNKNOWN ? snd_pcm_hw_params_set_format( handle, hwparams, pcmformat) : -1; } void QAlsaAudioOutput::start(QIODevice* device) { if(deviceState != QAudio::StoppedState) deviceState = QAudio::StoppedState; errorState = QAudio::NoError; // Handle change of mode if(audioSource && !pullMode) { delete audioSource; audioSource = 0; } close(); pullMode = true; audioSource = device; deviceState = QAudio::ActiveState; open(); emit stateChanged(deviceState); } QIODevice* QAlsaAudioOutput::start() { if(deviceState != QAudio::StoppedState) deviceState = QAudio::StoppedState; errorState = QAudio::NoError; // Handle change of mode if(audioSource && !pullMode) { delete audioSource; audioSource = 0; } close(); audioSource = new OutputPrivate(this); audioSource->open(QIODevice::WriteOnly|QIODevice::Unbuffered); pullMode = false; deviceState = QAudio::IdleState; open(); emit stateChanged(deviceState); return audioSource; } void QAlsaAudioOutput::stop() { if(deviceState == QAudio::StoppedState) return; errorState = QAudio::NoError; deviceState = QAudio::StoppedState; close(); emit stateChanged(deviceState); } bool QAlsaAudioOutput::open() { if(opened) return true; #ifdef DEBUG_AUDIO QTime now(QTime::currentTime()); qDebug()< devices = QAlsaAudioDeviceInfo::availableDevices(QAudio::AudioOutput); if(dev.compare(QLatin1String("default")) == 0) { #if(SND_LIB_MAJOR == 1 && SND_LIB_MINOR == 0 && SND_LIB_SUBMINOR >= 14) if (devices.size() > 0) dev = QLatin1String(devices.first()); else return false; #else dev = QLatin1String("hw:0,0"); #endif } else { #if(SND_LIB_MAJOR == 1 && SND_LIB_MINOR == 0 && SND_LIB_SUBMINOR >= 14) dev = QLatin1String(m_device); #else int idx = 0; char *name; QString shortName = QLatin1String(m_device.mid(m_device.indexOf('=',0)+1).constData()); while (snd_card_get_name(idx,&name) == 0) { if(qstrncmp(shortName.toLocal8Bit().constData(),name,shortName.length()) == 0) break; idx++; } dev = QString(QLatin1String("hw:%1,0")).arg(idx); #endif } // Step 1: try and open the device while((count < 5) && (err < 0)) { err=snd_pcm_open(&handle,dev.toLocal8Bit().constData(),SND_PCM_STREAM_PLAYBACK,0); if(err < 0) count++; } if (( err < 0)||(handle == 0)) { errorState = QAudio::OpenError; emit errorChanged(errorState); deviceState = QAudio::StoppedState; return false; } snd_pcm_nonblock( handle, 0 ); // Step 2: Set the desired HW parameters. snd_pcm_hw_params_alloca( &hwparams ); bool fatal = false; QString errMessage; unsigned int chunks = 8; err = snd_pcm_hw_params_any( handle, hwparams ); if ( err < 0 ) { fatal = true; errMessage = QString::fromLatin1("QAudioOutput: snd_pcm_hw_params_any: err = %1").arg(err); } if ( !fatal ) { err = snd_pcm_hw_params_set_rate_resample( handle, hwparams, 1 ); if ( err < 0 ) { fatal = true; errMessage = QString::fromLatin1("QAudioOutput: snd_pcm_hw_params_set_rate_resample: err = %1").arg(err); } } if ( !fatal ) { err = snd_pcm_hw_params_set_access( handle, hwparams, access ); if ( err < 0 ) { fatal = true; errMessage = QString::fromLatin1("QAudioOutput: snd_pcm_hw_params_set_access: err = %1").arg(err); } } if ( !fatal ) { err = setFormat(); if ( err < 0 ) { fatal = true; errMessage = QString::fromLatin1("QAudioOutput: snd_pcm_hw_params_set_format: err = %1").arg(err); } } if ( !fatal ) { err = snd_pcm_hw_params_set_channels( handle, hwparams, (unsigned int)settings.channelCount() ); if ( err < 0 ) { fatal = true; errMessage = QString::fromLatin1("QAudioOutput: snd_pcm_hw_params_set_channels: err = %1").arg(err); } } if ( !fatal ) { err = snd_pcm_hw_params_set_rate_near( handle, hwparams, &sampleRate, 0 ); if ( err < 0 ) { fatal = true; errMessage = QString::fromLatin1("QAudioOutput: snd_pcm_hw_params_set_rate_near: err = %1").arg(err); } } if ( !fatal ) { unsigned int maxBufferTime = 0; unsigned int minBufferTime = 0; unsigned int maxPeriodTime = 0; unsigned int minPeriodTime = 0; err = snd_pcm_hw_params_get_buffer_time_max(hwparams, &maxBufferTime, &dir); if ( err >= 0) err = snd_pcm_hw_params_get_buffer_time_min(hwparams, &minBufferTime, &dir); if ( err >= 0) err = snd_pcm_hw_params_get_period_time_max(hwparams, &maxPeriodTime, &dir); if ( err >= 0) err = snd_pcm_hw_params_get_period_time_min(hwparams, &minPeriodTime, &dir); if ( err < 0 ) { fatal = true; errMessage = QString::fromLatin1("QAudioOutput: buffer/period min and max: err = %1").arg(err); } else { if (maxBufferTime < buffer_time || buffer_time < minBufferTime || maxPeriodTime < period_time || minPeriodTime > period_time) { #ifdef DEBUG_AUDIO qDebug()<<"defaults out of range"; qDebug()<<"pmin="<start(period_time/1000); clockStamp.restart(); timeStamp.restart(); elapsedTimeOffset = 0; errorState = QAudio::NoError; totalTimeValue = 0; opened = true; return true; } void QAlsaAudioOutput::close() { timer->stop(); if ( handle ) { snd_pcm_drain( handle ); snd_pcm_close( handle ); handle = 0; delete [] audioBuffer; audioBuffer=0; } if(!pullMode && audioSource) { delete audioSource; audioSource = 0; } opened = false; } int QAlsaAudioOutput::bytesFree() const { if(resuming) return period_size; if(deviceState != QAudio::ActiveState && deviceState != QAudio::IdleState) return 0; int frames = snd_pcm_avail_update(handle); if (frames == -EPIPE) { // Try and handle buffer underrun int err = snd_pcm_recover(handle, frames, 0); if (err < 0) return 0; else frames = snd_pcm_avail_update(handle); } else if (frames < 0) { return 0; } if ((int)frames > (int)buffer_frames) frames = buffer_frames; return snd_pcm_frames_to_bytes(handle, frames); } qint64 QAlsaAudioOutput::write( const char *data, qint64 len ) { // Write out some audio data if ( !handle ) return 0; #ifdef DEBUG_AUDIO qDebug()<<"frames to write out = "<< snd_pcm_bytes_to_frames( handle, (int)len )<<" ("< 0) { totalTimeValue += err; resuming = false; errorState = QAudio::NoError; if (deviceState != QAudio::ActiveState) { deviceState = QAudio::ActiveState; emit stateChanged(deviceState); } return snd_pcm_frames_to_bytes( handle, err ); } else err = xrun_recovery(err); if(err < 0) { close(); errorState = QAudio::FatalError; emit errorChanged(errorState); deviceState = QAudio::StoppedState; emit stateChanged(deviceState); } return 0; } int QAlsaAudioOutput::periodSize() const { return period_size; } void QAlsaAudioOutput::setBufferSize(int value) { if(deviceState == QAudio::StoppedState) buffer_size = value; } int QAlsaAudioOutput::bufferSize() const { return buffer_size; } void QAlsaAudioOutput::setNotifyInterval(int ms) { intervalTime = qMax(0, ms); } int QAlsaAudioOutput::notifyInterval() const { return intervalTime; } qint64 QAlsaAudioOutput::processedUSecs() const { return qint64(1000000) * totalTimeValue / settings.sampleRate(); } void QAlsaAudioOutput::resume() { if(deviceState == QAudio::SuspendedState) { int err = 0; if(handle) { err = snd_pcm_prepare( handle ); if(err < 0) xrun_recovery(err); err = snd_pcm_start(handle); if(err < 0) xrun_recovery(err); bytesAvailable = (int)snd_pcm_frames_to_bytes(handle, buffer_frames); } resuming = true; deviceState = QAudio::ActiveState; errorState = QAudio::NoError; timer->start(period_time/1000); emit stateChanged(deviceState); } } void QAlsaAudioOutput::setFormat(const QAudioFormat& fmt) { if (deviceState == QAudio::StoppedState) settings = fmt; } QAudioFormat QAlsaAudioOutput::format() const { return settings; } void QAlsaAudioOutput::suspend() { if(deviceState == QAudio::ActiveState || deviceState == QAudio::IdleState || resuming) { timer->stop(); deviceState = QAudio::SuspendedState; errorState = QAudio::NoError; emit stateChanged(deviceState); } } void QAlsaAudioOutput::userFeed() { if(deviceState == QAudio::StoppedState || deviceState == QAudio::SuspendedState) return; #ifdef DEBUG_AUDIO QTime now(QTime::currentTime()); qDebug()<