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