1
2 /*
3 * Copyright (C) Roman Arutyunyan
4 */
5
6
7 #include <ngx_config.h>
8 #include <ngx_core.h>
9 #include "ngx_rtmp_relay_module.h"
10 #include "ngx_rtmp_cmd_module.h"
11 #include "ngx_rtmp_codec_module.h"
12
13
14 static ngx_rtmp_publish_pt next_publish;
15 static ngx_rtmp_play_pt next_play;
16 static ngx_rtmp_delete_stream_pt next_delete_stream;
17 static ngx_rtmp_close_stream_pt next_close_stream;
18
19
20 static ngx_int_t ngx_rtmp_relay_init_process(ngx_cycle_t *cycle);
21 static ngx_int_t ngx_rtmp_relay_postconfiguration(ngx_conf_t *cf);
22 static void * ngx_rtmp_relay_create_app_conf(ngx_conf_t *cf);
23 static char * ngx_rtmp_relay_merge_app_conf(ngx_conf_t *cf,
24 void *parent, void *child);
25 static char * ngx_rtmp_relay_push_pull(ngx_conf_t *cf, ngx_command_t *cmd,
26 void *conf);
27 static ngx_int_t ngx_rtmp_relay_publish(ngx_rtmp_session_t *s,
28 ngx_rtmp_publish_t *v);
29 static ngx_rtmp_relay_ctx_t * ngx_rtmp_relay_create_connection(
30 ngx_rtmp_conf_ctx_t *cctx, ngx_str_t* name,
31 ngx_rtmp_relay_target_t *target);
32
33
34 /* _____
35 * =push= | |---publish--->
36 * ---publish--->| |---publish--->
37 * (src) | |---publish--->
38 * ----- (next,relay)
39 * need reconnect
40 * =pull= _____
41 * -----play---->| |
42 * -----play---->| |----play----->
43 * -----play---->| | (src,relay)
44 * (next) -----
45 */
46
47
48 typedef struct {
49 ngx_array_t pulls; /* ngx_rtmp_relay_target_t * */
50 ngx_array_t pushes; /* ngx_rtmp_relay_target_t * */
51 ngx_array_t static_pulls; /* ngx_rtmp_relay_target_t * */
52 ngx_array_t static_events; /* ngx_event_t * */
53 ngx_log_t *log;
54 ngx_uint_t nbuckets;
55 ngx_msec_t buflen;
56 ngx_flag_t session_relay;
57 ngx_msec_t push_reconnect;
58 ngx_msec_t pull_reconnect;
59 ngx_rtmp_relay_ctx_t **ctx;
60 } ngx_rtmp_relay_app_conf_t;
61
62
63 typedef struct {
64 ngx_rtmp_conf_ctx_t cctx;
65 ngx_rtmp_relay_target_t *target;
66 } ngx_rtmp_relay_static_t;
67
68
69 #define NGX_RTMP_RELAY_CONNECT_TRANS 1
70 #define NGX_RTMP_RELAY_CREATE_STREAM_TRANS 2
71
72
73 #define NGX_RTMP_RELAY_CSID_AMF_INI 3
74 #define NGX_RTMP_RELAY_CSID_AMF 5
75 #define NGX_RTMP_RELAY_MSID 1
76
77
78 /* default flashVer */
79 #define NGX_RTMP_RELAY_FLASHVER "LNX.11,1,102,55"
80
81
82 static ngx_command_t ngx_rtmp_relay_commands[] = {
83
84 { ngx_string("push"),
85 NGX_RTMP_APP_CONF|NGX_CONF_1MORE,
86 ngx_rtmp_relay_push_pull,
87 NGX_RTMP_APP_CONF_OFFSET,
88 0,
89 NULL },
90
91 { ngx_string("pull"),
92 NGX_RTMP_APP_CONF|NGX_CONF_1MORE,
93 ngx_rtmp_relay_push_pull,
94 NGX_RTMP_APP_CONF_OFFSET,
95 0,
96 NULL },
97
98 { ngx_string("relay_buffer"),
99 NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,
100 ngx_conf_set_msec_slot,
101 NGX_RTMP_APP_CONF_OFFSET,
102 offsetof(ngx_rtmp_relay_app_conf_t, buflen),
103 NULL },
104
105 { ngx_string("push_reconnect"),
106 NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
107 ngx_conf_set_msec_slot,
108 NGX_RTMP_APP_CONF_OFFSET,
109 offsetof(ngx_rtmp_relay_app_conf_t, push_reconnect),
110 NULL },
111
112 { ngx_string("pull_reconnect"),
113 NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
114 ngx_conf_set_msec_slot,
115 NGX_RTMP_APP_CONF_OFFSET,
116 offsetof(ngx_rtmp_relay_app_conf_t, pull_reconnect),
117 NULL },
118
119 { ngx_string("session_relay"),
120 NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
121 ngx_conf_set_flag_slot,
122 NGX_RTMP_APP_CONF_OFFSET,
123 offsetof(ngx_rtmp_relay_app_conf_t, session_relay),
124 NULL },
125
126
127 ngx_null_command
128 };
129
130
131 static ngx_rtmp_module_t ngx_rtmp_relay_module_ctx = {
132 NULL, /* preconfiguration */
133 ngx_rtmp_relay_postconfiguration, /* postconfiguration */
134 NULL, /* create main configuration */
135 NULL, /* init main configuration */
136 NULL, /* create server configuration */
137 NULL, /* merge server configuration */
138 ngx_rtmp_relay_create_app_conf, /* create app configuration */
139 ngx_rtmp_relay_merge_app_conf /* merge app configuration */
140 };
141
142
143 ngx_module_t ngx_rtmp_relay_module = {
144 NGX_MODULE_V1,
145 &ngx_rtmp_relay_module_ctx, /* module context */
146 ngx_rtmp_relay_commands, /* module directives */
147 NGX_RTMP_MODULE, /* module type */
148 NULL, /* init master */
149 NULL, /* init module */
150 ngx_rtmp_relay_init_process, /* init process */
151 NULL, /* init thread */
152 NULL, /* exit thread */
153 NULL, /* exit process */
154 NULL, /* exit master */
155 NGX_MODULE_V1_PADDING
156 };
157
158
159 static void *
ngx_rtmp_relay_create_app_conf(ngx_conf_t * cf)160 ngx_rtmp_relay_create_app_conf(ngx_conf_t *cf)
161 {
162 ngx_rtmp_relay_app_conf_t *racf;
163
164 racf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_relay_app_conf_t));
165 if (racf == NULL) {
166 return NULL;
167 }
168
169 if (ngx_array_init(&racf->pushes, cf->pool, 1, sizeof(void *)) != NGX_OK) {
170 return NULL;
171 }
172
173 if (ngx_array_init(&racf->pulls, cf->pool, 1, sizeof(void *)) != NGX_OK) {
174 return NULL;
175 }
176
177 if (ngx_array_init(&racf->static_pulls, cf->pool, 1, sizeof(void *))
178 != NGX_OK)
179 {
180 return NULL;
181 }
182
183 if (ngx_array_init(&racf->static_events, cf->pool, 1, sizeof(void *))
184 != NGX_OK)
185 {
186 return NULL;
187 }
188
189 racf->nbuckets = 1024;
190 racf->log = &cf->cycle->new_log;
191 racf->buflen = NGX_CONF_UNSET_MSEC;
192 racf->session_relay = NGX_CONF_UNSET;
193 racf->push_reconnect = NGX_CONF_UNSET_MSEC;
194 racf->pull_reconnect = NGX_CONF_UNSET_MSEC;
195
196 return racf;
197 }
198
199
200 static char *
ngx_rtmp_relay_merge_app_conf(ngx_conf_t * cf,void * parent,void * child)201 ngx_rtmp_relay_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
202 {
203 ngx_rtmp_relay_app_conf_t *prev = parent;
204 ngx_rtmp_relay_app_conf_t *conf = child;
205
206 conf->ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_relay_ctx_t *)
207 * conf->nbuckets);
208
209 ngx_conf_merge_value(conf->session_relay, prev->session_relay, 0);
210 ngx_conf_merge_msec_value(conf->buflen, prev->buflen, 5000);
211 ngx_conf_merge_msec_value(conf->push_reconnect, prev->push_reconnect,
212 3000);
213 ngx_conf_merge_msec_value(conf->pull_reconnect, prev->pull_reconnect,
214 3000);
215
216 return NGX_CONF_OK;
217 }
218
219
220 static void
ngx_rtmp_relay_static_pull_reconnect(ngx_event_t * ev)221 ngx_rtmp_relay_static_pull_reconnect(ngx_event_t *ev)
222 {
223 ngx_rtmp_relay_static_t *rs = ev->data;
224
225 ngx_rtmp_relay_ctx_t *ctx;
226 ngx_rtmp_relay_app_conf_t *racf;
227
228 racf = ngx_rtmp_get_module_app_conf(&rs->cctx, ngx_rtmp_relay_module);
229
230 ngx_log_debug0(NGX_LOG_DEBUG_RTMP, racf->log, 0,
231 "relay: reconnecting static pull");
232
233 ctx = ngx_rtmp_relay_create_connection(&rs->cctx, &rs->target->name,
234 rs->target);
235 if (ctx) {
236 ctx->session->static_relay = 1;
237 ctx->static_evt = ev;
238 return;
239 }
240
241 ngx_add_timer(ev, racf->pull_reconnect);
242 }
243
244
245 static void
ngx_rtmp_relay_push_reconnect(ngx_event_t * ev)246 ngx_rtmp_relay_push_reconnect(ngx_event_t *ev)
247 {
248 ngx_rtmp_session_t *s = ev->data;
249
250 ngx_rtmp_relay_app_conf_t *racf;
251 ngx_rtmp_relay_ctx_t *ctx, *pctx;
252 ngx_uint_t n;
253 ngx_rtmp_relay_target_t *target, **t;
254
255 ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
256 "relay: push reconnect");
257
258 racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_relay_module);
259
260 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);
261 if (ctx == NULL) {
262 return;
263 }
264
265 t = racf->pushes.elts;
266 for (n = 0; n < racf->pushes.nelts; ++n, ++t) {
267 target = *t;
268
269 if (target->name.len && (ctx->name.len != target->name.len ||
270 ngx_memcmp(ctx->name.data, target->name.data, ctx->name.len)))
271 {
272 continue;
273 }
274
275 for (pctx = ctx->play; pctx; pctx = pctx->next) {
276 if (pctx->tag == &ngx_rtmp_relay_module &&
277 pctx->data == target)
278 {
279 break;
280 }
281 }
282
283 if (pctx) {
284 continue;
285 }
286
287 if (ngx_rtmp_relay_push(s, &ctx->name, target) == NGX_OK) {
288 continue;
289 }
290
291 ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
292 "relay: push reconnect failed name='%V' app='%V' "
293 "playpath='%V' url='%V'",
294 &ctx->name, &target->app, &target->play_path,
295 &target->url.url);
296
297 if (!ctx->push_evt.timer_set) {
298 ngx_add_timer(&ctx->push_evt, racf->push_reconnect);
299 }
300 }
301 }
302
303
304 static ngx_int_t
ngx_rtmp_relay_get_peer(ngx_peer_connection_t * pc,void * data)305 ngx_rtmp_relay_get_peer(ngx_peer_connection_t *pc, void *data)
306 {
307 return NGX_OK;
308 }
309
310
311 static void
ngx_rtmp_relay_free_peer(ngx_peer_connection_t * pc,void * data,ngx_uint_t state)312 ngx_rtmp_relay_free_peer(ngx_peer_connection_t *pc, void *data,
313 ngx_uint_t state)
314 {
315 }
316
317
318 typedef ngx_rtmp_relay_ctx_t * (* ngx_rtmp_relay_create_ctx_pt)
319 (ngx_rtmp_session_t *s, ngx_str_t *name, ngx_rtmp_relay_target_t *target);
320
321
322 static ngx_int_t
ngx_rtmp_relay_copy_str(ngx_pool_t * pool,ngx_str_t * dst,ngx_str_t * src)323 ngx_rtmp_relay_copy_str(ngx_pool_t *pool, ngx_str_t *dst, ngx_str_t *src)
324 {
325 if (src->len == 0) {
326 return NGX_OK;
327 }
328 dst->len = src->len;
329 dst->data = ngx_palloc(pool, src->len);
330 if (dst->data == NULL) {
331 return NGX_ERROR;
332 }
333 ngx_memcpy(dst->data, src->data, src->len);
334 return NGX_OK;
335 }
336
337
338 static ngx_rtmp_relay_ctx_t *
ngx_rtmp_relay_create_connection(ngx_rtmp_conf_ctx_t * cctx,ngx_str_t * name,ngx_rtmp_relay_target_t * target)339 ngx_rtmp_relay_create_connection(ngx_rtmp_conf_ctx_t *cctx, ngx_str_t* name,
340 ngx_rtmp_relay_target_t *target)
341 {
342 ngx_rtmp_relay_app_conf_t *racf;
343 ngx_rtmp_relay_ctx_t *rctx;
344 ngx_rtmp_addr_conf_t *addr_conf;
345 ngx_rtmp_conf_ctx_t *addr_ctx;
346 ngx_rtmp_session_t *rs;
347 ngx_peer_connection_t *pc;
348 ngx_connection_t *c;
349 ngx_addr_t *addr;
350 ngx_pool_t *pool;
351 ngx_int_t rc;
352 ngx_str_t v, *uri;
353 u_char *first, *last, *p;
354
355 racf = ngx_rtmp_get_module_app_conf(cctx, ngx_rtmp_relay_module);
356
357 ngx_log_debug0(NGX_LOG_DEBUG_RTMP, racf->log, 0,
358 "relay: create remote context");
359
360 pool = NULL;
361 pool = ngx_create_pool(4096, racf->log);
362 if (pool == NULL) {
363 return NULL;
364 }
365
366 rctx = ngx_pcalloc(pool, sizeof(ngx_rtmp_relay_ctx_t));
367 if (rctx == NULL) {
368 goto clear;
369 }
370
371 if (name && ngx_rtmp_relay_copy_str(pool, &rctx->name, name) != NGX_OK) {
372 goto clear;
373 }
374
375 if (ngx_rtmp_relay_copy_str(pool, &rctx->url, &target->url.url) != NGX_OK) {
376 goto clear;
377 }
378
379 rctx->tag = target->tag;
380 rctx->data = target->data;
381
382 #define NGX_RTMP_RELAY_STR_COPY(to, from) \
383 if (ngx_rtmp_relay_copy_str(pool, &rctx->to, &target->from) != NGX_OK) { \
384 goto clear; \
385 }
386
387 NGX_RTMP_RELAY_STR_COPY(app, app);
388 NGX_RTMP_RELAY_STR_COPY(tc_url, tc_url);
389 NGX_RTMP_RELAY_STR_COPY(page_url, page_url);
390 NGX_RTMP_RELAY_STR_COPY(swf_url, swf_url);
391 NGX_RTMP_RELAY_STR_COPY(flash_ver, flash_ver);
392 NGX_RTMP_RELAY_STR_COPY(play_path, play_path);
393
394 rctx->live = target->live;
395 rctx->start = target->start;
396 rctx->stop = target->stop;
397
398 #undef NGX_RTMP_RELAY_STR_COPY
399
400 if (rctx->app.len == 0 || rctx->play_path.len == 0) {
401 /* parse uri */
402 uri = &target->url.uri;
403 first = uri->data;
404 last = uri->data + uri->len;
405 if (first != last && *first == '/') {
406 ++first;
407 }
408
409 if (first != last) {
410
411 /* deduce app */
412 p = ngx_strlchr(first, last, '/');
413 if (p == NULL) {
414 p = last;
415 }
416
417 if (rctx->app.len == 0 && first != p) {
418 v.data = first;
419 v.len = p - first;
420 if (ngx_rtmp_relay_copy_str(pool, &rctx->app, &v) != NGX_OK) {
421 goto clear;
422 }
423 }
424
425 /* deduce play_path */
426 if (p != last) {
427 ++p;
428 }
429
430 if (rctx->play_path.len == 0 && p != last) {
431 v.data = p;
432 v.len = last - p;
433 if (ngx_rtmp_relay_copy_str(pool, &rctx->play_path, &v)
434 != NGX_OK)
435 {
436 goto clear;
437 }
438 }
439 }
440 }
441
442 pc = ngx_pcalloc(pool, sizeof(ngx_peer_connection_t));
443 if (pc == NULL) {
444 goto clear;
445 }
446
447 if (target->url.naddrs == 0) {
448 ngx_log_error(NGX_LOG_ERR, racf->log, 0,
449 "relay: no address");
450 goto clear;
451 }
452
453 /* get address */
454 addr = &target->url.addrs[target->counter % target->url.naddrs];
455 target->counter++;
456
457 /* copy log to keep shared log unchanged */
458 rctx->log = *racf->log;
459 pc->log = &rctx->log;
460 pc->get = ngx_rtmp_relay_get_peer;
461 pc->free = ngx_rtmp_relay_free_peer;
462 pc->name = &addr->name;
463 pc->socklen = addr->socklen;
464 pc->sockaddr = (struct sockaddr *)ngx_palloc(pool, pc->socklen);
465 if (pc->sockaddr == NULL) {
466 goto clear;
467 }
468 ngx_memcpy(pc->sockaddr, addr->sockaddr, pc->socklen);
469
470 rc = ngx_event_connect_peer(pc);
471 if (rc != NGX_OK && rc != NGX_AGAIN ) {
472 ngx_log_debug0(NGX_LOG_DEBUG_RTMP, racf->log, 0,
473 "relay: connection failed");
474 goto clear;
475 }
476 c = pc->connection;
477 c->pool = pool;
478 c->addr_text = rctx->url;
479
480 addr_conf = ngx_pcalloc(pool, sizeof(ngx_rtmp_addr_conf_t));
481 if (addr_conf == NULL) {
482 goto clear;
483 }
484 addr_ctx = ngx_pcalloc(pool, sizeof(ngx_rtmp_conf_ctx_t));
485 if (addr_ctx == NULL) {
486 goto clear;
487 }
488 addr_conf->ctx = addr_ctx;
489 addr_ctx->main_conf = cctx->main_conf;
490 addr_ctx->srv_conf = cctx->srv_conf;
491 ngx_str_set(&addr_conf->addr_text, "ngx-relay");
492
493 rs = ngx_rtmp_init_session(c, addr_conf);
494 if (rs == NULL) {
495 /* no need to destroy pool */
496 return NULL;
497 }
498 rs->app_conf = cctx->app_conf;
499 rs->relay = 1;
500 rs->ready_for_publish = 0;
501 rctx->session = rs;
502 ngx_rtmp_set_ctx(rs, rctx, ngx_rtmp_relay_module);
503 ngx_str_set(&rs->flashver, "ngx-local-relay");
504 ngx_memcpy(&rs->app, &rctx->app, sizeof(rctx->app));
505
506 #if (NGX_STAT_STUB)
507 (void) ngx_atomic_fetch_add(ngx_stat_active, 1);
508 #endif
509
510 ngx_rtmp_client_handshake(rs, 1);
511 return rctx;
512
513 clear:
514 if (pool) {
515 ngx_destroy_pool(pool);
516 }
517 return NULL;
518 }
519
520
521 static ngx_rtmp_relay_ctx_t *
ngx_rtmp_relay_create_remote_ctx(ngx_rtmp_session_t * s,ngx_str_t * name,ngx_rtmp_relay_target_t * target)522 ngx_rtmp_relay_create_remote_ctx(ngx_rtmp_session_t *s, ngx_str_t* name,
523 ngx_rtmp_relay_target_t *target)
524 {
525 ngx_rtmp_conf_ctx_t cctx;
526
527 cctx.app_conf = s->app_conf;
528 cctx.srv_conf = s->srv_conf;
529 cctx.main_conf = s->main_conf;
530
531 return ngx_rtmp_relay_create_connection(&cctx, name, target);
532 }
533
534
535 static ngx_rtmp_relay_ctx_t *
ngx_rtmp_relay_create_local_ctx(ngx_rtmp_session_t * s,ngx_str_t * name,ngx_rtmp_relay_target_t * target)536 ngx_rtmp_relay_create_local_ctx(ngx_rtmp_session_t *s, ngx_str_t *name,
537 ngx_rtmp_relay_target_t *target)
538 {
539 ngx_rtmp_relay_ctx_t *ctx;
540
541 ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
542 "relay: create local context");
543
544 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);
545 if (ctx == NULL) {
546 ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_rtmp_relay_ctx_t));
547 if (ctx == NULL) {
548 return NULL;
549 }
550 ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_relay_module);
551 }
552 ctx->session = s;
553
554 ctx->push_evt.data = s;
555 ctx->push_evt.log = s->connection->log;
556 ctx->push_evt.handler = ngx_rtmp_relay_push_reconnect;
557
558 if (ctx->publish) {
559 return NULL;
560 }
561
562 if (ngx_rtmp_relay_copy_str(s->connection->pool, &ctx->name, name)
563 != NGX_OK)
564 {
565 return NULL;
566 }
567
568 return ctx;
569 }
570
571
572 static ngx_int_t
ngx_rtmp_relay_create(ngx_rtmp_session_t * s,ngx_str_t * name,ngx_rtmp_relay_target_t * target,ngx_rtmp_relay_create_ctx_pt create_publish_ctx,ngx_rtmp_relay_create_ctx_pt create_play_ctx)573 ngx_rtmp_relay_create(ngx_rtmp_session_t *s, ngx_str_t *name,
574 ngx_rtmp_relay_target_t *target,
575 ngx_rtmp_relay_create_ctx_pt create_publish_ctx,
576 ngx_rtmp_relay_create_ctx_pt create_play_ctx)
577 {
578 ngx_rtmp_relay_app_conf_t *racf;
579 ngx_rtmp_relay_ctx_t *publish_ctx, *play_ctx, **cctx;
580 ngx_uint_t hash;
581
582
583 racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_relay_module);
584 if (racf == NULL) {
585 return NGX_ERROR;
586 }
587
588 play_ctx = create_play_ctx(s, name, target);
589 if (play_ctx == NULL) {
590 return NGX_ERROR;
591 }
592
593 hash = ngx_hash_key(name->data, name->len);
594 cctx = &racf->ctx[hash % racf->nbuckets];
595 for (; *cctx; cctx = &(*cctx)->next) {
596 if ((*cctx)->name.len == name->len
597 && !ngx_memcmp(name->data, (*cctx)->name.data,
598 name->len))
599 {
600 break;
601 }
602 }
603
604 if (*cctx) {
605 play_ctx->publish = (*cctx)->publish;
606 play_ctx->next = (*cctx)->play;
607 (*cctx)->play = play_ctx;
608 return NGX_OK;
609 }
610
611 publish_ctx = create_publish_ctx(s, name, target);
612 if (publish_ctx == NULL) {
613 ngx_rtmp_finalize_session(play_ctx->session);
614 return NGX_ERROR;
615 }
616
617 publish_ctx->publish = publish_ctx;
618 publish_ctx->play = play_ctx;
619 play_ctx->publish = publish_ctx;
620 *cctx = publish_ctx;
621
622 return NGX_OK;
623 }
624
625
626 ngx_int_t
ngx_rtmp_relay_pull(ngx_rtmp_session_t * s,ngx_str_t * name,ngx_rtmp_relay_target_t * target)627 ngx_rtmp_relay_pull(ngx_rtmp_session_t *s, ngx_str_t *name,
628 ngx_rtmp_relay_target_t *target)
629 {
630 ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
631 "relay: create pull name='%V' app='%V' playpath='%V' url='%V'",
632 name, &target->app, &target->play_path, &target->url.url);
633
634 return ngx_rtmp_relay_create(s, name, target,
635 ngx_rtmp_relay_create_remote_ctx,
636 ngx_rtmp_relay_create_local_ctx);
637 }
638
639
640 ngx_int_t
ngx_rtmp_relay_push(ngx_rtmp_session_t * s,ngx_str_t * name,ngx_rtmp_relay_target_t * target)641 ngx_rtmp_relay_push(ngx_rtmp_session_t *s, ngx_str_t *name,
642 ngx_rtmp_relay_target_t *target)
643 {
644 ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
645 "relay: create push name='%V' app='%V' playpath='%V' url='%V'",
646 name, &target->app, &target->play_path, &target->url.url);
647
648 return ngx_rtmp_relay_create(s, name, target,
649 ngx_rtmp_relay_create_local_ctx,
650 ngx_rtmp_relay_create_remote_ctx);
651 }
652
653
654 static ngx_int_t
ngx_rtmp_relay_publish(ngx_rtmp_session_t * s,ngx_rtmp_publish_t * v)655 ngx_rtmp_relay_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)
656 {
657 ngx_rtmp_relay_app_conf_t *racf;
658 ngx_rtmp_relay_target_t *target, **t;
659 ngx_str_t name;
660 size_t n;
661 ngx_rtmp_relay_ctx_t *ctx;
662
663 if (s->auto_pushed) {
664 goto next;
665 }
666
667 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);
668 if (ctx && s->relay) {
669 goto next;
670 }
671
672 racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_relay_module);
673 if (racf == NULL || racf->pushes.nelts == 0) {
674 goto next;
675 }
676
677 name.len = ngx_strlen(v->name);
678 name.data = v->name;
679
680 t = racf->pushes.elts;
681 for (n = 0; n < racf->pushes.nelts; ++n, ++t) {
682 target = *t;
683
684 if (target->name.len && (name.len != target->name.len ||
685 ngx_memcmp(name.data, target->name.data, name.len)))
686 {
687 continue;
688 }
689
690 if (ngx_rtmp_relay_push(s, &name, target) == NGX_OK) {
691 continue;
692 }
693
694 ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
695 "relay: push failed name='%V' app='%V' "
696 "playpath='%V' url='%V'",
697 &name, &target->app, &target->play_path,
698 &target->url.url);
699
700 if (!ctx->push_evt.timer_set) {
701 ngx_add_timer(&ctx->push_evt, racf->push_reconnect);
702 }
703 }
704
705 next:
706 return next_publish(s, v);
707 }
708
709
710 static ngx_int_t
ngx_rtmp_relay_play(ngx_rtmp_session_t * s,ngx_rtmp_play_t * v)711 ngx_rtmp_relay_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)
712 {
713 ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
714 "relay: ngx_rtmp_relay_play");
715
716 ngx_rtmp_relay_app_conf_t *racf;
717 ngx_rtmp_relay_target_t *target, **t;
718 ngx_str_t name;
719 size_t n;
720 ngx_rtmp_relay_ctx_t *ctx;
721
722 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);
723 if (ctx && s->relay) {
724 goto next;
725 }
726
727 racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_relay_module);
728 if (racf == NULL || racf->pulls.nelts == 0) {
729 goto next;
730 }
731
732 name.len = ngx_strlen(v->name);
733 name.data = v->name;
734
735 t = racf->pulls.elts;
736 for (n = 0; n < racf->pulls.nelts; ++n, ++t) {
737 target = *t;
738
739 if (target->name.len && (name.len != target->name.len ||
740 ngx_memcmp(name.data, target->name.data, name.len)))
741 {
742 continue;
743 }
744
745 if (ngx_rtmp_relay_pull(s, &name, target) == NGX_OK) {
746 continue;
747 }
748
749 ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
750 "relay: pull failed name='%V' app='%V' "
751 "playpath='%V' url='%V'",
752 &name, &target->app, &target->play_path,
753 &target->url.url);
754 }
755
756 next:
757 ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
758 "relay: ngx_rtmp_relay_play: next");
759
760 return next_play(s, v);
761 }
762
763
764 static ngx_int_t
ngx_rtmp_relay_play_local(ngx_rtmp_session_t * s)765 ngx_rtmp_relay_play_local(ngx_rtmp_session_t *s)
766 {
767 ngx_rtmp_play_t v;
768 ngx_rtmp_relay_ctx_t *ctx;
769
770 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);
771 if (ctx == NULL) {
772 return NGX_ERROR;
773 }
774
775 ngx_memzero(&v, sizeof(ngx_rtmp_play_t));
776 v.silent = 1;
777 *(ngx_cpymem(v.name, ctx->name.data,
778 ngx_min(sizeof(v.name) - 1, ctx->name.len))) = 0;
779
780 return ngx_rtmp_play(s, &v);
781 }
782
783
784 static ngx_int_t
ngx_rtmp_relay_publish_local(ngx_rtmp_session_t * s)785 ngx_rtmp_relay_publish_local(ngx_rtmp_session_t *s)
786 {
787 ngx_rtmp_publish_t v;
788 ngx_rtmp_relay_ctx_t *ctx;
789
790 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);
791 if (ctx == NULL) {
792 return NGX_ERROR;
793 }
794
795 ngx_memzero(&v, sizeof(ngx_rtmp_publish_t));
796 v.silent = 1;
797 *(ngx_cpymem(v.name, ctx->name.data,
798 ngx_min(sizeof(v.name) - 1, ctx->name.len))) = 0;
799
800 return ngx_rtmp_publish(s, &v);
801 }
802
803
804 static ngx_int_t
ngx_rtmp_relay_send_connect(ngx_rtmp_session_t * s)805 ngx_rtmp_relay_send_connect(ngx_rtmp_session_t *s)
806 {
807 static double trans = NGX_RTMP_RELAY_CONNECT_TRANS;
808 static double acodecs = 3575;
809 static double vcodecs = 252;
810
811 static ngx_rtmp_amf_elt_t out_cmd[] = {
812
813 { NGX_RTMP_AMF_STRING,
814 ngx_string("app"),
815 NULL, 0 }, /* <-- fill */
816
817 { NGX_RTMP_AMF_STRING,
818 ngx_string("tcUrl"),
819 NULL, 0 }, /* <-- fill */
820
821 { NGX_RTMP_AMF_STRING,
822 ngx_string("pageUrl"),
823 NULL, 0 }, /* <-- fill */
824
825 { NGX_RTMP_AMF_STRING,
826 ngx_string("swfUrl"),
827 NULL, 0 }, /* <-- fill */
828
829 { NGX_RTMP_AMF_STRING,
830 ngx_string("flashVer"),
831 NULL, 0 }, /* <-- fill */
832
833 { NGX_RTMP_AMF_NUMBER,
834 ngx_string("audioCodecs"),
835 &acodecs, 0 },
836
837 { NGX_RTMP_AMF_NUMBER,
838 ngx_string("videoCodecs"),
839 &vcodecs, 0 }
840 };
841
842 static ngx_rtmp_amf_elt_t out_elts[] = {
843
844 { NGX_RTMP_AMF_STRING,
845 ngx_null_string,
846 "connect", 0 },
847
848 { NGX_RTMP_AMF_NUMBER,
849 ngx_null_string,
850 &trans, 0 },
851
852 { NGX_RTMP_AMF_OBJECT,
853 ngx_null_string,
854 out_cmd, sizeof(out_cmd) }
855 };
856
857 ngx_rtmp_core_app_conf_t *cacf;
858 ngx_rtmp_core_srv_conf_t *cscf;
859 ngx_rtmp_relay_ctx_t *ctx;
860 ngx_rtmp_header_t h;
861 size_t len, url_len;
862 u_char *p, *url_end;
863
864
865 cacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_core_module);
866 cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
867 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);
868 if (cacf == NULL || ctx == NULL) {
869 return NGX_ERROR;
870 }
871
872 /* app */
873 if (ctx->app.len) {
874 out_cmd[0].data = ctx->app.data;
875 out_cmd[0].len = ctx->app.len;
876 } else {
877 out_cmd[0].data = cacf->name.data;
878 out_cmd[0].len = cacf->name.len;
879 }
880
881 /* tcUrl */
882 if (ctx->tc_url.len) {
883 out_cmd[1].data = ctx->tc_url.data;
884 out_cmd[1].len = ctx->tc_url.len;
885 } else {
886 len = sizeof("rtmp://") - 1 + ctx->url.len +
887 sizeof("/") - 1 + ctx->app.len;
888 p = ngx_palloc(s->connection->pool, len);
889 if (p == NULL) {
890 return NGX_ERROR;
891 }
892 out_cmd[1].data = p;
893 p = ngx_cpymem(p, "rtmp://", sizeof("rtmp://") - 1);
894
895 url_len = ctx->url.len;
896 url_end = ngx_strlchr(ctx->url.data, ctx->url.data + ctx->url.len, '/');
897 if (url_end) {
898 url_len = (size_t) (url_end - ctx->url.data);
899 }
900
901 p = ngx_cpymem(p, ctx->url.data, url_len);
902 *p++ = '/';
903 p = ngx_cpymem(p, ctx->app.data, ctx->app.len);
904 out_cmd[1].len = p - (u_char *)out_cmd[1].data;
905 }
906
907 /* pageUrl */
908 out_cmd[2].data = ctx->page_url.data;
909 out_cmd[2].len = ctx->page_url.len;
910
911 /* swfUrl */
912 out_cmd[3].data = ctx->swf_url.data;
913 out_cmd[3].len = ctx->swf_url.len;
914
915 /* flashVer */
916 if (ctx->flash_ver.len) {
917 out_cmd[4].data = ctx->flash_ver.data;
918 out_cmd[4].len = ctx->flash_ver.len;
919 } else {
920 out_cmd[4].data = NGX_RTMP_RELAY_FLASHVER;
921 out_cmd[4].len = sizeof(NGX_RTMP_RELAY_FLASHVER) - 1;
922 }
923
924 ngx_memzero(&h, sizeof(h));
925 h.csid = NGX_RTMP_RELAY_CSID_AMF_INI;
926 h.type = NGX_RTMP_MSG_AMF_CMD;
927
928 return ngx_rtmp_send_chunk_size(s, cscf->chunk_size) != NGX_OK
929 || ngx_rtmp_send_ack_size(s, cscf->ack_window) != NGX_OK
930 || ngx_rtmp_send_amf(s, &h, out_elts,
931 sizeof(out_elts) / sizeof(out_elts[0])) != NGX_OK
932 ? NGX_ERROR
933 : NGX_OK;
934 }
935
936
937 static ngx_int_t
ngx_rtmp_relay_send_create_stream(ngx_rtmp_session_t * s)938 ngx_rtmp_relay_send_create_stream(ngx_rtmp_session_t *s)
939 {
940 static double trans = NGX_RTMP_RELAY_CREATE_STREAM_TRANS;
941
942 static ngx_rtmp_amf_elt_t out_elts[] = {
943
944 { NGX_RTMP_AMF_STRING,
945 ngx_null_string,
946 "createStream", 0 },
947
948 { NGX_RTMP_AMF_NUMBER,
949 ngx_null_string,
950 &trans, 0 },
951
952 { NGX_RTMP_AMF_NULL,
953 ngx_null_string,
954 NULL, 0 }
955 };
956
957 ngx_rtmp_header_t h;
958
959
960 ngx_memzero(&h, sizeof(h));
961 h.csid = NGX_RTMP_RELAY_CSID_AMF_INI;
962 h.type = NGX_RTMP_MSG_AMF_CMD;
963
964 return ngx_rtmp_send_amf(s, &h, out_elts,
965 sizeof(out_elts) / sizeof(out_elts[0]));
966 }
967
968
969 static ngx_int_t
ngx_rtmp_relay_send_publish(ngx_rtmp_session_t * s)970 ngx_rtmp_relay_send_publish(ngx_rtmp_session_t *s)
971 {
972 static double trans;
973
974 static ngx_rtmp_amf_elt_t out_elts[] = {
975
976 { NGX_RTMP_AMF_STRING,
977 ngx_null_string,
978 "publish", 0 },
979
980 { NGX_RTMP_AMF_NUMBER,
981 ngx_null_string,
982 &trans, 0 },
983
984 { NGX_RTMP_AMF_NULL,
985 ngx_null_string,
986 NULL, 0 },
987
988 { NGX_RTMP_AMF_STRING,
989 ngx_null_string,
990 NULL, 0 }, /* <- to fill */
991
992 { NGX_RTMP_AMF_STRING,
993 ngx_null_string,
994 "live", 0 }
995 };
996
997 ngx_rtmp_header_t h;
998 ngx_rtmp_relay_ctx_t *ctx;
999
1000
1001 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);
1002 if (ctx == NULL) {
1003 return NGX_ERROR;
1004 }
1005
1006 if (ctx->play_path.len) {
1007 out_elts[3].data = ctx->play_path.data;
1008 out_elts[3].len = ctx->play_path.len;
1009 } else {
1010 out_elts[3].data = ctx->name.data;
1011 out_elts[3].len = ctx->name.len;
1012 }
1013
1014 ngx_memzero(&h, sizeof(h));
1015 h.csid = NGX_RTMP_RELAY_CSID_AMF;
1016 h.msid = NGX_RTMP_RELAY_MSID;
1017 h.type = NGX_RTMP_MSG_AMF_CMD;
1018
1019 return ngx_rtmp_send_amf(s, &h, out_elts,
1020 sizeof(out_elts) / sizeof(out_elts[0]));
1021 }
1022
1023
1024 static ngx_int_t
ngx_rtmp_relay_send_play(ngx_rtmp_session_t * s)1025 ngx_rtmp_relay_send_play(ngx_rtmp_session_t *s)
1026 {
1027 static double trans;
1028 static double start, duration;
1029
1030 static ngx_rtmp_amf_elt_t out_elts[] = {
1031
1032 { NGX_RTMP_AMF_STRING,
1033 ngx_null_string,
1034 "play", 0 },
1035
1036 { NGX_RTMP_AMF_NUMBER,
1037 ngx_null_string,
1038 &trans, 0 },
1039
1040 { NGX_RTMP_AMF_NULL,
1041 ngx_null_string,
1042 NULL, 0 },
1043
1044 { NGX_RTMP_AMF_STRING,
1045 ngx_null_string,
1046 NULL, 0 }, /* <- fill */
1047
1048 { NGX_RTMP_AMF_NUMBER,
1049 ngx_null_string,
1050 &start, 0 },
1051
1052 { NGX_RTMP_AMF_NUMBER,
1053 ngx_null_string,
1054 &duration, 0 },
1055 };
1056
1057 ngx_rtmp_header_t h;
1058 ngx_rtmp_relay_ctx_t *ctx;
1059 ngx_rtmp_relay_app_conf_t *racf;
1060
1061
1062 racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_relay_module);
1063 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);
1064 if (racf == NULL || ctx == NULL) {
1065 return NGX_ERROR;
1066 }
1067
1068 if (ctx->play_path.len) {
1069 out_elts[3].data = ctx->play_path.data;
1070 out_elts[3].len = ctx->play_path.len;
1071 } else {
1072 out_elts[3].data = ctx->name.data;
1073 out_elts[3].len = ctx->name.len;
1074 }
1075
1076 if (ctx->live) {
1077 start = -1000;
1078 duration = -1000;
1079 } else {
1080 start = (ctx->start ? ctx->start : -2000);
1081 duration = (ctx->stop ? ctx->stop - ctx->start : -1000);
1082 }
1083
1084 ngx_memzero(&h, sizeof(h));
1085 h.csid = NGX_RTMP_RELAY_CSID_AMF;
1086 h.msid = NGX_RTMP_RELAY_MSID;
1087 h.type = NGX_RTMP_MSG_AMF_CMD;
1088
1089 return ngx_rtmp_send_amf(s, &h, out_elts,
1090 sizeof(out_elts) / sizeof(out_elts[0])) != NGX_OK
1091 || ngx_rtmp_send_set_buflen(s, NGX_RTMP_RELAY_MSID,
1092 racf->buflen) != NGX_OK
1093 ? NGX_ERROR
1094 : NGX_OK;
1095 }
1096
1097
1098 static ngx_int_t
ngx_rtmp_relay_on_result(ngx_rtmp_session_t * s,ngx_rtmp_header_t * h,ngx_chain_t * in)1099 ngx_rtmp_relay_on_result(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
1100 ngx_chain_t *in)
1101 {
1102 ngx_rtmp_relay_ctx_t *ctx;
1103 static struct {
1104 double trans;
1105 u_char level[32];
1106 u_char code[128];
1107 u_char desc[1024];
1108 } v;
1109
1110 static ngx_rtmp_amf_elt_t in_inf[] = {
1111
1112 { NGX_RTMP_AMF_STRING,
1113 ngx_string("level"),
1114 &v.level, sizeof(v.level) },
1115
1116 { NGX_RTMP_AMF_STRING,
1117 ngx_string("code"),
1118 &v.code, sizeof(v.code) },
1119
1120 { NGX_RTMP_AMF_STRING,
1121 ngx_string("description"),
1122 &v.desc, sizeof(v.desc) },
1123 };
1124
1125 static ngx_rtmp_amf_elt_t in_elts[] = {
1126
1127 { NGX_RTMP_AMF_NUMBER,
1128 ngx_null_string,
1129 &v.trans, 0 },
1130
1131 { NGX_RTMP_AMF_NULL,
1132 ngx_null_string,
1133 NULL, 0 },
1134
1135 { NGX_RTMP_AMF_OBJECT,
1136 ngx_null_string,
1137 in_inf, sizeof(in_inf) },
1138 };
1139
1140
1141 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);
1142 if (ctx == NULL || !s->relay) {
1143 return NGX_OK;
1144 }
1145
1146 ngx_memzero(&v, sizeof(v));
1147 if (ngx_rtmp_receive_amf(s, in, in_elts,
1148 sizeof(in_elts) / sizeof(in_elts[0])))
1149 {
1150 return NGX_ERROR;
1151 }
1152
1153 ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
1154 "relay: _result: level='%s' code='%s' description='%s'",
1155 v.level, v.code, v.desc);
1156
1157 switch ((ngx_int_t)v.trans) {
1158 case NGX_RTMP_RELAY_CONNECT_TRANS:
1159 return ngx_rtmp_relay_send_create_stream(s);
1160
1161 case NGX_RTMP_RELAY_CREATE_STREAM_TRANS:
1162 if (ctx->publish != ctx && !s->static_relay) {
1163 if (ngx_rtmp_relay_send_publish(s) != NGX_OK) {
1164 return NGX_ERROR;
1165 }
1166 return ngx_rtmp_relay_play_local(s);
1167
1168 } else {
1169 if (ngx_rtmp_relay_send_play(s) != NGX_OK) {
1170 return NGX_ERROR;
1171 }
1172 return ngx_rtmp_relay_publish_local(s);
1173 }
1174
1175 default:
1176 return NGX_OK;
1177 }
1178 }
1179
1180
1181 static ngx_int_t
ngx_rtmp_relay_on_error(ngx_rtmp_session_t * s,ngx_rtmp_header_t * h,ngx_chain_t * in)1182 ngx_rtmp_relay_on_error(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
1183 ngx_chain_t *in)
1184 {
1185 ngx_rtmp_relay_ctx_t *ctx;
1186 static struct {
1187 double trans;
1188 u_char level[32];
1189 u_char code[128];
1190 u_char desc[1024];
1191 } v;
1192
1193 static ngx_rtmp_amf_elt_t in_inf[] = {
1194
1195 { NGX_RTMP_AMF_STRING,
1196 ngx_string("level"),
1197 &v.level, sizeof(v.level) },
1198
1199 { NGX_RTMP_AMF_STRING,
1200 ngx_string("code"),
1201 &v.code, sizeof(v.code) },
1202
1203 { NGX_RTMP_AMF_STRING,
1204 ngx_string("description"),
1205 &v.desc, sizeof(v.desc) },
1206 };
1207
1208 static ngx_rtmp_amf_elt_t in_elts[] = {
1209
1210 { NGX_RTMP_AMF_NUMBER,
1211 ngx_null_string,
1212 &v.trans, 0 },
1213
1214 { NGX_RTMP_AMF_NULL,
1215 ngx_null_string,
1216 NULL, 0 },
1217
1218 { NGX_RTMP_AMF_OBJECT,
1219 ngx_null_string,
1220 in_inf, sizeof(in_inf) },
1221 };
1222
1223
1224 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);
1225 if (ctx == NULL || !s->relay) {
1226 return NGX_OK;
1227 }
1228
1229 ngx_memzero(&v, sizeof(v));
1230 if (ngx_rtmp_receive_amf(s, in, in_elts,
1231 sizeof(in_elts) / sizeof(in_elts[0])))
1232 {
1233 return NGX_ERROR;
1234 }
1235
1236 ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
1237 "relay: _error: level='%s' code='%s' description='%s'",
1238 v.level, v.code, v.desc);
1239
1240 return NGX_OK;
1241 }
1242
1243 static ngx_int_t
ngx_rtmp_relay_send_set_data_frame(ngx_rtmp_session_t * s)1244 ngx_rtmp_relay_send_set_data_frame(ngx_rtmp_session_t *s)
1245 {
1246 ngx_rtmp_relay_ctx_t *ctx;
1247 ngx_rtmp_codec_ctx_t *codec_ctx;
1248 ngx_rtmp_header_t hdr;
1249
1250 static struct {
1251 double width;
1252 double height;
1253 double duration;
1254 double frame_rate;
1255 double video_data_rate;
1256 double video_codec_id;
1257 double audio_data_rate;
1258 double audio_codec_id;
1259 u_char profile[32];
1260 u_char level[32];
1261 } v;
1262
1263 static ngx_rtmp_amf_elt_t out_inf[] = {
1264
1265 { NGX_RTMP_AMF_STRING,
1266 ngx_string("Server"),
1267 "NGINX RTMP (github.com/arut/nginx-rtmp-module)", 0 },
1268
1269 { NGX_RTMP_AMF_NUMBER,
1270 ngx_string("width"),
1271 &v.width, 0 },
1272
1273 { NGX_RTMP_AMF_NUMBER,
1274 ngx_string("height"),
1275 &v.height, 0 },
1276
1277 { NGX_RTMP_AMF_NUMBER,
1278 ngx_string("displayWidth"),
1279 &v.width, 0 },
1280
1281 { NGX_RTMP_AMF_NUMBER,
1282 ngx_string("displayHeight"),
1283 &v.height, 0 },
1284
1285 { NGX_RTMP_AMF_NUMBER,
1286 ngx_string("duration"),
1287 &v.duration, 0 },
1288
1289 { NGX_RTMP_AMF_NUMBER,
1290 ngx_string("framerate"),
1291 &v.frame_rate, 0 },
1292
1293 { NGX_RTMP_AMF_NUMBER,
1294 ngx_string("fps"),
1295 &v.frame_rate, 0 },
1296
1297 { NGX_RTMP_AMF_NUMBER,
1298 ngx_string("videodatarate"),
1299 &v.video_data_rate, 0 },
1300
1301 { NGX_RTMP_AMF_NUMBER,
1302 ngx_string("videocodecid"),
1303 &v.video_codec_id, 0 },
1304
1305 { NGX_RTMP_AMF_NUMBER,
1306 ngx_string("audiodatarate"),
1307 &v.audio_data_rate, 0 },
1308
1309 { NGX_RTMP_AMF_NUMBER,
1310 ngx_string("audiocodecid"),
1311 &v.audio_codec_id, 0 },
1312
1313 { NGX_RTMP_AMF_STRING,
1314 ngx_string("profile"),
1315 &v.profile, sizeof(v.profile) },
1316
1317 { NGX_RTMP_AMF_STRING,
1318 ngx_string("level"),
1319 &v.level, sizeof(v.level) }
1320 };
1321
1322 static ngx_rtmp_amf_elt_t out_elts[] = {
1323
1324 { NGX_RTMP_AMF_STRING,
1325 ngx_null_string,
1326 "@setDataFrame", 0 },
1327
1328 { NGX_RTMP_AMF_STRING,
1329 ngx_null_string,
1330 "onMetaData", 0 },
1331
1332 { NGX_RTMP_AMF_OBJECT,
1333 ngx_null_string,
1334 out_inf, sizeof(out_inf) }
1335 };
1336
1337 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);
1338 if (ctx == NULL || !s->relay) {
1339 ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
1340 "relay: couldn't get relay context");
1341 return NGX_OK;
1342 }
1343
1344 /* we need to get the codec context from the incoming publisher in order to
1345 * send the metadata along */
1346 codec_ctx = ngx_rtmp_get_module_ctx(ctx->publish->session,
1347 ngx_rtmp_codec_module);
1348 if (codec_ctx == NULL) {
1349 ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
1350 "relay: couldn't get codec context");
1351 return NGX_OK;
1352 }
1353
1354 ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
1355 "relay: data frame from codec context: "
1356 "width=%ui height=%ui duration=%ui frame_rate=%ui "
1357 "video_codec_id=%ui audio_codec_id=%ui",
1358 codec_ctx->width, codec_ctx->height, codec_ctx->duration,
1359 codec_ctx->frame_rate, codec_ctx->video_codec_id,
1360 codec_ctx->audio_codec_id);
1361
1362 /* we only want to send the metadata if the codec module has already
1363 * parsed it -- is there a better way to check this? */
1364 if (codec_ctx->width > 0 && codec_ctx->height > 0) {
1365 v.width = codec_ctx->width;
1366 v.height = codec_ctx->height;
1367 v.duration = codec_ctx->duration;
1368 v.frame_rate = codec_ctx->frame_rate;
1369 v.video_data_rate = codec_ctx->video_data_rate;
1370 v.video_codec_id = codec_ctx->video_codec_id;
1371 v.audio_data_rate = codec_ctx->audio_data_rate;
1372 v.audio_codec_id = codec_ctx->audio_codec_id;
1373 ngx_memcpy(v.profile, codec_ctx->profile, sizeof(codec_ctx->profile));
1374 ngx_memcpy(v.level, codec_ctx->level, sizeof(codec_ctx->level));
1375
1376 ngx_memzero(&hdr, sizeof(hdr));
1377 hdr.csid = NGX_RTMP_RELAY_CSID_AMF_INI;
1378 hdr.msid = NGX_RTMP_RELAY_MSID;
1379 hdr.type = NGX_RTMP_MSG_AMF_META;
1380
1381 ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
1382 "relay: sending @setDataFrame");
1383
1384 return ngx_rtmp_send_amf(s, &hdr, out_elts,
1385 sizeof(out_elts) / sizeof(out_elts[0]));
1386 }
1387
1388 return NGX_OK;
1389 }
1390
1391 static ngx_int_t
ngx_rtmp_relay_on_status(ngx_rtmp_session_t * s,ngx_rtmp_header_t * h,ngx_chain_t * in)1392 ngx_rtmp_relay_on_status(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
1393 ngx_chain_t *in)
1394 {
1395 ngx_rtmp_relay_ctx_t *ctx;
1396
1397 static struct {
1398 double trans;
1399 u_char level[32];
1400 u_char code[128];
1401 u_char desc[1024];
1402 } v;
1403
1404 static ngx_rtmp_amf_elt_t in_inf[] = {
1405
1406 { NGX_RTMP_AMF_STRING,
1407 ngx_string("level"),
1408 &v.level, sizeof(v.level) },
1409
1410 { NGX_RTMP_AMF_STRING,
1411 ngx_string("code"),
1412 &v.code, sizeof(v.code) },
1413
1414 { NGX_RTMP_AMF_STRING,
1415 ngx_string("description"),
1416 &v.desc, sizeof(v.desc) },
1417 };
1418
1419 static ngx_rtmp_amf_elt_t in_elts[] = {
1420
1421 { NGX_RTMP_AMF_NUMBER,
1422 ngx_null_string,
1423 &v.trans, 0 },
1424
1425 { NGX_RTMP_AMF_NULL,
1426 ngx_null_string,
1427 NULL, 0 },
1428
1429 { NGX_RTMP_AMF_OBJECT,
1430 ngx_null_string,
1431 in_inf, sizeof(in_inf) },
1432 };
1433
1434 static ngx_rtmp_amf_elt_t in_elts_meta[] = {
1435
1436 { NGX_RTMP_AMF_OBJECT,
1437 ngx_null_string,
1438 in_inf, sizeof(in_inf) },
1439 };
1440
1441
1442 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);
1443 if (ctx == NULL || !s->relay) {
1444 return NGX_OK;
1445 }
1446
1447 ngx_memzero(&v, sizeof(v));
1448 if (h->type == NGX_RTMP_MSG_AMF_META) {
1449 ngx_rtmp_receive_amf(s, in, in_elts_meta,
1450 sizeof(in_elts_meta) / sizeof(in_elts_meta[0]));
1451 } else {
1452 ngx_rtmp_receive_amf(s, in, in_elts,
1453 sizeof(in_elts) / sizeof(in_elts[0]));
1454 }
1455
1456 ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
1457 "relay: onStatus: level='%s' code='%s' description='%s'",
1458 v.level, v.code, v.desc);
1459
1460 /* when doing a push to Adobe Media Server, we have to use the
1461 * @setDataFrame command to send the metadata
1462 * see: http://help.adobe.com/en_US/adobemediaserver/devguide/WS5b3ccc516d4fbf351e63e3d11a0773d56e-7ff6Dev.2.3.html
1463 */
1464 if (!ngx_strncasecmp(v.code, (u_char *)"NetStream.Publish.Start",
1465 ngx_strlen("NetStream.Publish.Start"))) {
1466
1467 ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
1468 "relay: sending metadata from NetStream.Publish.Start from player");
1469
1470 s->ready_for_publish = 1;
1471
1472 if (ngx_rtmp_relay_send_set_data_frame(s) != NGX_OK) {
1473 ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
1474 "relay: unable to send metadata via @setDataFrame");
1475 }
1476 }
1477
1478 return NGX_OK;
1479 }
1480
1481 static ngx_int_t
ngx_rtmp_relay_on_meta_data(ngx_rtmp_session_t * s,ngx_rtmp_header_t * h,ngx_chain_t * in)1482 ngx_rtmp_relay_on_meta_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
1483 ngx_chain_t *in)
1484 {
1485 /* when we receive onMetaData, the session (s) is our incoming publisher's
1486 * session, so we need to send the @setDataFrame to our ctx->play->session */
1487 ngx_rtmp_relay_ctx_t *ctx;
1488 ngx_rtmp_relay_ctx_t *pctx;
1489
1490 ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
1491 "relay: got metadata from @setDataFrame invocation from publisher.");
1492
1493 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);
1494 if (ctx == NULL) {
1495 return NGX_OK;
1496 }
1497
1498 for (pctx = ctx->play; pctx; pctx = pctx->next) {
1499 ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
1500 "relay: %ssending metadata from @setDataFrame invocation from publisher to %V/%V/%V",
1501 (pctx->session->relay && pctx->session->ready_for_publish) ? "" : "not ", &pctx->url, &pctx->app, &pctx->play_path);
1502 if (!pctx->session->relay || !pctx->session->ready_for_publish) continue;
1503 if (ngx_rtmp_relay_send_set_data_frame(pctx->session) != NGX_OK) {
1504 ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
1505 "relay: unable to send @setDataFrame to %V/%V", &pctx->url, &pctx->play_path);
1506 }
1507 }
1508
1509 return NGX_OK;
1510 }
1511
1512 static ngx_int_t
ngx_rtmp_relay_handshake_done(ngx_rtmp_session_t * s,ngx_rtmp_header_t * h,ngx_chain_t * in)1513 ngx_rtmp_relay_handshake_done(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
1514 ngx_chain_t *in)
1515 {
1516 ngx_rtmp_relay_ctx_t *ctx;
1517
1518 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);
1519 if (ctx == NULL || !s->relay) {
1520 return NGX_OK;
1521 }
1522
1523 return ngx_rtmp_relay_send_connect(s);
1524 }
1525
1526
1527 static void
ngx_rtmp_relay_close(ngx_rtmp_session_t * s)1528 ngx_rtmp_relay_close(ngx_rtmp_session_t *s)
1529 {
1530 ngx_rtmp_relay_app_conf_t *racf;
1531 ngx_rtmp_relay_ctx_t *ctx, **cctx;
1532 ngx_uint_t hash;
1533
1534 racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_relay_module);
1535
1536 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);
1537 if (ctx == NULL) {
1538 return;
1539 }
1540
1541 if (s->static_relay) {
1542 ngx_add_timer(ctx->static_evt, racf->pull_reconnect);
1543 }
1544
1545 if (ctx->publish == NULL) {
1546 return;
1547 }
1548
1549 /* play end disconnect? */
1550 if (ctx->publish != ctx) {
1551 for (cctx = &ctx->publish->play; *cctx; cctx = &(*cctx)->next) {
1552 if (*cctx == ctx) {
1553 *cctx = ctx->next;
1554 break;
1555 }
1556 }
1557
1558 ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ctx->session->connection->log, 0,
1559 "relay: play disconnect app='%V' name='%V'",
1560 &ctx->app, &ctx->name);
1561
1562 /* push reconnect */
1563 if (s->relay && ctx->tag == &ngx_rtmp_relay_module &&
1564 !ctx->publish->push_evt.timer_set)
1565 {
1566 ngx_add_timer(&ctx->publish->push_evt, racf->push_reconnect);
1567 }
1568
1569 #ifdef NGX_DEBUG
1570 {
1571 ngx_uint_t n = 0;
1572 for (cctx = &ctx->publish->play; *cctx; cctx = &(*cctx)->next, ++n);
1573 ngx_log_debug3(NGX_LOG_DEBUG_RTMP, ctx->session->connection->log, 0,
1574 "relay: play left after disconnect app='%V' name='%V': %ui",
1575 &ctx->app, &ctx->name, n);
1576 }
1577 #endif
1578
1579 if (ctx->publish->play == NULL && ctx->publish->session->relay) {
1580 ngx_log_debug2(NGX_LOG_DEBUG_RTMP,
1581 ctx->publish->session->connection->log, 0,
1582 "relay: publish disconnect empty app='%V' name='%V'",
1583 &ctx->app, &ctx->name);
1584 ngx_rtmp_finalize_session(ctx->publish->session);
1585 }
1586
1587 ctx->publish = NULL;
1588
1589 return;
1590 }
1591
1592 /* publish end disconnect */
1593 ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ctx->session->connection->log, 0,
1594 "relay: publish disconnect app='%V' name='%V'",
1595 &ctx->app, &ctx->name);
1596
1597 if (ctx->push_evt.timer_set) {
1598 ngx_del_timer(&ctx->push_evt);
1599 }
1600
1601 for (cctx = &ctx->play; *cctx; cctx = &(*cctx)->next) {
1602 (*cctx)->publish = NULL;
1603 ngx_log_debug2(NGX_LOG_DEBUG_RTMP, (*cctx)->session->connection->log,
1604 0, "relay: play disconnect orphan app='%V' name='%V'",
1605 &(*cctx)->app, &(*cctx)->name);
1606 ngx_rtmp_finalize_session((*cctx)->session);
1607 }
1608 ctx->publish = NULL;
1609
1610 hash = ngx_hash_key(ctx->name.data, ctx->name.len);
1611 cctx = &racf->ctx[hash % racf->nbuckets];
1612 for (; *cctx && *cctx != ctx; cctx = &(*cctx)->next);
1613 if (*cctx) {
1614 *cctx = ctx->next;
1615 }
1616 }
1617
1618
1619 static ngx_int_t
ngx_rtmp_relay_close_stream(ngx_rtmp_session_t * s,ngx_rtmp_close_stream_t * v)1620 ngx_rtmp_relay_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v)
1621 {
1622 ngx_rtmp_relay_app_conf_t *racf;
1623
1624 racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_relay_module);
1625 if (racf && !racf->session_relay) {
1626 ngx_rtmp_relay_close(s);
1627 }
1628
1629 return next_close_stream(s, v);
1630 }
1631
1632
1633 static ngx_int_t
ngx_rtmp_relay_delete_stream(ngx_rtmp_session_t * s,ngx_rtmp_delete_stream_t * v)1634 ngx_rtmp_relay_delete_stream(ngx_rtmp_session_t *s, ngx_rtmp_delete_stream_t *v)
1635 {
1636 ngx_rtmp_relay_close(s);
1637
1638 return next_delete_stream(s, v);
1639 }
1640
1641
1642 static char *
ngx_rtmp_relay_push_pull(ngx_conf_t * cf,ngx_command_t * cmd,void * conf)1643 ngx_rtmp_relay_push_pull(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
1644 {
1645 ngx_str_t *value, v, n;
1646 ngx_rtmp_relay_app_conf_t *racf;
1647 ngx_rtmp_relay_target_t *target, **t;
1648 ngx_url_t *u;
1649 ngx_uint_t i;
1650 ngx_int_t is_pull, is_static;
1651 ngx_event_t **ee, *e;
1652 ngx_rtmp_relay_static_t *rs;
1653 u_char *p;
1654
1655 value = cf->args->elts;
1656
1657 racf = ngx_rtmp_conf_get_module_app_conf(cf, ngx_rtmp_relay_module);
1658
1659 is_pull = (value[0].data[3] == 'l');
1660 is_static = 0;
1661
1662 target = ngx_pcalloc(cf->pool, sizeof(*target));
1663 if (target == NULL) {
1664 return NGX_CONF_ERROR;
1665 }
1666
1667 target->tag = &ngx_rtmp_relay_module;
1668 target->data = target;
1669
1670 u = &target->url;
1671 u->default_port = 1935;
1672 u->uri_part = 1;
1673 u->url = value[1];
1674
1675 if (ngx_strncasecmp(u->url.data, (u_char *) "rtmp://", 7) == 0) {
1676 u->url.data += 7;
1677 u->url.len -= 7;
1678 }
1679
1680 if (ngx_parse_url(cf->pool, u) != NGX_OK) {
1681 if (u->err) {
1682 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
1683 "%s in url \"%V\"", u->err, &u->url);
1684 }
1685 return NGX_CONF_ERROR;
1686 }
1687
1688 value += 2;
1689 for (i = 2; i < cf->args->nelts; ++i, ++value) {
1690 p = ngx_strlchr(value->data, value->data + value->len, '=');
1691
1692 if (p == NULL) {
1693 n = *value;
1694 ngx_str_set(&v, "1");
1695
1696 } else {
1697 n.data = value->data;
1698 n.len = p - value->data;
1699
1700 v.data = p + 1;
1701 v.len = value->data + value->len - p - 1;
1702 }
1703
1704 #define NGX_RTMP_RELAY_STR_PAR(name, var) \
1705 if (n.len == sizeof(name) - 1 \
1706 && ngx_strncasecmp(n.data, (u_char *) name, n.len) == 0) \
1707 { \
1708 target->var = v; \
1709 continue; \
1710 }
1711
1712 #define NGX_RTMP_RELAY_NUM_PAR(name, var) \
1713 if (n.len == sizeof(name) - 1 \
1714 && ngx_strncasecmp(n.data, (u_char *) name, n.len) == 0) \
1715 { \
1716 target->var = ngx_atoi(v.data, v.len); \
1717 continue; \
1718 }
1719
1720 NGX_RTMP_RELAY_STR_PAR("app", app);
1721 NGX_RTMP_RELAY_STR_PAR("name", name);
1722 NGX_RTMP_RELAY_STR_PAR("tcUrl", tc_url);
1723 NGX_RTMP_RELAY_STR_PAR("pageUrl", page_url);
1724 NGX_RTMP_RELAY_STR_PAR("swfUrl", swf_url);
1725 NGX_RTMP_RELAY_STR_PAR("flashVer", flash_ver);
1726 NGX_RTMP_RELAY_STR_PAR("playPath", play_path);
1727 NGX_RTMP_RELAY_NUM_PAR("live", live);
1728 NGX_RTMP_RELAY_NUM_PAR("start", start);
1729 NGX_RTMP_RELAY_NUM_PAR("stop", stop);
1730
1731 #undef NGX_RTMP_RELAY_STR_PAR
1732 #undef NGX_RTMP_RELAY_NUM_PAR
1733
1734 if (n.len == sizeof("static") - 1 &&
1735 ngx_strncasecmp(n.data, (u_char *) "static", n.len) == 0 &&
1736 ngx_atoi(v.data, v.len))
1737 {
1738 is_static = 1;
1739 continue;
1740 }
1741
1742 return "unsuppored parameter";
1743 }
1744
1745 if (is_static) {
1746
1747 if (!is_pull) {
1748 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
1749 "static push is not allowed");
1750 return NGX_CONF_ERROR;
1751 }
1752
1753 if (target->name.len == 0) {
1754 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
1755 "stream name missing in static pull "
1756 "declaration");
1757 return NGX_CONF_ERROR;
1758 }
1759
1760 ee = ngx_array_push(&racf->static_events);
1761 if (ee == NULL) {
1762 return NGX_CONF_ERROR;
1763 }
1764
1765 e = ngx_pcalloc(cf->pool, sizeof(ngx_event_t));
1766 if (e == NULL) {
1767 return NGX_CONF_ERROR;
1768 }
1769
1770 *ee = e;
1771
1772 rs = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_relay_static_t));
1773 if (rs == NULL) {
1774 return NGX_CONF_ERROR;
1775 }
1776
1777 rs->target = target;
1778
1779 e->data = rs;
1780 e->log = &cf->cycle->new_log;
1781 e->handler = ngx_rtmp_relay_static_pull_reconnect;
1782
1783 t = ngx_array_push(&racf->static_pulls);
1784
1785 } else if (is_pull) {
1786 t = ngx_array_push(&racf->pulls);
1787
1788 } else {
1789 t = ngx_array_push(&racf->pushes);
1790 }
1791
1792 if (t == NULL) {
1793 return NGX_CONF_ERROR;
1794 }
1795
1796 *t = target;
1797
1798 return NGX_CONF_OK;
1799 }
1800
1801
1802 static ngx_int_t
ngx_rtmp_relay_init_process(ngx_cycle_t * cycle)1803 ngx_rtmp_relay_init_process(ngx_cycle_t *cycle)
1804 {
1805 #if !(NGX_WIN32)
1806 ngx_rtmp_core_main_conf_t *cmcf = ngx_rtmp_core_main_conf;
1807 ngx_rtmp_core_srv_conf_t **pcscf, *cscf;
1808 ngx_rtmp_core_app_conf_t **pcacf, *cacf;
1809 ngx_rtmp_relay_app_conf_t *racf;
1810 ngx_uint_t n, m, k;
1811 ngx_rtmp_relay_static_t *rs;
1812 ngx_rtmp_listen_t *lst;
1813 ngx_event_t **pevent, *event;
1814
1815 if (cmcf == NULL || cmcf->listen.nelts == 0) {
1816 return NGX_OK;
1817 }
1818
1819 /* only first worker does static pulling */
1820
1821 if (ngx_process_slot) {
1822 return NGX_OK;
1823 }
1824
1825 lst = cmcf->listen.elts;
1826
1827 pcscf = cmcf->servers.elts;
1828 for (n = 0; n < cmcf->servers.nelts; ++n, ++pcscf) {
1829
1830 cscf = *pcscf;
1831 pcacf = cscf->applications.elts;
1832
1833 for (m = 0; m < cscf->applications.nelts; ++m, ++pcacf) {
1834
1835 cacf = *pcacf;
1836 racf = cacf->app_conf[ngx_rtmp_relay_module.ctx_index];
1837 pevent = racf->static_events.elts;
1838
1839 for (k = 0; k < racf->static_events.nelts; ++k, ++pevent) {
1840 event = *pevent;
1841
1842 rs = event->data;
1843 rs->cctx = *lst->ctx;
1844 rs->cctx.app_conf = cacf->app_conf;
1845
1846 ngx_post_event(event, &ngx_rtmp_init_queue);
1847 }
1848 }
1849 }
1850 #endif
1851 return NGX_OK;
1852 }
1853
1854
1855 static ngx_int_t
ngx_rtmp_relay_postconfiguration(ngx_conf_t * cf)1856 ngx_rtmp_relay_postconfiguration(ngx_conf_t *cf)
1857 {
1858 ngx_rtmp_core_main_conf_t *cmcf;
1859 ngx_rtmp_handler_pt *h;
1860 ngx_rtmp_amf_handler_t *ch;
1861
1862 cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);
1863
1864
1865 h = ngx_array_push(&cmcf->events[NGX_RTMP_HANDSHAKE_DONE]);
1866 *h = ngx_rtmp_relay_handshake_done;
1867
1868
1869 next_publish = ngx_rtmp_publish;
1870 ngx_rtmp_publish = ngx_rtmp_relay_publish;
1871
1872 next_play = ngx_rtmp_play;
1873 ngx_rtmp_play = ngx_rtmp_relay_play;
1874
1875 next_delete_stream = ngx_rtmp_delete_stream;
1876 ngx_rtmp_delete_stream = ngx_rtmp_relay_delete_stream;
1877
1878 next_close_stream = ngx_rtmp_close_stream;
1879 ngx_rtmp_close_stream = ngx_rtmp_relay_close_stream;
1880
1881
1882 ch = ngx_array_push(&cmcf->amf);
1883 ngx_str_set(&ch->name, "_result");
1884 ch->handler = ngx_rtmp_relay_on_result;
1885
1886 ch = ngx_array_push(&cmcf->amf);
1887 ngx_str_set(&ch->name, "_error");
1888 ch->handler = ngx_rtmp_relay_on_error;
1889
1890 ch = ngx_array_push(&cmcf->amf);
1891 ngx_str_set(&ch->name, "onStatus");
1892 ch->handler = ngx_rtmp_relay_on_status;
1893
1894 ch = ngx_array_push(&cmcf->amf);
1895 ngx_str_set(&ch->name, "@setDataFrame");
1896 ch->handler = ngx_rtmp_relay_on_meta_data;
1897
1898 return NGX_OK;
1899 }
1900