summaryrefslogtreecommitdiff
path: root/plugins/mms/libmms/mms.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/mms/libmms/mms.c')
-rw-r--r--plugins/mms/libmms/mms.c1802
1 files changed, 1802 insertions, 0 deletions
diff --git a/plugins/mms/libmms/mms.c b/plugins/mms/libmms/mms.c
new file mode 100644
index 00000000..7e0d4a07
--- /dev/null
+++ b/plugins/mms/libmms/mms.c
@@ -0,0 +1,1802 @@
+/*
+ * Copyright (C) 2002-2004 the xine project
+ *
+ * This file is part of LibMMS, an MMS protocol handling library.
+ *
+ * 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 ree 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: mms.c,v 1.31 2007/12/11 20:35:01 jwrdegoede Exp $
+ *
+ * MMS over TCP protocol
+ * based on work from major mms
+ * utility functions to handle communication with an mms server
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <unistd.h>
+#include <stdio.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 <iconv.h>
+
+/********** logging **********/
+#define lprintf(...) if (getenv("LIBMMS_DEBUG")) fprintf(stderr, __VA_ARGS__)
+
+#define __MMS_C__
+
+#include "bswap.h"
+#include "mms.h"
+#include "asfheader.h"
+#include "uri.h"
+#include "mms-common.h"
+
+/*
+ * mms specific types
+ */
+
+#define MMST_PORT 1755
+
+#define BUF_SIZE 102400
+
+#define CMD_HEADER_LEN 40
+#define CMD_PREFIX_LEN 8
+#define CMD_BODY_LEN 1024 * 16 /* FIXME: make this dynamic */
+
+#define ASF_HEADER_LEN (8192 * 2)
+
+
+#define MMS_PACKET_ERR 0
+#define MMS_PACKET_COMMAND 1
+#define MMS_PACKET_ASF_HEADER 2
+#define MMS_PACKET_ASF_PACKET 3
+
+#define ASF_HEADER_PACKET_ID_TYPE 2
+#define ASF_MEDIA_PACKET_ID_TYPE 4
+
+
+typedef struct mms_buffer_s mms_buffer_t;
+struct mms_buffer_s {
+ uint8_t *buffer;
+ int pos;
+};
+
+typedef struct mms_packet_header_s mms_packet_header_t;
+struct mms_packet_header_s {
+ uint32_t packet_len;
+ uint8_t flags;
+ uint8_t packet_id_type;
+ uint32_t packet_seq;
+};
+
+struct mms_s {
+
+ int s;
+
+ /* url parsing */
+ GURI *guri;
+ char *url;
+ char *proto;
+ char *host;
+ int port;
+ char *user;
+ char *password;
+ char *uri;
+
+ /* 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 */
+
+ /* receive buffer */
+ uint8_t buf[BUF_SIZE];
+ int buf_size;
+ int buf_read;
+ off_t buf_packet_seq_offset; /* packet sequence offset residing in
+ buf */
+
+ uint8_t asf_header[ASF_HEADER_LEN];
+ uint32_t asf_header_len;
+ uint32_t asf_header_read;
+ int seq_num;
+ int num_stream_ids;
+ mms_stream_t streams[ASF_MAX_NUM_STREAMS];
+ uint8_t packet_id_type;
+ off_t start_packet_seq; /* for live streams != 0, need to keep it around */
+ int need_discont; /* whether we need to set start_packet_seq */
+ uint32_t asf_packet_len;
+ uint64_t file_len;
+ uint64_t time_len; /* playback time in 100 nanosecs (10^-7) */
+ uint64_t preroll;
+ uint64_t asf_num_packets;
+ char guid[37];
+ int bandwidth;
+
+ int has_audio;
+ int has_video;
+ int live_flag;
+ int seekable;
+ off_t current_pos;
+ int eos;
+};
+
+static int fallback_io_select(void *data, int socket, int state, int timeout_msec)
+{
+ fd_set set;
+ struct timeval tv = { timeout_msec / 1000, (timeout_msec % 1000) * 1000};
+ FD_ZERO(&set);
+ FD_SET(socket, &set);
+ return select(1, (state == MMS_IO_READ_READY) ? &set : NULL,
+ (state == MMS_IO_WRITE_READY) ? &set : NULL, NULL, &tv);
+}
+
+static off_t fallback_io_read(void *data, int socket, char *buf, off_t num)
+{
+ off_t len = 0, ret;
+/* lprintf("%d\n", fallback_io_select(data, socket, MMS_IO_READ_READY, 1000)); */
+ errno = 0;
+ while (len < num)
+ {
+ ret = (off_t)read(socket, buf + len, num - len);
+ if(ret == 0)
+ break; /* EOF */
+ if(ret < 0) {
+ lprintf("mms: read error @ len = %lld: %s\n", (long long int) len,
+ strerror(errno));
+ switch(errno)
+ {
+ case EAGAIN:
+ continue;
+ default:
+ /* if already read something, return it, we will fail next time */
+ return len ? len : ret;
+ }
+ }
+ len += ret;
+ }
+ return len;
+}
+
+static off_t fallback_io_write(void *data, int socket, char *buf, off_t num)
+{
+ return (off_t)write(socket, buf, num);
+}
+
+static int fallback_io_tcp_connect(void *data, const char *host, int port)
+{
+
+ struct hostent *h;
+ int i, s;
+
+ h = gethostbyname(host);
+ if (h == NULL) {
+ lprintf("mms: unable to resolve host: %s\n", host);
+ return -1;
+ }
+
+ s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (s == -1) {
+ lprintf("mms: failed to create socket: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if (fcntl (s, F_SETFL, fcntl (s, F_GETFL) & ~O_NONBLOCK) == -1) {
+ lprintf("mms: failed to set socket flags: %s\n", strerror(errno));
+ return -1;
+ }
+
+ for (i = 0; h->h_addr_list[i]; i++) {
+ struct in_addr ia;
+ struct sockaddr_in sin;
+
+ memcpy (&ia, h->h_addr_list[i], 4);
+ 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) {
+ continue;
+ }
+
+ return s;
+ }
+ close(s);
+ return -1;
+}
+
+
+static mms_io_t fallback_io =
+ {
+ &fallback_io_select,
+ NULL,
+ &fallback_io_read,
+ NULL,
+ &fallback_io_write,
+ NULL,
+ &fallback_io_tcp_connect,
+ NULL,
+ };
+
+static mms_io_t default_io = {
+ &fallback_io_select,
+ NULL,
+ &fallback_io_read,
+ NULL,
+ &fallback_io_write,
+ NULL,
+ &fallback_io_tcp_connect,
+ NULL,
+ };
+
+
+#define io_read(io, args...) ((io) ? (io)->read(io->read_data , ## args) : default_io.read(NULL , ## args))
+#define io_write(io, args...) ((io) ? (io)->write(io->write_data , ## args) : default_io.write(NULL , ## args))
+#define io_select(io, args...) ((io) ? (io)->select(io->select_data , ## args) : default_io.select(NULL , ## args))
+#define io_connect(io, args...) ((io) ? (io)->connect(io->connect_data , ## args) : default_io.connect(NULL , ## args))
+
+const mms_io_t* mms_get_default_io_impl()
+{
+ return &default_io;
+}
+
+void mms_set_default_io_impl(const mms_io_t *io)
+{
+ if(io->select)
+ {
+ default_io.select = io->select;
+ default_io.select_data = io->select_data;
+ } else
+ {
+ default_io.select = fallback_io.select;
+ default_io.select_data = fallback_io.select_data;
+ }
+ if(io->read)
+ {
+ default_io.read = io->read;
+ default_io.read_data = io->read_data;
+ } else
+ {
+ default_io.read = fallback_io.read;
+ default_io.read_data = fallback_io.read_data;
+ }
+ if(io->write)
+ {
+ default_io.write = io->write;
+ default_io.write_data = io->write_data;
+ } else
+ {
+ default_io.write = fallback_io.write;
+ default_io.write_data = fallback_io.write_data;
+ }
+ if(io->connect)
+ {
+ default_io.connect = io->connect;
+ default_io.connect_data = io->connect_data;
+ } else
+ {
+ default_io.connect = fallback_io.connect;
+ default_io.connect_data = fallback_io.connect_data;
+ }
+}
+
+static void mms_buffer_init (mms_buffer_t *mms_buffer, uint8_t *buffer) {
+ mms_buffer->buffer = buffer;
+ mms_buffer->pos = 0;
+}
+
+static void mms_buffer_put_8 (mms_buffer_t *mms_buffer, uint8_t value) {
+
+ mms_buffer->buffer[mms_buffer->pos] = value & 0xff;
+
+ mms_buffer->pos += 1;
+}
+
+#if 0
+static void mms_buffer_put_16 (mms_buffer_t *mms_buffer, uint16_t value) {
+
+ mms_buffer->buffer[mms_buffer->pos] = value & 0xff;
+ mms_buffer->buffer[mms_buffer->pos + 1] = (value >> 8) & 0xff;
+
+ mms_buffer->pos += 2;
+}
+#endif
+
+static void mms_buffer_put_32 (mms_buffer_t *mms_buffer, uint32_t value) {
+
+ mms_buffer->buffer[mms_buffer->pos] = value & 0xff;
+ mms_buffer->buffer[mms_buffer->pos + 1] = (value >> 8) & 0xff;
+ mms_buffer->buffer[mms_buffer->pos + 2] = (value >> 16) & 0xff;
+ mms_buffer->buffer[mms_buffer->pos + 3] = (value >> 24) & 0xff;
+
+ mms_buffer->pos += 4;
+}
+
+static int get_guid (unsigned char *buffer, int offset) {
+ int i;
+ GUID g;
+
+ g.Data1 = LE_32(buffer + offset);
+ g.Data2 = LE_16(buffer + offset + 4);
+ g.Data3 = LE_16(buffer + offset + 6);
+ for(i = 0; i < 8; i++) {
+ g.Data4[i] = buffer[offset + 8 + i];
+ }
+
+ for (i = 1; i < GUID_END; i++) {
+ if (!memcmp(&g, &guids[i].guid, sizeof(GUID))) {
+ lprintf("mms: GUID: %s\n", guids[i].name);
+ return i;
+ }
+ }
+
+ lprintf("mms: 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.Data1, g.Data2, g.Data3,
+ g.Data4[0], g.Data4[1], g.Data4[2], g.Data4[3],
+ g.Data4[4], g.Data4[5], g.Data4[6], g.Data4[7]);
+
+ return GUID_ERROR;
+}
+
+
+static void print_command (char *data, int len) {
+
+#ifdef DEBUG
+ int i;
+ int dir = LE_32 (data + 36) >> 16;
+ int comm = LE_32 (data + 36) & 0xFFFF;
+
+ lprintf ("----------------------------------------------\n");
+ if (dir == 3) {
+ lprintf ("send command 0x%02x, %d bytes\n", comm, len);
+ } else {
+ lprintf ("receive command 0x%02x, %d bytes\n", comm, len);
+ }
+ lprintf (" start sequence %08x\n", LE_32 (data + 0));
+ lprintf (" command id %08x\n", LE_32 (data + 4));
+ lprintf (" length %8x \n", LE_32 (data + 8));
+ lprintf (" protocol %08x\n", LE_32 (data + 12));
+ lprintf (" len8 %8x \n", LE_32 (data + 16));
+ lprintf (" sequence # %08x\n", LE_32 (data + 20));
+ lprintf (" len8 (II) %8x \n", LE_32 (data + 32));
+ lprintf (" dir | comm %08x\n", LE_32 (data + 36));
+ if (len >= 4)
+ lprintf (" prefix1 %08x\n", LE_32 (data + 40));
+ if (len >= 8)
+ lprintf (" prefix2 %08x\n", LE_32 (data + 44));
+
+ for (i = (CMD_HEADER_LEN + CMD_PREFIX_LEN); i < (CMD_HEADER_LEN + CMD_PREFIX_LEN + len); i += 1) {
+ unsigned char c = data[i];
+
+ if ((c >= 32) && (c < 128))
+ lprintf ("%c", c);
+ else
+ lprintf (" %02x ", c);
+
+ }
+ if (len > CMD_HEADER_LEN)
+ lprintf ("\n");
+ lprintf ("----------------------------------------------\n");
+#endif
+}
+
+
+
+static int send_command (mms_io_t *io, mms_t *this, int command,
+ uint32_t prefix1, uint32_t prefix2,
+ int length) {
+ int len8;
+ off_t n;
+ mms_buffer_t command_buffer;
+
+ len8 = (length + 7) / 8;
+
+ this->scmd_len = 0;
+
+ mms_buffer_init(&command_buffer, this->scmd);
+ mms_buffer_put_32 (&command_buffer, 0x00000001); /* start sequence */
+ mms_buffer_put_32 (&command_buffer, 0xB00BFACE); /* #-)) */
+ mms_buffer_put_32 (&command_buffer, len8 * 8 + 32);
+ mms_buffer_put_32 (&command_buffer, 0x20534d4d); /* protocol type "MMS " */
+ mms_buffer_put_32 (&command_buffer, len8 + 4);
+ mms_buffer_put_32 (&command_buffer, this->seq_num);
+ this->seq_num++;
+ mms_buffer_put_32 (&command_buffer, 0x0); /* timestamp */
+ mms_buffer_put_32 (&command_buffer, 0x0);
+ mms_buffer_put_32 (&command_buffer, len8 + 2);
+ mms_buffer_put_32 (&command_buffer, 0x00030000 | command); /* dir | command */
+ /* end of the 40 byte command header */
+
+ mms_buffer_put_32 (&command_buffer, prefix1);
+ mms_buffer_put_32 (&command_buffer, prefix2);
+
+ if (length & 7)
+ memset(this->scmd + length + CMD_HEADER_LEN + CMD_PREFIX_LEN, 0, 8 - (length & 7));
+
+ n = io_write(io, this->s, this->scmd, len8 * 8 + CMD_HEADER_LEN + CMD_PREFIX_LEN);
+ if (n != (len8 * 8 + CMD_HEADER_LEN + CMD_PREFIX_LEN)) {
+ return 0;
+ }
+
+ print_command (this->scmd, length);
+
+ return 1;
+}
+
+static int string_utf16(iconv_t url_conv, char *dest, char *src, int dest_len)
+{
+ char *ip = src, *op = dest;
+ size_t ip_len = strlen(src);
+ size_t op_len = dest_len - 2; /* reserve 2 bytes for 0 termination */
+
+ if (iconv(url_conv, &ip, &ip_len, &op, &op_len) == (size_t)-1) {
+ lprintf("mms: Error converting uri to unicode: %s\n", strerror(errno));
+ return 0;
+ }
+
+ /* 0 terminate the string */
+ *op++ = 0;
+ *op++ = 0;
+
+ return op - dest;
+}
+
+/*
+ * return packet type
+ */
+static int get_packet_header (mms_io_t *io, mms_t *this, mms_packet_header_t *header) {
+ size_t len;
+ int packet_type;
+
+ header->packet_len = 0;
+ header->packet_seq = 0;
+ header->flags = 0;
+ header->packet_id_type = 0;
+ len = io_read(io, this->s, this->buf, 8);
+ this->buf_packet_seq_offset = -1;
+ if (len != 8)
+ goto error;
+
+ if (LE_32(this->buf + 4) == 0xb00bface) {
+ /* command packet */
+ header->flags = this->buf[3];
+ len = io_read(io, this->s, this->buf + 8, 4);
+ if (len != 4)
+ goto error;
+
+ header->packet_len = LE_32(this->buf + 8) + 4;
+ if (header->packet_len > BUF_SIZE - 12) {
+ lprintf("mms: get_packet_header error cmd packet length > bufsize\n");
+ header->packet_len = 0;
+ return MMS_PACKET_ERR;
+ }
+ packet_type = MMS_PACKET_COMMAND;
+ } else {
+ header->packet_seq = LE_32(this->buf);
+ header->packet_id_type = this->buf[4];
+ header->flags = this->buf[5];
+ header->packet_len = (LE_16(this->buf + 6) - 8) & 0xffff;
+ if (header->packet_id_type == ASF_HEADER_PACKET_ID_TYPE) {
+ packet_type = MMS_PACKET_ASF_HEADER;
+ } else {
+ packet_type = MMS_PACKET_ASF_PACKET;
+ }
+ }
+
+ return packet_type;
+
+error:
+ lprintf("mms: error reading packet header\n");
+ return MMS_PACKET_ERR;
+}
+
+
+static int get_packet_command (mms_io_t *io, mms_t *this, uint32_t packet_len) {
+
+
+ int command = 0;
+ size_t len;
+
+ len = io_read(io, this->s, this->buf + 12, packet_len) ;
+ //this->buf_packet_seq_offset = -1; // already set in get_packet_header
+ if (len != packet_len) {
+ lprintf("mms: error reading command packet\n");
+ return 0;
+ }
+
+ print_command (this->buf, len);
+
+ /* check protocol type ("MMS ") */
+ if (LE_32(this->buf + 12) != 0x20534D4D) {
+ lprintf("mms: unknown protocol type: %c%c%c%c (0x%08X)\n",
+ this->buf[12], this->buf[13], this->buf[14], this->buf[15],
+ LE_32(this->buf + 12));
+ return 0;
+ }
+
+ command = LE_32 (this->buf + 36) & 0xFFFF;
+ lprintf("mms: received command = %02x, len: %d\n", command, packet_len);
+
+ return command;
+}
+
+static int get_answer (mms_io_t *io, mms_t *this) {
+ int command = 0;
+ mms_packet_header_t header;
+
+ switch (get_packet_header (io, this, &header)) {
+ case MMS_PACKET_ERR:
+ break;
+ case MMS_PACKET_COMMAND:
+ command = get_packet_command (io, this, header.packet_len);
+ if (command == 0)
+ return 0;
+
+ if (command == 0x1b) {
+ if (!send_command (io, this, 0x1b, 0, 0, 0)) {
+ lprintf("mms: error sending ping reply\n");
+ return 0;
+ }
+ /* FIXME: limit recursion */
+ command = get_answer (io, this);
+ }
+ break;
+ case MMS_PACKET_ASF_HEADER:
+ lprintf("mms: unexpected asf header packet\n");
+ break;
+ case MMS_PACKET_ASF_PACKET:
+ lprintf("mms: unexpected asf packet\n");
+ break;
+ }
+
+ return command;
+}
+
+
+static int get_asf_header (mms_io_t *io, mms_t *this) {
+
+ off_t len;
+ int stop = 0;
+
+ this->asf_header_read = 0;
+ this->asf_header_len = 0;
+
+ while (!stop) {
+ mms_packet_header_t header;
+ int command;
+
+ switch (get_packet_header (io, this, &header)) {
+ case MMS_PACKET_ERR:
+ return 0;
+ case MMS_PACKET_COMMAND:
+ command = get_packet_command (io, this, header.packet_len);
+ if (command == 0)
+ return 0;
+
+ if (command == 0x1b) {
+ if (!send_command (io, this, 0x1b, 0, 0, 0)) {
+ lprintf("mms: error sending ping reply\n");
+ return 0;
+ }
+ command = get_answer (io, this);
+ } else {
+ lprintf("mms: unexpected command packet\n");
+ }
+ break;
+ case MMS_PACKET_ASF_HEADER:
+ case MMS_PACKET_ASF_PACKET:
+ if (header.packet_len + this->asf_header_len > ASF_HEADER_LEN) {
+ lprintf("mms: asf packet too large: %d\n",
+ header.packet_len + this->asf_header_len);
+ return 0;
+ }
+ len = io_read(io, this->s,
+ this->asf_header + this->asf_header_len, header.packet_len);
+ if (len != header.packet_len) {
+ lprintf("mms: error reading asf header\n");
+ return 0;
+ }
+ this->asf_header_len += header.packet_len;
+ lprintf("mms: header flags: %d\n", header.flags);
+ if ((header.flags == 0X08) || (header.flags == 0X0C))
+ stop = 1;
+ break;
+ }
+ }
+ return 1;
+}
+
+static void interp_stream_properties(mms_t *this, int i)
+{
+ uint16_t flags;
+ uint16_t stream_id;
+ int type;
+ int encrypted;
+ int guid;
+
+ 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:
+ case GUID_ASF_JFIF_MEDIA:
+ case GUID_ASF_DEGRADABLE_JPEG_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;
+ }
+
+ flags = LE_16(this->asf_header + i + 48);
+ stream_id = flags & 0x7F;
+ encrypted = flags >> 15;
+
+ lprintf("mms: stream object, stream id: %d, type: %d, encrypted: %d\n",
+ stream_id, type, encrypted);
+
+ if (this->num_stream_ids < ASF_MAX_NUM_STREAMS) {
+ this->streams[this->num_stream_ids].stream_type = type;
+ this->streams[this->num_stream_ids].stream_id = stream_id;
+ this->num_stream_ids++;
+ } else {
+ lprintf("mms: too many streams, skipping\n");
+ }
+}
+
+static void interp_asf_header (mms_t *this) {
+
+ int i;
+
+ this->asf_packet_len = 0;
+ this->num_stream_ids = 0;
+ this->asf_num_packets = 0;
+ /*
+ * parse header
+ */
+
+ i = 30;
+ while ((i + 24) <= this->asf_header_len) {
+
+ int guid;
+ uint64_t length;
+
+ guid = get_guid(this->asf_header, i);
+ length = LE_64(this->asf_header + i + 16);
+
+ if ((i + length) > this->asf_header_len) return;
+
+ switch (guid) {
+
+ case GUID_ASF_FILE_PROPERTIES:
+
+ this->asf_packet_len = LE_32(this->asf_header + i + 92);
+ if (this->asf_packet_len > BUF_SIZE) {
+ lprintf("mms: asf packet len too large: %d\n", this->asf_packet_len);
+ this->asf_packet_len = 0;
+ break;
+ }
+ this->file_len = LE_64(this->asf_header + i + 40);
+ this->time_len = LE_64(this->asf_header + i + 64);
+ //this->time_len = LE_64(this->asf_header + i + 72);
+ this->preroll = LE_64(this->asf_header + i + 80);
+ lprintf("mms: file object, packet length = %d (%d)\n",
+ this->asf_packet_len, LE_32(this->asf_header + i + 96));
+ break;
+
+ case GUID_ASF_STREAM_PROPERTIES:
+ interp_stream_properties(this, i + 24);
+ break;
+
+ case GUID_ASF_STREAM_BITRATE_PROPERTIES:
+ {
+ uint16_t streams = LE_16(this->asf_header + i + 24);
+ uint16_t stream_id;
+ int j;
+
+ for(j = 0; j < streams; j++) {
+ int stream_index;
+ stream_id = LE_16(this->asf_header + i + 24 + 2 + j * 6);
+ for(stream_index = 0; stream_index < this->num_stream_ids; stream_index++) {
+ if (this->streams[stream_index].stream_id == stream_id)
+ break;
+ }
+ if (stream_index < this->num_stream_ids) {
+ this->streams[stream_index].bitrate = LE_32(this->asf_header + i + 24 + 4 + j * 6);
+ this->streams[stream_index].bitrate_pos = i + 24 + 4 + j * 6;
+ lprintf("mms: stream id %d, bitrate %d\n", stream_id,
+ this->streams[stream_index].bitrate);
+ } else
+ lprintf ("mms: unknown stream id %d in bitrate properties\n",
+ stream_id);
+ }
+ }
+ break;
+
+ case GUID_ASF_HEADER_EXTENSION:
+ {
+ if ((24 + 18 + 4) > length)
+ break;
+
+ int size = LE_32(this->asf_header + i + 24 + 18);
+ int j = 24 + 18 + 4;
+ int l;
+ lprintf("mms: Extension header data size: %d\n", size);
+
+ while ((j + 24) <= length) {
+ guid = get_guid(this->asf_header, i + j);
+ l = LE_64(this->asf_header + i + j + 16);
+
+ if ((j + l) > length)
+ break;
+
+ if (guid == GUID_ASF_EXTENDED_STREAM_PROPERTIES &&
+ (24 + 64) <= l) {
+ int stream_no = LE_16(this->asf_header + i + j + 24 + 48);
+ int name_count = LE_16(this->asf_header + i + j + 24 + 60);
+ int ext_count = LE_16(this->asf_header + i + j + 24 + 62);
+ int ext_j = 24 + 64;
+ int x;
+
+ lprintf("mms: l: %d\n", l);
+ lprintf("mms: Stream No: %d\n", stream_no);
+ lprintf("mms: ext_count: %d\n", ext_count);
+
+ // Loop through the number of stream names
+ for (x = 0; x < name_count && (ext_j + 4) <= l; x++) {
+ int lang_id_index;
+ int stream_name_len;
+
+ lang_id_index = LE_16(this->asf_header + i + j + ext_j);
+ ext_j += 2;
+
+ stream_name_len = LE_16(this->asf_header + i + j + ext_j);
+ ext_j += stream_name_len + 2;
+
+ lprintf("mms: Language id index: %d\n", lang_id_index);
+ lprintf("mms: Stream name Len: %d\n", stream_name_len);
+ }
+
+ // Loop through the number of extension system info
+ for (x = 0; x < ext_count && (ext_j + 22) <= l; x++) {
+ ext_j += 18;
+ int len = LE_16(this->asf_header + i + j + ext_j);
+ ext_j += 4 + len;
+ }
+
+ lprintf("mms: ext_j: %d\n", ext_j);
+ // Finally, we arrive at the interesting point: The optional Stream Property Object
+ if ((ext_j + 24) <= l) {
+ guid = get_guid(this->asf_header, i + j + ext_j);
+ int len = LE_64(this->asf_header + i + j + ext_j + 16);
+ if (guid == GUID_ASF_STREAM_PROPERTIES &&
+ (ext_j + len) <= l) {
+ interp_stream_properties(this, i + j + ext_j + 24);
+ }
+ } else {
+ lprintf("mms: Sorry, field not long enough\n");
+ }
+ }
+ j += l;
+ }
+ }
+ break;
+
+ case GUID_ASF_DATA:
+ this->asf_num_packets = LE_64(this->asf_header + i + 40 - 24);
+ break;
+ }
+
+ lprintf("mms: length: %llu\n", (unsigned long long)length);
+ i += length;
+ }
+}
+
+const static char *const mmst_proto_s[] = { "mms", "mmst", NULL };
+
+static int mmst_valid_proto (char *proto) {
+ int i = 0;
+
+ if (!proto)
+ return 0;
+
+ while(mmst_proto_s[i]) {
+ if (!strcasecmp(proto, mmst_proto_s[i])) {
+ return 1;
+ }
+ i++;
+ }
+ return 0;
+}
+
+/*
+ * returns 1 on error
+ */
+static int mms_tcp_connect(mms_io_t *io, mms_t *this) {
+ if (!this->port) this->port = MMST_PORT;
+
+ /*
+ * try to connect
+ */
+ lprintf("mms: trying to connect to %s on port %d\n", this->host, this->port);
+ this->s = io_connect(io, this->host, this->port);
+ if (this->s == -1) {
+ lprintf("mms: failed to connect to %s\n", this->host);
+ return 1;
+ }
+
+ lprintf("mms: connected\n");
+ return 0;
+}
+
+static void mms_gen_guid(char guid[]) {
+ static char digit[16] = "0123456789ABCDEF";
+ int i = 0;
+
+ srand(time(NULL));
+ for (i = 0; i < 36; i++) {
+ guid[i] = digit[(int) ((16.0*rand())/(RAND_MAX+1.0))];
+ }
+ guid[8] = '-'; guid[13] = '-'; guid[18] = '-'; guid[23] = '-';
+ guid[36] = '\0';
+}
+
+const char *status_to_string(int status)
+{
+ switch (status) {
+ case 0x80070003:
+ return "Path not found";
+ case 0x80070005:
+ return "Access Denied";
+ default:
+ return "Unknown";
+ }
+}
+
+/*
+ * return 0 on error
+ */
+int static mms_choose_best_streams(mms_io_t *io, mms_t *this) {
+ int i;
+ int video_stream = -1;
+ int audio_stream = -1;
+ int max_arate = 0;
+ int min_vrate = 0;
+ int min_bw_left = 0;
+ int bandwitdh_left;
+ int res;
+
+ /* command 0x33 */
+ /* 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++) {
+ switch (this->streams[i].stream_type) {
+ case ASF_STREAM_TYPE_AUDIO:
+ if (this->streams[i].bitrate > max_arate) {
+ audio_stream = this->streams[i].stream_id;
+ max_arate = this->streams[i].bitrate;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* choose a video stream adapted to the user bandwidth */
+ bandwitdh_left = this->bandwidth - max_arate;
+ if (bandwitdh_left < 0) {
+ bandwitdh_left = 0;
+ }
+ lprintf("mms: bandwidth %d, left %d\n", this->bandwidth, bandwitdh_left);
+
+ min_bw_left = bandwitdh_left;
+ for (i = 0; i < this->num_stream_ids; i++) {
+ switch (this->streams[i].stream_type) {
+ case ASF_STREAM_TYPE_VIDEO:
+ if (((bandwitdh_left - this->streams[i].bitrate) < min_bw_left) &&
+ (bandwitdh_left >= this->streams[i].bitrate)) {
+ video_stream = this->streams[i].stream_id;
+ min_bw_left = bandwitdh_left - this->streams[i].bitrate;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* choose the lower bitrate of */
+ if (video_stream == -1 && this->has_video) {
+ for (i = 0; i < this->num_stream_ids; i++) {
+ switch (this->streams[i].stream_type) {
+ case ASF_STREAM_TYPE_VIDEO:
+ if ((this->streams[i].bitrate < min_vrate) ||
+ (!min_vrate)) {
+ video_stream = this->streams[i].stream_id;
+ min_vrate = this->streams[i].bitrate;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ lprintf("mms: selected streams: audio %d, video %d\n", audio_stream, video_stream);
+ memset (this->scmd_body, 0, 40);
+ for (i = 1; i < this->num_stream_ids; i++) {
+ this->scmd_body [ (i - 1) * 6 + 2 ] = 0xFF;
+ this->scmd_body [ (i - 1) * 6 + 3 ] = 0xFF;
+ this->scmd_body [ (i - 1) * 6 + 4 ] = this->streams[i].stream_id ;
+ this->scmd_body [ (i - 1) * 6 + 5 ] = this->streams[i].stream_id >> 8;
+ if ((this->streams[i].stream_id == audio_stream) ||
+ (this->streams[i].stream_id == video_stream)) {
+ this->scmd_body [ (i - 1) * 6 + 6 ] = 0x00;
+ this->scmd_body [ (i - 1) * 6 + 7 ] = 0x00;
+ } else {
+ lprintf("mms: disabling stream %d\n", this->streams[i].stream_id);
+ this->scmd_body [ (i - 1) * 6 + 6 ] = 0x02;
+ this->scmd_body [ (i - 1) * 6 + 7 ] = 0x00;
+
+ /* forces the asf demuxer to not choose this stream */
+ if (this->streams[i].bitrate_pos) {
+ if (this->streams[i].bitrate_pos+3 < ASF_HEADER_LEN) {
+ this->asf_header[this->streams[i].bitrate_pos ] = 0;
+ this->asf_header[this->streams[i].bitrate_pos + 1] = 0;
+ this->asf_header[this->streams[i].bitrate_pos + 2] = 0;
+ this->asf_header[this->streams[i].bitrate_pos + 3] = 0;
+ } else {
+ lprintf("mms: attempt to write beyond asf header limit\n");
+ }
+ }
+ }
+ }
+
+ lprintf("mms: send command 0x33\n");
+ if (!send_command (io, this, 0x33, this->num_stream_ids,
+ 0xFFFF | this->streams[0].stream_id << 16,
+ this->num_stream_ids * 6 + 2)) {
+ lprintf("mms: mms_choose_best_streams failed\n");
+ return 0;
+ }
+
+ if ((res = get_answer (io, this)) != 0x21) {
+ lprintf("mms: unexpected response: %02x (0x21)\n", res);
+ return 0;
+ }
+
+ res = LE_32(this->buf + 40);
+ if (res != 0) {
+ lprintf("mms: error answer 0x21 status: %08x (%s)\n",
+ res, status_to_string(res));
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+ * TODO: error messages
+ * network timing request
+ */
+/* FIXME: got somewhat broken during xine_stream_t->(void*) conversion */
+mms_t *mms_connect (mms_io_t *io, void *data, const char *url, int bandwidth) {
+ iconv_t url_conv = (iconv_t)-1;
+ mms_t *this;
+ int res;
+ uint32_t openid;
+ mms_buffer_t command_buffer;
+
+ if (!url)
+ return NULL;
+
+ /* FIXME: needs proper error-signalling work */
+ this = (mms_t*) malloc (sizeof (mms_t));
+
+ this->url = strdup (url);
+ this->s = -1;
+ this->seq_num = 0;
+ this->scmd_body = this->scmd + CMD_HEADER_LEN + CMD_PREFIX_LEN;
+ this->asf_header_len = 0;
+ this->asf_header_read = 0;
+ this->num_stream_ids = 0;
+ this->asf_packet_len = 0;
+ this->start_packet_seq= 0;
+ this->need_discont = 1;
+ this->buf_size = 0;
+ this->buf_read = 0;
+ this->buf_packet_seq_offset = -1;
+ this->has_audio = 0;
+ this->has_video = 0;
+ this->bandwidth = bandwidth;
+ this->current_pos = 0;
+ this->eos = 0;
+
+ this->guri = gnet_uri_new(this->url);
+ if(!this->guri) {
+ lprintf("mms: invalid url\n");
+ goto fail;
+ }
+
+ /* MMS wants unescaped (so not percent coded) strings */
+ gnet_uri_unescape(this->guri);
+
+ this->proto = this->guri->scheme;
+ this->user = this->guri->user;
+ this->host = this->guri->hostname;
+ this->port = this->guri->port;
+ this->password = this->guri->passwd;
+ this->uri = gnet_mms_helper(this->guri, 0);
+
+ if(!this->uri)
+ goto fail;
+
+ if (!mmst_valid_proto(this->proto)) {
+ lprintf("mms: unsupported protocol: %s\n", this->proto);
+ goto fail;
+ }
+
+ if (mms_tcp_connect(io, this)) {
+ goto fail;
+ }
+
+ url_conv = iconv_open("UTF-16LE", "UTF-8");
+ if (url_conv == (iconv_t)-1) {
+ lprintf("mms: could not get iconv handle to convert url to unicode\n");
+ goto fail;
+ }
+
+ /*
+ * let the negotiations begin...
+ */
+
+ /* command 0x1 */
+ lprintf("mms: send command 0x01\n");
+ mms_buffer_init(&command_buffer, this->scmd_body);
+ mms_buffer_put_32 (&command_buffer, 0x0003001C);
+ mms_gen_guid(this->guid);
+ sprintf(this->str, "NSPlayer/7.0.0.1956; {%s}; Host: %s", this->guid,
+ this->host);
+ res = string_utf16(url_conv, this->scmd_body + command_buffer.pos, this->str,
+ CMD_BODY_LEN - command_buffer.pos);
+ if(!res)
+ goto fail;
+
+ if (!send_command(io, this, 1, 0, 0x0004000b, command_buffer.pos + res)) {
+ lprintf("mms: failed to send command 0x01\n");
+ goto fail;
+ }
+
+ if ((res = get_answer (io, this)) != 0x01) {
+ lprintf("mms: unexpected response: %02x (0x01)\n", res);
+ goto fail;
+ }
+
+ res = LE_32(this->buf + 40);
+ if (res != 0) {
+ lprintf("mms: error answer 0x01 status: %08x (%s)\n",
+ res, status_to_string(res));
+ goto fail;
+ }
+
+ /* TODO: insert network timing request here */
+ /* command 0x2 */
+ lprintf("mms: send command 0x02\n");
+ mms_buffer_init(&command_buffer, this->scmd_body);
+ mms_buffer_put_32 (&command_buffer, 0x00000000);
+ mms_buffer_put_32 (&command_buffer, 0x00989680);
+ mms_buffer_put_32 (&command_buffer, 0x00000002);
+ res = string_utf16(url_conv, this->scmd_body + command_buffer.pos,
+ "\\\\192.168.0.129\\TCP\\1037",
+ CMD_BODY_LEN - command_buffer.pos);
+ if(!res)
+ goto fail;
+
+ if (!send_command(io, this, 2, 0, 0xffffffff, command_buffer.pos + res)) {
+ lprintf("mms: failed to send command 0x02\n");
+ goto fail;
+ }
+
+ switch (res = get_answer (io, this)) {
+ case 0x02:
+ /* protocol accepted */
+ break;
+ case 0x03:
+ lprintf("mms: protocol failed\n");
+ goto fail;
+ default:
+ lprintf("mms: unexpected response: %02x (0x02 or 0x03)\n", res);
+ goto fail;
+ }
+
+ res = LE_32(this->buf + 40);
+ if (res != 0) {
+ lprintf("mms: error answer 0x02 status: %08x (%s)\n",
+ res, status_to_string(res));
+ goto fail;
+ }
+
+ /* command 0x5 */
+ {
+ mms_buffer_t command_buffer;
+
+ lprintf("mms: send command 0x05\n");
+ mms_buffer_init(&command_buffer, this->scmd_body);
+ mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */
+ mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */
+
+ res = string_utf16(url_conv, this->scmd_body + command_buffer.pos,
+ this->uri, CMD_BODY_LEN - command_buffer.pos);
+ if(!res)
+ goto fail;
+
+ if (!send_command(io, this, 5, 1, 0, command_buffer.pos + res)) {
+ lprintf("mms: failed to send command 0x05\n");
+ goto fail;
+ }
+ }
+
+ switch (res = get_answer (io, this)) {
+ case 0x06:
+ {
+ int xx, yy;
+ /* no authentication required */
+ openid = LE_32(this->buf + 48);
+
+ /* Warning: sdp is not right here */
+ xx = this->buf[62];
+ yy = this->buf[63];
+ this->live_flag = ((xx == 0) && ((yy & 0xf) == 2));
+ this->seekable = !this->live_flag;
+ lprintf("mms: openid=%d, live: live_flag=%d, xx=%d, yy=%d\n", openid, this->live_flag, xx, yy);
+ }
+ break;
+ case 0x1A:
+ /* authentication request, not yet supported */
+ lprintf("mms: authentication request, not yet supported\n");
+ goto fail;
+ break;
+ default:
+ lprintf("mms: unexpected response: %02x (0x06 or 0x1A)\n", res);
+ goto fail;
+ }
+
+ res = LE_32(this->buf + 40);
+ if (res != 0) {
+ lprintf("mms: error answer 0x06 status: %08x (%s)\n",
+ res, status_to_string(res));
+ goto fail;
+ }
+
+ /* command 0x15 */
+ lprintf("mms: send command 0x15\n");
+ {
+ mms_buffer_t command_buffer;
+ mms_buffer_init(&command_buffer, this->scmd_body);
+ mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */
+ mms_buffer_put_32 (&command_buffer, 0x00008000); /* ?? */
+ mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF); /* ?? */
+ mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */
+ mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */
+ mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */
+ mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */
+ mms_buffer_put_32 (&command_buffer, 0x40AC2000); /* ?? */
+ mms_buffer_put_32 (&command_buffer, ASF_HEADER_PACKET_ID_TYPE); /* Header Packet ID type */
+ mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */
+ if (!send_command (io, this, 0x15, openid, 0, command_buffer.pos)) {
+ lprintf("mms: failed to send command 0x15\n");
+ goto fail;
+ }
+ }
+
+ if ((res = get_answer (io, this)) != 0x11) {
+ lprintf("mms: unexpected response: %02x (0x11)\n", res);
+ goto fail;
+ }
+
+ res = LE_32(this->buf + 40);
+ if (res != 0) {
+ lprintf("mms: error answer 0x11 status: %08x (%s)\n",
+ res, status_to_string(res));
+ goto fail;
+ }
+
+ this->num_stream_ids = 0;
+
+ if (!get_asf_header (io, this))
+ goto fail;
+
+ interp_asf_header (this);
+ if (!this->asf_packet_len || !this->num_stream_ids)
+ goto fail;
+
+ if (!mms_choose_best_streams(io, this)) {
+ lprintf("mms: mms_choose_best_streams failed\n");
+ goto fail;
+ }
+
+ /* command 0x07 */
+ this->packet_id_type = ASF_MEDIA_PACKET_ID_TYPE;
+ {
+ mms_buffer_t command_buffer;
+ mms_buffer_init(&command_buffer, this->scmd_body);
+ mms_buffer_put_32 (&command_buffer, 0x00000000); /* 64 byte float timestamp */
+ mms_buffer_put_32 (&command_buffer, 0x00000000);
+ mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF); /* ?? */
+ mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF); /* first packet sequence */
+ mms_buffer_put_8 (&command_buffer, 0xFF); /* max stream time limit (3 bytes) */
+ mms_buffer_put_8 (&command_buffer, 0xFF);
+ mms_buffer_put_8 (&command_buffer, 0xFF);
+ mms_buffer_put_8 (&command_buffer, 0x00); /* stream time limit flag */
+ mms_buffer_put_32 (&command_buffer, this->packet_id_type); /* asf media packet id type */
+ if (!send_command (io, this, 0x07, 1, 0x0001FFFF, command_buffer.pos)) {
+ lprintf("mms: failed to send command 0x07\n");
+ goto fail;
+ }
+ }
+
+ iconv_close(url_conv);
+ lprintf("mms: connect: passed\n");
+
+ return this;
+
+fail:
+ if (this->s != -1)
+ close (this->s);
+ if (this->url)
+ free(this->url);
+ if (this->guri)
+ gnet_uri_delete(this->guri);
+ if (this->uri)
+ free(this->uri);
+ if (url_conv != (iconv_t)-1)
+ iconv_close(url_conv);
+
+ free (this);
+ return NULL;
+}
+
+static int get_media_packet (mms_io_t *io, mms_t *this) {
+ mms_packet_header_t header;
+ off_t len;
+
+ switch (get_packet_header (io, this, &header)) {
+ case MMS_PACKET_ERR:
+ return 0;
+
+ case MMS_PACKET_COMMAND:
+ {
+ int command;
+ command = get_packet_command (io, this, header.packet_len);
+
+ switch (command) {
+ case 0:
+ return 0;
+
+ case 0x1e:
+ {
+ uint32_t error_code;
+
+ /* Warning: sdp is incomplete. Do not stop if error_code==1 */
+ error_code = LE_32(this->buf + CMD_HEADER_LEN);
+ lprintf("mms: End of the current stream. Continue=%d\n", error_code);
+
+ if (error_code == 0) {
+ this->eos = 1;
+ return 0;
+ }
+
+ }
+ break;
+
+ case 0x20:
+ {
+ lprintf("mms: new stream.\n");
+ /* asf header */
+ if (!get_asf_header (io, this)) {
+ lprintf("mms: failed to read new ASF header\n");
+ return 0;
+ }
+
+ interp_asf_header (this);
+ if (!this->asf_packet_len || !this->num_stream_ids)
+ return 0;
+
+ if (!mms_choose_best_streams(io, this))
+ return 0;
+
+ /* send command 0x07 */
+ /* TODO: ugly */
+ /* command 0x07 */
+ {
+ mms_buffer_t command_buffer;
+ mms_buffer_init(&command_buffer, this->scmd_body);
+ mms_buffer_put_32 (&command_buffer, 0x00000000); /* 64 byte float timestamp */
+ mms_buffer_put_32 (&command_buffer, 0x00000000);
+ mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF); /* ?? */
+ mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF); /* first packet sequence */
+ mms_buffer_put_8 (&command_buffer, 0xFF); /* max stream time limit (3 bytes) */
+ mms_buffer_put_8 (&command_buffer, 0xFF);
+ mms_buffer_put_8 (&command_buffer, 0xFF);
+ mms_buffer_put_8 (&command_buffer, 0x00); /* stream time limit flag */
+ mms_buffer_put_32 (&command_buffer, ASF_MEDIA_PACKET_ID_TYPE); /* asf media packet id type */
+ if (!send_command (io, this, 0x07, 1, 0x0001FFFF, command_buffer.pos)) {
+ lprintf("mms: failed to send command 0x07\n");
+ return 0;
+ }
+ }
+ this->current_pos = 0;
+
+ /* I don't know if this ever happens with none live (and thus
+ seekable streams), but I do know that if it happens all bets
+ with regards to seeking are off */
+ this->seekable = 0;
+ }
+ break;
+
+ case 0x1b:
+ {
+ if (!send_command (io, this, 0x1b, 0, 0, 0)) {
+ lprintf("mms: error sending ping reply\n");
+ return 0;
+ }
+ }
+ break;
+
+ case 0x05:
+ break;
+
+ default:
+ lprintf("mms: unexpected mms command %02x\n", command);
+ }
+ this->buf_size = 0;
+ }
+ break;
+
+ case MMS_PACKET_ASF_HEADER:
+ lprintf("mms: unexpected asf header packet\n");
+ this->buf_size = 0;
+ break;
+
+ case MMS_PACKET_ASF_PACKET:
+ {
+ /* media packet */
+
+ /* FIXME: probably needs some more sophisticated logic, but
+ until we do seeking, this should work */
+ if(this->need_discont &&
+ header.packet_id_type == ASF_MEDIA_PACKET_ID_TYPE)
+ {
+ this->need_discont = 0;
+ this->start_packet_seq = header.packet_seq;
+ }
+
+ if (header.packet_len > this->asf_packet_len) {
+ lprintf("mms: invalid asf packet len: %d bytes\n", header.packet_len);
+ return 0;
+ }
+
+ /* simulate a seek */
+ this->current_pos = (off_t)this->asf_header_len +
+ ((off_t)header.packet_seq - this->start_packet_seq) * (off_t)this->asf_packet_len;
+
+ len = io_read(io, this->s, this->buf, header.packet_len);
+ if (len != header.packet_len) {
+ lprintf("mms: error reading asf packet\n");
+ return 0;
+ }
+
+ /* explicit padding with 0 */
+ memset(this->buf + header.packet_len, 0, this->asf_packet_len - header.packet_len);
+ if (header.packet_id_type == this->packet_id_type) {
+ this->buf_size = this->asf_packet_len;
+ this->buf_packet_seq_offset =
+ header.packet_seq - this->start_packet_seq;
+ } else {
+ this->buf_size = 0;
+ // Don't set this packet sequence for reuse in seek(), since the
+ // subsequence packet may be discontinued.
+ //this->buf_packet_seq_offset = header.packet_seq;
+ // already set to -1 in get_packet_header
+ //this->buf_packet_seq_offset = -1;
+ }
+ }
+ break;
+ }
+
+ return 1;
+}
+
+
+int mms_peek_header (mms_t *this, char *data, int maxsize) {
+
+ int len;
+
+ len = (this->asf_header_len < maxsize) ? this->asf_header_len : maxsize;
+
+ memcpy(data, this->asf_header, len);
+ return len;
+}
+
+int mms_read (mms_io_t *io, mms_t *this, char *data, int len) {
+ int total;
+
+ total = 0;
+ while (total < len && !this->eos) {
+
+ 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;
+ this->current_pos += n;
+ } else {
+
+ int n, bytes_left;
+
+ bytes_left = this->buf_size - this->buf_read;
+ if (bytes_left == 0) {
+ this->buf_size = this->buf_read = 0;
+ if (!get_media_packet (io, this)) {
+ lprintf("mms: 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;
+ this->current_pos += n;
+ }
+ }
+ return total;
+}
+
+// To be inline function?
+static int mms_request_data_packet (mms_io_t *io, mms_t *this,
+ double time_sec, unsigned long first_packet, unsigned long time_msec_limit) {
+ /* command 0x07 */
+ {
+ mms_buffer_t command_buffer;
+ //mms_buffer_init(&command_buffer, this->scmd_body);
+ //mms_buffer_put_32 (&command_buffer, 0x00000000); /* 64 byte float timestamp */
+ //mms_buffer_put_32 (&command_buffer, 0x00000000);
+ memcpy(this->scmd_body, &time_sec, 8);
+ mms_buffer_init(&command_buffer, this->scmd_body+8);
+ mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF); /* ?? */
+ //mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF); /* first packet sequence */
+ mms_buffer_put_32 (&command_buffer, first_packet); /* first packet sequence */
+ //mms_buffer_put_8 (&command_buffer, 0xFF); /* max stream time limit (3 bytes) */
+ //mms_buffer_put_8 (&command_buffer, 0xFF);
+ //mms_buffer_put_8 (&command_buffer, 0xFF);
+ //mms_buffer_put_8 (&command_buffer, 0x00); /* stream time limit flag */
+ mms_buffer_put_32 (&command_buffer, time_msec_limit & 0x00FFFFFF);/* max stream time limit (3 bytes) */
+ mms_buffer_put_32 (&command_buffer, this->packet_id_type); /* asf media packet id type */
+ if (!send_command (io, this, 0x07, 1, 0x0001FFFF, 8+command_buffer.pos)) {
+ lprintf("mms: failed to send command 0x07\n");
+ return 0;
+ }
+ }
+ /* TODO: adjust current_pos, considering asf_header_read */
+ return 1;
+}
+
+int mms_request_time_seek (mms_io_t *io, mms_t *this, double time_sec) {
+ if (++this->packet_id_type <= ASF_MEDIA_PACKET_ID_TYPE)
+ this->packet_id_type = ASF_MEDIA_PACKET_ID_TYPE+1;
+ //return mms_request_data_packet (io, this, time_sec, 0xFFFFFFFF, 0x00FFFFFF);
+ // also adjust time by preroll
+ return mms_request_data_packet (io, this,
+ time_sec+(double)(this->preroll)/1000,
+ 0xFFFFFFFF, 0x00FFFFFF);
+}
+
+// set current_pos to the first byte of the requested packet by peeking at
+// the first packet.
+// To be inline function?
+static int peek_and_set_pos (mms_io_t *io, mms_t *this) {
+ uint8_t saved_buf[BUF_SIZE];
+ int saved_buf_size;
+ off_t saved_buf_packet_seq_offset;
+ // save buf and buf_size that may be changed in get_media_packet()
+ memcpy(saved_buf, this->buf, this->buf_size);
+ saved_buf_size = this->buf_size;
+ saved_buf_packet_seq_offset = this->buf_packet_seq_offset;
+ //this->buf_size = this->buf_read = 0; // reset buf, only if success peeking
+ this->buf_size = 0;
+ while (!this->eos) {
+ // get_media_packet() will set current_pos if data packet is read.
+ if (!get_media_packet (io, this)) {
+ lprintf("mms: get_media_packet failed\n");
+ // restore buf and buf_size that may be changed in get_media_packet()
+ memcpy(this->buf, saved_buf, saved_buf_size);
+ this->buf_size = saved_buf_size;
+ this->buf_packet_seq_offset = saved_buf_packet_seq_offset;
+ return 0;
+ }
+ if (this->buf_size > 0) break;
+ }
+ // flush header and reset buf_read, only if success peeking
+ this->asf_header_read = this->asf_header_len;
+ this->buf_read = 0;
+ return 1;
+ //return this->current_pos;
+}
+
+// send time seek request, and update current_pos corresponding to the next
+// requested packet
+// Note that, the current_pos will always does not less than asf_header_len
+int mms_time_seek (mms_io_t *io, mms_t *this, double time_sec) {
+ if (!this->seekable)
+ return 0;
+
+ if (!mms_request_time_seek (io, this, time_sec)) return 0;
+ return peek_and_set_pos (io, this);
+}
+
+// http://sdp.ppona.com/zipfiles/MMSprotocol_pdf.zip said that, this
+// packet_seq value make no difference in version 9 servers.
+// But from my experiment with
+// mms://202.142.200.130/tltk/56k/tltkD2006-08-08ID-7209.wmv and
+// mms://202.142.200.130/tltk/56k/tltkD2006-09-01ID-7467.wmv (the url may valid
+// in only 2-3 months) whose server is version 9, it does response and return
+// the requested packet.
+int mms_request_packet_seek (mms_io_t *io, mms_t *this,
+ unsigned long packet_seq) {
+ if (++this->packet_id_type <= ASF_MEDIA_PACKET_ID_TYPE)
+ this->packet_id_type = ASF_MEDIA_PACKET_ID_TYPE+1;
+ return mms_request_data_packet (io, this, 0, packet_seq, 0x00FFFFFF);
+}
+
+// send packet seek request, and update current_pos corresponding to the next
+// requested packet
+// Note that, the current_pos will always does not less than asf_header_len
+// Not export this function. Let user use mms_seek() instead?
+static int mms_packet_seek (mms_io_t *io, mms_t *this,
+ unsigned long packet_seq) {
+ if (!mms_request_packet_seek (io, this, packet_seq)) return 0;
+ return peek_and_set_pos (io, this);
+}
+
+/*
+TODO: To use this table to calculate buf_packet_seq_offset rather than store
+and retrieve it from this->buf_packet_seq_offset?
+current_packet_seq == (current_pos - asf_header_len) / asf_packet_len
+current_packet_seq == -1 if current_pos < asf_header_len
+buf_packet_seq_offset indicating which packet sequence are residing in the buf.
+Possible status after read(), "last" means last value or unchange.
+current_packet_seq | buf_read | buf_size | buf_packet_seq_offset
+-------------------+----------------+-----------+---------------
+<= 0 | 0 (last) | 0 (last) | none
+<= 0 | 0 (last) | 0 (last) | eos at #0
+<= 0 | 0 (last) | 0 (last) | eos at > #0
+<= 0 | 0 (last) | > 0 (last)| #0
+<= 0 | buf_size (last)| > 0 (last)| > #0
+> 0 | 0 | 0 | eos at current_packet_seq
+> 0 | 0(never happen)| > 0 | (never happen)
+> 0 | buf_size | > 0 | current_packet_seq-1
+*/
+// TODO: How to handle seek() in multi stream source?
+// The stream that follows 0x20 ("new stream") command.
+off_t mms_seek (mms_io_t *io, mms_t *this, off_t offset, int origin) {
+ off_t dest;
+ off_t dest_packet_seq;
+ //off_t buf_packet_seq_offset;
+
+ if (!this->seekable)
+ return this->current_pos;
+
+ switch (origin) {
+ case SEEK_SET:
+ dest = offset;
+ break;
+ case SEEK_CUR:
+ dest = this->current_pos + offset;
+ break;
+ case SEEK_END:
+ //if (this->asf_num_packets == 0) {
+ // //printf ("input_mms: unknown end position in seek!\n");
+ // return this->current_pos;
+ //}
+ dest = mms_get_length (this) + offset;
+ default:
+ printf ("input_mms: unknown origin in seek!\n");
+ return this->current_pos;
+ }
+
+ dest_packet_seq = dest - this->asf_header_len;
+ //if (dest_packet_seq > 0) dest_packet_seq /= this->asf_packet_len;
+ dest_packet_seq = dest_packet_seq >= 0 ?
+ dest_packet_seq / this->asf_packet_len : -1;
+#if 0
+ // buf_packet_seq_offset will identify which packet sequence are residing in
+ // the buf.
+#if 1 /* To show both of the alternate styles :D */
+ buf_packet_seq_offset = this->current_pos - this->asf_header_len;
+ //if (buf_packet_seq_offset > 0) buf_packet_seq_offset /= this->asf_packet_len;
+ buf_packet_seq_offset = buf_packet_seq_offset >= 0 ?
+ buf_packet_seq_offset / this->asf_packet_len : -1;
+ // Note: buf_read == buf_size == 0 may means that it is eos,
+ // eos means that the packet has been peek'ed.
+ if (this->buf_read >= this->buf_size && this->buf_size > 0 &&
+ buf_packet_seq_offset >= 0 ||
+ // assuming packet not peek'ed in the following condition
+ /*this->buf_read >= this->buf_size && */this->buf_size == 0 &&
+ buf_packet_seq_offset == 0)
+ // The buf is all read but the packet has not been peek'ed.
+ --buf_packet_seq_offset;
+#else
+ buf_packet_seq_offset = this->current_pos - this->asf_header_len - 1;
+ //if (buf_packet_seq_offset > 0) buf_packet_seq_offset /= this->asf_packet_len;
+ buf_packet_seq_offset = buf_packet_seq_offset >= 0 ?
+ buf_packet_seq_offset / this->asf_packet_len : -1;
+ // Note: buf_read == buf_size == 0 may means that it is eos,
+ // eos means that the packet has been peek'ed.
+ if (this->buf_read == 0/* && buf_packet_seq_offset >= 0*/)
+ // Since the packet has just been peek'ed.
+ ++buf_packet_seq_offset;
+#endif
+#endif
+
+ if (dest_packet_seq < 0) {
+ if (this->buf_packet_seq_offset > 0) {
+ if (!mms_request_packet_seek (io, this, 0xFFFFFFFF))
+ return this->current_pos;
+#if 1
+ // clear buf
+ this->buf_read = this->buf_size = 0;
+ this->buf_packet_seq_offset = -1;
+ } else {
+#else
+ // clear buf
+ this->buf_read = this->buf_size;
+ // Set this packet sequence not to be reused, since the subsequence
+ // packet may be discontinued.
+ this->buf_packet_seq_offset = -1;
+ // don't reset buf_read if buf_packet_seq_offset < 0, since the previous
+ // buf may not be cleared.
+ } else if (this->buf_packet_seq_offset == 0) {
+#endif
+ // reset buf_read
+ this->buf_read = 0;
+ }
+ this->asf_header_read = dest;
+ return this->current_pos = dest;
+ }
+ // dest_packet_seq >= 0
+ if (this->asf_num_packets && dest == this->asf_header_len +
+ this->asf_num_packets*this->asf_packet_len) {
+ // Requesting the packet beyond the last packet, can cause the server to
+ // not return any packet or any eos command. This can cause
+ // mms_packet_seek() to hang.
+ // This is to allow seeking at end of stream, and avoid hanging.
+ --dest_packet_seq;
+ }
+ if (dest_packet_seq != this->buf_packet_seq_offset) {
+ if (this->asf_num_packets && dest_packet_seq >= this->asf_num_packets) {
+ // Do not seek beyond the last packet.
+ return this->current_pos;
+ }
+ if (!mms_packet_seek (io, this, this->start_packet_seq + dest_packet_seq))
+ return this->current_pos;
+ // Check if current_pos is correct.
+ // This can happen if the server ignore packet seek command.
+ // If so, just return unchanged current_pos, rather than trying to
+ // mms_read() to reach the destination pos.
+ // It should let the caller to decide to choose the alternate method, such
+ // as, mms_time_seek() and/or mms_read() until the destination pos is
+ // reached.
+ if (dest_packet_seq != this->buf_packet_seq_offset)
+ return this->current_pos;
+ // This has already been set in mms_packet_seek().
+ //if (current_packet_seq < 0)
+ // this->asf_header_read = this->asf_header_len;
+ //this->asf_header_read = this->asf_header_len;
+ }
+ // eos is reached ?
+ //if (this->buf_size <= 0) return this->current_pos;
+ //this->buf_read = (dest - this->asf_header_len) % this->asf_packet_len;
+ this->buf_read = dest -
+ (this->asf_header_len + dest_packet_seq*this->asf_packet_len);
+ // will never happen ?
+ //if (this->buf_size <= this->buf_read) return this->current_pos;
+ return this->current_pos = dest;
+}
+
+
+void mms_close (mms_t *this) {
+
+ if (this->s != -1)
+ close (this->s);
+ if (this->url)
+ free(this->url);
+ if (this->guri)
+ gnet_uri_delete(this->guri);
+ if (this->uri)
+ free(this->uri);
+
+ free (this);
+}
+
+double mms_get_time_length (mms_t *this) {
+ return (double)(this->time_len) / 1e7;
+}
+
+uint64_t mms_get_raw_time_length (mms_t *this) {
+ return this->time_len;
+}
+
+uint32_t mms_get_length (mms_t *this) {
+ /* we could / should return this->file_len here, but usually this->file_len
+ is longer then the calculation below, as usually an asf file contains an
+ asf index object after the data stream. However since we do not have a
+ (known) way to get to this index object through mms, we return a
+ calculated size of what we can get to when we know. */
+ if (this->asf_num_packets)
+ return this->asf_header_len + this->asf_num_packets*this->asf_packet_len;
+ else
+ return this->file_len;
+}
+
+off_t mms_get_current_pos (mms_t *this) {
+ return this->current_pos;
+}
+
+uint32_t mms_get_asf_header_len (mms_t *this) {
+ return this->asf_header_len;
+}
+
+uint64_t mms_get_asf_packet_len (mms_t *this) {
+ return this->asf_packet_len;
+}
+
+int mms_get_seekable (mms_t *this) {
+ return this->seekable;
+}