FFmpeg
mmsh.c
Go to the documentation of this file.
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.
50 typedef enum {
51  CHUNK_TYPE_DATA = 0x4424,
53  CHUNK_TYPE_END = 0x4524,
55 } ChunkType;
56 
57 typedef struct MMSHContext {
60  int request_seq; ///< request packet sequence
61  int chunk_seq; ///< data packet sequence
62 } MMSHContext;
63 
64 static int mmsh_close(URLContext *h)
65 {
66  MMSHContext *mmsh = (MMSHContext *)h->priv_data;
67  MMSContext *mms = &mmsh->mms;
69  av_freep(&mms->streams);
71  return 0;
72 }
73 
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:
93  ext_header_len = 4;
94  break;
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 
115 static 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)) {
121  "Data packet length %d exceeds the in_buffer size %"SIZE_SPECIFIER"\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_log(NULL, AV_LOG_TRACE, "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) {
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 
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_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);
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) {
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) {
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)) {
196  "Other packet len = %d exceed the in_buffer size %"SIZE_SPECIFIER"\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_log(NULL, AV_LOG_TRACE, "Skip chunk type %d \n", chunk_type);
206  continue;
207  }
208  }
209  }
210  }
211 }
212 
213 static int mmsh_open_internal(URLContext *h, const char *uri, int flags, int timestamp, int64_t pos)
214 {
215  int i, port, err;
216  char httpname[256], path[256], host[128];
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(mmsh->location, uri, sizeof(mmsh->location));
225 
226  av_url_split(NULL, 0, NULL, 0,
227  host, sizeof(host), &port, path, sizeof(path), mmsh->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",
245  host, port, mmsh->request_seq++);
246  av_opt_set(mms->mms_hd->priv_data, "headers", headers, 0);
247 
248  if (!mms->mms_hd->protocol_whitelist && h->protocol_whitelist) {
250  if (!mms->mms_hd->protocol_whitelist) {
251  err = AVERROR(ENOMEM);
252  goto fail;
253  }
254  }
255 
256  err = ffurl_connect(mms->mms_hd, NULL);
257  if (err) {
258  goto fail;
259  }
260  err = get_http_header_data(mmsh);
261  if (err) {
262  av_log(NULL, AV_LOG_ERROR, "Get http header data failed!\n");
263  goto fail;
264  }
265 
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) {
271  goto fail;
272  }
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++) {
277  char tmp[20];
278  err = snprintf(tmp, sizeof(tmp), "ffff:%d:0 ", mms->streams[i].id);
279  if (err < 0)
280  goto fail;
281  av_strlcat(stream_selection, tmp, mms->stream_num * 19 + 1);
282  }
283  // send play request
284  err = snprintf(headers, sizeof(headers),
285  "Accept: */*\r\n"
286  USERAGENT
287  "Host: %s:%d\r\n"
288  "Pragma: no-cache,rate=1.000000,request-context=%u\r\n"
289  "Pragma: xPlayStrm=1\r\n"
290  CLIENTGUID
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);
297  if (err < 0) {
298  av_log(NULL, AV_LOG_ERROR, "Build play request failed!\n");
299  goto fail;
300  }
301  av_log(NULL, AV_LOG_TRACE, "out_buffer is %s", headers);
302  av_opt_set(mms->mms_hd->priv_data, "headers", headers, 0);
303 
304  err = ffurl_connect(mms->mms_hd, NULL);
305  if (err) {
306  goto fail;
307  }
308 
309  err = get_http_header_data(mmsh);
310  if (err) {
311  av_log(NULL, AV_LOG_ERROR, "Get http header data failed!\n");
312  goto fail;
313  }
314 
315  av_log(NULL, AV_LOG_TRACE, "Connection successfully open\n");
316  return 0;
317 fail:
318  av_freep(&stream_selection);
319  mmsh_close(h);
320  av_log(NULL, AV_LOG_TRACE, "Connection failed with error %d\n", err);
321  return err;
322 }
323 
324 static int mmsh_open(URLContext *h, const char *uri, int flags)
325 {
326  return mmsh_open_internal(h, uri, flags, 0, 0);
327 }
328 
330 {
331  MMSContext *mms = &mmsh->mms;
332  int res, len = 0;
333  ChunkType chunk_type;
334  chunk_type = get_chunk_header(mmsh, &len);
335 
336  switch (chunk_type) {
337  case CHUNK_TYPE_END:
338  mmsh->chunk_seq = 0;
339  av_log(NULL, AV_LOG_ERROR, "Stream ended!\n");
340  return AVERROR(EIO);
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");
345  return res;
346  }
347  break;
348  case CHUNK_TYPE_DATA:
349  return read_data_packet(mmsh, len);
350  default:
351  av_log(NULL, AV_LOG_ERROR, "Recv other type packet %d\n", chunk_type);
352  return AVERROR_INVALIDDATA;
353  }
354  return 0;
355 }
356 
357 static int mmsh_read(URLContext *h, uint8_t *buf, int size)
358 {
359  int res = 0;
360  MMSHContext *mmsh = h->priv_data;
361  MMSContext *mms = &mmsh->mms;
362  do {
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);
366  } else {
367  if (!mms->remaining_in_len && (res = handle_chunk_type(mmsh)))
368  return res;
369  res = ff_mms_read_data(mms, buf, size);
370  }
371  } while (!res);
372  return res;
373 }
374 
375 static int64_t mmsh_read_seek(URLContext *h, int stream_index,
376  int64_t timestamp, int flags)
377 {
378  MMSHContext *mmsh_old = h->priv_data;
379  MMSHContext *mmsh = av_mallocz(sizeof(*mmsh));
380  int ret;
381 
382  if (!mmsh)
383  return AVERROR(ENOMEM);
384 
385  h->priv_data = mmsh;
386  ret= mmsh_open_internal(h, mmsh_old->location, 0, FFMAX(timestamp, 0), 0);
387  if(ret>=0){
388  h->priv_data = mmsh_old;
389  mmsh_close(h);
390  h->priv_data = mmsh;
391  av_free(mmsh_old);
393  }else {
394  h->priv_data = mmsh_old;
395  av_free(mmsh);
396  }
397 
398  return ret;
399 }
400 
401 static int64_t mmsh_seek(URLContext *h, int64_t pos, int whence)
402 {
403  MMSHContext *mmsh = h->priv_data;
404  MMSContext *mms = &mmsh->mms;
405 
406  if(pos == 0 && whence == SEEK_CUR)
407  return mms->asf_header_read_size + mms->remaining_in_len + mmsh->chunk_seq * (int64_t)mms->asf_packet_len;
408  return AVERROR(ENOSYS);
409 }
410 
412  .name = "mmsh",
413  .url_open = mmsh_open,
414  .url_read = mmsh_read,
415  .url_seek = mmsh_seek,
416  .url_close = mmsh_close,
417  .url_read_seek = mmsh_read_seek,
418  .priv_data_size = sizeof(MMSHContext),
420  .default_whitelist = "http,tcp",
421 };
static int mmsh_open(URLContext *h, const char *uri, int flags)
Definition: mmsh.c:324
void av_url_split(char *proto, int proto_size, char *authorization, int authorization_size, char *hostname, int hostname_size, int *port_ptr, char *path, int path_size, const char *url)
Split a URL string into components.
Definition: utils.c:4792
#define NULL
Definition: coverity.c:32
int request_seq
request packet sequence
Definition: mmsh.c:60
#define AVERROR_INVALIDDATA
Invalid data found when processing input.
Definition: error.h:59
URLContext * mms_hd
TCP connection handle.
Definition: mms.h:31
#define URL_PROTOCOL_FLAG_NETWORK
Definition: url.h:34
uint8_t * read_in_ptr
Pointer for reading from incoming buffer.
Definition: mms.h:43
ChunkType
Definition: g2meet.c:47
static int get_http_header_data(MMSHContext *mmsh)
Definition: mmsh.c:143
int is_streamed
true if streamed (no seek possible), default = false
Definition: url.h:45
int ffurl_connect(URLContext *uc, AVDictionary **options)
Connect an URLContext that has been allocated by ffurl_alloc.
Definition: avio.c:170
AVIOInterruptCB interrupt_callback
Definition: url.h:47
Definition: mms.h:30
static int read_data_packet(MMSHContext *mmsh, const int len)
Definition: mmsh.c:115
#define EXT_HEADER_LENGTH
Definition: mmsh.c:39
#define AVIO_FLAG_READ
read-only
Definition: avio.h:674
int ff_mms_read_header(MMSContext *mms, uint8_t *buf, const int size)
Definition: mms.c:29
int stream_num
stream numbers.
Definition: mms.h:56
void * av_mallocz(size_t size)
Allocate a memory block with alignment suitable for all memory accesses (including vectors if availab...
Definition: mem.c:237
uint64_t_TMPL AV_WL64 unsigned int_TMPL AV_WL32 unsigned int_TMPL AV_WL24 unsigned int_TMPL AV_RL16
Definition: bytestream.h:87
int header_parsed
The header has been received and parsed.
Definition: mms.h:51
static int handle_chunk_type(MMSHContext *mmsh)
Definition: mmsh.c:329
uint8_t * asf_header
Internal handling of the ASF header.
Definition: mms.h:49
uint8_t
AVOptions.
#define AV_LOG_TRACE
Extremely verbose debugging, useful for libav* development.
Definition: log.h:220
static int64_t mmsh_seek(URLContext *h, int64_t pos, int whence)
Definition: mmsh.c:401
int ff_mms_read_data(MMSContext *mms, uint8_t *buf, const int size)
Definition: mms.c:44
int asf_packet_len
Definition: mms.h:52
#define CHUNK_HEADER_LENGTH
Definition: mmsh.c:38
ptrdiff_t size
Definition: opengl_enc.c:100
#define av_log(a,...)
uint8_t in_buffer[65536]
Buffer for incoming packets.
Definition: mms.h:42
MMSContext mms
Definition: mmsh.c:58
int ffurl_alloc(URLContext **puc, const char *filename, int flags, const AVIOInterruptCB *int_cb)
Create a URLContext for accessing to the resource indicated by url, but do not initiate the connectio...
Definition: avio.c:297
#define AV_LOG_ERROR
Something went wrong and cannot losslessly be recovered.
Definition: log.h:194
const char * protocol_whitelist
Definition: url.h:49
unsigned int pos
Definition: spdifenc.c:410
static ChunkType get_chunk_header(MMSHContext *mmsh, int *len)
Definition: mmsh.c:74
#define FFMAX(a, b)
Definition: common.h:94
size_t av_strlcpy(char *dst, const char *src, size_t size)
Copy the string src to dst, but no more than size - 1 bytes, and null-terminate dst.
Definition: avstring.c:83
#define fail()
Definition: checkasm.h:123
uint8_t location[1024]
Definition: mmsh.c:59
int asf_header_read_size
Definition: mms.h:53
ChunkType
Definition: mmsh.c:50
int ffurl_closep(URLContext **hh)
Close the resource accessed by the URLContext h, and free the memory used by it.
Definition: avio.c:446
int ff_url_join(char *str, int size, const char *proto, const char *authorization, const char *hostname, int port, const char *fmt,...)
Definition: url.c:37
char * av_strdup(const char *s)
Duplicate a string.
Definition: mem.c:253
int remaining_in_len
Reading length from incoming buffer.
Definition: mms.h:44
Definition: url.h:38
void * priv_data
Definition: url.h:41
static int64_t mmsh_read_seek(URLContext *h, int stream_index, int64_t timestamp, int flags)
Definition: mmsh.c:375
static int mmsh_open_internal(URLContext *h, const char *uri, int flags, int timestamp, int64_t pos)
Definition: mmsh.c:213
#define snprintf
Definition: snprintf.h:34
int chunk_seq
data packet sequence
Definition: mmsh.c:61
size_t av_strlcat(char *dst, const char *src, size_t size)
Append the string src to the string dst, but to a total length of no more than size - 1 bytes...
Definition: avstring.c:93
const char * name
Definition: url.h:55
#define SIZE_SPECIFIER
Definition: internal.h:229
#define flags(name, subs,...)
Definition: cbs_av1.c:560
int asf_header_size
Size of stored ASF header.
Definition: mms.h:50
#define USERAGENT
Definition: mmsh.c:42
int ffurl_read_complete(URLContext *h, unsigned char *buf, int size)
Read as many bytes as possible (up to size), calling the read function multiple times if necessary...
Definition: avio.c:416
const URLProtocol ff_mmsh_protocol
Definition: mmsh.c:411
static int mmsh_close(URLContext *h)
Definition: mmsh.c:64
#define av_free(p)
int id
Definition: mms.h:27
int len
#define CLIENTGUID
Definition: mmsh.c:45
MMSStream * streams
Definition: mms.h:32
#define av_freep(p)
static int mmsh_read(URLContext *h, uint8_t *buf, int size)
Definition: mmsh.c:357
unbuffered private I/O API
Filter the word “frame” indicates either a video frame or a group of audio as stored in an AVFrame structure Format for each input and each output the list of supported formats For video that means pixel format For audio that means channel sample they are references to shared objects When the negotiation mechanism computes the intersection of the formats supported at each end of a all references to both lists are replaced with a reference to the intersection And when a single format is eventually chosen for a link amongst the remaining all references to the list are updated That means that if a filter requires that its input and output have the same format amongst a supported all it has to do is use a reference to the same list of formats query_formats can leave some formats unset and return AVERROR(EAGAIN) to cause the negotiation mechanism toagain later.That can be used by filters with complex requirements to use the format negotiated on one link to set the formats supported on another.Frame references ownership and permissions
uint64_t_TMPL AV_WL64 unsigned int_TMPL AV_RL32
Definition: bytestream.h:87
int av_opt_set(void *obj, const char *name, const char *val, int search_flags)
Definition: opt.c:465
int i
Definition: input.c:406
FFmpeg currently uses a custom build this text attempts to document some of its obscure features and options Makefile the full command issued by make and its output will be shown on the screen DBG Preprocess x86 external assembler files to a dbg asm file in the object which then gets compiled Helps in developing those assembler files DESTDIR Destination directory for the install useful to prepare packages or install FFmpeg in cross environments GEN Set to ‘1’ to generate the missing or mismatched references Makefile builds all the libraries and the executables fate Run the fate test note that you must have installed it fate list List all fate regression test targets install Install headers
Definition: build_system.txt:1
int ff_mms_asf_header_parser(MMSContext *mms)
Definition: mms.c:54
static uint8_t tmp[11]
Definition: aes_ctr.c:26