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 * + * 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 + +#include + +#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 +#include +#include +#include +#include +#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 _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 * + * Erik Jaelevik, Last.fm Ltd * + * * + * 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 + +#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 * + * Erik Jaelevik, Last.fm Ltd * + * * + * 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 +#include "alsaaudio.h" + +#include +#include + +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 @@ +/*************************************************************************** + * Copyright (C) 2005 - 2006 by * + * Christian Muehlhaeuser, Last.fm Ltd * + * Erik Jaelevik, Last.fm Ltd * + * * + * 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. * + ***************************************************************************/ + +/* Add C includes here */ + +#if defined __cplusplus + /* Add C++ includes here */ + #include + #include + #include + #include +#endif // __cplusplus 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 + * + * Licensed under GNU LGPL version 2. + */ + +#include +#include +#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 + * + * 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 ) { - QObject* obj = loadService( "playback_rtaudio" ); + QObject* obj = loadService( "playback_alsaaudio" ); g_playbackService = qobject_cast( obj ); }