Logo Search packages:      
Sourcecode: lastfm version File versions  Download package

playback.cpp

/***************************************************************************
 *   Copyright (C) 2005 - 2006 by Christian Muehlhaeuser, Last.fm Ltd.     *
 *   chris@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 <QHttp>
#include <QUrl>
#include <QMutex>
#include <QTcpSocket>
#include <QTcpServer>
#include <QMessageBox>

#include <unistd.h>

#include "rtaudio/RtAudio.h"
#include "libsamplerate/samplerate.h"

#include "playback.h"
#include "playlist.h"
#include "settings.h"
#include "song.h"
#include "webserviceconnector.h"

#define BUFFER 32768
#define PLAYBACKBUFFER 2048


QMutex mutex;
struct mpstr_tag mpeg;
SRC_STATE *src_state = 0;
SRC_DATA src_data;

Playback *Playback::s_instance = 0;
QByteArray *Playback::m_waveBuffer = new QByteArray();

void
WaveThread::run()
{
    char tempBuf[16384];
    int size;

    while ( Playback::instance()->isPlaying() )
    {
        mutex.lock();
        Playback::instance()->m_waveBuffer->reserve( PLAYBACKBUFFER * 2 );

        if ( Playback::instance()->m_mp3Buffer->size() >= PLAYBACKBUFFER && Playback::instance()->m_waveBuffer->size() < 32768 )
        {
            QByteArray *in = new QByteArray( Playback::instance()->m_mp3Buffer->left( PLAYBACKBUFFER ) );
            Playback::instance()->m_mp3Buffer->remove( 0, PLAYBACKBUFFER );
            mutex.unlock();

            int result = decodeMP3( &mpeg, (unsigned char*)in->data(), PLAYBACKBUFFER, tempBuf, sizeof( tempBuf ), &size );

            while ( result == MP3_OK )
            {
                if ( src_state && src_data.src_ratio != 1.0 )
                {
                    float input[size];
                    float output[size];

                    memset( input, 0, sizeof( input ) );
                    memset( output, 0, sizeof( output ) );

                    src_data.end_of_input = 0;
                    src_data.input_frames = size / 4;
                    src_data.output_frames = size / 2;
                    src_data.data_in = input;
                    src_data.data_out = output;

                    src_short_to_float_array( (short *)tempBuf, input, size / 2 );
                    memset( tempBuf, 0, sizeof( tempBuf ) );

                    src_process( src_state, &src_data );
                    src_float_to_short_array( output, (short *)tempBuf, src_data.output_frames_gen * 2 );

                    size = src_data.output_frames_gen * 4;
                }

                float scale = (float)Playback::instance()->volume() / 100;
                mutex.lock();
                // Append it the binary-safe way!
                for ( int i = 0; i < ( size / 2 ); i++ )
                {
                    union PCMDATA
                    {
                        short i;
                        unsigned char b[2];
                    } pcmData;

                    pcmData.b[0] = tempBuf[i * 2];
                    pcmData.b[1] = tempBuf[i * 2 + 1];

                    float pcmValue = (float)pcmData.i * scale;
                    pcmData.i = (short)pcmValue;

                    Playback::instance()->m_waveBuffer->append( pcmData.b[0] );
                    Playback::instance()->m_waveBuffer->append( pcmData.b[1] );
                }
                mutex.unlock();

                result = decodeMP3( &mpeg, NULL, 0, tempBuf, sizeof( tempBuf ), &size );
            }

            delete in;

        }
        else
        {
            mutex.unlock();
            this->msleep( 10 );
        }
    }

    exit();
}


Playback::Playback()
    : playingSong( new Song() )
    , m_playlist( 0 )
    , m_audio( 0 )
    , m_playing( false )
{
    s_instance = this;

    InitMP3( &mpeg );
    m_mp3Buffer = new QByteArray();
    m_mp3Buffer->reserve( BUFFER * 2 );

    m_http = new QHttp( this );
    connect( WebserviceConnector::instance(), SIGNAL( playlistResult( Playlist ) ), this, SLOT( playlistResult( Playlist ) ) );
    connect( m_http, SIGNAL( readyRead( QHttpResponseHeader ) ), this, SLOT( dataAvailable( QHttpResponseHeader ) ) );
    connect( m_http, SIGNAL( responseHeaderReceived( QHttpResponseHeader ) ), this, SLOT( responseHeaderReceived( QHttpResponseHeader ) ) );
    connect( m_http, SIGNAL( stateChanged( int ) ), this, SLOT( stateChanged( int ) ) );

    waveThread = new WaveThread();
}


Playback::~Playback()
{
    stopPlayback();
    qDebug() << "Deinit playback";

    if ( Settings::instance()->soundSystem() == Settings::instance()->externalSoundSystem() )
    {
        proxyServerSocket->close();
        delete proxyServerSocket;
        proxyServerSocket = 0;
    }

    delete m_http;
    delete m_mp3Buffer;
    delete m_waveBuffer;
}


bool
Playback::startPlayback( QUrl url )
{
    if ( !initSound() )
        return false;

    mutex.lock();
    m_mp3Buffer->truncate( 0 );
    mutex.unlock();

    if ( Settings::instance()->soundSystem() != Settings::instance()->externalSoundSystem() )
        m_audio->startStream();

    if ( Settings::instance()->proxyUsage() && !Settings::instance()->proxyHost().isEmpty() )
        m_http->setProxy( Settings::instance()->proxyHost(), Settings::instance()->proxyPort(), Settings::instance()->proxyUsername(), Settings::instance()->proxyPassword() );

    m_http->setHost( url.host(), url.port() > 0 ? url.port() : 80 );
    if ( !url.encodedQuery().isEmpty() )
        m_http->get( url.path() + "?" + QString( url.encodedQuery() ) );
    else
        m_http->get( url.path() );

    return true;
}


void
Playback::stopPlayback()
{
    if ( !isPlaying() )
        return;

    m_playing = false;
    playingSong->clear();
    qDebug( "Stopping playback!" );

    mutex.lock();
    m_http->abort();

    if ( waveThread->isRunning() )
        waveThread->wait( 200 );

    m_mp3Buffer->truncate( 0 );
    m_waveBuffer->truncate( 0 );
    mutex.unlock();

    if ( src_state != 0 )
    {
        src_delete( src_state );
        src_state = 0;
    }

    qDebug() << "Deinit sockets";
    if ( Settings::instance()->soundSystem() == Settings::instance()->externalSoundSystem() )
    {
        if ( proxySocketList.count() > 0 )
        {
            for ( int i = 0; i < proxySocketList.count(); i++ )
            {
                if ( proxySocketList.at( i )->state() == QAbstractSocket::ConnectedState )
                {
                    proxySocketList.at( i )->flush();
                    proxySocketList.at( i )->close();
                }

                delete proxySocketList.at( i );
            }
        }

        proxySocketList.clear();
        qDebug() << "Proxy clients left:" << proxySocketList.count();
    }
    else
    {
        if ( m_audio != 0 )
        {
            m_audio->stopStream();
            m_audio->closeStream();

            delete m_audio;
            m_audio = 0;
        }
    }
}


bool
Playback::initSound()
{
    if ( m_audio != 0 )
    {
        m_audio->stopStream();
        m_audio->closeStream();
        delete m_audio;
        m_audio = 0;
    }
    if ( src_state != 0 )
    {
        src_delete( src_state );
        src_state = 0;
    }

    if ( Settings::instance()->soundSystem() == Settings::instance()->externalSoundSystem() )
    {
        proxyServerSocket = new QTcpServer( this );
        connect( proxyServerSocket, SIGNAL( newConnection() ), this, SLOT( proxyClientConnect() ) );
        proxyServerSocket->listen( QHostAddress( "127.0.0.1" ), 32214 );

        return true;
    }

    char data[2];
    int channels = 2;
    int nBuffers = 8;
    int bufferSize = 512;
    int sampleRate = -1;
    try
    {
        RtAudio::RtAudioApi api = RtAudio::UNSPECIFIED;
        RtAudioFormat format = RTAUDIO_SINT16;

        #ifdef Q_WS_X11
        switch ( Settings::instance()->soundSystem() )
        {
            case 0:
                qDebug( "Using ALSA!" );
                api = RtAudio::LINUX_ALSA;
                break;

            case 1:
                qDebug( "Using OSS!" );
                api = RtAudio::LINUX_OSS;
                break;
        }
        #endif

        int delta = 1;
        #ifdef WIN32
        delta = 2;
        #endif

        m_audio = new RtAudio();
        RtAudioDeviceInfo info = m_audio->getDeviceInfo( Settings::instance()->soundCard() + delta );
        if ( info.nativeFormats & RTAUDIO_SINT32 )
            format = RTAUDIO_SINT32;
        if ( info.nativeFormats & RTAUDIO_SINT24 )
            format = RTAUDIO_SINT24;
        if ( info.nativeFormats & RTAUDIO_SINT16 )
            format = RTAUDIO_SINT16;

        for ( uint j = 0; j < info.sampleRates.size(); j++ )
        {
            if ( abs( info.sampleRates.at( j ) - 44100 ) < abs( sampleRate - 44100 ) )
                sampleRate = info.sampleRates.at( j );
        }

        delete m_audio;
        m_audio = new RtAudio( Settings::instance()->soundCard(), channels, 0, 0, format,
                             sampleRate, &bufferSize, nBuffers, api );

        if ( (double)sampleRate / 44100.0 != 1.0 )
        {
            int error;
            src_state = src_new( SRC_SINC_BEST_QUALITY, 2, &error );

            src_data.end_of_input = 0;
            src_data.input_frames = 0;
            src_data.output_frames = 0;
            src_data.src_ratio = (double)sampleRate / 44100.0;
        }
    }
    catch ( RtError &error )
    {
        error.printMessage();
        delete m_audio;
        m_audio = 0;

        return false;
    }

    m_audio->setStreamCallback( &audioCallback, (void *)data );
    if ( isPlaying() )
        m_audio->startStream();

    return true;
}


int
Playback::audioCallback( char *buffer, int bufferSize, void *data )
{
    int bufs = bufferSize * 4;
    memset( buffer, 0, bufs );

    if ( m_waveBuffer->size() > bufs && Playback::instance()->isPlaying() )
    {
        mutex.lock();
        memcpy( buffer, m_waveBuffer->data(), bufs );
        m_waveBuffer->remove( 0, bufs );
        mutex.unlock();
    }

    return 0;
}


void
Playback::playlistResult( const Playlist& playlist )
{
    if ( m_playlist )
        delete m_playlist;
    m_playlist = new Playlist();

    for ( int i = 0; i < playlist.count(); i++ )
    {
        QPair<QPair<QString, QString>, QString> track = playlist.trackAt( i );
        m_playlist->appendTrack( track.first.first, track.second, track.first.second );
    }
}


void
Playback::dataAvailable( const QHttpResponseHeader &resp )
{
    char inBuf[8192];

    if ( resp.statusCode() > 400 )
        showErrorCode( resp.statusCode() );

    if ( !m_directSkip && ( m_mp3Buffer->size() >= BUFFER * 2 ) )
        return;

    memset( inBuf, 0, sizeof( inBuf ) );
    int len = m_http->read( inBuf, sizeof( inBuf ) );

    if ( strstr( inBuf, "HTTP/1.0 " ) != NULL )
    {
        if ( strstr( inBuf, "HTTP/1.0 401" ) != NULL ) showErrorCode( 401 );
        if ( strstr( inBuf, "HTTP/1.0 503" ) != NULL ) showErrorCode( 503 );
        if ( strstr( inBuf, "HTTP/1.0 666" ) != NULL ) showErrorCode( 666 );
        if ( strstr( inBuf, "HTTP/1.0 667" ) != NULL ) showErrorCode( 667 );
    }

    // Append it the binary-safe way!
    mutex.lock();
    for ( int i = 0; i < len; i++ )
        m_mp3Buffer->append( inBuf[ i ] );

    if ( m_mp3Buffer->indexOf( "SYNC" ) >= 0 )
    {
        WebserviceConnector::instance()->metaData();

        if ( m_directSkip )
        {
            m_directSkip = false;
            if ( m_mp3Buffer->size() > BUFFER )
                m_mp3Buffer->remove( BUFFER, m_mp3Buffer->size() );
        }

        m_mp3Buffer->remove( m_mp3Buffer->indexOf( "SYNC" ), 4 );
    }

    if ( Settings::instance()->soundSystem() == Settings::instance()->externalSoundSystem() )
    {
        if ( proxySocketList.count() > 0 )
            for ( int i = 0; i < proxySocketList.count(); i++ )
            {
                if ( proxySocketList.at( i )->state() == QAbstractSocket::ConnectedState )
                    proxySocketList.at( i )->write( m_mp3Buffer->data(), m_mp3Buffer->size() );
            }

        m_mp3Buffer->truncate( 0 );
    }

    if ( ( ( m_mp3Buffer->size() + len ) < PLAYBACKBUFFER ) && waveThread->isRunning() )
        qDebug( "Buffer empty!" );

    if ( !waveThread->isRunning() && m_mp3Buffer->size() >= BUFFER )
        waveThread->start( QThread::TimeCriticalPriority );

    mutex.unlock();
}


void
Playback::stateChanged( int state )
{
    if ( !m_playing && state == QHttp::Reading )
    {
        m_playing = true;
        emit playbackStarting();
    }

    if ( state == QHttp::Unconnected )
    {
        stopPlayback();
        emit playbackFinished();
    }
}


void
Playback::responseHeaderReceived( const QHttpResponseHeader &resp )
{
    if ( resp.statusCode() == 503 || resp.statusCode() == 401 || resp.statusCode() == 666 )
        showErrorCode( resp.statusCode() );
}


void
Playback::showErrorCode( int error )
{
    switch( error )
    {
        case 401:
            QMessageBox::information( 0, "Attention", "Your session is invalid! Please login, again!\n" );
            break;

        case 503:
            QMessageBox::information( 0, "Attention", "No more streaming slots available. Try again in a few minutes!\n" );
            break;

        case 666:
            QMessageBox::information( 0, "Attention", "Streamer is shutting down for maintenance! Try again in a few minutes!\n" );
            break;

        case 667:
            QMessageBox::information( 0, "Error", "There is not enough content left to play this station.\n" );
            break;
    }
}


void
Playback::proxyClientConnect()
{
    qDebug() << "New proxy client connected!";

    QTcpSocket *socket = proxyServerSocket->nextPendingConnection();
    connect( socket, SIGNAL( disconnected() ), this, SLOT( proxyClientDisconnect() ) );

    // welcome the new client
    QByteArray token( "HTTP/1.1 200 OK\n\n" );
    socket->write( token, token.length() );

    proxySocketList.append( socket );
    if ( proxySocketList.count() == 1 )
        emit proxyClientConnected();

    qDebug() << "Proxy clients atm:" << proxySocketList.count();
}


void
Playback::proxyClientDisconnect()
{
    qDebug() << "Proxy client disconnected!";

    #ifdef WIN32
    Sleep( 500 );
    #endif
    #ifndef WIN32
    usleep( 500 );
    #endif

    QApplication::processEvents();

    for ( int i = 0; i < proxySocketList.count(); i++ )
    {
        if ( proxySocketList.at( i )->state() == QAbstractSocket::ConnectedState )
        {
            return;
        }
    }

    stopPlayback();
    emit proxyClientDisconnected();
}

Generated by  Doxygen 1.6.0   Back to index