/*! * \file vdr_decoder_mp3.h * \brief An mp3 decoder for a VDR media plugin (muggle) * * \version $Revision: 1.2 $ * \date $Date: 2004/05/28 15:29:19 $ * \author Ralf Klueber, Lars von Wedel, Andreas Kellner * \author Responsible author: $Author: lvw $ * * $Id: vdr_decoder_mp3.c,v 1.2 2004/05/28 15:29:19 lvw Exp $ * * Adapted from * MP3/MPlayer plugin to VDR (C++) * (C) 2001,2002 Stefan Huelswitt */ #include #include #include #include #include #include #include #include #include "vdr_config.h" #include "vdr_decoder_mp3.h" #include "vdr_stream.h" #include "mg_tools.h" #define d(x) x using namespace std; // ---------------------------------------------------------------- int mgMadStream(struct mad_stream *stream, mgStream *str) { unsigned char *data; unsigned long len; if( str->stream( data, len, stream->next_frame ) ) { if( len > 0 ) { mad_stream_buffer(stream, data, len); } return len; } return -1; } // --- mgMP3Decoder ------------------------------------------------------------- mgMP3Decoder::mgMP3Decoder(string filename, bool preinit) : mgDecoder(filename) { MGLOG( "mgMP3Decoder::mgMP3Decoder" ); m_stream = 0; m_isStream = false; if( preinit ) { m_stream = new mgStream( filename ); } m_madframe = 0; m_madsynth = 0; memset( &m_madstream, 0, sizeof(m_madstream) ); init(); } mgMP3Decoder::~mgMP3Decoder() { MGLOG( "mgMP3Decoder::~mgMP3Decoder" ); clean(); delete m_stream; } void mgMP3Decoder::init() { MGLOG( "mgMP3Decoder::init" ); clean(); mad_stream_init( &m_madstream ); m_madframe = new mad_frame; mad_frame_init( m_madframe ); m_madsynth = new mad_synth; mad_synth_init( m_madsynth ); mad_stream_options( &m_madstream, MAD_OPTION_IGNORECRC); m_playtime = mad_timer_zero; m_skiptime = mad_timer_zero; m_framenum = m_framemax = 0; m_mute = m_errcount = 0; } void mgMP3Decoder::clean() { MGLOG( "mgMP3Decoder::clean" ); m_playing = false; if( m_madsynth ) { mad_synth_finish( m_madsynth ); delete m_madsynth; m_madsynth = 0; } if( m_madframe ) { mad_frame_finish( m_madframe ); delete m_madframe; m_madframe = 0; } mad_stream_finish( &m_madstream ); /* if( m_frameinfo ) { cout << "mgMP3Decoder::clean: m_frameinfo deleted." << endl << flush; delete m_frameinfo; cout << "mgMP3Decoder::clean: m_frameinfo deleted." << endl << flush; m_frameinfo = 0; } */ } bool mgMP3Decoder::valid(void) { MGLOG( "mgMP3Decoder::valid" ); bool res = false; if( tryLock() ) { if( start() ) { struct mgDecode *dd; int count = 10; do { dd = decode(); if( dd->status == dsEof ) { count=0; } if( dd->status != dsPlay) { break; } } while( --count ); if( !count ) { res=true; } stop(); } unlock(); } return res; } mgPlayInfo *mgMP3Decoder::playInfo() { MGLOG( "mgMP3Decoder::playInfo" ); if( m_playing ) { m_playinfo.m_index = mad_timer_count( m_playtime, MAD_UNITS_SECONDS ); // m_playinfo.Total = m_scan->Total; // TODO return &m_playinfo; } return 0; } bool mgMP3Decoder::start() { MGLOG( "mgMP3Decoder::start" ); lock(true); init(); m_playing = true; if( m_stream->open(true) ) { if(!m_isStream) { m_stream->seek(); printf( "mgMP3Decoder::start: m_framemax not determined, rewinding disabled!!!\n" ); /* m_framemax = scan->Frames+20; // TODO m_frameinfo = new struct FrameInfo[m_framemax]; if(!m_frameinfo) { printf( "mgMP3Decoder::start: no memory for frame index, rewinding disabled" ); } */ } unlock(); printf( "mgMP3Decoder::start: true\n" ); return true; } m_stream->close(); clean(); unlock(); printf( "mgMP3Decoder::start: false" ); return false; } bool mgMP3Decoder::stop(void) { MGLOG( "mgMP3Decoder::stop" ); lock(); if( m_playing ) { m_stream->close(); clean(); } unlock(); return true; } struct mgDecode *mgMP3Decoder::done(eDecodeStatus status) { // MGLOG( "mgMP3Decoder::done" ); m_ds.status = status; m_ds.index = mad_timer_count( m_playtime, MAD_UNITS_MILLISECONDS ); m_ds.pcm = &m_madsynth->pcm; unlock(); // release the lock from Decode() return &m_ds; } eDecodeStatus mgMP3Decoder::decodeError(bool hdr) { MGLOG( "mgMP3Decoder::decodeError" ); if( m_madstream.error == MAD_ERROR_BUFLEN || m_madstream.error == MAD_ERROR_BUFPTR ) { int s = mgMadStream( &m_madstream, m_stream ); if( s < 0 ) { printf( "mgMP3Decoder::decodeError: dsError returned\n" ); return dsError; } if( s == 0 ) { printf( "mgMP3Decoder::decodeError: dsEof returned\n" ); return dsEof; } } else if( !MAD_RECOVERABLE( m_madstream.error ) ) { printf( "mgMP3Decoder::decodeError: mad decode %sfailed, frame=%d: %s. Returning dsError\n", hdr? "hdr " : "", m_framenum, mad_stream_errorstr( &m_madstream ) ); return dsError; } else { m_errcount += hdr? 1: 100; printf( "mgMP3Decoder::decodeError: mad decode %s error, frame=%d count=%d: %s. Returning dsOK\n", hdr? "hdr ": "", m_framenum, m_errcount, mad_stream_errorstr( &m_madstream ) ); } return dsOK; } struct mgDecode *mgMP3Decoder::decode() { // MGLOG( "mgMP3Decoder::decode" ); lock(); // this is released in Done() eDecodeStatus r; while( m_playing ) { if( m_errcount >= MAX_FRAME_ERR*100 ) { printf( "mgMP3Decoder::decode: excessive decoding errors," " aborting file %s\n", m_filename.c_str() ); return done(dsError); } if( mad_header_decode( &m_madframe->header, &m_madstream) == -1) { if( (r = decodeError(true) ) ) { return done(r); } } else { if(!m_isStream) { #ifdef DEBUG2 if( m_framenum >= m_framemax ) { printf( "mgMP3Decoder::start: framenum >= framemax!!!!\n" ); } #endif if( m_frameinfo && m_framenum < m_framemax ) { m_frameinfo[m_framenum].Pos = m_stream->bufferPos() + ( m_madstream.this_frame - m_madstream.buffer ); m_frameinfo[m_framenum].Time = m_playtime; } } mad_timer_add( &m_playtime, m_madframe->header.duration); m_framenum ++; if( mad_timer_compare(m_playtime, m_skiptime) >= 0 ) { m_skiptime = mad_timer_zero; } else { return done(dsSkip); // skipping, decode next header } if( mad_frame_decode( m_madframe, &m_madstream ) == -1 ) { if( ( r = decodeError(false) ) ) { return done(r); } } else { m_errcount = 0; // TODO: // m_scan->InfoHook( &frame->header ); mad_synth_frame( m_madsynth, m_madframe); if( m_mute ) { m_mute--; return done( dsSkip ); } return done( dsPlay ); } } } return done( dsError ); } void mgMP3Decoder::makeSkipTime( mad_timer_t *skiptime, mad_timer_t playtime, int secs, int avail, int dvbrate) { mad_timer_t time; *skiptime = playtime; mad_timer_set( &time, abs(secs), 0, 0 ); if( secs < 0 ) { mad_timer_negate(&time); } mad_timer_add( skiptime, time ); float bufsecs = (float)avail / (float)(dvbrate * (16/8 * 2)); // Byte/s = samplerate * 16 bit * 2 chan printf( "mgMP3Decoder::makeSkipTime: skip: avail=%d bufsecs=%f\n", avail, bufsecs ); int full = (int)bufsecs; bufsecs -= (float)full; mad_timer_set( &time, full, (int)(bufsecs*1000.0), 1000); mad_timer_negate(&time); mad_timer_add( skiptime, time ); printf( "mgMP3Decoder::makeSkipTime: skip: playtime=%ld secs=%d full=%d bufsecs=%f skiptime=%ld\n", mad_timer_count(playtime,MAD_UNITS_MILLISECONDS), secs, full, bufsecs, mad_timer_count(*skiptime,MAD_UNITS_MILLISECONDS ) ); } bool mgMP3Decoder::skip( int seconds, int avail, int rate ) { lock(); bool res = false; if( m_playing && !m_isStream ) { if( !mad_timer_compare( m_skiptime, mad_timer_zero ) ) { // allow only one skip at any time mad_timer_t time; makeSkipTime( &time, m_playtime, seconds, avail, rate); if( mad_timer_compare( m_playtime, time ) <= 0 ) { // forward skip #ifdef DEBUG int i = mad_timer_count( time, MAD_UNITS_SECONDS ); printf( "mgMP3Decoder::skip: forward skipping to %02d:%02d\n", i/60, i%60 ); #endif m_skiptime = time; m_mute=1; res = true; } else { // backward skip if( m_frameinfo ) { #ifdef DEBUG int i = mad_timer_count( time, MAD_UNITS_SECONDS ); printf( "mgMP3Decoder::skip: rewinding to %02d:%02d\n", i/60, i%60 ); #endif while( m_framenum && mad_timer_compare( time, m_frameinfo[--m_framenum].Time) < 0) ; m_mute = 2; if( m_framenum >= 2) { m_framenum-=2; } m_playtime = m_frameinfo[m_framenum].Time; m_stream->seek( m_frameinfo[m_framenum].Pos ); mad_stream_finish( &m_madstream ); // reset stream buffer mad_stream_init( &m_madstream ); #ifdef DEBUG i = mad_timer_count( m_playtime, MAD_UNITS_MILLISECONDS ); printf( "mgMP3Decoder::skip: new playtime=%d framenum=%d filepos=%lld\n", i, m_framenum, m_frameinfo[m_framenum].Pos); #endif res = true; } } } } unlock(); return res; }