diff options
| author | Thibaut Mattern <tmattern@users.sourceforge.net> | 2003-01-13 01:11:57 +0000 | 
|---|---|---|
| committer | Thibaut Mattern <tmattern@users.sourceforge.net> | 2003-01-13 01:11:57 +0000 | 
| commit | 676d0a2b8e5bcf46808b76ca7434a7e8f65576ea (patch) | |
| tree | 87f10fb2f1d6a58d2dc0a21ec7833fcd41e11917 | |
| parent | eaac42f1becbbcc62317e9bf81e07608aaad62fd (diff) | |
| download | xine-lib-676d0a2b8e5bcf46808b76ca7434a7e8f65576ea.tar.gz xine-lib-676d0a2b8e5bcf46808b76ca7434a7e8f65576ea.tar.bz2 | |
Experimental mmsh protocol support.
Some explanantions about mms protocols in xine :
mms:/, mmst:/, mmsu:/, mmsh:/ mrls are handled by the mms input plugin
mmst:/ and mmsu:/ mrls point out the 'mms over tcp' protocol (mms.c)
mmsh:/ mrls point out the 'mms over http' protocol (mmsh.c)
When a mms:/ url is encountered, mmst is tried first, then mmsh is tried if mmst failed
To try the new mmsh support :
xine mmsh://wmp.tf1.coltfrance.com/wmetf1/cinema/hpotter/harry_potter_secrets_320.wmv
xine mmsh://wmp.tf1.coltfrance.com/wmetf1/clip/johnny/marie_320.wmv
CVS patchset: 3885
CVS date: 2003/01/13 01:11:57
| -rw-r--r-- | src/input/Makefile.am | 2 | ||||
| -rw-r--r-- | src/input/input_mms.c | 115 | ||||
| -rw-r--r-- | src/input/mmsh.c | 1107 | ||||
| -rw-r--r-- | src/input/mmsh.h | 42 | 
4 files changed, 1242 insertions, 24 deletions
| diff --git a/src/input/Makefile.am b/src/input/Makefile.am index 4985ba18b..eb22810d0 100644 --- a/src/input/Makefile.am +++ b/src/input/Makefile.am @@ -78,7 +78,7 @@ xineplug_inp_net_la_SOURCES = input_net.c net_buf_ctrl.c  xineplug_inp_net_la_LIBADD = $(XINE_LIB)  xineplug_inp_net_la_LDFLAGS = -avoid-version -module @XINE_PLUGIN_MIN_SYMS@ -xineplug_inp_mms_la_SOURCES = input_mms.c net_buf_ctrl.c mms.c  +xineplug_inp_mms_la_SOURCES = input_mms.c net_buf_ctrl.c mms.c mmsh.c  xineplug_inp_mms_la_LIBADD = $(XINE_LIB)  xineplug_inp_mms_la_LDFLAGS = -avoid-version -module @XINE_PLUGIN_MIN_SYMS@ diff --git a/src/input/input_mms.c b/src/input/input_mms.c index 3ce436f38..1a7b078fd 100644 --- a/src/input/input_mms.c +++ b/src/input/input_mms.c @@ -17,7 +17,7 @@   * along with this program; if not, write to the Free Software   * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA   * - * $Id: input_mms.c,v 1.30 2002/12/27 16:47:10 miguelfreitas Exp $ + * $Id: input_mms.c,v 1.31 2003/01/13 01:11:57 tmattern Exp $   *   * mms input plugin based on work from major mms   */ @@ -43,12 +43,17 @@  #include "input_plugin.h"  #include "mms.h" +#include "mmsh.h"  #include "net_buf_ctrl.h"  /*  #define LOG  */ +#define PROTOCOL_UNDEFINED 0 +#define PROTOCOL_MMST      1 +#define PROTOCOL_MMSH      2 +  #if !defined(NDELAY) && defined(O_NDELAY)  #define FNDELAY O_NDELAY  #endif @@ -57,14 +62,18 @@  const uint32_t mms_bandwidths[]={14400,19200,28800,33600,34430,57600,                                    115200,262200,393216,524300,1544000,10485800}; -const char * mms_bandwidth_strs[]={"14.4 Kbps", "19.2 Kbps", "28.8 Kbps", "33.6 Kbps", -                           "34.4 Kbps", "57.6 Kbps", "115.2 Kbps","262.2 Kbps", -                           "393.2 Kbps","524.3 Kbps", "1.5 Mbps", "10.5 Mbps", NULL}; +const char * mms_bandwidth_strs[]={"14.4 Kbps (Modem)", "19.2 Kbps (Modem)", +                                   "28.8 Kbps (Modem)", "33.6 Kbps (Modem)", +                                   "34.4 Kbps (Modem)", "57.6 Kbps (Modem)", +                                   "115.2 Kbps (ISDN)", "262.2 Kbps (Cable/DSL)", +                                   "393.2 Kbps (Cable/DSL)","524.3 Kbps (Cable/DSL)", +                                   "1.5 Mbps (T1)", "10.5 Mbps (LAN)", NULL};  typedef struct {    input_plugin_t   input_plugin;    mms_t           *mms; +  mmsh_t          *mmsh;    char            *mrl; @@ -75,7 +84,8 @@ typedef struct {    char             scratch[1025];    int              bandwidth; - +  int              protocol;       /* mmst or mmsh */ +    } mms_input_plugin_t;  typedef struct { @@ -99,7 +109,15 @@ static off_t mms_plugin_read (input_plugin_t *this_gen,    nbc_check_buffers (this->nbc); -  n = mms_read (this->mms, buf, len); +  switch (this->protocol) { +    case PROTOCOL_MMST: +      n = mms_read (this->mms, buf, len); +      break; +    case PROTOCOL_MMSH: +      n = mmsh_read (this->mmsh, buf, len); +      break; +  } +                  this->curpos += n;    return n; @@ -168,7 +186,15 @@ static off_t mms_plugin_seek (input_plugin_t *this_gen, off_t offset, int origin      if (diff>1024)        diff = 1024; -    n = mms_read (this->mms, this->scratch, diff); +    switch (this->protocol) { +      case PROTOCOL_MMST: +        n = mms_read (this->mms, this->scratch, diff); +        break; +      case PROTOCOL_MMSH: +        n = mmsh_read (this->mmsh, this->scratch, diff); +        break; +    } +          this->curpos += n;      if (n < diff) @@ -186,7 +212,14 @@ static off_t mms_plugin_get_length (input_plugin_t *this_gen) {    if (!this->mms)      return 0; -  length = mms_get_length (this->mms); +  switch (this->protocol) { +    case PROTOCOL_MMST: +      length = mms_get_length (this->mms); +      break; +    case PROTOCOL_MMSH: +      length = mmsh_get_length (this->mmsh); +      break; +  }  #ifdef LOG    printf ("input_mms: length is %lld\n", length); @@ -218,8 +251,16 @@ static void mms_plugin_dispose (input_plugin_t *this_gen) {    mms_input_plugin_t *this = (mms_input_plugin_t *) this_gen;    if (this->mms) { -    mms_close (this->mms); -    this->mms = NULL; +    switch (this->protocol) { +      case PROTOCOL_MMST: +        mms_close (this->mms); +        break; +      case PROTOCOL_MMSH: +        mmsh_close (this->mmsh); +        break; +    } +    this->mms  = NULL; +    this->mmsh = NULL;    }    if (this->nbc) { @@ -246,7 +287,14 @@ static int mms_plugin_get_optional_data (input_plugin_t *this_gen,    switch (data_type) {    case INPUT_OPTIONAL_DATA_PREVIEW: -    return mms_peek_header (this->mms, data); +    switch (this->protocol) { +      case PROTOCOL_MMST: +        return mms_peek_header (this->mms, data); +        break; +      case PROTOCOL_MMSH: +        return mmsh_peek_header (this->mmsh, data); +        break; +    }      break;    default: @@ -280,39 +328,60 @@ static input_plugin_t *open_plugin (input_class_t *cls_gen, xine_stream_t *strea    mms_input_class_t  *cls = (mms_input_class_t *) cls_gen;    mms_input_plugin_t *this;    mms_t              *mms; +  mmsh_t             *mmsh;    char               *mrl = strdup(data);    xine_cfg_entry_t    bandwidth_entry; +  int                 protocol;  #ifdef LOG    printf ("input_mms: trying to open '%s'\n", mrl);  #endif -  if (strncasecmp (mrl, "mms://", 6)) { +  if (!strncasecmp (mrl, "mms://", 6)) { +    protocol = PROTOCOL_UNDEFINED; +  } else if (!strncasecmp (mrl, "mmst://", 7)) { +    protocol =   PROTOCOL_MMST; +  } else if (!strncasecmp (mrl, "mmsh://", 7)) { +    protocol =   PROTOCOL_MMSH; +  } else {      free (mrl);      return NULL;    }    this = (mms_input_plugin_t *) malloc (sizeof (mms_input_plugin_t));    cls->ip = this; - +      if (xine_config_lookup_entry (stream->xine, "input.mms_network_bandwidth",                                   &bandwidth_entry)) {      bandwidth_changed_cb(cls, &bandwidth_entry);    } -  mms = mms_connect (stream, mrl, this->bandwidth); - -  if (!mms) { +     +  switch (this->protocol) { +    case PROTOCOL_UNDEFINED: +      mms = mms_connect (stream, mrl, this->bandwidth); +      if (!mms) { +        mmsh = mmsh_connect (stream, mrl, this->bandwidth); +      } +      break; +    case PROTOCOL_MMST: +      mms = mms_connect (stream, mrl, this->bandwidth); +      break; +    case PROTOCOL_MMSH: +      mmsh = mmsh_connect (stream, mrl, this->bandwidth); +      break; +  } +   +  if (!mms && !mmsh) {      free (mrl);      return NULL;    } - -  -   -  this->mms    = mms; -  this->mrl    = mrl;  -  this->curpos = 0; -  this->nbc    = nbc_init (stream); +  this->mms      = mms; +  this->mmsh     = mmsh; +  this->protocol = protocol; +  this->mrl      = mrl;  +  this->curpos   = 0; +  this->nbc      = nbc_init (stream);    this->input_plugin.get_capabilities  = mms_plugin_get_capabilities;    this->input_plugin.read              = mms_plugin_read; diff --git a/src/input/mmsh.c b/src/input/mmsh.c new file mode 100644 index 000000000..70ec4458e --- /dev/null +++ b/src/input/mmsh.c @@ -0,0 +1,1107 @@ +/* + * Copyright (C) 2002 the xine project + *  + * This file is part of xine, a free video player. + *  + * xine is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + *  + * xine is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + *  + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA + * + * $Id: mmsh.c,v 1.1 2003/01/13 01:11:57 tmattern Exp $ + * + * based on mms.c and specs from avifile + * (http://avifile.sourceforge.net/asf-1.0.htm) + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <unistd.h> +#include <stdio.h> +#include <assert.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> +#include <stdlib.h> +#include <time.h> + +#include "xine_internal.h" +#include "xineutils.h" + +#include "bswap.h" +#include "mmsh.h" +#include "../demuxers/asfheader.h" + +/* +#define LOG +*/ +#define MMSH_PORT 80 + +#define BUF_SIZE 102400 + +#define CMD_HEADER_LEN   48 +#define CMD_BODY_LEN   1024 + +#define USERAGENT "User-Agent: NSPlayer/7.1.0.3055\r\n" +#define CLIENTGUID "Pragma: xClientGUID={c77e7400-738a-11d2-9add-0020af0a3278}\r\n" + +#define MMSH_SEEKABLE               1 +#define MMSH_LIVE                   2 +#define CHUNK_HEADER_LENGTH        12 +#define CHUNK_TYPE_DATA        0x4424 +#define CHUNK_TYPE_END         0x4524 +#define CHUNK_TYPE_ASF_HEADER  0x4824 +  +static const char* mmsh_FirstRequest = +    "GET %s HTTP/1.0\r\n" +    "Accept: */*\r\n" +    USERAGENT +    "Host: %s\r\n" +    "Pragma: no-cache,rate=1.000000,stream-time=0,stream-offset=0:0,request-context=%u,max-duration=0\r\n" +    CLIENTGUID +    "Connection: Close\r\n\r\n\r\n\r\n\n\n"; + +static const char* mmsh_SeekableRequest = +    "GET %s HTTP/1.0\r\n" +    "Accept: */*\r\n" +    USERAGENT +    "Host: %s\r\n" +    "Pragma: no-cache,rate=1.000000,stream-time=%u,stream-offset=%u:%u,request-context=%u,max-duration=%u\r\n" +    CLIENTGUID +    "Pragma: xPlayStrm=1\r\n" +    "Pragma: stream-switch-count=%d\r\n" +    "Pragma: stream-switch-entry=%s\r\n" // ffff:1:0 ffff:2:0 +    "Connection: Close\r\n\r\n"; + +static const char* mmsh_LiveRequest = +    "GET %s HTTP/1.0\r\n" +    "Accept: */*\r\n" +    USERAGENT +    "Host: %s\r\n" +    "Pragma: no-cache,rate=1.000000,request-context=%u\r\n" +    "Pragma: xPlayStrm=1\r\n" +    CLIENTGUID +    "Pragma: stream-switch-count=%d\r\n" +    "Pragma: stream-switch-entry=%s\r\n" +    "Connection: Close\r\n\r\n"; + + +#if 0 +/* Unused requests */ +static const char* mmsh_PostRequest = +    "POST %s HTTP/1.0\r\n" +    "Accept: */*\r\n" +    USERAGENT +    "Host: %s\r\n" +    "Pragma: client-id=%u\r\n" +/*    "Pragma: log-line=no-cache,rate=1.000000,stream-time=%u,stream-offset=%u:%u,request-context=2,max-duration=%u\r\n" */ +    "Pragma: Content-Length: 0\r\n" +    CLIENTGUID +    "\r\n"; + +static const char* mmsh_RangeRequest = +    "GET %s HTTP/1.0\r\n" +    "Accept: */*\r\n" +    USERAGENT +    "Host: %s\r\n" +    "Range: bytes=%Lu-\r\n" +    CLIENTGUID +    "Connection: Close\r\n\r\n"; +#endif + +/*  + * mmsh specific types  + */ + + +struct mmsh_s { + +  int           s; + +  char         *host; +  char         *path; +  char         *file; +  char         *url; + +  /* command to send */ +  char          scmd[CMD_HEADER_LEN + CMD_BODY_LEN]; +  char         *scmd_body; /* pointer to &scmd[CMD_HEADER_LEN] */ +  int           scmd_len; /* num bytes written in header */ + +  char          str[1024]; /* scratch buffer to built strings */ + +  int           stream_type;  /* seekable or broadcast */ +   +  /* receive buffer */ +   +  /* chunk */ +  uint16_t      chunk_type; +  uint16_t      chunk_length; +  uint16_t      chunk_seq_number; +  uint8_t       buf[BUF_SIZE]; +   +  int           buf_size; +  int           buf_read; + +  uint8_t       asf_header[8192]; +  uint32_t      asf_header_len; +  uint32_t      asf_header_read; +  int           seq_num; +  int           num_stream_ids; +  int           stream_ids[ASF_MAX_NUM_STREAMS]; +  int           stream_types[ASF_MAX_NUM_STREAMS]; +  int           packet_length; +  uint32_t      file_length; +  char          guid[37]; +  uint32_t      bitrates[ASF_MAX_NUM_STREAMS]; +  uint32_t      bitrates_pos[ASF_MAX_NUM_STREAMS]; + +  int           has_audio; +  int           has_video; +}; + +/* network/socket utility functions */ + +static ssize_t read_timeout(int fd, void *buf, size_t count) { +   +  ssize_t ret, total; + +  total = 0; + +  while (total < count) { +   +    fd_set rset; +    struct timeval timeout; + +    FD_ZERO (&rset); +    FD_SET  (fd, &rset); +     +    timeout.tv_sec  = 30; +    timeout.tv_usec = 0; +     +    if (select (fd+1, &rset, NULL, NULL, &timeout) <= 0) { +      return -1; +    } +     +    ret=read (fd, ((uint8_t*)buf)+total, count-total); + +    if (ret<=0) { +#ifdef LOG +      printf ("libmmsh: read error.\n"); +#endif +      return ret; +    } else +      total += ret; +  } + +#ifdef LOG +  /* printf ("mmsh: read completed %d/%d\n", total, count); */ +#endif + +  return total; +} + +static int host_connect_attempt(struct in_addr ia, int port) { + +  int                s; +  struct sockaddr_in sin; + +  s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);   +  if (s == -1) { +    printf ("libmmsh: socket(): %s\n", strerror(errno)); +    return -1; +  } + +  /* put socket in non-blocking mode */ +  fcntl (s, F_SETFL, fcntl (s, F_GETFL) | O_NONBLOCK);    + +  sin.sin_family = AF_INET;	 +  sin.sin_addr   = ia; +  sin.sin_port   = htons(port); +   +  if (connect(s, (struct sockaddr *)&sin, sizeof(sin))==-1  +      && errno != EINPROGRESS) { +    printf ("libmmsh: connect(): %s\n", strerror(errno)); +    close(s); +    return -1; +  }	 +	 +  return s; +} + +static int host_connect(const char *host, int port) { + +  struct hostent *h; +  int             i, s; +   +  h = gethostbyname(host); +  if (h == NULL) { +    printf ("libmmsh: unable to resolve '%s'.\n", host); +    return -1; +  } +	 +  for (i = 0; h->h_addr_list[i]; i++) { +    struct in_addr ia; + +    memcpy (&ia, h->h_addr_list[i], 4); +    s = host_connect_attempt(ia, port); +    if(s != -1) +      return s; +  } +  printf ("libmmsh: unable to connect to '%s'.\n", host); +  return -1; +} + +static void put_32 (mmsh_t *this, uint32_t value) { + +  this->scmd[this->scmd_len    ] = value & 0xff; +  this->scmd[this->scmd_len + 1] = (value  >> 8) & 0xff; +  this->scmd[this->scmd_len + 2] = (value  >> 16) & 0xff; +  this->scmd[this->scmd_len + 3] = (value  >> 24) & 0xff; + +  this->scmd_len += 4; +} + +static uint32_t get_64 (uint8_t *buffer, int offset) { + +  uint64_t ret; + +  ret = ((uint64_t)buffer[offset]) | +        ((uint64_t)buffer[offset + 1] << 8) | +        ((uint64_t)buffer[offset + 2] << 16) | +        ((uint64_t)buffer[offset + 2] << 24) | +        ((uint64_t)buffer[offset + 2] << 32) | +        ((uint64_t)buffer[offset + 2] << 40) | +        ((uint64_t)buffer[offset + 2] << 48) | +        ((uint64_t)buffer[offset + 2] << 56); + +  return ret; +} + +static uint32_t get_32 (uint8_t *buffer, int offset) { + +  uint32_t ret; + +  ret = buffer[offset] | +        buffer[offset + 1] << 8 | +        buffer[offset + 2] << 16 | +        buffer[offset + 3] << 24 ; + +  return ret; +} + +static uint16_t get_16 (unsigned char *buffer, int offset) { + +  uint16_t ret; + +  ret = buffer[offset] | +        buffer[offset + 1] << 8; + +  return ret; +} + +static int get_guid (unsigned char *buffer, int offset) { +  int i; +  GUID g; +   +  g.v1 = get_32(buffer, offset); +  g.v2 = get_16(buffer, offset + 4); +  g.v3 = get_16(buffer, offset + 6); +  for(i = 0; i < 8; i++) { +    g.v4[i] = buffer[offset + 8 + i]; +  } +   +  for (i = 1; i < GUID_END; i++) { +    if (!memcmp(&g, &guids[i].guid, sizeof(GUID))) { +#ifdef LOG +      printf ("libmmsh: GUID: %s\n", guids[i].name); +#endif +      return i; +    } +  } +   +  printf ("libmmsh: unknown GUID: 0x%x, 0x%x, 0x%x, " +          "{ 0x%hx, 0x%hx, 0x%hx, 0x%hx, 0x%hx, 0x%hx, 0x%hx, 0x%hx }\n", +          g.v1, g.v2, g.v3, +          g.v4[0], g.v4[1], g.v4[2], g.v4[3], g.v4[4], g.v4[5], g.v4[6], g.v4[7]); +  return GUID_ERROR; +} + +static int send_data (int s, char *buf, int len) { +  int total, timeout; + +  total = 0; timeout = 30; +  while (total < len){  +    int n; + +    n = write (s, &buf[total], len - total); + +#ifdef LOG +    printf ("libmmsh: sending data, %d of %d\n", n, len); +#endif + +    if (n > 0) +      total += n; +    else if (n < 0) { +      if ((timeout>0) && ((errno == EAGAIN) || (errno == EINPROGRESS))) { +	sleep (1); timeout--; +      } else +	return -1; +    } +  } +  return total; +} + +static int send_command (mmsh_t *this, char *cmd)  { +  int length; + +#ifdef LOG +  printf ("libmmsh: send_command:\n%s\n", cmd); +#endif +  length = strlen(cmd); +  if (send_data (this->s, cmd, length) != length) { +    printf ("libmmsh: send error\n"); +    return 0; +  } +  return 1; +} + +static void string_utf16(char *dest, char *src, int len) { +  int i; + +  memset (dest, 0, 1000); + +  for (i = 0; i < len; i++) { +    dest[i * 2] = src[i]; +    dest[i * 2 + 1] = 0; +  } + +  dest[i * 2] = 0; +  dest[i * 2 + 1] = 0; +} + + +static int get_answer (mmsh_t *this) { +  +  int done, len, linenum; + +  done = 0; len = 0; linenum = 0; + +  while (!done) { + +    if (read_timeout (this->s, &(this->buf[len]), 1) <= 0) { +      printf ("libmmsh: alert: end of stream\n"); +      return 0; +    } + +    if (this->buf[len] == '\012') { + +      this->buf[len] = '\0'; +      len--; +       +      if (len >= 0 && this->buf[len] == '\015') { +        this->buf[len] = '\0'; +        len--; +      } + +      linenum++; +       +#ifdef LOG +      printf ("libmmsh: answer: >%s<\n", this->buf); +#endif + +      if (linenum == 1) { +        int httpver, httpsub, httpcode; +        char httpstatus[100]; + +        if (sscanf(this->buf, "HTTP/%d.%d %d %[^\015\012]", &httpver, &httpsub, +                   &httpcode, httpstatus) != 4) { +          return 0; +        } + +        if (httpcode >= 300 && httpcode < 400) { +          printf( _("libmmsh: 3xx redirection not implemented: >%d %s<\n"), +                 httpcode, httpstatus); +          return 0; +        } + +        if (httpcode < 200 || httpcode >= 300) { +          printf( _("libmmsh: http status not 2xx: >%d %s<\n"), +                 httpcode, httpstatus); +          return 0; +        } +      } else { + +        if (!strncasecmp(this->buf, "Location: ", 10)) { +          printf( _("libmmsh: Location redirection not implemented\n")); +          return 0; +        } +         +        if (!strncasecmp(this->buf, "Pragma: features", 16)) { +          if (strstr(this->buf + 16, "seekable")) { +            printf("libmmsh: seekable stream\n"); +            this->stream_type = MMSH_SEEKABLE; +          } else { +            if (strstr(this->buf + 16, "broadcast")) { +              printf("libmmsh: live stream\n"); +              this->stream_type = MMSH_LIVE; +            } else { +              printf("libmmsh: unknown stream type\n"); +              this->stream_type = MMSH_SEEKABLE; /* FIXME ? */ +            } +          } +        } +      } +       +      if (len == -1) { +        done = 1; +      } else { +        len = 0; +      } +    } else { +      len ++; +    } +  } +  return 1; +} + +static int receive (int s, char *buf, size_t count) { + +  ssize_t  len; + +  len = read_timeout (s, buf, count); +  if (len < 0) { +    perror ("libmmsh: read error:"); +    return 0; +  } + +  return len; +} + +static int get_chunk_header (mmsh_t *this) { +  char chunk_header[CHUNK_HEADER_LENGTH]; +  int len; +   +#ifdef LOG     +  printf ("libmmsh: get_chunk\n"); +#endif +  /* chunk header */ +  len = receive (this->s, chunk_header, CHUNK_HEADER_LENGTH); +  if (len != CHUNK_HEADER_LENGTH) { +    printf ("libmmsh: chunk header read failed\n"); +    return 0; +  } +  this->chunk_type       = get_16 (chunk_header, 0); +  this->chunk_length     = get_16 (chunk_header, 2) - 8; +  this->chunk_seq_number = get_32 (chunk_header, 4); +   +  /* display debug infos */ +#ifdef LOG +  switch (this->chunk_type) { +    case CHUNK_TYPE_DATA: +      printf ("libmmsh: chunk type:       CHUNK_TYPE_DATA\n"); +      break; +    case CHUNK_TYPE_END: +      printf ("libmmsh: chunk type:       CHUNK_TYPE_END\n"); +      break; +    case CHUNK_TYPE_ASF_HEADER: +      printf ("libmmsh: chunk type:       CHUNK_TYPE_ASF_HEADER\n"); +      break; +  } +  printf ("libmmsh: chunk length:     %d\n", this->chunk_length); +  printf ("libmmsh: chunk seq_number: %d\n", this->chunk_seq_number); +#endif + +  return 1; +} + +static int get_header (mmsh_t *this) { + +  int len = 0; +  int done = 0; +   +  this->asf_header_len = 0; + +  /* read chunk */ +  while (!done) { +    if (get_chunk_header(this)) { +     +      if (this->chunk_type == CHUNK_TYPE_ASF_HEADER) { +        len = read_timeout (this->s, this->asf_header + this->asf_header_len, +                            this->chunk_length); +        this->asf_header_len += len; +        if (!len) { +          done = 1; +        } +      } else { +        done = 1; +      } +    } else { +      return 0; +    } +  } +   +  if (!len) { +    return 0; +  } else { +    /* read the first data chunk */ +    len = read_timeout (this->s, this->buf, this->chunk_length); +    this->buf_size = this->packet_length; +    return 1; +  } +} + +static void interp_header (mmsh_t *this) { + +  int i; + +  this->packet_length = 0; + +  /* +   * parse asf header +   */ + +  i = 30; +  while (i < this->asf_header_len) { +     +    int guid; +    uint64_t length; + +    guid = get_guid(this->asf_header, i); +    i += 16; +         +    length = get_64(this->asf_header, i); +    i += 8; + +    switch (guid) { +     +      case GUID_ASF_FILE_PROPERTIES: + +        this->packet_length = get_32(this->asf_header, i + 92 - 24); +        this->file_length   = get_32(this->asf_header, i + 40 - 24); +#ifdef LOG     +        printf ("libmmsh: file object, packet length = %d (%d)\n", +                this->packet_length, get_32(this->asf_header, i + 96 - 24)); +#endif +        break; + +      case GUID_ASF_STREAM_PROPERTIES: +        { +          uint16_t stream_id; +          int      type;   + +          guid = get_guid(this->asf_header, i); +          switch (guid) { +            case GUID_ASF_AUDIO_MEDIA: +              type = ASF_STREAM_TYPE_AUDIO; +              this->has_audio = 1; +              break; +     +            case GUID_ASF_VIDEO_MEDIA: +              type = ASF_STREAM_TYPE_VIDEO; +              this->has_video = 1; +              break; +           +            case GUID_ASF_COMMAND_MEDIA: +              type = ASF_STREAM_TYPE_CONTROL; +              break; +         +            default: +              type = ASF_STREAM_TYPE_UNKNOWN; +          } + +          stream_id = get_16(this->asf_header, i + 48); + +#ifdef LOG     +          printf ("libmmsh: stream object, stream id: %d\n", stream_id); +#endif +          this->stream_types[stream_id] = type; +          this->stream_ids[this->num_stream_ids] = stream_id; +          this->num_stream_ids++; +       +        } +        break; + +      case GUID_ASF_STREAM_BITRATE_PROPERTIES: +        { +          uint16_t streams = get_16(this->asf_header, i); +          uint16_t stream_id; +          int j; + +#ifdef LOG     +          printf ("libmmsh: stream bitrate properties\n"); +#endif + +#ifdef LOG     +          printf ("libmmsh: streams %d\n", streams);  +#endif +          for(j = 0; j < streams; j++) { +            stream_id = get_16(this->asf_header, i + 2 + j * 6); +#ifdef LOG     +            printf ("libmmsh: stream id %d\n", stream_id);  +#endif +            this->bitrates[stream_id] = get_32(this->asf_header, i + 4 + j * 6); +            this->bitrates_pos[stream_id] = i + 4 + j * 6; +            printf ("libmmsh: stream id %d, bitrate %d\n", stream_id,  +                    this->bitrates[stream_id]); +          } +        } +        break; +     +      default: +#ifdef LOG     +        printf ("libmmsh: unknown object\n"); +#endif +        break; +    } + +#ifdef LOG     +    printf ("libmmsh: length    : %lld\n", length); +#endif + +    if (length > 24) { +      i += length - 24; +    } +  } +} + +const static char *const mmsh_url_s[] = { "MMS://", "MMSH://", NULL }; + +static int mmsh_valid_url (char* url, const char *const * mms_url) { +  int i = 0; +  int len; +     +  if(!url ) +    return 0; + +  while(mms_url[i]) { +    len = strlen(mms_url[i]); +    if(!strncasecmp(url, mms_url[i], len)) { +      return len; +    } +    i++; +  } +  return 0; +}  + +char* mmsh_connect_common(int *s, int *port, char *url, char **host, char **path, char **file) { +  int     proto_len; +  char   *hostend; +  char   *forport; +  char   *_url; +  char   *_host; +     +  if ((proto_len = mmsh_valid_url(url, mmsh_url_s)) <= 0) { +#ifdef LOG +    printf ("libmms: invalid url >%s< (should be mmsh:// - style)\n", url); +#endif +    return NULL; +  } +   +  /* Create a local copy (alloca()'ed), avoid to corrupt the original URL */ +  xine_strdupa(_url, &url[proto_len]); +   +  _host = _url; +   +  /* extract hostname */ +#ifdef LOG +  printf ("libmmsh: extracting host name \n"); +#endif +  hostend = strchr(_host, '/'); +  if ((!hostend) || (strlen(hostend) <= 1)) { +    printf ("libmmsh: invalid url >%s<, failed to find hostend\n", url); +    return NULL; +  } +   +  *hostend++ = '\0'; + +  /* Is port specified ? */ +  forport = strchr(_host, ':'); +  if(forport) { +    *forport++ = '\0'; +    *port = atoi(forport); +  } +   +  *host = strdup(_host); +   +  if(path) +    *path = &url[proto_len] + (hostend - _url - 1); +   +  if(file) +    *file = strrchr (url, '/'); +   +  /*  +   * try to connect  +   */ +#ifdef LOG +  printf("libmmsh: try to connect to %s on port %d \n", *host, *port); +#endif +  *s = host_connect (*host, *port); +   +  if (*s == -1) { +    printf ("libmmsh: failed to connect '%s'\n", *host); +    free (*host); +    return NULL; +  } + +#ifdef LOG +  printf ("libmmsh: connected\n"); +#endif + +  return url; +} + + +static void report_progress (xine_stream_t *stream, int p) { + +  xine_event_t             event; +  xine_progress_data_t     prg; + +  prg.description = _("Connecting MMS server..."); +  prg.percent = p; +   +  event.type = XINE_EVENT_PROGRESS; +  event.data = &prg; +  event.data_length = sizeof (xine_progress_data_t); +   +  xine_event_send (stream, &event); +} + +mmsh_t *mmsh_connect (xine_stream_t *stream, const char *url_, int bandwidth) { +  mmsh_t *this; +  char  *url     = NULL; +  char  *url1    = NULL; +  char  *path    = NULL; +  char  *file    = NULL; +  char  *host    = NULL; +  int    port; +  int    i, s; +  int    video_stream = 0; +  int    audio_stream = 0; +  int    max_arate    = 0; +  int    min_vrate    = 0; +  int    min_bw_left  = 0; +  int    stream_id; +  int    bandwitdh_left; +  char   stream_selection[9 * 20]; /* 9 chars per stream */ +  +  if (!url_) +    return NULL; + +  report_progress (stream, 0); + +  url = strdup (url_); +  port = MMSH_PORT; +  url1 = mmsh_connect_common(&s, &port, url, &host, &path, &file); + +  if(!url1){ +    free(url); +    return NULL; +  } +   +  report_progress (stream, 10); + +  this = (mmsh_t*) xine_xmalloc (sizeof (mmsh_t)); + +  this->url             = url; +  this->host            = host; +  this->path            = path; +  this->file            = file; +  this->s               = s; +  this->asf_header_len  = 0; +  this->asf_header_read = 0; +  this->num_stream_ids  = 0; +  this->packet_length   = 0; +  this->buf_size        = 0; +  this->buf_read        = 0; +  this->has_audio       = 0; +  this->has_video       = 0; +   +#ifdef LOG +  printf ("libmmsh: url=%s\nlibmmsh:   host=%s\nlibmmsh:   " +          "path=%s\nlibmmsh:   file=%s\n", url, host, path, file); +#endif + +  /* +   * let the negotiations begin... +   */ + +  /* first request */ +  printf("libmmsh: first http request\n"); +  sprintf (this->str, mmsh_FirstRequest, path, host, 1); +   +  if (!send_command (this, this->str)) +    goto fail; +   +  if (!get_answer (this))  +    goto fail; + +     +  get_header(this); +  interp_header(this); +   +  close(this->s); +  report_progress (stream, 20); + +   +  /* choose the best quality for the audio stream */ +  /* i've never seen more than one audio stream */ +  for (i = 0; i < this->num_stream_ids; i++) { +    stream_id = this->stream_ids[i]; +    switch (this->stream_types[stream_id]) { +      case ASF_STREAM_TYPE_AUDIO: +        if (this->bitrates[stream_id] > max_arate) { +          audio_stream = stream_id; +          max_arate = this->bitrates[stream_id]; +        } +        break; +      default: +        break; +    } +  } +   +  /* choose a video stream adapted to the user bandwidth */ +  bandwitdh_left = bandwidth - max_arate; +  if (bandwitdh_left < 0) { +    bandwitdh_left = 0; +  } +#ifdef LOG +  printf("libmmsh: bandwitdh %d, left %d\n", bandwidth, bandwitdh_left); +#endif + +  min_bw_left = bandwitdh_left; +  for (i = 0; i < this->num_stream_ids; i++) { +    stream_id = this->stream_ids[i]; +    switch (this->stream_types[stream_id]) { +      case ASF_STREAM_TYPE_VIDEO: +        if (((bandwitdh_left - this->bitrates[stream_id]) < min_bw_left) && +            (bandwitdh_left >= this->bitrates[stream_id])) { +          video_stream = stream_id; +          min_bw_left = bandwitdh_left - this->bitrates[stream_id]; +        } +        break; +      default: +        break; +    } +  }   + +  /* choose the stream with the lower bitrate */ +  if (!video_stream && this->has_video) { +    for (i = 0; i < this->num_stream_ids; i++) { +      stream_id = this->stream_ids[i]; +      switch (this->stream_types[stream_id]) { +        case ASF_STREAM_TYPE_VIDEO: +          if ((this->bitrates[stream_id] < min_vrate) || +              (!min_vrate)) { +            video_stream = stream_id; +            min_vrate = this->bitrates[stream_id]; +          } +          break; +        default: +          break; +      } +    } +  } +     +  printf("libmmsh: audio stream %d, video stream %d\n", audio_stream, video_stream); + +   +    /* second request */ +  printf("libmmsh: second http request\n"); +  url1 = mmsh_connect_common(&s, &port, url, &host, &path, &file); +  if(!url1){ +    free(url); +    return NULL; +  } +  this->s = s; + +  /* stream selection string */ +  /* The same selection is done with mmst */ +  /* 0 means selected */ +  /* 2 means disabled */ +  for (i = 0; i < this->num_stream_ids; i++) { +    if ((this->stream_ids[i] == audio_stream) || +        (this->stream_ids[i] == video_stream)) { +      sprintf(stream_selection + i * 9, "ffff:%d:0 ", this->stream_ids[i]); +    } else { +#ifdef LOG +      printf("libmms: disabling stream %d\n", this->stream_ids[i]); +#endif +      sprintf(stream_selection + i * 9, "ffff:%d:2 ", this->stream_ids[i]); +    } +  } +   +  switch (this->stream_type) { +    case MMSH_SEEKABLE: +      sprintf (this->str, mmsh_SeekableRequest, path, host, 0, 0, 0, 2, 0, +               this->num_stream_ids, stream_selection); +      break; +    case MMSH_LIVE: +      sprintf (this->str, mmsh_LiveRequest, path, host, 2, +               this->num_stream_ids, stream_selection); +      break; +  } +   +  if (!send_command (this, this->str)) +    goto fail; +   +#ifdef LOG +  printf("libmmsh: before read \n"); +#endif + +  if (!get_answer (this)) +    goto fail; + +  get_header(this); +  interp_header(this); +   +  /* FIXME: find something better */ +  for (i = 0; i < this->num_stream_ids; i++) { +    if ((this->stream_ids[i] != audio_stream) && +        (this->stream_ids[i] != video_stream)) { +      printf("libmms: disabling stream %d\n", this->stream_ids[i]); +      /* forces the asf demuxer to not choose this stream */ +      this->asf_header[this->bitrates_pos[this->stream_ids[i]]]     = 0; +      this->asf_header[this->bitrates_pos[this->stream_ids[i]] + 1] = 0; +      this->asf_header[this->bitrates_pos[this->stream_ids[i]] + 2] = 0; +      this->asf_header[this->bitrates_pos[this->stream_ids[i]] + 3] = 0; +    } +  } + +  report_progress (stream, 100); + +#ifdef LOG +  printf(" mmsh_connect: passed\n" ); +#endif +  return this; + + fail: + +  close (this->s); +  free (url); +  free (this); +  return NULL; + +} + + +static int get_media_packet (mmsh_t *this) { +  int len; + +#ifdef LOG +  printf("this->packet_length: %d\n", this->packet_length); +#endif +   +  if( get_chunk_header(this)) { +    len = read_timeout (this->s, this->buf, this->chunk_length); +   +    if (len) { +      /* implicit padding (with "random" data) */ +      this->buf_size = this->packet_length; +      return 1; +    } else { +      return 0; +    } +  } else { +    return 0; +  } +} + +int mmsh_peek_header (mmsh_t *this, char *data) { + +  memcpy (data, this->asf_header, this->asf_header_len); +  return this->asf_header_len; +} + +int mmsh_read (mmsh_t *this, char *data, int len) { +  int total; + +  total = 0; + +#ifdef LOG +  printf ("libmmsh: mmsh_read: len: %d\n", len); +#endif +   +  while (total < len) { + +    if (this->asf_header_read < this->asf_header_len) { +      int n, bytes_left ; + +      bytes_left = this->asf_header_len - this->asf_header_read ; + +      if ((len-total) < bytes_left) +	n = len-total; +      else +	n = bytes_left; + +      memcpy (&data[total], &this->asf_header[this->asf_header_read], n); + +      this->asf_header_read += n; +      total += n; +    } else { + +      int n, bytes_left ; + +      bytes_left = this->buf_size - this->buf_read; + +      while (!bytes_left) { +	 +	this->buf_read = 0; + +	if (!get_media_packet (this)) { +	  printf ("libmmsh: get_media_packet failed\n"); +	  return total; +	} +	bytes_left = this->buf_size; +      } +       + +      if ((len-total)<bytes_left) +	n = len-total; +      else +	n = bytes_left; + +      memcpy (&data[total], &this->buf[this->buf_read], n); + +      this->buf_read += n; +      total += n; +    } +  } + +  return total; + +} + + +void mmsh_close (mmsh_t *this) { + +  if (this->s >= 0) { +    close(this->s); +  } + +  free (this->host); +  free (this->url); +  free (this); +} + + +uint32_t mmsh_get_length (mmsh_t *this) { +  return this->file_length; +} + diff --git a/src/input/mmsh.h b/src/input/mmsh.h new file mode 100644 index 000000000..6005851a5 --- /dev/null +++ b/src/input/mmsh.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2002 the xine project + *  + * This file is part of xine, a free video player. + *  + * xine is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + *  + * xine is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + *  + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA + * + * $Id: mmsh.h,v 1.1 2003/01/13 01:11:57 tmattern Exp $ + * + * libmmsh public header + */ + +#ifndef HAVE_MMSH_H +#define HAVE_MMSH_H + +#include <inttypes.h> +#include "xine_internal.h" + +typedef struct mmsh_s mmsh_t; + +char*    mmsh_connect_common(int *s ,int *port, char *url, char **host, char **path, char **file); +mmsh_t*   mmsh_connect (xine_stream_t *stream, const char *url_, int bandwidth); + +int      mmsh_read (mmsh_t *this, char *data, int len); +uint32_t mmsh_get_length (mmsh_t *this); +void     mmsh_close (mmsh_t *this); + +int      mmsh_peek_header (mmsh_t *this, char *data); + +#endif | 
