nathan@0: /* nathan@0: * MP3/MPlayer plugin to VDR (C++) nathan@0: * nathan@0: * (C) 2001-2005 Stefan Huelswitt nathan@0: * nathan@0: * This code is free software; you can redistribute it and/or nathan@0: * modify it under the terms of the GNU General Public License nathan@0: * as published by the Free Software Foundation; either version 2 nathan@0: * of the License, or (at your option) any later version. nathan@0: * nathan@0: * This code is distributed in the hope that it will be useful, nathan@0: * but WITHOUT ANY WARRANTY; without even the implied warranty of nathan@0: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the nathan@0: * GNU General Public License for more details. nathan@0: * nathan@0: * You should have received a copy of the GNU General Public License nathan@0: * along with this program; if not, write to the Free Software nathan@0: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. nathan@0: * Or, point your browser to http://www.gnu.org/copyleft/gpl.html nathan@0: */ nathan@0: nathan@0: #ifdef HAVE_VORBISFILE nathan@0: nathan@0: #include nathan@0: #include nathan@0: #include nathan@0: nathan@0: #include "common.h" nathan@0: #include "decoder-ogg.h" nathan@0: nathan@0: // --- cOggFile ---------------------------------------------------------------- nathan@0: nathan@0: cOggFile::cOggFile(const char *Filename) nathan@0: :cFileInfo(Filename) nathan@0: { nathan@0: canSeek=opened=false; nathan@0: } nathan@0: nathan@0: cOggFile::~cOggFile() nathan@0: { nathan@0: Close(); nathan@0: } nathan@0: nathan@0: bool cOggFile::Open(bool log) nathan@0: { nathan@0: if(opened) { nathan@0: if(canSeek) return (Seek()>=0); nathan@0: return true; nathan@0: } nathan@0: nathan@0: if(FileInfo(log)) { nathan@0: FILE *f=fopen(Filename,"r"); nathan@0: if(f) { nathan@0: int r=ov_open(f,&vf,0,0); nathan@0: if(!r) { nathan@0: canSeek=(ov_seekable(&vf)!=0); nathan@0: opened=true; nathan@0: } nathan@0: else { nathan@0: fclose(f); nathan@0: if(log) Error("open",r); nathan@0: } nathan@0: } nathan@0: else if(log) { esyslog("ERROR: failed to open file %s: %s",Filename,strerror(errno)); } nathan@0: } nathan@0: return opened; nathan@0: } nathan@0: nathan@0: void cOggFile::Close(void) nathan@0: { nathan@0: if(opened) { ov_clear(&vf); opened=false; } nathan@0: } nathan@0: nathan@0: void cOggFile::Error(const char *action, const int err) nathan@0: { nathan@0: char *errstr; nathan@0: switch(err) { nathan@0: case OV_FALSE: errstr="false/no data available"; break; nathan@0: case OV_EOF: errstr="EOF"; break; nathan@0: case OV_HOLE: errstr="missing or corrupted data"; break; nathan@0: case OV_EREAD: errstr="read error"; break; nathan@0: case OV_EFAULT: errstr="internal error"; break; nathan@0: case OV_EIMPL: errstr="unimplemented feature"; break; nathan@0: case OV_EINVAL: errstr="invalid argument"; break; nathan@0: case OV_ENOTVORBIS: errstr="no Ogg Vorbis stream"; break; nathan@0: case OV_EBADHEADER: errstr="corrupted Ogg Vorbis stream"; break; nathan@0: case OV_EVERSION: errstr="unsupported bitstream version"; break; nathan@0: case OV_ENOTAUDIO: errstr="ENOTAUDIO"; break; nathan@0: case OV_EBADPACKET: errstr="EBADPACKET"; break; nathan@0: case OV_EBADLINK: errstr="corrupted link"; break; nathan@0: case OV_ENOSEEK: errstr="stream not seekable"; break; nathan@0: default: errstr="unspecified error"; break; nathan@0: } nathan@0: esyslog("ERROR: vorbisfile %s failed on %s: %s",action,Filename,errstr); nathan@0: } nathan@0: nathan@0: long long cOggFile::IndexMs(void) nathan@0: { nathan@0: double p=ov_time_tell(&vf); nathan@0: if(p<0.0) p=0.0; nathan@0: return (long long)(p*1000.0); nathan@0: } nathan@0: nathan@0: long long cOggFile::Seek(long long posMs, bool relativ) nathan@0: { nathan@0: if(relativ) posMs+=IndexMs(); nathan@0: int r=ov_time_seek(&vf,(double)posMs/1000.0); nathan@0: if(r) { nathan@0: Error("seek",r); nathan@0: return -1; nathan@0: } nathan@0: posMs=IndexMs(); nathan@0: return posMs; nathan@0: } nathan@0: nathan@0: int cOggFile::Stream(short *buffer, int samples) nathan@0: { nathan@0: int n; nathan@0: do { nathan@0: int stream; nathan@0: n=ov_read(&vf,(char *)buffer,samples*2,0,2,1,&stream); nathan@0: } while(n==OV_HOLE); nathan@0: if(n<0) Error("read",n); nathan@0: return (n/2); nathan@0: } nathan@0: nathan@0: // --- cOggInfo ---------------------------------------------------------------- nathan@0: nathan@0: cOggInfo::cOggInfo(cOggFile *File) nathan@0: { nathan@0: file=File; nathan@0: } nathan@0: nathan@0: bool cOggInfo::Abort(bool result) nathan@0: { nathan@0: if(!keepOpen) file->Close(); nathan@0: return result; nathan@0: } nathan@0: nathan@0: bool cOggInfo::DoScan(bool KeepOpen) nathan@0: { nathan@0: keepOpen=KeepOpen; nathan@0: if(!file->Open()) return Abort(false); nathan@0: if(HasInfo()) return Abort(true); nathan@0: nathan@0: // check the infocache nathan@0: cCacheData *dat=InfoCache.Search(file); nathan@0: if(dat) { nathan@0: Set(dat); dat->Unlock(); nathan@0: if(!DecoderID) { nathan@0: DecoderID=DEC_OGG; nathan@0: InfoCache.Cache(this,file); nathan@0: } nathan@0: return Abort(true); nathan@0: } nathan@0: nathan@0: Clear(); nathan@0: nathan@0: vorbis_comment *vc=ov_comment(&file->vf,-1); nathan@0: if(vc) { nathan@0: for(int i=0 ; icomments ; i++) { nathan@0: const char *cc=vc->user_comments[i]; nathan@0: d(printf("ogg: comment%d='%s'\n",i,cc)) nathan@0: char *p=strchr(cc,'='); nathan@0: if(p) { nathan@0: const int len=p-cc; nathan@0: p++; nathan@0: if(!strncasecmp(cc,"TITLE",len)) { nathan@0: if(!Title) Title=strdup(p); nathan@0: } nathan@0: else if(!strncasecmp(cc,"ARTIST",len)) { nathan@0: if(!Artist) Artist=strdup(p); nathan@0: } nathan@0: else if(!strncasecmp(cc,"ALBUM",len)) { nathan@0: if(!Album) Album=strdup(p); nathan@0: } nathan@0: else if(!strncasecmp(cc,"YEAR",len)) { nathan@0: if(Year<0) { nathan@0: Year=atoi(p); nathan@0: if(Year<1800 || Year>2100) Year=-1; nathan@0: } nathan@0: } nathan@0: } nathan@0: } nathan@0: } nathan@0: if(!Title) FakeTitle(file->Filename); nathan@0: nathan@0: vorbis_info *vi=ov_info(&file->vf,-1); nathan@0: if(!vi) Abort(false); nathan@0: d(printf("ogg: info ch=%d srate=%ld brate_low=%ld brate_high=%ld brate_avg=%ld\n", nathan@0: vi->channels,vi->rate,vi->bitrate_lower,vi->bitrate_upper,vi->bitrate_nominal)) nathan@0: Channels=vi->channels; nathan@0: ChMode=Channels>1 ? 3:0; nathan@0: SampleFreq=vi->rate; nathan@0: if(vi->bitrate_upper>0 && vi->bitrate_lower>0) { nathan@0: Bitrate=vi->bitrate_lower; nathan@0: MaxBitrate=vi->bitrate_upper; nathan@0: } nathan@0: else nathan@0: Bitrate=vi->bitrate_nominal; nathan@0: nathan@0: Total=(int)ov_time_total(&file->vf,-1); nathan@0: Frames=-1; nathan@0: DecoderID=DEC_OGG; nathan@0: nathan@0: InfoDone(); nathan@0: InfoCache.Cache(this,file); nathan@0: return Abort(true); nathan@0: } nathan@0: nathan@0: // --- cOggDecoder ------------------------------------------------------------- nathan@0: nathan@0: cOggDecoder::cOggDecoder(const char *Filename) nathan@0: :cDecoder(Filename) nathan@0: ,file(Filename) nathan@0: ,info(&file) nathan@0: { nathan@0: pcm=0; nathan@0: } nathan@0: nathan@0: cOggDecoder::~cOggDecoder() nathan@0: { nathan@0: Clean(); nathan@0: } nathan@0: nathan@0: bool cOggDecoder::Valid(void) nathan@0: { nathan@0: bool res=false; nathan@0: if(TryLock()) { nathan@0: if(file.Open(false)) res=true; nathan@0: Unlock(); nathan@0: } nathan@0: return res; nathan@0: } nathan@0: nathan@0: cFileInfo *cOggDecoder::FileInfo(void) nathan@0: { nathan@0: cFileInfo *fi=0; nathan@0: if(file.HasInfo()) fi=&file; nathan@0: else if(TryLock()){ nathan@0: if(file.Open()) { fi=&file; file.Close(); } nathan@0: Unlock(); nathan@0: } nathan@0: return fi; nathan@0: } nathan@0: nathan@0: cSongInfo *cOggDecoder::SongInfo(bool get) nathan@0: { nathan@0: cSongInfo *si=0; nathan@0: if(info.HasInfo()) si=&info; nathan@0: else if(get && TryLock()) { nathan@0: if(info.DoScan(false)) si=&info; nathan@0: Unlock(); nathan@0: } nathan@0: return si; nathan@0: } nathan@0: nathan@0: cPlayInfo *cOggDecoder::PlayInfo(void) nathan@0: { nathan@0: if(playing) { nathan@0: pi.Index=index/1000; nathan@0: pi.Total=info.Total; nathan@0: return π nathan@0: } nathan@0: return 0; nathan@0: } nathan@0: nathan@0: void cOggDecoder::Init(void) nathan@0: { nathan@0: Clean(); nathan@0: pcm=new struct mad_pcm; nathan@0: index=0; nathan@0: } nathan@0: nathan@0: bool cOggDecoder::Clean(void) nathan@0: { nathan@0: playing=false; nathan@0: delete pcm; pcm=0; nathan@0: file.Close(); nathan@0: return false; nathan@0: } nathan@0: nathan@0: #define SF_SAMPLES (sizeof(pcm->samples[0])/sizeof(mad_fixed_t)) nathan@0: nathan@0: bool cOggDecoder::Start(void) nathan@0: { nathan@0: Lock(true); nathan@0: Init(); playing=true; nathan@0: if(file.Open() && info.DoScan(true)) { nathan@0: d(printf("ogg: open rate=%d channels=%d seek=%d\n", nathan@0: info.SampleFreq,info.Channels,file.CanSeek())) nathan@0: if(info.Channels<=2) { nathan@0: Unlock(); nathan@0: return true; nathan@0: } nathan@0: else esyslog("ERROR: cannot play ogg file %s: more than 2 channels",filename); nathan@0: } nathan@0: Clean(); nathan@0: Unlock(); nathan@0: return false; nathan@0: } nathan@0: nathan@0: bool cOggDecoder::Stop(void) nathan@0: { nathan@0: Lock(); nathan@0: if(playing) Clean(); nathan@0: Unlock(); nathan@0: return true; nathan@0: } nathan@0: nathan@0: struct Decode *cOggDecoder::Done(eDecodeStatus status) nathan@0: { nathan@0: ds.status=status; nathan@0: ds.index=index; nathan@0: ds.pcm=pcm; nathan@0: Unlock(); // release the lock from Decode() nathan@0: return &ds; nathan@0: } nathan@0: nathan@0: struct Decode *cOggDecoder::Decode(void) nathan@0: { nathan@0: Lock(); // this is released in Done() nathan@0: if(playing) { nathan@0: short framebuff[2*SF_SAMPLES]; nathan@0: int n=file.Stream(framebuff,SF_SAMPLES); nathan@0: if(n<0) return Done(dsError); nathan@0: if(n==0) return Done(dsEof); nathan@0: nathan@0: pcm->samplerate=info.SampleFreq; nathan@0: pcm->channels=info.Channels; nathan@0: n/=pcm->channels; nathan@0: pcm->length=n; nathan@0: index=file.IndexMs(); nathan@0: nathan@0: short *data=framebuff; nathan@0: mad_fixed_t *sam0=pcm->samples[0], *sam1=pcm->samples[1]; nathan@0: const int s=MAD_F_FRACBITS+1-(sizeof(short)*8); // shift value for mad_fixed conversion nathan@0: if(pcm->channels>1) { nathan@0: for(; n>0 ; n--) { nathan@0: *sam0++=(*data++) << s; nathan@0: *sam1++=(*data++) << s; nathan@0: } nathan@0: } nathan@0: else { nathan@0: for(; n>0 ; n--) nathan@0: *sam0++=(*data++) << s; nathan@0: } nathan@0: return Done(dsPlay); nathan@0: } nathan@0: return Done(dsError); nathan@0: } nathan@0: nathan@0: bool cOggDecoder::Skip(int Seconds, float bsecs) nathan@0: { nathan@0: Lock(); nathan@0: bool res=false; nathan@0: if(playing && file.CanSeek()) { nathan@0: float fsecs=(float)Seconds - bsecs; nathan@0: long long newpos=file.IndexMs()+(long long)(fsecs*1000.0); nathan@0: if(newpos<0) newpos=0; nathan@0: d(printf("ogg: skip: secs=%d fsecs=%f current=%lld new=%lld\n",Seconds,fsecs,file.IndexMs(),newpos)) nathan@0: nathan@0: newpos=file.Seek(newpos,false); nathan@0: if(newpos>=0) { nathan@0: index=file.IndexMs(); nathan@0: #ifdef DEBUG nathan@0: int i=index/1000; nathan@0: printf("ogg: skipping to %02d:%02d\n",i/60,i%60); nathan@0: #endif nathan@0: res=true; nathan@0: } nathan@0: } nathan@0: Unlock(); nathan@0: return res; nathan@0: } nathan@0: nathan@0: #endif //HAVE_VORBISFILE