2 * MP3/MPlayer plugin to VDR (C++)
4 * (C) 2001-2005 Stefan Huelswitt <s.huelswitt@gmx.de>
6 * This code is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This code is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
36 #include <sys/types.h>
41 #include "setup-mp3.h"
42 #include "decoder-snd.h"
45 #include "menu-async.h"
50 #error You must use libsndfile version 1.x.x
53 #define CDFS_TRACK_OFF 150
54 #define CDFS_PROC "/proc/cdfs"
55 #define CDFS_MARK_ID "CD (discid=%x) contains %d tracks:"
56 #define CDFS_MARK_TR "%*[^[][ %d - %d"
57 #define CDFS_TRACK "track-"
59 #define CDDB_PROTO 5 // used protocol level
60 #define CDDB_TOUT 30*1000 // connection timeout (ms)
62 const char *cddbpath="/var/lib/cddb"; // default local cddb path
64 #define CDDB_DEBUG // debug cddb queries
65 //#define DEBUG_CDFS // debug cdfs parsing
66 //#define GUARD_DEBUG // enable framebuffer guard
68 #if !defined(NO_DEBUG) && defined(DEBUG_CDFS)
69 #define dc(x) { (x); }
76 // --- cSndDecoder -------------------------------------------------------------
78 #define SF_SAMPLES (sizeof(pcm->samples[0])/sizeof(mad_fixed_t))
80 cSndDecoder::cSndDecoder(const char *Filename)
85 pcm=0; framebuff=0; playing=ready=false;
88 cSndDecoder::~cSndDecoder()
93 bool cSndDecoder::Valid(void)
97 if(file.Open(false)) res=true;
103 cFileInfo *cSndDecoder::FileInfo(void)
106 if(file.HasInfo()) fi=&file;
108 if(file.Open()) { fi=&file; file.Close(); }
114 cSongInfo *cSndDecoder::SongInfo(bool get)
117 if(info.HasInfo()) si=&info;
118 else if(get && TryLock()) {
119 if(info.DoScan(false)) si=&info;
125 cPlayInfo *cSndDecoder::PlayInfo(void)
128 pi.Index=index/info.SampleFreq;
135 void cSndDecoder::Init(void)
138 pcm=new struct mad_pcm;
139 framebuff=MALLOC(int,2*SF_SAMPLES+8);
141 for(int i=0; i<8; i++) framebuff[i+(SF_SAMPLES*2)-4]=0xdeadbeaf;
146 bool cSndDecoder::Clean(void)
151 run=false; bgCond.Broadcast();
156 if(!ready) { deferedN=-1; ready=true; }
163 printf("snd: bufferguard");
164 for(int i=0; i<8; i++) printf(" %08x",framebuff[i+(SF_SAMPLES*2)-4]);
168 free(framebuff); framebuff=0;
173 bool cSndDecoder::Start(void)
175 cDecoder::Lock(true);
176 Init(); playing=true;
177 if(file.Open() && info.DoScan(true)) {
178 d(printf("snd: open rate=%d frames=%lld channels=%d format=0x%x seek=%d\n",
179 file.sfi.samplerate,file.sfi.frames,file.sfi.channels,file.sfi.format,file.sfi.seekable))
180 if(file.sfi.channels<=2) {
181 ready=false; run=true; softCount=0;
186 else esyslog("ERROR: cannot play sound file %s: more than 2 channels",filename);
192 bool cSndDecoder::Stop(void)
200 void cSndDecoder::Action(void)
204 if(ready) bgCond.Wait(buffMutex);
207 deferedN=file.Stream(framebuff,SF_SAMPLES);
209 ready=true; fgCond.Broadcast();
215 struct Decode *cSndDecoder::Done(eDecodeStatus status)
218 ds.index=index*1000/info.SampleFreq;
220 cDecoder::Unlock(); // release the lock from Decode()
224 struct Decode *cSndDecoder::Decode(void)
226 cDecoder::Lock(); // this is released in Done()
228 cMutexLock lock(&buffMutex);
230 if(!softCount || !fgCond.TimedWait(buffMutex,softCount*5)) {
231 if(softCount<20) softCount++;
232 return Done(dsSoftError);
235 ready=false; bgCond.Broadcast();
238 if(n<0) return Done(dsError);
239 if(n==0) return Done(dsEof);
241 pcm->samplerate=file.sfi.samplerate;
242 pcm->channels=file.sfi.channels;
247 mad_fixed_t *sam0=pcm->samples[0], *sam1=pcm->samples[1];
248 const int s=(sizeof(int)*8)-1-MAD_F_FRACBITS; // shift value for mad_fixed conversion
249 if(pcm->channels>1) {
251 *sam0++=(*data++) >> s;
252 *sam1++=(*data++) >> s;
257 *sam0++=(*data++) >> s;
261 return Done(dsError);
264 bool cSndDecoder::Skip(int Seconds, float bsecs)
268 if(playing && file.sfi.seekable) {
269 float fsecs=(float)Seconds-bsecs;
270 sf_count_t frames=(sf_count_t)(fsecs*(float)file.sfi.samplerate);
271 sf_count_t newpos=file.Seek(0,true)+frames;
272 if(newpos>file.sfi.frames) newpos=file.sfi.frames-1;
273 if(newpos<0) newpos=0;
274 d(printf("snd: skip: secs=%d fsecs=%f frames=%lld current=%lld new=%lld\n",Seconds,fsecs,frames,file.Seek(0,true),newpos))
277 frames=file.Seek(newpos,false);
278 ready=false; bgCond.Broadcast();
283 int i=frames/file.sfi.samplerate;
284 printf("snd: skipping to %02d:%02d (frame %lld)\n",i/60,i%60,frames);
295 // --- cDiscID -------------------------------------------------------------------
299 int discid, ntrks, nsecs;
307 cDiscID::cDiscID(void)
309 offsets=0; discid=ntrks=0;
317 bool cDiscID::Get(void)
320 FILE *f=fopen(CDFS_PROC,"r");
325 while(fgets(line,sizeof(line),f)) {
328 if(sscanf(line,CDFS_MARK_ID,&id,&n)==2) {
329 d(printf("discid: found id=%08x n=%d\n",id,n))
330 if(discid==id && ntrks==n) {
336 delete offsets; offsets=new int[ntrks];
343 if(sscanf(line,CDFS_MARK_TR,&off,&end)==2) {
344 dc(printf("discid: found offset=%d end=%d for track %d\n",off,end,tr+1))
345 offsets[tr]=off+CDFS_TRACK_OFF;
348 dc(printf("discid: nsecs=%d / 0x%x\n",nsecs,nsecs))
360 // --- cCDDBSong ---------------------------------------------------------------
362 // The CDDB code is loosely based on the implementation in mp3c 0.27 which is
363 // (C) 1999-2001 WSPse, Matthias Hensler, <matthias@wspse.de>
365 class cCDDBSong : public cListObject {
372 char *Title, *Artist;
375 cCDDBSong::cCDDBSong(void)
377 Title=Artist=0; TTitle=ExtT=0;
380 cCDDBSong::~cCDDBSong()
388 // --- cCDDBDisc ---------------------------------------------------------------
390 const char *sampler[] = { // some artist names to identify sampler discs
404 class cCDDBDisc : public cList<cCDDBSong> {
411 cCDDBSong *GetTrack(const char *name, unsigned int pos);
412 cCDDBSong *FindTrack(int tr);
413 void Strcat(char * &store, char *value);
414 bool Split(const char *source, char div, char * &first, char * &second, bool only3=false);
415 void Put(const char *from, char * &to);
420 bool Load(cDiscID *id, const char *filename);
421 bool Cached(cDiscID *id) { return DiscID==id->discid; }
422 bool TrackInfo(int tr, cSongInfo *si);
424 char *Album, *Artist;
428 cCDDBDisc::cCDDBDisc(void)
430 Album=Artist=0; DTitle=ExtD=0; DiscID=0;
433 cCDDBDisc::~cCDDBDisc()
438 void cCDDBDisc::Clean(void)
440 free(DTitle); DTitle=0;
442 free(Artist); Artist=0;
443 free(Album); Album=0;
444 Year=-1; DiscID=0; isSampler=false;
447 bool cCDDBDisc::TrackInfo(int tr, cSongInfo *si)
449 cCDDBSong *s=FindTrack(tr);
451 Put(s->Title,si->Title);
452 if(s->Artist) Put(s->Artist,si->Artist); else Put(Artist,si->Artist);
453 Put(Album,si->Album);
454 if(Year>0) si->Year=Year;
460 void cCDDBDisc::Put(const char *from, char * &to)
463 to=from ? strdup(from):0;
466 bool cCDDBDisc::Load(cDiscID *id, const char *filename)
471 d(printf("cddb: loading discid %08x from %s\n",id->discid,filename))
473 FILE *f=fopen(filename,"r");
476 while(fgets(buff,sizeof(buff),f)) {
478 while(i && (buff[i-1]=='\n' || buff[i-1]=='\r')) buff[--i]=0;
480 if(buff[0]=='#') { // special comment line handling
486 char *name =compactspace(buff);
487 char *value=compactspace(p+1);
488 if(*name && *value) {
489 if(!strcasecmp(name,"DTITLE")) Strcat(DTitle,value);
490 else if(!strcasecmp(name,"EXTD")) Strcat(ExtD,value);
491 else if(!strcasecmp(name,"DYEAR")) Year=atoi(value);
492 else if(!strncasecmp(name,"TTITLE",6)) {
493 cCDDBSong *s=GetTrack(name,6);
494 if(s) Strcat(s->TTitle,value);
496 else if(!strncasecmp(name,"EXTT",4)) {
497 cCDDBSong *s=GetTrack(name,4);
498 if(s) Strcat(s->ExtT,value);
506 // read all data, now post-processing
509 if(Split(DTitle,'/',Artist,Album)) {
510 for(int n=0 ; sampler[n] ; n++)
511 if(!strncasecmp(Artist,sampler[n],strlen(sampler[n]))) {
517 Album=strdup(DTitle);
521 d(printf("cddb: found artist='%s' album='%s' isSampler=%d\n",Artist,Album,isSampler))
522 free(DTitle); DTitle=0;
524 if(!isSampler && Artist && Album && !strncmp(Album,Artist,strlen(Artist))) {
525 d(printf("cddb: detecting sampler from Artist==Album\n"))
530 int nofail1=0, nofail2=0;
531 cCDDBSong *s=First();
534 if(strstr(s->TTitle," / ")) nofail1++;
535 //if(strstr(s->TTitle," - ")) nofail2++;
539 if(nofail1==Count() || nofail2==Count()) {
540 d(printf("cddb: detecting sampler from nofail\n"))
545 if(Year<0 && ExtD && (p=strstr(ExtD,"YEAR:"))) Year=atoi(p+5);
547 d(printf("cddb: found year=%d\n",Year))
549 cCDDBSong *s=First();
553 if(!Split(s->TTitle,'/',s->Artist,s->Title) &&
554 !Split(s->TTitle,'-',s->Artist,s->Title,true)) {
555 s->Title=compactspace(strdup(s->TTitle));
556 if(s->ExtT) s->Artist=compactspace(strdup(s->ExtT));
560 s->Title=compactspace(strdup(s->TTitle));
561 if(Artist) s->Artist=strdup(Artist);
564 else s->Title=strdup(tr("unknown"));
566 free(s->TTitle); s->TTitle=0;
567 free(s->ExtT); s->ExtT=0;
568 d(printf("cddb: found track %d title='%s' artist='%s'\n",s->Track,s->Title,s->Artist))
577 bool cCDDBDisc::Split(const char *source, char div, char * &first, char * &second, bool only3)
580 char *p, l[4]={ ' ',div,' ',0 };
581 if ((p=strstr(source,l))) { pos=p-source; n=3; }
582 else if(!only3 && (p=strchr(source,div))) { pos=p-source; n=1; }
584 free(first); first=strdup(source); first[pos]=0; compactspace(first);
585 free(second); second=strdup(source+pos+n); compactspace(second);
591 void cCDDBDisc::Strcat(char * &store, char *value)
594 char *n=MALLOC(char,strlen(store)+strlen(value)+1);
598 free(store); store=n;
601 else store=strdup(value);
604 cCDDBSong *cCDDBDisc::GetTrack(const char *name, unsigned int pos)
607 if(strlen(name)>pos) {
608 int tr=atoi(&name[pos]);
619 cCDDBSong *cCDDBDisc::FindTrack(int tr)
621 cCDDBSong *s=First();
623 if(s->Track==tr) break;
631 // --- cCDDB -------------------------------------------------------------------
633 class cCDDB : public cScanDir, cMutex {
639 char searchID[10], cddbstr[256];
641 virtual void DoItem(cFileSource *src, const char *subdir, const char *name);
642 bool LocalQuery(cDiscID *id);
643 bool RemoteGet(cDiscID *id);
644 bool GetLine(char *buff, int size, bool log=true);
645 int GetCddbResponse(void);
646 int DoCddbCmd(const char *format, ...);
650 bool Lookup(cDiscID *id, int track, cSongInfo *si);
657 src=0; file=0; net=0;
667 bool cCDDB::Lookup(cDiscID *id, int track, cSongInfo *si)
671 if(!cache.Cached(id)) {
672 if(LocalQuery(id) || (MP3Setup.UseCddb>1 && RemoteGet(id) && LocalQuery(id)))
673 cache.Load(id,file->FullPath());
675 if(cache.Cached(id) && cache.TrackInfo(track,si)) res=true;
680 bool cCDDB::LocalQuery(cDiscID *id)
684 if(!src) src=new cFileSource(cddbpath,"CDDB database",false);
686 snprintf(searchID,sizeof(searchID),"%08x",id->discid);
687 if(ScanDir(src,0,stDir,0,0,false) && file) res=true;
692 void cCDDB::DoItem(cFileSource *src, const char *subdir, const char *name)
695 file=new cFileObj(src,name,searchID,otFile);
696 if(access(file->FullPath(),R_OK)) { delete file; file=0; }
700 bool cCDDB::RemoteGet(cDiscID *id)
703 asyncStatus.Set(tr("Remote CDDB lookup..."));
705 delete net; net=new cNet(16*1024,CDDB_TOUT,CDDB_TOUT);
706 if(net->Connect(MP3Setup.CddbHost,MP3Setup.CddbPort)) {
707 int code=GetCddbResponse();
709 const char *host=getenv("HOSTNAME"); if(!host) host="unknown";
710 const char *user=getenv("USER"); if(!user) user="nobody";
711 code=DoCddbCmd("cddb hello %s %s %s %s\n",user,host,PLUGIN_NAME,PLUGIN_VERSION);
713 code=DoCddbCmd("proto %d\n",CDDB_PROTO);
716 off[0]=0; for(int i=0 ; i<id->ntrks ; i++) sprintf(&off[strlen(off)]," %d",id->offsets[i]);
718 code=DoCddbCmd("cddb query %08x %d %s %d\n",id->discid,id->ntrks,off,id->nsecs);
721 if(code==200) cat=strdup(cddbstr);
723 if(GetLine(off,sizeof(off))) {
725 while(GetLine(off,sizeof(off)) && off[0]!='.');
730 char *s=index(cat,' '); if(s) *s=0;
731 code=DoCddbCmd("cddb read %s %08x\n",cat,id->discid);
734 asprintf(&name,"%s/%s/%08x",cddbpath,cat,id->discid);
735 if(MakeDirs(name,false)) {
736 FILE *out=fopen(name,"w");
738 while(GetLine(off,sizeof(off),false) && off[0]!='.') fputs(off,out);
742 else esyslog("fopen() failed: %s",strerror(errno));
746 else if(code>0) esyslog("server read error: %d %s",code,cddbstr);
750 else if(code>0) esyslog("server query error: %d %s",code,cddbstr);
752 else esyslog("server proto error: %d %s",code,cddbstr);
754 else if(code>0) esyslog("server hello error: %d %s",code,cddbstr);
756 else if(code>0) esyslog("server sign-on error: %d %s",code,cddbstr);
764 bool cCDDB::GetLine(char *buff, int size, bool log)
766 if(net->Gets(buff,size)>0) {
768 if(log) printf("cddb: <- %s",buff);
775 int cCDDB::GetCddbResponse(void)
778 if(GetLine(buf,sizeof(buf))) {
780 if(sscanf(buf,"%d %255[^\n]",&code,cddbstr)==2) return code;
781 else esyslog("Unexpected server response: %s",buf);
786 int cCDDB::DoCddbCmd(const char *format, ...)
791 vasprintf(&buff,format,ap);
794 printf("cddb: -> %s",buff);
796 int r=net->Puts(buff);
799 return GetCddbResponse();
802 // --- cSndInfo ----------------------------------------------------------------
804 cSndInfo::cSndInfo(cSndFile *File)
810 cSndInfo::~cSndInfo()
815 bool cSndInfo::Abort(bool result)
817 if(!keepOpen) file->Close();
821 bool cSndInfo::DoScan(bool KeepOpen)
824 if(!file->Open()) return Abort(false);
825 if(HasInfo()) return Abort(true);
827 // check the infocache
828 cCacheData *dat=InfoCache.Search(file);
830 Set(dat); dat->Unlock();
833 InfoCache.Cache(this,file);
840 if(file->FsType!=CDFS_MAGIC || !MP3Setup.UseCddb || !CDDBLookup(file->Filename))
841 FakeTitle(file->Filename);
843 Frames=file->sfi.frames;
844 SampleFreq=file->sfi.samplerate;
845 Channels=file->sfi.channels;
846 ChMode=Channels>1 ? 3:0;
847 Total=Frames/SampleFreq;
848 Bitrate=file->Filesize*8/Total; //XXX SampleFreq*Channels*file->sfi.pcmbitwidth;
852 InfoCache.Cache(this,file);
856 bool cSndInfo::CDDBLookup(const char *filename)
860 char *s=strstr(filename,CDFS_TRACK);
861 if(s && sscanf(s+strlen(CDFS_TRACK),"%d",&tr)==1) {
862 d(printf("snd: looking up disc id %08x track %d\n",id->discid,tr))
863 return cddb.Lookup(id,tr-1,this);
869 // --- cSndFile ----------------------------------------------------------------
871 cSndFile::cSndFile(const char *Filename)
877 cSndFile::~cSndFile()
882 bool cSndFile::Open(bool log)
884 if(sf) return (Seek()>=0);
887 sf=sf_open(Filename,SFM_READ,&sfi);
888 if(!sf && log) Error("open");
893 void cSndFile::Close(void)
895 if(sf) { sf_close(sf); sf=0; }
898 void cSndFile::Error(const char *action)
901 sf_error_str(sf,buff,sizeof(buff));
902 esyslog("ERROR: sndfile %s failed on %s: %s",action,Filename,buff);
905 sf_count_t cSndFile::Seek(sf_count_t frames, bool relativ)
908 if(!relativ) dir=SEEK_SET;
909 int n=sf_seek(sf,frames,dir);
910 if(n<0) Error("seek");
914 sf_count_t cSndFile::Stream(int *buffer, sf_count_t frames)
916 sf_count_t n=sf_readf_int(sf,buffer,frames);
917 if(n<0) Error("read");
922 #endif //HAVE_SNDFILE
927 // g++ -g -DTEST_MAIN -o test mp3-decoder-snd.c tools.o thread.o -lpthread
933 extern const char *tr(const char *test)
938 int main (int argc, char *argv[])
942 cddb.Load(1,argv[1]);