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/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,
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
;
69 ffurl_closep(&mms
->mms_hd
);
70 av_freep(&mms
->streams
);
71 av_freep(&mms
->asf_header
);
75 static ChunkType
get_chunk_header(MMSHContext
*mmsh
, int *len
)
77 MMSContext
*mms
= &mmsh
->mms
;
78 uint8_t chunk_header
[CHUNK_HEADER_LENGTH
];
79 uint8_t ext_header
[EXT_HEADER_LENGTH
];
81 int chunk_len
, res
, ext_header_len
;
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");
88 chunk_type
= AV_RL16(chunk_header
);
89 chunk_len
= AV_RL16(chunk_header
+ 2);
93 case CHUNK_TYPE_STREAM_CHANGE
:
96 case CHUNK_TYPE_ASF_HEADER
:
101 av_log(NULL
, AV_LOG_ERROR
, "Strange chunk type %d\n", chunk_type
);
102 return AVERROR_INVALIDDATA
;
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");
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
);
116 static int read_data_packet(MMSHContext
*mmsh
, const int len
)
118 MMSContext
*mms
= &mmsh
->mms
;
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
));
126 res
= ffurl_read_complete(mms
->mms_hd
, mms
->in_buffer
, len
);
127 av_dlog(NULL
, "Data packet len = %d\n", len
);
129 av_log(NULL
, AV_LOG_ERROR
, "Read data packet failed!\n");
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
;
137 memset(mms
->in_buffer
+ len
, 0, mms
->asf_packet_len
- len
); // padding
139 mms
->read_in_ptr
= mms
->in_buffer
;
140 mms
->remaining_in_len
= mms
->asf_packet_len
;
144 static int get_http_header_data(MMSHContext
*mmsh
)
146 MMSContext
*mms
= &mmsh
->mms
;
148 ChunkType chunk_type
;
152 res
= chunk_type
= get_chunk_header(mmsh
, &len
);
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
);
166 mms
->asf_header
= av_mallocz(len
);
167 if (!mms
->asf_header
) {
168 return AVERROR(ENOMEM
);
170 mms
->asf_header_size
= len
;
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
);
178 res
= ffurl_read_complete(mms
->mms_hd
, mms
->asf_header
, len
);
180 av_log(NULL
, AV_LOG_ERROR
,
181 "Recv asf header data len %d != expected len %d\n", res
, len
);
184 mms
->asf_header_size
= len
;
185 if (!mms
->header_parsed
) {
186 res
= ff_mms_asf_header_parser(mms
);
187 mms
->header_parsed
= 1;
190 } else if (chunk_type
== CHUNK_TYPE_DATA
) {
191 // read data packet and do padding
192 return read_data_packet(mmsh
, 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
));
201 res
= ffurl_read_complete(mms
->mms_hd
, mms
->in_buffer
, len
);
203 av_log(NULL
, AV_LOG_ERROR
, "Read other chunk type data failed!\n");
206 av_dlog(NULL
, "Skip chunk type %d \n", chunk_type
);
214 static int mmsh_open_internal(URLContext
*h
, const char *uri
, int flags
, int timestamp
, int64_t pos
)
217 char httpname
[256], path
[256], host
[128];
218 char *stream_selection
= NULL
;
220 MMSHContext
*mmsh
= h
->priv_data
;
223 mmsh
->request_seq
= h
->is_streamed
= 1;
225 av_strlcpy(mmsh
->location
, uri
, sizeof(mmsh
->location
));
227 av_url_split(NULL
, 0, NULL
, 0,
228 host
, sizeof(host
), &port
, path
, sizeof(path
), mmsh
->location
);
230 port
= 80; // default mmsh protocol port
231 ff_url_join(httpname
, sizeof(httpname
), "http", NULL
, host
, port
, "%s", path
);
233 if (ffurl_alloc(&mms
->mms_hd
, httpname
, AVIO_FLAG_READ
,
234 &h
->interrupt_callback
) < 0) {
238 snprintf(headers
, sizeof(headers
),
242 "Pragma: no-cache,rate=1.000000,stream-time=0,"
243 "stream-offset=0:0,request-context=%u,max-duration=0\r\n"
245 "Connection: Close\r\n",
246 host
, port
, mmsh
->request_seq
++);
247 av_opt_set(mms
->mms_hd
->priv_data
, "headers", headers
, 0);
249 err
= ffurl_connect(mms
->mms_hd
, NULL
);
253 err
= get_http_header_data(mmsh
);
255 av_log(NULL
, AV_LOG_ERROR
, "Get http header data failed!\n");
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) {
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
++) {
271 err
= snprintf(tmp
, sizeof(tmp
), "ffff:%d:0 ", mms
->streams
[i
].id
);
274 av_strlcat(stream_selection
, tmp
, mms
->stream_num
* 19 + 1);
277 err
= snprintf(headers
, sizeof(headers
),
281 "Pragma: no-cache,rate=1.000000,request-context=%u\r\n"
282 "Pragma: xPlayStrm=1\r\n"
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
);
291 av_log(NULL
, AV_LOG_ERROR
, "Build play request failed!\n");
294 av_dlog(NULL
, "out_buffer is %s", headers
);
295 av_opt_set(mms
->mms_hd
->priv_data
, "headers", headers
, 0);
297 err
= ffurl_connect(mms
->mms_hd
, NULL
);
302 err
= get_http_header_data(mmsh
);
304 av_log(NULL
, AV_LOG_ERROR
, "Get http header data failed!\n");
308 av_dlog(NULL
, "Connection successfully open\n");
311 av_freep(&stream_selection
);
313 av_dlog(NULL
, "Connection failed with error %d\n", err
);
317 static int mmsh_open(URLContext
*h
, const char *uri
, int flags
)
319 return mmsh_open_internal(h
, uri
, flags
, 0, 0);
322 static int handle_chunk_type(MMSHContext
*mmsh
)
324 MMSContext
*mms
= &mmsh
->mms
;
326 ChunkType chunk_type
;
327 chunk_type
= get_chunk_header(mmsh
, &len
);
329 switch (chunk_type
) {
332 av_log(NULL
, AV_LOG_ERROR
, "Stream ended!\n");
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");
341 case CHUNK_TYPE_DATA
:
342 return read_data_packet(mmsh
, len
);
344 av_log(NULL
, AV_LOG_ERROR
, "Recv other type packet %d\n", chunk_type
);
345 return AVERROR_INVALIDDATA
;
350 static int mmsh_read(URLContext
*h
, uint8_t *buf
, int size
)
353 MMSHContext
*mmsh
= h
->priv_data
;
354 MMSContext
*mms
= &mmsh
->mms
;
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
);
360 if (!mms
->remaining_in_len
&& (res
= handle_chunk_type(mmsh
)))
362 res
= ff_mms_read_data(mms
, buf
, size
);
368 static int64_t mmsh_read_seek(URLContext
*h
, int stream_index
,
369 int64_t timestamp
, int flags
)
371 MMSHContext
*mmsh_old
= h
->priv_data
;
372 MMSHContext
*mmsh
= av_mallocz(sizeof(*mmsh
));
376 return AVERROR(ENOMEM
);
379 ret
= mmsh_open_internal(h
, mmsh_old
->location
, 0, FFMAX(timestamp
, 0), 0);
381 h
->priv_data
= mmsh_old
;
385 mmsh
->mms
.asf_header_read_size
= mmsh
->mms
.asf_header_size
;
387 h
->priv_data
= mmsh_old
;
394 static int64_t mmsh_seek(URLContext
*h
, int64_t pos
, int whence
)
396 MMSHContext
*mmsh
= h
->priv_data
;
397 MMSContext
*mms
= &mmsh
->mms
;
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
);
404 URLProtocol ff_mmsh_protocol
= {
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
,