/* * MP3/MPlayer plugin to VDR (C++) * * (C) 2001-2009 Stefan Huelswitt * * This code 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. * * This code 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * Or, point your browser to http://www.gnu.org/copyleft/gpl.html */ #ifdef HAVE_VORBISFILE #include #include #include #include "common.h" #include "decoder-ogg.h" // --- cOggFile ---------------------------------------------------------------- cOggFile::cOggFile(const char *Filename) :cFileInfo(Filename) { canSeek=opened=false; } cOggFile::~cOggFile() { Close(); } bool cOggFile::Open(bool log) { if(opened) { if(canSeek) return (Seek()>=0); return true; } if(FileInfo(log)) { FILE *f=fopen(Filename,"r"); if(f) { int r=ov_open(f,&vf,0,0); if(!r) { canSeek=(ov_seekable(&vf)!=0); opened=true; } else { fclose(f); if(log) Error("open",r); } } else if(log) { esyslog("ERROR: failed to open file %s: %s",Filename,strerror(errno)); } } return opened; } void cOggFile::Close(void) { if(opened) { ov_clear(&vf); opened=false; } } void cOggFile::Error(const char *action, const int err) { const 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,Filename,errstr); } long long cOggFile::IndexMs(void) { double p=ov_time_tell(&vf); if(p<0.0) p=0.0; return (long long)(p*1000.0); } long long cOggFile::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 cOggFile::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); } // --- cOggInfo ---------------------------------------------------------------- cOggInfo::cOggInfo(cOggFile *File) { file=File; } bool cOggInfo::Abort(bool result) { if(!keepOpen) file->Close(); return result; } bool cOggInfo::DoScan(bool KeepOpen) { keepOpen=KeepOpen; if(!file->Open()) return Abort(false); if(HasInfo()) return Abort(true); // check the infocache cCacheData *dat=InfoCache.Search(file); if(dat) { Set(dat); dat->Unlock(); ConvertToSys(); if(!DecoderID) { DecoderID=DEC_OGG; InfoCache.Cache(this,file); } return Abort(true); } Clear(); vorbis_comment *vc=ov_comment(&file->vf,-1); if(vc) { for(int i=0 ; icomments ; i++) { const char *cc=vc->user_comments[i]; d(printf("ogg: comment%d='%s'\n",i,cc)) const char *p=strchr(cc,'='); if(p) { const int len=p-cc; p++; if(!strncasecmp(cc,"TITLE",len)) { if(!Title) Title=strdup(p); } else if(!strncasecmp(cc,"ARTIST",len)) { if(!Artist) Artist=strdup(p); } else if(!strncasecmp(cc,"ALBUM",len)) { if(!Album) Album=strdup(p); } else if(!strncasecmp(cc,"YEAR",len)) { if(Year<0) { Year=atoi(p); if(Year<1800 || Year>2100) Year=-1; } } } } } if(!Title) FakeTitle(file->Filename); vorbis_info *vi=ov_info(&file->vf,-1); if(!vi) Abort(false); d(printf("ogg: info ch=%d srate=%ld brate_low=%ld brate_high=%ld brate_avg=%ld\n", vi->channels,vi->rate,vi->bitrate_lower,vi->bitrate_upper,vi->bitrate_nominal)) Channels=vi->channels; ChMode=Channels>1 ? 3:0; SampleFreq=vi->rate; if(vi->bitrate_upper>0 && vi->bitrate_lower>0) { Bitrate=vi->bitrate_lower; MaxBitrate=vi->bitrate_upper; } else Bitrate=vi->bitrate_nominal; Total=(int)ov_time_total(&file->vf,-1); Frames=-1; DecoderID=DEC_OGG; InfoDone(); InfoCache.Cache(this,file); ConvertToSys(); return Abort(true); } // --- cOggDecoder ------------------------------------------------------------- cOggDecoder::cOggDecoder(const char *Filename, bool preinit) :cDecoder(Filename) { file=0; info=0; pcm=0; if(preinit) { file=new cOggFile(Filename); info=new cOggInfo(file); } } cOggDecoder::~cOggDecoder() { Clean(); delete info; delete file; } bool cOggDecoder::Valid(void) { bool res=false; if(TryLock()) { if(file->Open(false)) res=true; Unlock(); } return res; } cFileInfo *cOggDecoder::FileInfo(void) { cFileInfo *fi=0; if(file->HasInfo()) fi=file; else if(TryLock()){ if(file->Open()) { fi=file; file->Close(); } Unlock(); } return fi; } cSongInfo *cOggDecoder::SongInfo(bool get) { cSongInfo *si=0; if(info->HasInfo()) si=info; else if(get && TryLock()) { if(info->DoScan(false)) si=info; Unlock(); } return si; } cPlayInfo *cOggDecoder::PlayInfo(void) { if(playing) { pi.Index=index/1000; pi.Total=info->Total; return π } return 0; } void cOggDecoder::Init(void) { Clean(); pcm=new struct mad_pcm; index=0; } bool cOggDecoder::Clean(void) { playing=false; delete pcm; pcm=0; file->Close(); return false; } #define SF_SAMPLES (sizeof(pcm->samples[0])/sizeof(mad_fixed_t)) bool cOggDecoder::Start(void) { Lock(true); Init(); playing=true; if(file->Open() && info->DoScan(true)) { d(printf("ogg: open rate=%d channels=%d seek=%d\n", info->SampleFreq,info->Channels,file->CanSeek())) if(info->Channels<=2) { Unlock(); return true; } else esyslog("ERROR: cannot play ogg file %s: more than 2 channels",filename); } Clean(); Unlock(); return false; } bool cOggDecoder::Stop(void) { Lock(); if(playing) Clean(); Unlock(); return true; } struct Decode *cOggDecoder::Done(eDecodeStatus status) { ds.status=status; ds.index=index; ds.pcm=pcm; Unlock(); // release the lock from Decode() return &ds; } struct Decode *cOggDecoder::Decode(void) { Lock(); // this is released in Done() if(playing) { short framebuff[2*SF_SAMPLES]; int n=file->Stream(framebuff,SF_SAMPLES); if(n<0) return Done(dsError); if(n==0) return Done(dsEof); pcm->samplerate=info->SampleFreq; pcm->channels=info->Channels; n/=pcm->channels; pcm->length=n; index=file->IndexMs(); short *data=framebuff; mad_fixed_t *sam0=pcm->samples[0], *sam1=pcm->samples[1]; const int s=MAD_F_FRACBITS+1-(sizeof(short)*8); // shift value for mad_fixed conversion if(pcm->channels>1) { for(; n>0 ; n--) { *sam0++=(*data++) << s; *sam1++=(*data++) << s; } } else { for(; n>0 ; n--) *sam0++=(*data++) << s; } info->InfoHook(); return Done(dsPlay); } return Done(dsError); } bool cOggDecoder::Skip(int Seconds, float bsecs) { Lock(); bool res=false; if(playing && file->CanSeek()) { float fsecs=(float)Seconds - bsecs; long long newpos=file->IndexMs()+(long long)(fsecs*1000.0); if(newpos<0) newpos=0; d(printf("ogg: skip: secs=%d fsecs=%f current=%lld new=%lld\n",Seconds,fsecs,file->IndexMs(),newpos)) newpos=file->Seek(newpos,false); if(newpos>=0) { index=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