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