1/* 2 * MMS protocol over HTTP 3 * Copyright (c) 2010 Zhentan Feng <spyfeng at gmail dot com> 4 * 5 * This file is part of FFmpeg. 6 * 7 * FFmpeg 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 * FFmpeg 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 FFmpeg; 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 uint8_t location[1024]; 60 int request_seq; ///< request packet sequence 61 int chunk_seq; ///< data packet sequence 62} MMSHContext; 63 64static int mmsh_close(URLContext *h) 65{ 66 MMSHContext *mmsh = (MMSHContext *)h->priv_data; 67 MMSContext *mms = &mmsh->mms; 68 if (mms->mms_hd) 69 ffurl_closep(&mms->mms_hd); 70 av_freep(&mms->streams); 71 av_freep(&mms->asf_header); 72 return 0; 73} 74 75static ChunkType get_chunk_header(MMSHContext *mmsh, int *len) 76{ 77 MMSContext *mms = &mmsh->mms; 78 uint8_t chunk_header[CHUNK_HEADER_LENGTH]; 79 uint8_t ext_header[EXT_HEADER_LENGTH]; 80 ChunkType chunk_type; 81 int chunk_len, res, ext_header_len; 82 83 res = ffurl_read_complete(mms->mms_hd, chunk_header, CHUNK_HEADER_LENGTH); 84 if (res != CHUNK_HEADER_LENGTH) { 85 av_log(NULL, AV_LOG_ERROR, "Read data packet header failed!\n"); 86 return AVERROR(EIO); 87 } 88 chunk_type = AV_RL16(chunk_header); 89 chunk_len = AV_RL16(chunk_header + 2); 90 91 switch (chunk_type) { 92 case CHUNK_TYPE_END: 93 case CHUNK_TYPE_STREAM_CHANGE: 94 ext_header_len = 4; 95 break; 96 case CHUNK_TYPE_ASF_HEADER: 97 case CHUNK_TYPE_DATA: 98 ext_header_len = 8; 99 break; 100 default: 101 av_log(NULL, AV_LOG_ERROR, "Strange chunk type %d\n", chunk_type); 102 return AVERROR_INVALIDDATA; 103 } 104 105 res = ffurl_read_complete(mms->mms_hd, ext_header, ext_header_len); 106 if (res != ext_header_len) { 107 av_log(NULL, AV_LOG_ERROR, "Read ext header failed!\n"); 108 return AVERROR(EIO); 109 } 110 *len = chunk_len - ext_header_len; 111 if (chunk_type == CHUNK_TYPE_END || chunk_type == CHUNK_TYPE_DATA) 112 mmsh->chunk_seq = AV_RL32(ext_header); 113 return chunk_type; 114} 115 116static int read_data_packet(MMSHContext *mmsh, const int len) 117{ 118 MMSContext *mms = &mmsh->mms; 119 int res; 120 if (len > sizeof(mms->in_buffer)) { 121 av_log(NULL, AV_LOG_ERROR, 122 "Data packet length %d exceeds the in_buffer size %"SIZE_SPECIFIER"\n", 123 len, sizeof(mms->in_buffer)); 124 return AVERROR(EIO); 125 } 126 res = ffurl_read_complete(mms->mms_hd, mms->in_buffer, len); 127 av_dlog(NULL, "Data packet len = %d\n", len); 128 if (res != len) { 129 av_log(NULL, AV_LOG_ERROR, "Read data packet failed!\n"); 130 return AVERROR(EIO); 131 } 132 if (len > mms->asf_packet_len) { 133 av_log(NULL, AV_LOG_ERROR, 134 "Chunk length %d exceed packet length %d\n",len, mms->asf_packet_len); 135 return AVERROR_INVALIDDATA; 136 } else { 137 memset(mms->in_buffer + len, 0, mms->asf_packet_len - len); // padding 138 } 139 mms->read_in_ptr = mms->in_buffer; 140 mms->remaining_in_len = mms->asf_packet_len; 141 return 0; 142} 143 144static int get_http_header_data(MMSHContext *mmsh) 145{ 146 MMSContext *mms = &mmsh->mms; 147 int res, len; 148 ChunkType chunk_type; 149 150 for (;;) { 151 len = 0; 152 res = chunk_type = get_chunk_header(mmsh, &len); 153 if (res < 0) { 154 return res; 155 } else if (chunk_type == CHUNK_TYPE_ASF_HEADER){ 156 // get asf header and stored it 157 if (!mms->header_parsed) { 158 if (mms->asf_header) { 159 if (len != mms->asf_header_size) { 160 mms->asf_header_size = len; 161 av_dlog(NULL, "Header len changed from %d to %d\n", 162 mms->asf_header_size, len); 163 av_freep(&mms->asf_header); 164 } 165 } 166 mms->asf_header = av_mallocz(len); 167 if (!mms->asf_header) { 168 return AVERROR(ENOMEM); 169 } 170 mms->asf_header_size = len; 171 } 172 if (len > mms->asf_header_size) { 173 av_log(NULL, AV_LOG_ERROR, 174 "Asf header packet len = %d exceed the asf header buf size %d\n", 175 len, mms->asf_header_size); 176 return AVERROR(EIO); 177 } 178 res = ffurl_read_complete(mms->mms_hd, mms->asf_header, len); 179 if (res != len) { 180 av_log(NULL, AV_LOG_ERROR, 181 "Recv asf header data len %d != expected len %d\n", res, len); 182 return AVERROR(EIO); 183 } 184 mms->asf_header_size = len; 185 if (!mms->header_parsed) { 186 res = ff_mms_asf_header_parser(mms); 187 mms->header_parsed = 1; 188 return res; 189 } 190 } else if (chunk_type == CHUNK_TYPE_DATA) { 191 // read data packet and do padding 192 return read_data_packet(mmsh, len); 193 } else { 194 if (len) { 195 if (len > sizeof(mms->in_buffer)) { 196 av_log(NULL, AV_LOG_ERROR, 197 "Other packet len = %d exceed the in_buffer size %"SIZE_SPECIFIER"\n", 198 len, sizeof(mms->in_buffer)); 199 return AVERROR(EIO); 200 } 201 res = ffurl_read_complete(mms->mms_hd, mms->in_buffer, len); 202 if (res != len) { 203 av_log(NULL, AV_LOG_ERROR, "Read other chunk type data failed!\n"); 204 return AVERROR(EIO); 205 } else { 206 av_dlog(NULL, "Skip chunk type %d \n", chunk_type); 207 continue; 208 } 209 } 210 } 211 } 212} 213 214static int mmsh_open_internal(URLContext *h, const char *uri, int flags, int timestamp, int64_t pos) 215{ 216 int i, port, err; 217 char httpname[256], path[256], host[128]; 218 char *stream_selection = NULL; 219 char headers[1024]; 220 MMSHContext *mmsh = h->priv_data; 221 MMSContext *mms; 222 223 mmsh->request_seq = h->is_streamed = 1; 224 mms = &mmsh->mms; 225 av_strlcpy(mmsh->location, uri, sizeof(mmsh->location)); 226 227 av_url_split(NULL, 0, NULL, 0, 228 host, sizeof(host), &port, path, sizeof(path), mmsh->location); 229 if (port<0) 230 port = 80; // default mmsh protocol port 231 ff_url_join(httpname, sizeof(httpname), "http", NULL, host, port, "%s", path); 232 233 if (ffurl_alloc(&mms->mms_hd, httpname, AVIO_FLAG_READ, 234 &h->interrupt_callback) < 0) { 235 return AVERROR(EIO); 236 } 237 238 snprintf(headers, sizeof(headers), 239 "Accept: */*\r\n" 240 USERAGENT 241 "Host: %s:%d\r\n" 242 "Pragma: no-cache,rate=1.000000,stream-time=0," 243 "stream-offset=0:0,request-context=%u,max-duration=0\r\n" 244 CLIENTGUID 245 "Connection: Close\r\n", 246 host, port, mmsh->request_seq++); 247 av_opt_set(mms->mms_hd->priv_data, "headers", headers, 0); 248 249 err = ffurl_connect(mms->mms_hd, NULL); 250 if (err) { 251 goto fail; 252 } 253 err = get_http_header_data(mmsh); 254 if (err) { 255 av_log(NULL, AV_LOG_ERROR, "Get http header data failed!\n"); 256 goto fail; 257 } 258 259 // close the socket and then reopen it for sending the second play request. 260 ffurl_close(mms->mms_hd); 261 memset(headers, 0, sizeof(headers)); 262 if ((err = ffurl_alloc(&mms->mms_hd, httpname, AVIO_FLAG_READ, 263 &h->interrupt_callback)) < 0) { 264 goto fail; 265 } 266 stream_selection = av_mallocz(mms->stream_num * 19 + 1); 267 if (!stream_selection) 268 return AVERROR(ENOMEM); 269 for (i = 0; i < mms->stream_num; i++) { 270 char tmp[20]; 271 err = snprintf(tmp, sizeof(tmp), "ffff:%d:0 ", mms->streams[i].id); 272 if (err < 0) 273 goto fail; 274 av_strlcat(stream_selection, tmp, mms->stream_num * 19 + 1); 275 } 276 // send play request 277 err = snprintf(headers, sizeof(headers), 278 "Accept: */*\r\n" 279 USERAGENT 280 "Host: %s:%d\r\n" 281 "Pragma: no-cache,rate=1.000000,request-context=%u\r\n" 282 "Pragma: xPlayStrm=1\r\n" 283 CLIENTGUID 284 "Pragma: stream-switch-count=%d\r\n" 285 "Pragma: stream-switch-entry=%s\r\n" 286 "Pragma: no-cache,rate=1.000000,stream-time=%u" 287 "Connection: Close\r\n", 288 host, port, mmsh->request_seq++, mms->stream_num, stream_selection, timestamp); 289 av_freep(&stream_selection); 290 if (err < 0) { 291 av_log(NULL, AV_LOG_ERROR, "Build play request failed!\n"); 292 goto fail; 293 } 294 av_dlog(NULL, "out_buffer is %s", headers); 295 av_opt_set(mms->mms_hd->priv_data, "headers", headers, 0); 296 297 err = ffurl_connect(mms->mms_hd, NULL); 298 if (err) { 299 goto fail; 300 } 301 302 err = get_http_header_data(mmsh); 303 if (err) { 304 av_log(NULL, AV_LOG_ERROR, "Get http header data failed!\n"); 305 goto fail; 306 } 307 308 av_dlog(NULL, "Connection successfully open\n"); 309 return 0; 310fail: 311 av_freep(&stream_selection); 312 mmsh_close(h); 313 av_dlog(NULL, "Connection failed with error %d\n", err); 314 return err; 315} 316 317static int mmsh_open(URLContext *h, const char *uri, int flags) 318{ 319 return mmsh_open_internal(h, uri, flags, 0, 0); 320} 321 322static int handle_chunk_type(MMSHContext *mmsh) 323{ 324 MMSContext *mms = &mmsh->mms; 325 int res, len = 0; 326 ChunkType chunk_type; 327 chunk_type = get_chunk_header(mmsh, &len); 328 329 switch (chunk_type) { 330 case CHUNK_TYPE_END: 331 mmsh->chunk_seq = 0; 332 av_log(NULL, AV_LOG_ERROR, "Stream ended!\n"); 333 return AVERROR(EIO); 334 case CHUNK_TYPE_STREAM_CHANGE: 335 mms->header_parsed = 0; 336 if (res = get_http_header_data(mmsh)) { 337 av_log(NULL, AV_LOG_ERROR,"Stream changed! Failed to get new header!\n"); 338 return res; 339 } 340 break; 341 case CHUNK_TYPE_DATA: 342 return read_data_packet(mmsh, len); 343 default: 344 av_log(NULL, AV_LOG_ERROR, "Recv other type packet %d\n", chunk_type); 345 return AVERROR_INVALIDDATA; 346 } 347 return 0; 348} 349 350static int mmsh_read(URLContext *h, uint8_t *buf, int size) 351{ 352 int res = 0; 353 MMSHContext *mmsh = h->priv_data; 354 MMSContext *mms = &mmsh->mms; 355 do { 356 if (mms->asf_header_read_size < mms->asf_header_size) { 357 // copy asf header into buffer 358 res = ff_mms_read_header(mms, buf, size); 359 } else { 360 if (!mms->remaining_in_len && (res = handle_chunk_type(mmsh))) 361 return res; 362 res = ff_mms_read_data(mms, buf, size); 363 } 364 } while (!res); 365 return res; 366} 367 368static int64_t mmsh_read_seek(URLContext *h, int stream_index, 369 int64_t timestamp, int flags) 370{ 371 MMSHContext *mmsh_old = h->priv_data; 372 MMSHContext *mmsh = av_mallocz(sizeof(*mmsh)); 373 int ret; 374 375 if (!mmsh) 376 return AVERROR(ENOMEM); 377 378 h->priv_data = mmsh; 379 ret= mmsh_open_internal(h, mmsh_old->location, 0, FFMAX(timestamp, 0), 0); 380 if(ret>=0){ 381 h->priv_data = mmsh_old; 382 mmsh_close(h); 383 h->priv_data = mmsh; 384 av_free(mmsh_old); 385 mmsh->mms.asf_header_read_size = mmsh->mms.asf_header_size; 386 }else { 387 h->priv_data = mmsh_old; 388 av_free(mmsh); 389 } 390 391 return ret; 392} 393 394static int64_t mmsh_seek(URLContext *h, int64_t pos, int whence) 395{ 396 MMSHContext *mmsh = h->priv_data; 397 MMSContext *mms = &mmsh->mms; 398 399 if(pos == 0 && whence == SEEK_CUR) 400 return mms->asf_header_read_size + mms->remaining_in_len + mmsh->chunk_seq * (int64_t)mms->asf_packet_len; 401 return AVERROR(ENOSYS); 402} 403 404URLProtocol ff_mmsh_protocol = { 405 .name = "mmsh", 406 .url_open = mmsh_open, 407 .url_read = mmsh_read, 408 .url_seek = mmsh_seek, 409 .url_close = mmsh_close, 410 .url_read_seek = mmsh_read_seek, 411 .priv_data_size = sizeof(MMSHContext), 412 .flags = URL_PROTOCOL_FLAG_NETWORK, 413}; 414