decoder-ogg.c
author nathan
Fri, 13 Nov 2009 19:27:36 +0800
branchtrunk
changeset 33 65ed49cbc08b
parent 25 887faebaba0a
child 34 afc13760179b
permissions -rw-r--r--
add OGG streaming support
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@33
   215
cOggDecoder::cOggDecoder(const char *Filename, bool preinit)
nathan@0
   216
:cDecoder(Filename)
nathan@33
   217
{
nathan@33
   218
  file=0; info=0; pcm=0;
nathan@33
   219
  if(preinit) {
nathan@33
   220
    file=new cOggFile(Filename);
nathan@33
   221
    info=new cOggInfo(file);
nathan@33
   222
  }
nathan@0
   223
}
nathan@0
   224
nathan@0
   225
cOggDecoder::~cOggDecoder()
nathan@0
   226
{
nathan@0
   227
  Clean();
nathan@33
   228
  delete info;
nathan@33
   229
  delete file;
nathan@0
   230
}
nathan@0
   231
nathan@0
   232
bool cOggDecoder::Valid(void)
nathan@0
   233
{
nathan@0
   234
  bool res=false;
nathan@0
   235
  if(TryLock()) {
nathan@33
   236
    if(file->Open(false)) res=true;
nathan@0
   237
    Unlock();
nathan@0
   238
    }
nathan@0
   239
  return res;
nathan@0
   240
}
nathan@0
   241
nathan@0
   242
cFileInfo *cOggDecoder::FileInfo(void)
nathan@0
   243
{
nathan@0
   244
  cFileInfo *fi=0;
nathan@33
   245
  if(file->HasInfo()) fi=file;
nathan@0
   246
  else if(TryLock()){
nathan@33
   247
    if(file->Open()) { fi=file; file->Close(); }
nathan@0
   248
    Unlock();
nathan@0
   249
    }
nathan@0
   250
  return fi;
nathan@0
   251
}
nathan@0
   252
nathan@0
   253
cSongInfo *cOggDecoder::SongInfo(bool get)
nathan@0
   254
{
nathan@0
   255
  cSongInfo *si=0;
nathan@33
   256
  if(info->HasInfo()) si=info;
nathan@0
   257
  else if(get && TryLock()) {
nathan@33
   258
    if(info->DoScan(false)) si=info;
nathan@0
   259
    Unlock();
nathan@0
   260
    }
nathan@0
   261
  return si;
nathan@0
   262
}
nathan@0
   263
nathan@0
   264
cPlayInfo *cOggDecoder::PlayInfo(void)
nathan@0
   265
{
nathan@0
   266
  if(playing) {
nathan@0
   267
    pi.Index=index/1000;
nathan@33
   268
    pi.Total=info->Total;
nathan@0
   269
    return &pi;
nathan@0
   270
    }
nathan@0
   271
  return 0;
nathan@0
   272
}
nathan@0
   273
nathan@0
   274
void cOggDecoder::Init(void)
nathan@0
   275
{
nathan@0
   276
  Clean();
nathan@0
   277
  pcm=new struct mad_pcm;
nathan@0
   278
  index=0;
nathan@0
   279
}
nathan@0
   280
nathan@0
   281
bool cOggDecoder::Clean(void)
nathan@0
   282
{
nathan@0
   283
  playing=false;
nathan@0
   284
  delete pcm; pcm=0;
nathan@33
   285
  file->Close();
nathan@0
   286
  return false;
nathan@0
   287
}
nathan@0
   288
nathan@0
   289
#define SF_SAMPLES (sizeof(pcm->samples[0])/sizeof(mad_fixed_t))
nathan@0
   290
nathan@0
   291
bool cOggDecoder::Start(void)
nathan@0
   292
{
nathan@0
   293
  Lock(true);
nathan@0
   294
  Init(); playing=true;
nathan@33
   295
  if(file->Open() && info->DoScan(true)) {
nathan@0
   296
    d(printf("ogg: open rate=%d channels=%d seek=%d\n",
nathan@33
   297
             info->SampleFreq,info->Channels,file->CanSeek()))
nathan@33
   298
    if(info->Channels<=2) {
nathan@0
   299
      Unlock();
nathan@0
   300
      return true;
nathan@0
   301
      }
nathan@0
   302
    else esyslog("ERROR: cannot play ogg file %s: more than 2 channels",filename);
nathan@0
   303
    }
nathan@0
   304
  Clean();
nathan@0
   305
  Unlock();
nathan@0
   306
  return false;
nathan@0
   307
}
nathan@0
   308
nathan@0
   309
bool cOggDecoder::Stop(void)
nathan@0
   310
{
nathan@0
   311
  Lock();
nathan@0
   312
  if(playing) Clean();
nathan@0
   313
  Unlock();
nathan@0
   314
  return true;
nathan@0
   315
}
nathan@0
   316
nathan@0
   317
struct Decode *cOggDecoder::Done(eDecodeStatus status)
nathan@0
   318
{
nathan@0
   319
  ds.status=status;
nathan@0
   320
  ds.index=index;
nathan@0
   321
  ds.pcm=pcm;
nathan@0
   322
  Unlock(); // release the lock from Decode()
nathan@0
   323
  return &ds;
nathan@0
   324
}
nathan@0
   325
nathan@0
   326
struct Decode *cOggDecoder::Decode(void)
nathan@0
   327
{
nathan@0
   328
  Lock(); // this is released in Done()
nathan@0
   329
  if(playing) {
nathan@0
   330
    short framebuff[2*SF_SAMPLES];
nathan@33
   331
    int n=file->Stream(framebuff,SF_SAMPLES);
nathan@0
   332
    if(n<0) return Done(dsError);
nathan@0
   333
    if(n==0) return Done(dsEof);
nathan@0
   334
nathan@33
   335
    pcm->samplerate=info->SampleFreq;
nathan@33
   336
    pcm->channels=info->Channels;
nathan@0
   337
    n/=pcm->channels;
nathan@0
   338
    pcm->length=n;
nathan@33
   339
    index=file->IndexMs();
nathan@0
   340
nathan@0
   341
    short *data=framebuff;
nathan@0
   342
    mad_fixed_t *sam0=pcm->samples[0], *sam1=pcm->samples[1]; 
nathan@0
   343
    const int s=MAD_F_FRACBITS+1-(sizeof(short)*8); // shift value for mad_fixed conversion
nathan@0
   344
    if(pcm->channels>1) {
nathan@0
   345
      for(; n>0 ; n--) {
nathan@0
   346
        *sam0++=(*data++) << s;
nathan@0
   347
        *sam1++=(*data++) << s;
nathan@0
   348
        }
nathan@0
   349
      }
nathan@0
   350
    else {
nathan@0
   351
      for(; n>0 ; n--)
nathan@0
   352
        *sam0++=(*data++) << s;
nathan@0
   353
      }
nathan@33
   354
    info->InfoHook();
nathan@0
   355
    return Done(dsPlay);
nathan@0
   356
    }
nathan@0
   357
  return Done(dsError);
nathan@0
   358
}
nathan@0
   359
nathan@0
   360
bool cOggDecoder::Skip(int Seconds, float bsecs)
nathan@0
   361
{
nathan@0
   362
  Lock();
nathan@0
   363
  bool res=false;
nathan@33
   364
  if(playing && file->CanSeek()) {
nathan@0
   365
    float fsecs=(float)Seconds - bsecs;
nathan@33
   366
    long long newpos=file->IndexMs()+(long long)(fsecs*1000.0);
nathan@0
   367
    if(newpos<0) newpos=0;
nathan@33
   368
    d(printf("ogg: skip: secs=%d fsecs=%f current=%lld new=%lld\n",Seconds,fsecs,file->IndexMs(),newpos))
nathan@33
   369
nathan@33
   370
    newpos=file->Seek(newpos,false);
nathan@0
   371
    if(newpos>=0) {
nathan@33
   372
      index=file->IndexMs();
nathan@0
   373
#ifdef DEBUG
nathan@0
   374
      int i=index/1000;
nathan@0
   375
      printf("ogg: skipping to %02d:%02d\n",i/60,i%60);
nathan@0
   376
#endif
nathan@0
   377
      res=true;
nathan@0
   378
      }
nathan@0
   379
    }
nathan@0
   380
  Unlock();
nathan@0
   381
  return res;
nathan@0
   382
}
nathan@0
   383
nathan@0
   384
#endif //HAVE_VORBISFILE