From f25a3715dae61916512c91d7ccd9200a4ef08c17 Mon Sep 17 00:00:00 2001 From: dridri Date: Mon, 29 Sep 2014 23:22:39 +0200 Subject: [PATCH] first draft to get rid of phonon and directly use LibVLC --- CMakeLists.txt | 19 +- CMakeModules/FindLIBVLC.cmake | 93 ++++++ src/libtomahawk/CMakeLists.txt | 4 + src/libtomahawk/audio/AudioEngine.cpp | 183 ++++------- src/libtomahawk/audio/AudioEngine.h | 6 +- src/libtomahawk/audio/AudioEngine_p.h | 12 +- src/libtomahawk/audio/AudioOutput.cpp | 320 +++++++++++++++++++ src/libtomahawk/audio/AudioOutput.h | 87 +++++ src/libtomahawk/utils/MediaStream.cpp | 128 ++++++++ src/libtomahawk/utils/MediaStream.h | 63 ++++ src/libtomahawk/utils/Qnr_IoDeviceStream.cpp | 57 ++-- src/libtomahawk/utils/Qnr_IoDeviceStream.h | 12 +- 12 files changed, 802 insertions(+), 182 deletions(-) create mode 100644 CMakeModules/FindLIBVLC.cmake create mode 100644 src/libtomahawk/audio/AudioOutput.cpp create mode 100644 src/libtomahawk/audio/AudioOutput.h create mode 100644 src/libtomahawk/utils/MediaStream.cpp create mode 100644 src/libtomahawk/utils/MediaStream.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c8154c256..99f3111d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -346,6 +346,9 @@ endif() macro_log_feature(LIBSNORE_FOUND "Libsnore" "Library for notifications" "https://github.com/TheOneRing/Snorenotify" FALSE "" "") endif() +find_package(LIBVLC REQUIRED) +macro_log_feature(LIBVLC_FOUND "LibVLC" "Provides audio output" TRUE "" "") + set(QXTWEB_FOUND TRUE) set(QXTWEB_LIBRARIES qxtweb-standalone) set(QXTWEB_INCLUDE_DIRS ${THIRDPARTY_DIR}/qxt/qxtweb-standalone/web ${THIRDPARTY_DIR}/qxt/qxtweb-standalone/network ${THIRDPARTY_DIR}/qxt/qxtweb-standalone/core ${CMAKE_CURRENT_BINARY_DIR}) @@ -377,15 +380,15 @@ if (WITH_KDE4) endif(WITH_KDE4) macro_log_feature(KDE4_FOUND "KDE4" "Provides support for configuring Telepathy Accounts from inside Tomahawk" "https://www.kde.org" FALSE "" "") -if(NOT Phonon_FOUND) - macro_optional_find_package(Phonon 4.5.0) -endif() -macro_log_feature(Phonon_FOUND "Phonon" "The Phonon multimedia library" "http://phonon.kde.org" TRUE "" "") - -if(Phonon_FOUND) - message(STATUS "Phonon found; ensure that phonon-vlc is at least 0.4") -endif() +# if(NOT Phonon_FOUND) +# macro_optional_find_package(Phonon 4.5.0) +# endif() +# macro_log_feature(Phonon_FOUND "Phonon" "The Phonon multimedia library" "http://phonon.kde.org" TRUE "" "") +# if(Phonon_FOUND) +# message(STATUS "Phonon found; ensure that phonon-vlc is at least 0.4") +# endif() +# IF( KDE4_FOUND OR Phonon_FOUND ) IF( CMAKE_C_FLAGS ) # KDE4 adds and removes some compiler flags that we don't like diff --git a/CMakeModules/FindLIBVLC.cmake b/CMakeModules/FindLIBVLC.cmake new file mode 100644 index 000000000..2a31e9128 --- /dev/null +++ b/CMakeModules/FindLIBVLC.cmake @@ -0,0 +1,93 @@ + +# CMake module to search for LIBVLC (VLC library) +# Authors: Rohit Yadav +# Harald Sitter +# +# If it's found it sets LIBVLC_FOUND to TRUE +# and following variables are set: +# LIBVLC_INCLUDE_DIR +# LIBVLC_LIBRARY +# LIBVLC_VERSION + +if(NOT LIBVLC_MIN_VERSION) + set(LIBVLC_MIN_VERSION "0.0") +endif(NOT LIBVLC_MIN_VERSION) + +# find_path and find_library normally search standard locations +# before the specified paths. To search non-standard paths first, +# FIND_* is invoked first with specified paths and NO_DEFAULT_PATH +# and then again with no specified paths to search the default +# locations. When an earlier FIND_* succeeds, subsequent FIND_*s +# searching for the same item do nothing. + +if (NOT WIN32) + find_package(PkgConfig) + pkg_check_modules(PC_LIBVLC libvlc) + set(LIBVLC_DEFINITIONS ${PC_LIBVLC_CFLAGS_OTHER}) +endif (NOT WIN32) + +#Put here path to custom location +#example: /home/user/vlc/include etc.. +find_path(LIBVLC_INCLUDE_DIR vlc/vlc.h +HINTS "$ENV{LIBVLC_INCLUDE_PATH}" +PATHS + "$ENV{LIB_DIR}/include" + "$ENV{LIB_DIR}/include/vlc" + "/usr/include" + "/usr/include/vlc" + "/usr/local/include" + "/usr/local/include/vlc" + #mingw + c:/msys/local/include +) +find_path(LIBVLC_INCLUDE_DIR PATHS "${CMAKE_INCLUDE_PATH}/vlc" NAMES vlc.h + HINTS ${PC_LIBVLC_INCLUDEDIR} ${PC_LIBVLC_INCLUDE_DIRS}) + +#Put here path to custom location +#example: /home/user/vlc/lib etc.. +find_library(LIBVLC_LIBRARY NAMES vlc libvlc +HINTS "$ENV{LIBVLC_LIBRARY_PATH}" ${PC_LIBVLC_LIBDIR} ${PC_LIBVLC_LIBRARY_DIRS} +PATHS + "$ENV{LIB_DIR}/lib" + #mingw + c:/msys/local/lib +) +find_library(LIBVLC_LIBRARY NAMES vlc libvlc) +find_library(LIBVLCCORE_LIBRARY NAMES vlccore libvlccore +HINTS "$ENV{LIBVLC_LIBRARY_PATH}" ${PC_LIBVLC_LIBDIR} ${PC_LIBVLC_LIBRARY_DIRS} +PATHS + "$ENV{LIB_DIR}/lib" + #mingw + c:/msys/local/lib +) +find_library(LIBVLCCORE_LIBRARY NAMES vlccore libvlccore) + +set(LIBVLC_VERSION ${PC_LIBVLC_VERSION}) +if (NOT LIBVLC_VERSION) +# TODO: implement means to detect version on windows (vlc --version && regex? ... ultimately we would get it from a header though...) +endif (NOT LIBVLC_VERSION) + +if (LIBVLC_INCLUDE_DIR AND LIBVLC_LIBRARY AND LIBVLCCORE_LIBRARY) +set(LIBVLC_FOUND TRUE) +endif (LIBVLC_INCLUDE_DIR AND LIBVLC_LIBRARY AND LIBVLCCORE_LIBRARY) + +if (LIBVLC_VERSION STRLESS "${LIBVLC_MIN_VERSION}") + message(WARNING "LibVLC version not found: version searched: ${LIBVLC_MIN_VERSION}, found ${LIBVLC_VERSION}\nUnless you are on Windows this is bound to fail.") +# TODO: only activate once version detection can be garunteed (which is currently not the case on windows) +# set(LIBVLC_FOUND FALSE) +endif (LIBVLC_VERSION STRLESS "${LIBVLC_MIN_VERSION}") + +if (LIBVLC_FOUND) + if (NOT LIBVLC_FIND_QUIETLY) + message(STATUS "Found LibVLC include-dir path: ${LIBVLC_INCLUDE_DIR}") + message(STATUS "Found LibVLC library path:${LIBVLC_LIBRARY}") + message(STATUS "Found LibVLCcore library path:${LIBVLCCORE_LIBRARY}") + message(STATUS "Found LibVLC version: ${LIBVLC_VERSION} (searched for: ${LIBVLC_MIN_VERSION})") + endif (NOT LIBVLC_FIND_QUIETLY) +else (LIBVLC_FOUND) + if (LIBVLC_FIND_REQUIRED) + message(FATAL_ERROR "Could not find LibVLC") + endif (LIBVLC_FIND_REQUIRED) +endif (LIBVLC_FOUND) + + diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index a6ea0d840..d4b05cb3c 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -214,6 +214,7 @@ list(APPEND libSources accounts/spotify/SpotifyInfoPlugin.cpp audio/AudioEngine.cpp + audio/AudioOutput.cpp collection/Collection.cpp collection/ArtistsRequest.cpp @@ -353,6 +354,7 @@ list(APPEND libSources utils/WeakObjectHash.cpp utils/WeakObjectList.cpp utils/PluginLoader.cpp + utils/MediaStream.cpp ) add_subdirectory( accounts/configstorage ) @@ -390,6 +392,7 @@ include_directories( ${ECHONEST_INCLUDE_DIR} ${LUCENEPP_INCLUDE_DIRS} ${PHONON_INCLUDES} + ${LIBVLC_INCLUDES} ${Boost_INCLUDE_DIR} ${LIBPORTFWD_INCLUDE_DIR} @@ -502,6 +505,7 @@ ENDIF( UNIX AND NOT APPLE ) TARGET_LINK_LIBRARIES( tomahawklib LINK_PRIVATE ${PHONON_LIBRARY} + ${LIBVLC_LIBRARY} # Thirdparty shipped with tomahawk ${LIBPORTFWD_LIBRARIES} diff --git a/src/libtomahawk/audio/AudioEngine.cpp b/src/libtomahawk/audio/AudioEngine.cpp index 64e9d220e..8e221c729 100644 --- a/src/libtomahawk/audio/AudioEngine.cpp +++ b/src/libtomahawk/audio/AudioEngine.cpp @@ -53,16 +53,16 @@ static QString s_aeInfoIdentifier = QString( "AUDIOENGINE" ); void -AudioEnginePrivate::onStateChanged( Phonon::State newState, Phonon::State oldState ) +AudioEnginePrivate::onStateChanged( AudioOutput::AudioState newState, AudioOutput::AudioState oldState ) { - tDebug( LOGVERBOSE ) << Q_FUNC_INFO << oldState << newState << expectStop << q_ptr->state(); + tDebug() << Q_FUNC_INFO << oldState << newState << expectStop << q_ptr->state(); - if ( newState == Phonon::LoadingState ) + if ( newState == AudioOutput::Loading ) { // We don't emit this state to listeners - yet. state = AudioEngine::Loading; } - if ( newState == Phonon::BufferingState ) + if ( newState == AudioOutput::Buffering ) { if ( underrunCount > UNDERRUNTHRESHOLD && !underrunNotified ) { @@ -72,16 +72,16 @@ AudioEnginePrivate::onStateChanged( Phonon::State newState, Phonon::State oldSta else underrunCount++; } - if ( newState == Phonon::ErrorState ) + if ( newState == AudioOutput::Error ) { q_ptr->stop( AudioEngine::UnknownError ); - tDebug() << "Phonon Error:" << mediaObject->errorString() << mediaObject->errorType(); +//TODO tDebug() << "Phonon Error:" << audioOutput->errorString() << audioOutput->errorType(); emit q_ptr->error( AudioEngine::UnknownError ); q_ptr->setState( AudioEngine::Error ); } - if ( newState == Phonon::PlayingState ) + if ( newState == AudioOutput::Playing ) { bool emitSignal = false; if ( q_ptr->state() != AudioEngine::Paused && q_ptr->state() != AudioEngine::Playing ) @@ -95,23 +95,23 @@ AudioEnginePrivate::onStateChanged( Phonon::State newState, Phonon::State oldSta if ( emitSignal ) emit q_ptr->started( currentTrack ); } - if ( newState == Phonon::StoppedState && oldState == Phonon::PausedState ) + if ( newState == AudioOutput::Stopped && oldState == AudioOutput::Paused ) { // GStreamer backend hack: instead of going from PlayingState to StoppedState, it traverses PausedState q_ptr->setState( AudioEngine::Stopped ); } - if ( oldState == Phonon::PlayingState ) + if ( oldState == AudioOutput::Playing ) { bool stopped = false; switch ( newState ) { - case Phonon::PausedState: + case AudioOutput::Paused: { - if ( mediaObject && currentTrack ) - { - qint64 duration = mediaObject->totalTime() > 0 ? mediaObject->totalTime() : currentTrack->track()->duration() * 1000; - stopped = ( duration - 1000 < mediaObject->currentTime() ); + if ( audioOutput && currentTrack ) + {/* TODO + qint64 duration = audioOutput->totalTime() > 0 ? audioOutput->totalTime() : currentTrack->track()->duration() * 1000; + stopped = ( duration - 1000 < audioOutput->currentTime() ); */ } else stopped = true; @@ -121,7 +121,7 @@ AudioEnginePrivate::onStateChanged( Phonon::State newState, Phonon::State oldSta break; } - case Phonon::StoppedState: + case AudioOutput::Stopped: { stopped = true; break; @@ -158,40 +158,12 @@ AudioEnginePrivate::onStateChanged( Phonon::State newState, Phonon::State oldSta } } - +/* void AudioEnginePrivate::onAudioDataArrived( QMap > data ) { - QMap< AudioEngine::AudioChannel, QVector< qint16 > > result; - - if( data.contains( Phonon::AudioDataOutput::LeftChannel ) ) - { - result[ AudioEngine::LeftChannel ] = QVector< qint16 >( data[ Phonon::AudioDataOutput::LeftChannel ] ); - } - if( data.contains( Phonon::AudioDataOutput::LeftSurroundChannel ) ) - { - result[ AudioEngine::LeftChannel ] = QVector< qint16 >( data[ Phonon::AudioDataOutput::LeftSurroundChannel ] ); - } - if( data.contains( Phonon::AudioDataOutput::RightChannel ) ) - { - result[ AudioEngine::RightChannel ] = QVector< qint16 >( data[ Phonon::AudioDataOutput::RightChannel ] ); - } - if( data.contains( Phonon::AudioDataOutput::RightSurroundChannel ) ) - { - result[ AudioEngine::LeftChannel ] = QVector< qint16 >( data[ Phonon::AudioDataOutput::RightSurroundChannel ] ); - } - if( data.contains( Phonon::AudioDataOutput::CenterChannel ) ) - { - result[ AudioEngine::LeftChannel ] = QVector< qint16 >( data[ Phonon::AudioDataOutput::CenterChannel ] ); - } - if( data.contains( Phonon::AudioDataOutput::SubwooferChannel ) ) - { - result[ AudioEngine::LeftChannel ] = QVector< qint16 >( data[ Phonon::AudioDataOutput::SubwooferChannel ] ); - } - - s_instance->audioDataArrived( result ); } - +*/ AudioEngine* AudioEnginePrivate::s_instance = 0; @@ -214,30 +186,16 @@ AudioEngine::AudioEngine() d->waitingOnNewTrack = false; d->state = Stopped; d->coverTempFile = 0; - d->audioEffect = 0; - d->dspPluginCallback = 0; d->s_instance = this; tDebug() << "Init AudioEngine"; + d->audioOutput = new AudioOutput(this); + + QObject::connect( d->audioOutput, SIGNAL( stateChanged( AudioOutput::AudioState, AudioOutput::AudioState ) ), d, SLOT( onStateChanged( AudioOutput::AudioState, AudioOutput::AudioState ) ) ); +getchar(); qRegisterMetaType< AudioErrorCode >("AudioErrorCode"); qRegisterMetaType< AudioState >("AudioState"); - - d->mediaObject = new Phonon::MediaObject( this ); - d->audioOutput = new Phonon::AudioOutput( Phonon::MusicCategory, this ); - d->audioPath = Phonon::createPath( d->mediaObject, d->audioOutput ); - - d->mediaObject->setTickInterval( 150 ); - connect( d->mediaObject, SIGNAL( stateChanged( Phonon::State, Phonon::State ) ), d_func(), SLOT( onStateChanged( Phonon::State, Phonon::State ) ) ); - connect( d->mediaObject, SIGNAL( tick( qint64 ) ), SLOT( timerTriggered( qint64 ) ) ); - connect( d->mediaObject, SIGNAL( aboutToFinish() ), SLOT( onAboutToFinish() ) ); - connect( d->audioOutput, SIGNAL( volumeChanged( qreal ) ), SLOT( onVolumeChanged( qreal ) ) ); - connect( d->audioOutput, SIGNAL( mutedChanged( bool ) ), SIGNAL( mutedChanged( bool ) ) ); - - onVolumeChanged( d->audioOutput->volume() ); - setVolume( TomahawkSettings::instance()->volume() ); - - d->mediaObject->setCurrentSource(Phonon::MediaSource("PhononDSP::effectCallback=" + QString::number((qulonglong)&AudioEngine::dspCallback, 16))); } @@ -245,7 +203,6 @@ AudioEngine::~AudioEngine() { tDebug() << Q_FUNC_INFO; - d_func()->mediaObject->stop(); TomahawkSettings::instance()->setVolume( volume() ); @@ -258,13 +215,11 @@ AudioEngine::supportedMimeTypes() const { if ( d_func()->supportedMimeTypes.isEmpty() ) { - d_func()->supportedMimeTypes = Phonon::BackendCapabilities::availableMimeTypes(); + d_func()->supportedMimeTypes << "audio/*"; d_func()->supportedMimeTypes << "audio/basic"; - - return d_func()->supportedMimeTypes; } - else - return d_func()->supportedMimeTypes; + + return d_func()->supportedMimeTypes; } @@ -300,7 +255,7 @@ AudioEngine::play() if ( isPaused() ) { - d->mediaObject->play(); + d->audioOutput->play(); emit resumed(); sendNowPlayingNotification( Tomahawk::InfoSystem::InfoNowResumed ); @@ -330,7 +285,7 @@ AudioEngine::pause() tDebug( LOGEXTRA ) << Q_FUNC_INFO; - d->mediaObject->pause(); + d->audioOutput->pause(); emit paused(); Tomahawk::InfoSystem::InfoSystem::instance()->pushInfo( Tomahawk::InfoSystem::InfoPushData( s_aeInfoIdentifier, Tomahawk::InfoSystem::InfoNowPaused, QVariant(), Tomahawk::InfoSystem::PushNoFlag ) ); @@ -358,8 +313,8 @@ AudioEngine::stop( AudioErrorCode errorCode ) else setState( Error ); - if ( d->mediaObject->state() != Phonon::StoppedState ) - d->mediaObject->stop(); + if ( d->audioOutput->state() != AudioOutput::Stopped ) + d->audioOutput->stop(); emit stopped(); @@ -382,11 +337,7 @@ bool AudioEngine::activateDataOutput() { Q_D( AudioEngine ); - d->audioDataPath = Phonon::createPath( d->mediaObject, d->audioDataOutput ); - connect( d->audioDataOutput, SIGNAL( dataReady( QMap< Phonon::AudioDataOutput::Channel, QVector< qint16 > > ) ), - d_func(), SLOT( onAudioDataArrived( QMap< Phonon::AudioDataOutput::Channel, QVector< qint16 > > ) ) ); - - return d->audioDataPath.isValid(); + return true; } @@ -395,15 +346,9 @@ bool AudioEngine::deactivateDataOutput() { Q_D( AudioEngine ); - return d->audioDataPath.disconnect(); + return true; } -void AudioEngine::audioDataArrived( QMap< AudioEngine::AudioChannel, QVector< qint16 > >& data ) -{ - emit audioDataReady( data ); -} - - void AudioEngine::previous() { @@ -490,16 +435,11 @@ bool AudioEngine::canSeek() { Q_D( AudioEngine ); - - bool phononCanSeek = true; - /* TODO: When phonon properly reports this, re-enable it - if ( d->mediaObject && d->mediaObject->isValid() ) - phononCanSeek = d->mediaObject->isSeekable(); - */ +/* if ( d->playlist.isNull() ) - return phononCanSeek; - - return !d->playlist.isNull() && ( d->playlist.data()->seekRestrictions() != PlaylistModes::NoSeek ) && phononCanSeek; + return true; +*/ + return !d->playlist.isNull() && ( d->playlist.data()->seekRestrictions() != PlaylistModes::NoSeek ); } @@ -517,7 +457,7 @@ AudioEngine::seek( qint64 ms ) if ( isPlaying() || isPaused() ) { tDebug( LOGVERBOSE ) << Q_FUNC_INFO << ms; - d->mediaObject->seek( ms ); + d->audioOutput->seek( ms ); emit seeked( ms ); } } @@ -542,6 +482,7 @@ AudioEngine::setVolume( int percentage ) if ( percentage > 0 && d->audioOutput->isMuted() ) d->audioOutput->setMuted( false ); + emit volumeChanged( percentage ); } @@ -564,6 +505,7 @@ bool AudioEngine::isMuted() const { return d_func()->audioOutput->isMuted(); + return 0; } @@ -766,18 +708,22 @@ AudioEngine::performLoadTrack( const Tomahawk::result_ptr result, const QString QSharedPointer qnr = io.objectCast(); if ( !qnr.isNull() ) { - d->mediaObject->setCurrentSource( new QNR_IODeviceStream( qnr, this ) ); + tLog() << "CASE 1"; + d->audioOutput->setCurrentSource( new QNR_IODeviceStream( qnr, this ) ); // We keep track of the QNetworkReply in QNR_IODeviceStream // and Phonon handles the deletion of the // QNR_IODeviceStream object ioToKeep.clear(); - d->mediaObject->currentSource().setAutoDelete( true ); + // TODO d->audioOutput->setAutoDelete( true ); } else { - d->mediaObject->setCurrentSource( io.data() ); + tLog() << "CASE 2"; +/* TODO + d->audioOutput->setCurrentSource( io.data() ); // We handle the deletion via tracking in d->input - d->mediaObject->currentSource().setAutoDelete( false ); + d->audioOutput->setAutoDelete( false ); +*/ } } else @@ -788,6 +734,7 @@ AudioEngine::performLoadTrack( const Tomahawk::result_ptr result, const QString */ if ( !TomahawkUtils::isLocalResult( url ) ) { + tLog() << "CASE 3"; QUrl furl = url; if ( url.contains( "?" ) ) { @@ -796,19 +743,22 @@ AudioEngine::performLoadTrack( const Tomahawk::result_ptr result, const QString } tLog( LOGVERBOSE ) << "Passing to Phonon:" << furl; - d->mediaObject->setCurrentSource( furl ); + d->audioOutput->setCurrentSource( furl ); } else { + tLog() << "CASE 4"; QString furl = url; if ( furl.startsWith( "file://" ) ) furl = furl.right( furl.length() - 7 ); tLog( LOGVERBOSE ) << "Passing to Phonon:" << QUrl::fromLocalFile( furl ); - d->mediaObject->setCurrentSource( QUrl::fromLocalFile( furl ) ); + d->audioOutput->setCurrentSource( QUrl::fromLocalFile( furl ) ); } - d->mediaObject->currentSource().setAutoDelete( true ); +/* TODO + d->audioOutput->setAutoDelete( true ); +*/ } if ( !d->input.isNull() ) @@ -817,7 +767,7 @@ AudioEngine::performLoadTrack( const Tomahawk::result_ptr result, const QString d->input.clear(); } d->input = ioToKeep; - d->mediaObject->play(); + d->audioOutput->play(); if ( TomahawkSettings::instance()->privateListeningMode() != TomahawkSettings::FullyPrivate ) { @@ -1290,14 +1240,16 @@ AudioEngine::setState( AudioState state ) qint64 AudioEngine::currentTime() const { - return d_func()->mediaObject->currentTime(); +// TODO return d_func()->mediaObject->currentTime(); + return 0; } qint64 AudioEngine::currentTrackTotalTime() const { - return d_func()->mediaObject->totalTime(); +// TODO return d_func()->mediaObject->totalTime(); + return 0; } @@ -1384,29 +1336,10 @@ AudioEngine::setCurrentTrackPlaylist( const playlistinterface_ptr& playlist ) } -void -AudioEngine::dspCallback( signed short* samples, int nb_channels, int nb_samples ) -{ - AudioEngine::instance()->dspCallbackInternal( samples, nb_channels, nb_samples ); -} - - -void -AudioEngine::dspCallbackInternal( signed short* samples, int nb_channels, int nb_samples ) -{ - Q_D( AudioEngine ); - - if ( d->dspPluginCallback != 0 ) - { - d->dspPluginCallback( samples, nb_channels, nb_samples ); - } -} - - void AudioEngine::setDspCallback( void ( *cb ) ( signed short*, int, int ) ) { Q_D( AudioEngine ); - d->dspPluginCallback = cb; + d->audioOutput->setDspCallback( cb ); } diff --git a/src/libtomahawk/audio/AudioEngine.h b/src/libtomahawk/audio/AudioEngine.h index db9ca1884..eb3d0862f 100644 --- a/src/libtomahawk/audio/AudioEngine.h +++ b/src/libtomahawk/audio/AudioEngine.h @@ -156,7 +156,7 @@ signals: void paused(); void resumed(); - void audioDataReady( QMap< AudioEngine::AudioChannel, QVector > data ); +// void audioDataReady( QMap< AudioEngine::AudioChannel, QVector > data ); void stopAfterTrackChanged(); @@ -200,9 +200,7 @@ private: void setState( AudioState state ); void setCurrentTrackPlaylist( const Tomahawk::playlistinterface_ptr& playlist ); - static void dspCallback( signed short* samples, int nb_channels, int nb_samples ); - void dspCallbackInternal( signed short* samples, int nb_channels, int nb_samples ); - void audioDataArrived( QMap< AudioEngine::AudioChannel, QVector< qint16 > >& data ); +// void audioDataArrived( QMap< AudioEngine::AudioChannel, QVector< qint16 > >& data ); Q_DECLARE_PRIVATE( AudioEngine ) diff --git a/src/libtomahawk/audio/AudioEngine_p.h b/src/libtomahawk/audio/AudioEngine_p.h index b03338634..4d2f275a4 100644 --- a/src/libtomahawk/audio/AudioEngine_p.h +++ b/src/libtomahawk/audio/AudioEngine_p.h @@ -1,10 +1,12 @@ - +/* #include #include #include #include #include #include +*/ +#include "AudioOutput.h" #include @@ -29,8 +31,8 @@ public: public slots: - void onStateChanged( Phonon::State newState, Phonon::State oldState ); - void onAudioDataArrived( QMap< Phonon::AudioDataOutput::Channel, QVector< qint16 > > data ); + void onStateChanged( AudioOutput::AudioState newState, AudioOutput::AudioState oldState ); +// void onAudioDataArrived( QMap< Phonon::AudioDataOutput::Channel, QVector< qint16 > > data ); private: QSharedPointer input; @@ -41,6 +43,8 @@ private: Tomahawk::playlistinterface_ptr currentTrackPlaylist; Tomahawk::playlistinterface_ptr queue; + AudioOutput* audioOutput; +/* Phonon::MediaObject* mediaObject; Phonon::AudioOutput* audioOutput; @@ -49,7 +53,7 @@ private: Phonon::AudioDataOutput* audioDataOutput; Phonon::Path audioDataPath; - +*/ unsigned int timeElapsed; bool expectStop; diff --git a/src/libtomahawk/audio/AudioOutput.cpp b/src/libtomahawk/audio/AudioOutput.cpp new file mode 100644 index 000000000..34b97854d --- /dev/null +++ b/src/libtomahawk/audio/AudioOutput.cpp @@ -0,0 +1,320 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2014, Christian Muehlhaeuser + * Copyright 2010-2012, Jeff Mitchell + * Copyright 2013, Teo Mrnjavac + * + * Tomahawk 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 3 of the License, or + * (at your option) any later version. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "AudioOutput.h" + +#include "utils/Logger.h" + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +static QString s_aeInfoIdentifier = QString( "AUDIOOUTPUT" ); + + +AudioOutput* AudioOutput::s_instance = 0; + + +AudioOutput* +AudioOutput::instance() +{ + return AudioOutput::s_instance; +} + + +AudioOutput::AudioOutput( QObject* parent ) + : QObject( parent ) + , dspPluginCallback( 0 ) + , currentState( Stopped ) + , muted( false ) + , m_volume( 1.0 ) +{ + tDebug() << Q_FUNC_INFO; + + AudioOutput::s_instance = this; + currentStream = 0; + + QList args; + + args << "--ignore-config"; + args << "--verbose=11111"; + args << "--no-plugins-cache"; + args << "--extraintf=logger"; + args << "--no-media-library"; + args << "--no-osd"; + args << "--no-stats"; + args << "--no-video-title-show"; + args << "--no-snapshot-preview"; + args << "--no-xlib"; + args << "--services-discovery=''"; + args << "--no-one-instance"; + args << "--no-video"; +// args << "--audio-filter=dsp"; +// args << QString("--dsp-callback=%1").arg((quint64)&AudioOutput::s_dspCallback, 0, 16).toAscii(); + + QVarLengthArray< const char * , 64 > vlcArgs( args.size() ); + for ( int i = 0 ; i < args.size() ; ++i ) { + vlcArgs[i] = args.at( i ).constData(); + tDebug() << args.at( i ); + } + + // Create and initialize a libvlc instance (it should be done only once) + if ( !( vlcInstance = libvlc_new( vlcArgs.size(), vlcArgs.constData() ) ) ) { + tDebug() << "libVLC: could not initialize"; + } + vlcPlayer = libvlc_media_player_new( vlcInstance ); + + getchar(); + tDebug() << "AudioOutput::AudioOutput OK !\n"; +} + + +AudioOutput::~AudioOutput() +{ + tDebug() << Q_FUNC_INFO; +} + +void +AudioOutput::setCurrentSource(MediaStream stream) +{ + setCurrentSource( new MediaStream(stream) ); +} + +void +AudioOutput::setCurrentSource(MediaStream* stream) +{ + tDebug() << Q_FUNC_INFO; + currentStream = stream; + + currentState = Loading; + + QByteArray url; + switch (stream->type()) { + case MediaStream::Unknown: + tDebug() << "MediaStream Type is Invalid:" << stream->type(); + break; + + case MediaStream::Empty: + tDebug() << "MediaStream is empty."; + break; + + case MediaStream::Url: + tDebug() << "MediaStream::Url:" << stream->url(); + if (stream->url().scheme().isEmpty()) { + url = "file:///"; + if (stream->url().isRelative()) + url.append(QFile::encodeName(QDir::currentPath()) + '/'); + } + url += stream->url().toEncoded(); + break; + + case MediaStream::Stream: + url = QByteArray("imem://"); + break; + } + + tDebug() << "MediaStream::Final Url:" << url; + + vlcMedia = libvlc_media_new_location( vlcInstance, url.constData() ); + + libvlc_media_player_set_media( vlcPlayer, vlcMedia ); + + if ( stream->type() == MediaStream::Stream ) { + libvlc_media_add_option_flag(vlcMedia, "imem-cat=4", libvlc_media_option_trusted); + libvlc_media_add_option_flag(vlcMedia, (QString("imem-data=") + QString::number((quint64)stream)).toUtf8().data(), libvlc_media_option_trusted); + libvlc_media_add_option_flag(vlcMedia, (QString("imem-get=") + QString::number((quint64)&MediaStream::readCallback)).toUtf8().data(), libvlc_media_option_trusted); + libvlc_media_add_option_flag(vlcMedia, (QString("imem-release=") + QString::number((quint64)&MediaStream::readDoneCallback)).toUtf8().data(), libvlc_media_option_trusted); + libvlc_media_add_option_flag(vlcMedia, (QString("imem-seek=") + QString::number((quint64)&MediaStream::seekCallback)).toUtf8().data(), libvlc_media_option_trusted); + } + libvlc_media_add_option(vlcMedia, "audio-filter=dsp"); + libvlc_media_add_option(vlcMedia, ":audio-filter=dsp"); + libvlc_media_add_option(vlcMedia, "--audio-filter=dsp"); + libvlc_media_add_option(vlcMedia, "audio-filter dsp"); + libvlc_media_add_option(vlcMedia, ":audio-filter dsp"); + libvlc_media_add_option(vlcMedia, "--audio-filter dsp"); + + currentState = Stopped; +} + + +AudioOutput::AudioState +AudioOutput::state() +{ + tDebug() << Q_FUNC_INFO; + return currentState; +} + + +void +AudioOutput::setState( AudioState state ) +{ + tDebug() << Q_FUNC_INFO; + emit stateChanged ( state, currentState ); + currentState = state; +} + + +void +AudioOutput::play() +{ + tDebug() << Q_FUNC_INFO; + if ( libvlc_media_player_is_playing ( vlcPlayer ) ) { + libvlc_media_player_set_pause ( vlcPlayer, 0 ); + } else { + libvlc_media_player_play ( vlcPlayer ); + } + + setState( Playing ); +} + + +void +AudioOutput::pause() +{ + tDebug() << Q_FUNC_INFO; +// libvlc_media_player_pause( vlcPlayer ); + libvlc_media_player_set_pause ( vlcPlayer, 1 ); + + setState( Paused ); +} + + +void +AudioOutput::stop() +{ + tDebug() << Q_FUNC_INFO; + libvlc_media_player_stop ( vlcPlayer ); + + setState( Stopped ); +} + + +void +AudioOutput::seek( qint64 milliseconds ) +{ + tDebug() << Q_FUNC_INFO; + + switch ( currentState ) { + case Playing: + case Paused: + case Loading: + case Buffering: + break; + default: + // Seeking while not being in a playingish state is cached for later. +// TODO m_seekpoint = milliseconds; + return; + } + + tDebug() << "AudioOutput:: seeking" << milliseconds << "msec"; + + libvlc_media_player_set_time ( vlcPlayer, milliseconds ); +/* + const qint64 time = currentTime(); + const qint64 total = totalTime(); +*/ +/* + // Reset last tick marker so we emit time even after seeking + if (time < m_lastTick) + m_lastTick = time; + if (time < total - m_prefinishMark) + m_prefinishEmitted = false; + if (time < total - ABOUT_TO_FINISH_TIME) + m_aboutToFinishEmitted = false; +*/ +} + + +bool +AudioOutput::isMuted() +{ + tDebug() << Q_FUNC_INFO; + + return muted; +} + + +void +AudioOutput::setMuted(bool m) +{ + tDebug() << Q_FUNC_INFO; + + muted = m; + if ( muted == true ) { + libvlc_audio_set_volume( vlcPlayer, 0 ); + } else { + libvlc_audio_set_volume( vlcPlayer, m_volume * 100.0 ); + } +} + + +qreal +AudioOutput::volume() +{ + tDebug() << Q_FUNC_INFO; + + return muted ? 0 : m_volume; +} + + +void +AudioOutput::setVolume(qreal vol) +{ + tDebug() << Q_FUNC_INFO; + + m_volume = vol; + if ( !muted ) { + libvlc_audio_set_volume( vlcPlayer, m_volume * 100.0 ); + } +} + + +void +AudioOutput::s_dspCallback( signed short* samples, int nb_channels, int nb_samples ) +{ + tDebug() << Q_FUNC_INFO; + + AudioOutput::instance()->dspCallback( samples, nb_channels, nb_samples ); +} + + +void +AudioOutput::dspCallback( signed short* samples, int nb_channels, int nb_samples ) +{ + if ( dspPluginCallback != 0 ) + { + dspPluginCallback( samples, nb_channels, nb_samples ); + } +} + + +void +AudioOutput::setDspCallback( void ( *cb ) ( signed short*, int, int ) ) +{ + dspPluginCallback = cb; +} diff --git a/src/libtomahawk/audio/AudioOutput.h b/src/libtomahawk/audio/AudioOutput.h new file mode 100644 index 000000000..b231b565e --- /dev/null +++ b/src/libtomahawk/audio/AudioOutput.h @@ -0,0 +1,87 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2014, Christian Muehlhaeuser + * Copyright 2010-2012, Jeff Mitchell + * Copyright 2013, Teo Mrnjavac + * + * Tomahawk 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 3 of the License, or + * (at your option) any later version. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef AUDIOOUTPUT_H +#define AUDIOOUTPUT_H + +#include "../Typedefs.h" + +#include "DllMacro.h" + +#include "utils/MediaStream.h" + +struct libvlc_instance_t; +struct libvlc_media_player_t; +struct libvlc_media_t; + +class DLLEXPORT AudioOutput : public QObject +{ +Q_OBJECT + +public: + enum AudioState { Stopped = 0, Playing = 1, Paused = 2, Error = 3, Loading = 4, Buffering = 5 }; + + explicit AudioOutput(QObject* parent = 0); + ~AudioOutput(); + + AudioState state(); + + void setCurrentSource(MediaStream stream); + void setCurrentSource(MediaStream* stream); + + void play(); + void pause(); + void stop(); + void seek(qint64 milliseconds); + + bool isMuted(); + void setMuted(bool m); + void setVolume(qreal vol); + qreal volume(); + + void setDspCallback( void ( *cb ) ( signed short*, int, int ) ); + + static AudioOutput* instance(); + +public slots: + +signals: + void stateChanged( AudioOutput::AudioState, AudioOutput::AudioState ); + +private: + void setState( AudioState state ); + + static void s_dspCallback( signed short* samples, int nb_channels, int nb_samples ); + void dspCallback( signed short* samples, int nb_channels, int nb_samples ); + + void ( *dspPluginCallback ) ( signed short* samples, int nb_channels, int nb_samples ); + + static AudioOutput* s_instance; + AudioState currentState; + MediaStream* currentStream; + bool muted; + qreal m_volume; + + libvlc_instance_t* vlcInstance; + libvlc_media_player_t* vlcPlayer; + libvlc_media_t* vlcMedia; +}; + +#endif // AUDIOOUTPUT_H diff --git a/src/libtomahawk/utils/MediaStream.cpp b/src/libtomahawk/utils/MediaStream.cpp new file mode 100644 index 000000000..f400e812b --- /dev/null +++ b/src/libtomahawk/utils/MediaStream.cpp @@ -0,0 +1,128 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2014, Christian Muehlhaeuser + * Copyright 2010-2012, Jeff Mitchell + * Copyright 2013, Teo Mrnjavac + * + * Tomahawk 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 3 of the License, or + * (at your option) any later version. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "MediaStream.h" + +#include "utils/Logger.h" + + +static QString s_aeInfoIdentifier = QString( "MEDIASTREAM" ); + + +MediaStream::MediaStream() + : m_type( Unknown ) + , m_url( QUrl() ) + , m_pos( 0 ) + , m_streamSize( 0 ) +{ + tDebug() << Q_FUNC_INFO; +} + + +MediaStream::MediaStream( const QUrl &url ) + : m_type(Url) +{ + tDebug() << Q_FUNC_INFO; + + m_url = url; +} + + +MediaStream::~MediaStream() +{ + tDebug() << Q_FUNC_INFO; +} + + +MediaStream::MediaType +MediaStream::type() +{ + tDebug() << Q_FUNC_INFO; + return m_type; +} + + +QUrl +MediaStream::url() +{ + tDebug() << Q_FUNC_INFO; + return m_url; +} + + +qint64 +MediaStream::streamSize() +{ + return m_streamSize; +} + + +void +MediaStream::setStreamSize( qint64 size ) +{ + m_streamSize = size; +} + + +int +MediaStream::readCallback ( void* data, const char* cookie, int64_t* dts, int64_t* pts, unsigned* flags, size_t* bufferSize, void** buffer ) +{ + tDebug() << Q_FUNC_INFO; + + Q_UNUSED(cookie); + Q_UNUSED(dts); + Q_UNUSED(pts); + Q_UNUSED(flags); + + MediaStream* that = static_cast < MediaStream * > ( data ); + + *bufferSize = that->needData(buffer); + return 0; +} + + +int +MediaStream::readDoneCallback ( void *data, const char *cookie, size_t bufferSize, void *buffer ) +{ + tDebug() << Q_FUNC_INFO; + + Q_UNUSED(data); + Q_UNUSED(cookie); + Q_UNUSED(bufferSize); + delete static_cast(buffer); + + return 0; +} + + +int +MediaStream::seekCallback ( void *data, const uint64_t pos ) +{ + tDebug() << Q_FUNC_INFO; + + MediaStream* that = static_cast < MediaStream * > ( data ); + if ( static_cast < int64_t > ( pos ) > that->streamSize() ) { + return -1; + } + + that->m_pos = pos; + + return 0; +} diff --git a/src/libtomahawk/utils/MediaStream.h b/src/libtomahawk/utils/MediaStream.h new file mode 100644 index 000000000..28000c4ca --- /dev/null +++ b/src/libtomahawk/utils/MediaStream.h @@ -0,0 +1,63 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2014, Christian Muehlhaeuser + * Copyright 2010-2012, Jeff Mitchell + * Copyright 2013, Teo Mrnjavac + * + * Tomahawk 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 3 of the License, or + * (at your option) any later version. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef MEDIASTREAM_H +#define MEDIASTREAM_H + +#include "../Typedefs.h" + +#include "DllMacro.h" +#include "utils/Logger.h" +#include + +#include + +class DLLEXPORT MediaStream +{ + +public: + enum MediaType { Unknown = -1, Empty = 0, Url = 1, Stream = 2 }; + + MediaStream(); + MediaStream( const QUrl &url ); + virtual ~MediaStream(); + + MediaType type(); + QUrl url(); + + void setStreamSize( qint64 size ); + qint64 streamSize(); + + virtual void seekStream( qint64 offset ) { (void)offset; } + virtual size_t needData ( void** buffer ) { (void)buffer; tDebug() << Q_FUNC_INFO; return 0; } + + static int readCallback ( void* data, const char* cookie, int64_t* dts, int64_t* pts, unsigned* flags, size_t* bufferSize, void** buffer ); + static int readDoneCallback ( void *data, const char *cookie, size_t bufferSize, void *buffer ); + static int seekCallback ( void *data, const uint64_t pos ); + +protected: + MediaType m_type; + QUrl m_url; + + qint64 m_pos; + qint64 m_streamSize; +}; + +#endif // MEDIASTREAM_H diff --git a/src/libtomahawk/utils/Qnr_IoDeviceStream.cpp b/src/libtomahawk/utils/Qnr_IoDeviceStream.cpp index 0176f452d..ed72c4463 100644 --- a/src/libtomahawk/utils/Qnr_IoDeviceStream.cpp +++ b/src/libtomahawk/utils/Qnr_IoDeviceStream.cpp @@ -33,11 +33,15 @@ using namespace Tomahawk; #define BLOCK_SIZE 1048576 QNR_IODeviceStream::QNR_IODeviceStream( const QSharedPointer& reply, QObject* parent ) - : Phonon::AbstractMediaStream( parent ) + : QObject( parent ) + , MediaStream() , m_networkReply( reply ) - , m_pos( 0 ) , m_timer( new QTimer( this ) ) { + tDebug() << Q_FUNC_INFO; + + m_type = MediaStream::Stream; + if ( !m_networkReply->isOpen() ) { m_networkReply->open(QIODevice::ReadOnly); } @@ -50,7 +54,7 @@ QNR_IODeviceStream::QNR_IODeviceStream( const QSharedPointer& rep // All data is ready, read it! m_data = m_networkReply->readAll(); Q_ASSERT( m_networkReply->atEnd() ); - setStreamSeekable( true ); +//TODO setStreamSeekable( true ); setStreamSize( m_data.size() ); } else @@ -59,7 +63,7 @@ QNR_IODeviceStream::QNR_IODeviceStream( const QSharedPointer& rep QVariant contentLength = m_networkReply->header( QNetworkRequest::ContentLengthHeader ); if ( contentLength.isValid() && contentLength.toLongLong() > 0 ) { - setStreamSize( contentLength.toLongLong() ); +//TODO setStreamSize( contentLength.toLongLong() ); } // Just consume all data that is already available. m_data = m_networkReply->readAll(); @@ -73,54 +77,39 @@ QNR_IODeviceStream::QNR_IODeviceStream( const QSharedPointer& rep QNR_IODeviceStream::~QNR_IODeviceStream() { -} - - -void -QNR_IODeviceStream::enoughData() -{ - m_timer->stop(); -} - - -void -QNR_IODeviceStream::needData() -{ - m_timer->start(); - moreData(); -} - -void -QNR_IODeviceStream::reset() -{ - m_pos = 0; + tDebug() << Q_FUNC_INFO; } void QNR_IODeviceStream::seekStream( qint64 offset ) { + tDebug() << Q_FUNC_INFO; + m_pos = offset; } -void -QNR_IODeviceStream::moreData() + +size_t +QNR_IODeviceStream::needData ( void** buffer ) { +// tDebug() << Q_FUNC_INFO; + QByteArray data = m_data.mid( m_pos, BLOCK_SIZE ); m_pos += data.size(); if ( ( data.size() == 0 ) && m_networkReply->atEnd() && m_networkReply->isFinished() ) { // We're done. - endOfData(); - m_timer->stop(); - } - else - { - writeData( data ); +//TODO endOfData(); + return 0; } + + *buffer = new char[data.size()]; + memcpy(*buffer, data.data(), data.size()); + tDebug() << Q_FUNC_INFO << " Returning buffer with size " << data.size(); + return data.size(); } - void QNR_IODeviceStream::readyRead() { diff --git a/src/libtomahawk/utils/Qnr_IoDeviceStream.h b/src/libtomahawk/utils/Qnr_IoDeviceStream.h index 27f5d9ad4..e43a6437a 100644 --- a/src/libtomahawk/utils/Qnr_IoDeviceStream.h +++ b/src/libtomahawk/utils/Qnr_IoDeviceStream.h @@ -25,37 +25,35 @@ #include "DllMacro.h" -#include +//#include #include #include #include +#include "MediaStream.h" + class QIODevice; class QTimer; namespace Tomahawk { -class DLLEXPORT QNR_IODeviceStream : public Phonon::AbstractMediaStream +class DLLEXPORT QNR_IODeviceStream : public QObject, public MediaStream { Q_OBJECT public: explicit QNR_IODeviceStream( const QSharedPointer& reply, QObject *parent = 0 ); ~QNR_IODeviceStream(); - virtual void enoughData(); - virtual void needData(); - virtual void reset(); virtual void seekStream( qint64 offset ); + virtual size_t needData ( void** buffer ); private slots: - void moreData(); void readyRead(); private: QByteArray m_data; QSharedPointer m_networkReply; - qint64 m_pos; QTimer* m_timer; };