/*! \file vdr_decoder_ogg.c * \ingroup vdr * * The file implements a decoder which is used by the player to decode ogg vorbis audio files. * * Adapted from * MP3/MPlayer plugin to VDR (C++) * (C) 2001-2003 Stefan Huelswitt */ #ifdef HAVE_VORBISFILE #include "vdr_decoder_ogg.h" #include #include #include #include #include #include "mg_content_interface.h" using namespace std; // --- mgOggFile ---------------------------------------------------------------- class mgOggFile // : public mgFileInfo { private: bool m_opened, m_canSeek; OggVorbis_File vf; void error( const char *action, const int err ); string m_filename; public: mgOggFile( string filename ); ~mgOggFile(); bool open(bool log=true); void close(void); long long seek(long long posMs=0, bool relativ=false); int stream(short *buffer, int samples); bool canSeek() { return m_canSeek; } long long indexMs(void); }; mgOggFile::mgOggFile( string filename ) : m_filename( filename ) { m_canSeek = false; m_opened = false; } mgOggFile::~mgOggFile() { close(); } bool mgOggFile::open(bool log) { if( m_opened ) { if( m_canSeek ) { return ( seek() >= 0 ); } return true; } FILE *f = fopen( m_filename.c_str(), "r" ); if( f ) { int r = ov_open( f, &vf, 0, 0 ); if( !r ) { m_canSeek = ( ov_seekable( &vf ) !=0 ); m_opened = true; } else { fclose( f ); if( log ) { error( "open", r ); } } } else { if(log) { // esyslog("ERROR: failed to open file %s: %s", m_filename.c_str(), strerror(errno) ); } } return m_opened; } void mgOggFile::close() { if( m_opened ) { ov_clear( &vf ); m_opened = false; } } void mgOggFile::error( const char *action, const int err ) { char *errstr; switch(err) { case OV_FALSE: errstr = "false/no data available"; break; case OV_EOF: errstr = "EOF"; break; case OV_HOLE: errstr = "missing or corrupted data"; break; case OV_EREAD: errstr = "read error"; break; case OV_EFAULT: errstr = "internal error"; break; case OV_EIMPL: errstr = "unimplemented feature"; break; case OV_EINVAL: errstr = "invalid argument"; break; case OV_ENOTVORBIS: errstr = "no Ogg Vorbis stream"; break; case OV_EBADHEADER: errstr = "corrupted Ogg Vorbis stream"; break; case OV_EVERSION: errstr = "unsupported bitstream version"; break; case OV_ENOTAUDIO: errstr = "ENOTAUDIO"; break; case OV_EBADPACKET: errstr = "EBADPACKET"; break; case OV_EBADLINK: errstr = "corrupted link"; break; case OV_ENOSEEK: errstr = "stream not seekable"; break; default: errstr = "unspecified error"; break; } // esyslog( "ERROR: vorbisfile %s failed on %s: %s", action, m_filename.c_str(), errstr ); } long long mgOggFile::indexMs(void) { double p = ov_time_tell(&vf); if( p < 0.0 ) { p = 0.0; } return (long long)( p*1000.0 ); } long long mgOggFile::seek( long long posMs, bool relativ ) { if( relativ ) { posMs += indexMs(); } int r = ov_time_seek( &vf, (double) posMs/1000.0 ); if(r) { error( "seek", r ); return -1; } posMs = indexMs(); return posMs; } int mgOggFile::stream( short *buffer, int samples ) { int n; do { int stream; n = ov_read( &vf, (char *)buffer, samples*2, 0, 2, 1, &stream ); } while( n == OV_HOLE ); if(n < 0) { error( "read", n ); } return (n/2); } // --- mgOggDecoder ------------------------------------------------------------- mgOggDecoder::mgOggDecoder( mgContentItem *item ) : mgDecoder( item ) { m_filename = item->getSourceFile(); m_file = new mgOggFile( m_filename ); m_pcm = 0; init(); } mgOggDecoder::~mgOggDecoder() { delete m_file; clean(); } bool mgOggDecoder::valid() { bool res = false; if( tryLock() ) { if( m_file->open( false ) ) { res = true; } unlock(); } return res; } mgPlayInfo *mgOggDecoder::playInfo(void) { if( m_playing ) { // m_playinfo.m_index = index/1000; // m_playinfo.m_total = info.Total; return &m_playinfo; } return 0; } void mgOggDecoder::init() { clean(); m_pcm = new struct mad_pcm; m_index = 0; } bool mgOggDecoder::clean() { m_playing = false; delete m_pcm; m_pcm = 0; m_file->close(); return false; } #define SF_SAMPLES (sizeof(m_pcm->samples[0])/sizeof(mad_fixed_t)) bool mgOggDecoder::start() { lock(true); init(); m_playing = true; if( m_file->open() /*&& info.DoScan(true)*/ ) { // obtain from database: rate, channels /* d(printf("ogg: open rate=%d channels=%d seek=%d\n", info.SampleFreq,info.Channels,file.CanSeek())) */ if( m_item->getChannels() <= 2 ) { unlock(); return true; } else { // esyslog( "ERROR: cannot play ogg file %s: more than 2 channels", m_filename.c_str() ); } } clean(); unlock(); return false; } bool mgOggDecoder::stop(void) { lock(); if( m_playing ) { clean(); } unlock(); return true; } struct mgDecode *mgOggDecoder::done(eDecodeStatus status) { m_ds.status = status; m_ds.index = m_index; m_ds.pcm = m_pcm; unlock(); // release the lock from decode() return &m_ds; } struct mgDecode *mgOggDecoder::decode(void) { lock(); // this is released in Done() if( m_playing ) { short framebuff[2*SF_SAMPLES]; int n = m_file->stream( framebuff, SF_SAMPLES ); if( n < 0 ) { return done(dsError); } if( n == 0 ) { return done(dsEof); } // should be done during initialization m_pcm->samplerate = m_item->getSampleRate(); // from database m_pcm->channels = m_item->getChannels(); // from database n /= m_pcm->channels; m_pcm->length = n; m_index = m_file->indexMs(); short *data = framebuff; mad_fixed_t *sam0 = m_pcm->samples[0], *sam1 = m_pcm->samples[1]; const int s = MAD_F_FRACBITS + 1 - ( sizeof(short)*8 ); // shift value for mad_fixed conversion if( m_pcm->channels>1 ) { for(; n > 0 ; n-- ) { *sam0++=(*data++) << s; *sam1++=(*data++) << s; } } else { for(; n>0 ; n--) { *sam0++=(*data++) << s; } } return done(dsPlay); } return done(dsError); } bool mgOggDecoder::skip(int Seconds, int Avail, int Rate) { lock(); bool res = false; if( m_playing && m_file->canSeek() ) { float fsecs = (float)Seconds - ( (float)Avail / (float)(Rate * (16/8 * 2) ) ); // Byte/s = samplerate * 16 bit * 2 chan long long newpos = m_file->indexMs() + (long long)(fsecs*1000.0); if( newpos < 0 ) { newpos=0; } newpos = m_file->seek( newpos, false ); if( newpos >= 0 ) { m_index = m_file->indexMs(); #ifdef DEBUG int i = index/1000; printf( "ogg: skipping to %02d:%02d\n", i/60, i%60 ); #endif res = true; } } unlock(); return res; } #endif //HAVE_VORBISFILE