decoder-ogg.c
branchtrunk
changeset 0 474a1293c3c0
child 6 111ef8181229
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/decoder-ogg.c	Sat Dec 29 14:47:40 2007 +0100
     1.3 @@ -0,0 +1,377 @@
     1.4 +/*
     1.5 + * MP3/MPlayer plugin to VDR (C++)
     1.6 + *
     1.7 + * (C) 2001-2005 Stefan Huelswitt <s.huelswitt@gmx.de>
     1.8 + *
     1.9 + * This code is free software; you can redistribute it and/or
    1.10 + * modify it under the terms of the GNU General Public License
    1.11 + * as published by the Free Software Foundation; either version 2
    1.12 + * of the License, or (at your option) any later version.
    1.13 + *
    1.14 + * This code is distributed in the hope that it will be useful,
    1.15 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    1.16 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    1.17 + * GNU General Public License for more details.
    1.18 + *
    1.19 + * You should have received a copy of the GNU General Public License
    1.20 + * along with this program; if not, write to the Free Software
    1.21 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
    1.22 + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
    1.23 + */
    1.24 +
    1.25 +#ifdef HAVE_VORBISFILE
    1.26 +
    1.27 +#include <stdlib.h>
    1.28 +#include <stdio.h>
    1.29 +#include <errno.h>
    1.30 +
    1.31 +#include "common.h"
    1.32 +#include "decoder-ogg.h"
    1.33 +
    1.34 +// --- cOggFile ----------------------------------------------------------------
    1.35 +
    1.36 +cOggFile::cOggFile(const char *Filename)
    1.37 +:cFileInfo(Filename)
    1.38 +{
    1.39 +  canSeek=opened=false;
    1.40 +}
    1.41 +
    1.42 +cOggFile::~cOggFile()
    1.43 +{
    1.44 +  Close();
    1.45 +}
    1.46 +
    1.47 +bool cOggFile::Open(bool log)
    1.48 +{
    1.49 +  if(opened) {
    1.50 +    if(canSeek) return (Seek()>=0);
    1.51 +    return true;
    1.52 +    }
    1.53 +
    1.54 +  if(FileInfo(log)) {
    1.55 +    FILE *f=fopen(Filename,"r");
    1.56 +    if(f) {
    1.57 +      int r=ov_open(f,&vf,0,0);
    1.58 +      if(!r) {
    1.59 +        canSeek=(ov_seekable(&vf)!=0);
    1.60 +        opened=true;
    1.61 +        }
    1.62 +      else {
    1.63 +        fclose(f);
    1.64 +        if(log) Error("open",r);
    1.65 +        }
    1.66 +      }
    1.67 +    else if(log) { esyslog("ERROR: failed to open file %s: %s",Filename,strerror(errno)); }
    1.68 +    }
    1.69 +  return opened;
    1.70 +}
    1.71 +
    1.72 +void cOggFile::Close(void)
    1.73 +{
    1.74 +  if(opened) { ov_clear(&vf); opened=false; }
    1.75 +}
    1.76 +
    1.77 +void cOggFile::Error(const char *action, const int err)
    1.78 +{
    1.79 +  char *errstr;
    1.80 +  switch(err) {
    1.81 +    case OV_FALSE:      errstr="false/no data available"; break;
    1.82 +    case OV_EOF:        errstr="EOF"; break;
    1.83 +    case OV_HOLE:       errstr="missing or corrupted data"; break;
    1.84 +    case OV_EREAD:      errstr="read error"; break;
    1.85 +    case OV_EFAULT:     errstr="internal error"; break;
    1.86 +    case OV_EIMPL:      errstr="unimplemented feature"; break;
    1.87 +    case OV_EINVAL:     errstr="invalid argument"; break;
    1.88 +    case OV_ENOTVORBIS: errstr="no Ogg Vorbis stream"; break;
    1.89 +    case OV_EBADHEADER: errstr="corrupted Ogg Vorbis stream"; break;
    1.90 +    case OV_EVERSION:   errstr="unsupported bitstream version"; break;
    1.91 +    case OV_ENOTAUDIO:  errstr="ENOTAUDIO"; break;
    1.92 +    case OV_EBADPACKET: errstr="EBADPACKET"; break;
    1.93 +    case OV_EBADLINK:   errstr="corrupted link"; break;
    1.94 +    case OV_ENOSEEK:    errstr="stream not seekable"; break;
    1.95 +    default:            errstr="unspecified error"; break;
    1.96 +    }
    1.97 +  esyslog("ERROR: vorbisfile %s failed on %s: %s",action,Filename,errstr);
    1.98 +}
    1.99 +
   1.100 +long long cOggFile::IndexMs(void)
   1.101 +{
   1.102 +  double p=ov_time_tell(&vf);
   1.103 +  if(p<0.0) p=0.0;
   1.104 +  return (long long)(p*1000.0);
   1.105 +}
   1.106 +
   1.107 +long long cOggFile::Seek(long long posMs, bool relativ)
   1.108 +{
   1.109 +  if(relativ) posMs+=IndexMs();
   1.110 +  int r=ov_time_seek(&vf,(double)posMs/1000.0);
   1.111 +  if(r) {
   1.112 +    Error("seek",r);
   1.113 +    return -1;
   1.114 +    }
   1.115 +  posMs=IndexMs();
   1.116 +  return posMs;
   1.117 +}
   1.118 +
   1.119 +int cOggFile::Stream(short *buffer, int samples)
   1.120 +{
   1.121 +  int n;
   1.122 +  do {
   1.123 +    int stream;
   1.124 +    n=ov_read(&vf,(char *)buffer,samples*2,0,2,1,&stream);
   1.125 +    } while(n==OV_HOLE);
   1.126 +  if(n<0) Error("read",n);
   1.127 +  return (n/2);
   1.128 +}
   1.129 +
   1.130 +// --- cOggInfo ----------------------------------------------------------------
   1.131 +
   1.132 +cOggInfo::cOggInfo(cOggFile *File)
   1.133 +{
   1.134 +  file=File;
   1.135 +}
   1.136 +
   1.137 +bool cOggInfo::Abort(bool result)
   1.138 +{
   1.139 +  if(!keepOpen) file->Close();
   1.140 +  return result;
   1.141 +}
   1.142 +
   1.143 +bool cOggInfo::DoScan(bool KeepOpen)
   1.144 +{
   1.145 +  keepOpen=KeepOpen;
   1.146 +  if(!file->Open()) return Abort(false);
   1.147 +  if(HasInfo()) return Abort(true);
   1.148 +
   1.149 +  // check the infocache
   1.150 +  cCacheData *dat=InfoCache.Search(file);
   1.151 +  if(dat) {
   1.152 +    Set(dat); dat->Unlock();
   1.153 +    if(!DecoderID) {
   1.154 +      DecoderID=DEC_OGG;
   1.155 +      InfoCache.Cache(this,file);
   1.156 +      }
   1.157 +    return Abort(true);
   1.158 +    }
   1.159 +
   1.160 +  Clear();
   1.161 +
   1.162 +  vorbis_comment *vc=ov_comment(&file->vf,-1);
   1.163 +  if(vc) {
   1.164 +    for(int i=0 ; i<vc->comments ; i++) {
   1.165 +      const char *cc=vc->user_comments[i];
   1.166 +      d(printf("ogg: comment%d='%s'\n",i,cc))
   1.167 +      char *p=strchr(cc,'=');
   1.168 +      if(p) {
   1.169 +        const int len=p-cc;
   1.170 +        p++;
   1.171 +        if(!strncasecmp(cc,"TITLE",len)) {
   1.172 +          if(!Title) Title=strdup(p);
   1.173 +          }
   1.174 +        else if(!strncasecmp(cc,"ARTIST",len)) {
   1.175 +          if(!Artist) Artist=strdup(p);
   1.176 +          }
   1.177 +        else if(!strncasecmp(cc,"ALBUM",len)) {
   1.178 +          if(!Album) Album=strdup(p);
   1.179 +          }
   1.180 +        else if(!strncasecmp(cc,"YEAR",len)) {
   1.181 +          if(Year<0) {
   1.182 +            Year=atoi(p);
   1.183 +            if(Year<1800 || Year>2100) Year=-1;
   1.184 +            }
   1.185 +          }
   1.186 +        }
   1.187 +      }
   1.188 +    }
   1.189 +  if(!Title) FakeTitle(file->Filename);
   1.190 +
   1.191 +  vorbis_info *vi=ov_info(&file->vf,-1);
   1.192 +  if(!vi) Abort(false);
   1.193 +  d(printf("ogg: info ch=%d srate=%ld brate_low=%ld brate_high=%ld brate_avg=%ld\n",
   1.194 +            vi->channels,vi->rate,vi->bitrate_lower,vi->bitrate_upper,vi->bitrate_nominal))
   1.195 +  Channels=vi->channels;
   1.196 +  ChMode=Channels>1 ? 3:0;
   1.197 +  SampleFreq=vi->rate;
   1.198 +  if(vi->bitrate_upper>0 && vi->bitrate_lower>0) {
   1.199 +    Bitrate=vi->bitrate_lower;
   1.200 +    MaxBitrate=vi->bitrate_upper;
   1.201 +    }
   1.202 +  else
   1.203 +    Bitrate=vi->bitrate_nominal;
   1.204 +
   1.205 +  Total=(int)ov_time_total(&file->vf,-1);
   1.206 +  Frames=-1;
   1.207 +  DecoderID=DEC_OGG;
   1.208 +
   1.209 +  InfoDone();
   1.210 +  InfoCache.Cache(this,file);
   1.211 +  return Abort(true);  
   1.212 +}
   1.213 +
   1.214 +// --- cOggDecoder -------------------------------------------------------------
   1.215 +
   1.216 +cOggDecoder::cOggDecoder(const char *Filename)
   1.217 +:cDecoder(Filename)
   1.218 +,file(Filename)
   1.219 +,info(&file)
   1.220 +{
   1.221 +  pcm=0;
   1.222 +}
   1.223 +
   1.224 +cOggDecoder::~cOggDecoder()
   1.225 +{
   1.226 +  Clean();
   1.227 +}
   1.228 +
   1.229 +bool cOggDecoder::Valid(void)
   1.230 +{
   1.231 +  bool res=false;
   1.232 +  if(TryLock()) {
   1.233 +    if(file.Open(false)) res=true;
   1.234 +    Unlock();
   1.235 +    }
   1.236 +  return res;
   1.237 +}
   1.238 +
   1.239 +cFileInfo *cOggDecoder::FileInfo(void)
   1.240 +{
   1.241 +  cFileInfo *fi=0;
   1.242 +  if(file.HasInfo()) fi=&file;
   1.243 +  else if(TryLock()){
   1.244 +    if(file.Open()) { fi=&file; file.Close(); }
   1.245 +    Unlock();
   1.246 +    }
   1.247 +  return fi;
   1.248 +}
   1.249 +
   1.250 +cSongInfo *cOggDecoder::SongInfo(bool get)
   1.251 +{
   1.252 +  cSongInfo *si=0;
   1.253 +  if(info.HasInfo()) si=&info;
   1.254 +  else if(get && TryLock()) {
   1.255 +    if(info.DoScan(false)) si=&info;
   1.256 +    Unlock();
   1.257 +    }
   1.258 +  return si;
   1.259 +}
   1.260 +
   1.261 +cPlayInfo *cOggDecoder::PlayInfo(void)
   1.262 +{
   1.263 +  if(playing) {
   1.264 +    pi.Index=index/1000;
   1.265 +    pi.Total=info.Total;
   1.266 +    return &pi;
   1.267 +    }
   1.268 +  return 0;
   1.269 +}
   1.270 +
   1.271 +void cOggDecoder::Init(void)
   1.272 +{
   1.273 +  Clean();
   1.274 +  pcm=new struct mad_pcm;
   1.275 +  index=0;
   1.276 +}
   1.277 +
   1.278 +bool cOggDecoder::Clean(void)
   1.279 +{
   1.280 +  playing=false;
   1.281 +  delete pcm; pcm=0;
   1.282 +  file.Close();
   1.283 +  return false;
   1.284 +}
   1.285 +
   1.286 +#define SF_SAMPLES (sizeof(pcm->samples[0])/sizeof(mad_fixed_t))
   1.287 +
   1.288 +bool cOggDecoder::Start(void)
   1.289 +{
   1.290 +  Lock(true);
   1.291 +  Init(); playing=true;
   1.292 +  if(file.Open() && info.DoScan(true)) {
   1.293 +    d(printf("ogg: open rate=%d channels=%d seek=%d\n",
   1.294 +             info.SampleFreq,info.Channels,file.CanSeek()))
   1.295 +    if(info.Channels<=2) {
   1.296 +      Unlock();
   1.297 +      return true;
   1.298 +      }
   1.299 +    else esyslog("ERROR: cannot play ogg file %s: more than 2 channels",filename);
   1.300 +    }
   1.301 +  Clean();
   1.302 +  Unlock();
   1.303 +  return false;
   1.304 +}
   1.305 +
   1.306 +bool cOggDecoder::Stop(void)
   1.307 +{
   1.308 +  Lock();
   1.309 +  if(playing) Clean();
   1.310 +  Unlock();
   1.311 +  return true;
   1.312 +}
   1.313 +
   1.314 +struct Decode *cOggDecoder::Done(eDecodeStatus status)
   1.315 +{
   1.316 +  ds.status=status;
   1.317 +  ds.index=index;
   1.318 +  ds.pcm=pcm;
   1.319 +  Unlock(); // release the lock from Decode()
   1.320 +  return &ds;
   1.321 +}
   1.322 +
   1.323 +struct Decode *cOggDecoder::Decode(void)
   1.324 +{
   1.325 +  Lock(); // this is released in Done()
   1.326 +  if(playing) {
   1.327 +    short framebuff[2*SF_SAMPLES];
   1.328 +    int n=file.Stream(framebuff,SF_SAMPLES);
   1.329 +    if(n<0) return Done(dsError);
   1.330 +    if(n==0) return Done(dsEof);
   1.331 +
   1.332 +    pcm->samplerate=info.SampleFreq;
   1.333 +    pcm->channels=info.Channels;
   1.334 +    n/=pcm->channels;
   1.335 +    pcm->length=n;
   1.336 +    index=file.IndexMs();
   1.337 +
   1.338 +    short *data=framebuff;
   1.339 +    mad_fixed_t *sam0=pcm->samples[0], *sam1=pcm->samples[1]; 
   1.340 +    const int s=MAD_F_FRACBITS+1-(sizeof(short)*8); // shift value for mad_fixed conversion
   1.341 +    if(pcm->channels>1) {
   1.342 +      for(; n>0 ; n--) {
   1.343 +        *sam0++=(*data++) << s;
   1.344 +        *sam1++=(*data++) << s;
   1.345 +        }
   1.346 +      }
   1.347 +    else {
   1.348 +      for(; n>0 ; n--)
   1.349 +        *sam0++=(*data++) << s;
   1.350 +      }
   1.351 +    return Done(dsPlay);
   1.352 +    }
   1.353 +  return Done(dsError);
   1.354 +}
   1.355 +
   1.356 +bool cOggDecoder::Skip(int Seconds, float bsecs)
   1.357 +{
   1.358 +  Lock();
   1.359 +  bool res=false;
   1.360 +  if(playing && file.CanSeek()) {
   1.361 +    float fsecs=(float)Seconds - bsecs;
   1.362 +    long long newpos=file.IndexMs()+(long long)(fsecs*1000.0);
   1.363 +    if(newpos<0) newpos=0;
   1.364 +    d(printf("ogg: skip: secs=%d fsecs=%f current=%lld new=%lld\n",Seconds,fsecs,file.IndexMs(),newpos))
   1.365 +
   1.366 +    newpos=file.Seek(newpos,false);
   1.367 +    if(newpos>=0) {
   1.368 +      index=file.IndexMs();
   1.369 +#ifdef DEBUG
   1.370 +      int i=index/1000;
   1.371 +      printf("ogg: skipping to %02d:%02d\n",i/60,i%60);
   1.372 +#endif
   1.373 +      res=true;
   1.374 +      }
   1.375 +    }
   1.376 +  Unlock();
   1.377 +  return res;
   1.378 +}
   1.379 +
   1.380 +#endif //HAVE_VORBISFILE