2 * MP3/MPlayer plugin to VDR (C++)
4 * (C) 2001-2009 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)
61 #define CDDB_CHARSET "ISO8859-1" // data charset
63 const char *cddbpath="/var/lib/cddb"; // default local cddb path
65 #define CDDB_DEBUG // debug cddb queries
66 //#define DEBUG_CDFS // debug cdfs parsing
67 //#define GUARD_DEBUG // enable framebuffer guard
69 #if !defined(NO_DEBUG) && defined(DEBUG_CDFS)
70 #define dc(x) { (x); }
77 // --- cSndDecoder -------------------------------------------------------------
79 #define SF_SAMPLES (sizeof(pcm->samples[0])/sizeof(mad_fixed_t))
81 cSndDecoder::cSndDecoder(const char *Filename)
86 pcm=0; framebuff=0; playing=ready=false;
89 cSndDecoder::~cSndDecoder()
94 bool cSndDecoder::Valid(void)
98 if(file.Open(false)) res=true;
104 cFileInfo *cSndDecoder::FileInfo(void)
107 if(file.HasInfo()) fi=&file;
109 if(file.Open()) { fi=&file; file.Close(); }
115 cSongInfo *cSndDecoder::SongInfo(bool get)
118 if(info.HasInfo()) si=&info;
119 else if(get && TryLock()) {
120 if(info.DoScan(false)) si=&info;
126 cPlayInfo *cSndDecoder::PlayInfo(void)
129 pi.Index=index/info.SampleFreq;
136 void cSndDecoder::Init(void)
139 pcm=new struct mad_pcm;
140 framebuff=MALLOC(int,2*SF_SAMPLES+8);
142 for(int i=0; i<8; i++) framebuff[i+(SF_SAMPLES*2)-4]=0xdeadbeaf;
147 bool cSndDecoder::Clean(void)
152 run=false; bgCond.Broadcast();
157 if(!ready) { deferedN=-1; ready=true; }
164 printf("snd: bufferguard");
165 for(int i=0; i<8; i++) printf(" %08x",framebuff[i+(SF_SAMPLES*2)-4]);
169 free(framebuff); framebuff=0;
174 bool cSndDecoder::Start(void)
176 cDecoder::Lock(true);
177 Init(); playing=true;
178 if(file.Open() && info.DoScan(true)) {
179 d(printf("snd: open rate=%d frames=%lld channels=%d format=0x%x seek=%d\n",
180 file.sfi.samplerate,file.sfi.frames,file.sfi.channels,file.sfi.format,file.sfi.seekable))
181 if(file.sfi.channels<=2) {
182 ready=false; run=true; softCount=0;
187 else esyslog("ERROR: cannot play sound file %s: more than 2 channels",filename);
193 bool cSndDecoder::Stop(void)
201 void cSndDecoder::Action(void)
205 if(ready) bgCond.Wait(buffMutex);
208 deferedN=file.Stream(framebuff,SF_SAMPLES);
210 ready=true; fgCond.Broadcast();
216 struct Decode *cSndDecoder::Done(eDecodeStatus status)
219 ds.index=index*1000/info.SampleFreq;
221 cDecoder::Unlock(); // release the lock from Decode()
225 struct Decode *cSndDecoder::Decode(void)
227 cDecoder::Lock(); // this is released in Done()
229 cMutexLock lock(&buffMutex);
231 if(!softCount || !fgCond.TimedWait(buffMutex,softCount*5)) {
232 if(softCount<20) softCount++;
233 return Done(dsSoftError);
236 ready=false; bgCond.Broadcast();
239 if(n<0) return Done(dsError);
240 if(n==0) return Done(dsEof);
242 pcm->samplerate=file.sfi.samplerate;
243 pcm->channels=file.sfi.channels;
248 mad_fixed_t *sam0=pcm->samples[0], *sam1=pcm->samples[1];
249 const int s=(sizeof(int)*8)-1-MAD_F_FRACBITS; // shift value for mad_fixed conversion
250 if(pcm->channels>1) {
252 *sam0++=(*data++) >> s;
253 *sam1++=(*data++) >> s;
258 *sam0++=(*data++) >> s;
262 return Done(dsError);
265 bool cSndDecoder::Skip(int Seconds, float bsecs)
269 if(playing && file.sfi.seekable) {
270 float fsecs=(float)Seconds-bsecs;
271 sf_count_t frames=(sf_count_t)(fsecs*(float)file.sfi.samplerate);
272 sf_count_t newpos=file.Seek(0,true)+frames;
273 if(newpos>file.sfi.frames) newpos=file.sfi.frames-1;
274 if(newpos<0) newpos=0;
275 d(printf("snd: skip: secs=%d fsecs=%f frames=%lld current=%lld new=%lld\n",Seconds,fsecs,frames,file.Seek(0,true),newpos))
278 frames=file.Seek(newpos,false);
279 ready=false; bgCond.Broadcast();
284 int i=frames/file.sfi.samplerate;
285 printf("snd: skipping to %02d:%02d (frame %lld)\n",i/60,i%60,frames);
296 // --- cDiscID -------------------------------------------------------------------
300 int discid, ntrks, nsecs;
308 cDiscID::cDiscID(void)
310 offsets=0; discid=ntrks=0;
318 bool cDiscID::Get(void)
321 FILE *f=fopen(CDFS_PROC,"r");
326 while(fgets(line,sizeof(line),f)) {
329 if(sscanf(line,CDFS_MARK_ID,&id,&n)==2) {
330 d(printf("discid: found id=%08x n=%d\n",id,n))
331 if(discid==id && ntrks==n) {
337 delete offsets; offsets=new int[ntrks];
344 if(sscanf(line,CDFS_MARK_TR,&off,&end)==2) {
345 dc(printf("discid: found offset=%d end=%d for track %d\n",off,end,tr+1))
346 offsets[tr]=off+CDFS_TRACK_OFF;
349 dc(printf("discid: nsecs=%d / 0x%x\n",nsecs,nsecs))
361 // --- cCDDBSong ---------------------------------------------------------------
363 // The CDDB code is loosely based on the implementation in mp3c 0.27 which is
364 // (C) 1999-2001 WSPse, Matthias Hensler, <matthias@wspse.de>
366 class cCDDBSong : public cListObject {
373 char *Title, *Artist;
376 cCDDBSong::cCDDBSong(void)
378 Title=Artist=0; TTitle=ExtT=0;
381 cCDDBSong::~cCDDBSong()
389 // --- cCDDBDisc ---------------------------------------------------------------
391 const char *sampler[] = { // some artist names to identify sampler discs
405 class cCDDBDisc : public cList<cCDDBSong> {
412 cCDDBSong *GetTrack(const char *name, unsigned int pos);
413 cCDDBSong *FindTrack(int tr);
414 void Strcat(char * &store, const char *value);
415 bool Split(const char *source, char div, char * &first, char * &second, bool only3=false);
416 void Put(const char *from, char * &to);
421 bool Load(cDiscID *id, const char *filename);
422 bool Cached(cDiscID *id) { return DiscID==id->discid; }
423 bool TrackInfo(int tr, cSongInfo *si);
425 char *Album, *Artist;
429 cCDDBDisc::cCDDBDisc(void)
431 Album=Artist=0; DTitle=ExtD=0; DiscID=0;
434 cCDDBDisc::~cCDDBDisc()
439 void cCDDBDisc::Clean(void)
441 free(DTitle); DTitle=0;
443 free(Artist); Artist=0;
444 free(Album); Album=0;
445 Year=-1; DiscID=0; isSampler=false;
448 bool cCDDBDisc::TrackInfo(int tr, cSongInfo *si)
450 cCDDBSong *s=FindTrack(tr);
452 Put(s->Title,si->Title);
453 if(s->Artist) Put(s->Artist,si->Artist); else Put(Artist,si->Artist);
454 Put(Album,si->Album);
455 if(Year>0) si->Year=Year;
461 void cCDDBDisc::Put(const char *from, char * &to)
464 to=from ? strdup(from):0;
467 bool cCDDBDisc::Load(cDiscID *id, const char *filename)
472 d(printf("cddb: loading discid %08x from %s\n",id->discid,filename))
474 FILE *f=fopen(filename,"r");
476 cCharSetConv csc(CDDB_CHARSET);
478 while(fgets(buff,sizeof(buff),f)) {
480 while(i && (buff[i-1]=='\n' || buff[i-1]=='\r')) buff[--i]=0;
482 if(buff[0]=='#') { // special comment line handling
488 char *name =compactspace(buff);
489 const char *value=csc.Convert(compactspace(p+1));
490 if(*name && *value) {
491 if(!strcasecmp(name,"DTITLE")) Strcat(DTitle,value);
492 else if(!strcasecmp(name,"EXTD")) Strcat(ExtD,value);
493 else if(!strcasecmp(name,"DYEAR")) Year=atoi(value);
494 else if(!strncasecmp(name,"TTITLE",6)) {
495 cCDDBSong *s=GetTrack(name,6);
496 if(s) Strcat(s->TTitle,value);
498 else if(!strncasecmp(name,"EXTT",4)) {
499 cCDDBSong *s=GetTrack(name,4);
500 if(s) Strcat(s->ExtT,value);
508 // read all data, now post-processing
511 if(Split(DTitle,'/',Artist,Album)) {
512 for(int n=0 ; sampler[n] ; n++)
513 if(!strncasecmp(Artist,sampler[n],strlen(sampler[n]))) {
519 Album=strdup(DTitle);
523 d(printf("cddb: found artist='%s' album='%s' isSampler=%d\n",Artist,Album,isSampler))
524 free(DTitle); DTitle=0;
526 if(!isSampler && Artist && Album && !strncmp(Album,Artist,strlen(Artist))) {
527 d(printf("cddb: detecting sampler from Artist==Album\n"))
532 int nofail1=0, nofail2=0;
533 cCDDBSong *s=First();
536 if(strstr(s->TTitle," / ")) nofail1++;
537 //if(strstr(s->TTitle," - ")) nofail2++;
541 if(nofail1==Count() || nofail2==Count()) {
542 d(printf("cddb: detecting sampler from nofail\n"))
547 if(Year<0 && ExtD && (p=strstr(ExtD,"YEAR:"))) Year=atoi(p+5);
549 d(printf("cddb: found year=%d\n",Year))
551 cCDDBSong *s=First();
555 if(!Split(s->TTitle,'/',s->Artist,s->Title) &&
556 !Split(s->TTitle,'-',s->Artist,s->Title,true)) {
557 s->Title=compactspace(strdup(s->TTitle));
558 if(s->ExtT) s->Artist=compactspace(strdup(s->ExtT));
562 s->Title=compactspace(strdup(s->TTitle));
563 if(Artist) s->Artist=strdup(Artist);
566 else s->Title=strdup(tr("unknown"));
568 free(s->TTitle); s->TTitle=0;
569 free(s->ExtT); s->ExtT=0;
570 d(printf("cddb: found track %d title='%s' artist='%s'\n",s->Track,s->Title,s->Artist))
579 bool cCDDBDisc::Split(const char *source, char div, char * &first, char * &second, bool only3)
583 char l[4]={ ' ',div,' ',0 };
584 if ((p=strstr(source,l))) { pos=p-source; n=3; }
585 else if(!only3 && (p=strchr(source,div))) { pos=p-source; n=1; }
587 free(first); first=strdup(source); first[pos]=0; compactspace(first);
588 free(second); second=strdup(source+pos+n); compactspace(second);
594 void cCDDBDisc::Strcat(char * &store, const char *value)
597 char *n=MALLOC(char,strlen(store)+strlen(value)+1);
601 free(store); store=n;
604 else store=strdup(value);
607 cCDDBSong *cCDDBDisc::GetTrack(const char *name, unsigned int pos)
610 if(strlen(name)>pos) {
611 int tr=atoi(&name[pos]);
622 cCDDBSong *cCDDBDisc::FindTrack(int tr)
624 cCDDBSong *s=First();
626 if(s->Track==tr) break;
634 // --- cCDDB -------------------------------------------------------------------
636 class cCDDB : public cScanDir, cMutex {
642 char searchID[10], cddbstr[256];
644 virtual void DoItem(cFileSource *src, const char *subdir, const char *name);
645 bool LocalQuery(cDiscID *id);
646 bool RemoteGet(cDiscID *id);
647 bool GetLine(char *buff, int size, bool log=true);
648 int GetCddbResponse(void);
649 int DoCddbCmd(const char *format, ...);
653 bool Lookup(cDiscID *id, int track, cSongInfo *si);
660 src=0; file=0; net=0;
670 bool cCDDB::Lookup(cDiscID *id, int track, cSongInfo *si)
674 if(!cache.Cached(id)) {
675 if(LocalQuery(id) || (MP3Setup.UseCddb>1 && RemoteGet(id) && LocalQuery(id)))
676 cache.Load(id,file->FullPath());
678 if(cache.Cached(id) && cache.TrackInfo(track,si)) res=true;
683 bool cCDDB::LocalQuery(cDiscID *id)
687 if(!src) src=new cFileSource(cddbpath,"CDDB database",false);
689 snprintf(searchID,sizeof(searchID),"%08x",id->discid);
690 if(ScanDir(src,0,stDir,0,0,false) && file) res=true;
695 void cCDDB::DoItem(cFileSource *src, const char *subdir, const char *name)
698 file=new cFileObj(src,name,searchID,otFile);
699 if(access(file->FullPath(),R_OK)) { delete file; file=0; }
703 bool cCDDB::RemoteGet(cDiscID *id)
706 asyncStatus.Set(tr("Remote CDDB lookup..."));
708 delete net; net=new cNet(16*1024,CDDB_TOUT,CDDB_TOUT);
709 if(net->Connect(MP3Setup.CddbHost,MP3Setup.CddbPort)) {
710 int code=GetCddbResponse();
712 const char *host=getenv("HOSTNAME"); if(!host) host="unknown";
713 const char *user=getenv("USER"); if(!user) user="nobody";
714 code=DoCddbCmd("cddb hello %s %s %s %s\n",user,host,PLUGIN_NAME,PluginVersion);
716 code=DoCddbCmd("proto %d\n",CDDB_PROTO);
719 off[0]=0; for(int i=0 ; i<id->ntrks ; i++) sprintf(&off[strlen(off)]," %d",id->offsets[i]);
721 code=DoCddbCmd("cddb query %08x %d %s %d\n",id->discid,id->ntrks,off,id->nsecs);
724 if(code==200) cat=strdup(cddbstr);
726 if(GetLine(off,sizeof(off))) {
728 while(GetLine(off,sizeof(off)) && off[0]!='.');
733 char *s=index(cat,' '); if(s) *s=0;
734 code=DoCddbCmd("cddb read %s %08x\n",cat,id->discid);
736 char *name=aprintf("%s/%s/%08x",cddbpath,cat,id->discid);
737 if(MakeDirs(name,false)) {
738 FILE *out=fopen(name,"w");
740 while(GetLine(off,sizeof(off),false) && off[0]!='.') fputs(off,out);
744 else esyslog("fopen() failed: %s",strerror(errno));
748 else if(code>0) esyslog("server read error: %d %s",code,cddbstr);
752 else if(code>0) esyslog("server query error: %d %s",code,cddbstr);
754 else esyslog("server proto error: %d %s",code,cddbstr);
756 else if(code>0) esyslog("server hello error: %d %s",code,cddbstr);
758 else if(code>0) esyslog("server sign-on error: %d %s",code,cddbstr);
766 bool cCDDB::GetLine(char *buff, int size, bool log)
768 if(net->Gets(buff,size)>0) {
770 if(log) printf("cddb: <- %s",buff);
777 int cCDDB::GetCddbResponse(void)
780 if(GetLine(buf,sizeof(buf))) {
782 if(sscanf(buf,"%d %255[^\n]",&code,cddbstr)==2) return code;
783 else esyslog("Unexpected server response: %s",buf);
788 int cCDDB::DoCddbCmd(const char *format, ...)
793 if(vasprintf(&buff,format,ap)<0);
796 printf("cddb: -> %s",buff);
798 int r=net->Puts(buff);
801 return GetCddbResponse();
804 // --- cSndInfo ----------------------------------------------------------------
806 cSndInfo::cSndInfo(cSndFile *File)
812 cSndInfo::~cSndInfo()
817 bool cSndInfo::Abort(bool result)
819 if(!keepOpen) file->Close();
823 bool cSndInfo::DoScan(bool KeepOpen)
826 if(!file->Open()) return Abort(false);
827 if(HasInfo()) return Abort(true);
829 // check the infocache
830 cCacheData *dat=InfoCache.Search(file);
832 Set(dat); dat->Unlock();
836 InfoCache.Cache(this,file);
843 if(file->FsType!=CDFS_MAGIC || !MP3Setup.UseCddb || !CDDBLookup(file->Filename))
844 FakeTitle(file->Filename);
846 Frames=file->sfi.frames;
847 SampleFreq=file->sfi.samplerate;
848 Channels=file->sfi.channels;
849 ChMode=Channels>1 ? 3:0;
850 Total=Frames/SampleFreq;
851 Bitrate=Total ? file->Filesize*8/Total : 0; //XXX SampleFreq*Channels*file->sfi.pcmbitwidth;
855 InfoCache.Cache(this,file);
860 bool cSndInfo::CDDBLookup(const char *filename)
864 const char *s=strstr(filename,CDFS_TRACK);
865 if(s && sscanf(s+strlen(CDFS_TRACK),"%d",&tr)==1) {
866 d(printf("snd: looking up disc id %08x track %d\n",id->discid,tr))
867 return cddb.Lookup(id,tr-1,this);
873 // --- cSndFile ----------------------------------------------------------------
875 cSndFile::cSndFile(const char *Filename)
881 cSndFile::~cSndFile()
886 bool cSndFile::Open(bool log)
888 if(sf) return (Seek()>=0);
891 sf=sf_open(Filename,SFM_READ,&sfi);
892 if(!sf && log) Error("open");
897 void cSndFile::Close(void)
899 if(sf) { sf_close(sf); sf=0; }
902 void cSndFile::Error(const char *action)
905 sf_error_str(sf,buff,sizeof(buff));
906 esyslog("ERROR: sndfile %s failed on %s: %s",action,Filename,buff);
909 sf_count_t cSndFile::Seek(sf_count_t frames, bool relativ)
912 if(!relativ) dir=SEEK_SET;
913 int n=sf_seek(sf,frames,dir);
914 if(n<0) Error("seek");
918 sf_count_t cSndFile::Stream(int *buffer, sf_count_t frames)
920 sf_count_t n=sf_readf_int(sf,buffer,frames);
921 if(n<0) Error("read");
926 #endif //HAVE_SNDFILE
931 // g++ -g -DTEST_MAIN -o test mp3-decoder-snd.c tools.o thread.o -lpthread
937 extern const char *tr(const char *test)
942 int main (int argc, char *argv[])
946 cddb.Load(1,argv[1]);