--- /dev/null
+diff -urN last.fm-1.1.3.orig/LastFM.pro last.fm-1.1.3/LastFM.pro
+--- last.fm-1.1.3.orig/LastFM.pro 2007-03-18 20:57:23.000000000 -0700
++++ last.fm-1.1.3/LastFM.pro 2007-03-18 20:59:36.000000000 -0700
+@@ -5,8 +5,19 @@
+ src/webservice/ src/settingsservice/ \
+ src/httpinput/ \
+ src/mp3transcode/ \
+- src/rtaudioplayback/ \
+ src/metadataextension/ src/sidebarextension/ src/searchextension/ src/userinfoextension/ \
+ src/mediadevices/itunes/
+
+ TRANSLATIONS = i18n/lastfm_jp_JP.ts
++
++win32 {
++ SUBDIRS += src/rtaudioplayback/
++}
++
++unix:!linux-g++ {
++ SUBDIRS += src/rtaudioplayback/
++}
++
++unix:linux-g++ {
++ SUBDIRS += src/alsaplayback/
++}
+diff -urN last.fm-1.1.3.orig/src/alsaplayback/alsaaudio.cpp last.fm-1.1.3/src/alsaplayback/alsaaudio.cpp
+--- last.fm-1.1.3.orig/src/alsaplayback/alsaaudio.cpp 1969-12-31 16:00:00.000000000 -0800
++++ last.fm-1.1.3/src/alsaplayback/alsaaudio.cpp 2007-03-18 20:57:28.000000000 -0700
+@@ -0,0 +1,851 @@
++/***************************************************************************
++ * Copyright (C) 2007 by John Stamp *
++ * jstamp@users.sourceforge.net *
++ * *
++ * 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-2006 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 02111-1307, USA. *
++ ***************************************************************************/
++#include "alsaaudio.h"
++#include "Loqqer.h"
++#include <qendian.h>
++
++#include <QDebug>
++
++#define MIN( a, b ) ( ( ( a ) < ( b ) ) ? ( a ) : ( b ) )
++#define MAX_BUFFER_SIZE 5
++
++QMutex AlsaAudio::mutex;
++pthread_t AlsaAudio::audio_thread;
++
++QByteArray AlsaAudio::audioData;
++snd_output_t* AlsaAudio::logs = NULL;
++bool AlsaAudio::going = false;
++snd_pcm_t *AlsaAudio::alsa_pcm = NULL;
++
++int AlsaAudio::hw_period_size_in = 0;
++snd_format* AlsaAudio::inputf = NULL;
++snd_format* AlsaAudio::outputf = NULL;
++float AlsaAudio::volume = 1.0;
++
++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;
++bool AlsaAudio::use_mmap = false;
++
++
++AlsaAudio::AlsaAudio()
++{
++ maxBufferSize = 0;
++}
++
++
++AlsaAudio::~AlsaAudio()
++{
++}
++
++
++/******************************************************************************
++ Device Detection
++******************************************************************************/
++
++int AlsaAudio::getCards( void )
++{
++ int card = -1;
++ int err = 0;
++ _devices.clear();
++
++ if ( ( err = snd_card_next( &card ) ) != 0 )
++ {
++ LOGL( 1, "AlsaAudio::getCards() failed: " << snd_strerror( -err ) );
++ return -1;
++ }
++
++ while ( card > -1 )
++ {
++ getDevicesForCard( card );
++ if ( ( err = snd_card_next( &card ) ) != 0 )
++ {
++ LOGL( 1, "AlsaAudio::getCards() failed: " << snd_strerror( -err ) );
++ return -1;
++ }
++ }
++ return _devices.size();
++}
++
++
++void AlsaAudio::getDevicesForCard( int card )
++{
++ int pcm_device = -1, err;
++ snd_pcm_info_t *pcm_info;
++ snd_ctl_t *ctl;
++ char devName[64], *card_name;
++
++ sprintf( devName, "hw:%i", card );
++
++ if ( ( err = snd_ctl_open( &ctl, devName, 0 ) ) < 0 )
++ {
++ LOGL( 1, "AlsaAudio::getDevicesForCard() failed: " << snd_strerror( -err ) );
++ return;
++ }
++
++ if ( ( err = snd_card_get_name( card, &card_name ) ) != 0 )
++ {
++ LOGL( 1, "AlsaAudio::getDevicesForCard() failed: " << snd_strerror( -err ) );
++ card_name = "Unknown soundcard";
++ }
++
++ // Each card has its own default device
++ // But test, just to be sure it's there
++ AlsaDeviceInfo dev;
++ dev.name = QString( "%1: Default Device (default:%2)" ).arg( card_name ).arg( card );
++ dev.device = QString( "default:%1" ).arg( card );
++ snd_pcm_t *test_pcm;
++ err = snd_pcm_open( &test_pcm, dev.device.toStdString().c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK );
++ if ( err >= 0 )
++ snd_pcm_close( test_pcm );
++ if ( err == 0 || err == -EBUSY )
++ _devices.push_back( dev );
++
++ snd_pcm_info_alloca( &pcm_info );
++
++ for ( ;; )
++ {
++ char device[64], descr[128];
++ if ( ( err = snd_ctl_pcm_next_device( ctl, &pcm_device ) ) < 0 )
++ {
++ LOGL( 1, "AlsaAudio::getDevicesForCard() 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 )
++ LOGL( 1, "AlsaAudio::getDevicesForCard: snd_ctl_pcm_info() failed ("
++ << card << ":" << pcm_device << "): " << snd_strerror( -err ) );
++ continue;
++ }
++
++ sprintf( device, "hw:%d,%d", card, pcm_device );
++ sprintf( descr, "%s: %s (%s)", card_name,
++ snd_pcm_info_get_name( pcm_info ),
++ device );
++ dev.name = descr;
++ dev.device = device;
++ _devices.push_back( dev );
++ }
++
++ snd_ctl_close( ctl );
++}
++
++
++AlsaDeviceInfo AlsaAudio::getDeviceInfo( int device )
++{
++ return _devices[device];
++}
++
++
++/******************************************************************************
++ Device Setup
++******************************************************************************/
++
++bool AlsaAudio::alsaSetup( QString device, snd_pcm_uframes_t periodSize, uint periodCount, snd_format *f )
++{
++ int err, 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;
++ snd_pcm_access_mask_t *mask;
++
++#ifndef QT_NO_DEBUG
++ qDebug() << "AlsaAudio::alsaSetup()";
++ snd_output_stdio_attach( &logs, stderr, 0 );
++#endif
++
++ alsa_convert_func = NULL;
++ alsa_stereo_convert_func = NULL;
++ alsa_frequency_convert_func = NULL;
++
++ free( outputf );
++ outputf = snd_format_from_xmms( f->xmms_format, f->rate, f->channels );
++
++#ifndef QT_NO_DEBUG
++ qDebug() << "Opening device:" << device;
++#endif
++ // FIXME: Can snd_pcm_open() return EAGAIN?
++ if ( ( err = snd_pcm_open( &alsa_pcm, device.toStdString().c_str(),
++ SND_PCM_STREAM_PLAYBACK,
++ SND_PCM_NONBLOCK ) ) < 0 )
++ {
++ LOGL( 1, "AlsaAudio::alsaSetup(): Failed to open pcm device (" << device << "): " << snd_strerror( -err ) );
++ alsa_pcm = NULL;
++ free( outputf );
++ outputf = NULL;
++ return false;
++ }
++
++#ifndef QT_NO_DEBUG
++ 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 << "Device:" << alsa_device << "Subdevice:" << alsa_subdevice;
++#endif
++
++ snd_pcm_hw_params_alloca( &hwparams );
++
++ if ( ( err = snd_pcm_hw_params_any( alsa_pcm, hwparams ) ) < 0 )
++ {
++ LOGL( 1, "AlsaAudio::alsaSetup(): No configuration available for playback: " << snd_strerror( -err ) );
++ return false;
++ }
++
++ // First try to set up mmapped access
++ mask = (snd_pcm_access_mask_t*)alloca(snd_pcm_access_mask_sizeof());
++ snd_pcm_access_mask_none(mask);
++ snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_INTERLEAVED);
++ snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
++ snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_COMPLEX);
++
++#ifndef QT_NO_DEBUG
++ qDebug() << "Trying to set mmapped write mode";
++#endif
++ if ( ( err = snd_pcm_hw_params_set_access_mask( alsa_pcm, hwparams, mask ) ) < 0 )
++ {
++ use_mmap = false;
++#ifndef QT_NO_DEBUG
++ qDebug() << "Setting mmapped write mode failed: " << snd_strerror( -err ) << "\n Trying normal write mode";
++#endif
++ if ( ( err = snd_pcm_hw_params_set_access( alsa_pcm, hwparams,
++ SND_PCM_ACCESS_RW_INTERLEAVED ) ) < 0 )
++ {
++ LOGL( 1, "AlsaAudio::alsaSetup(): Cannot set normal write mode: " << snd_strerror( -err ) );
++ return false;
++ }
++ }
++ else
++ use_mmap = true;
++
++ 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 != f->format )
++ {
++ outputf->xmms_format = (AFormat)format_from_alsa( outputf->format );
++#ifndef QT_NO_DEBUG
++ qDebug() << "Converting format from" << f->xmms_format << "to" << outputf->xmms_format;
++#endif
++ if ( outputf->xmms_format < 0 )
++ return -1;
++ alsa_convert_func = xmms_convert_get_func( outputf->xmms_format, f->xmms_format );
++ if ( alsa_convert_func == NULL )
++ {
++ LOGL( 1, "Format translation needed, but not available. Input: " << f->xmms_format << "; Output: " << outputf->xmms_format );
++ return false;
++ }
++ }
++ else
++ {
++ LOGL( 1, "AlsaAudio::alsaSetup(): Sample format not available for playback: " << snd_strerror( -err ) );
++ return false;
++ }
++ }
++
++ snd_pcm_hw_params_set_channels_near( alsa_pcm, hwparams, &outputf->channels );
++ if ( outputf->channels != f->channels )
++ {
++#ifndef QT_NO_DEBUG
++ qDebug() << "Converting channels from" << f->channels << "to" << outputf->channels;
++#endif
++ alsa_stereo_convert_func =
++ xmms_convert_get_channel_func( outputf->xmms_format,
++ outputf->channels,
++ f->channels );
++ if ( alsa_stereo_convert_func == NULL )
++ {
++ LOGL( 1, "No stereo conversion available. Format: " << outputf->xmms_format << "; Input Channels: " << f->channels << "; Output Channels: " << outputf->channels );
++ return false;
++ }
++ }
++
++ snd_pcm_hw_params_set_rate_near( alsa_pcm, hwparams, &outputf->rate, 0 );
++ if ( outputf->rate == 0 )
++ {
++ LOGL( 1, "AlsaAudio::alsaSetup: No usable samplerate available." );
++ return false;
++ }
++ if ( outputf->rate != f->rate )
++ {
++ LOGL( 3, "Converting samplerate from " << f->rate << " to " << outputf->rate );
++ if ( outputf->channels < 1 || outputf->channels > 2 )
++ {
++ LOGL( 1, "Unsupported number of channels: " << outputf->channels << ". Resample function not available" );
++ alsa_frequency_convert_func = NULL;
++ return false;
++ }
++ alsa_frequency_convert_func =
++ xmms_convert_get_frequency_func( outputf->xmms_format,
++ outputf->channels );
++ if ( alsa_frequency_convert_func == NULL )
++ {
++ LOGL( 1, "Resample function not available. Format " << outputf->xmms_format );
++ 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 )
++ {
++ LOGL( 1, "AlsaAudio::alsaSetup(): Set period size failed: " << snd_strerror( -err ) );
++ return false;
++ }
++
++ if ( ( err = snd_pcm_hw_params_set_periods_near( alsa_pcm, hwparams,
++ &periodCount, 0 ) ) < 0 )
++ {
++ LOGL( 1, "AlsaAudio::alsaSetup(): Set period count failed: " << snd_strerror( -err ) );
++ return false;
++ }
++
++ if ( snd_pcm_hw_params( alsa_pcm, hwparams ) < 0 )
++ {
++#ifndef QT_NO_DEBUG
++ snd_pcm_hw_params_dump( hwparams, logs );
++#endif
++ LOGL( 1, "AlsaAudio::alsaSetup(): Unable to install hw params" );
++ return false;
++ }
++
++ if ( ( err = snd_pcm_hw_params_get_buffer_size( hwparams, &alsa_buffer_size ) ) < 0 )
++ {
++ LOGL( 1, "AlsaAudio::alsaSetup(): snd_pcm_hw_params_get_buffer_size() failed: " << snd_strerror( -err ) );
++ return false;
++ }
++
++ if ( ( err = snd_pcm_hw_params_get_period_size( hwparams, &alsa_period_size, 0 ) ) < 0 )
++ {
++ LOGL( 1, "AlsaAudio::alsaSetup(): snd_pcm_hw_params_get_period_size() failed: " << snd_strerror( -err ) );
++ 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 ) )
++ LOGL( 1, "AlsaAudio::alsaSetup(): setting start threshold failed: " << snd_strerror( -err ) );
++ if ( snd_pcm_sw_params( alsa_pcm, swparams ) < 0 )
++ {
++ LOGL( 1, "AlsaAudio::alsaSetup(): Unable to install sw params" );
++ 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;
++ }
++
++#ifndef QT_NO_DEBUG
++ qDebug() << "Device setup: period size:" << hw_period_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;
++#endif
++ return true;
++}
++
++bool AlsaAudio::alsaOpen( QString device, AFormat format, unsigned int rate, unsigned int channels, unsigned int buffer_time, unsigned int period_time )
++{
++#ifndef QT_NO_DEBUG
++ qDebug() << "Opening device";
++#endif
++ inputf = snd_format_from_xmms( format, rate, channels );
++
++ // We'll be using this in alsaWrite
++ maxBufferSize = inputf->bps * MAX_BUFFER_SIZE;
++ // And clear the buffer, just in case
++ clearBuffer();
++
++ if ( alsaSetup( device, buffer_time, period_time, inputf ) == false )
++ {
++ alsaClose();
++ return false;
++ }
++
++ going = true;
++ convertb = xmms_convert_buffers_new();
++
++ AlsaAudio* aaThread = new AlsaAudio();
++#ifndef QT_NO_DEBUG
++ qDebug() << "Starting thread";
++#endif
++ pthread_create( &audio_thread, NULL, &alsa_loop, (void*)aaThread );
++
++ return true;
++}
++
++void AlsaAudio::clearBuffer( void )
++{
++ mutex.lock();
++ audioData.clear();
++ mutex.unlock();
++}
++
++/******************************************************************************
++ Play Interface
++******************************************************************************/
++
++void AlsaAudio::alsaWrite( const QByteArray* inputData )
++{
++ if ( ( audioData.size() + inputData->size() ) < maxBufferSize )
++ {
++ mutex.lock();
++#ifndef QT_NO_DEBUG
++ qDebug( "max buffer size: %d; buffer data: %d; input data: %d", maxBufferSize, audioData.size(), inputData->size() );
++#endif
++ audioData.append( *inputData );
++ mutex.unlock();
++ }
++ else
++ {
++ mutex.lock();
++ int inSize = maxBufferSize - audioData.size();
++#ifndef QT_NO_DEBUG
++ qDebug( "max buffer size: %d; buffer data: %d; input data: %d; truncated to: %d",
++ maxBufferSize, audioData.size(), inputData->size(), inSize );
++#endif
++ audioData.append( inputData->left( inSize ) );
++ mutex.unlock();
++ LOGL( 1, "Max data buffer size reached. Bytes dropped: " << inSize );
++ }
++}
++
++
++int AlsaAudio::bufferSize( void )
++{
++ return audioData.size();
++}
++
++void AlsaAudio::setVolume ( float vol )
++{
++ volume = vol;
++}
++
++
++void AlsaAudio::alsaClose( void )
++{
++ if ( !going )
++ return;
++
++#ifndef QT_NO_DEBUG
++ qDebug() << "Closing device";
++#endif
++
++ going = false;
++
++ pthread_join( audio_thread, NULL );
++
++ xmms_convert_buffers_destroy( convertb );
++ convertb = NULL;
++ free( inputf );
++ inputf = NULL;
++ free( outputf );
++ outputf = NULL;
++
++#ifndef QT_NO_DEBUG
++ snd_output_close( logs );
++ qDebug() << "Device closed";
++#endif
++}
++
++
++/******************************************************************************
++ Play Thread
++******************************************************************************/
++
++void* AlsaAudio::alsa_loop( void* pthis )
++{
++ AlsaAudio* aaThread = (AlsaAudio*)pthis;
++ aaThread->run();
++ return NULL;
++}
++
++
++void AlsaAudio::run( void )
++{
++ int npfds = snd_pcm_poll_descriptors_count( alsa_pcm );
++ struct pollfd *pfds;
++ unsigned short *revents;
++
++ if ( npfds <= 0 )
++ goto _error;
++ pfds = (struct pollfd*)malloc( sizeof( *pfds ) * npfds );
++ revents = (unsigned short*)malloc( sizeof( *revents ) * npfds );
++ while ( going && alsa_pcm )
++ {
++ if ( audioData.size() > hw_period_size_in )
++ {
++ snd_pcm_poll_descriptors( alsa_pcm, pfds, npfds );
++ if ( poll( pfds, npfds, 10 ) > 0 )
++ {
++ // need to check revents. poll() with
++ // dmix returns a postive value even
++ // if no data is available
++ int i;
++ snd_pcm_poll_descriptors_revents( alsa_pcm, pfds,
++ npfds, revents );
++ for ( i = 0; i < npfds; i++ )
++ if ( revents[i] & POLLOUT )
++ {
++ pumpThreadData();
++ break;
++ }
++ }
++ }
++ else
++ {
++ struct timespec req;
++ req.tv_sec = 0;
++ req.tv_nsec = 10000000;
++ nanosleep( &req, NULL );
++ }
++ }
++ free( pfds );
++ free( revents );
++
++ _error:
++ alsa_close_pcm();
++ mutex.lock();
++ audioData.clear();
++ mutex.unlock();
++#ifndef QT_NO_DEBUG
++ qDebug() << "Exiting thread";
++#endif
++ pthread_exit( NULL );
++}
++
++
++/* transfer audio data from thread buffer to h/w */
++void AlsaAudio::pumpThreadData( void )
++{
++ int length, cnt, avail, datSize;
++
++ datSize = audioData.size();
++ length = MIN( hw_period_size_in, datSize );
++ avail = snd_pcm_frames_to_bytes( alsa_pcm, getAvailableFrames() );
++ length = MIN( length, avail );
++ while ( length > 0 )
++ {
++ cnt = MIN( length, datSize );
++ convertData( audioData.left( cnt ).data(), cnt );
++ mutex.lock();
++ audioData.remove( 0, cnt );
++ mutex.unlock();
++ length -= cnt;
++ }
++}
++
++
++/* update and get the available space on h/w buffer (in frames) */
++snd_pcm_sframes_t AlsaAudio::getAvailableFrames( 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 )
++ {
++ LOGL( 1, "alsa_get_avail(): snd_pcm_avail_update() failed: " << snd_strerror( -ret ) );
++ return 0;
++ }
++ 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::convertData( void* data, int 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 );
++ }
++
++ adjustVolume( data, length, outputf->xmms_format );
++
++ writeToCard( (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::adjustVolume( void* data, int length, AFormat fmt )
++{
++ int 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:
++ LOGL( 1, "AlsaAudio::adjustVolume(): unhandled format: " << fmt );
++ break;
++ }
++}
++
++
++/* transfer data to audio h/w via normal write */
++void AlsaAudio::writeToCard( char *data, int length )
++{
++ snd_pcm_sframes_t written_frames;
++
++ while ( length > 0 )
++ {
++ int frames = snd_pcm_bytes_to_frames( alsa_pcm, length );
++
++ if ( use_mmap )
++ {
++ written_frames = snd_pcm_mmap_writei( alsa_pcm, data, frames );
++ }
++ else
++ written_frames = snd_pcm_writei( alsa_pcm, data, frames );
++
++ if ( written_frames > 0 )
++ {
++ int written = snd_pcm_frames_to_bytes( alsa_pcm, written_frames );
++ length -= written;
++ data += written;
++ }
++ else
++ {
++ int err = alsa_handle_error( (int)written_frames );
++ if ( err < 0 )
++ {
++ LOGL( 1, "AlsaAudio::writeToCard(): 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 )
++ LOGL( 1, "alsa_pcm_close() 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;
++ LOGL( 1, "Unsupported format: " << snd_pcm_format_name( fmt ) );
++ return -1;
++}
++
++struct snd_format * AlsaAudio::snd_format_from_xmms( AFormat fmt, int rate, 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 )
++ {
++ LOGL( 3, "alsa_handle_error(): snd_pcm_resume() failed." );
++ return snd_pcm_prepare( alsa_pcm );
++ }
++ return err;
++}
+diff -urN last.fm-1.1.3.orig/src/alsaplayback/alsaaudio.h last.fm-1.1.3/src/alsaplayback/alsaaudio.h
+--- last.fm-1.1.3.orig/src/alsaplayback/alsaaudio.h 1969-12-31 16:00:00.000000000 -0800
++++ last.fm-1.1.3/src/alsaplayback/alsaaudio.h 2007-03-18 20:57:28.000000000 -0700
+@@ -0,0 +1,121 @@
++/***************************************************************************
++ * Copyright (C) 2007 by John Stamp *
++ * jstamp@users.sourceforge.net *
++ * *
++ * 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 02111-1307, USA. *
++ ***************************************************************************/
++#ifndef ALSAAUDIO_H
++#define ALSAAUDIO_H
++
++#include <alsa/asoundlib.h>
++#include <QByteArray>
++#include <QList>
++#include <QString>
++#include <QMutex>
++#include "xconvert.h"
++
++struct AlsaDeviceInfo {
++ QString name;
++ QString device;
++};
++
++struct snd_format {
++ unsigned int rate;
++ unsigned int channels;
++ snd_pcm_format_t format;
++ AFormat xmms_format;
++ int sample_bits;
++ int bps;
++};
++
++static const struct {
++ AFormat xmms;
++ snd_pcm_format_t alsa;
++} format_table[] =
++ { { FMT_S16_LE, SND_PCM_FORMAT_S16_LE },
++ { FMT_S16_BE, SND_PCM_FORMAT_S16_BE },
++ { FMT_S16_NE, SND_PCM_FORMAT_S16 },
++ { FMT_U16_LE, SND_PCM_FORMAT_U16_LE },
++ { FMT_U16_BE, SND_PCM_FORMAT_U16_BE },
++ { FMT_U16_NE, SND_PCM_FORMAT_U16 },
++ { FMT_U8, SND_PCM_FORMAT_U8 },
++ { FMT_S8, SND_PCM_FORMAT_S8 },
++ };
++
++class AlsaAudio {
++public:
++ AlsaAudio();
++
++ ~AlsaAudio();
++
++ int getCards();
++ AlsaDeviceInfo getDeviceInfo( int device );
++
++ bool alsaOpen( QString device, AFormat format, unsigned int rate,
++ unsigned int channels, unsigned int buffer_time,
++ unsigned int period_time );
++ void alsaWrite( const QByteArray* inputData );
++
++ void alsaClose( void );
++
++ void setVolume ( float vol );
++ int bufferSize( void );
++ void clearBuffer( void );
++
++private:
++ QList<AlsaDeviceInfo> _devices;
++
++ int maxBufferSize;
++
++ // The following static variables are configured in either
++ // alsaOpen or alsaSetup and used later in the audio thread
++ static int hw_period_size_in;
++ static snd_output_t *logs;
++ static bool going;
++ static snd_pcm_t *alsa_pcm;
++ static snd_format* inputf;
++ static snd_format* outputf;
++ static float volume;
++ static QByteArray audioData;
++ static convert_func_t alsa_convert_func;
++ static convert_channel_func_t alsa_stereo_convert_func;
++ static convert_freq_func_t alsa_frequency_convert_func;
++ static xmms_convert_buffers *convertb;
++ static pthread_t audio_thread;
++ static QMutex mutex;
++ static bool use_mmap;
++
++ void getDevicesForCard( int card );
++ bool alsaSetup( QString device, snd_pcm_uframes_t periodSize, uint periodCount, snd_format *f );
++
++ static void* alsa_loop( void* );
++ void run( void );
++ void pumpThreadData( void );
++ void convertData( void* data, int length );
++ void adjustVolume( void* data, int length, AFormat fmt );
++ void writeToCard( char *data, int length );
++
++ snd_pcm_sframes_t getAvailableFrames( void );
++ int alsa_handle_error( int err );
++ int xrun_recover( void );
++ int suspend_recover( void );
++ int format_from_alsa( snd_pcm_format_t fmt );
++ snd_format* snd_format_from_xmms( AFormat fmt, int rate, int channels );
++
++ void alsa_close_pcm( void );
++};
++
++#endif
+diff -urN last.fm-1.1.3.orig/src/alsaplayback/alsaplayback.cpp last.fm-1.1.3/src/alsaplayback/alsaplayback.cpp
+--- last.fm-1.1.3.orig/src/alsaplayback/alsaplayback.cpp 1969-12-31 16:00:00.000000000 -0800
++++ last.fm-1.1.3/src/alsaplayback/alsaplayback.cpp 2007-03-18 20:57:28.000000000 -0700
+@@ -0,0 +1,205 @@
++/***************************************************************************
++ * Copyright (C) 2005 - 2006 by *
++ * Christian Muehlhaeuser, Last.fm Ltd <chris@last.fm> *
++ * Erik Jaelevik, Last.fm Ltd <erik@last.fm> *
++ * *
++ * 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., *
++ * 51 Franklin Steet, Fifth Floor, Boston, MA 02111-1307, USA. *
++ ***************************************************************************/
++
++#include <QtGui>
++
++#include "alsaplayback.h"
++#include "containerutils.h"
++#include "Loqqer.h"
++
++float AlsaPlayback::m_volume = 0.5;
++
++
++AlsaPlayback::AlsaPlayback() :
++ m_audio( 0 ),
++ m_timer( new QTimer( this ) )
++{
++ gLogger.Init( savePath( "playback.log" ), false );
++ gLogger.SetLevel( 4 );
++
++ LOGL( 3, "Initialising AlsaAudio Playback" );
++ initAudio();
++
++ connect( m_timer, SIGNAL( timeout() ), this, SLOT( checkBuffer() ) );
++}
++
++AlsaPlayback::~AlsaPlayback()
++{
++ delete m_audio;
++}
++
++void
++AlsaPlayback::setVolume( int volume )
++{
++ if ( m_audio )
++ m_audio->setVolume( (float)volume / 100.0 );
++}
++
++
++QStringList
++AlsaPlayback::soundSystems()
++{
++ QStringList l;
++ l << "Alsa";
++ return l;
++}
++
++
++QStringList
++AlsaPlayback::devices()
++{
++#ifndef QT_NO_DEBUG
++ qDebug() << "Querying audio devices";
++#endif
++ QStringList l;
++
++ if ( !m_audio )
++ return l;
++
++ int cards = m_audio->getCards();
++#ifndef QT_NO_DEBUG
++ qDebug() << "Device nums:" << cards;
++#endif
++
++ for ( int i = 0; i < cards; i++ )
++ {
++ AlsaDeviceInfo info;
++ info = m_audio->getDeviceInfo( i );
++#ifndef QT_NO_DEBUG
++ qDebug() << "Device name:" << info.name ;
++#endif
++
++ l << info.name;
++ }
++
++ return l;
++}
++
++
++void
++AlsaPlayback::startPlayback()
++{
++ int channels = 2;
++ int sampleRate = 44100;
++ int periodSize = 1024; // According to mplayer, these two are good defaults.
++ int periodCount = 16; // They create a buffer size of 16384 frames.
++ QString cardDevice;
++
++ if ( !m_audio )
++ {
++ LOGL( 1, "No AlsaAudio instance available." );
++ goto _error;
++ }
++
++ cardDevice = internalSoundCardID();
++
++ if ( !m_audio->alsaOpen( cardDevice, FMT_S16_LE, sampleRate, channels, periodSize, periodCount ) )
++ goto _error;
++
++ if ( !m_timer->isActive() )
++ m_timer->start( 15 );
++
++ return;
++
++_error:
++ // We need to send a stop signal to m_iInput here, otherwise
++ // it will keep running and filling up the buffers even though
++ // there is no available device.
++ emit stop();
++ QMessageBox::critical( qApp->activeWindow(), tr("Audio Error"), tr("No soundcard available."));
++ return;
++}
++
++
++void
++AlsaPlayback::stopPlayback()
++{
++ if ( !m_audio )
++ {
++ return;
++ }
++
++ m_timer->stop();
++ m_audio->alsaClose();
++}
++
++
++void
++AlsaPlayback::initAudio()
++{
++ m_audio = new AlsaAudio();
++
++ if ( m_audio )
++ {
++ LOGL( 1, "AlsaAudio successfully initialised." );
++ }
++ else
++ {
++ LOGL( 1, "Initialising AlsaAudio failed." );
++ }
++}
++
++
++void
++AlsaPlayback::dataAvailable( const QByteArray &buffer )
++{
++ m_audio->alsaWrite( &buffer );
++}
++
++
++void
++AlsaPlayback::clearBuffers()
++{
++ m_audio->clearBuffer();
++}
++
++
++void
++AlsaPlayback::checkBuffer()
++{
++ // NOTE: this is wavedata! 16kb of wavedata aprox. equals 1.6kb of mp3
++ if ( m_audio->bufferSize() < 16384 * 12 )
++ emit needData();
++}
++
++
++QString
++AlsaPlayback::internalSoundCardID()
++{
++ if ( !m_audio )
++ return "";
++
++ int cards = m_audio->getCards();
++
++ int cardnum = settingsService()->soundCard();
++
++ if ( cardnum < cards )
++ {
++ AlsaDeviceInfo info;
++ info = m_audio->getDeviceInfo( cardnum );
++ return info.device;
++ }
++ else
++ return "default";
++}
++
++
++Q_EXPORT_PLUGIN2( playback, AlsaPlayback )
+diff -urN last.fm-1.1.3.orig/src/alsaplayback/alsaplayback.h last.fm-1.1.3/src/alsaplayback/alsaplayback.h
+--- last.fm-1.1.3.orig/src/alsaplayback/alsaplayback.h 1969-12-31 16:00:00.000000000 -0800
++++ last.fm-1.1.3/src/alsaplayback/alsaplayback.h 2007-03-18 20:57:28.000000000 -0700
+@@ -0,0 +1,71 @@
++/***************************************************************************
++ * Copyright (C) 2005 - 2006 by *
++ * Christian Muehlhaeuser, Last.fm Ltd <chris@last.fm> *
++ * Erik Jaelevik, Last.fm Ltd <erik@last.fm> *
++ * *
++ * 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., *
++ * 51 Franklin Steet, Fifth Floor, Boston, MA 02111-1307, USA. *
++ ***************************************************************************/
++
++#ifndef ALSAPLAYBACK_H
++#define ALSAPLAYBACK_H
++
++#include <playbackinterface.h>
++#include "alsaaudio.h"
++
++#include <QObject>
++#include <QTimer>
++
++class AlsaPlayback : public PlaybackInterface
++{
++ Q_OBJECT
++ Q_INTERFACES( PlaybackInterface )
++
++ public:
++ AlsaPlayback();
++ ~AlsaPlayback();
++
++ void initAudio();
++ float volume() { return m_volume; }
++
++ QStringList soundSystems();
++ QStringList devices();
++
++ static float m_volume;
++
++ signals:
++ void needData();
++ void stop(); // connected to m_iInput::stop()
++
++ public slots:
++ void dataAvailable( const QByteArray &buffer );
++ void clearBuffers();
++
++ void startPlayback();
++ void stopPlayback();
++
++ void setVolume( int volume );
++
++ private:
++ AlsaAudio *m_audio;
++ QTimer *m_timer;
++
++ QString internalSoundCardID();
++
++ private slots:
++ void checkBuffer();
++};
++
++#endif
+diff -urN last.fm-1.1.3.orig/src/alsaplayback/alsaplayback.pro last.fm-1.1.3/src/alsaplayback/alsaplayback.pro
+--- last.fm-1.1.3.orig/src/alsaplayback/alsaplayback.pro 1969-12-31 16:00:00.000000000 -0800
++++ last.fm-1.1.3/src/alsaplayback/alsaplayback.pro 2007-03-18 20:57:28.000000000 -0700
+@@ -0,0 +1,19 @@
++TEMPLATE = lib
++CONFIG += plugin
++INCLUDEPATH += ../ ../libLastFMTools
++LOGGERDIR = ../build
++LIBS += -L../../bin -lLastFMTools -lasound
++OBJECTS_DIR = ../build
++MOC_DIR = ../build
++UI_DIR = ../build
++TARGET = playback_alsaaudio
++DESTDIR = ../../bin/services
++
++include(../../definitions.pro.inc)
++
++HEADERS = alsaplayback.h alsaaudio.h xconvert.h
++SOURCES = alsaplayback.cpp alsaaudio.cpp xconvert.c
++
++LIBS += $$LOGGERDIR/Loqqer.o
++
++QT += gui xml network
+diff -urN last.fm-1.1.3.orig/src/alsaplayback/precompiled.h last.fm-1.1.3/src/alsaplayback/precompiled.h
+--- last.fm-1.1.3.orig/src/alsaplayback/precompiled.h 1969-12-31 16:00:00.000000000 -0800
++++ last.fm-1.1.3/src/alsaplayback/precompiled.h 2007-03-18 20:57:28.000000000 -0700
+@@ -0,0 +1,30 @@
++/***************************************************************************\r
++ * Copyright (C) 2005 - 2006 by *\r
++ * Christian Muehlhaeuser, Last.fm Ltd <chris@last.fm> *\r
++ * Erik Jaelevik, Last.fm Ltd <erik@last.fm> *\r
++ * *\r
++ * This program is free software; you can redistribute it and/or modify *\r
++ * it under the terms of the GNU General Public License as published by *\r
++ * the Free Software Foundation; either version 2 of the License, or *\r
++ * (at your option) any later version. *\r
++ * *\r
++ * This program is distributed in the hope that it will be useful, *\r
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of *\r
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *\r
++ * GNU General Public License for more details. *\r
++ * *\r
++ * You should have received a copy of the GNU General Public License *\r
++ * along with this program; if not, write to the *\r
++ * Free Software Foundation, Inc., *\r
++ * 51 Franklin Steet, Fifth Floor, Boston, MA 02111-1307, USA. *\r
++ ***************************************************************************/\r
++\r
++/* Add C includes here */\r
++\r
++#if defined __cplusplus\r
++ /* Add C++ includes here */\r
++ #include <QtCore>\r
++ #include <QtGui>\r
++ #include <QtNetwork>\r
++ #include <QtXml>\r
++#endif // __cplusplus\r
+diff -urN last.fm-1.1.3.orig/src/alsaplayback/xconvert.c last.fm-1.1.3/src/alsaplayback/xconvert.c
+--- last.fm-1.1.3.orig/src/alsaplayback/xconvert.c 1969-12-31 16:00:00.000000000 -0800
++++ last.fm-1.1.3/src/alsaplayback/xconvert.c 2007-03-18 20:57:28.000000000 -0700
+@@ -0,0 +1,771 @@
++/*
++ * Copyright (C) 2001-2003 Haavard Kvaalen <havardk@xmms.org>
++ *
++ * Licensed under GNU LGPL version 2.
++ */
++
++#include <stdlib.h>
++#include <stdint.h>
++#include "xconvert.h"
++
++// These are adapted from defines in gtypes.h and glibconfig.h
++#ifndef FALSE
++#define FALSE ( 0 )
++#endif
++
++#ifndef TRUE
++#define TRUE ( !FALSE )
++#endif
++
++# define GUINT16_SWAP_LE_BE( val ) \
++ ( ( uint16_t ) \
++ ( \
++ ( uint16_t ) ( ( uint16_t ) ( val ) >> 8 ) | \
++ ( uint16_t ) ( ( uint16_t ) ( val ) << 8 ) \
++ ) \
++ )
++
++# define GINT16_SWAP_LE_BE( val ) ( ( int16_t ) GUINT16_SWAP_LE_BE ( val ) )
++
++#ifdef WORDS_BIGENDIAN
++
++# define IS_BIG_ENDIAN TRUE
++
++# define GINT16_TO_BE( val ) ( ( int16_t ) ( val ) )
++# define GINT16_FROM_BE( val ) ( ( int16_t ) ( val ) )
++# define GUINT16_TO_BE( val ) ( ( uint16_t ) ( val ) )
++# define GUINT16_FROM_BE( val ) ( ( uint16_t ) ( val ) )
++
++# define GUINT16_TO_LE( val ) ( GUINT16_SWAP_LE_BE ( val ) )
++# define GUINT16_FROM_LE( val ) ( GUINT16_SWAP_LE_BE ( val ) )
++# define GINT16_TO_LE( val ) ( ( int16_t ) GUINT16_SWAP_LE_BE ( val ) )
++# define GINT16_FROM_LE( val ) ( ( int16_t ) GUINT16_SWAP_LE_BE ( val ) )
++
++#else
++
++# define IS_BIG_ENDIAN FALSE
++
++# define GINT16_TO_LE( val ) ( ( int16_t ) ( val ) )
++# define GINT16_FROM_LE( val ) ( ( int16_t ) ( val ) )
++# define GUINT16_TO_LE( val ) ( ( uint16_t ) ( val ) )
++# define GUINT16_FROM_LE( val ) ( ( uint16_t ) ( val ) )
++
++# define GUINT16_TO_BE( val ) ( GUINT16_SWAP_LE_BE ( val ) )
++# define GUINT16_FROM_BE( val ) ( GUINT16_SWAP_LE_BE ( val ) )
++# define GINT16_TO_BE( val ) ( ( int16_t ) GUINT16_SWAP_LE_BE ( val ) )
++# define GINT16_FROM_BE( val ) ( ( int16_t ) GUINT16_SWAP_LE_BE ( val ) )
++
++#endif
++
++
++struct buffer {
++ void *buffer;
++ uint size;
++};
++
++struct xmms_convert_buffers {
++ struct buffer format_buffer, stereo_buffer, freq_buffer;
++};
++
++struct xmms_convert_buffers* xmms_convert_buffers_new( void )
++{
++ return calloc( 1, sizeof( struct xmms_convert_buffers ) );
++}
++
++static void* convert_get_buffer( struct buffer *buffer, size_t size )
++{
++ if ( size > 0 && size <= buffer->size )
++ return buffer->buffer;
++
++ buffer->size = size;
++ buffer->buffer = realloc( buffer->buffer, size );
++ return buffer->buffer;
++}
++
++void xmms_convert_buffers_free( struct xmms_convert_buffers* buf )
++{
++ convert_get_buffer( &buf->format_buffer, 0 );
++ convert_get_buffer( &buf->stereo_buffer, 0 );
++ convert_get_buffer( &buf->freq_buffer, 0 );
++}
++
++void xmms_convert_buffers_destroy( struct xmms_convert_buffers* buf )
++{
++ if ( !buf )
++ return;
++ xmms_convert_buffers_free( buf );
++ free( buf );
++}
++
++static int convert_swap_endian( struct xmms_convert_buffers* buf, void **data, int length )
++{
++ uint16_t *ptr = *data;
++ int i;
++ for ( i = 0; i < length; i += 2, ptr++ )
++ *ptr = GUINT16_SWAP_LE_BE( *ptr );
++
++ return i;
++}
++
++static int convert_swap_sign_and_endian_to_native( struct xmms_convert_buffers* buf, void **data, int length )
++{
++ uint16_t *ptr = *data;
++ int i;
++ for ( i = 0; i < length; i += 2, ptr++ )
++ *ptr = GUINT16_SWAP_LE_BE( *ptr ) ^ 1 << 15;
++
++ return i;
++}
++
++static int convert_swap_sign_and_endian_to_alien( struct xmms_convert_buffers* buf, void **data, int length )
++{
++ uint16_t *ptr = *data;
++ int i;
++ for ( i = 0; i < length; i += 2, ptr++ )
++ *ptr = GUINT16_SWAP_LE_BE( *ptr ^ 1 << 15 );
++
++ return i;
++}
++
++static int convert_swap_sign16( struct xmms_convert_buffers* buf, void **data, int length )
++{
++ int16_t *ptr = *data;
++ int i;
++ for ( i = 0; i < length; i += 2, ptr++ )
++ *ptr ^= 1 << 15;
++
++ return i;
++}
++
++static int convert_swap_sign8( struct xmms_convert_buffers* buf, void **data, int length )
++{
++ int8_t *ptr = *data;
++ int i;
++ for ( i = 0; i < length; i++ )
++ *ptr++ ^= 1 << 7;
++
++ return i;
++}
++
++static int convert_to_8_native_endian( struct xmms_convert_buffers* buf, void **data, int length )
++{
++ int8_t *output = *data;
++ int16_t *input = *data;
++ int i;
++ for ( i = 0; i < length / 2; i++ )
++ *output++ = *input++ >> 8;
++
++ return i;
++}
++
++static int convert_to_8_native_endian_swap_sign( struct xmms_convert_buffers* buf, void **data, int length )
++{
++ int8_t *output = *data;
++ int16_t *input = *data;
++ int i;
++ for ( i = 0; i < length / 2; i++ )
++ *output++ = ( *input++ >> 8 ) ^ ( 1 << 7 );
++
++ return i;
++}
++
++
++static int convert_to_8_alien_endian( struct xmms_convert_buffers* buf, void **data, int length )
++{
++ int8_t *output = *data;
++ int16_t *input = *data;
++ int i;
++ for ( i = 0; i < length / 2; i++ )
++ *output++ = *input++ & 0xff;
++
++ return i;
++}
++
++static int convert_to_8_alien_endian_swap_sign( struct xmms_convert_buffers* buf, void **data, int length )
++{
++ int8_t *output = *data;
++ int16_t *input = *data;
++ int i;
++ for ( i = 0; i < length / 2; i++ )
++ *output++ = ( *input++ & 0xff ) ^ ( 1 << 7 );
++
++ return i;
++}
++
++static int convert_to_16_native_endian( struct xmms_convert_buffers* buf, void **data, int length )
++{
++ uint8_t *input = *data;
++ uint16_t *output;
++ int i;
++ *data = convert_get_buffer( &buf->format_buffer, length * 2 );
++ output = *data;
++ for ( i = 0; i < length; i++ )
++ *output++ = *input++ << 8;
++
++ return i * 2;
++}
++
++static int convert_to_16_native_endian_swap_sign( struct xmms_convert_buffers* buf, void **data, int length )
++{
++ uint8_t *input = *data;
++ uint16_t *output;
++ int i;
++ *data = convert_get_buffer( &buf->format_buffer, length * 2 );
++ output = *data;
++ for ( i = 0; i < length; i++ )
++ *output++ = ( *input++ << 8 ) ^ ( 1 << 15 );
++
++ return i * 2;
++}
++
++
++static int convert_to_16_alien_endian( struct xmms_convert_buffers* buf, void **data, int length )
++{
++ uint8_t *input = *data;
++ uint16_t *output;
++ int i;
++ *data = convert_get_buffer( &buf->format_buffer, length * 2 );
++ output = *data;
++ for ( i = 0; i < length; i++ )
++ *output++ = *input++;
++
++ return i * 2;
++}
++
++static int convert_to_16_alien_endian_swap_sign( struct xmms_convert_buffers* buf, void **data, int length )
++{
++ uint8_t *input = *data;
++ uint16_t *output;
++ int i;
++ *data = convert_get_buffer( &buf->format_buffer, length * 2 );
++ output = *data;
++ for ( i = 0; i < length; i++ )
++ *output++ = *input++ ^ ( 1 << 7 );
++
++ return i * 2;
++}
++
++static AFormat unnativize( AFormat fmt )
++{
++ if ( fmt == FMT_S16_NE )
++ {
++ if ( IS_BIG_ENDIAN )
++ return FMT_S16_BE;
++ else
++ return FMT_S16_LE;
++ }
++ if ( fmt == FMT_U16_NE )
++ {
++ if ( IS_BIG_ENDIAN )
++ return FMT_U16_BE;
++ else
++ return FMT_U16_LE;
++ }
++ return fmt;
++}
++
++convert_func_t xmms_convert_get_func( AFormat output, AFormat input )
++{
++ output = unnativize( output );
++ input = unnativize( input );
++
++ if ( output == input )
++ return NULL;
++
++ if ( ( output == FMT_U16_BE && input == FMT_U16_LE ) ||
++ ( output == FMT_U16_LE && input == FMT_U16_BE ) ||
++ ( output == FMT_S16_BE && input == FMT_S16_LE ) ||
++ ( output == FMT_S16_LE && input == FMT_S16_BE ) )
++ return convert_swap_endian;
++
++ if ( ( output == FMT_U16_BE && input == FMT_S16_BE ) ||
++ ( output == FMT_U16_LE && input == FMT_S16_LE ) ||
++ ( output == FMT_S16_BE && input == FMT_U16_BE ) ||
++ ( output == FMT_S16_LE && input == FMT_U16_LE ) )
++ return convert_swap_sign16;
++
++ if ( ( IS_BIG_ENDIAN &&
++ ( ( output == FMT_U16_BE && input == FMT_S16_LE ) ||
++ ( output == FMT_S16_BE && input == FMT_U16_LE ) ) ) ||
++ ( !IS_BIG_ENDIAN &&
++ ( ( output == FMT_U16_LE && input == FMT_S16_BE ) ||
++ ( output == FMT_S16_LE && input == FMT_U16_BE ) ) ) )
++ return convert_swap_sign_and_endian_to_native;
++
++ if ( ( !IS_BIG_ENDIAN &&
++ ( ( output == FMT_U16_BE && input == FMT_S16_LE ) ||
++ ( output == FMT_S16_BE && input == FMT_U16_LE ) ) ) ||
++ ( IS_BIG_ENDIAN &&
++ ( ( output == FMT_U16_LE && input == FMT_S16_BE ) ||
++ ( output == FMT_S16_LE && input == FMT_U16_BE ) ) ) )
++ return convert_swap_sign_and_endian_to_alien;
++
++ if ( ( IS_BIG_ENDIAN &&
++ ( ( output == FMT_U8 && input == FMT_U16_BE ) ||
++ ( output == FMT_S8 && input == FMT_S16_BE ) ) ) ||
++ ( !IS_BIG_ENDIAN &&
++ ( ( output == FMT_U8 && input == FMT_U16_LE ) ||
++ ( output == FMT_S8 && input == FMT_S16_LE ) ) ) )
++ return convert_to_8_native_endian;
++
++ if ( ( IS_BIG_ENDIAN &&
++ ( ( output == FMT_U8 && input == FMT_S16_BE ) ||
++ ( output == FMT_S8 && input == FMT_U16_BE ) ) ) ||
++ ( !IS_BIG_ENDIAN &&
++ ( ( output == FMT_U8 && input == FMT_S16_LE ) ||
++ ( output == FMT_S8 && input == FMT_U16_LE ) ) ) )
++ return convert_to_8_native_endian_swap_sign;
++
++ if ( ( !IS_BIG_ENDIAN &&
++ ( ( output == FMT_U8 && input == FMT_U16_BE ) ||
++ ( output == FMT_S8 && input == FMT_S16_BE ) ) ) ||
++ ( IS_BIG_ENDIAN &&
++ ( ( output == FMT_U8 && input == FMT_U16_LE ) ||
++ ( output == FMT_S8 && input == FMT_S16_LE ) ) ) )
++ return convert_to_8_alien_endian;
++
++ if ( ( !IS_BIG_ENDIAN &&
++ ( ( output == FMT_U8 && input == FMT_S16_BE ) ||
++ ( output == FMT_S8 && input == FMT_U16_BE ) ) ) ||
++ ( IS_BIG_ENDIAN &&
++ ( ( output == FMT_U8 && input == FMT_S16_LE ) ||
++ ( output == FMT_S8 && input == FMT_U16_LE ) ) ) )
++ return convert_to_8_alien_endian_swap_sign;
++
++ if ( ( output == FMT_U8 && input == FMT_S8 ) ||
++ ( output == FMT_S8 && input == FMT_U8 ) )
++ return convert_swap_sign8;
++
++ if ( ( IS_BIG_ENDIAN &&
++ ( ( output == FMT_U16_BE && input == FMT_U8 ) ||
++ ( output == FMT_S16_BE && input == FMT_S8 ) ) ) ||
++ ( !IS_BIG_ENDIAN &&
++ ( ( output == FMT_U16_LE && input == FMT_U8 ) ||
++ ( output == FMT_S16_LE && input == FMT_S8 ) ) ) )
++ return convert_to_16_native_endian;
++
++ if ( ( IS_BIG_ENDIAN &&
++ ( ( output == FMT_U16_BE && input == FMT_S8 ) ||
++ ( output == FMT_S16_BE && input == FMT_U8 ) ) ) ||
++ ( !IS_BIG_ENDIAN &&
++ ( ( output == FMT_U16_LE && input == FMT_S8 ) ||
++ ( output == FMT_S16_LE && input == FMT_U8 ) ) ) )
++ return convert_to_16_native_endian_swap_sign;
++
++ if ( ( !IS_BIG_ENDIAN &&
++ ( ( output == FMT_U16_BE && input == FMT_U8 ) ||
++ ( output == FMT_S16_BE && input == FMT_S8 ) ) ) ||
++ ( IS_BIG_ENDIAN &&
++ ( ( output == FMT_U16_LE && input == FMT_U8 ) ||
++ ( output == FMT_S16_LE && input == FMT_S8 ) ) ) )
++ return convert_to_16_alien_endian;
++
++ if ( ( !IS_BIG_ENDIAN &&
++ ( ( output == FMT_U16_BE && input == FMT_S8 ) ||
++ ( output == FMT_S16_BE && input == FMT_U8 ) ) ) ||
++ ( IS_BIG_ENDIAN &&
++ ( ( output == FMT_U16_LE && input == FMT_S8 ) ||
++ ( output == FMT_S16_LE && input == FMT_U8 ) ) ) )
++ return convert_to_16_alien_endian_swap_sign;
++
++ //g_warning( "Translation needed, but not available.\n"
++ // "Input: %d; Output %d.", input, output );
++ return NULL;
++}
++
++static int convert_mono_to_stereo( struct xmms_convert_buffers* buf, void **data, int length, int b16 )
++{
++ int i;
++ void *outbuf = convert_get_buffer( &buf->stereo_buffer, length * 2 );
++
++ if ( b16 )
++ {
++ uint16_t *output = outbuf, *input = *data;
++ for ( i = 0; i < length / 2; i++ )
++ {
++ *output++ = *input;
++ *output++ = *input;
++ input++;
++ }
++ }
++ else
++ {
++ uint8_t *output = outbuf, *input = *data;
++ for ( i = 0; i < length; i++ )
++ {
++ *output++ = *input;
++ *output++ = *input;
++ input++;
++ }
++ }
++ *data = outbuf;
++
++ return length * 2;
++}
++
++static int convert_mono_to_stereo_8( struct xmms_convert_buffers* buf, void **data, int length )
++{
++ return convert_mono_to_stereo( buf, data, length, FALSE );
++}
++
++static int convert_mono_to_stereo_16( struct xmms_convert_buffers* buf, void **data, int length )
++{
++ return convert_mono_to_stereo( buf, data, length, TRUE );
++}
++
++static int convert_stereo_to_mono_u8( struct xmms_convert_buffers* buf, void **data, int length )
++{
++ uint8_t *output = *data, *input = *data;
++ int i;
++ for ( i = 0; i < length / 2; i++ )
++ {
++ uint16_t tmp;
++ tmp = *input++;
++ tmp += *input++;
++ *output++ = tmp / 2;
++ }
++ return length / 2;
++}
++static int convert_stereo_to_mono_s8( struct xmms_convert_buffers* buf, void **data, int length )
++{
++ int8_t *output = *data, *input = *data;
++ int i;
++ for ( i = 0; i < length / 2; i++ )
++ {
++ int16_t tmp;
++ tmp = *input++;
++ tmp += *input++;
++ *output++ = tmp / 2;
++ }
++ return length / 2;
++}
++static int convert_stereo_to_mono_u16le( struct xmms_convert_buffers* buf, void **data, int length )
++{
++ uint16_t *output = *data, *input = *data;
++ int i;
++ for ( i = 0; i < length / 4; i++ )
++ {
++ uint32_t tmp;
++ uint16_t stmp;
++ tmp = GUINT16_FROM_LE( *input );
++ input++;
++ tmp += GUINT16_FROM_LE( *input );
++ input++;
++ stmp = tmp / 2;
++ *output++ = GUINT16_TO_LE( stmp );
++ }
++ return length / 2;
++}
++
++static int convert_stereo_to_mono_u16be( struct xmms_convert_buffers* buf, void **data, int length )
++{
++ uint16_t *output = *data, *input = *data;
++ int i;
++ for ( i = 0; i < length / 4; i++ )
++ {
++ uint32_t tmp;
++ uint16_t stmp;
++ tmp = GUINT16_FROM_BE( *input );
++ input++;
++ tmp += GUINT16_FROM_BE( *input );
++ input++;
++ stmp = tmp / 2;
++ *output++ = GUINT16_TO_BE( stmp );
++ }
++ return length / 2;
++}
++
++static int convert_stereo_to_mono_s16le( struct xmms_convert_buffers* buf, void **data, int length )
++{
++ int16_t *output = *data, *input = *data;
++ int i;
++ for ( i = 0; i < length / 4; i++ )
++ {
++ int32_t tmp;
++ int16_t stmp;
++ tmp = GINT16_FROM_LE( *input );
++ input++;
++ tmp += GINT16_FROM_LE( *input );
++ input++;
++ stmp = tmp / 2;
++ *output++ = GINT16_TO_LE( stmp );
++ }
++ return length / 2;
++}
++
++static int convert_stereo_to_mono_s16be( struct xmms_convert_buffers* buf, void **data, int length )
++{
++ int16_t *output = *data, *input = *data;
++ int i;
++ for ( i = 0; i < length / 4; i++ )
++ {
++ int32_t tmp;
++ int16_t stmp;
++ tmp = GINT16_FROM_BE( *input );
++ input++;
++ tmp += GINT16_FROM_BE( *input );
++ input++;
++ stmp = tmp / 2;
++ *output++ = GINT16_TO_BE( stmp );
++ }
++ return length / 2;
++}
++
++convert_channel_func_t xmms_convert_get_channel_func( AFormat fmt, int output, int input )
++{
++ fmt = unnativize( fmt );
++
++ if ( output == input )
++ return NULL;
++
++ if ( input == 1 && output == 2 )
++ switch ( fmt )
++ {
++ case FMT_U8:
++ case FMT_S8:
++ return convert_mono_to_stereo_8;
++ case FMT_U16_LE:
++ case FMT_U16_BE:
++ case FMT_S16_LE:
++ case FMT_S16_BE:
++ return convert_mono_to_stereo_16;
++ default:
++ //g_warning( "Unknown format: %d"
++ // "No conversion available.", fmt );
++ return NULL;
++ }
++ if ( input == 2 && output == 1 )
++ switch ( fmt )
++ {
++ case FMT_U8:
++ return convert_stereo_to_mono_u8;
++ case FMT_S8:
++ return convert_stereo_to_mono_s8;
++ case FMT_U16_LE:
++ return convert_stereo_to_mono_u16le;
++ case FMT_U16_BE:
++ return convert_stereo_to_mono_u16be;
++ case FMT_S16_LE:
++ return convert_stereo_to_mono_s16le;
++ case FMT_S16_BE:
++ return convert_stereo_to_mono_s16be;
++ default:
++ //g_warning( "Unknown format: %d. "
++ // "No conversion available.", fmt );
++ return NULL;
++ }
++
++ //g_warning( "Input has %d channels, soundcard uses %d channels\n"
++ // "No conversion is available", input, output );
++ return NULL;
++}
++
++
++#define RESAMPLE_STEREO( sample_type, bswap ) \
++do { \
++ const int shift = sizeof ( sample_type ); \
++ int i, in_samples, out_samples, x, delta; \
++ sample_type *inptr = *data, *outptr; \
++ uint nlen = ( ( ( length >> shift ) * ofreq ) / ifreq ); \
++ void *nbuf; \
++ if ( nlen == 0 ) \
++ break; \
++ nlen <<= shift; \
++ if ( bswap ) \
++ convert_swap_endian( NULL, data, length ); \
++ nbuf = convert_get_buffer( &buf->freq_buffer, nlen ); \
++ outptr = nbuf; \
++ in_samples = length >> shift; \
++ out_samples = nlen >> shift; \
++ delta = ( in_samples << 12 ) / out_samples; \
++ for ( x = 0, i = 0; i < out_samples; i++ ) \
++ { \
++ int x1, frac; \
++ x1 = ( x >> 12 ) << 12; \
++ frac = x - x1; \
++ *outptr++ = \
++ ( ( inptr[( x1 >> 12 ) << 1] * \
++ ( ( 1<<12 ) - frac ) + \
++ inptr[( ( x1 >> 12 ) + 1 ) << 1] * \
++ frac ) >> 12 ); \
++ *outptr++ = \
++ ( ( inptr[( ( x1 >> 12 ) << 1 ) + 1] * \
++ ( ( 1<<12 ) - frac ) + \
++ inptr[( ( ( x1 >> 12 ) + 1 ) << 1 ) + 1] * \
++ frac ) >> 12 ); \
++ x += delta; \
++ } \
++ if ( bswap ) \
++ convert_swap_endian( NULL, &nbuf, nlen ); \
++ *data = nbuf; \
++ return nlen; \
++} while ( 0 )
++
++
++#define RESAMPLE_MONO( sample_type, bswap ) \
++do { \
++ const int shift = sizeof ( sample_type ) - 1; \
++ int i, x, delta, in_samples, out_samples; \
++ sample_type *inptr = *data, *outptr; \
++ uint nlen = ( ( ( length >> shift ) * ofreq ) / ifreq ); \
++ void *nbuf; \
++ if ( nlen == 0 ) \
++ break; \
++ nlen <<= shift; \
++ if ( bswap ) \
++ convert_swap_endian( NULL, data, length ); \
++ nbuf = convert_get_buffer( &buf->freq_buffer, nlen ); \
++ outptr = nbuf; \
++ in_samples = length >> shift; \
++ out_samples = nlen >> shift; \
++ delta = ( ( length >> shift ) << 12 ) / out_samples; \
++ for ( x = 0, i = 0; i < out_samples; i++ ) \
++ { \
++ int x1, frac; \
++ x1 = ( x >> 12 ) << 12; \
++ frac = x - x1; \
++ *outptr++ = \
++ ( ( inptr[x1 >> 12] * ( ( 1<<12 ) - frac ) + \
++ inptr[( x1 >> 12 ) + 1] * frac ) >> 12 ); \
++ x += delta; \
++ } \
++ if ( bswap ) \
++ convert_swap_endian( NULL, &nbuf, nlen ); \
++ *data = nbuf; \
++ return nlen; \
++} while ( 0 )
++
++static int convert_resample_stereo_s16ne( struct xmms_convert_buffers* buf, void **data, int length, int ifreq, int ofreq )
++{
++ RESAMPLE_STEREO( int16_t, FALSE );
++ return 0;
++}
++
++static int convert_resample_stereo_s16ae( struct xmms_convert_buffers* buf, void **data, int length, int ifreq, int ofreq )
++{
++ RESAMPLE_STEREO( int16_t, TRUE );
++ return 0;
++}
++
++static int convert_resample_stereo_u16ne( struct xmms_convert_buffers* buf, void **data, int length, int ifreq, int ofreq )
++{
++ RESAMPLE_STEREO( uint16_t, FALSE );
++ return 0;
++}
++
++static int convert_resample_stereo_u16ae( struct xmms_convert_buffers* buf, void **data, int length, int ifreq, int ofreq )
++{
++ RESAMPLE_STEREO( uint16_t, TRUE );
++ return 0;
++}
++
++static int convert_resample_mono_s16ne( struct xmms_convert_buffers* buf, void **data, int length, int ifreq, int ofreq )
++{
++ RESAMPLE_MONO( int16_t, FALSE );
++ return 0;
++}
++
++static int convert_resample_mono_s16ae( struct xmms_convert_buffers* buf, void **data, int length, int ifreq, int ofreq )
++{
++ RESAMPLE_MONO( int16_t, TRUE );
++ return 0;
++}
++
++static int convert_resample_mono_u16ne( struct xmms_convert_buffers* buf, void **data, int length, int ifreq, int ofreq )
++{
++ RESAMPLE_MONO( uint16_t, FALSE );
++ return 0;
++}
++
++static int convert_resample_mono_u16ae( struct xmms_convert_buffers* buf, void **data, int length, int ifreq, int ofreq )
++{
++ RESAMPLE_MONO( uint16_t, TRUE );
++ return 0;
++}
++
++static int convert_resample_stereo_u8( struct xmms_convert_buffers* buf, void **data, int length, int ifreq, int ofreq )
++{
++ RESAMPLE_STEREO( uint8_t, FALSE );
++ return 0;
++}
++
++static int convert_resample_mono_u8( struct xmms_convert_buffers* buf, void **data, int length, int ifreq, int ofreq )
++{
++ RESAMPLE_MONO( uint8_t, FALSE );
++ return 0;
++}
++
++static int convert_resample_stereo_s8( struct xmms_convert_buffers* buf, void **data, int length, int ifreq, int ofreq )
++{
++ RESAMPLE_STEREO( int8_t, FALSE );
++ return 0;
++}
++
++static int convert_resample_mono_s8( struct xmms_convert_buffers* buf, void **data, int length, int ifreq, int ofreq )
++{
++ RESAMPLE_MONO( int8_t, FALSE );
++ return 0;
++}
++
++
++convert_freq_func_t xmms_convert_get_frequency_func( AFormat fmt, int channels )
++{
++ fmt = unnativize( fmt );
++ //g_message( "fmt %d, channels: %d", fmt, channels );
++
++ if ( channels < 1 || channels > 2 )
++ {
++ //g_warning( "Unsupported number of channels: %d. "
++ // "Resample function not available", channels );
++ return NULL;
++ }
++ if ( ( IS_BIG_ENDIAN && fmt == FMT_U16_BE ) ||
++ ( !IS_BIG_ENDIAN && fmt == FMT_U16_LE ) )
++ {
++ if ( channels == 1 )
++ return convert_resample_mono_u16ne;
++ else
++ return convert_resample_stereo_u16ne;
++ }
++ if ( ( IS_BIG_ENDIAN && fmt == FMT_S16_BE ) ||
++ ( !IS_BIG_ENDIAN && fmt == FMT_S16_LE ) )
++ {
++ if ( channels == 1 )
++ return convert_resample_mono_s16ne;
++ else
++ return convert_resample_stereo_s16ne;
++ }
++ if ( ( !IS_BIG_ENDIAN && fmt == FMT_U16_BE ) ||
++ ( IS_BIG_ENDIAN && fmt == FMT_U16_LE ) )
++ {
++ if ( channels == 1 )
++ return convert_resample_mono_u16ae;
++ else
++ return convert_resample_stereo_u16ae;
++ }
++ if ( ( !IS_BIG_ENDIAN && fmt == FMT_S16_BE ) ||
++ ( IS_BIG_ENDIAN && fmt == FMT_S16_LE ) )
++ {
++ if ( channels == 1 )
++ return convert_resample_mono_s16ae;
++ else
++ return convert_resample_stereo_s16ae;
++ }
++ if ( fmt == FMT_U8 )
++ {
++ if ( channels == 1 )
++ return convert_resample_mono_u8;
++ else
++ return convert_resample_stereo_u8;
++ }
++ if ( fmt == FMT_S8 )
++ {
++ if ( channels == 1 )
++ return convert_resample_mono_s8;
++ else
++ return convert_resample_stereo_s8;
++ }
++ //g_warning( "Resample function not available"
++ // "Format %d.", fmt );
++ return NULL;
++}
+diff -urN last.fm-1.1.3.orig/src/alsaplayback/xconvert.h last.fm-1.1.3/src/alsaplayback/xconvert.h
+--- last.fm-1.1.3.orig/src/alsaplayback/xconvert.h 1969-12-31 16:00:00.000000000 -0800
++++ last.fm-1.1.3/src/alsaplayback/xconvert.h 2007-03-18 20:57:28.000000000 -0700
+@@ -0,0 +1,39 @@
++/*
++ * Copyright (C) 2003 Haavard Kvaalen <havardk@xmms.org>
++ *
++ * Licensed under GNU LGPL version 2.
++ */
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++typedef enum
++{
++ FMT_U8, FMT_S8, FMT_U16_LE, FMT_U16_BE, FMT_U16_NE, FMT_S16_LE, FMT_S16_BE, FMT_S16_NE
++}
++AFormat;
++
++struct xmms_convert_buffers;
++
++struct xmms_convert_buffers* xmms_convert_buffers_new(void);
++/*
++ * Free the data assosiated with the buffers, without destroying the
++ * context. The context can be reused.
++ */
++void xmms_convert_buffers_free(struct xmms_convert_buffers* buf);
++void xmms_convert_buffers_destroy(struct xmms_convert_buffers* buf);
++
++
++typedef int (*convert_func_t)(struct xmms_convert_buffers* buf, void **data, int length);
++typedef int (*convert_channel_func_t)(struct xmms_convert_buffers* buf, void **data, int length);
++typedef int (*convert_freq_func_t)(struct xmms_convert_buffers* buf, void **data, int length, int ifreq, int ofreq);
++
++
++convert_func_t xmms_convert_get_func(AFormat output, AFormat input);
++convert_channel_func_t xmms_convert_get_channel_func(AFormat fmt, int output, int input);
++convert_freq_func_t xmms_convert_get_frequency_func(AFormat fmt, int channels);
++
++#ifdef __cplusplus
++}
++#endif
+diff -urN last.fm-1.1.3.orig/src/container.cpp last.fm-1.1.3/src/container.cpp
+--- last.fm-1.1.3.orig/src/container.cpp 2007-03-18 20:57:23.000000000 -0700
++++ last.fm-1.1.3/src/container.cpp 2007-03-18 20:57:28.000000000 -0700
+@@ -517,6 +517,7 @@
+ connect( m_iInput, SIGNAL( newData( QByteArray ) ), m_iTranscode, SLOT( dataAvailable( QByteArray ) ) );
+ connect( m_iTranscode, SIGNAL( newData( QByteArray ) ), m_iPlayback, SLOT( dataAvailable( QByteArray ) ) );
+ connect( m_iPlayback, SIGNAL( needData() ), m_iInput, SLOT( requestData() ) );
++ connect( m_iPlayback, SIGNAL( stop() ), m_iInput, SLOT( stopStreaming() ) );
+
+ // Auto updater
+ connect( &m_updater, SIGNAL( updateCheckDone( bool, bool, QString ) ),
+diff -urN last.fm-1.1.3.orig/src/libLastFMTools/containerutils.cpp last.fm-1.1.3/src/libLastFMTools/containerutils.cpp
+--- last.fm-1.1.3.orig/src/libLastFMTools/containerutils.cpp 2007-03-18 20:57:23.000000000 -0700
++++ last.fm-1.1.3/src/libLastFMTools/containerutils.cpp 2007-03-18 20:57:28.000000000 -0700
+@@ -288,7 +288,7 @@
+ {
+ if ( g_playbackService == NULL )
+ {\r
+- QObject* obj = loadService( "playback_rtaudio" );\r
++ QObject* obj = loadService( "playback_alsaaudio" );\r
+ g_playbackService = qobject_cast<PlaybackInterface*>( obj );\r
+ }
+