summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThibaut Mattern <tmattern@users.sourceforge.net>2003-01-13 01:11:57 +0000
committerThibaut Mattern <tmattern@users.sourceforge.net>2003-01-13 01:11:57 +0000
commit676d0a2b8e5bcf46808b76ca7434a7e8f65576ea (patch)
tree87f10fb2f1d6a58d2dc0a21ec7833fcd41e11917
parenteaac42f1becbbcc62317e9bf81e07608aaad62fd (diff)
downloadxine-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.am2
-rw-r--r--src/input/input_mms.c115
-rw-r--r--src/input/mmsh.c1107
-rw-r--r--src/input/mmsh.h42
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