decoder-ogg.c
author nathan
Thu, 12 Feb 2009 20:47:23 +0800
branchtrunk
changeset 25 887faebaba0a
parent 6 111ef8181229
child 33 65ed49cbc08b
permissions -rw-r--r--
make song information handling UTF8 aware
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 HAVE_VORBISFILE
nathan@0
    23
nathan@0
    24
#include <stdlib.h>
nathan@0
    25
#include <stdio.h>
nathan@0
    26
#include <errno.h>
nathan@0
    27
nathan@0
    28
#include "common.h"
nathan@0
    29
#include "decoder-ogg.h"
nathan@0
    30
nathan@0
    31
// --- cOggFile ----------------------------------------------------------------
nathan@0
    32
nathan@0
    33
cOggFile::cOggFile(const char *Filename)
nathan@0
    34
:cFileInfo(Filename)
nathan@0
    35
{
nathan@0
    36
  canSeek=opened=false;
nathan@0
    37
}
nathan@0
    38
nathan@0
    39
cOggFile::~cOggFile()
nathan@0
    40
{
nathan@0
    41
  Close();
nathan@0
    42
}
nathan@0
    43
nathan@0
    44
bool cOggFile::Open(bool log)
nathan@0
    45
{
nathan@0
    46
  if(opened) {
nathan@0
    47
    if(canSeek) return (Seek()>=0);
nathan@0
    48
    return true;
nathan@0
    49
    }
nathan@0
    50
nathan@0
    51
  if(FileInfo(log)) {
nathan@0
    52
    FILE *f=fopen(Filename,"r");
nathan@0
    53
    if(f) {
nathan@0
    54
      int r=ov_open(f,&vf,0,0);
nathan@0
    55
      if(!r) {
nathan@0
    56
        canSeek=(ov_seekable(&vf)!=0);
nathan@0
    57
        opened=true;
nathan@0
    58
        }
nathan@0
    59
      else {
nathan@0
    60
        fclose(f);
nathan@0
    61
        if(log) Error("open",r);
nathan@0
    62
        }
nathan@0
    63
      }
nathan@0
    64
    else if(log) { esyslog("ERROR: failed to open file %s: %s",Filename,strerror(errno)); }
nathan@0
    65
    }
nathan@0
    66
  return opened;
nathan@0
    67
}
nathan@0
    68
nathan@0
    69
void cOggFile::Close(void)
nathan@0
    70
{
nathan@0
    71
  if(opened) { ov_clear(&vf); opened=false; }
nathan@0
    72
}
nathan@0
    73
nathan@0
    74
void cOggFile::Error(const char *action, const int err)
nathan@0
    75
{
nathan@6
    76
  const char *errstr;
nathan@0
    77
  switch(err) {
nathan@0
    78
    case OV_FALSE:      errstr="false/no data available"; break;
nathan@0
    79
    case OV_EOF:        errstr="EOF"; break;
nathan@0
    80
    case OV_HOLE:       errstr="missing or corrupted data"; break;
nathan@0
    81
    case OV_EREAD:      errstr="read error"; break;
nathan@0
    82
    case OV_EFAULT:     errstr="internal error"; break;
nathan@0
    83
    case OV_EIMPL:      errstr="unimplemented feature"; break;
nathan@0
    84
    case OV_EINVAL:     errstr="invalid argument"; break;
nathan@0
    85
    case OV_ENOTVORBIS: errstr="no Ogg Vorbis stream"; break;
nathan@0
    86
    case OV_EBADHEADER: errstr="corrupted Ogg Vorbis stream"; break;
nathan@0
    87
    case OV_EVERSION:   errstr="unsupported bitstream version"; break;
nathan@0
    88
    case OV_ENOTAUDIO:  errstr="ENOTAUDIO"; break;
nathan@0
    89
    case OV_EBADPACKET: errstr="EBADPACKET"; break;
nathan@0
    90
    case OV_EBADLINK:   errstr="corrupted link"; break;
nathan@0
    91
    case OV_ENOSEEK:    errstr="stream not seekable"; break;
nathan@0
    92
    default:            errstr="unspecified error"; break;
nathan@0
    93
    }
nathan@0
    94
  esyslog("ERROR: vorbisfile %s failed on %s: %s",action,Filename,errstr);
nathan@0
    95
}
nathan@0
    96
nathan@0
    97
long long cOggFile::IndexMs(void)
nathan@0
    98
{
nathan@0
    99
  double p=ov_time_tell(&vf);
nathan@0
   100
  if(p<0.0) p=0.0;
nathan@0
   101
  return (long long)(p*1000.0);
nathan@0
   102
}
nathan@0
   103
nathan@0
   104
long long cOggFile::Seek(long long posMs, bool relativ)
nathan@0
   105
{
nathan@0
   106
  if(relativ) posMs+=IndexMs();
nathan@0
   107
  int r=ov_time_seek(&vf,(double)posMs/1000.0);
nathan@0
   108
  if(r) {
nathan@0
   109
    Error("seek",r);
nathan@0
   110
    return -1;
nathan@0
   111
    }
nathan@0
   112
  posMs=IndexMs();
nathan@0
   113
  return posMs;
nathan@0
   114
}
nathan@0
   115
nathan@0
   116
int cOggFile::Stream(short *buffer, int samples)
nathan@0
   117
{
nathan@0
   118
  int n;
nathan@0
   119
  do {
nathan@0
   120
    int stream;
nathan@0
   121
    n=ov_read(&vf,(char *)buffer,samples*2,0,2,1,&stream);
nathan@0
   122
    } while(n==OV_HOLE);
nathan@0
   123
  if(n<0) Error("read",n);
nathan@0
   124
  return (n/2);
nathan@0
   125
}
nathan@0
   126
nathan@0
   127
// --- cOggInfo ----------------------------------------------------------------
nathan@0
   128
nathan@0
   129
cOggInfo::cOggInfo(cOggFile *File)
nathan@0
   130
{
nathan@0
   131
  file=File;
nathan@0
   132
}
nathan@0
   133
nathan@0
   134
bool cOggInfo::Abort(bool result)
nathan@0
   135
{
nathan@0
   136
  if(!keepOpen) file->Close();
nathan@0
   137
  return result;
nathan@0
   138
}
nathan@0
   139
nathan@0
   140
bool cOggInfo::DoScan(bool KeepOpen)
nathan@0
   141
{
nathan@0
   142
  keepOpen=KeepOpen;
nathan@0
   143
  if(!file->Open()) return Abort(false);
nathan@0
   144
  if(HasInfo()) return Abort(true);
nathan@0
   145
nathan@0
   146
  // check the infocache
nathan@0
   147
  cCacheData *dat=InfoCache.Search(file);
nathan@0
   148
  if(dat) {
nathan@0
   149
    Set(dat); dat->Unlock();
nathan@25
   150
    ConvertToSys();
nathan@0
   151
    if(!DecoderID) {
nathan@0
   152
      DecoderID=DEC_OGG;
nathan@0
   153
      InfoCache.Cache(this,file);
nathan@0
   154
      }
nathan@0
   155
    return Abort(true);
nathan@0
   156
    }
nathan@0
   157
nathan@0
   158
  Clear();
nathan@0
   159
nathan@0
   160
  vorbis_comment *vc=ov_comment(&file->vf,-1);
nathan@0
   161
  if(vc) {
nathan@0
   162
    for(int i=0 ; i<vc->comments ; i++) {
nathan@0
   163
      const char *cc=vc->user_comments[i];
nathan@0
   164
      d(printf("ogg: comment%d='%s'\n",i,cc))
nathan@0
   165
      char *p=strchr(cc,'=');
nathan@0
   166
      if(p) {
nathan@0
   167
        const int len=p-cc;
nathan@0
   168
        p++;
nathan@0
   169
        if(!strncasecmp(cc,"TITLE",len)) {
nathan@0
   170
          if(!Title) Title=strdup(p);
nathan@0
   171
          }
nathan@0
   172
        else if(!strncasecmp(cc,"ARTIST",len)) {
nathan@0
   173
          if(!Artist) Artist=strdup(p);
nathan@0
   174
          }
nathan@0
   175
        else if(!strncasecmp(cc,"ALBUM",len)) {
nathan@0
   176
          if(!Album) Album=strdup(p);
nathan@0
   177
          }
nathan@0
   178
        else if(!strncasecmp(cc,"YEAR",len)) {
nathan@0
   179
          if(Year<0) {
nathan@0
   180
            Year=atoi(p);
nathan@0
   181
            if(Year<1800 || Year>2100) Year=-1;
nathan@0
   182
            }
nathan@0
   183
          }
nathan@0
   184
        }
nathan@0
   185
      }
nathan@0
   186
    }
nathan@0
   187
  if(!Title) FakeTitle(file->Filename);
nathan@0
   188
nathan@0
   189
  vorbis_info *vi=ov_info(&file->vf,-1);
nathan@0
   190
  if(!vi) Abort(false);
nathan@0
   191
  d(printf("ogg: info ch=%d srate=%ld brate_low=%ld brate_high=%ld brate_avg=%ld\n",
nathan@0
   192
            vi->channels,vi->rate,vi->bitrate_lower,vi->bitrate_upper,vi->bitrate_nominal))
nathan@0
   193
  Channels=vi->channels;
nathan@0
   194
  ChMode=Channels>1 ? 3:0;
nathan@0
   195
  SampleFreq=vi->rate;
nathan@0
   196
  if(vi->bitrate_upper>0 && vi->bitrate_lower>0) {
nathan@0
   197
    Bitrate=vi->bitrate_lower;
nathan@0
   198
    MaxBitrate=vi->bitrate_upper;
nathan@0
   199
    }
nathan@0
   200
  else
nathan@0
   201
    Bitrate=vi->bitrate_nominal;
nathan@0
   202
nathan@0
   203
  Total=(int)ov_time_total(&file->vf,-1);
nathan@0
   204
  Frames=-1;
nathan@0
   205
  DecoderID=DEC_OGG;
nathan@0
   206
nathan@0
   207
  InfoDone();
nathan@0
   208
  InfoCache.Cache(this,file);
nathan@25
   209
  ConvertToSys();
nathan@25
   210
  return Abort(true);
nathan@0
   211
}
nathan@0
   212
nathan@0
   213
// --- cOggDecoder -------------------------------------------------------------
nathan@0
   214
nathan@0
   215
cOggDecoder::cOggDecoder(const char *Filename)
nathan@0
   216
:cDecoder(Filename)
nathan@0
   217
,file(Filename)
nathan@0
   218
,info(&file)
nathan@0
   219
{
nathan@0
   220
  pcm=0;
nathan@0
   221
}
nathan@0
   222
nathan@0
   223
cOggDecoder::~cOggDecoder()
nathan@0
   224
{
nathan@0
   225
  Clean();
nathan@0
   226
}
nathan@0
   227
nathan@0
   228
bool cOggDecoder::Valid(void)
nathan@0
   229
{
nathan@0
   230
  bool res=false;
nathan@0
   231
  if(TryLock()) {
nathan@0
   232
    if(file.Open(false)) res=true;
nathan@0
   233
    Unlock();
nathan@0
   234
    }
nathan@0
   235
  return res;
nathan@0
   236
}
nathan@0
   237
nathan@0
   238
cFileInfo *cOggDecoder::FileInfo(void)
nathan@0
   239
{
nathan@0
   240
  cFileInfo *fi=0;
nathan@0
   241
  if(file.HasInfo()) fi=&file;
nathan@0
   242
  else if(TryLock()){
nathan@0
   243
    if(file.Open()) { fi=&file; file.Close(); }
nathan@0
   244
    Unlock();
nathan@0
   245
    }
nathan@0
   246
  return fi;
nathan@0
   247
}
nathan@0
   248
nathan@0
   249
cSongInfo *cOggDecoder::SongInfo(bool get)
nathan@0
   250
{
nathan@0
   251
  cSongInfo *si=0;
nathan@0
   252
  if(info.HasInfo()) si=&info;
nathan@0
   253
  else if(get && TryLock()) {
nathan@0
   254
    if(info.DoScan(false)) si=&info;
nathan@0
   255
    Unlock();
nathan@0
   256
    }
nathan@0
   257
  return si;
nathan@0
   258
}
nathan@0
   259
nathan@0
   260
cPlayInfo *cOggDecoder::PlayInfo(void)
nathan@0
   261
{
nathan@0
   262
  if(playing) {
nathan@0
   263
    pi.Index=index/1000;
nathan@0
   264
    pi.Total=info.Total;
nathan@0
   265
    return &pi;
nathan@0
   266
    }
nathan@0
   267
  return 0;
nathan@0
   268
}
nathan@0
   269
nathan@0
   270
void cOggDecoder::Init(void)
nathan@0
   271
{
nathan@0
   272
  Clean();
nathan@0
   273
  pcm=new struct mad_pcm;
nathan@0
   274
  index=0;
nathan@0
   275
}
nathan@0
   276
nathan@0
   277
bool cOggDecoder::Clean(void)
nathan@0
   278
{
nathan@0
   279
  playing=false;
nathan@0
   280
  delete pcm; pcm=0;
nathan@0
   281
  file.Close();
nathan@0
   282
  return false;
nathan@0
   283
}
nathan@0
   284
nathan@0
   285
#define SF_SAMPLES (sizeof(pcm->samples[0])/sizeof(mad_fixed_t))
nathan@0
   286
nathan@0
   287
bool cOggDecoder::Start(void)
nathan@0
   288
{
nathan@0
   289
  Lock(true);
nathan@0
   290
  Init(); playing=true;
nathan@0
   291
  if(file.Open() && info.DoScan(true)) {
nathan@0
   292
    d(printf("ogg: open rate=%d channels=%d seek=%d\n",
nathan@0
   293
             info.SampleFreq,info.Channels,file.CanSeek()))
nathan@0
   294
    if(info.Channels<=2) {
nathan@0
   295
      Unlock();
nathan@0
   296
      return true;
nathan@0
   297
      }
nathan@0
   298
    else esyslog("ERROR: cannot play ogg file %s: more than 2 channels",filename);
nathan@0
   299
    }
nathan@0
   300
  Clean();
nathan@0
   301
  Unlock();
nathan@0
   302
  return false;
nathan@0
   303
}
nathan@0
   304
nathan@0
   305
bool cOggDecoder::Stop(void)
nathan@0
   306
{
nathan@0
   307
  Lock();
nathan@0
   308
  if(playing) Clean();
nathan@0
   309
  Unlock();
nathan@0
   310
  return true;
nathan@0
   311
}
nathan@0
   312
nathan@0
   313
struct Decode *cOggDecoder::Done(eDecodeStatus status)
nathan@0
   314
{
nathan@0
   315
  ds.status=status;
nathan@0
   316
  ds.index=index;
nathan@0
   317
  ds.pcm=pcm;
nathan@0
   318
  Unlock(); // release the lock from Decode()
nathan@0
   319
  return &ds;
nathan@0
   320
}
nathan@0
   321
nathan@0
   322
struct Decode *cOggDecoder::Decode(void)
nathan@0
   323
{
nathan@0
   324
  Lock(); // this is released in Done()
nathan@0
   325
  if(playing) {
nathan@0
   326
    short framebuff[2*SF_SAMPLES];
nathan@0
   327
    int n=file.Stream(framebuff,SF_SAMPLES);
nathan@0
   328
    if(n<0) return Done(dsError);
nathan@0
   329
    if(n==0) return Done(dsEof);
nathan@0
   330
nathan@0
   331
    pcm->samplerate=info.SampleFreq;
nathan@0
   332
    pcm->channels=info.Channels;
nathan@0
   333
    n/=pcm->channels;
nathan@0
   334
    pcm->length=n;
nathan@0
   335
    index=file.IndexMs();
nathan@0
   336
nathan@0
   337
    short *data=framebuff;
nathan@0
   338
    mad_fixed_t *sam0=pcm->samples[0], *sam1=pcm->samples[1]; 
nathan@0
   339
    const int s=MAD_F_FRACBITS+1-(sizeof(short)*8); // shift value for mad_fixed conversion
nathan@0
   340
    if(pcm->channels>1) {
nathan@0
   341
      for(; n>0 ; n--) {
nathan@0
   342
        *sam0++=(*data++) << s;
nathan@0
   343
        *sam1++=(*data++) << s;
nathan@0
   344
        }
nathan@0
   345
      }
nathan@0
   346
    else {
nathan@0
   347
      for(; n>0 ; n--)
nathan@0
   348
        *sam0++=(*data++) << s;
nathan@0
   349
      }
nathan@0
   350
    return Done(dsPlay);
nathan@0
   351
    }
nathan@0
   352
  return Done(dsError);
nathan@0
   353
}
nathan@0
   354
nathan@0
   355
bool cOggDecoder::Skip(int Seconds, float bsecs)
nathan@0
   356
{
nathan@0
   357
  Lock();
nathan@0
   358
  bool res=false;
nathan@0
   359
  if(playing && file.CanSeek()) {
nathan@0
   360
    float fsecs=(float)Seconds - bsecs;
nathan@0
   361
    long long newpos=file.IndexMs()+(long long)(fsecs*1000.0);
nathan@0
   362
    if(newpos<0) newpos=0;
nathan@0
   363
    d(printf("ogg: skip: secs=%d fsecs=%f current=%lld new=%lld\n",Seconds,fsecs,file.IndexMs(),newpos))
nathan@0
   364
nathan@0
   365
    newpos=file.Seek(newpos,false);
nathan@0
   366
    if(newpos>=0) {
nathan@0
   367
      index=file.IndexMs();
nathan@0
   368
#ifdef DEBUG
nathan@0
   369
      int i=index/1000;
nathan@0
   370
      printf("ogg: skipping to %02d:%02d\n",i/60,i%60);
nathan@0
   371
#endif
nathan@0
   372
      res=true;
nathan@0
   373
      }
nathan@0
   374
    }
nathan@0
   375
  Unlock();
nathan@0
   376
  return res;
nathan@0
   377
}
nathan@0
   378
nathan@0
   379
#endif //HAVE_VORBISFILE