/* * 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 #include #include #include #include #include #include #include #include #include #include #include //#define USE_ICONV #ifdef USE_ICONV #include #else // use deadbeef's charset conversion instead of iconv #include "../../../deadbeef.h" extern DB_functions_t *deadbeef; #endif /********** 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; int *need_abort; }; 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, int *need_abort) { off_t len = 0, ret; /* lprintf("%d\n", fallback_io_select(data, socket, MMS_IO_READ_READY, 1000)); */ errno = 0; int nretry = 600; lprintf ("mms: fallback_io_read: need_abort ptr = %p\n", need_abort); while (len < num && nretry > 0 && (!need_abort || !(*need_abort))) { // original read from upstream libmms //ret = (off_t)read(socket, buf + len, num - len); for (;;) { ret = (off_t)recv(socket, buf + len, num - len, MSG_DONTWAIT); if ((ret != EAGAIN && ret != EWOULDBLOCK) || (need_abort && *need_abort)) { break; } } if (need_abort && *need_abort) { return 0; } 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: usleep (30000); // sleeping 30ms 200 times will give us about 6 sec of time to complete the request nretry--; 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, int *need_abort) { struct hostent *h; int i, s; #ifdef USE_GETHOSTBYNAME h = gethostbyname(host); if (h == NULL) { lprintf("mms: unable to resolve host: %s\n", host); return -1; } char **h_addr_list = h->h_addr_list; #else char sport[10]; snprintf (sport, 10, "%d", port); struct addrinfo *res; for (;;) { int err = getaddrinfo (host, sport, NULL, &res); if (need_abort && *need_abort) { if (res) { freeaddrinfo(res); } return -1; } if (err == EAI_AGAIN) { lprintf ("getaddrinfo again\n"); continue; } else if (err == 0) { lprintf ("getaddrinfo success\n"); break; } lprintf ("getaddrinfo err: %d\n", err); return -1; } #endif 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; } #ifdef USE_GETHOSTBYNAME for (i = 0; h_addr_list[i]; i++) { struct in_addr ia; struct sockaddr_in sin; memcpy (&ia, h_addr_list[i], 4); sin.sin_family = AF_INET; sin.sin_addr = ia; sin.sin_port = htons(port); #else struct addrinfo *rp; for (rp = res; rp != NULL; rp = rp->ai_next) { struct sockaddr_in sin; memset (&sin, 0, sizeof (sin)); int l = rp->ai_addrlen; if (l > sizeof (sin)) { l = sizeof (sin); } memcpy (&sin, rp->ai_addr, l); #endif time_t t = time (NULL); int error = 0; while (!need_abort || !(*need_abort)) { int res = connect(s, (struct sockaddr *)&sin, sizeof(sin)); if (res == -1 && (errno == EINPROGRESS || errno == EALREADY)) { if (time (NULL) - t > 3) { error = -1; break; } usleep(100000); continue; } else if (res == -1 && errno == EISCONN) { break; } else if (res == -1) { error = -1; break; } } if (need_abort && *need_abort) { lprintf ("fallback_io_tcp_connect: aborted\n"); s = -1; break; } // if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) ==-1 && errno != EINPROGRESS) { // continue; // } if (error) { continue; } #ifndef USE_GETHOSTBYNAME if (res) { freeaddrinfo(res); } #endif return s; } #ifndef USE_GETHOSTBYNAME if (res) { freeaddrinfo(res); } #endif 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(char *dest, char *src, int dest_len) { #ifdef USE_ICONV char *ip = src, *op = dest; size_t ip_len = strlen(src); size_t op_len = dest_len - 2; /* reserve 2 bytes for 0 termination */ iconv_t url_conv = iconv_open("UTF-16LE", "UTF-8"); if (iconv(url_conv, &ip, &ip_len, &op, &op_len) == (size_t)-1) { iconv_close(url_conv); lprintf("mms: Error converting uri to unicode: %s\n", strerror(errno)); return 0; } iconv_close(url_conv); /* 0 terminate the string */ *op++ = 0; *op++ = 0; return op - dest; #else char *ip = src, *op = dest; size_t ip_len = strlen(src); size_t op_len = dest_len - 2; int res = deadbeef->junk_iconv (ip, ip_len, op, ip_len * 2, "UTF-8", "UTF-16LE"); if (res == -1) { lprintf("mms: Error converting uri to unicode: %s\n", strerror(errno)); return 0; } op += res; *op++ = 0; *op++ = 0; return op - dest; #endif } /* * 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; lprintf ("mms: get_packet_header: need_abort ptr = %p\n", this->need_abort); 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->need_abort); 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, this->need_abort); 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->need_abort) ; //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; lprintf ("mms: get_answer: need_abort ptr = %p\n", this->need_abort); 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, this->need_abort); 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, this->need_abort); 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, int *need_abort) { 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->need_abort = need_abort; 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; } /* * 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(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(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(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; } } 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); 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, this->need_abort); 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 *need_abort) { int total; total = 0; while (total < len && !this->eos && (!need_abort || !(*need_abort))) { 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; } } if (need_abort && *need_abort) { lprintf ("mms_read aborted\n"); return -1; } 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: fprintf (stderr, "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; }