1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-02-26 12:53:41 +01:00
tomahawk/alsa-playback/alsaaudio.cpp
Christian Muehlhaeuser 1f592fbbd9 Initial Tomahawk import.
2010-10-17 05:32:01 +02:00

921 lines
26 KiB
C++

/***************************************************************************
* Copyright (C) 2007 by John Stamp, <jstamp@users.sourceforge.net> *
* Copyright (C) 2007 by Max Howell, Last.fm Ltd. *
* Copyright (C) 2010 by Christian Muehlhaeuser <muesli@gmail.com> *
* *
* Large portions of this code are shamelessly copied from audio.c: *
* The XMMS ALSA output plugin *
* Copyright (C) 2001-2003 Matthieu Sozeau <mattam@altern.org> *
* Copyright (C) 1998-2003 Peter Alm, Mikael Alm, Olle Hallnas, *
* Thomas Nilsson and 4Front Technologies *
* Copyright (C) 1999-2007 Haavard Kvaalen *
* Copyright (C) 2005 Takashi Iwai *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02110-1301, USA. *
***************************************************************************/
#include "alsaaudio.h"
#include <qendian.h>
#include <QDebug>
//no debug
#define snd_pcm_hw_params_dump( hwparams, logs )
#define snd_pcm_sw_params_dump( x, y )
#define snd_pcm_dump( x, y )
pthread_t AlsaAudio::audio_thread;
char* AlsaAudio::thread_buffer = NULL;
int AlsaAudio::thread_buffer_size = 0;
int AlsaAudio::rd_index = 0;
int AlsaAudio::wr_index = 0;
unsigned int AlsaAudio::pcmCounter = 0;
snd_output_t* AlsaAudio::logs = NULL;
bool AlsaAudio::going = false;
snd_pcm_t *AlsaAudio::alsa_pcm = NULL;
ssize_t AlsaAudio::hw_period_size_in = 0;
snd_format* AlsaAudio::inputf = NULL;
snd_format* AlsaAudio::outputf = NULL;
float AlsaAudio::volume = 1.0;
bool AlsaAudio::paused = false;
convert_func_t AlsaAudio::alsa_convert_func = NULL;
convert_channel_func_t AlsaAudio::alsa_stereo_convert_func = NULL;
convert_freq_func_t AlsaAudio::alsa_frequency_convert_func = NULL;
xmms_convert_buffers* AlsaAudio::convertb = NULL;
AlsaAudio::AlsaAudio()
{
}
AlsaAudio::~AlsaAudio()
{
// Close here just to be sure
// These are safe to call more than once
stopPlayback();
alsaClose();
}
/******************************************************************************
* Device Detection
******************************************************************************/
int
AlsaAudio::getCards( void )
{
int card = -1;
int err = 0;
m_devices.clear();
// First add the default PCM device
AlsaDeviceInfo dev;
dev.name = "Default PCM device (default)";
dev.device = "default";
m_devices.push_back( dev );
if ( (err = snd_card_next( &card )) != 0 )
goto getCardsFailed;
while ( card > -1 )
{
getDevicesForCard( card );
if ( (err = snd_card_next( &card )) != 0 )
goto getCardsFailed;
}
return m_devices.size();
getCardsFailed:
qDebug() << __PRETTY_FUNCTION__ << "failed:" << snd_strerror( -err );
return -1;
}
void
AlsaAudio::getDevicesForCard( int card )
{
int pcm_device = -1, err;
snd_pcm_info_t *pcm_info;
snd_ctl_t *ctl;
char *alsa_name;
QString cardName = "Unknown soundcard";
QString device_name = QString( "hw:%1" ).arg( card );
if ((err = snd_ctl_open( &ctl, device_name.toAscii(), 0 )) < 0) {
qDebug() << "Failed:" << snd_strerror( -err );
return;
}
if ((err = snd_card_get_name( card, &alsa_name )) != 0)
{
qDebug() << "Failed:" << snd_strerror( -err );
}
else
cardName = alsa_name;
snd_pcm_info_alloca( &pcm_info );
for (;;)
{
if ((err = snd_ctl_pcm_next_device( ctl, &pcm_device )) < 0)
{
qDebug() << "Failed:" << snd_strerror( -err );
pcm_device = -1;
}
if (pcm_device < 0)
break;
snd_pcm_info_set_device( pcm_info, pcm_device );
snd_pcm_info_set_subdevice( pcm_info, 0 );
snd_pcm_info_set_stream( pcm_info, SND_PCM_STREAM_PLAYBACK );
if ((err = snd_ctl_pcm_info( ctl, pcm_info )) < 0)
{
if ( err != -ENOENT )
qDebug() << "Failed: snd_ctl_pcm_info() failed"
"(" << card << ":" << pcm_device << "): "
<< snd_strerror( -err );
continue;
}
AlsaDeviceInfo dev;
dev.device = QString( "hw:%1,%2" )
.arg( card )
.arg( pcm_device );
dev.name = QString( "%1: %2 (%3)" )
.arg( cardName )
.arg( snd_pcm_info_get_name( pcm_info ) )
.arg( dev.device );
m_devices.push_back( dev );
}
snd_ctl_close( ctl );
}
AlsaDeviceInfo
AlsaAudio::getDeviceInfo( int device )
{
return m_devices[device];
}
/******************************************************************************
Device Setup
******************************************************************************/
bool
AlsaAudio::alsaOpen( QString device, AFormat format, unsigned int rate,
unsigned int channels, snd_pcm_uframes_t periodSize,
unsigned int periodCount, int minBufferCapacity )
{
int err, hw_buffer_size;
ssize_t hw_period_size;
snd_pcm_hw_params_t *hwparams;
snd_pcm_sw_params_t *swparams;
snd_pcm_uframes_t alsa_buffer_size, alsa_period_size;
inputf = snd_format_from_xmms( format, rate, channels );
convertb = xmms_convert_buffers_new();
snd_output_stdio_attach( &logs, stderr, 0 );
alsa_convert_func = NULL;
alsa_stereo_convert_func = NULL;
alsa_frequency_convert_func = NULL;
free( outputf );
outputf = snd_format_from_xmms( inputf->xmms_format, inputf->rate, inputf->channels );
qDebug() << "Opening device:" << device;
// FIXME: Can snd_pcm_open() return EAGAIN?
if ((err = snd_pcm_open( &alsa_pcm,
device.toAscii(),
SND_PCM_STREAM_PLAYBACK,
SND_PCM_NONBLOCK )) < 0)
{
qDebug() << "Failed to open pcm device (" << device << "):" << snd_strerror( -err );
alsa_pcm = NULL;
free( outputf );
outputf = NULL;
return false;
}
snd_pcm_info_t *info;
int alsa_card, alsa_device, alsa_subdevice;
snd_pcm_info_alloca( &info );
snd_pcm_info( alsa_pcm, info );
alsa_card = snd_pcm_info_get_card( info );
alsa_device = snd_pcm_info_get_device( info );
alsa_subdevice = snd_pcm_info_get_subdevice( info );
// qDebug() << "Card:" << alsa_card;
// qDebug() << "Device:" << alsa_device;
// qDebug() << "Subdevice:" << alsa_subdevice;
snd_pcm_hw_params_alloca( &hwparams );
if ( (err = snd_pcm_hw_params_any( alsa_pcm, hwparams ) ) < 0 )
{
qDebug() << "No configuration available for playback:"
<< snd_strerror( -err );
alsaClose();
return false;
}
if ( ( err = snd_pcm_hw_params_set_access( alsa_pcm, hwparams,
SND_PCM_ACCESS_RW_INTERLEAVED ) ) < 0 )
{
qDebug() << "Cannot set normal write mode:" << snd_strerror( -err );
alsaClose();
return false;
}
if ( ( err = snd_pcm_hw_params_set_format( alsa_pcm, hwparams, outputf->format ) ) < 0 )
{
// Try if one of these format work (one of them should work
// on almost all soundcards)
snd_pcm_format_t formats[] = { SND_PCM_FORMAT_S16_LE,
SND_PCM_FORMAT_S16_BE,
SND_PCM_FORMAT_U8 };
uint i;
for ( i = 0; i < sizeof( formats ) / sizeof( formats[0] ); i++ )
{
if ( snd_pcm_hw_params_set_format( alsa_pcm, hwparams, formats[i] ) == 0 )
{
outputf->format = formats[i];
break;
}
}
if ( outputf->format != inputf->format )
{
outputf->xmms_format = (AFormat)format_from_alsa( outputf->format );
qDebug() << "Converting format from" << inputf->xmms_format << "to" << outputf->xmms_format;
if ( outputf->xmms_format < 0 )
return -1;
alsa_convert_func = xmms_convert_get_func( outputf->xmms_format, inputf->xmms_format );
if ( alsa_convert_func == NULL )
{
qDebug() << "Format translation needed, but not available. Input:" << inputf->xmms_format << "; Output:" << outputf->xmms_format ;
alsaClose();
return false;
}
}
else
{
qDebug() << "Sample format not available for playback:" << snd_strerror( -err );
alsaClose();
return false;
}
}
snd_pcm_hw_params_set_channels_near( alsa_pcm, hwparams, &outputf->channels );
if ( outputf->channels != inputf->channels )
{
qDebug() << "Converting channels from" << inputf->channels << "to" << outputf->channels;
alsa_stereo_convert_func =
xmms_convert_get_channel_func( outputf->xmms_format,
outputf->channels,
inputf->channels );
if ( alsa_stereo_convert_func == NULL )
{
qDebug() << "No stereo conversion available. Format:" << outputf->xmms_format << "; Input Channels:" << inputf->channels << "; Output Channels:" << outputf->channels ;
alsaClose();
return false;
}
}
snd_pcm_hw_params_set_rate_near( alsa_pcm, hwparams, &outputf->rate, 0 );
if ( outputf->rate == 0 )
{
qDebug() << "No usable samplerate available.";
alsaClose();
return false;
}
if ( outputf->rate != inputf->rate )
{
qDebug() << "Converting samplerate from" << inputf->rate << "to" << outputf->rate ;
if ( outputf->channels < 1 || outputf->channels > 2 )
{
qDebug() << "Unsupported number of channels:" << outputf->channels << "- Resample function not available" ;
alsa_frequency_convert_func = NULL;
alsaClose();
return false;
}
alsa_frequency_convert_func =
xmms_convert_get_frequency_func( outputf->xmms_format,
outputf->channels );
if ( alsa_frequency_convert_func == NULL )
{
qDebug() << "Resample function not available. Format" << outputf->xmms_format ;
alsaClose();
return false;
}
}
outputf->sample_bits = snd_pcm_format_physical_width( outputf->format );
outputf->bps = ( outputf->rate * outputf->sample_bits * outputf->channels ) >> 3;
if ( ( err = snd_pcm_hw_params_set_period_size_near( alsa_pcm, hwparams,
&periodSize, NULL ) ) < 0 )
{
qDebug() << "Set period size failed:" << snd_strerror( -err );
alsaClose();
return false;
}
if ( ( err = snd_pcm_hw_params_set_periods_near( alsa_pcm, hwparams,
&periodCount, 0 ) ) < 0 )
{
qDebug() << "Set period count failed:" << snd_strerror( -err );
alsaClose();
return false;
}
if ( snd_pcm_hw_params( alsa_pcm, hwparams ) < 0 )
{
snd_pcm_hw_params_dump( hwparams, logs );
qDebug() << "Unable to install hw params";
alsaClose();
return false;
}
if ( ( err = snd_pcm_hw_params_get_buffer_size( hwparams, &alsa_buffer_size ) ) < 0 )
{
qDebug() << "snd_pcm_hw_params_get_buffer_size() failed:" << snd_strerror( -err );
alsaClose();
return false;
}
if ( ( err = snd_pcm_hw_params_get_period_size( hwparams, &alsa_period_size, 0 ) ) < 0 )
{
qDebug() << "snd_pcm_hw_params_get_period_size() failed:" << snd_strerror( -err );
alsaClose();
return false;
}
snd_pcm_sw_params_alloca( &swparams );
snd_pcm_sw_params_current( alsa_pcm, swparams );
if ( ( err = snd_pcm_sw_params_set_start_threshold( alsa_pcm,
swparams, alsa_buffer_size - alsa_period_size ) < 0 ) )
qDebug() << "Setting start threshold failed:" << snd_strerror( -err );
if ( snd_pcm_sw_params( alsa_pcm, swparams ) < 0 )
{
qDebug() << "Unable to install sw params";
alsaClose();
return false;
}
#ifndef QT_NO_DEBUG
snd_pcm_sw_params_dump( swparams, logs );
snd_pcm_dump( alsa_pcm, logs );
#endif
hw_period_size = snd_pcm_frames_to_bytes( alsa_pcm, alsa_period_size );
if ( inputf->bps != outputf->bps )
{
int align = ( inputf->sample_bits * inputf->channels ) / 8;
hw_period_size_in = ( (quint64)hw_period_size * inputf->bps +
outputf->bps/2 ) / outputf->bps;
hw_period_size_in -= hw_period_size_in % align;
}
else
{
hw_period_size_in = hw_period_size;
}
hw_buffer_size = snd_pcm_frames_to_bytes( alsa_pcm, alsa_buffer_size );
thread_buffer_size = minBufferCapacity * 4;
if ( thread_buffer_size < hw_buffer_size )
thread_buffer_size = hw_buffer_size * 2;
if ( thread_buffer_size < 8192 )
thread_buffer_size = 8192;
thread_buffer_size += hw_buffer_size;
thread_buffer_size -= thread_buffer_size % hw_period_size;
thread_buffer = (char*)calloc(thread_buffer_size, sizeof(char));
// qDebug() << "Device setup: period size:" << hw_period_size;
// qDebug() << "Device setup: hw_period_size_in:" << hw_period_size_in;
// qDebug() << "Device setup: hw_buffer_size:" << hw_buffer_size;
// qDebug() << "Device setup: thread_buffer_size:" << thread_buffer_size;
// qDebug() << "bits per sample:" << snd_pcm_format_physical_width( outputf->format )
// << "frame size:" << snd_pcm_frames_to_bytes( alsa_pcm, 1 )
// << "Bps:" << outputf->bps;
return true;
}
int
AlsaAudio::startPlayback()
{
int pthreadError = 0;
// We should double check this here. AlsaPlayback::initAudio
// isn't having its emitted error caught.
// So double check here to avoid a potential assert.
if ( !alsa_pcm )
return 1;
going = true;
// qDebug() << "Starting thread";
AlsaAudio* aaThread = new AlsaAudio();
pthreadError = pthread_create( &audio_thread, NULL, &alsa_loop, (void*)aaThread );
return pthreadError;
}
void
AlsaAudio::clearBuffer( void )
{
wr_index = rd_index = pcmCounter = 0;
if ( thread_buffer )
memset( thread_buffer, 0, thread_buffer_size );
}
/******************************************************************************
Play Interface
******************************************************************************/
void
AlsaAudio::alsaWrite( const QByteArray& input )
{
int cnt;
const char *src = input.data();
int length = input.size();
//qDebug() << "alsaWrite length:" << length;
while ( length > 0 )
{
int wr;
cnt = qMin(length, thread_buffer_size - wr_index);
memcpy(thread_buffer + wr_index, src, cnt);
wr = (wr_index + cnt) % thread_buffer_size;
wr_index = wr;
length -= cnt;
src += cnt;
}
}
int
AlsaAudio::get_thread_buffer_filled() const
{
if ( wr_index >= rd_index )
{
return wr_index - rd_index;
}
return ( thread_buffer_size - ( rd_index - wr_index ) );
}
// HACK: the buffer may have data, but not enough to send to the card. In that
// case we tell alsaplayback that we don't have any. This may chop off some
// data, but only at the natural end of a track. On my machine, this is at
// most 3759 bytes. That's less than 0.022 sec. It beats padding the buffer
// with 0's if the stream fails mid track. No stutter this way.
int
AlsaAudio::hasData()
{
int tempSize = get_thread_buffer_filled();
if ( tempSize < hw_period_size_in )
return 0;
else
return tempSize;
}
int
AlsaAudio::alsa_free() const
{
//qDebug() << "alsa_free:" << thread_buffer_size - get_thread_buffer_filled() - 1;
return thread_buffer_size - get_thread_buffer_filled() - 1;
}
void
AlsaAudio::setVolume ( float v )
{
volume = v;
}
void
AlsaAudio::stopPlayback()
{
if (going)
{
// Q_DEBUG_BLOCK;
going = false;
pthread_join( audio_thread, NULL );
}
}
void
AlsaAudio::alsaClose()
{
// Q_DEBUG_BLOCK;
alsa_close_pcm();
xmms_convert_buffers_destroy( convertb );
convertb = NULL;
if ( thread_buffer )
{
free(thread_buffer);
thread_buffer = NULL;
}
if ( inputf )
{
free( inputf );
inputf = NULL;
}
if (outputf )
{
free( outputf );
outputf = NULL;
}
if ( logs )
{
snd_output_close( logs );
logs = NULL;
}
}
/******************************************************************************
Play Thread
******************************************************************************/
void*
AlsaAudio::alsa_loop( void* pthis )
{
AlsaAudio* aaThread = (AlsaAudio*)pthis;
aaThread->run();
return NULL;
}
void
AlsaAudio::run()
{
int npfds = snd_pcm_poll_descriptors_count( alsa_pcm );
int wr = 0;
int err;
if ( npfds <= 0 )
goto _error;
err = snd_pcm_prepare( alsa_pcm );
if ( err < 0 )
qDebug() << "snd_pcm_prepare error:" << snd_strerror( err );
while ( going && alsa_pcm )
{
if ( !paused && get_thread_buffer_filled() >= hw_period_size_in )
{
wr = snd_pcm_wait( alsa_pcm, 10 );
if ( wr > 0 )
{
alsa_write_out_thread_data();
}
else if ( wr < 0 )
{
alsa_handle_error( wr );
}
}
else
{
struct timespec req;
req.tv_sec = 0;
req.tv_nsec = 10000000; //0.1 seconds
nanosleep( &req, NULL );
}
}
_error:
err = snd_pcm_drop( alsa_pcm );
if ( err < 0 )
qDebug() << "snd_pcm_drop error:" << snd_strerror( err );
wr_index = rd_index = 0;
memset( thread_buffer, 0, thread_buffer_size );
// qDebug() << "Exiting thread";
pthread_exit( NULL );
}
/* transfer audio data from thread buffer to h/w */
void
AlsaAudio::alsa_write_out_thread_data( void )
{
ssize_t length;
int cnt;
length = qMin( hw_period_size_in, ssize_t(get_thread_buffer_filled()) );
length = qMin( length, snd_pcm_frames_to_bytes( alsa_pcm, alsa_get_avail() ) );
while (length > 0)
{
int rd;
cnt = qMin(int(length), thread_buffer_size - rd_index);
alsa_do_write( thread_buffer + rd_index, cnt);
rd = (rd_index + cnt) % thread_buffer_size;
rd_index = rd;
length -= cnt;
}
}
/* update and get the available space on h/w buffer (in frames) */
snd_pcm_sframes_t
AlsaAudio::alsa_get_avail( void )
{
snd_pcm_sframes_t ret;
if ( alsa_pcm == NULL )
return 0;
while ( ( ret = snd_pcm_avail_update( alsa_pcm ) ) < 0 )
{
ret = alsa_handle_error( ret );
if ( ret < 0 )
{
qDebug() << "alsa_get_avail(): snd_pcm_avail_update() failed:" << snd_strerror( -ret );
return 0;
}
}
return ret;
}
/* transfer data to audio h/w; length is given in bytes
*
* data can be modified via rate conversion or
* software volume before passed to audio h/w
*/
void
AlsaAudio::alsa_do_write( void* data, ssize_t length )
{
if ( alsa_convert_func != NULL )
length = alsa_convert_func( convertb, &data, length );
if ( alsa_stereo_convert_func != NULL )
length = alsa_stereo_convert_func( convertb, &data, length );
if ( alsa_frequency_convert_func != NULL )
{
length = alsa_frequency_convert_func( convertb, &data, length,
inputf->rate,
outputf->rate );
}
volume_adjust( data, length, outputf->xmms_format );
alsa_write_audio( (char*)data, length );
}
#define VOLUME_ADJUST( type, endian ) \
do { \
type *ptr = (type*)data; \
for ( i = 0; i < length; i += 2 ) \
{ \
*ptr = qTo##endian( (type)( qFrom##endian( *ptr ) * volume ) ); \
ptr++; \
} \
} while ( 0 )
#define VOLUME_ADJUST8( type ) \
do { \
type *ptr = (type*)data; \
for ( i = 0; i < length; i++ ) \
{ \
*ptr = (type)( *ptr * volume ); \
ptr++; \
} \
} while ( 0 )
void
AlsaAudio::volume_adjust( void* data, ssize_t length, AFormat fmt )
{
ssize_t i;
if ( volume == 1.0 )
return;
switch ( fmt )
{
case FMT_S16_LE:
VOLUME_ADJUST( qint16, LittleEndian );
break;
case FMT_U16_LE:
VOLUME_ADJUST( quint16, LittleEndian );
break;
case FMT_S16_BE:
VOLUME_ADJUST( qint16, BigEndian );
break;
case FMT_U16_BE:
VOLUME_ADJUST( quint16, BigEndian );
break;
case FMT_S8:
VOLUME_ADJUST8( qint8 );
break;
case FMT_U8:
VOLUME_ADJUST8( quint8 );
break;
default:
qDebug() << __PRETTY_FUNCTION__ << "unhandled format:" << fmt ;
break;
}
}
/* transfer data to audio h/w via normal write */
void
AlsaAudio::alsa_write_audio( char *data, ssize_t length )
{
snd_pcm_sframes_t written_frames;
while ( length > 0 )
{
snd_pcm_sframes_t frames = snd_pcm_bytes_to_frames( alsa_pcm, length );
written_frames = snd_pcm_writei( alsa_pcm, data, frames );
if ( written_frames > 0 )
{
ssize_t written = snd_pcm_frames_to_bytes( alsa_pcm, written_frames );
pcmCounter += written;
length -= written;
data += written;
}
else
{
int err = alsa_handle_error( (int)written_frames );
if ( err < 0 )
{
qDebug() << __PRETTY_FUNCTION__ << "write error:" << snd_strerror( -err );
break;
}
}
}
}
/* handle generic errors */
int
AlsaAudio::alsa_handle_error( int err )
{
switch ( err )
{
case -EPIPE:
return xrun_recover();
case -ESTRPIPE:
return suspend_recover();
}
return err;
}
/* close PCM and release associated resources */
void
AlsaAudio::alsa_close_pcm( void )
{
if ( alsa_pcm )
{
int err;
snd_pcm_drop( alsa_pcm );
if ( ( err = snd_pcm_close( alsa_pcm ) ) < 0 )
qDebug() << "alsa_close_pcm() failed:" << snd_strerror( -err );
alsa_pcm = NULL;
}
}
int
AlsaAudio::format_from_alsa( snd_pcm_format_t fmt )
{
uint i;
for ( i = 0; i < sizeof( format_table ) / sizeof( format_table[0] ); i++ )
if ( format_table[i].alsa == fmt )
return format_table[i].xmms;
qDebug() << "Unsupported format:" << snd_pcm_format_name( fmt );
return -1;
}
struct snd_format*
AlsaAudio::snd_format_from_xmms( AFormat fmt, unsigned int rate, unsigned int channels )
{
struct snd_format *f = (struct snd_format*)malloc( sizeof( struct snd_format ) );
uint i;
f->xmms_format = fmt;
f->format = SND_PCM_FORMAT_UNKNOWN;
for ( i = 0; i < sizeof( format_table ) / sizeof( format_table[0] ); i++ )
{
if ( format_table[i].xmms == fmt )
{
f->format = format_table[i].alsa;
break;
}
}
/* Get rid of _NE */
for ( i = 0; i < sizeof( format_table ) / sizeof( format_table[0] ); i++ )
{
if ( format_table[i].alsa == f->format )
{
f->xmms_format = format_table[i].xmms;
break;
}
}
f->rate = rate;
f->channels = channels;
f->sample_bits = snd_pcm_format_physical_width( f->format );
f->bps = ( rate * f->sample_bits * channels ) >> 3;
return f;
}
int
AlsaAudio::xrun_recover( void )
{
#ifndef QT_NO_DEBUG
snd_pcm_status_t *alsa_status;
snd_pcm_status_alloca( &alsa_status );
if ( snd_pcm_status( alsa_pcm, alsa_status ) < 0 )
{
qDebug() << "AlsaAudio::xrun_recover(): snd_pcm_status() failed";
}
else
{
snd_pcm_status_dump( alsa_status, logs );
qDebug() << "Status:\n" << logs;
}
#endif
return snd_pcm_prepare( alsa_pcm );
}
int
AlsaAudio::suspend_recover( void )
{
int err;
while ( ( err = snd_pcm_resume( alsa_pcm ) ) == -EAGAIN )
/* wait until suspend flag is released */
sleep( 1 );
if ( err < 0 )
{
qDebug() << "alsa_handle_error(): snd_pcm_resume() failed." ;
return snd_pcm_prepare( alsa_pcm );
}
return err;
}
unsigned int
AlsaAudio::timeElapsed()
{
return pcmCounter / outputf->bps;
}