decoder-snd.c
author nathan
Sat, 29 Dec 2007 14:58:47 +0100
branchtrunk
changeset 11 7f424cf3f798
parent 9 dc75c2890a31
child 16 9d034fc2c5ec
permissions -rw-r--r--
use po2i18n for non-locale vdr
     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 #ifdef TEST_MAIN
    23 #define HAVE_SNDFILE
    24 #define REMOTE_LIRC
    25 #define _GNU_SOURCE
    26 #define DEBUG
    27 #endif
    28 
    29 #ifdef HAVE_SNDFILE
    30 
    31 #include <ctype.h>
    32 #include <stdlib.h>
    33 #include <stdio.h>
    34 #include <stdarg.h>
    35 #include <errno.h>
    36 #include <sys/types.h>
    37 #include <unistd.h>
    38 #include <math.h>
    39 
    40 #include "common.h"
    41 #include "setup-mp3.h"
    42 #include "decoder-snd.h"
    43 #include "data.h"
    44 #include "network.h"
    45 #include "menu-async.h"
    46 #include "i18n.h"
    47 #include "version.h"
    48 
    49 #ifndef SNDFILE_1
    50 #error You must use libsndfile version 1.x.x
    51 #endif
    52 
    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-"
    58 
    59 #define CDDB_PROTO 5                   // used protocol level
    60 #define CDDB_TOUT  30*1000             // connection timeout (ms)
    61 
    62 const char *cddbpath="/var/lib/cddb";  // default local cddb path
    63 
    64 #define CDDB_DEBUG  // debug cddb queries
    65 //#define DEBUG_CDFS  // debug cdfs parsing
    66 //#define GUARD_DEBUG // enable framebuffer guard
    67 
    68 #if !defined(NO_DEBUG) && defined(DEBUG_CDFS)
    69 #define dc(x) { (x); }
    70 #else
    71 #define dc(x) ; 
    72 #endif
    73 
    74 #ifndef TEST_MAIN
    75 
    76 // --- cSndDecoder -------------------------------------------------------------
    77 
    78 #define SF_SAMPLES (sizeof(pcm->samples[0])/sizeof(mad_fixed_t))
    79 
    80 cSndDecoder::cSndDecoder(const char *Filename)
    81 :cDecoder(Filename)
    82 ,file(Filename)
    83 ,info(&file)
    84 {
    85   pcm=0; framebuff=0; playing=ready=false;
    86 }
    87 
    88 cSndDecoder::~cSndDecoder()
    89 {
    90   Clean();
    91 }
    92 
    93 bool cSndDecoder::Valid(void)
    94 {
    95   bool res=false;
    96   if(TryLock()) {
    97     if(file.Open(false)) res=true;
    98     cDecoder::Unlock();
    99     }
   100   return res;
   101 }
   102 
   103 cFileInfo *cSndDecoder::FileInfo(void)
   104 {
   105   cFileInfo *fi=0;
   106   if(file.HasInfo()) fi=&file;
   107   else if(TryLock()){
   108     if(file.Open()) { fi=&file; file.Close(); }
   109     cDecoder::Unlock();
   110     }
   111   return fi;
   112 }
   113 
   114 cSongInfo *cSndDecoder::SongInfo(bool get)
   115 {
   116   cSongInfo *si=0;
   117   if(info.HasInfo()) si=&info;
   118   else if(get && TryLock()) {
   119     if(info.DoScan(false)) si=&info;
   120     cDecoder::Unlock();
   121     }
   122   return si;
   123 }
   124 
   125 cPlayInfo *cSndDecoder::PlayInfo(void)
   126 {
   127   if(playing) {
   128     pi.Index=index/info.SampleFreq;
   129     pi.Total=info.Total;
   130     return &pi;
   131     }
   132   return 0;
   133 }
   134 
   135 void cSndDecoder::Init(void)
   136 {
   137   Clean();
   138   pcm=new struct mad_pcm;
   139   framebuff=MALLOC(int,2*SF_SAMPLES+8);
   140 #ifdef GUARD_DEBUG
   141   for(int i=0; i<8; i++) framebuff[i+(SF_SAMPLES*2)-4]=0xdeadbeaf;
   142 #endif
   143   index=0;
   144 }
   145 
   146 bool cSndDecoder::Clean(void)
   147 {
   148   playing=false;
   149 
   150   buffMutex.Lock();
   151   run=false; bgCond.Broadcast();
   152   buffMutex.Unlock();
   153   cThread::Cancel(3);
   154 
   155   buffMutex.Lock();
   156   if(!ready) { deferedN=-1; ready=true; }
   157   fgCond.Broadcast();
   158   buffMutex.Unlock();
   159 
   160   delete pcm; pcm=0;
   161 #ifdef GUARD_DEBUG
   162   if(framebuff) {
   163     printf("snd: bufferguard");
   164     for(int i=0; i<8; i++) printf(" %08x",framebuff[i+(SF_SAMPLES*2)-4]);
   165     printf("\n");
   166     }
   167 #endif
   168   free(framebuff); framebuff=0;
   169   file.Close();
   170   return false;
   171 }
   172 
   173 bool cSndDecoder::Start(void)
   174 {
   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;
   182       cThread::Start();
   183       cDecoder::Unlock();
   184       return true;
   185       }
   186     else esyslog("ERROR: cannot play sound file %s: more than 2 channels",filename);
   187     }
   188   cDecoder::Unlock();
   189   return Clean();
   190 }
   191 
   192 bool cSndDecoder::Stop(void)
   193 {
   194   cDecoder::Lock();
   195   if(playing) Clean();
   196   cDecoder::Unlock();
   197   return true;
   198 }
   199 
   200 void cSndDecoder::Action(void)
   201 {
   202   buffMutex.Lock();
   203   while(run) {
   204     if(ready) bgCond.Wait(buffMutex);
   205     if(!ready) {
   206       buffMutex.Unlock();
   207       deferedN=file.Stream(framebuff,SF_SAMPLES);
   208       buffMutex.Lock();
   209       ready=true; fgCond.Broadcast();
   210       }
   211     }
   212   buffMutex.Unlock();
   213 }
   214 
   215 struct Decode *cSndDecoder::Done(eDecodeStatus status)
   216 {
   217   ds.status=status;
   218   ds.index=index*1000/info.SampleFreq;
   219   ds.pcm=pcm;
   220   cDecoder::Unlock(); // release the lock from Decode()
   221   return &ds;
   222 }
   223 
   224 struct Decode *cSndDecoder::Decode(void)
   225 {
   226   cDecoder::Lock(); // this is released in Done()
   227   if(playing) {
   228     cMutexLock lock(&buffMutex);
   229     while(!ready)
   230       if(!softCount || !fgCond.TimedWait(buffMutex,softCount*5)) {
   231         if(softCount<20) softCount++;
   232         return Done(dsSoftError);
   233         }
   234     softCount=0;
   235     ready=false; bgCond.Broadcast();
   236 
   237     int n=deferedN;
   238     if(n<0) return Done(dsError);
   239     if(n==0) return Done(dsEof);
   240 
   241     pcm->samplerate=file.sfi.samplerate;
   242     pcm->channels=file.sfi.channels;
   243     pcm->length=n;
   244     index+=n;
   245 
   246     int *data=framebuff;
   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) {
   250       for(; n>0 ; n--) {
   251         *sam0++=(*data++) >> s;
   252         *sam1++=(*data++) >> s;
   253         }
   254       }
   255     else {
   256       for(; n>0 ; n--)
   257         *sam0++=(*data++) >> s;
   258       }
   259     return Done(dsPlay);
   260     }
   261   return Done(dsError);
   262 }
   263 
   264 bool cSndDecoder::Skip(int Seconds, float bsecs)
   265 {
   266   cDecoder::Lock();
   267   bool res=false;
   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))
   275 
   276     buffMutex.Lock();
   277     frames=file.Seek(newpos,false);
   278     ready=false; bgCond.Broadcast();
   279     buffMutex.Unlock();
   280     if(frames>=0) {
   281       index=frames;
   282 #ifdef DEBUG
   283       int i=frames/file.sfi.samplerate;
   284       printf("snd: skipping to %02d:%02d (frame %lld)\n",i/60,i%60,frames);
   285 #endif
   286       res=true;
   287       }
   288     }
   289   cDecoder::Unlock();
   290   return res;
   291 }
   292 
   293 #endif //TEST_MAIN
   294 
   295 // --- cDiscID -------------------------------------------------------------------
   296 
   297 class cDiscID {
   298 public:
   299   int discid, ntrks, nsecs;
   300   int *offsets;
   301   //
   302   cDiscID(void);
   303   ~cDiscID();
   304   bool Get(void);
   305   };
   306 
   307 cDiscID::cDiscID(void)
   308 {
   309   offsets=0; discid=ntrks=0;
   310 }
   311 
   312 cDiscID::~cDiscID()
   313 {
   314   delete offsets;
   315 }
   316 
   317 bool cDiscID::Get(void)
   318 {
   319   bool res=false;
   320   FILE *f=fopen(CDFS_PROC,"r");
   321   if(f) {
   322     char line[256];
   323     bool state=false;
   324     int tr=0;
   325     while(fgets(line,sizeof(line),f)) {
   326       if(!state) {
   327         int id, n;
   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) {
   331             res=true;
   332             break;
   333             }
   334           else {
   335             discid=id; ntrks=n;
   336             delete offsets; offsets=new int[ntrks];
   337             state=true;
   338             }
   339           }
   340         }
   341       else {
   342         int off, end;
   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;
   346           if(++tr==ntrks) {
   347             nsecs=(end+1)/75;
   348             dc(printf("discid: nsecs=%d / 0x%x\n",nsecs,nsecs))
   349             res=true;
   350             break;
   351             }
   352           }
   353         }
   354       }
   355     fclose(f);
   356     }
   357   return res;
   358 }
   359 
   360 // --- cCDDBSong ---------------------------------------------------------------
   361 
   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>
   364 
   365 class cCDDBSong : public cListObject {
   366 public:
   367   cCDDBSong(void);
   368   ~cCDDBSong();
   369   //
   370   int Track;
   371   char *TTitle, *ExtT;
   372   char *Title, *Artist;
   373   };
   374 
   375 cCDDBSong::cCDDBSong(void)
   376 {
   377   Title=Artist=0; TTitle=ExtT=0;
   378 }
   379 
   380 cCDDBSong::~cCDDBSong()
   381 {
   382   free(Title);
   383   free(Artist);
   384   free(TTitle);
   385   free(ExtT);
   386 }
   387 
   388 // --- cCDDBDisc ---------------------------------------------------------------
   389 
   390 const char *sampler[] = { // some artist names to identify sampler discs
   391   "various",
   392   "varios",
   393   "variété",
   394   "compilation",
   395   "sampler",
   396   "mixed",
   397   "divers",
   398   "v.a.",
   399   "VA",
   400   "misc",
   401   "none",
   402   0 };
   403 
   404 class cCDDBDisc : public cList<cCDDBSong> {
   405 private:
   406   int DiscID;
   407   //
   408   bool isSampler;
   409   char *DTitle, *ExtD;
   410   //
   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);
   416   void Clean(void);
   417 public:
   418   cCDDBDisc(void);
   419   ~cCDDBDisc();
   420   bool Load(cDiscID *id, const char *filename);
   421   bool Cached(cDiscID *id) { return DiscID==id->discid; }
   422   bool TrackInfo(int tr, cSongInfo *si);
   423   //
   424   char *Album, *Artist;
   425   int Year;
   426   };
   427 
   428 cCDDBDisc::cCDDBDisc(void)
   429 {
   430   Album=Artist=0; DTitle=ExtD=0; DiscID=0;
   431 }
   432 
   433 cCDDBDisc::~cCDDBDisc()
   434 {
   435   Clean();
   436 }
   437 
   438 void cCDDBDisc::Clean(void)
   439 {
   440   free(DTitle); DTitle=0;
   441   free(ExtD); ExtD=0;
   442   free(Artist); Artist=0;
   443   free(Album); Album=0;
   444   Year=-1; DiscID=0; isSampler=false;
   445 }
   446 
   447 bool cCDDBDisc::TrackInfo(int tr, cSongInfo *si)
   448 {
   449   cCDDBSong *s=FindTrack(tr);
   450   if(s) {
   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;
   455     return true;
   456     }
   457   return false;
   458 }
   459 
   460 void cCDDBDisc::Put(const char *from, char * &to)
   461 {
   462   free(to);
   463   to=from ? strdup(from):0;
   464 }
   465 
   466 bool cCDDBDisc::Load(cDiscID *id, const char *filename)
   467 {
   468   char *p;
   469   Clean(); Clear();
   470 
   471   d(printf("cddb: loading discid %08x from %s\n",id->discid,filename))
   472   DiscID=id->discid;
   473   FILE *f=fopen(filename,"r");
   474   if(f) {
   475     char buff[1024];
   476     while(fgets(buff,sizeof(buff),f)) {
   477       int i=strlen(buff);
   478       while(i && (buff[i-1]=='\n' || buff[i-1]=='\r')) buff[--i]=0;
   479 
   480       if(buff[0]=='#') { // special comment line handling
   481         }
   482       else {
   483         p=strchr(buff,'=');
   484         if(p) {
   485            *p=0;
   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);
   495                }
   496              else if(!strncasecmp(name,"EXTT",4)) {
   497                cCDDBSong *s=GetTrack(name,4);
   498                if(s) Strcat(s->ExtT,value);
   499                }
   500              }
   501            }
   502         }
   503       }
   504     fclose(f);
   505 
   506     // read all data, now post-processing
   507     if(Count()>0) {
   508       if(DTitle) {
   509         if(Split(DTitle,'/',Artist,Album)) {
   510           for(int n=0 ; sampler[n] ; n++)
   511             if(!strncasecmp(Artist,sampler[n],strlen(sampler[n]))) {
   512               isSampler=true;
   513               break;
   514               }
   515           }
   516         else {
   517           Album=strdup(DTitle);
   518           isSampler=true;
   519           }
   520         }
   521       d(printf("cddb: found artist='%s' album='%s' isSampler=%d\n",Artist,Album,isSampler))
   522       free(DTitle); DTitle=0;
   523 
   524       if(!isSampler && Artist && Album && !strncmp(Album,Artist,strlen(Artist))) {
   525         d(printf("cddb: detecting sampler from Artist==Album\n"))
   526         isSampler=true;
   527         }
   528 
   529       if(!isSampler) {
   530         int nofail1=0, nofail2=0;
   531         cCDDBSong *s=First();
   532         while(s) {
   533           if(s->TTitle) {
   534             if(strstr(s->TTitle," / ")) nofail1++;
   535             //if(strstr(s->TTitle," - ")) nofail2++;
   536             }
   537           s=Next(s);
   538           }
   539         if(nofail1==Count() || nofail2==Count()) {
   540           d(printf("cddb: detecting sampler from nofail\n"))
   541           isSampler=true;
   542           }
   543         }
   544 
   545       if(Year<0 && ExtD && (p=strstr(ExtD,"YEAR:"))) Year=atoi(p+5);
   546       free(ExtD); ExtD=0;
   547       d(printf("cddb: found year=%d\n",Year))
   548 
   549       cCDDBSong *s=First();
   550       while(s) {
   551         if(s->TTitle) {
   552           if(isSampler) {
   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));
   557               }
   558             }
   559           else {
   560             s->Title=compactspace(strdup(s->TTitle));
   561             if(Artist) s->Artist=strdup(Artist);
   562             }
   563           }
   564         else s->Title=strdup(tr("unknown"));
   565 
   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))
   569         s=Next(s);
   570         }
   571       return true;
   572       }
   573     }
   574   return false;
   575 }
   576 
   577 bool cCDDBDisc::Split(const char *source, char div, char * &first, char * &second, bool only3)
   578 {
   579   int pos=-1, n=0;
   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; }
   583   if(pos>=0) {
   584     free(first); first=strdup(source); first[pos]=0; compactspace(first);
   585     free(second); second=strdup(source+pos+n); compactspace(second);
   586     return true;
   587     }
   588   return false;
   589 }
   590 
   591 void cCDDBDisc::Strcat(char * &store, char *value)
   592 {
   593   if(store) {
   594     char *n=MALLOC(char,strlen(store)+strlen(value)+1);
   595     if(n) {
   596       strcpy(n,store);
   597       strcat(n,value);
   598       free(store); store=n;
   599       }
   600     }
   601   else store=strdup(value);
   602 }
   603 
   604 cCDDBSong *cCDDBDisc::GetTrack(const char *name, unsigned int pos)
   605 {
   606   cCDDBSong *s=0;
   607   if(strlen(name)>pos) {
   608     int tr=atoi(&name[pos]);
   609     s=FindTrack(tr);
   610     if(!s) {
   611       s=new cCDDBSong;
   612       Add(s);
   613       s->Track=tr;
   614       }
   615     }
   616   return s;
   617 }
   618 
   619 cCDDBSong *cCDDBDisc::FindTrack(int tr)
   620 {
   621   cCDDBSong *s=First();
   622   while(s) {
   623     if(s->Track==tr) break;
   624     s=Next(s);
   625     }
   626   return s;
   627 }
   628 
   629 #ifndef TEST_MAIN
   630 
   631 // --- cCDDB -------------------------------------------------------------------
   632 
   633 class cCDDB : public cScanDir, cMutex {
   634 private:
   635   cCDDBDisc cache;
   636   cFileSource *src;
   637   cFileObj *file;
   638   cNet *net;
   639   char searchID[10], cddbstr[256];
   640   //
   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, ...);
   647 public:
   648   cCDDB(void);
   649   virtual ~cCDDB();
   650   bool Lookup(cDiscID *id, int track, cSongInfo *si);
   651   };
   652 
   653 cCDDB cddb;
   654 
   655 cCDDB::cCDDB(void)
   656 {
   657   src=0; file=0; net=0;
   658 }
   659 
   660 cCDDB::~cCDDB()
   661 {
   662   delete file;
   663   delete src;
   664   delete net;
   665 }
   666 
   667 bool cCDDB::Lookup(cDiscID *id, int track, cSongInfo *si)
   668 {
   669   bool res=false;
   670   Lock();
   671   if(!cache.Cached(id)) {
   672     if(LocalQuery(id) || (MP3Setup.UseCddb>1 && RemoteGet(id) && LocalQuery(id)))
   673       cache.Load(id,file->FullPath());
   674     }
   675   if(cache.Cached(id) && cache.TrackInfo(track,si)) res=true;
   676   Unlock();
   677   return res;
   678 }
   679 
   680 bool cCDDB::LocalQuery(cDiscID *id)
   681 {
   682   bool res=false;
   683   delete file; file=0;
   684   if(!src) src=new cFileSource(cddbpath,"CDDB database",false);
   685   if(src) {
   686     snprintf(searchID,sizeof(searchID),"%08x",id->discid);
   687     if(ScanDir(src,0,stDir,0,0,false) && file) res=true;
   688     }
   689   return res;
   690 }
   691 
   692 void cCDDB::DoItem(cFileSource *src, const char *subdir, const char *name)
   693 {
   694   if(!file) {
   695     file=new cFileObj(src,name,searchID,otFile);
   696     if(access(file->FullPath(),R_OK)) { delete file; file=0; }
   697     }
   698 }
   699 
   700 bool cCDDB::RemoteGet(cDiscID *id)
   701 {
   702   bool res=false;
   703   asyncStatus.Set(tr("Remote CDDB lookup..."));
   704 
   705   delete net; net=new cNet(16*1024,CDDB_TOUT,CDDB_TOUT);
   706   if(net->Connect(MP3Setup.CddbHost,MP3Setup.CddbPort)) {
   707     int code=GetCddbResponse();
   708     if(code/100==2) {
   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,PluginVersion);
   712       if(code/100==2) {
   713         code=DoCddbCmd("proto %d\n",CDDB_PROTO);
   714         if(code>0) {
   715           char off[1024];
   716           off[0]=0; for(int i=0 ; i<id->ntrks ; i++) sprintf(&off[strlen(off)]," %d",id->offsets[i]);
   717 
   718           code=DoCddbCmd("cddb query %08x %d %s %d\n",id->discid,id->ntrks,off,id->nsecs);
   719           if(code/100==2) {
   720             char *cat=0;
   721             if(code==200) cat=strdup(cddbstr);
   722             else if(code==210) {
   723               if(GetLine(off,sizeof(off))) {
   724                 cat=strdup(off);
   725                 while(GetLine(off,sizeof(off)) && off[0]!='.');
   726                 }
   727               }
   728 
   729             if(cat) {
   730               char *s=index(cat,' '); if(s) *s=0;
   731               code=DoCddbCmd("cddb read %s %08x\n",cat,id->discid);
   732               if(code==210) {
   733                 char *name=0;
   734                 asprintf(&name,"%s/%s/%08x",cddbpath,cat,id->discid);
   735                 if(MakeDirs(name,false)) {
   736                   FILE *out=fopen(name,"w");
   737                   if(out) {
   738                     while(GetLine(off,sizeof(off),false) && off[0]!='.') fputs(off,out);
   739                     fclose(out);
   740                     res=true;
   741                     }
   742                   else esyslog("fopen() failed: %s",strerror(errno));
   743                   }
   744                 free(name);
   745                 }
   746               else if(code>0) esyslog("server read error: %d %s",code,cddbstr);
   747               free(cat);
   748               }
   749             }
   750           else if(code>0) esyslog("server query error: %d %s",code,cddbstr);
   751           }
   752         else esyslog("server proto error: %d %s",code,cddbstr);
   753         }
   754       else if(code>0) esyslog("server hello error: %d %s",code,cddbstr);
   755       }
   756     else if(code>0) esyslog("server sign-on error: %d %s",code,cddbstr);
   757     }
   758 
   759   delete net; net=0;
   760   asyncStatus.Set(0);
   761   return res;
   762 }
   763 
   764 bool cCDDB::GetLine(char *buff, int size, bool log)
   765 {
   766   if(net->Gets(buff,size)>0) {
   767 #ifdef CDDB_DEBUG
   768     if(log) printf("cddb: <- %s",buff);
   769 #endif
   770     return true;
   771     }
   772   return false;
   773 }
   774 
   775 int cCDDB::GetCddbResponse(void)
   776 {
   777   char buf[1024];
   778   if(GetLine(buf,sizeof(buf))) {
   779     int code;
   780     if(sscanf(buf,"%d %255[^\n]",&code,cddbstr)==2) return code;
   781     else esyslog("Unexpected server response: %s",buf);
   782     }
   783   return -1;
   784 }
   785 
   786 int cCDDB::DoCddbCmd(const char *format, ...)
   787 {
   788   va_list ap;
   789   va_start(ap,format);
   790   char *buff=0;
   791   vasprintf(&buff,format,ap);
   792   va_end(ap);
   793 #ifdef CDDB_DEBUG
   794   printf("cddb: -> %s",buff);
   795 #endif
   796   int r=net->Puts(buff);
   797   free(buff);
   798   if(r<0) return -1;
   799   return GetCddbResponse();
   800 }
   801 
   802 // --- cSndInfo ----------------------------------------------------------------
   803 
   804 cSndInfo::cSndInfo(cSndFile *File)
   805 {
   806   file=File;
   807   id=new cDiscID;
   808 }
   809 
   810 cSndInfo::~cSndInfo()
   811 {
   812   delete id;
   813 }
   814 
   815 bool cSndInfo::Abort(bool result)
   816 {
   817   if(!keepOpen) file->Close();
   818   return result;
   819 }
   820 
   821 bool cSndInfo::DoScan(bool KeepOpen)
   822 {
   823   keepOpen=KeepOpen;
   824   if(!file->Open()) return Abort(false);
   825   if(HasInfo()) return Abort(true);
   826 
   827   // check the infocache
   828   cCacheData *dat=InfoCache.Search(file);
   829   if(dat) {
   830     Set(dat); dat->Unlock();
   831     if(!DecoderID) {
   832       DecoderID=DEC_SND;
   833       InfoCache.Cache(this,file);
   834       }
   835     return Abort(true);
   836     }
   837 
   838   Clear();
   839 
   840   if(file->FsType!=CDFS_MAGIC || !MP3Setup.UseCddb || !CDDBLookup(file->Filename))
   841     FakeTitle(file->Filename);
   842 
   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;
   849   DecoderID=DEC_SND;
   850 
   851   InfoDone();
   852   InfoCache.Cache(this,file);
   853   return Abort(true);  
   854 }
   855 
   856 bool cSndInfo::CDDBLookup(const char *filename)
   857 {
   858   if(id->Get()) {
   859     int tr;
   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);
   864       }
   865     }
   866   return false;
   867 }
   868 
   869 // --- cSndFile ----------------------------------------------------------------
   870 
   871 cSndFile::cSndFile(const char *Filename)
   872 :cFileInfo(Filename)
   873 {
   874   sf=0;
   875 }
   876 
   877 cSndFile::~cSndFile()
   878 {
   879   Close();
   880 }
   881 
   882 bool cSndFile::Open(bool log)
   883 {
   884   if(sf) return (Seek()>=0);
   885 
   886   if(FileInfo(log)) {
   887     sf=sf_open(Filename,SFM_READ,&sfi);
   888     if(!sf && log) Error("open");
   889     }
   890   return (sf!=0);
   891 }
   892 
   893 void cSndFile::Close(void)
   894 {
   895   if(sf) { sf_close(sf); sf=0; } 
   896 }
   897 
   898 void cSndFile::Error(const char *action)
   899 {
   900   char buff[128];
   901   sf_error_str(sf,buff,sizeof(buff));
   902   esyslog("ERROR: sndfile %s failed on %s: %s",action,Filename,buff);
   903 }
   904 
   905 sf_count_t cSndFile::Seek(sf_count_t frames, bool relativ)
   906 {
   907   int dir=SEEK_CUR;
   908   if(!relativ) dir=SEEK_SET;
   909   int n=sf_seek(sf,frames,dir);
   910   if(n<0) Error("seek");
   911   return n;
   912 }
   913 
   914 sf_count_t cSndFile::Stream(int *buffer, sf_count_t frames)
   915 {
   916   sf_count_t n=sf_readf_int(sf,buffer,frames);
   917   if(n<0) Error("read");
   918   return n;
   919 }
   920 
   921 #endif //TEST_MAIN
   922 #endif //HAVE_SNDFILE
   923 
   924 #ifdef TEST_MAIN
   925 //
   926 // to compile:
   927 // g++ -g -DTEST_MAIN -o test mp3-decoder-snd.c tools.o thread.o -lpthread
   928 //
   929 // calling:
   930 // test <cddb-file>
   931 //
   932 
   933 extern const char *tr(const char *test)
   934 {
   935   return test;
   936 }
   937 
   938 int main (int argc, char *argv[])
   939 {
   940   cCDDBDisc cddb;
   941   
   942   cddb.Load(1,argv[1]);
   943   return 0;
   944 }
   945 #endif