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