FFmpeg
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
librtmp.c
Go to the documentation of this file.
1 /*
2  * RTMP network protocol
3  * Copyright (c) 2010 Howard Chu
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  * @file
24  * RTMP protocol based on http://rtmpdump.mplayerhq.hu/ librtmp
25  */
26 
27 #include "libavutil/avstring.h"
28 #include "libavutil/mathematics.h"
29 #include "libavutil/opt.h"
30 #include "avformat.h"
31 #include "url.h"
32 
33 #include <librtmp/rtmp.h>
34 #include <librtmp/log.h>
35 
36 typedef struct LibRTMPContext {
37  const AVClass *class;
38  RTMP rtmp;
39  char *app;
40  char *conn;
41  char *subscribe;
42  char *playpath;
43  char *tcurl;
44  char *flashver;
45  char *swfurl;
46  char *swfverify;
47  char *pageurl;
49  int live;
52 
53 static void rtmp_log(int level, const char *fmt, va_list args)
54 {
55  switch (level) {
56  default:
57  case RTMP_LOGCRIT: level = AV_LOG_FATAL; break;
58  case RTMP_LOGERROR: level = AV_LOG_ERROR; break;
59  case RTMP_LOGWARNING: level = AV_LOG_WARNING; break;
60  case RTMP_LOGINFO: level = AV_LOG_INFO; break;
61  case RTMP_LOGDEBUG: level = AV_LOG_VERBOSE; break;
62  case RTMP_LOGDEBUG2: level = AV_LOG_DEBUG; break;
63  }
64 
65  av_vlog(NULL, level, fmt, args);
66  av_log(NULL, level, "\n");
67 }
68 
69 static int rtmp_close(URLContext *s)
70 {
71  LibRTMPContext *ctx = s->priv_data;
72  RTMP *r = &ctx->rtmp;
73 
74  RTMP_Close(r);
75  av_freep(&ctx->temp_filename);
76  return 0;
77 }
78 
79 /**
80  * Open RTMP connection and verify that the stream can be played.
81  *
82  * URL syntax: rtmp://server[:port][/app][/playpath][ keyword=value]...
83  * where 'app' is first one or two directories in the path
84  * (e.g. /ondemand/, /flash/live/, etc.)
85  * and 'playpath' is a file name (the rest of the path,
86  * may be prefixed with "mp4:")
87  *
88  * Additional RTMP library options may be appended as
89  * space-separated key-value pairs.
90  */
91 static int rtmp_open(URLContext *s, const char *uri, int flags)
92 {
93  LibRTMPContext *ctx = s->priv_data;
94  RTMP *r = &ctx->rtmp;
95  int rc = 0, level;
96  char *filename = s->filename;
97  int len = strlen(s->filename) + 1;
98 
99  switch (av_log_get_level()) {
100  default:
101  case AV_LOG_FATAL: level = RTMP_LOGCRIT; break;
102  case AV_LOG_ERROR: level = RTMP_LOGERROR; break;
103  case AV_LOG_WARNING: level = RTMP_LOGWARNING; break;
104  case AV_LOG_INFO: level = RTMP_LOGINFO; break;
105  case AV_LOG_VERBOSE: level = RTMP_LOGDEBUG; break;
106  case AV_LOG_DEBUG: level = RTMP_LOGDEBUG2; break;
107  }
108  RTMP_LogSetLevel(level);
109  RTMP_LogSetCallback(rtmp_log);
110 
111  if (ctx->app) len += strlen(ctx->app) + sizeof(" app=");
112  if (ctx->tcurl) len += strlen(ctx->tcurl) + sizeof(" tcUrl=");
113  if (ctx->pageurl) len += strlen(ctx->pageurl) + sizeof(" pageUrl=");
114  if (ctx->flashver) len += strlen(ctx->flashver) + sizeof(" flashver=");
115 
116  if (ctx->conn) {
117  char *sep, *p = ctx->conn;
118  int options = 0;
119 
120  while (p) {
121  options++;
122  p += strspn(p, " ");
123  if (!*p)
124  break;
125  sep = strchr(p, ' ');
126  if (sep)
127  p = sep + 1;
128  else
129  break;
130  }
131  len += options * sizeof(" conn=");
132  len += strlen(ctx->conn);
133  }
134 
135  if (ctx->playpath)
136  len += strlen(ctx->playpath) + sizeof(" playpath=");
137  if (ctx->live)
138  len += sizeof(" live=1");
139  if (ctx->subscribe)
140  len += strlen(ctx->subscribe) + sizeof(" subscribe=");
141 
142  if (ctx->client_buffer_time)
143  len += strlen(ctx->client_buffer_time) + sizeof(" buffer=");
144 
145  if (ctx->swfurl || ctx->swfverify) {
146  len += sizeof(" swfUrl=");
147 
148  if (ctx->swfverify)
149  len += strlen(ctx->swfverify) + sizeof(" swfVfy=1");
150  else
151  len += strlen(ctx->swfurl);
152  }
153 
154  if (!(ctx->temp_filename = filename = av_malloc(len)))
155  return AVERROR(ENOMEM);
156 
157  av_strlcpy(filename, s->filename, len);
158  if (ctx->app) {
159  av_strlcat(filename, " app=", len);
160  av_strlcat(filename, ctx->app, len);
161  }
162  if (ctx->tcurl) {
163  av_strlcat(filename, " tcUrl=", len);
164  av_strlcat(filename, ctx->tcurl, len);
165  }
166  if (ctx->pageurl) {
167  av_strlcat(filename, " pageUrl=", len);
168  av_strlcat(filename, ctx->pageurl, len);
169  }
170  if (ctx->swfurl) {
171  av_strlcat(filename, " swfUrl=", len);
172  av_strlcat(filename, ctx->pageurl, len);
173  }
174  if (ctx->flashver) {
175  av_strlcat(filename, " flashVer=", len);
176  av_strlcat(filename, ctx->flashver, len);
177  }
178  if (ctx->conn) {
179  char *sep, *p = ctx->conn;
180  while (p) {
181  av_strlcat(filename, " conn=", len);
182  p += strspn(p, " ");
183  if (!*p)
184  break;
185  sep = strchr(p, ' ');
186  if (sep)
187  *sep = '\0';
188  av_strlcat(filename, p, len);
189 
190  if (sep)
191  p = sep + 1;
192  }
193  }
194  if (ctx->playpath) {
195  av_strlcat(filename, " playpath=", len);
196  av_strlcat(filename, ctx->playpath, len);
197  }
198  if (ctx->live)
199  av_strlcat(filename, " live=1", len);
200  if (ctx->subscribe) {
201  av_strlcat(filename, " subscribe=", len);
202  av_strlcat(filename, ctx->subscribe, len);
203  }
204  if (ctx->client_buffer_time) {
205  av_strlcat(filename, " buffer=", len);
206  av_strlcat(filename, ctx->client_buffer_time, len);
207  }
208  if (ctx->swfurl || ctx->swfverify) {
209  av_strlcat(filename, " swfUrl=", len);
210 
211  if (ctx->swfverify) {
212  av_strlcat(filename, ctx->swfverify, len);
213  av_strlcat(filename, " swfVfy=1", len);
214  } else {
215  av_strlcat(filename, ctx->swfurl, len);
216  }
217  }
218 
219  RTMP_Init(r);
220  if (!RTMP_SetupURL(r, filename)) {
221  rc = AVERROR_UNKNOWN;
222  goto fail;
223  }
224 
225  if (flags & AVIO_FLAG_WRITE)
226  RTMP_EnableWrite(r);
227 
228  if (!RTMP_Connect(r, NULL) || !RTMP_ConnectStream(r, 0)) {
229  rc = AVERROR_UNKNOWN;
230  goto fail;
231  }
232 
233  s->is_streamed = 1;
234  return 0;
235 fail:
236  av_freep(&ctx->temp_filename);
237  if (rc)
238  RTMP_Close(r);
239 
240  return rc;
241 }
242 
243 static int rtmp_write(URLContext *s, const uint8_t *buf, int size)
244 {
245  LibRTMPContext *ctx = s->priv_data;
246  RTMP *r = &ctx->rtmp;
247 
248  return RTMP_Write(r, buf, size);
249 }
250 
251 static int rtmp_read(URLContext *s, uint8_t *buf, int size)
252 {
253  LibRTMPContext *ctx = s->priv_data;
254  RTMP *r = &ctx->rtmp;
255 
256  return RTMP_Read(r, buf, size);
257 }
258 
259 static int rtmp_read_pause(URLContext *s, int pause)
260 {
261  LibRTMPContext *ctx = s->priv_data;
262  RTMP *r = &ctx->rtmp;
263 
264  if (!RTMP_Pause(r, pause))
265  return AVERROR_UNKNOWN;
266  return 0;
267 }
268 
269 static int64_t rtmp_read_seek(URLContext *s, int stream_index,
270  int64_t timestamp, int flags)
271 {
272  LibRTMPContext *ctx = s->priv_data;
273  RTMP *r = &ctx->rtmp;
274 
275  if (flags & AVSEEK_FLAG_BYTE)
276  return AVERROR(ENOSYS);
277 
278  /* seeks are in milliseconds */
279  if (stream_index < 0)
280  timestamp = av_rescale_rnd(timestamp, 1000, AV_TIME_BASE,
282 
283  if (!RTMP_SendSeek(r, timestamp))
284  return AVERROR_UNKNOWN;
285  return timestamp;
286 }
287 
289 {
290  LibRTMPContext *ctx = s->priv_data;
291  RTMP *r = &ctx->rtmp;
292 
293  return RTMP_Socket(r);
294 }
295 
296 #define OFFSET(x) offsetof(LibRTMPContext, x)
297 #define DEC AV_OPT_FLAG_DECODING_PARAM
298 #define ENC AV_OPT_FLAG_ENCODING_PARAM
299 static const AVOption options[] = {
300  {"rtmp_app", "Name of application to connect to on the RTMP server", OFFSET(app), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC},
301  {"rtmp_buffer", "Set buffer time in milliseconds. The default is 3000.", OFFSET(client_buffer_time), AV_OPT_TYPE_STRING, {.str = "3000"}, 0, 0, DEC|ENC},
302  {"rtmp_conn", "Append arbitrary AMF data to the Connect message", OFFSET(conn), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC},
303  {"rtmp_flashver", "Version of the Flash plugin used to run the SWF player.", OFFSET(flashver), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC},
304  {"rtmp_live", "Specify that the media is a live stream.", OFFSET(live), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, DEC, "rtmp_live"},
305  {"any", "both", 0, AV_OPT_TYPE_CONST, {.i64 = -2}, 0, 0, DEC, "rtmp_live"},
306  {"live", "live stream", 0, AV_OPT_TYPE_CONST, {.i64 = -1}, 0, 0, DEC, "rtmp_live"},
307  {"recorded", "recorded stream", 0, AV_OPT_TYPE_CONST, {.i64 = 0}, 0, 0, DEC, "rtmp_live"},
308  {"rtmp_pageurl", "URL of the web page in which the media was embedded. By default no value will be sent.", OFFSET(pageurl), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC},
309  {"rtmp_playpath", "Stream identifier to play or to publish", OFFSET(playpath), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC},
310  {"rtmp_subscribe", "Name of live stream to subscribe to. Defaults to rtmp_playpath.", OFFSET(subscribe), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC},
311  {"rtmp_swfurl", "URL of the SWF player. By default no value will be sent", OFFSET(swfurl), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC},
312  {"rtmp_swfverify", "URL to player swf file, compute hash/size automatically. (unimplemented)", OFFSET(swfverify), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC},
313  {"rtmp_tcurl", "URL of the target stream. Defaults to proto://host[:port]/app.", OFFSET(tcurl), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC},
314  { NULL },
315 };
316 
317 #define RTMP_CLASS(flavor)\
318 static const AVClass lib ## flavor ## _class = {\
319  .class_name = "lib" #flavor " protocol",\
320  .item_name = av_default_item_name,\
321  .option = options,\
322  .version = LIBAVUTIL_VERSION_INT,\
323 };
324 
325 RTMP_CLASS(rtmp)
327  .name = "rtmp",
328  .url_open = rtmp_open,
329  .url_read = rtmp_read,
330  .url_write = rtmp_write,
331  .url_close = rtmp_close,
332  .url_read_pause = rtmp_read_pause,
333  .url_read_seek = rtmp_read_seek,
334  .url_get_file_handle = rtmp_get_file_handle,
335  .priv_data_size = sizeof(LibRTMPContext),
336  .priv_data_class = &librtmp_class,
338 };
339 
340 RTMP_CLASS(rtmpt)
342  .name = "rtmpt",
343  .url_open = rtmp_open,
344  .url_read = rtmp_read,
345  .url_write = rtmp_write,
346  .url_close = rtmp_close,
347  .url_read_pause = rtmp_read_pause,
348  .url_read_seek = rtmp_read_seek,
349  .url_get_file_handle = rtmp_get_file_handle,
350  .priv_data_size = sizeof(LibRTMPContext),
351  .priv_data_class = &librtmpt_class,
353 };
354 
355 RTMP_CLASS(rtmpe)
357  .name = "rtmpe",
358  .url_open = rtmp_open,
359  .url_read = rtmp_read,
360  .url_write = rtmp_write,
361  .url_close = rtmp_close,
362  .url_read_pause = rtmp_read_pause,
363  .url_read_seek = rtmp_read_seek,
364  .url_get_file_handle = rtmp_get_file_handle,
365  .priv_data_size = sizeof(LibRTMPContext),
366  .priv_data_class = &librtmpe_class,
368 };
369 
370 RTMP_CLASS(rtmpte)
372  .name = "rtmpte",
373  .url_open = rtmp_open,
374  .url_read = rtmp_read,
375  .url_write = rtmp_write,
376  .url_close = rtmp_close,
377  .url_read_pause = rtmp_read_pause,
378  .url_read_seek = rtmp_read_seek,
379  .url_get_file_handle = rtmp_get_file_handle,
380  .priv_data_size = sizeof(LibRTMPContext),
381  .priv_data_class = &librtmpte_class,
383 };
384 
385 RTMP_CLASS(rtmps)
387  .name = "rtmps",
388  .url_open = rtmp_open,
389  .url_read = rtmp_read,
390  .url_write = rtmp_write,
391  .url_close = rtmp_close,
392  .url_read_pause = rtmp_read_pause,
393  .url_read_seek = rtmp_read_seek,
394  .url_get_file_handle = rtmp_get_file_handle,
395  .priv_data_size = sizeof(LibRTMPContext),
396  .priv_data_class = &librtmps_class,
398 };