decoder-ogg.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 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@0
    76
  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@0
   150
    if(!DecoderID) {
nathan@0
   151
      DecoderID=DEC_OGG;
nathan@0
   152
      InfoCache.Cache(this,file);
nathan@0
   153
      }
nathan@0
   154
    return Abort(true);
nathan@0
   155
    }
nathan@0
   156
nathan@0
   157
  Clear();
nathan@0
   158
nathan@0
   159
  vorbis_comment *vc=ov_comment(&file->vf,-1);
nathan@0
   160
  if(vc) {
nathan@0
   161
    for(int i=0 ; i<vc->comments ; i++) {
nathan@0
   162
      const char *cc=vc->user_comments[i];
nathan@0
   163
      d(printf("ogg: comment%d='%s'\n",i,cc))
nathan@0
   164
      char *p=strchr(cc,'=');
nathan@0
   165
      if(p) {
nathan@0
   166
        const int len=p-cc;
nathan@0
   167
        p++;
nathan@0
   168
        if(!strncasecmp(cc,"TITLE",len)) {
nathan@0
   169
          if(!Title) Title=strdup(p);
nathan@0
   170
          }
nathan@0
   171
        else if(!strncasecmp(cc,"ARTIST",len)) {
nathan@0
   172
          if(!Artist) Artist=strdup(p);
nathan@0
   173
          }
nathan@0
   174
        else if(!strncasecmp(cc,"ALBUM",len)) {
nathan@0
   175
          if(!Album) Album=strdup(p);
nathan@0
   176
          }
nathan@0
   177
        else if(!strncasecmp(cc,"YEAR",len)) {
nathan@0
   178
          if(Year<0) {
nathan@0
   179
            Year=atoi(p);
nathan@0
   180
            if(Year<1800 || Year>2100) Year=-1;
nathan@0
   181
            }
nathan@0
   182
          }
nathan@0
   183
        }
nathan@0
   184
      }
nathan@0
   185
    }
nathan@0
   186
  if(!Title) FakeTitle(file->Filename);
nathan@0
   187
nathan@0
   188
  vorbis_info *vi=ov_info(&file->vf,-1);
nathan@0
   189
  if(!vi) Abort(false);
nathan@0
   190
  d(printf("ogg: info ch=%d srate=%ld brate_low=%ld brate_high=%ld brate_avg=%ld\n",
nathan@0
   191
            vi->channels,vi->rate,vi->bitrate_lower,vi->bitrate_upper,vi->bitrate_nominal))
nathan@0
   192
  Channels=vi->channels;
nathan@0
   193
  ChMode=Channels>1 ? 3:0;
nathan@0
   194
  SampleFreq=vi->rate;
nathan@0
   195
  if(vi->bitrate_upper>0 && vi->bitrate_lower>0) {
nathan@0
   196
    Bitrate=vi->bitrate_lower;
nathan@0
   197
    MaxBitrate=vi->bitrate_upper;
nathan@0
   198
    }
nathan@0
   199
  else
nathan@0
   200
    Bitrate=vi->bitrate_nominal;
nathan@0
   201
nathan@0
   202
  Total=(int)ov_time_total(&file->vf,-1);
nathan@0
   203
  Frames=-1;
nathan@0
   204
  DecoderID=DEC_OGG;
nathan@0
   205
nathan@0
   206
  InfoDone();
nathan@0
   207
  InfoCache.Cache(this,file);
nathan@0
   208
  return Abort(true);  
nathan@0
   209
}
nathan@0
   210
nathan@0
   211
// --- cOggDecoder -------------------------------------------------------------
nathan@0
   212
nathan@0
   213
cOggDecoder::cOggDecoder(const char *Filename)
nathan@0
   214
:cDecoder(Filename)
nathan@0
   215
,file(Filename)
nathan@0
   216
,info(&file)
nathan@0
   217
{
nathan@0
   218
  pcm=0;
nathan@0
   219
}
nathan@0
   220
nathan@0
   221
cOggDecoder::~cOggDecoder()
nathan@0
   222
{
nathan@0
   223
  Clean();
nathan@0
   224
}
nathan@0
   225
nathan@0
   226
bool cOggDecoder::Valid(void)
nathan@0
   227
{
nathan@0
   228
  bool res=false;
nathan@0
   229
  if(TryLock()) {
nathan@0
   230
    if(file.Open(false)) res=true;
nathan@0
   231
    Unlock();
nathan@0
   232
    }
nathan@0
   233
  return res;
nathan@0
   234
}
nathan@0
   235
nathan@0
   236
cFileInfo *cOggDecoder::FileInfo(void)
nathan@0
   237
{
nathan@0
   238
  cFileInfo *fi=0;
nathan@0
   239
  if(file.HasInfo()) fi=&file;
nathan@0
   240
  else if(TryLock()){
nathan@0
   241
    if(file.Open()) { fi=&file; file.Close(); }
nathan@0
   242
    Unlock();
nathan@0
   243
    }
nathan@0
   244
  return fi;
nathan@0
   245
}
nathan@0
   246
nathan@0
   247
cSongInfo *cOggDecoder::SongInfo(bool get)
nathan@0
   248
{
nathan@0
   249
  cSongInfo *si=0;
nathan@0
   250
  if(info.HasInfo()) si=&info;
nathan@0
   251
  else if(get && TryLock()) {
nathan@0
   252
    if(info.DoScan(false)) si=&info;
nathan@0
   253
    Unlock();
nathan@0
   254
    }
nathan@0
   255
  return si;
nathan@0
   256
}
nathan@0
   257
nathan@0
   258
cPlayInfo *cOggDecoder::PlayInfo(void)
nathan@0
   259
{
nathan@0
   260
  if(playing) {
nathan@0
   261
    pi.Index=index/1000;
nathan@0
   262
    pi.Total=info.Total;
nathan@0
   263
    return &pi;
nathan@0
   264
    }
nathan@0
   265
  return 0;
nathan@0
   266
}
nathan@0
   267
nathan@0
   268
void cOggDecoder::Init(void)
nathan@0
   269
{
nathan@0
   270
  Clean();
nathan@0
   271
  pcm=new struct mad_pcm;
nathan@0
   272
  index=0;
nathan@0
   273
}
nathan@0
   274
nathan@0
   275
bool cOggDecoder::Clean(void)
nathan@0
   276
{
nathan@0
   277
  playing=false;
nathan@0
   278
  delete pcm; pcm=0;
nathan@0
   279
  file.Close();
nathan@0
   280
  return false;
nathan@0
   281
}
nathan@0
   282
nathan@0
   283
#define SF_SAMPLES (sizeof(pcm->samples[0])/sizeof(mad_fixed_t))
nathan@0
   284
nathan@0
   285
bool cOggDecoder::Start(void)
nathan@0
   286
{
nathan@0
   287
  Lock(true);
nathan@0
   288
  Init(); playing=true;
nathan@0
   289
  if(file.Open() && info.DoScan(true)) {
nathan@0
   290
    d(printf("ogg: open rate=%d channels=%d seek=%d\n",
nathan@0
   291
             info.SampleFreq,info.Channels,file.CanSeek()))
nathan@0
   292
    if(info.Channels<=2) {
nathan@0
   293
      Unlock();
nathan@0
   294
      return true;
nathan@0
   295
      }
nathan@0
   296
    else esyslog("ERROR: cannot play ogg file %s: more than 2 channels",filename);
nathan@0
   297
    }
nathan@0
   298
  Clean();
nathan@0
   299
  Unlock();
nathan@0
   300
  return false;
nathan@0
   301
}
nathan@0
   302
nathan@0
   303
bool cOggDecoder::Stop(void)
nathan@0
   304
{
nathan@0
   305
  Lock();
nathan@0
   306
  if(playing) Clean();
nathan@0
   307
  Unlock();
nathan@0
   308
  return true;
nathan@0
   309
}
nathan@0
   310
nathan@0
   311
struct Decode *cOggDecoder::Done(eDecodeStatus status)
nathan@0
   312
{
nathan@0
   313
  ds.status=status;
nathan@0
   314
  ds.index=index;
nathan@0
   315
  ds.pcm=pcm;
nathan@0
   316
  Unlock(); // release the lock from Decode()
nathan@0
   317
  return &ds;
nathan@0
   318
}
nathan@0
   319
nathan@0
   320
struct Decode *cOggDecoder::Decode(void)
nathan@0
   321
{
nathan@0
   322
  Lock(); // this is released in Done()
nathan@0
   323
  if(playing) {
nathan@0
   324
    short framebuff[2*SF_SAMPLES];
nathan@0
   325
    int n=file.Stream(framebuff,SF_SAMPLES);
nathan@0
   326
    if(n<0) return Done(dsError);
nathan@0
   327
    if(n==0) return Done(dsEof);
nathan@0
   328
nathan@0
   329
    pcm->samplerate=info.SampleFreq;
nathan@0
   330
    pcm->channels=info.Channels;
nathan@0
   331
    n/=pcm->channels;
nathan@0
   332
    pcm->length=n;
nathan@0
   333
    index=file.IndexMs();
nathan@0
   334
nathan@0
   335
    short *data=framebuff;
nathan@0
   336
    mad_fixed_t *sam0=pcm->samples[0], *sam1=pcm->samples[1]; 
nathan@0
   337
    const int s=MAD_F_FRACBITS+1-(sizeof(short)*8); // shift value for mad_fixed conversion
nathan@0
   338
    if(pcm->channels>1) {
nathan@0
   339
      for(; n>0 ; n--) {
nathan@0
   340
        *sam0++=(*data++) << s;
nathan@0
   341
        *sam1++=(*data++) << s;
nathan@0
   342
        }
nathan@0
   343
      }
nathan@0
   344
    else {
nathan@0
   345
      for(; n>0 ; n--)
nathan@0
   346
        *sam0++=(*data++) << s;
nathan@0
   347
      }
nathan@0
   348
    return Done(dsPlay);
nathan@0
   349
    }
nathan@0
   350
  return Done(dsError);
nathan@0
   351
}
nathan@0
   352
nathan@0
   353
bool cOggDecoder::Skip(int Seconds, float bsecs)
nathan@0
   354
{
nathan@0
   355
  Lock();
nathan@0
   356
  bool res=false;
nathan@0
   357
  if(playing && file.CanSeek()) {
nathan@0
   358
    float fsecs=(float)Seconds - bsecs;
nathan@0
   359
    long long newpos=file.IndexMs()+(long long)(fsecs*1000.0);
nathan@0
   360
    if(newpos<0) newpos=0;
nathan@0
   361
    d(printf("ogg: skip: secs=%d fsecs=%f current=%lld new=%lld\n",Seconds,fsecs,file.IndexMs(),newpos))
nathan@0
   362
nathan@0
   363
    newpos=file.Seek(newpos,false);
nathan@0
   364
    if(newpos>=0) {
nathan@0
   365
      index=file.IndexMs();
nathan@0
   366
#ifdef DEBUG
nathan@0
   367
      int i=index/1000;
nathan@0
   368
      printf("ogg: skipping to %02d:%02d\n",i/60,i%60);
nathan@0
   369
#endif
nathan@0
   370
      res=true;
nathan@0
   371
      }
nathan@0
   372
    }
nathan@0
   373
  Unlock();
nathan@0
   374
  return res;
nathan@0
   375
}
nathan@0
   376
nathan@0
   377
#endif //HAVE_VORBISFILE