stream.c
author nathan
Sun, 12 Dec 2010 12:15:17 +0100
branchtrunk
changeset 40 31537cd8ec5e
parent 34 afc13760179b
permissions -rw-r--r--
fixed image_convert.sh for newer mjpegtools
     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 #include <stdlib.h>
    23 #include <stdio.h>
    24 #include <unistd.h>
    25 #include <errno.h>
    26 
    27 #include "common.h"
    28 #include "setup-mp3.h"
    29 #include "stream.h"
    30 #include "network.h"
    31 #include "data.h"
    32 #include "menu-async.h"
    33 #include "i18n.h"
    34 #include "version.h"
    35 
    36 #define USE_MMAP
    37 #define MAX_MMAP_SIZE   (32*1024*1024)
    38 #define MP3FILE_BUFSIZE (32*1024)
    39 
    40 #ifdef USE_MMAP
    41 #include <sys/mman.h>
    42 #endif
    43 
    44 #define DEFAULT_PORT 80 // default port for streaming (HTTP)
    45 
    46 //#define DUMP_HEAD "/var/tmp/headers"
    47 
    48 // --- cIO ---------------------------------------------------------------------
    49 
    50 #if 0
    51 cIO::cIO(const char *Filename, bool Log)
    52 :cFileInfo(Filename)
    53 {
    54   log=Log;
    55   readpos=0;
    56 }
    57 
    58 cIO::~cIO()
    59 {
    60 }
    61 
    62 // --- cStreamIO ---------------------------------------------------------------
    63 
    64 cStreamIO::cStreamIO(void)
    65 {
    66   data=0;
    67 }
    68 
    69 cStreamIO::~cStreamIO()
    70 {
    71   StreamClear(true);
    72 }
    73 
    74 void cStreamIO::StreamClear(bool all)
    75 {
    76   if(all) { free(data); data=0; }
    77   fill=0;
    78 }
    79 
    80 unsigned char *cStreamIO::StreamInit(int Size)
    81 {
    82   StreamClear(true);
    83   size=Size; data=(unsigned char *)malloc(size);
    84   return data;
    85 }
    86 
    87 int cStreamIO::Stream(const unsigned char *rest)
    88 {
    89   if(rest && fill) { // copy remaining data to start of buffer
    90     fill-=(rest-data);
    91     memmove(data,rest,fill);
    92     }
    93   else fill=0;
    94 
    95   unsigned long r=Read(data+fill,size-fill);
    96   if(r>=0) { 
    97     fill+=r;
    98     return fill;
    99     }
   100   return -1;    
   101 }
   102 
   103 // --- cFileIO -----------------------------------------------------------------
   104 
   105 cFileIO::cFileIO(const char *Filename, bool Log)
   106 :cIO(Filename,Log)
   107 {
   108   fd=-1;
   109 }
   110 
   111 cFileIO::~cFileIO()
   112 {
   113   Close();
   114 }
   115 
   116 bool cFileIO::Open(void)
   117 {
   118   if(fd>=0) return Seek(0,SEEK_SET);
   119   if(FileInfo(log)) {
   120     if((fd=open(Filename,O_RDONLY))>=0) {
   121       StreamClear(false);
   122       readpos=0;
   123       return true;
   124       }
   125     else if(log) { esyslog("ERROR: open failed on %s: %s",Filename,strerror(errno)); }
   126     }
   127   Close();
   128   return false;
   129 }
   130 
   131 void cFileIO::Close(void)
   132 {
   133   if(fd>=0) { close(fd); fd=-1; }
   134 }
   135 
   136 int cFileIO::Read(unsigned char *Data, int Size)
   137 {
   138   unsigned long r=read(fd,Data,Size);
   139   if(r>=0) readpos+=r;
   140   else if(log) { esyslog("ERROR: read failed in %s: %s",Filename,strerror(errno)); }
   141   return r;
   142 }
   143 
   144 bool cFileIO::Seek(unsigned long long pos, int whence)
   145 {
   146   if(pos>=0 && pos<=Filesize) {
   147     StreamClear(false);
   148     if((readpos=lseek64(fd,pos,whence))>=0) {
   149       if(readpos!=pos) { dsyslog("seek mismatch in %s, wanted %lld, got %lld",Filename,pos,readpos); }
   150       return true;
   151       }
   152     else if(log) { esyslog("ERROR: seek failed in %s: %s",Filename,strerror(errno)); }
   153     }
   154   else d(printf("mp3: bad seek call fd=%d pos=%lld name=%s\n",fd,pos,Filename))
   155   return false;
   156 }
   157 #endif
   158 
   159 // --- cStream -----------------------------------------------------------------
   160 
   161 cStream::cStream(const char *Filename)
   162 :cFileInfo(Filename)
   163 {
   164   fd=-1; ismmap=false; buffer=0;
   165 }
   166 
   167 cStream::~cStream()
   168 {
   169   Close();
   170 }
   171 
   172 bool cStream::Open(bool log)
   173 {
   174   if(fd>=0) return Seek();
   175 
   176   if(FileInfo(log)) {
   177     if((fd=open(Filename,O_RDONLY))>=0) {
   178       buffpos=readpos=0; fill=0;
   179 
   180 #ifdef USE_MMAP
   181       if(Filesize<=MAX_MMAP_SIZE) {
   182         buffer=(unsigned char *)mmap(0,Filesize,PROT_READ,MAP_SHARED,fd,0);
   183         if(buffer!=MAP_FAILED) {
   184           ismmap=true;
   185           return true;
   186           }
   187         else dsyslog("mmap() failed for %s: %s",Filename,strerror(errno));
   188         }
   189 #endif
   190 
   191         buffer = new unsigned char[MP3FILE_BUFSIZE];
   192         if(buffer) return true;
   193         else { esyslog("ERROR: not enough memory for buffer: %s",Filename); }
   194       }
   195     else if(log) { esyslog("ERROR: failed to open file %s: %s",Filename,strerror(errno)); }
   196     }
   197 
   198   Close();
   199   return false;
   200 }
   201 
   202 void cStream::Close(void)
   203 {
   204 #ifdef USE_MMAP
   205   if(ismmap) { 
   206     munmap(buffer,Filesize); buffer=0; ismmap=false;
   207     }
   208   else {
   209 #endif
   210     delete buffer; buffer=0;
   211 #ifdef USE_MMAP
   212     }
   213 #endif
   214   if(fd>=0) { close(fd); fd=-1; }
   215 }
   216 
   217 bool cStream::Seek(unsigned long long pos)
   218 {
   219   if(fd>=0 && pos>=0 && pos<=Filesize) {
   220     buffpos=0; fill=0;
   221     if(ismmap) {
   222       readpos=pos;
   223       return true;
   224       }
   225     else {
   226       if((readpos=lseek64(fd,pos,SEEK_SET))>=0) {
   227         if(readpos!=pos) { dsyslog("seek mismatch in %s, wanted %lld, got %lld",Filename,pos,readpos); }
   228         return true;
   229         }
   230       else { esyslog("ERROR: seeking failed in %s: %d,%s",Filename,errno,strerror(errno)); }
   231       }
   232     }
   233   else d(printf("mp3: bad seek call fd=%d pos=%lld name=%s\n",fd,pos,Filename))
   234   return false;
   235 }
   236 
   237 bool cStream::Stream(unsigned char * &data, unsigned long &len, const unsigned char *rest)
   238 {
   239   if(fd>=0) {
   240     if(readpos<Filesize) {
   241       if(ismmap) {
   242         if(rest && fill) readpos=(rest-buffer);   // take care of remaining data
   243         fill=Filesize-readpos;
   244         data=buffer+readpos; len=fill;
   245         buffpos=readpos; readpos+=fill;
   246         return true;
   247         }
   248       else {
   249         if(rest && fill) {       // copy remaining data to start of buffer
   250           fill-=(rest-buffer);   // remaing bytes
   251           memmove(buffer,rest,fill);
   252           }
   253         else fill=0;
   254 
   255         int r;
   256         do { 
   257           r=read(fd,buffer+fill,MP3FILE_BUFSIZE-fill);
   258           } while(r==-1 && errno==EINTR);
   259 
   260         if(r>=0) {
   261           buffpos=readpos-fill; readpos+=r; fill+=r;
   262           data=buffer; len=fill;
   263           return true;
   264           }
   265         else { esyslog("ERROR: read failed in %s: %d,%s",Filename,errno,strerror(errno)); }
   266         }
   267       }
   268     else {
   269       len=0;
   270       return true;
   271       }
   272     }
   273   return false;
   274 }
   275 
   276 // -----------------------------------------------------------------------------
   277 
   278 #ifdef DUMP_HEAD
   279 void Dump(const char *name, char *buffer)
   280 {
   281   FILE *f=fopen(name,"a");
   282   if(f) {
   283     fprintf(f,"<<<< %s\n",buffer);
   284 /*
   285     int n=strlen(buffer);
   286     for(int i=0 ; i<n ; i+=8) {
   287       fprintf(f,"%04x: ",i);
   288       for(int l=0 ; l<8 && i+l<n ; l++) fprintf(f,"%02x ",buffer[i+l]);
   289       fprintf(f,"\n");
   290       }
   291 */
   292     fclose(f);
   293     }
   294 }
   295 #endif
   296 
   297 // --- cNetStream -----------------------------------------------------------------
   298 
   299 cNetStream::cNetStream(const char *Filename)
   300 :cStream(Filename)
   301 {
   302   net=0; host=path=auth=0; cc=0;
   303   icyName=icyUrl=icyTitle=0; icyChanged=false;
   304   metaInt=0;
   305   InfoDone();
   306 }
   307 
   308 cNetStream::~cNetStream()
   309 {
   310   free(host); free(path); free(auth);
   311   free(icyName); free(icyUrl); free(icyTitle);
   312 }
   313 
   314 bool cNetStream::ParseURL(const char *line, bool log)
   315 {
   316   char pr[32], h[512], p[512], a[512];
   317   int r=sscanf(line," %31[^:]://%511[^/]%511[^\r\n]",pr,h,p);
   318   if(r==2) {
   319     d(printf("netstream: adding default path '/'\n"))
   320     strcpy(p,"/");
   321     r++;
   322     }
   323   if(r==3) {
   324     a[0]=0;
   325     char *s=index(h,'@');
   326     if(s) {
   327       *s=0;
   328       strcpy(a,h);
   329       strcpy(h,s+1);
   330       }
   331     d(printf("netstream: parsed proto='%s' host='%s' path='%s' auth='%s'\n",pr,h,p,a))
   332     if(!strcasecmp(pr,"http")) {
   333       int pp=DEFAULT_PORT;
   334       s=rindex(h,':');
   335       if(s) { *s++=0; pp=atoi(s); }
   336 
   337       free(host); host=strdup(h);
   338       free(path); path=strdup(p);
   339       free(auth); auth=a[0] ? strdup(a) : 0;
   340       port=pp;
   341       return true;
   342       }
   343     else if(log) esyslog("Unsupported protocol %s in: %s",pr,line);
   344     }
   345   else if(log) esyslog("Bad URL line: %s",line);
   346   return false;    
   347 }
   348 
   349 bool cNetStream::ParseURLFile(const char *name, bool log)
   350 {
   351   bool res=false;
   352   FILE *f=fopen(name,"r");
   353   if(f) {
   354     char line[2048];
   355     if(fgets(line,sizeof(line),f)) {
   356       res=ParseURL(line,log);
   357       }
   358     else if(log) esyslog("Nothing to read from URL file %s. File empty?",name);
   359     fclose(f);
   360     }
   361   else if(log) esyslog("fopen() failed on URL file %s: %s",name,strerror(errno));
   362   return res;
   363 }
   364 
   365 bool cNetStream::SendRequest(void)
   366 {
   367   bool res=false;
   368   char buff[2048];
   369 
   370   char *p;
   371   char *h=aprintf(port!=DEFAULT_PORT ? "%s:%d":"%s",host,port);
   372   if(MP3Setup.UseProxy) p=aprintf("http://%s%s",h,path);
   373   else p=aprintf("%s",path);
   374 
   375   char a[1024];
   376   a[0]=0;
   377   if(auth) {
   378     cBase64Encoder b64((uchar *)auth,strlen(auth),76);
   379     int q=0;
   380     const char *l;
   381     while((l=b64.NextLine())) {
   382       q+=snprintf(&a[q],sizeof(a)-q,"%s%s\r\n",q==0?"Authorization: Basic ":" ",l);
   383       }
   384     }
   385 
   386   snprintf(buff,sizeof(buff),
   387            "GET %s HTTP/1.0\r\n"
   388            "User-Agent: %s/%s\r\n"
   389            "Host: %s\r\n"
   390            "Accept: audio/mpeg\r\n"   //XXX audio/x-mpegurl, */*
   391            "Icy-MetaData: 1\r\n"
   392            "%s\r\n",
   393            p,PLUGIN_NAME,PluginVersion,h,a);
   394   free(p); free(h);  
   395 
   396   if(++cc==1) asyncStatus.Set(tr("Connecting to stream server ..."));
   397 
   398   if(net->Connect(MP3Setup.UseProxy ? MP3Setup.ProxyHost:host , MP3Setup.UseProxy ? MP3Setup.ProxyPort:port)) {
   399     d(printf("netstream: -> %s",buff))
   400     if(net->Puts(buff)>0) res=GetHTTPResponse();
   401     }
   402 
   403   if(cc--==1) asyncStatus.Set(0);
   404   return res;
   405 }
   406 
   407 bool cNetStream::ParseHeader(const char *buff, const char *name, char **value)
   408 {
   409   const char *s=index(buff,':');
   410   if(s && !strncasecmp(buff,name,s-buff)) {
   411     s=skipspace(s+1);
   412     d(printf("netstream: found header '%s' contents '%s'\n",name,s))
   413     free(*value); *value=strdup(s);
   414     return true;
   415     }
   416   return false;
   417 }
   418 
   419 bool cNetStream::GetHTTPResponse(void)
   420 {
   421   bool res=false;
   422   char buff[1024], text[128], *newurl=0;
   423   int code=-1, hcount=0;
   424   while(net->Gets(buff,sizeof(buff))>0) {
   425     stripspace(buff);
   426 #ifdef DUMP_HEAD
   427     Dump(DUMP_HEAD,buff);
   428 #endif
   429     d(printf("netstream: <- %s\n",buff))
   430     hcount++;
   431     if(hcount==1) {   // parse status line
   432       if(sscanf(buff,"%*[^ ] %d %128s",&code,text)!=2) {
   433         esyslog("Bad HTTP response '%s' from %s:%d",buff,host,port);
   434         goto out;
   435         }
   436       }
   437     else {            // parse header lines
   438       if(buff[0]==0) { // headers finish if we receive a empty line
   439         switch(code) {
   440           case 200: // OK
   441              res=true;
   442              goto out;
   443           case 300: // MULTIPLE_CHOICES
   444           case 301: // MOVED_PERMANENTLY
   445           case 302: // MOVED_TEMPORARILY
   446              if(newurl) {
   447                if(ParseURL(newurl,true)) res=SendRequest();
   448                }
   449              else esyslog("No location header for redirection from %s:%d",host,port);
   450              goto out;
   451           default:
   452              esyslog("Unhandled HTTP response '%d %s' from %s:%d",code,text,host,port);
   453              goto out;
   454           }
   455         }
   456 
   457       ParseHeader(buff,"Location",&newurl);
   458       ParseHeader(buff,"icy-name",&icyName);
   459       ParseHeader(buff,"icy-url",&icyUrl);
   460       char *meta=0;
   461       if(ParseHeader(buff,"icy-metaint",&meta)) {
   462         metaInt=metaCnt=atol(meta);
   463         d(printf("netstream: meta interval set to %d\n",metaInt));
   464         }
   465       free(meta);
   466       }
   467     }
   468 out:
   469   free(newurl);
   470   return res;
   471 }
   472 
   473 bool cNetStream::Open(bool log)
   474 {
   475   if(net && net->Connected()) return true;
   476 
   477   if(!net) net=new cNet(0,0,0);
   478   net->Disconnect();
   479   
   480   if(ParseURLFile(Filename,log)) {
   481     buffpos=readpos=0; fill=0;
   482     buffer = new unsigned char[MP3FILE_BUFSIZE];
   483     if(buffer) {
   484       if(SendRequest()) {
   485         return true;
   486         }
   487       }
   488     else esyslog("Not enough memory for buffer");
   489     }
   490 
   491   Close();
   492   return false;
   493 }
   494 
   495 void cNetStream::Close(void)
   496 {
   497   delete buffer; buffer=0;
   498   delete net; net=0;
   499 }
   500 
   501 bool cNetStream::Seek(unsigned long long pos)
   502 {
   503   return false;
   504 }
   505 
   506 bool cNetStream::Stream(unsigned char * &data, unsigned long &len, const unsigned char *rest)
   507 {
   508   if(net && net->Connected()) {
   509     if(rest && fill) {       // copy remaining data to start of buffer
   510       fill-=(rest-buffer);   // remaing bytes
   511       memmove(buffer,rest,fill);
   512       }
   513     else fill=0;
   514 
   515     int r=MP3FILE_BUFSIZE-fill;
   516     if(metaInt && r>metaCnt) r=metaCnt;
   517     r=net->Read(buffer+fill,r);
   518     if(r>=0) {
   519       fill+=r; data=buffer; len=fill;
   520       metaCnt-=r;
   521       if(metaInt && metaCnt<=0) {
   522         ParseMetaData();
   523         metaCnt=metaInt;
   524         }
   525       return true;
   526       }
   527     }
   528   return false;
   529 }
   530 
   531 char *cNetStream::ParseMetaString(char *buff, const char *name, char **value)
   532 {
   533   char *s=index(buff,'=');
   534   if(s && !strncasecmp(buff,name,s-buff)) {
   535     char *end=index(s+2,'\'');
   536     if(s[1]=='\'' && end) {
   537       *end=0;
   538       s=stripspace(skipspace(s+2));
   539       if(strlen(s)>0) {
   540         d(printf("netstream: found metadata '%s' contents '%s'\n",name,s))
   541         free(*value); *value=strdup(s);
   542         }
   543       //else d(printf("netstream: found empty metadata '%s'\n",name))
   544       return end+1;
   545       }
   546     else d(printf("netstream: bad metadata format\n"))
   547     }
   548   return 0;
   549 }
   550 
   551 bool cNetStream::ParseMetaData(void)
   552 {
   553   unsigned char byte;
   554   int r=net->Read(&byte,1);
   555   if(r<=0) return false;
   556   int metalen=byte*16;
   557   if(metalen>0) {
   558     char data[metalen+1];
   559     data[metalen]=0;
   560     int cnt=0;
   561     do {
   562       r=net->Read((unsigned char *)data+cnt,metalen-cnt);
   563       if(r<=0) return false;
   564       cnt+=r;
   565       } while(cnt<metalen);
   566 
   567 #ifdef DUMP_HEAD
   568     Dump(DUMP_HEAD,data);
   569 #endif
   570 
   571     char *p=data;
   572     while(*p && p-data<metalen) {
   573       char *n;
   574       if((n=ParseMetaString(p,"StreamTitle",&icyTitle)) ||
   575          (n=ParseMetaString(p,"StreamUrl",&icyUrl))) {
   576         p=n;
   577         icyChanged=true;
   578         }
   579       else  p++;
   580       }
   581     }
   582   return true;
   583 }
   584 
   585 bool cNetStream::IcyChanged(void)
   586 {
   587   bool c=icyChanged;
   588   icyChanged=false;
   589   return c;
   590 }