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 |