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