summaryrefslogtreecommitdiff
path: root/vdr_decoder_ogg.c
diff options
context:
space:
mode:
Diffstat (limited to 'vdr_decoder_ogg.c')
-rw-r--r--vdr_decoder_ogg.c393
1 files changed, 393 insertions, 0 deletions
diff --git a/vdr_decoder_ogg.c b/vdr_decoder_ogg.c
new file mode 100644
index 0000000..9525979
--- /dev/null
+++ b/vdr_decoder_ogg.c
@@ -0,0 +1,393 @@
+/*! \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 <huels@iname.com>
+ */
+
+
+#ifdef HAVE_VORBISFILE
+
+#include "vdr_decoder_ogg.h"
+
+#include <mad.h>
+#include <vorbis/vorbisfile.h>
+
+#include <string>
+#include <stdlib.h>
+#include <stdio.h>
+
+#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