Libav 0.7.1
|
00001 /* 00002 * MMS protocol over HTTP 00003 * Copyright (c) 2010 Zhentan Feng <spyfeng at gmail dot com> 00004 * 00005 * This file is part of Libav. 00006 * 00007 * Libav is free software; you can redistribute it and/or 00008 * modify it under the terms of the GNU Lesser General Public 00009 * License as published by the Free Software Foundation; either 00010 * version 2.1 of the License, or (at your option) any later version. 00011 * 00012 * Libav is distributed in the hope that it will be useful, 00013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00015 * Lesser General Public License for more details. 00016 * 00017 * You should have received a copy of the GNU Lesser General Public 00018 * License along with Libav; if not, write to the Free Software 00019 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 00020 */ 00021 00022 /* 00023 * Reference 00024 * Windows Media HTTP Streaming Protocol. 00025 * http://msdn.microsoft.com/en-us/library/cc251059(PROT.10).aspx 00026 */ 00027 00028 #include <string.h> 00029 #include "libavutil/intreadwrite.h" 00030 #include "libavutil/avstring.h" 00031 #include "internal.h" 00032 #include "mms.h" 00033 #include "asf.h" 00034 #include "http.h" 00035 #include "url.h" 00036 00037 #define CHUNK_HEADER_LENGTH 4 // 2bytes chunk type and 2bytes chunk length. 00038 #define EXT_HEADER_LENGTH 8 // 4bytes sequence, 2bytes useless and 2bytes chunk length. 00039 00040 // see Ref 2.2.1.8 00041 #define USERAGENT "User-Agent: NSPlayer/4.1.0.3856\r\n" 00042 // see Ref 2.2.1.4.33 00043 // the guid value can be changed to any valid value. 00044 #define CLIENTGUID "Pragma: xClientGUID={c77e7400-738a-11d2-9add-0020af0a3278}\r\n" 00045 00046 // see Ref 2.2.3 for packet type define: 00047 // chunk type contains 2 fields: Frame and PacketID. 00048 // Frame is 0x24 or 0xA4(rarely), different PacketID indicates different packet type. 00049 typedef enum { 00050 CHUNK_TYPE_DATA = 0x4424, 00051 CHUNK_TYPE_ASF_HEADER = 0x4824, 00052 CHUNK_TYPE_END = 0x4524, 00053 CHUNK_TYPE_STREAM_CHANGE = 0x4324, 00054 } ChunkType; 00055 00056 typedef struct { 00057 MMSContext mms; 00058 int request_seq; 00059 int chunk_seq; 00060 } MMSHContext; 00061 00062 static int mmsh_close(URLContext *h) 00063 { 00064 MMSHContext *mmsh = (MMSHContext *)h->priv_data; 00065 MMSContext *mms = &mmsh->mms; 00066 if (mms->mms_hd) 00067 ffurl_close(mms->mms_hd); 00068 av_free(mms->streams); 00069 av_free(mms->asf_header); 00070 av_freep(&h->priv_data); 00071 return 0; 00072 } 00073 00074 static ChunkType get_chunk_header(MMSHContext *mmsh, int *len) 00075 { 00076 MMSContext *mms = &mmsh->mms; 00077 uint8_t chunk_header[CHUNK_HEADER_LENGTH]; 00078 uint8_t ext_header[EXT_HEADER_LENGTH]; 00079 ChunkType chunk_type; 00080 int chunk_len, res, ext_header_len; 00081 00082 res = ffurl_read_complete(mms->mms_hd, chunk_header, CHUNK_HEADER_LENGTH); 00083 if (res != CHUNK_HEADER_LENGTH) { 00084 av_log(NULL, AV_LOG_ERROR, "Read data packet header failed!\n"); 00085 return AVERROR(EIO); 00086 } 00087 chunk_type = AV_RL16(chunk_header); 00088 chunk_len = AV_RL16(chunk_header + 2); 00089 00090 switch (chunk_type) { 00091 case CHUNK_TYPE_END: 00092 case CHUNK_TYPE_STREAM_CHANGE: 00093 ext_header_len = 4; 00094 break; 00095 case CHUNK_TYPE_ASF_HEADER: 00096 case CHUNK_TYPE_DATA: 00097 ext_header_len = 8; 00098 break; 00099 default: 00100 av_log(NULL, AV_LOG_ERROR, "Strange chunk type %d\n", chunk_type); 00101 return AVERROR_INVALIDDATA; 00102 } 00103 00104 res = ffurl_read_complete(mms->mms_hd, ext_header, ext_header_len); 00105 if (res != ext_header_len) { 00106 av_log(NULL, AV_LOG_ERROR, "Read ext header failed!\n"); 00107 return AVERROR(EIO); 00108 } 00109 *len = chunk_len - ext_header_len; 00110 if (chunk_type == CHUNK_TYPE_END || chunk_type == CHUNK_TYPE_DATA) 00111 mmsh->chunk_seq = AV_RL32(ext_header); 00112 return chunk_type; 00113 } 00114 00115 static int read_data_packet(MMSHContext *mmsh, const int len) 00116 { 00117 MMSContext *mms = &mmsh->mms; 00118 int res; 00119 if (len > sizeof(mms->in_buffer)) { 00120 av_log(NULL, AV_LOG_ERROR, 00121 "Data packet length %d exceeds the in_buffer size %zu\n", 00122 len, sizeof(mms->in_buffer)); 00123 return AVERROR(EIO); 00124 } 00125 res = ffurl_read_complete(mms->mms_hd, mms->in_buffer, len); 00126 av_dlog(NULL, "Data packet len = %d\n", len); 00127 if (res != len) { 00128 av_log(NULL, AV_LOG_ERROR, "Read data packet failed!\n"); 00129 return AVERROR(EIO); 00130 } 00131 if (len > mms->asf_packet_len) { 00132 av_log(NULL, AV_LOG_ERROR, 00133 "Chunk length %d exceed packet length %d\n",len, mms->asf_packet_len); 00134 return AVERROR_INVALIDDATA; 00135 } else { 00136 memset(mms->in_buffer + len, 0, mms->asf_packet_len - len); // padding 00137 } 00138 mms->read_in_ptr = mms->in_buffer; 00139 mms->remaining_in_len = mms->asf_packet_len; 00140 return 0; 00141 } 00142 00143 static int get_http_header_data(MMSHContext *mmsh) 00144 { 00145 MMSContext *mms = &mmsh->mms; 00146 int res, len; 00147 ChunkType chunk_type; 00148 00149 for (;;) { 00150 len = 0; 00151 res = chunk_type = get_chunk_header(mmsh, &len); 00152 if (res < 0) { 00153 return res; 00154 } else if (chunk_type == CHUNK_TYPE_ASF_HEADER){ 00155 // get asf header and stored it 00156 if (!mms->header_parsed) { 00157 if (mms->asf_header) { 00158 if (len != mms->asf_header_size) { 00159 mms->asf_header_size = len; 00160 av_dlog(NULL, "Header len changed from %d to %d\n", 00161 mms->asf_header_size, len); 00162 av_freep(&mms->asf_header); 00163 } 00164 } 00165 mms->asf_header = av_mallocz(len); 00166 if (!mms->asf_header) { 00167 return AVERROR(ENOMEM); 00168 } 00169 mms->asf_header_size = len; 00170 } 00171 if (len > mms->asf_header_size) { 00172 av_log(NULL, AV_LOG_ERROR, 00173 "Asf header packet len = %d exceed the asf header buf size %d\n", 00174 len, mms->asf_header_size); 00175 return AVERROR(EIO); 00176 } 00177 res = ffurl_read_complete(mms->mms_hd, mms->asf_header, len); 00178 if (res != len) { 00179 av_log(NULL, AV_LOG_ERROR, 00180 "Recv asf header data len %d != expected len %d\n", res, len); 00181 return AVERROR(EIO); 00182 } 00183 mms->asf_header_size = len; 00184 if (!mms->header_parsed) { 00185 res = ff_mms_asf_header_parser(mms); 00186 mms->header_parsed = 1; 00187 return res; 00188 } 00189 } else if (chunk_type == CHUNK_TYPE_DATA) { 00190 // read data packet and do padding 00191 return read_data_packet(mmsh, len); 00192 } else { 00193 if (len) { 00194 if (len > sizeof(mms->in_buffer)) { 00195 av_log(NULL, AV_LOG_ERROR, 00196 "Other packet len = %d exceed the in_buffer size %zu\n", 00197 len, sizeof(mms->in_buffer)); 00198 return AVERROR(EIO); 00199 } 00200 res = ffurl_read_complete(mms->mms_hd, mms->in_buffer, len); 00201 if (res != len) { 00202 av_log(NULL, AV_LOG_ERROR, "Read other chunk type data failed!\n"); 00203 return AVERROR(EIO); 00204 } else { 00205 av_dlog(NULL, "Skip chunk type %d \n", chunk_type); 00206 continue; 00207 } 00208 } 00209 } 00210 } 00211 return 0; 00212 } 00213 00214 static int mmsh_open(URLContext *h, const char *uri, int flags) 00215 { 00216 int i, port, err; 00217 char httpname[256], path[256], host[128], location[1024]; 00218 char *stream_selection = NULL; 00219 char headers[1024]; 00220 MMSHContext *mmsh; 00221 MMSContext *mms; 00222 00223 mmsh = h->priv_data = av_mallocz(sizeof(MMSHContext)); 00224 if (!h->priv_data) 00225 return AVERROR(ENOMEM); 00226 mmsh->request_seq = h->is_streamed = 1; 00227 mms = &mmsh->mms; 00228 av_strlcpy(location, uri, sizeof(location)); 00229 00230 av_url_split(NULL, 0, NULL, 0, 00231 host, sizeof(host), &port, path, sizeof(path), location); 00232 if (port<0) 00233 port = 80; // default mmsh protocol port 00234 ff_url_join(httpname, sizeof(httpname), "http", NULL, host, port, "%s", path); 00235 00236 if (ffurl_alloc(&mms->mms_hd, httpname, AVIO_FLAG_READ) < 0) { 00237 return AVERROR(EIO); 00238 } 00239 00240 snprintf(headers, sizeof(headers), 00241 "Accept: */*\r\n" 00242 USERAGENT 00243 "Host: %s:%d\r\n" 00244 "Pragma: no-cache,rate=1.000000,stream-time=0," 00245 "stream-offset=0:0,request-context=%u,max-duration=0\r\n" 00246 CLIENTGUID 00247 "Connection: Close\r\n\r\n", 00248 host, port, mmsh->request_seq++); 00249 ff_http_set_headers(mms->mms_hd, headers); 00250 00251 err = ffurl_connect(mms->mms_hd); 00252 if (err) { 00253 goto fail; 00254 } 00255 err = get_http_header_data(mmsh); 00256 if (err) { 00257 av_log(NULL, AV_LOG_ERROR, "Get http header data failed!\n"); 00258 goto fail; 00259 } 00260 00261 // close the socket and then reopen it for sending the second play request. 00262 ffurl_close(mms->mms_hd); 00263 memset(headers, 0, sizeof(headers)); 00264 if (ffurl_alloc(&mms->mms_hd, httpname, AVIO_FLAG_READ) < 0) { 00265 return AVERROR(EIO); 00266 } 00267 stream_selection = av_mallocz(mms->stream_num * 19 + 1); 00268 if (!stream_selection) 00269 return AVERROR(ENOMEM); 00270 for (i = 0; i < mms->stream_num; i++) { 00271 char tmp[20]; 00272 err = snprintf(tmp, sizeof(tmp), "ffff:%d:0 ", mms->streams[i].id); 00273 if (err < 0) 00274 goto fail; 00275 av_strlcat(stream_selection, tmp, mms->stream_num * 19 + 1); 00276 } 00277 // send play request 00278 err = snprintf(headers, sizeof(headers), 00279 "Accept: */*\r\n" 00280 USERAGENT 00281 "Host: %s:%d\r\n" 00282 "Pragma: no-cache,rate=1.000000,request-context=%u\r\n" 00283 "Pragma: xPlayStrm=1\r\n" 00284 CLIENTGUID 00285 "Pragma: stream-switch-count=%d\r\n" 00286 "Pragma: stream-switch-entry=%s\r\n" 00287 "Connection: Close\r\n\r\n", 00288 host, port, mmsh->request_seq++, mms->stream_num, stream_selection); 00289 av_freep(&stream_selection); 00290 if (err < 0) { 00291 av_log(NULL, AV_LOG_ERROR, "Build play request failed!\n"); 00292 goto fail; 00293 } 00294 av_dlog(NULL, "out_buffer is %s", headers); 00295 ff_http_set_headers(mms->mms_hd, headers); 00296 00297 err = ffurl_connect(mms->mms_hd); 00298 if (err) { 00299 goto fail; 00300 } 00301 00302 err = get_http_header_data(mmsh); 00303 if (err) { 00304 av_log(NULL, AV_LOG_ERROR, "Get http header data failed!\n"); 00305 goto fail; 00306 } 00307 00308 av_dlog(NULL, "Connection successfully open\n"); 00309 return 0; 00310 fail: 00311 av_freep(&stream_selection); 00312 mmsh_close(h); 00313 av_dlog(NULL, "Connection failed with error %d\n", err); 00314 return err; 00315 } 00316 00317 static int handle_chunk_type(MMSHContext *mmsh) 00318 { 00319 MMSContext *mms = &mmsh->mms; 00320 int res, len = 0; 00321 ChunkType chunk_type; 00322 chunk_type = get_chunk_header(mmsh, &len); 00323 00324 switch (chunk_type) { 00325 case CHUNK_TYPE_END: 00326 mmsh->chunk_seq = 0; 00327 av_log(NULL, AV_LOG_ERROR, "Stream ended!\n"); 00328 return AVERROR(EIO); 00329 case CHUNK_TYPE_STREAM_CHANGE: 00330 mms->header_parsed = 0; 00331 if (res = get_http_header_data(mmsh)) { 00332 av_log(NULL, AV_LOG_ERROR,"Stream changed! Failed to get new header!\n"); 00333 return res; 00334 } 00335 break; 00336 case CHUNK_TYPE_DATA: 00337 return read_data_packet(mmsh, len); 00338 default: 00339 av_log(NULL, AV_LOG_ERROR, "Recv other type packet %d\n", chunk_type); 00340 return AVERROR_INVALIDDATA; 00341 } 00342 return 0; 00343 } 00344 00345 static int mmsh_read(URLContext *h, uint8_t *buf, int size) 00346 { 00347 int res = 0; 00348 MMSHContext *mmsh = h->priv_data; 00349 MMSContext *mms = &mmsh->mms; 00350 do { 00351 if (mms->asf_header_read_size < mms->asf_header_size) { 00352 // copy asf header into buffer 00353 res = ff_mms_read_header(mms, buf, size); 00354 } else { 00355 if (!mms->remaining_in_len && (res = handle_chunk_type(mmsh))) 00356 return res; 00357 res = ff_mms_read_data(mms, buf, size); 00358 } 00359 } while (!res); 00360 return res; 00361 } 00362 00363 URLProtocol ff_mmsh_protocol = { 00364 .name = "mmsh", 00365 .url_open = mmsh_open, 00366 .url_read = mmsh_read, 00367 .url_write = NULL, 00368 .url_seek = NULL, 00369 .url_close = mmsh_close, 00370 };