1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/stream.c Sat Dec 29 14:47:40 2007 +0100
1.3 @@ -0,0 +1,589 @@
1.4 +/*
1.5 + * MP3/MPlayer plugin to VDR (C++)
1.6 + *
1.7 + * (C) 2001-2006 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 +#include <stdlib.h>
1.26 +#include <stdio.h>
1.27 +#include <unistd.h>
1.28 +#include <errno.h>
1.29 +
1.30 +#include "common.h"
1.31 +#include "setup-mp3.h"
1.32 +#include "stream.h"
1.33 +#include "network.h"
1.34 +#include "menu-async.h"
1.35 +#include "i18n.h"
1.36 +#include "version.h"
1.37 +
1.38 +#define USE_MMAP
1.39 +#define MAX_MMAP_SIZE (32*1024*1024)
1.40 +#define MP3FILE_BUFSIZE (32*1024)
1.41 +
1.42 +#ifdef USE_MMAP
1.43 +#include <sys/mman.h>
1.44 +#endif
1.45 +
1.46 +#define DEFAULT_PORT 80 // default port for streaming (HTTP)
1.47 +
1.48 +//#define DUMP_HEAD "/var/tmp/headers"
1.49 +
1.50 +// --- cIO ---------------------------------------------------------------------
1.51 +
1.52 +#if 0
1.53 +cIO::cIO(const char *Filename, bool Log)
1.54 +:cFileInfo(Filename)
1.55 +{
1.56 + log=Log;
1.57 + readpos=0;
1.58 +}
1.59 +
1.60 +cIO::~cIO()
1.61 +{
1.62 +}
1.63 +
1.64 +// --- cStreamIO ---------------------------------------------------------------
1.65 +
1.66 +cStreamIO::cStreamIO(void)
1.67 +{
1.68 + data=0;
1.69 +}
1.70 +
1.71 +cStreamIO::~cStreamIO()
1.72 +{
1.73 + StreamClear(true);
1.74 +}
1.75 +
1.76 +void cStreamIO::StreamClear(bool all)
1.77 +{
1.78 + if(all) { free(data); data=0; }
1.79 + fill=0;
1.80 +}
1.81 +
1.82 +unsigned char *cStreamIO::StreamInit(int Size)
1.83 +{
1.84 + StreamClear(true);
1.85 + size=Size; data=(unsigned char *)malloc(size);
1.86 + return data;
1.87 +}
1.88 +
1.89 +int cStreamIO::Stream(const unsigned char *rest)
1.90 +{
1.91 + if(rest && fill) { // copy remaining data to start of buffer
1.92 + fill-=(rest-data);
1.93 + memmove(data,rest,fill);
1.94 + }
1.95 + else fill=0;
1.96 +
1.97 + unsigned long r=Read(data+fill,size-fill);
1.98 + if(r>=0) {
1.99 + fill+=r;
1.100 + return fill;
1.101 + }
1.102 + return -1;
1.103 +}
1.104 +
1.105 +// --- cFileIO -----------------------------------------------------------------
1.106 +
1.107 +cFileIO::cFileIO(const char *Filename, bool Log)
1.108 +:cIO(Filename,Log)
1.109 +{
1.110 + fd=-1;
1.111 +}
1.112 +
1.113 +cFileIO::~cFileIO()
1.114 +{
1.115 + Close();
1.116 +}
1.117 +
1.118 +bool cFileIO::Open(void)
1.119 +{
1.120 + if(fd>=0) return Seek(0,SEEK_SET);
1.121 + if(FileInfo(log)) {
1.122 + if((fd=open(Filename,O_RDONLY))>=0) {
1.123 + StreamClear(false);
1.124 + readpos=0;
1.125 + return true;
1.126 + }
1.127 + else if(log) { esyslog("ERROR: open failed on %s: %s",Filename,strerror(errno)); }
1.128 + }
1.129 + Close();
1.130 + return false;
1.131 +}
1.132 +
1.133 +void cFileIO::Close(void)
1.134 +{
1.135 + if(fd>=0) { close(fd); fd=-1; }
1.136 +}
1.137 +
1.138 +int cFileIO::Read(unsigned char *Data, int Size)
1.139 +{
1.140 + unsigned long r=read(fd,Data,Size);
1.141 + if(r>=0) readpos+=r;
1.142 + else if(log) { esyslog("ERROR: read failed in %s: %s",Filename,strerror(errno)); }
1.143 + return r;
1.144 +}
1.145 +
1.146 +bool cFileIO::Seek(unsigned long long pos, int whence)
1.147 +{
1.148 + if(pos>=0 && pos<=Filesize) {
1.149 + StreamClear(false);
1.150 + if((readpos=lseek64(fd,pos,whence))>=0) {
1.151 + if(readpos!=pos) { dsyslog("seek mismatch in %s, wanted %lld, got %lld",Filename,pos,readpos); }
1.152 + return true;
1.153 + }
1.154 + else if(log) { esyslog("ERROR: seek failed in %s: %s",Filename,strerror(errno)); }
1.155 + }
1.156 + else d(printf("mp3: bad seek call fd=%d pos=%lld name=%s\n",fd,pos,Filename))
1.157 + return false;
1.158 +}
1.159 +#endif
1.160 +
1.161 +// --- cStream -----------------------------------------------------------------
1.162 +
1.163 +cStream::cStream(const char *Filename)
1.164 +:cFileInfo(Filename)
1.165 +{
1.166 + fd=-1; ismmap=false; buffer=0;
1.167 +}
1.168 +
1.169 +cStream::~cStream()
1.170 +{
1.171 + Close();
1.172 +}
1.173 +
1.174 +bool cStream::Open(bool log)
1.175 +{
1.176 + if(fd>=0) return Seek();
1.177 +
1.178 + if(FileInfo(log)) {
1.179 + if((fd=open(Filename,O_RDONLY))>=0) {
1.180 + buffpos=readpos=0; fill=0;
1.181 +
1.182 +#ifdef USE_MMAP
1.183 + if(Filesize<=MAX_MMAP_SIZE) {
1.184 + buffer=(unsigned char *)mmap(0,Filesize,PROT_READ,MAP_SHARED,fd,0);
1.185 + if(buffer!=MAP_FAILED) {
1.186 + ismmap=true;
1.187 + return true;
1.188 + }
1.189 + else dsyslog("mmap() failed for %s: %s",Filename,strerror(errno));
1.190 + }
1.191 +#endif
1.192 +
1.193 + buffer = new unsigned char[MP3FILE_BUFSIZE];
1.194 + if(buffer) return true;
1.195 + else { esyslog("ERROR: not enough memory for buffer: %s",Filename); }
1.196 + }
1.197 + else if(log) { esyslog("ERROR: failed to open file %s: %s",Filename,strerror(errno)); }
1.198 + }
1.199 +
1.200 + Close();
1.201 + return false;
1.202 +}
1.203 +
1.204 +void cStream::Close(void)
1.205 +{
1.206 +#ifdef USE_MMAP
1.207 + if(ismmap) {
1.208 + munmap(buffer,Filesize); buffer=0; ismmap=false;
1.209 + }
1.210 + else {
1.211 +#endif
1.212 + delete buffer; buffer=0;
1.213 +#ifdef USE_MMAP
1.214 + }
1.215 +#endif
1.216 + if(fd>=0) { close(fd); fd=-1; }
1.217 +}
1.218 +
1.219 +bool cStream::Seek(unsigned long long pos)
1.220 +{
1.221 + if(fd>=0 && pos>=0 && pos<=Filesize) {
1.222 + buffpos=0; fill=0;
1.223 + if(ismmap) {
1.224 + readpos=pos;
1.225 + return true;
1.226 + }
1.227 + else {
1.228 + if((readpos=lseek64(fd,pos,SEEK_SET))>=0) {
1.229 + if(readpos!=pos) { dsyslog("seek mismatch in %s, wanted %lld, got %lld",Filename,pos,readpos); }
1.230 + return true;
1.231 + }
1.232 + else { esyslog("ERROR: seeking failed in %s: %d,%s",Filename,errno,strerror(errno)); }
1.233 + }
1.234 + }
1.235 + else d(printf("mp3: bad seek call fd=%d pos=%lld name=%s\n",fd,pos,Filename))
1.236 + return false;
1.237 +}
1.238 +
1.239 +bool cStream::Stream(unsigned char * &data, unsigned long &len, const unsigned char *rest)
1.240 +{
1.241 + if(fd>=0) {
1.242 + if(readpos<Filesize) {
1.243 + if(ismmap) {
1.244 + if(rest && fill) readpos=(rest-buffer); // take care of remaining data
1.245 + fill=Filesize-readpos;
1.246 + data=buffer+readpos; len=fill;
1.247 + buffpos=readpos; readpos+=fill;
1.248 + return true;
1.249 + }
1.250 + else {
1.251 + if(rest && fill) { // copy remaining data to start of buffer
1.252 + fill-=(rest-buffer); // remaing bytes
1.253 + memmove(buffer,rest,fill);
1.254 + }
1.255 + else fill=0;
1.256 +
1.257 + int r;
1.258 + do {
1.259 + r=read(fd,buffer+fill,MP3FILE_BUFSIZE-fill);
1.260 + } while(r==-1 && errno==EINTR);
1.261 +
1.262 + if(r>=0) {
1.263 + buffpos=readpos-fill; readpos+=r; fill+=r;
1.264 + data=buffer; len=fill;
1.265 + return true;
1.266 + }
1.267 + else { esyslog("ERROR: read failed in %s: %d,%s",Filename,errno,strerror(errno)); }
1.268 + }
1.269 + }
1.270 + else {
1.271 + len=0;
1.272 + return true;
1.273 + }
1.274 + }
1.275 + return false;
1.276 +}
1.277 +
1.278 +// -----------------------------------------------------------------------------
1.279 +
1.280 +#ifdef DUMP_HEAD
1.281 +void Dump(const char *name, char *buffer)
1.282 +{
1.283 + FILE *f=fopen(name,"a");
1.284 + if(f) {
1.285 + fprintf(f,"<<<< %s\n",buffer);
1.286 +/*
1.287 + int n=strlen(buffer);
1.288 + for(int i=0 ; i<n ; i+=8) {
1.289 + fprintf(f,"%04x: ",i);
1.290 + for(int l=0 ; l<8 && i+l<n ; l++) fprintf(f,"%02x ",buffer[i+l]);
1.291 + fprintf(f,"\n");
1.292 + }
1.293 +*/
1.294 + fclose(f);
1.295 + }
1.296 +}
1.297 +#endif
1.298 +
1.299 +// --- cNetStream -----------------------------------------------------------------
1.300 +
1.301 +cNetStream::cNetStream(const char *Filename)
1.302 +:cStream(Filename)
1.303 +{
1.304 + net=0; host=path=auth=0; cc=0;
1.305 + icyName=icyUrl=icyTitle=0; icyChanged=false;
1.306 + metaInt=0;
1.307 + InfoDone();
1.308 +}
1.309 +
1.310 +cNetStream::~cNetStream()
1.311 +{
1.312 + free(host); free(path); free(auth);
1.313 + free(icyName); free(icyUrl); free(icyTitle);
1.314 +}
1.315 +
1.316 +bool cNetStream::ParseURL(const char *line, bool log)
1.317 +{
1.318 + char pr[32], h[512], p[512], a[512];
1.319 + int r=sscanf(line," %31[^:]://%511[^/]%511[^\r\n]",pr,h,p);
1.320 + if(r==2) {
1.321 + d(printf("netstream: adding default path '/'\n"))
1.322 + strcpy(p,"/");
1.323 + r++;
1.324 + }
1.325 + if(r==3) {
1.326 + a[0]=0;
1.327 + char *s=index(h,'@');
1.328 + if(s) {
1.329 + *s=0;
1.330 + strcpy(a,h);
1.331 + strcpy(h,s+1);
1.332 + }
1.333 + d(printf("netstream: parsed proto='%s' host='%s' path='%s' auth='%s'\n",pr,h,p,a))
1.334 + if(!strcasecmp(pr,"http")) {
1.335 + int pp=DEFAULT_PORT;
1.336 + s=rindex(h,':');
1.337 + if(s) { *s++=0; pp=atoi(s); }
1.338 +
1.339 + free(host); host=strdup(h);
1.340 + free(path); path=strdup(p);
1.341 + free(auth); auth=a[0] ? strdup(a) : 0;
1.342 + port=pp;
1.343 + return true;
1.344 + }
1.345 + else if(log) esyslog("Unsupported protocol %s in: %s",pr,line);
1.346 + }
1.347 + else if(log) esyslog("Bad URL line: %s",line);
1.348 + return false;
1.349 +}
1.350 +
1.351 +bool cNetStream::ParseURLFile(const char *name, bool log)
1.352 +{
1.353 + bool res=false;
1.354 + FILE *f=fopen(name,"r");
1.355 + if(f) {
1.356 + char line[2048];
1.357 + if(fgets(line,sizeof(line),f)) {
1.358 + res=ParseURL(line,log);
1.359 + }
1.360 + else if(log) esyslog("Nothing to read from URL file %s. File empty?",name);
1.361 + fclose(f);
1.362 + }
1.363 + else if(log) esyslog("fopen() failed on URL file %s: %s",name,strerror(errno));
1.364 + return res;
1.365 +}
1.366 +
1.367 +bool cNetStream::SendRequest(void)
1.368 +{
1.369 + bool res=false;
1.370 + char buff[2048];
1.371 +
1.372 + char *h, *p;
1.373 + asprintf(&h,port!=DEFAULT_PORT ? "%s:%d":"%s",host,port);
1.374 + if(MP3Setup.UseProxy) asprintf(&p,"http://%s%s",h,path);
1.375 + else asprintf(&p,"%s",path);
1.376 +
1.377 + char a[1024];
1.378 + a[0]=0;
1.379 + if(auth) {
1.380 + cBase64Encoder b64((uchar *)auth,strlen(auth),76);
1.381 + int q=0;
1.382 + const char *l;
1.383 + while((l=b64.NextLine())) {
1.384 + q+=snprintf(&a[q],sizeof(a)-q,"%s%s\r\n",q==0?"Authorization: Basic ":" ",l);
1.385 + }
1.386 + }
1.387 +
1.388 + snprintf(buff,sizeof(buff),
1.389 + "GET %s HTTP/1.0\r\n"
1.390 + "User-Agent: %s/%s\r\n"
1.391 + "Host: %s\r\n"
1.392 + "Accept: audio/mpeg\r\n" //XXX audio/x-mpegurl, */*
1.393 + "Icy-MetaData: 1\r\n"
1.394 + "%s\r\n",
1.395 + p,PLUGIN_NAME,PLUGIN_VERSION,h,a);
1.396 + free(p); free(h);
1.397 +
1.398 + if(++cc==1) asyncStatus.Set(tr("Connecting to stream server ..."));
1.399 +
1.400 + if(net->Connect(MP3Setup.UseProxy ? MP3Setup.ProxyHost:host , MP3Setup.UseProxy ? MP3Setup.ProxyPort:port)) {
1.401 + d(printf("netstream: -> %s",buff))
1.402 + if(net->Puts(buff)>0) res=GetHTTPResponse();
1.403 + }
1.404 +
1.405 + if(cc--==1) asyncStatus.Set(0);
1.406 + return res;
1.407 +}
1.408 +
1.409 +bool cNetStream::ParseHeader(const char *buff, const char *name, char **value)
1.410 +{
1.411 + char *s=index(buff,':');
1.412 + if(s && !strncasecmp(buff,name,s-buff)) {
1.413 + s=skipspace(s+1);
1.414 + d(printf("netstream: found header '%s' contents '%s'\n",name,s))
1.415 + free(*value); *value=strdup(s);
1.416 + return true;
1.417 + }
1.418 + return false;
1.419 +}
1.420 +
1.421 +bool cNetStream::GetHTTPResponse(void)
1.422 +{
1.423 + bool res=false;
1.424 + char buff[1024], text[128], *newurl=0;
1.425 + int code=-1, hcount=0;
1.426 + while(net->Gets(buff,sizeof(buff))>0) {
1.427 + stripspace(buff);
1.428 +#ifdef DUMP_HEAD
1.429 + Dump(DUMP_HEAD,buff);
1.430 +#endif
1.431 + d(printf("netstream: <- %s\n",buff))
1.432 + hcount++;
1.433 + if(hcount==1) { // parse status line
1.434 + if(sscanf(buff,"%*[^ ] %d %128s",&code,text)!=2) {
1.435 + esyslog("Bad HTTP response '%s' from %s:%d",buff,host,port);
1.436 + goto out;
1.437 + }
1.438 + }
1.439 + else { // parse header lines
1.440 + if(buff[0]==0) { // headers finish if we receive a empty line
1.441 + switch(code) {
1.442 + case 200: // OK
1.443 + res=true;
1.444 + goto out;
1.445 + case 300: // MULTIPLE_CHOICES
1.446 + case 301: // MOVED_PERMANENTLY
1.447 + case 302: // MOVED_TEMPORARILY
1.448 + if(newurl) {
1.449 + if(ParseURL(newurl,true)) res=SendRequest();
1.450 + }
1.451 + else esyslog("No location header for redirection from %s:%d",host,port);
1.452 + goto out;
1.453 + default:
1.454 + esyslog("Unhandled HTTP response '%d %s' from %s:%d",code,text,host,port);
1.455 + goto out;
1.456 + }
1.457 + }
1.458 +
1.459 + ParseHeader(buff,"Location",&newurl);
1.460 + ParseHeader(buff,"icy-name",&icyName);
1.461 + ParseHeader(buff,"icy-url",&icyUrl);
1.462 + char *meta=0;
1.463 + if(ParseHeader(buff,"icy-metaint",&meta)) {
1.464 + metaInt=metaCnt=atol(meta);
1.465 + d(printf("netstream: meta interval set to %d\n",metaInt));
1.466 + }
1.467 + free(meta);
1.468 + }
1.469 + }
1.470 +out:
1.471 + free(newurl);
1.472 + return res;
1.473 +}
1.474 +
1.475 +bool cNetStream::Open(bool log)
1.476 +{
1.477 + if(net && net->Connected()) return true;
1.478 +
1.479 + if(!net) net=new cNet(0,0,0);
1.480 + net->Disconnect();
1.481 +
1.482 + if(ParseURLFile(Filename,log)) {
1.483 + buffpos=readpos=0; fill=0;
1.484 + buffer = new unsigned char[MP3FILE_BUFSIZE];
1.485 + if(buffer) {
1.486 + if(SendRequest()) {
1.487 + return true;
1.488 + }
1.489 + }
1.490 + else esyslog("Not enough memory for buffer");
1.491 + }
1.492 +
1.493 + Close();
1.494 + return false;
1.495 +}
1.496 +
1.497 +void cNetStream::Close(void)
1.498 +{
1.499 + delete buffer; buffer=0;
1.500 + delete net; net=0;
1.501 +}
1.502 +
1.503 +bool cNetStream::Seek(unsigned long long pos)
1.504 +{
1.505 + return false;
1.506 +}
1.507 +
1.508 +bool cNetStream::Stream(unsigned char * &data, unsigned long &len, const unsigned char *rest)
1.509 +{
1.510 + if(net && net->Connected()) {
1.511 + if(rest && fill) { // copy remaining data to start of buffer
1.512 + fill-=(rest-buffer); // remaing bytes
1.513 + memmove(buffer,rest,fill);
1.514 + }
1.515 + else fill=0;
1.516 +
1.517 + int r=MP3FILE_BUFSIZE-fill;
1.518 + if(metaInt && r>metaCnt) r=metaCnt;
1.519 + r=net->Read(buffer+fill,r);
1.520 + if(r>=0) {
1.521 + fill+=r; data=buffer; len=fill;
1.522 + metaCnt-=r;
1.523 + if(metaInt && metaCnt<=0) {
1.524 + ParseMetaData();
1.525 + metaCnt=metaInt;
1.526 + }
1.527 + return true;
1.528 + }
1.529 + }
1.530 + return false;
1.531 +}
1.532 +
1.533 +char *cNetStream::ParseMetaString(const char *buff, const char *name, char **value)
1.534 +{
1.535 + char *s=index(buff,'=');
1.536 + if(s && !strncasecmp(buff,name,s-buff)) {
1.537 + char *end=index(s+2,'\'');
1.538 + if(s[1]=='\'' && end) {
1.539 + *end=0;
1.540 + s=stripspace(skipspace(s+2));
1.541 + if(strlen(s)>0) {
1.542 + d(printf("netstream: found metadata '%s' contents '%s'\n",name,s))
1.543 + free(*value); *value=strdup(s);
1.544 + }
1.545 + //else d(printf("netstream: found empty metadata '%s'\n",name))
1.546 + return end+1;
1.547 + }
1.548 + else d(printf("netstream: bad metadata format\n"))
1.549 + }
1.550 + return 0;
1.551 +}
1.552 +
1.553 +bool cNetStream::ParseMetaData(void)
1.554 +{
1.555 + unsigned char byte;
1.556 + int r=net->Read(&byte,1);
1.557 + if(r<=0) return false;
1.558 + int metalen=byte*16;
1.559 + if(metalen>0) {
1.560 + char data[metalen+1];
1.561 + data[metalen]=0;
1.562 + int cnt=0;
1.563 + do {
1.564 + r=net->Read((unsigned char *)data+cnt,metalen-cnt);
1.565 + if(r<=0) return false;
1.566 + cnt+=r;
1.567 + } while(cnt<metalen);
1.568 +
1.569 +#ifdef DUMP_HEAD
1.570 + Dump(DUMP_HEAD,data);
1.571 +#endif
1.572 +
1.573 + char *p=data;
1.574 + while(*p && p-data<metalen) {
1.575 + char *n;
1.576 + if((n=ParseMetaString(p,"StreamTitle",&icyTitle)) ||
1.577 + (n=ParseMetaString(p,"StreamUrl",&icyUrl))) {
1.578 + p=n;
1.579 + icyChanged=true;
1.580 + }
1.581 + else p++;
1.582 + }
1.583 + }
1.584 + return true;
1.585 +}
1.586 +
1.587 +bool cNetStream::IcyChanged(void)
1.588 +{
1.589 + bool c=icyChanged;
1.590 + icyChanged=false;
1.591 + return c;
1.592 +}