diff options
Diffstat (limited to 'plugins/mms/libmms/mms.c')
-rw-r--r-- | plugins/mms/libmms/mms.c | 1802 |
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; +} |