decoder.c
author nathan
Sat, 29 Dec 2007 14:49:09 +0100
branchtrunk
changeset 2 4c1f7b705009
parent 0 474a1293c3c0
child 15 710f847b02af
permissions -rw-r--r--
release 0.10.1
     1 /*
     2  * MP3/MPlayer plugin to VDR (C++)
     3  *
     4  * (C) 2001-2007 Stefan Huelswitt <s.huelswitt@gmx.de>
     5  *
     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.
    10  *
    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.
    15  *
    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
    20  */
    21 
    22 #include <stdlib.h>
    23 #include <stdio.h>
    24 #include <errno.h>
    25 #include <sys/stat.h>
    26 #include <sys/vfs.h>
    27 
    28 #include <vdr/videodir.h>
    29 
    30 #include "common.h"
    31 #include "data-mp3.h"
    32 #include "data-src.h"
    33 #include "decoder.h"
    34 #include "decoder-core.h"
    35 #include "decoder-mp3.h"
    36 #include "decoder-mp3-stream.h"
    37 #include "decoder-snd.h"
    38 #include "decoder-ogg.h"
    39 
    40 #define CACHEFILENAME     "id3info.cache"
    41 #define CACHESAVETIMEOUT  120 // secs
    42 #define CACHEPURGETIMEOUT 120 // days
    43 
    44 extern cFileSources MP3Sources;
    45 
    46 cInfoCache InfoCache;
    47 char *cachedir=0;
    48 
    49 int MakeHashBuff(const char *buff, int len)
    50 {
    51   int h=len;
    52   while(len--) h=(h*13 + *buff++) & 0x7ff;
    53   return h;
    54 }
    55 
    56 // --- cSongInfo ---------------------------------------------------------------
    57 
    58 cSongInfo::cSongInfo(void)
    59 {
    60   Title=Artist=Album=0;
    61   Clear();
    62 }
    63 
    64 cSongInfo::~cSongInfo()
    65 {
    66   Clear();
    67 }
    68 
    69 void cSongInfo::Clear(void)
    70 {
    71   Frames=0; Total=-1; DecoderID=0;
    72   SampleFreq=Channels=Bitrate=MaxBitrate=ChMode=-1;
    73   free(Title); Title=0;
    74   free(Artist); Artist=0;
    75   free(Album); Album=0;
    76   Year=-1;
    77   Level=Peak=0.0;
    78   infoDone=false;
    79 }
    80 
    81 void cSongInfo::Set(cSongInfo *si)
    82 {
    83   Clear(); InfoDone();
    84   Frames=si->Frames;
    85   Total=si->Total;
    86   SampleFreq=si->SampleFreq;
    87   Channels=si->Channels;
    88   Bitrate=si->Bitrate;
    89   MaxBitrate=si->MaxBitrate;
    90   ChMode=si->ChMode;
    91   Year=si->Year;
    92   Title=si->Title ? strdup(si->Title):0;
    93   Artist=si->Artist ? strdup(si->Artist):0;
    94   Album=si->Album ? strdup(si->Album):0;
    95   if(si->Level>0.0) { // preserve old level
    96     Level=si->Level;
    97     Peak=si->Peak;
    98     }
    99   DecoderID=si->DecoderID;
   100 }
   101 
   102 void cSongInfo::FakeTitle(const char *filename, const char *extention)
   103 {
   104   // if no title, try to build a reasonable from the filename
   105   if(!Title && filename)  {
   106     char *s=rindex(filename,'/');
   107     if(s && *s=='/') {
   108       s++;
   109       Title=strdup(s);
   110       strreplace(Title,'_',' ');
   111       if(extention) {                            // strip given extention
   112         int l=strlen(Title)-strlen(extention);
   113         if(l>0 && !strcasecmp(Title+l,extention)) Title[l]=0;
   114         }
   115       else {                                     // strip any extention
   116         s=rindex(Title,'.');
   117         if(s && *s=='.' && strlen(s)<=5) *s=0;
   118         }
   119       d(printf("mp3: faking title '%s' from filename '%s'\n",Title,filename))
   120       }
   121     }
   122 }
   123 
   124 // --- cFileInfo ---------------------------------------------------------------
   125 
   126 cFileInfo::cFileInfo(void)
   127 {
   128   Filename=FsID=0; Clear();
   129 }
   130 
   131 cFileInfo::cFileInfo(const char *Name)
   132 {
   133   Filename=FsID=0; Clear();
   134   Filename=strdup(Name);
   135 }
   136 
   137 cFileInfo::~cFileInfo()
   138 {
   139   Clear();
   140 }
   141 
   142 void cFileInfo::Clear(void)
   143 {
   144   free(Filename); Filename=0;
   145   free(FsID); FsID=0;
   146   Filesize=0; CTime=0; FsType=0; removable=-1;
   147   infoDone=false;
   148 }
   149 
   150 bool cFileInfo::Removable(void)
   151 {
   152   if(removable<0 && Filename) {
   153     cFileSource *src=MP3Sources.FindSource(Filename);
   154     if(src) removable=src->NeedsMount();
   155     else removable=1;
   156     }
   157   return (removable!=0);
   158 }
   159 
   160 void cFileInfo::Set(cFileInfo *fi)
   161 {
   162   Clear(); InfoDone();
   163   Filename=fi->Filename ? strdup(fi->Filename):0;
   164   FsID=fi->FsID ? strdup(fi->FsID):0;
   165   Filesize=fi->Filesize;
   166   CTime=fi->CTime;
   167 }
   168 
   169 
   170 bool cFileInfo::FileInfo(bool log)
   171 {
   172   if(Filename) {
   173     struct stat64 ds;
   174     if(!stat64(Filename,&ds)) {
   175       if(S_ISREG(ds.st_mode)) {
   176         free(FsID); FsID=0;
   177         FsType=0;
   178         struct statfs64 sfs;
   179         if(!statfs64(Filename,&sfs)) {
   180           if(Removable()) asprintf(&FsID,"%llx:%llx",sfs.f_blocks,sfs.f_files);
   181           FsType=sfs.f_type;
   182           }
   183         else if(errno!=ENOSYS && log) { esyslog("ERROR: can't statfs %s: %s",Filename,strerror(errno)); }
   184         Filesize=ds.st_size;
   185         CTime=ds.st_ctime;
   186 #ifdef CDFS_MAGIC
   187         if(FsType==CDFS_MAGIC) CTime=0; // CDFS returns mount time as ctime
   188 #endif
   189         InfoDone();
   190         return true;
   191         }
   192       else if(log) { esyslog("ERROR: %s is not a regular file",Filename); }
   193       }
   194     else if(log) { esyslog("ERROR: can't stat %s: %s",Filename,strerror(errno)); }
   195     }
   196   return false;
   197 }
   198 
   199 // --- cDecoders ---------------------------------------------------------------
   200 
   201 cDecoder *cDecoders::FindDecoder(cFileObj *Obj)
   202 {
   203   const char *full=Obj->FullPath();
   204   cFileInfo fi(full);
   205   cCacheData *dat;
   206   cDecoder *decoder=0;
   207   if(fi.FileInfo(false) && (dat=InfoCache.Search(&fi))) {
   208     if(dat->DecoderID) {
   209       //d(printf("mp3: found DecoderID '%s' for %s from cache\n",cDecoders::ID2Str(dat->DecoderID),Filename))
   210       switch(dat->DecoderID) {
   211         case DEC_MP3:  decoder=new cMP3Decoder(full); break;
   212         case DEC_MP3S: decoder=new cMP3StreamDecoder(full); break;
   213 #ifdef HAVE_SNDFILE
   214         case DEC_SND:  decoder=new cSndDecoder(full); break;
   215 #endif
   216 #ifdef HAVE_VORBISFILE
   217         case DEC_OGG:  decoder=new cOggDecoder(full); break;
   218 #endif
   219         default:       esyslog("ERROR: bad DecoderID '%s' from info cache: %s",cDecoders::ID2Str(dat->DecoderID),full); break;
   220         }
   221       }
   222     dat->Unlock();
   223     }
   224 
   225   if(!decoder || !decoder->Valid()) {
   226     // no decoder in cache or cached decoder doesn't matches.
   227     // try to detect a decoder
   228 
   229     delete decoder; decoder=0;
   230 #ifdef HAVE_SNDFILE
   231     if(!decoder) {
   232       decoder=new cSndDecoder(full);
   233       if(!decoder || !decoder->Valid()) { delete decoder; decoder=0; }
   234       }
   235 #endif
   236 #ifdef HAVE_VORBISFILE
   237     if(!decoder) {
   238       decoder=new cOggDecoder(full);
   239       if(!decoder || !decoder->Valid()) { delete decoder; decoder=0; }
   240       }
   241 #endif
   242     if(!decoder) {
   243       decoder=new cMP3StreamDecoder(full);
   244       if(!decoder || !decoder->Valid()) { delete decoder; decoder=0; }
   245       }
   246     if(!decoder) {
   247       decoder=new cMP3Decoder(full);
   248       if(!decoder || !decoder->Valid()) { delete decoder; decoder=0; }
   249       }
   250     if(!decoder) esyslog("ERROR: no decoder found for %s",Obj->Name());
   251     }
   252   return decoder;
   253 }
   254 
   255 const char *cDecoders::ID2Str(int id)
   256 {
   257   switch(id) {
   258     case DEC_MP3:  return DEC_MP3_STR;
   259     case DEC_MP3S: return DEC_MP3S_STR;
   260     case DEC_SND:  return DEC_SND_STR;
   261     case DEC_OGG:  return DEC_OGG_STR;
   262     }
   263   return 0;
   264 }
   265 
   266 int cDecoders::Str2ID(const char *str)
   267 {
   268   if     (!strcmp(str,DEC_MP3_STR )) return DEC_MP3;
   269   else if(!strcmp(str,DEC_MP3S_STR)) return DEC_MP3S;
   270   else if(!strcmp(str,DEC_SND_STR )) return DEC_SND;
   271   else if(!strcmp(str,DEC_OGG_STR )) return DEC_OGG;
   272   return 0;
   273 }
   274 
   275 // --- cDecoder ----------------------------------------------------------------
   276 
   277 cDecoder::cDecoder(const char *Filename)
   278 {
   279   filename=strdup(Filename);
   280   locked=0; urgentLock=playing=false;
   281 }
   282 
   283 cDecoder::~cDecoder()
   284 {
   285   free(filename);
   286 }
   287 
   288 void cDecoder::Lock(bool urgent)
   289 {
   290   locklock.Lock();
   291   if(urgent && locked) urgentLock=true; // signal other locks to release quickly
   292   locked++;
   293   locklock.Unlock(); // don't hold the "locklock" when locking "lock", may cause a deadlock
   294   lock.Lock();
   295   urgentLock=false;
   296 }
   297 
   298 void cDecoder::Unlock(void)
   299 {
   300   locklock.Lock();
   301   locked--;
   302   lock.Unlock();
   303   locklock.Unlock();
   304 }
   305 
   306 bool cDecoder::TryLock(void)
   307 {
   308   bool res=false;
   309   locklock.Lock();
   310   if(!locked && !playing) {
   311     Lock();
   312     res=true;
   313     }
   314   locklock.Unlock();
   315   return res;
   316 }
   317 
   318 // --- cCacheData -----------------------------------------------------
   319 
   320 cCacheData::cCacheData(void)
   321 {
   322   touch=0; version=0;
   323 }
   324 
   325 void cCacheData::Touch(void)
   326 {
   327   touch=time(0);
   328 }
   329 
   330 #define SECS_PER_DAY (24*60*60)
   331 
   332 bool cCacheData::Purge(void)
   333 {
   334   time_t now=time(0);
   335   //XXX does this realy made sense?
   336   //if(touch+CACHEPURGETIMEOUT*SECS_PER_DAY < now) {
   337   //  d(printf("cache: purged: timeout %s\n",Filename))
   338   //  return true;
   339   //  }
   340   if(touch+CACHEPURGETIMEOUT*SECS_PER_DAY/10 < now) {
   341     if(!Removable()) {                            // is this a permant source?
   342       struct stat64 ds;                           // does the file exists? if not, purge
   343       if(stat64(Filename,&ds) || !S_ISREG(ds.st_mode) || access(Filename,R_OK)) {
   344         d(printf("cache: purged: file not found %s\n",Filename))
   345         return true;
   346         }
   347       }
   348     }
   349   return false;
   350 }
   351 
   352 bool cCacheData::Upgrade(void)
   353 {
   354   if(version<7) {
   355     if(DecoderID==DEC_SND || (Title && startswith(Title,"track-")))
   356       return false;              // Trash older SND entries (incomplete)
   357 
   358     if(Removable()) {
   359       if(!FsID) FsID=strdup("old"); // Dummy entry, will be replaced in InfoCache::Search()
   360       }
   361     else { free(FsID); FsID=0; }
   362     }
   363   if(version<4) {
   364     Touch();                     // Touch entry
   365     }
   366   if(version<3 && !Title) {
   367     FakeTitle(Filename,".mp3");  // Fake title
   368     }
   369   if(version<2 && Bitrate<=0) {
   370     return false;                // Trash entry without bitrate
   371     }
   372   return true;
   373 }
   374 
   375 void cCacheData::Create(cFileInfo *fi, cSongInfo *si)
   376 {
   377   cFileInfo::Set(fi);
   378   cSongInfo::Set(si);
   379   hash=MakeHash(Filename);
   380   Touch();
   381 }
   382 
   383 bool cCacheData::Save(FILE *f)
   384 {
   385   fprintf(f,"##BEGIN\n"
   386             "Filename=%s\n"
   387             "Filesize=%lld\n"
   388             "Timestamp=%ld\n"
   389             "Touch=%ld\n"
   390             "Version=%d\n"
   391             "Frames=%d\n"
   392             "Total=%d\n"
   393             "SampleFreq=%d\n"
   394             "Channels=%d\n"
   395             "Bitrate=%d\n"
   396             "MaxBitrate=%d\n"
   397             "ChMode=%d\n"
   398             "Year=%d\n"
   399             "Level=%.4f\n"
   400             "Peak=%.4f\n",
   401             Filename,Filesize,CTime,touch,CACHE_VERSION,Frames,Total,SampleFreq,Channels,Bitrate,MaxBitrate,ChMode,Year,Level,Peak);
   402   if(Title)     fprintf(f,"Title=%s\n"    ,Title);
   403   if(Artist)    fprintf(f,"Artist=%s\n"   ,Artist);
   404   if(Album)     fprintf(f,"Album=%s\n"    ,Album);
   405   if(DecoderID) fprintf(f,"DecoderID=%s\n",cDecoders::ID2Str(DecoderID));
   406   if(FsID)      fprintf(f,"FsID=%s\n"     ,FsID);
   407   fprintf(f,"##END\n");
   408   return ferror(f)==0;
   409 }
   410 
   411 bool cCacheData::Load(FILE *f)
   412 {
   413   char buf[1024], *delimiters="=\n";
   414 
   415   cFileInfo::Clear();
   416   cSongInfo::Clear();
   417   while(fgets(buf,sizeof(buf),f)) {
   418     char *ptrptr;
   419     char *name =strtok_r(buf ,delimiters,&ptrptr);
   420     char *value=strtok_r(0,delimiters,&ptrptr);
   421     if(name) {
   422       if(!strcasecmp(name,"##END")) break;
   423       if(value) {
   424         if     (!strcasecmp(name,"Filename"))   Filename  =strdup(value);
   425         else if(!strcasecmp(name,"Filesize") ||
   426                 !strcasecmp(name,"Size"))       Filesize  =atoll(value);
   427         else if(!strcasecmp(name,"FsID"))       FsID      =strdup(value);
   428         else if(!strcasecmp(name,"Timestamp"))  CTime     =atol(value);
   429         else if(!strcasecmp(name,"Touch"))      touch     =atol(value);
   430         else if(!strcasecmp(name,"Version"))    version   =atoi(value);
   431         else if(!strcasecmp(name,"DecoderID"))  DecoderID =cDecoders::Str2ID(value);
   432         else if(!strcasecmp(name,"Frames"))     Frames    =atoi(value);
   433         else if(!strcasecmp(name,"Total"))      Total     =atoi(value);
   434         else if(!strcasecmp(name,"SampleFreq")) SampleFreq=atoi(value);
   435         else if(!strcasecmp(name,"Channels"))   Channels  =atoi(value);
   436         else if(!strcasecmp(name,"Bitrate"))    Bitrate   =atoi(value);
   437         else if(!strcasecmp(name,"MaxBitrate")) MaxBitrate=atoi(value);
   438         else if(!strcasecmp(name,"ChMode"))     ChMode    =atoi(value);
   439         else if(!strcasecmp(name,"Year"))       Year      =atoi(value);
   440         else if(!strcasecmp(name,"Title"))      Title     =strdup(value);
   441         else if(!strcasecmp(name,"Artist"))     Artist    =strdup(value);
   442         else if(!strcasecmp(name,"Album"))      Album     =strdup(value);
   443         else if(!strcasecmp(name,"Level"))      Level     =atof(value);
   444         else if(!strcasecmp(name,"Peak"))       Peak      =atof(value);
   445         else d(printf("cache: ignoring bad token '%s' from cache file\n",name))
   446         }
   447       }
   448     }
   449 
   450   if(ferror(f) || !Filename) return false;
   451   hash=MakeHash(Filename);
   452   return true;
   453 }
   454 
   455 // --- cInfoCache ----------------------------------------------------
   456 
   457 cInfoCache::cInfoCache(void)
   458 {
   459   lasttime=0; modified=false;
   460   lastpurge=time(0)-(50*60);
   461 }
   462 
   463 void cInfoCache::Shutdown(void)
   464 {
   465   Cancel(10);
   466   Save(true);
   467 }
   468 
   469 void cInfoCache::Cache(cSongInfo *info, cFileInfo *file)
   470 {
   471   lock.Lock();
   472   cCacheData *dat=Search(file);
   473   if(dat) {
   474     dat->Create(file,info);
   475     Modified();
   476     dat->Unlock();
   477     d(printf("cache: updating infos for %s\n",file->Filename))
   478     }
   479   else {
   480     dat=new cCacheData;
   481     dat->Create(file,info);
   482     AddEntry(dat);
   483     d(printf("cache: caching infos for %s\n",file->Filename))
   484     }
   485   lock.Unlock();
   486 }
   487 
   488 cCacheData *cInfoCache::Search(cFileInfo *file)
   489 {
   490   int hash=MakeHash(file->Filename);
   491   lock.Lock();
   492   cCacheData *dat=FirstEntry(hash);
   493   while(dat) {
   494     if(dat->hash==hash && !strcmp(dat->Filename,file->Filename) && dat->Filesize==file->Filesize) {
   495       dat->Lock();
   496       if(file->FsID && dat->FsID && !strcmp(dat->FsID,"old")) { // duplicate FsID for old entries
   497         dat->FsID=strdup(file->FsID);
   498         dat->Touch(); Modified();
   499         //d(printf("adding FsID for %s\n",dat->Filename))
   500         }
   501 
   502       if((!file->FsID && !dat->FsID) || (file->FsID && dat->FsID && !strcmp(dat->FsID,file->FsID))) {
   503         //d(printf("cache: found cache entry for %s\n",dat->Filename))
   504         dat->Touch(); Modified();
   505         if(dat->CTime!=file->CTime) {
   506           d(printf("cache: ctime differs, removing from cache: %s\n",dat->Filename))
   507           DelEntry(dat); dat=0;
   508           }
   509         break;
   510         }
   511       dat->Unlock();
   512       }
   513     dat=(cCacheData *)dat->Next();
   514     }
   515   lock.Unlock();
   516   return dat;
   517 }
   518 
   519 void cInfoCache::AddEntry(cCacheData *dat)
   520 {
   521   lists[dat->hash%CACHELINES].Add(dat);
   522   Modified();
   523 }
   524 
   525 void cInfoCache::DelEntry(cCacheData *dat)
   526 {
   527   dat->Lock();
   528   lists[dat->hash%CACHELINES].Del(dat);
   529   Modified();
   530 }
   531 
   532 cCacheData *cInfoCache::FirstEntry(int hash)
   533 {
   534   return lists[hash%CACHELINES].First();
   535 }
   536 
   537 bool cInfoCache::Purge(void)
   538 {
   539   time_t now=time(0);
   540   if(now-lastpurge>(60*60)) {
   541     isyslog("cleaning up id3 cache");
   542     Start();
   543     lastpurge=now;
   544     }
   545   return Active();
   546 }
   547 
   548 void cInfoCache::Action(void)
   549 {
   550   d(printf("cache: id3 cache purge thread started (pid=%d)\n",getpid()))
   551   nice(3);
   552   lock.Lock();
   553   for(int i=0,n=0 ; i<CACHELINES && Running(); i++) {
   554     cCacheData *dat=FirstEntry(i);
   555     while(dat && Running()) {
   556       cCacheData *ndat=(cCacheData *)dat->Next();
   557       if(dat->Purge()) DelEntry(dat);
   558       dat=ndat;
   559 
   560       if(++n>30) {
   561         lastmod=false; n=0;
   562         lock.Unlock(); lock.Lock();    // give concurrent thread an access chance
   563         if(lastmod) dat=FirstEntry(i); // restart line, if cache changed meanwhile
   564         }
   565       }
   566     }
   567   lock.Unlock();
   568   d(printf("cache: id3 cache purge thread ended (pid=%d)\n",getpid()))
   569 }
   570 
   571 char *cInfoCache::CacheFile(void)
   572 {
   573   return AddPath(cachedir?cachedir:VideoDirectory,CACHEFILENAME);
   574 }
   575 
   576 void cInfoCache::Save(bool force)
   577 {
   578   if(modified && (force || (!Purge() && time(0)>lasttime))) {
   579     char *name=CacheFile();
   580     cSafeFile f(name);
   581     free(name);
   582     if(f.Open()) {
   583       lock.Lock();
   584       fprintf(f,"## This is a generated file. DO NOT EDIT!!\n"
   585                 "## This file will be OVERWRITTEN WITHOUT WARNING!!\n");
   586       for(int i=0 ; i<CACHELINES ; i++) {
   587         cCacheData *dat=FirstEntry(i);
   588         while(dat) {
   589           if(!dat->Save(f)) { i=CACHELINES+1; break; }
   590           dat=(cCacheData *)dat->Next();
   591           }
   592         }
   593       lock.Unlock();
   594       f.Close();
   595       modified=false; lasttime=time(0)+CACHESAVETIMEOUT;
   596       d(printf("cache: saved cache to file\n"))
   597       }
   598     }
   599 }
   600 
   601 void cInfoCache::Load(void)
   602 {
   603   char *name=CacheFile();
   604   if(access(name,F_OK)==0) {
   605     isyslog("loading id3 cache from %s",name);
   606     FILE *f=fopen(name,"r");
   607     if(f) {
   608       char buf[256];
   609       bool mod=false;
   610       lock.Lock();
   611       for(int i=0 ; i<CACHELINES ; i++) lists[i].Clear();
   612       while(fgets(buf,sizeof(buf),f)) {
   613         if(!strcasecmp(buf,"##BEGIN\n")) {
   614           cCacheData *dat=new cCacheData;
   615           if(dat->Load(f)) {
   616             if(dat->version!=CACHE_VERSION) {
   617               if(dat->Upgrade()) mod=true;
   618               else { delete dat; continue; }
   619               }
   620             AddEntry(dat);
   621             }
   622           else {
   623             delete dat;
   624             if(ferror(f)) {
   625               esyslog("ERROR: failed to load id3 cache");
   626               break;
   627               }
   628             }
   629           }
   630         }
   631       lock.Unlock();
   632       fclose(f);
   633       modified=false; if(mod) Modified();
   634       }
   635     else LOG_ERROR_STR(name);
   636     }
   637   free(name);
   638 }