1/* 2 * MMS protocol over HTTP 3 * Copyright (c) 2010 Zhentan Feng <spyfeng at gmail dot com> 4 * 5 * This file is part of Libav. 6 * 7 * Libav is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Lesser General Public 9 * License as published by the Free Software Foundation; either 10 * version 2.1 of the License, or (at your option) any later version. 11 * 12 * Libav is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with Libav; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 */ 21 22/* 23 * Reference 24 * Windows Media HTTP Streaming Protocol. 25 * http://msdn.microsoft.com/en-us/library/cc251059(PROT.10).aspx 26 */ 27 28#include <string.h> 29#include "libavutil/intreadwrite.h" 30#include "libavutil/avstring.h" 31#include "libavutil/opt.h" 32#include "internal.h" 33#include "mms.h" 34#include "asf.h" 35#include "http.h" 36#include "url.h" 37 38#define CHUNK_HEADER_LENGTH 4 // 2bytes chunk type and 2bytes chunk length. 39#define EXT_HEADER_LENGTH 8 // 4bytes sequence, 2bytes useless and 2bytes chunk length. 40 41// see Ref 2.2.1.8 42#define USERAGENT "User-Agent: NSPlayer/4.1.0.3856\r\n" 43// see Ref 2.2.1.4.33 44// the guid value can be changed to any valid value. 45#define CLIENTGUID "Pragma: xClientGUID={c77e7400-738a-11d2-9add-0020af0a3278}\r\n" 46 47// see Ref 2.2.3 for packet type define: 48// chunk type contains 2 fields: Frame and PacketID. 49// Frame is 0x24 or 0xA4(rarely), different PacketID indicates different packet type. 50typedef enum { 51 CHUNK_TYPE_DATA = 0x4424, 52 CHUNK_TYPE_ASF_HEADER = 0x4824, 53 CHUNK_TYPE_END = 0x4524, 54 CHUNK_TYPE_STREAM_CHANGE = 0x4324, 55} ChunkType; 56 57typedef struct { 58 MMSContext mms; 59 int request_seq; ///< request packet sequence 60 int chunk_seq; ///< data packet sequence 61} MMSHContext; 62 63static int mmsh_close(URLContext *h) 64{ 65 MMSHContext *mmsh = (MMSHContext *)h->priv_data; 66 MMSContext *mms = &mmsh->mms; 67 if (mms->mms_hd) 68 ffurl_close(mms->mms_hd); 69 av_free(mms->streams); 70 av_free(mms->asf_header); 71 return 0; 72} 73 74static ChunkType get_chunk_header(MMSHContext *mmsh, int *len) 75{ 76 MMSContext *mms = &mmsh->mms; 77 uint8_t chunk_header[CHUNK_HEADER_LENGTH]; 78 uint8_t ext_header[EXT_HEADER_LENGTH]; 79 ChunkType chunk_type; 80 int chunk_len, res, ext_header_len; 81 82 res = ffurl_read_complete(mms->mms_hd, chunk_header, CHUNK_HEADER_LENGTH); 83 if (res != CHUNK_HEADER_LENGTH) { 84 av_log(NULL, AV_LOG_ERROR, "Read data packet header failed!\n"); 85 return AVERROR(EIO); 86 } 87 chunk_type = AV_RL16(chunk_header); 88 chunk_len = AV_RL16(chunk_header + 2); 89 90 switch (chunk_type) { 91 case CHUNK_TYPE_END: 92 case CHUNK_TYPE_STREAM_CHANGE: 93 ext_header_len = 4; 94 break; 95 case CHUNK_TYPE_ASF_HEADER: 96 case CHUNK_TYPE_DATA: 97 ext_header_len = 8; 98 break; 99 default: 100 av_log(NULL, AV_LOG_ERROR, "Strange chunk type %d\n", chunk_type); 101 return AVERROR_INVALIDDATA; 102 } 103 104 res = ffurl_read_complete(mms->mms_hd, ext_header, ext_header_len); 105 if (res != ext_header_len) { 106 av_log(NULL, AV_LOG_ERROR, "Read ext header failed!\n"); 107 return AVERROR(EIO); 108 } 109 *len = chunk_len - ext_header_len; 110 if (chunk_type == CHUNK_TYPE_END || chunk_type == CHUNK_TYPE_DATA) 111 mmsh->chunk_seq = AV_RL32(ext_header); 112 return chunk_type; 113} 114 115static int read_data_packet(MMSHContext *mmsh, const int len) 116{ 117 MMSContext *mms = &mmsh->mms; 118 int res; 119 if (len > sizeof(mms->in_buffer)) { 120 av_log(NULL, AV_LOG_ERROR, 121 "Data packet length %d exceeds the in_buffer size %zu\n", 122 len, sizeof(mms->in_buffer)); 123 return AVERROR(EIO); 124 } 125 res = ffurl_read_complete(mms->mms_hd, mms->in_buffer, len); 126 av_dlog(NULL, "Data packet len = %d\n", len); 127 if (res != len) { 128 av_log(NULL, AV_LOG_ERROR, "Read data packet failed!\n"); 129 return AVERROR(EIO); 130 } 131 if (len > mms->asf_packet_len) { 132 av_log(NULL, AV_LOG_ERROR, 133 "Chunk length %d exceed packet length %d\n",len, mms->asf_packet_len); 134 return AVERROR_INVALIDDATA; 135 } else { 136 memset(mms->in_buffer + len, 0, mms->asf_packet_len - len); // padding 137 } 138 mms->read_in_ptr = mms->in_buffer; 139 mms->remaining_in_len = mms->asf_packet_len; 140 return 0; 141} 142 143static int get_http_header_data(MMSHContext *mmsh) 144{ 145 MMSContext *mms = &mmsh->mms; 146 int res, len; 147 ChunkType chunk_type; 148 149 for (;;) { 150 len = 0; 151 res = chunk_type = get_chunk_header(mmsh, &len); 152 if (res < 0) { 153 return res; 154 } else if (chunk_type == CHUNK_TYPE_ASF_HEADER){ 155 // get asf header and stored it 156 if (!mms->header_parsed) { 157 if (mms->asf_header) { 158 if (len != mms->asf_header_size) { 159 mms->asf_header_size = len; 160 av_dlog(NULL, "Header len changed from %d to %d\n", 161 mms->asf_header_size, len); 162 av_freep(&mms->asf_header); 163 } 164 } 165 mms->asf_header = av_mallocz(len); 166 if (!mms->asf_header) { 167 return AVERROR(ENOMEM); 168 } 169 mms->asf_header_size = len; 170 } 171 if (len > mms->asf_header_size) { 172 av_log(NULL, AV_LOG_ERROR, 173 "Asf header packet len = %d exceed the asf header buf size %d\n", 174 len, mms->asf_header_size); 175 return AVERROR(EIO); 176 } 177 res = ffurl_read_complete(mms->mms_hd, mms->asf_header, len); 178 if (res != len) { 179 av_log(NULL, AV_LOG_ERROR, 180 "Recv asf header data len %d != expected len %d\n", res, len); 181 return AVERROR(EIO); 182 } 183 mms->asf_header_size = len; 184 if (!mms->header_parsed) { 185 res = ff_mms_asf_header_parser(mms); 186 mms->header_parsed = 1; 187 return res; 188 } 189 } else if (chunk_type == CHUNK_TYPE_DATA) { 190 // read data packet and do padding 191 return read_data_packet(mmsh, len); 192 } else { 193 if (len) { 194 if (len > sizeof(mms->in_buffer)) { 195 av_log(NULL, AV_LOG_ERROR, 196 "Other packet len = %d exceed the in_buffer size %zu\n", 197 len, sizeof(mms->in_buffer)); 198 return AVERROR(EIO); 199 } 200 res = ffurl_read_complete(mms->mms_hd, mms->in_buffer, len); 201 if (res != len) { 202 av_log(NULL, AV_LOG_ERROR, "Read other chunk type data failed!\n"); 203 return AVERROR(EIO); 204 } else { 205 av_dlog(NULL, "Skip chunk type %d \n", chunk_type); 206 continue; 207 } 208 } 209 } 210 } 211} 212 213static int mmsh_open(URLContext *h, const char *uri, int flags) 214{ 215 int i, port, err; 216 char httpname[256], path[256], host[128], location[1024]; 217 char *stream_selection = NULL; 218 char headers[1024]; 219 MMSHContext *mmsh = h->priv_data; 220 MMSContext *mms; 221 222 mmsh->request_seq = h->is_streamed = 1; 223 mms = &mmsh->mms; 224 av_strlcpy(location, uri, sizeof(location)); 225 226 av_url_split(NULL, 0, NULL, 0, 227 host, sizeof(host), &port, path, sizeof(path), location); 228 if (port<0) 229 port = 80; // default mmsh protocol port 230 ff_url_join(httpname, sizeof(httpname), "http", NULL, host, port, "%s", path); 231 232 if (ffurl_alloc(&mms->mms_hd, httpname, AVIO_FLAG_READ, 233 &h->interrupt_callback) < 0) { 234 return AVERROR(EIO); 235 } 236 237 snprintf(headers, sizeof(headers), 238 "Accept: */*\r\n" 239 USERAGENT 240 "Host: %s:%d\r\n" 241 "Pragma: no-cache,rate=1.000000,stream-time=0," 242 "stream-offset=0:0,request-context=%u,max-duration=0\r\n" 243 CLIENTGUID 244 "Connection: Close\r\n\r\n", 245 host, port, mmsh->request_seq++); 246 av_opt_set(mms->mms_hd->priv_data, "headers", headers, 0); 247 248 err = ffurl_connect(mms->mms_hd, NULL); 249 if (err) { 250 goto fail; 251 } 252 err = get_http_header_data(mmsh); 253 if (err) { 254 av_log(NULL, AV_LOG_ERROR, "Get http header data failed!\n"); 255 goto fail; 256 } 257 258 // close the socket and then reopen it for sending the second play request. 259 ffurl_close(mms->mms_hd); 260 memset(headers, 0, sizeof(headers)); 261 if ((err = ffurl_alloc(&mms->mms_hd, httpname, AVIO_FLAG_READ, 262 &h->interrupt_callback)) < 0) { 263 goto fail; 264 } 265 stream_selection = av_mallocz(mms->stream_num * 19 + 1); 266 if (!stream_selection) 267 return AVERROR(ENOMEM); 268 for (i = 0; i < mms->stream_num; i++) { 269 char tmp[20]; 270 err = snprintf(tmp, sizeof(tmp), "ffff:%d:0 ", mms->streams[i].id); 271 if (err < 0) 272 goto fail; 273 av_strlcat(stream_selection, tmp, mms->stream_num * 19 + 1); 274 } 275 // send play request 276 err = snprintf(headers, sizeof(headers), 277 "Accept: */*\r\n" 278 USERAGENT 279 "Host: %s:%d\r\n" 280 "Pragma: no-cache,rate=1.000000,request-context=%u\r\n" 281 "Pragma: xPlayStrm=1\r\n" 282 CLIENTGUID 283 "Pragma: stream-switch-count=%d\r\n" 284 "Pragma: stream-switch-entry=%s\r\n" 285 "Connection: Close\r\n\r\n", 286 host, port, mmsh->request_seq++, mms->stream_num, stream_selection); 287 av_freep(&stream_selection); 288 if (err < 0) { 289 av_log(NULL, AV_LOG_ERROR, "Build play request failed!\n"); 290 goto fail; 291 } 292 av_dlog(NULL, "out_buffer is %s", headers); 293 av_opt_set(mms->mms_hd->priv_data, "headers", headers, 0); 294 295 err = ffurl_connect(mms->mms_hd, NULL); 296 if (err) { 297 goto fail; 298 } 299 300 err = get_http_header_data(mmsh); 301 if (err) { 302 av_log(NULL, AV_LOG_ERROR, "Get http header data failed!\n"); 303 goto fail; 304 } 305 306 av_dlog(NULL, "Connection successfully open\n"); 307 return 0; 308fail: 309 av_freep(&stream_selection); 310 mmsh_close(h); 311 av_dlog(NULL, "Connection failed with error %d\n", err); 312 return err; 313} 314 315static int handle_chunk_type(MMSHContext *mmsh) 316{ 317 MMSContext *mms = &mmsh->mms; 318 int res, len = 0; 319 ChunkType chunk_type; 320 chunk_type = get_chunk_header(mmsh, &len); 321 322 switch (chunk_type) { 323 case CHUNK_TYPE_END: 324 mmsh->chunk_seq = 0; 325 av_log(NULL, AV_LOG_ERROR, "Stream ended!\n"); 326 return AVERROR(EIO); 327 case CHUNK_TYPE_STREAM_CHANGE: 328 mms->header_parsed = 0; 329 if (res = get_http_header_data(mmsh)) { 330 av_log(NULL, AV_LOG_ERROR,"Stream changed! Failed to get new header!\n"); 331 return res; 332 } 333 break; 334 case CHUNK_TYPE_DATA: 335 return read_data_packet(mmsh, len); 336 default: 337 av_log(NULL, AV_LOG_ERROR, "Recv other type packet %d\n", chunk_type); 338 return AVERROR_INVALIDDATA; 339 } 340 return 0; 341} 342 343static int mmsh_read(URLContext *h, uint8_t *buf, int size) 344{ 345 int res = 0; 346 MMSHContext *mmsh = h->priv_data; 347 MMSContext *mms = &mmsh->mms; 348 do { 349 if (mms->asf_header_read_size < mms->asf_header_size) { 350 // copy asf header into buffer 351 res = ff_mms_read_header(mms, buf, size); 352 } else { 353 if (!mms->remaining_in_len && (res = handle_chunk_type(mmsh))) 354 return res; 355 res = ff_mms_read_data(mms, buf, size); 356 } 357 } while (!res); 358 return res; 359} 360 361URLProtocol ff_mmsh_protocol = { 362 .name = "mmsh", 363 .url_open = mmsh_open, 364 .url_read = mmsh_read, 365 .url_close = mmsh_close, 366 .priv_data_size = sizeof(MMSHContext), 367 .flags = URL_PROTOCOL_FLAG_NETWORK, 368}; 369