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;
68  if (mms->mms_hd)
70  av_freep(&mms->streams);
72  return 0;
73 }
74 
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:
94  ext_header_len = 4;
95  break;
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 
116 static 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)) {
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_log(NULL, AV_LOG_TRACE, "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) {
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 
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_log(NULL, AV_LOG_TRACE, "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) {
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) {
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)) {
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_log(NULL, AV_LOG_TRACE, "Skip chunk type %d \n", chunk_type);
207  continue;
208  }
209  }
210  }
211  }
212 }
213 
214 static 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  if (!mms->mms_hd->protocol_whitelist && h->protocol_whitelist) {
251  if (!mms->mms_hd->protocol_whitelist) {
252  err = AVERROR(ENOMEM);
253  goto fail;
254  }
255  }
256 
257  err = ffurl_connect(mms->mms_hd, NULL);
258  if (err) {
259  goto fail;
260  }
261  err = get_http_header_data(mmsh);
262  if (err) {
263  av_log(NULL, AV_LOG_ERROR, "Get http header data failed!\n");
264  goto fail;
265  }
266 
267  // close the socket and then reopen it for sending the second play request.
268  ffurl_close(mms->mms_hd);
269  memset(headers, 0, sizeof(headers));
270  if ((err = ffurl_alloc(&mms->mms_hd, httpname, AVIO_FLAG_READ,
271  &h->interrupt_callback)) < 0) {
272  goto fail;
273  }
274  stream_selection = av_mallocz(mms->stream_num * 19 + 1);
275  if (!stream_selection)
276  return AVERROR(ENOMEM);
277  for (i = 0; i < mms->stream_num; i++) {
278  char tmp[20];
279  err = snprintf(tmp, sizeof(tmp), "ffff:%d:0 ", mms->streams[i].id);
280  if (err < 0)
281  goto fail;
282  av_strlcat(stream_selection, tmp, mms->stream_num * 19 + 1);
283  }
284  // send play request
285  err = snprintf(headers, sizeof(headers),
286  "Accept: */*\r\n"
287  USERAGENT
288  "Host: %s:%d\r\n"
289  "Pragma: no-cache,rate=1.000000,request-context=%u\r\n"
290  "Pragma: xPlayStrm=1\r\n"
291  CLIENTGUID
292  "Pragma: stream-switch-count=%d\r\n"
293  "Pragma: stream-switch-entry=%s\r\n"
294  "Pragma: no-cache,rate=1.000000,stream-time=%u"
295  "Connection: Close\r\n",
296  host, port, mmsh->request_seq++, mms->stream_num, stream_selection, timestamp);
297  av_freep(&stream_selection);
298  if (err < 0) {
299  av_log(NULL, AV_LOG_ERROR, "Build play request failed!\n");
300  goto fail;
301  }
302  av_log(NULL, AV_LOG_TRACE, "out_buffer is %s", headers);
303  av_opt_set(mms->mms_hd->priv_data, "headers", headers, 0);
304 
305  err = ffurl_connect(mms->mms_hd, NULL);
306  if (err) {
307  goto fail;
308  }
309 
310  err = get_http_header_data(mmsh);
311  if (err) {
312  av_log(NULL, AV_LOG_ERROR, "Get http header data failed!\n");
313  goto fail;
314  }
315 
316  av_log(NULL, AV_LOG_TRACE, "Connection successfully open\n");
317  return 0;
318 fail:
319  av_freep(&stream_selection);
320  mmsh_close(h);
321  av_log(NULL, AV_LOG_TRACE, "Connection failed with error %d\n", err);
322  return err;
323 }
324 
325 static int mmsh_open(URLContext *h, const char *uri, int flags)
326 {
327  return mmsh_open_internal(h, uri, flags, 0, 0);
328 }
329 
331 {
332  MMSContext *mms = &mmsh->mms;
333  int res, len = 0;
334  ChunkType chunk_type;
335  chunk_type = get_chunk_header(mmsh, &len);
336 
337  switch (chunk_type) {
338  case CHUNK_TYPE_END:
339  mmsh->chunk_seq = 0;
340  av_log(NULL, AV_LOG_ERROR, "Stream ended!\n");
341  return AVERROR(EIO);
343  mms->header_parsed = 0;
344  if (res = get_http_header_data(mmsh)) {
345  av_log(NULL, AV_LOG_ERROR,"Stream changed! Failed to get new header!\n");
346  return res;
347  }
348  break;
349  case CHUNK_TYPE_DATA:
350  return read_data_packet(mmsh, len);
351  default:
352  av_log(NULL, AV_LOG_ERROR, "Recv other type packet %d\n", chunk_type);
353  return AVERROR_INVALIDDATA;
354  }
355  return 0;
356 }
357 
358 static int mmsh_read(URLContext *h, uint8_t *buf, int size)
359 {
360  int res = 0;
361  MMSHContext *mmsh = h->priv_data;
362  MMSContext *mms = &mmsh->mms;
363  do {
364  if (mms->asf_header_read_size < mms->asf_header_size) {
365  // copy asf header into buffer
366  res = ff_mms_read_header(mms, buf, size);
367  } else {
368  if (!mms->remaining_in_len && (res = handle_chunk_type(mmsh)))
369  return res;
370  res = ff_mms_read_data(mms, buf, size);
371  }
372  } while (!res);
373  return res;
374 }
375 
376 static int64_t mmsh_read_seek(URLContext *h, int stream_index,
377  int64_t timestamp, int flags)
378 {
379  MMSHContext *mmsh_old = h->priv_data;
380  MMSHContext *mmsh = av_mallocz(sizeof(*mmsh));
381  int ret;
382 
383  if (!mmsh)
384  return AVERROR(ENOMEM);
385 
386  h->priv_data = mmsh;
387  ret= mmsh_open_internal(h, mmsh_old->location, 0, FFMAX(timestamp, 0), 0);
388  if(ret>=0){
389  h->priv_data = mmsh_old;
390  mmsh_close(h);
391  h->priv_data = mmsh;
392  av_free(mmsh_old);
394  }else {
395  h->priv_data = mmsh_old;
396  av_free(mmsh);
397  }
398 
399  return ret;
400 }
401 
402 static int64_t mmsh_seek(URLContext *h, int64_t pos, int whence)
403 {
404  MMSHContext *mmsh = h->priv_data;
405  MMSContext *mms = &mmsh->mms;
406 
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);
410 }
411 
413  .name = "mmsh",
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),
421  .default_whitelist = "http,tcp",
422 };
static int mmsh_open(URLContext *h, const char *uri, int flags)
Definition: mmsh.c:325
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:4742
#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:144
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:166
AVIOInterruptCB interrupt_callback
Definition: url.h:47
Definition: mms.h:30
static int read_data_packet(MMSHContext *mmsh, const int len)
Definition: mmsh.c:116
#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:236
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:330
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:202
static int64_t mmsh_seek(URLContext *h, int64_t pos, int whence)
Definition: mmsh.c:402
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:290
#define i(width, name, range_min, range_max)
Definition: cbs_h2645.c:259
#define AV_LOG_ERROR
Something went wrong and cannot losslessly be recovered.
Definition: log.h:176
const char * protocol_whitelist
Definition: url.h:49
static ChunkType get_chunk_header(MMSHContext *mmsh, int *len)
Definition: mmsh.c:75
#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:121
uint8_t location[1024]
Definition: mmsh.c:59
int asf_header_read_size
Definition: mms.h:53
ChunkType
Definition: mmsh.c:50
if(ret)
int ffurl_closep(URLContext **hh)
Close the resource accessed by the URLContext h, and free the memory used by it.
Definition: avio.c:444
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:36
char * av_strdup(const char *s)
Duplicate a string.
Definition: mem.c:251
int remaining_in_len
Reading length from incoming buffer.
Definition: mms.h:44
void * buf
Definition: avisynth_c.h:766
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:376
static int mmsh_open_internal(URLContext *h, const char *uri, int flags, int timestamp, int64_t pos)
Definition: mmsh.c:214
#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:262
#define flags(name, subs,...)
Definition: cbs_av1.c:561
int asf_header_size
Size of stored ASF header.
Definition: mms.h:50
int ffurl_close(URLContext *h)
Definition: avio.c:467
#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:414
const URLProtocol ff_mmsh_protocol
Definition: mmsh.c:412
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:358
unbuffered private I/O API
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
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:449
int ff_mms_asf_header_parser(MMSContext *mms)
Definition: mms.c:54
static uint8_t tmp[11]
Definition: aes_ctr.c:26