2 * MMS protocol over HTTP
3 * Copyright (c) 2010 Zhentan Feng <spyfeng at gmail dot com>
5 * This file is part of FFmpeg.
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.
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.
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
24 * Windows Media HTTP Streaming Protocol.
25 * http://msdn.microsoft.com/en-us/library/cc251059(PROT.10).aspx
29 #include "libavutil/intreadwrite.h"
30 #include "libavutil/avstring.h"
31 #include "libavutil/mem.h"
32 #include "libavutil/opt.h"
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.
42 #define USERAGENT "User-Agent: NSPlayer/4.1.0.3856\r\n"
44 // the GUID value can be changed to any valid value.
45 #define CLIENTGUID "Pragma: xClientGUID={c77e7400-738a-11d2-9add-0020af0a3278}\r\n"
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.
51 CHUNK_TYPE_DATA
= 0x4424,
52 CHUNK_TYPE_ASF_HEADER
= 0x4824,
53 CHUNK_TYPE_END
= 0x4524,
54 CHUNK_TYPE_STREAM_CHANGE
= 0x4324,
57 typedef struct MMSHContext
{
59 uint8_t location
[1024];
60 int request_seq
; ///< request packet sequence
61 int chunk_seq
; ///< data packet sequence
64 static int mmsh_close(URLContext
*h
)
66 MMSHContext
*mmsh
= (MMSHContext
*)h
->priv_data
;
67 MMSContext
*mms
= &mmsh
->mms
;
68 ffurl_closep(&mms
->mms_hd
);
69 av_freep(&mms
->streams
);
70 av_freep(&mms
->asf_header
);
74 static ChunkType
get_chunk_header(MMSHContext
*mmsh
, int *len
)
76 MMSContext
*mms
= &mmsh
->mms
;
77 uint8_t chunk_header
[CHUNK_HEADER_LENGTH
];
78 uint8_t ext_header
[EXT_HEADER_LENGTH
];
80 int chunk_len
, res
, ext_header_len
;
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");
87 chunk_type
= AV_RL16(chunk_header
);
88 chunk_len
= AV_RL16(chunk_header
+ 2);
92 case CHUNK_TYPE_STREAM_CHANGE
:
95 case CHUNK_TYPE_ASF_HEADER
:
100 av_log(NULL
, AV_LOG_ERROR
, "Strange chunk type %d\n", chunk_type
);
101 return AVERROR_INVALIDDATA
;
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");
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
);
115 static int read_data_packet(MMSHContext
*mmsh
, const int len
)
117 MMSContext
*mms
= &mmsh
->mms
;
119 if (len
> sizeof(mms
->in_buffer
)) {
120 av_log(NULL
, AV_LOG_ERROR
,
121 "Data packet length %d exceeds the in_buffer size %"SIZE_SPECIFIER
"\n",
122 len
, sizeof(mms
->in_buffer
));
125 res
= ffurl_read_complete(mms
->mms_hd
, mms
->in_buffer
, len
);
126 av_log(NULL
, AV_LOG_TRACE
, "Data packet len = %d\n", len
);
128 av_log(NULL
, AV_LOG_ERROR
, "Read data packet failed!\n");
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
;
136 memset(mms
->in_buffer
+ len
, 0, mms
->asf_packet_len
- len
); // padding
138 mms
->read_in_ptr
= mms
->in_buffer
;
139 mms
->remaining_in_len
= mms
->asf_packet_len
;
143 static int get_http_header_data(MMSHContext
*mmsh
)
145 MMSContext
*mms
= &mmsh
->mms
;
147 ChunkType chunk_type
;
151 res
= chunk_type
= get_chunk_header(mmsh
, &len
);
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_log(NULL
, AV_LOG_TRACE
, "Header len changed from %d to %d\n",
161 mms
->asf_header_size
, len
);
162 av_freep(&mms
->asf_header
);
165 mms
->asf_header
= av_mallocz(len
);
166 if (!mms
->asf_header
) {
167 return AVERROR(ENOMEM
);
169 mms
->asf_header_size
= len
;
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
);
177 res
= ffurl_read_complete(mms
->mms_hd
, mms
->asf_header
, len
);
179 av_log(NULL
, AV_LOG_ERROR
,
180 "Recv asf header data len %d != expected len %d\n", res
, len
);
183 mms
->asf_header_size
= len
;
184 if (!mms
->header_parsed
) {
185 res
= ff_mms_asf_header_parser(mms
);
186 mms
->header_parsed
= 1;
189 } else if (chunk_type
== CHUNK_TYPE_DATA
) {
190 // read data packet and do padding
191 return read_data_packet(mmsh
, 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 %"SIZE_SPECIFIER
"\n",
197 len
, sizeof(mms
->in_buffer
));
200 res
= ffurl_read_complete(mms
->mms_hd
, mms
->in_buffer
, len
);
202 av_log(NULL
, AV_LOG_ERROR
, "Read other chunk type data failed!\n");
205 av_log(NULL
, AV_LOG_TRACE
, "Skip chunk type %d \n", chunk_type
);
213 static int mmsh_open_internal(URLContext
*h
, const char *uri
, int flags
, int timestamp
, int64_t pos
)
216 char httpname
[256], path
[256], host
[128];
217 char *stream_selection
= NULL
;
219 MMSHContext
*mmsh
= h
->priv_data
;
222 mmsh
->request_seq
= h
->is_streamed
= 1;
224 av_strlcpy(mmsh
->location
, uri
, sizeof(mmsh
->location
));
226 av_url_split(NULL
, 0, NULL
, 0,
227 host
, sizeof(host
), &port
, path
, sizeof(path
), mmsh
->location
);
229 port
= 80; // default mmsh protocol port
230 ff_url_join(httpname
, sizeof(httpname
), "http", NULL
, host
, port
, "%s", path
);
232 if (ffurl_alloc(&mms
->mms_hd
, httpname
, AVIO_FLAG_READ
,
233 &h
->interrupt_callback
) < 0) {
237 snprintf(headers
, sizeof(headers
),
241 "Pragma: no-cache,rate=1.000000,stream-time=0,"
242 "stream-offset=0:0,request-context=%u,max-duration=0\r\n"
244 "Connection: Close\r\n",
245 host
, port
, mmsh
->request_seq
++);
246 av_opt_set(mms
->mms_hd
->priv_data
, "headers", headers
, 0);
248 if (!mms
->mms_hd
->protocol_whitelist
&& h
->protocol_whitelist
) {
249 mms
->mms_hd
->protocol_whitelist
= av_strdup(h
->protocol_whitelist
);
250 if (!mms
->mms_hd
->protocol_whitelist
) {
251 err
= AVERROR(ENOMEM
);
256 err
= ffurl_connect(mms
->mms_hd
, NULL
);
260 err
= get_http_header_data(mmsh
);
262 av_log(NULL
, AV_LOG_ERROR
, "Get http header data failed!\n");
266 // close the socket and then reopen it for sending the second play request.
267 ffurl_closep(&mms
->mms_hd
);
268 memset(headers
, 0, sizeof(headers
));
269 if ((err
= ffurl_alloc(&mms
->mms_hd
, httpname
, AVIO_FLAG_READ
,
270 &h
->interrupt_callback
)) < 0) {
273 stream_selection
= av_mallocz(mms
->stream_num
* 19 + 1);
274 if (!stream_selection
)
275 return AVERROR(ENOMEM
);
276 for (i
= 0; i
< mms
->stream_num
; i
++) {
278 err
= snprintf(tmp
, sizeof(tmp
), "ffff:%d:0 ", mms
->streams
[i
].id
);
281 av_strlcat(stream_selection
, tmp
, mms
->stream_num
* 19 + 1);
284 err
= snprintf(headers
, sizeof(headers
),
288 "Pragma: no-cache,rate=1.000000,request-context=%u\r\n"
289 "Pragma: xPlayStrm=1\r\n"
291 "Pragma: stream-switch-count=%d\r\n"
292 "Pragma: stream-switch-entry=%s\r\n"
293 "Pragma: no-cache,rate=1.000000,stream-time=%u"
294 "Connection: Close\r\n",
295 host
, port
, mmsh
->request_seq
++, mms
->stream_num
, stream_selection
, timestamp
);
296 av_freep(&stream_selection
);
298 av_log(NULL
, AV_LOG_ERROR
, "Build play request failed!\n");
301 av_log(NULL
, AV_LOG_TRACE
, "out_buffer is %s", headers
);
302 av_opt_set(mms
->mms_hd
->priv_data
, "headers", headers
, 0);
304 err
= ffurl_connect(mms
->mms_hd
, NULL
);
309 err
= get_http_header_data(mmsh
);
311 av_log(NULL
, AV_LOG_ERROR
, "Get http header data failed!\n");
315 av_log(NULL
, AV_LOG_TRACE
, "Connection successfully open\n");
318 av_freep(&stream_selection
);
320 av_log(NULL
, AV_LOG_TRACE
, "Connection failed with error %d\n", err
);
324 static int mmsh_open(URLContext
*h
, const char *uri
, int flags
)
326 return mmsh_open_internal(h
, uri
, flags
, 0, 0);
329 static int handle_chunk_type(MMSHContext
*mmsh
)
331 MMSContext
*mms
= &mmsh
->mms
;
333 ChunkType chunk_type
;
334 chunk_type
= get_chunk_header(mmsh
, &len
);
336 switch (chunk_type
) {
339 av_log(NULL
, AV_LOG_ERROR
, "Stream ended!\n");
341 case CHUNK_TYPE_STREAM_CHANGE
:
342 mms
->header_parsed
= 0;
343 if (res
= get_http_header_data(mmsh
)) {
344 av_log(NULL
, AV_LOG_ERROR
,"Stream changed! Failed to get new header!\n");
348 case CHUNK_TYPE_DATA
:
349 return read_data_packet(mmsh
, len
);
351 av_log(NULL
, AV_LOG_ERROR
, "Recv other type packet %d\n", chunk_type
);
352 return AVERROR_INVALIDDATA
;
357 static int mmsh_read(URLContext
*h
, uint8_t *buf
, int size
)
360 MMSHContext
*mmsh
= h
->priv_data
;
361 MMSContext
*mms
= &mmsh
->mms
;
363 if (mms
->asf_header_read_size
< mms
->asf_header_size
) {
364 // copy asf header into buffer
365 res
= ff_mms_read_header(mms
, buf
, size
);
367 if (!mms
->remaining_in_len
&& (res
= handle_chunk_type(mmsh
)))
369 res
= ff_mms_read_data(mms
, buf
, size
);
375 static int64_t mmsh_read_seek(void *opaque
, int stream_index
,
376 int64_t timestamp
, int flags
)
378 URLContext
*h
= opaque
;
379 MMSHContext
*mmsh_old
= h
->priv_data
;
380 MMSHContext
*mmsh
= av_mallocz(sizeof(*mmsh
));
384 return AVERROR(ENOMEM
);
387 ret
= mmsh_open_internal(h
, mmsh_old
->location
, 0, FFMAX(timestamp
, 0), 0);
389 h
->priv_data
= mmsh_old
;
393 mmsh
->mms
.asf_header_read_size
= mmsh
->mms
.asf_header_size
;
395 h
->priv_data
= mmsh_old
;
402 static int64_t mmsh_seek(URLContext
*h
, int64_t pos
, int whence
)
404 MMSHContext
*mmsh
= h
->priv_data
;
405 MMSContext
*mms
= &mmsh
->mms
;
407 if(pos
== 0 && whence
== SEEK_CUR
)
408 return mms
->asf_header_read_size
+ mms
->remaining_in_len
+ mmsh
->chunk_seq
* (int64_t)mms
->asf_packet_len
;
409 return AVERROR(ENOSYS
);
412 const URLProtocol ff_mmsh_protocol
= {
414 .url_open
= mmsh_open
,
415 .url_read
= mmsh_read
,
416 .url_seek
= mmsh_seek
,
417 .url_close
= mmsh_close
,
418 .url_read_seek
= mmsh_read_seek
,
419 .priv_data_size
= sizeof(MMSHContext
),
420 .flags
= URL_PROTOCOL_FLAG_NETWORK
,
421 .default_whitelist
= "http,tcp",