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