You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
592 lines
18 KiB
592 lines
18 KiB
/***************************************************************************
|
|
masengine.cpp - MAS audio interface
|
|
-------------------
|
|
begin : 2004-07-20
|
|
copyright : (C) 2004-05 by Roland Gigler
|
|
email : rolandg@web.de
|
|
what : interface to the Media Application Server (MAS)
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* 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. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#define DEBUG_PREFIX "MAS-Engine"
|
|
|
|
#include "debug.h"
|
|
#include "enginebase.h"
|
|
#include "engineobserver.h"
|
|
|
|
#include <assert.h>
|
|
#include <math.h> //setVolume(), timerEvent()
|
|
#include <string>
|
|
//#include <vector>
|
|
|
|
#include <tqtimer.h>
|
|
#include <tqfile.h>
|
|
|
|
#include <kapplication.h>
|
|
#include <kconfig.h>
|
|
#include <kfileitem.h>
|
|
#include <kgenericfactory.h>
|
|
#include <klocale.h>
|
|
#include <kmessagebox.h>
|
|
#include <kmimetype.h>
|
|
#include <kstandarddirs.h>
|
|
#include <kurl.h>
|
|
|
|
#include "masengine.h"
|
|
//#define DB_CUTOFF -40.0
|
|
#define BUFFER_TIME_MS 300
|
|
#define POSTOUT_TIME_MS 100
|
|
//#define QUERY_MIX_VOLUME 0
|
|
//#define QUERY_MIX_EPSILON 5
|
|
|
|
|
|
|
|
AMAROK_EXPORT_PLUGIN( MasEngine )
|
|
|
|
|
|
MasEngine::MasEngine()
|
|
: Engine::Base()
|
|
, m_inited (false)
|
|
// , m_scopeId( 0 )
|
|
// , m_xfadeFadeout( false )
|
|
// , m_xfadeValue( 0.0 )
|
|
// , m_xfadeCurrent( "invalue2" )
|
|
, m_lastKnownPosition( 0 )
|
|
, m_state( Engine::Empty )
|
|
, m_pPlayingTimer( new TQTimer( this ) )
|
|
|
|
{
|
|
DEBUG_FUNC_INFO
|
|
|
|
// NOT SUPPORTED
|
|
//addPluginProperty( "HasConfigure", "true" );
|
|
//addPluginProperty( "HasCrossfading", "true" );
|
|
//addPluginProperty( "HasEqualizer", "true" );
|
|
}
|
|
|
|
|
|
MasEngine::~MasEngine()
|
|
{
|
|
DEBUG_BLOCK
|
|
|
|
if ( m_inited ) stop();
|
|
m_pPlayingTimer->stop();
|
|
killTimers();
|
|
}
|
|
|
|
|
|
bool MasEngine::init()
|
|
{
|
|
DEBUG_BLOCK
|
|
|
|
if (!masinit() ) {
|
|
KMessageBox::error( 0, i18n("<h3>Amarok could not initialise MAS.</h3>"
|
|
"<p>Check for a running mas daemon.</p>") );
|
|
error() << " connecting to MAS daemon failed. Aborting. " << endl;
|
|
debug() << " Please restart amarok." << endl;
|
|
debug() << k_funcinfo << " returns false !" << endl;
|
|
return false;
|
|
}
|
|
m_inited=true; // we connected to MAS
|
|
|
|
connect ( m_pPlayingTimer, TQT_SIGNAL( timeout() ), this, TQT_SLOT( playingTimeout() ) );
|
|
|
|
emit statusText( "MAS Engine inited :-)");
|
|
return true;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// PUBLIC METHODS
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool MasEngine::canDecode( const KURL &url ) const
|
|
{
|
|
DEBUG_BLOCK
|
|
|
|
TQStringList list;
|
|
bool playable;
|
|
|
|
debug() << " Param: url: " << url << endl;
|
|
//debug() << " url.protocol() >" << url.protocol() <<"<"<< endl;
|
|
|
|
if (url.protocol() == "http" ) return false;
|
|
|
|
// TODO determine list of supported MimeTypes/Extensions from MAS
|
|
list += TQString("audio/x-mp3");
|
|
|
|
KFileItem fileItem( KFileItem::Unknown, KFileItem::Unknown, url, false ); //false = determineMimeType straight away
|
|
KMimeType::Ptr mimetype = fileItem.determineMimeType();
|
|
debug() << "mimetype: " << mimetype->name().latin1() << endl;
|
|
|
|
playable = list.contains( mimetype->name().latin1() );
|
|
if ( !playable )
|
|
warning() << "Mimetype is not playable by MAS (" << url << ")" << endl;
|
|
|
|
return playable;
|
|
} // canDecode
|
|
|
|
|
|
bool MasEngine::load( const KURL& url, bool stream )
|
|
{
|
|
DEBUG_BLOCK
|
|
struct mas_package pkg;
|
|
char pbuf[10240];
|
|
int pos = 0;
|
|
|
|
m_isStream = stream;
|
|
debug() << " m_url: " << m_url << endl;
|
|
debug() << " Param: stream: " << stream << endl;
|
|
debug() << " Param: url " << url << endl;
|
|
|
|
if ( !url.isLocalFile() ) { // for now
|
|
debug() << " only local files are supported (for now)" << endl;
|
|
return false;
|
|
}
|
|
|
|
if ( !canDecode(url) ) {
|
|
debug() << " cannot decode!" << endl;
|
|
return false;
|
|
}
|
|
|
|
if ( m_url == url ) {
|
|
return true;
|
|
} else {
|
|
stop();
|
|
}
|
|
m_url = url;
|
|
|
|
/* send fresh data to MAS */;
|
|
masc_setup_package( &pkg, pbuf, sizeof pbuf, MASC_PACKAGE_STATIC );
|
|
masc_pushk_int16( &pkg, (char*)"pos", pos );
|
|
//masc_push_string( &pkg, (char *)m_url.path().latin1() );
|
|
|
|
TQCString cs= TQFile::encodeName( m_url.path());
|
|
const char *pcs = cs;
|
|
masc_push_string( &pkg, (char *)pcs);
|
|
masc_finalize_package( &pkg );
|
|
|
|
mas_set( m_mp1a_source_device, (char*)"playlist", &pkg );
|
|
masc_strike_package( &pkg );
|
|
|
|
mas_dev_show_state (m_mp1a_source_device);
|
|
mas_source_flush( m_codec );
|
|
|
|
m_lastKnownPosition = 0;
|
|
m_state = Engine::Idle;
|
|
|
|
return true;
|
|
} // load
|
|
|
|
|
|
bool MasEngine::play( unsigned int offset)
|
|
{
|
|
DEBUG_BLOCK
|
|
struct mas_package pkg;
|
|
char pbuf[10240];
|
|
|
|
debug() << " param: offset " << offset << endl;
|
|
if ( m_state != Engine::Playing ) {
|
|
/* change the track */
|
|
masc_setup_package( &pkg, pbuf, sizeof pbuf, MASC_PACKAGE_STATIC );
|
|
masc_pushk_int16( &pkg, (char*)"pos", 1 );
|
|
masc_finalize_package( &pkg );
|
|
mas_set( m_mp1a_source_device, (char*)"ctrack", &pkg );
|
|
masc_finalize_package( &pkg );
|
|
|
|
mas_source_flush( m_codec );
|
|
mas_source_play( m_mp1a_source_device );
|
|
mas_source_play_on_mark( m_sbuf );
|
|
|
|
debug() << "mas_source_play()" << endl;
|
|
}
|
|
|
|
m_pPlayingTimer->start(MAS_TIMER, false);
|
|
|
|
m_state = Engine::Playing;
|
|
emit stateChanged( Engine::Playing );
|
|
|
|
return true;
|
|
} // play
|
|
uint
|
|
MasEngine::length() const
|
|
{
|
|
DEBUG_BLOCK
|
|
char pbuf[128];
|
|
struct mas_package pkg;
|
|
struct mas_package nugget;
|
|
float trklen;
|
|
|
|
masc_setup_package( &pkg, pbuf, sizeof pbuf, MASC_PACKAGE_STATIC );
|
|
masc_pushk_int16( &pkg, (char *)"pos", 1 );
|
|
masc_finalize_package( &pkg );
|
|
|
|
mas_get( m_mp1a_source_device, (char *)"trklen", &pkg, &nugget );
|
|
masc_strike_package( &pkg );
|
|
|
|
masc_pullk_float( &nugget, (char *)"trklen", &trklen );
|
|
masc_strike_package( &nugget );
|
|
|
|
debug() << "trklen: " << trklen << endl;
|
|
return uint(trklen*1000);
|
|
} // position
|
|
|
|
/*
|
|
* return current position in milli seconds
|
|
*/
|
|
uint MasEngine::position() const
|
|
{
|
|
return m_lastKnownPosition;
|
|
} // position
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
void MasEngine::playingTimeout() //SLOT
|
|
{
|
|
//DEBUG_BLOCK
|
|
m_lastKnownPosition += MAS_TIMER;
|
|
|
|
// ask MAS if it's still playing
|
|
struct mas_package nugget;
|
|
int16 pos;
|
|
mas_get( m_mp1a_source_device, (char *)"ctrack", NULL, &nugget );
|
|
masc_pullk_int16( &nugget, (char *)"pos", &pos );
|
|
masc_strike_package( &nugget );
|
|
|
|
if ( pos == 0 ) {
|
|
m_pPlayingTimer->stop();
|
|
|
|
m_state = Engine::Idle;
|
|
emit trackEnded();
|
|
}
|
|
|
|
} // playingTimeout //SLOT
|
|
|
|
|
|
void MasEngine::stop()
|
|
{
|
|
DEBUG_BLOCK
|
|
|
|
//switch xfade channels
|
|
/* m_xfadeCurrent = ( m_xfadeCurrent == "invalue1" ) ? "invalue2" : "invalue1";
|
|
|
|
if ( m_xfadeValue == 0.0 )
|
|
m_xfadeValue = 1.0;
|
|
*/
|
|
mas_source_stop( m_mp1a_source_device );
|
|
mas_source_stop( m_sbuf );
|
|
debug() << "performed: mas_source_stop()" << endl;
|
|
|
|
m_pPlayingTimer->stop();
|
|
m_state = Engine::Empty;
|
|
m_lastKnownPosition = 0;
|
|
|
|
//emit stateChanged( m_state );
|
|
}
|
|
|
|
|
|
void MasEngine::pause()
|
|
{
|
|
DEBUG_BLOCK
|
|
|
|
if(m_state == Engine::Paused) {
|
|
mas_source_play( m_mp1a_source_device );
|
|
mas_source_play( m_sbuf );
|
|
debug() << "performed: mas_source_play()" << endl;
|
|
|
|
m_state = Engine::Playing;
|
|
m_pPlayingTimer->start(MAS_TIMER, false);
|
|
|
|
} else {
|
|
mas_source_pause( m_mp1a_source_device );
|
|
mas_source_pause( m_sbuf );
|
|
debug() << "performed: mas_source_pause()" << endl;
|
|
|
|
m_state = Engine::Paused;
|
|
m_pPlayingTimer->stop();
|
|
}
|
|
|
|
emit stateChanged( m_state );
|
|
} // pause
|
|
|
|
|
|
// TODO depends on MAS support
|
|
void MasEngine::seek( unsigned int ms )
|
|
{
|
|
//DEBUG_BLOCK
|
|
AMAROK_NOTIMPLEMENTED
|
|
//debug() << " param: ms " << ms << endl;
|
|
} // seek
|
|
|
|
|
|
void MasEngine::setVolumeSW( unsigned int percent )
|
|
{
|
|
DEBUG_BLOCK
|
|
debug() << " Param: percent " << percent << endl;
|
|
|
|
#ifdef USE_MIX_VOLUME_GENERIC
|
|
// MAS takes values from 0 to 128
|
|
int16 vol = (int16)(percent*1.28);
|
|
debug() << " setting vol to " << vol << endl;
|
|
|
|
// the more generic way
|
|
struct mas_package pkg;
|
|
char buffer[128];
|
|
|
|
masc_setup_package( &pkg, buffer, sizeof buffer, MASC_PACKAGE_STATIC );
|
|
masc_push_int32( &pkg, m_mix_sink->portnum );
|
|
masc_push_uint16( &pkg, vol );
|
|
masc_finalize_package( &pkg );
|
|
|
|
mas_set( m_mix_device, (char *)"multiplier", &pkg );
|
|
masc_strike_package( &pkg );
|
|
debug() << " after mas_set" << endl;
|
|
#else
|
|
// use the mix api
|
|
double vol = percent*0.01;
|
|
debug() << " setting vol to " << vol << endl;
|
|
mas_mix_attenuate_linear( m_mix_device, m_mix_sink, vol);
|
|
#endif
|
|
|
|
} // setVolumeSW
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// PRIVATE METHODS
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/*
|
|
void MasEngine::timerEvent( TQTimerEvent* )
|
|
{
|
|
if ( m_xfadeValue > 0.0 )
|
|
{
|
|
m_xfadeValue -= ( m_xfadeLength ) ? 1.0 / m_xfadeLength * ARTS_TIMER : 1.0;
|
|
|
|
if ( m_xfadeValue <= 0.0 )
|
|
{
|
|
m_xfadeValue = 0.0;
|
|
}
|
|
float value;
|
|
if ( m_xfadeFadeout )
|
|
value = 1.0 - log10( ( 1.0 - m_xfadeValue ) * 9.0 + 1.0 );
|
|
else
|
|
value = log10( m_xfadeValue * 9.0 + 1.0 );
|
|
|
|
}
|
|
}
|
|
*/
|
|
|
|
bool MasEngine::masinit()
|
|
{
|
|
DEBUG_BLOCK
|
|
int32 err;
|
|
|
|
masc_log_verbosity( MAS_VERBLVL_DEBUG );
|
|
masc_log_message( 0, (char*)"amarok/MASengine" );
|
|
masc_log_message( 0, (char*)"tries to plays audio using MAS ;-)");
|
|
masc_log_message( 0, (char*)"" );
|
|
|
|
err = mas_init();
|
|
debug() << " mas_init err:" << err << endl;
|
|
if (err < 0)
|
|
{
|
|
warning() << "MAS daemon not running. Starting! Please provide password..." << endl;
|
|
// masd seems not to be running, let's try to run it
|
|
TQCString cmdline;
|
|
cmdline = TQFile::encodeName( KStandardDirs::findExe( TQString::fromLatin1( "kdesu" ) ) );
|
|
// TODO !!!hardcoded path
|
|
cmdline += " -n -f /usr/local/mas/log/mas-1.log -c ";
|
|
cmdline += "/usr/local/mas/bin/mas-launch";
|
|
|
|
debug() << " cmdline: " << cmdline << endl;
|
|
int status = ::system( cmdline );
|
|
debug() << " status: " << status << endl;
|
|
|
|
debug() << " give the MAS daemon some time (3s) ... " << endl;
|
|
::sleep( 3 );
|
|
|
|
if ( status != -1 && WIFEXITED( status ) )
|
|
{
|
|
int time = 0;
|
|
do
|
|
{
|
|
debug() << " time: " << time << endl;
|
|
// every time it fails, we should wait a little longer
|
|
// between tries
|
|
::sleep( 1 + time / 2 );
|
|
err = mas_init();
|
|
debug() << " mas_init err: " << err << endl;
|
|
}
|
|
while ( ++time < 5 && ( err < 0 ) );
|
|
}
|
|
}
|
|
|
|
if (err < 0)
|
|
{
|
|
TQString text = "Connection to MAS daemon FAILED. Please check your installation.";
|
|
error() << text << endl;
|
|
emit statusText( text );
|
|
return false;
|
|
}
|
|
debug() << "Connection to MAS daemon is up :-)" << endl;
|
|
|
|
struct mas_data_characteristic* dc;
|
|
struct mas_package nugget;
|
|
mas_device_t anx;
|
|
mas_port_t tmp_source;
|
|
mas_channel_t local;
|
|
|
|
err = mas_get_local_control_channel( &local );
|
|
if ( err < 0 ) masc_logerror( err, "getting local control channel" );
|
|
debug() << " after mas_get_local_control_channel" << endl;
|
|
|
|
/* Get the id of the sample clock provided by the anx device -- if
|
|
we can! */
|
|
err = mas_asm_get_device_by_name( (char*)"anx", &anx );
|
|
mas_assert( err >= 0, (char*)"Couldn't get anx device" );
|
|
debug() << " after mas_asm_get_device_by_name" << endl;
|
|
|
|
m_sink_clkid = 0;
|
|
err = mas_get( anx, (char*)"mc_clkid", NULL, &nugget );
|
|
if ( err >= 0 )
|
|
{
|
|
masc_pull_int32( &nugget, &m_sink_clkid );
|
|
masc_strike_package( &nugget );
|
|
}
|
|
debug() <<" after mas_get (mc_clkid)" << endl;
|
|
|
|
/* CODEC */
|
|
err = mas_asm_instantiate_device( (char*)"codec_mp1a_mad", 0, 0, &m_codec );
|
|
if (err < 0 )
|
|
{
|
|
masc_log_message( MAS_VERBLVL_INFO, (char*)"Couldn't instantiate mp1a_mad codec, trying mp1a codec.");
|
|
err = mas_asm_instantiate_device( (char*)"codec_mp1a", 0, 0, &m_codec );
|
|
mas_assert( err >= 0, (char*)"Couldn't instantiate MPEG codec");
|
|
}
|
|
debug() << " after mas_asm_instantiate_device (codec_mp1a_mad)" << endl;
|
|
|
|
/* source - instantiated on the local MAS server */
|
|
err = mas_asm_instantiate_device_on_channel( (char*)"source_mp1a", 0, 0, &m_mp1a_source_device, local );
|
|
mas_assert( err >= 0, (char*)"Couldn't instantiate source_mp1a" );
|
|
|
|
/* buffer */
|
|
err = mas_asm_instantiate_device( (char*)"sbuf", 0, 0, &m_sbuf );
|
|
mas_assert( err >= 0, (char*)"Couldn't instantiate sbuf device" );
|
|
debug() << " after mas_asm_instantiate_device (sbuf)" << endl;
|
|
|
|
m_visual = NULL;
|
|
#ifdef USE_VISUAL
|
|
err = mas_asm_instantiate_device( (char*)"visual", 0, 0, &m_visual );
|
|
if ( err < 0 )
|
|
{
|
|
masc_log_message( 0, (char*)"Couldn't instantiate visual device. It's okay, I just won't use it." );
|
|
m_visual = NULL;
|
|
}
|
|
debug() << " after mas_asm_instantiate_device (visual)" << endl;
|
|
#endif
|
|
|
|
|
|
/* get a handle to the mixer */
|
|
err = mas_asm_get_device_by_name( (char*)"mix", &m_mix_device );
|
|
mas_assert( err >= 0, (char*)"Couldn't get mixer device" );
|
|
debug() << " after mas_asm_get_device_by_name (mix)" << endl;
|
|
|
|
/* start making connections:
|
|
*
|
|
* source->codec->visual->mix
|
|
*/
|
|
|
|
err = mas_asm_connect_devices( m_mp1a_source_device, m_codec, (char*)"source", (char*)"sink" );
|
|
mas_assert( err >= 0, "Couldn't connect MPEG source to MPEG codec" );
|
|
|
|
dc = masc_make_audio_basic_dc( MAS_LINEAR_FMT, 44100, 20, 2, MAS_HOST_ENDIAN_FMT );
|
|
mas_assert( dc, "Couldn't create audio data characteristic." );
|
|
|
|
err = mas_asm_get_port_by_name( m_mix_device, (char*)"default_mix_sink", &m_mix_sink );
|
|
mas_assert( err >= 0, "Couldn't get default mixer sink" );
|
|
debug() << " m_mix_sink:" << m_mix_sink << endl;
|
|
|
|
err = mas_asm_connect_devices_dc( m_codec, m_sbuf, (char*)"source", (char*)"sink", dc );
|
|
mas_assert( err >= 0, "Couldn't connect MPEG codec to sbuf" );
|
|
debug() << " after mas_asm_connect_device_dc (source, sink)" << endl;
|
|
|
|
if (m_visual != NULL)
|
|
{
|
|
err = mas_asm_connect_devices_dc( m_sbuf, m_visual, (char*)"source", (char*)"sink", dc );
|
|
mas_assert( err >= 0, "Couldn't connect sbuf to visual device." );
|
|
|
|
err = mas_asm_get_port_by_name( m_visual, (char*)"source", &tmp_source );
|
|
mas_assert( err >= 0, "Couldn't get visual source port" );
|
|
|
|
err = mas_asm_connect_source_sink( tmp_source, m_mix_sink, dc );
|
|
mas_assert( err >= 0, "Couldn't connect visual device to mixer sink." );
|
|
}
|
|
else
|
|
{
|
|
err = mas_asm_get_port_by_name( m_sbuf, (char*)"source", &tmp_source );
|
|
mas_assert( err >= 0, "Couldn't get sbuf source port" );
|
|
|
|
err = mas_asm_connect_source_sink( tmp_source, m_mix_sink, dc );
|
|
mas_assert( err >= 0, "Couldn't connect sbuf to mix device." );
|
|
}
|
|
debug() << " after ... visual ..." << endl;
|
|
|
|
/* set the buffer time */
|
|
masc_setup_package( &nugget, NULL, 0, 0 );
|
|
masc_pushk_uint32( &nugget, (char*)"buftime_ms", BUFFER_TIME_MS );
|
|
masc_finalize_package( &nugget );
|
|
mas_set( m_sbuf, (char*)"buftime_ms", &nugget );
|
|
masc_strike_package( &nugget );
|
|
|
|
debug() << " after ... masc_strike_package ..." << endl;
|
|
|
|
masc_setup_package( &nugget, NULL, 0, 0 );
|
|
masc_pushk_uint32( &nugget, "postout_time_ms", POSTOUT_TIME_MS );
|
|
masc_finalize_package( &nugget );
|
|
mas_set( m_sbuf, "postout_time_ms", &nugget );
|
|
masc_strike_package( &nugget );
|
|
|
|
/* If we can use a sample clock, let's do it... */
|
|
if ( m_sink_clkid > 0 )
|
|
{
|
|
mas_get_mc_device( &m_sink_mc );
|
|
mas_get_mc_device_on_channel( &m_source_mc, local );
|
|
debug() << " after ... masc_get_mc_device_on_channel ..." << endl;
|
|
|
|
//get_measured_sample_freq();
|
|
/*
|
|
m_source_clkid = mas_mc_add_fixed_clock( "player", m_measured_sample_freq, local );
|
|
printf (" after ... masc_mc_add_fixed_clock ...\n");
|
|
masc_log_message( 0, "got clock: %d", m_source_clkid );
|
|
*/
|
|
/* the file source device uses the file clock */
|
|
/* masc_setup_package( &nugget, NULL, 0, 0 );
|
|
masc_pushk_int32( &nugget, "mc_clkid", m_source_clkid );
|
|
masc_finalize_package( &nugget );
|
|
mas_set( mp1a_source_device, "mc_clkid", &nugget );
|
|
masc_strike_package( &nugget );
|
|
*/
|
|
/* and the sbuf, which could be on a different machine, uses
|
|
the sample clock from that machine's anx device. */
|
|
/* masc_setup_package( &nugget, NULL, 0, 0 );
|
|
masc_pushk_int32( &nugget, "mc_clkid", m_sink_clkid );
|
|
masc_finalize_package( &nugget );
|
|
mas_set( m_sbuf, "mc_clkid", &nugget );
|
|
masc_strike_package( &nugget );
|
|
*/
|
|
}
|
|
|
|
return true;
|
|
} // masinit
|
|
|
|
#include "masengine.moc"
|
|
|