1
2 /*
3 * Copyright (C) Roman Arutyunyan
4 */
5
6
7 #include <ngx_config.h>
8 #include <ngx_core.h>
9 #include "ngx_rtmp_codec_module.h"
10 #include "ngx_rtmp_live_module.h"
11 #include "ngx_rtmp_cmd_module.h"
12 #include "ngx_rtmp_bitop.h"
13
14
15 #define NGX_RTMP_CODEC_META_OFF 0
16 #define NGX_RTMP_CODEC_META_ON 1
17 #define NGX_RTMP_CODEC_META_COPY 2
18
19
20 static void * ngx_rtmp_codec_create_app_conf(ngx_conf_t *cf);
21 static char * ngx_rtmp_codec_merge_app_conf(ngx_conf_t *cf,
22 void *parent, void *child);
23 static ngx_int_t ngx_rtmp_codec_postconfiguration(ngx_conf_t *cf);
24 static ngx_int_t ngx_rtmp_codec_reconstruct_meta(ngx_rtmp_session_t *s);
25 static ngx_int_t ngx_rtmp_codec_copy_meta(ngx_rtmp_session_t *s,
26 ngx_rtmp_header_t *h, ngx_chain_t *in);
27 static ngx_int_t ngx_rtmp_codec_prepare_meta(ngx_rtmp_session_t *s,
28 uint32_t timestamp);
29 static void ngx_rtmp_codec_parse_aac_header(ngx_rtmp_session_t *s,
30 ngx_chain_t *in);
31 static void ngx_rtmp_codec_parse_avc_header(ngx_rtmp_session_t *s,
32 ngx_chain_t *in);
33 #if (NGX_DEBUG)
34 static void ngx_rtmp_codec_dump_header(ngx_rtmp_session_t *s, const char *type,
35 ngx_chain_t *in);
36 #endif
37
38
39 typedef struct {
40 ngx_uint_t meta;
41 } ngx_rtmp_codec_app_conf_t;
42
43
44 static ngx_conf_enum_t ngx_rtmp_codec_meta_slots[] = {
45 { ngx_string("off"), NGX_RTMP_CODEC_META_OFF },
46 { ngx_string("on"), NGX_RTMP_CODEC_META_ON },
47 { ngx_string("copy"), NGX_RTMP_CODEC_META_COPY },
48 { ngx_null_string, 0 }
49 };
50
51
52 static ngx_command_t ngx_rtmp_codec_commands[] = {
53
54 { ngx_string("meta"),
55 NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
56 ngx_conf_set_enum_slot,
57 NGX_RTMP_APP_CONF_OFFSET,
58 offsetof(ngx_rtmp_codec_app_conf_t, meta),
59 &ngx_rtmp_codec_meta_slots },
60
61 ngx_null_command
62 };
63
64
65 static ngx_rtmp_module_t ngx_rtmp_codec_module_ctx = {
66 NULL, /* preconfiguration */
67 ngx_rtmp_codec_postconfiguration, /* postconfiguration */
68 NULL, /* create main configuration */
69 NULL, /* init main configuration */
70 NULL, /* create server configuration */
71 NULL, /* merge server configuration */
72 ngx_rtmp_codec_create_app_conf, /* create app configuration */
73 ngx_rtmp_codec_merge_app_conf /* merge app configuration */
74 };
75
76
77 ngx_module_t ngx_rtmp_codec_module = {
78 NGX_MODULE_V1,
79 &ngx_rtmp_codec_module_ctx, /* module context */
80 ngx_rtmp_codec_commands, /* module directives */
81 NGX_RTMP_MODULE, /* module type */
82 NULL, /* init master */
83 NULL, /* init module */
84 NULL, /* init process */
85 NULL, /* init thread */
86 NULL, /* exit thread */
87 NULL, /* exit process */
88 NULL, /* exit master */
89 NGX_MODULE_V1_PADDING
90 };
91
92
93 static const char *
94 audio_codecs[] = {
95 "",
96 "ADPCM",
97 "MP3",
98 "LinearLE",
99 "Nellymoser16",
100 "Nellymoser8",
101 "Nellymoser",
102 "G711A",
103 "G711U",
104 "",
105 "AAC",
106 "Speex",
107 "",
108 "",
109 "MP3-8K",
110 "DeviceSpecific",
111 "Uncompressed"
112 };
113
114
115 static const char *
116 video_codecs[] = {
117 "",
118 "Jpeg",
119 "Sorenson-H263",
120 "ScreenVideo",
121 "On2-VP6",
122 "On2-VP6-Alpha",
123 "ScreenVideo2",
124 "H264",
125 };
126
127
128 u_char *
ngx_rtmp_get_audio_codec_name(ngx_uint_t id)129 ngx_rtmp_get_audio_codec_name(ngx_uint_t id)
130 {
131 return (u_char *)(id < sizeof(audio_codecs) / sizeof(audio_codecs[0])
132 ? audio_codecs[id]
133 : "");
134 }
135
136
137 u_char *
ngx_rtmp_get_video_codec_name(ngx_uint_t id)138 ngx_rtmp_get_video_codec_name(ngx_uint_t id)
139 {
140 return (u_char *)(id < sizeof(video_codecs) / sizeof(video_codecs[0])
141 ? video_codecs[id]
142 : "");
143 }
144
145
146 static ngx_uint_t
ngx_rtmp_codec_get_next_version()147 ngx_rtmp_codec_get_next_version()
148 {
149 ngx_uint_t v;
150 static ngx_uint_t version;
151
152 do {
153 v = ++version;
154 } while (v == 0);
155
156 return v;
157 }
158
159
160 static ngx_int_t
ngx_rtmp_codec_disconnect(ngx_rtmp_session_t * s,ngx_rtmp_header_t * h,ngx_chain_t * in)161 ngx_rtmp_codec_disconnect(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
162 ngx_chain_t *in)
163 {
164 ngx_rtmp_codec_ctx_t *ctx;
165 ngx_rtmp_core_srv_conf_t *cscf;
166
167 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
168 if (ctx == NULL) {
169 return NGX_OK;
170 }
171
172 cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
173
174 if (ctx->avc_header) {
175 ngx_rtmp_free_shared_chain(cscf, ctx->avc_header);
176 ctx->avc_header = NULL;
177 }
178
179 if (ctx->aac_header) {
180 ngx_rtmp_free_shared_chain(cscf, ctx->aac_header);
181 ctx->aac_header = NULL;
182 }
183
184 if (ctx->meta) {
185 ngx_rtmp_free_shared_chain(cscf, ctx->meta);
186 ctx->meta = NULL;
187 }
188
189 return NGX_OK;
190 }
191
192
193 static ngx_int_t
ngx_rtmp_codec_av(ngx_rtmp_session_t * s,ngx_rtmp_header_t * h,ngx_chain_t * in)194 ngx_rtmp_codec_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
195 ngx_chain_t *in)
196 {
197 ngx_rtmp_core_srv_conf_t *cscf;
198 ngx_rtmp_codec_ctx_t *ctx;
199 ngx_chain_t **header;
200 uint8_t fmt;
201 static ngx_uint_t sample_rates[] =
202 { 5512, 11025, 22050, 44100 };
203
204 if (h->type != NGX_RTMP_MSG_AUDIO && h->type != NGX_RTMP_MSG_VIDEO) {
205 return NGX_OK;
206 }
207
208 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
209 if (ctx == NULL) {
210 ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_rtmp_codec_ctx_t));
211 ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_codec_module);
212 }
213
214 /* save codec */
215 if (in->buf->last - in->buf->pos < 1) {
216 return NGX_OK;
217 }
218
219 fmt = in->buf->pos[0];
220 if (h->type == NGX_RTMP_MSG_AUDIO) {
221 ctx->audio_codec_id = (fmt & 0xf0) >> 4;
222 ctx->audio_channels = (fmt & 0x01) + 1;
223 ctx->sample_size = (fmt & 0x02) ? 2 : 1;
224
225 if (ctx->sample_rate == 0) {
226 ctx->sample_rate = sample_rates[(fmt & 0x0c) >> 2];
227 }
228 } else {
229 ctx->video_codec_id = (fmt & 0x0f);
230 }
231
232 /* save AVC/AAC header */
233 if (in->buf->last - in->buf->pos < 3) {
234 return NGX_OK;
235 }
236
237 /* no conf */
238 if (!ngx_rtmp_is_codec_header(in)) {
239 return NGX_OK;
240 }
241
242 cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
243 header = NULL;
244
245 if (h->type == NGX_RTMP_MSG_AUDIO) {
246 if (ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC) {
247 header = &ctx->aac_header;
248 ngx_rtmp_codec_parse_aac_header(s, in);
249 }
250 } else {
251 if (ctx->video_codec_id == NGX_RTMP_VIDEO_H264) {
252 header = &ctx->avc_header;
253 ngx_rtmp_codec_parse_avc_header(s, in);
254 }
255 }
256
257 if (header == NULL) {
258 return NGX_OK;
259 }
260
261 if (*header) {
262 ngx_rtmp_free_shared_chain(cscf, *header);
263 }
264
265 *header = ngx_rtmp_append_shared_bufs(cscf, NULL, in);
266
267 return NGX_OK;
268 }
269
270
271 static void
ngx_rtmp_codec_parse_aac_header(ngx_rtmp_session_t * s,ngx_chain_t * in)272 ngx_rtmp_codec_parse_aac_header(ngx_rtmp_session_t *s, ngx_chain_t *in)
273 {
274 ngx_uint_t idx;
275 ngx_rtmp_codec_ctx_t *ctx;
276 ngx_rtmp_bit_reader_t br;
277
278 static ngx_uint_t aac_sample_rates[] =
279 { 96000, 88200, 64000, 48000,
280 44100, 32000, 24000, 22050,
281 16000, 12000, 11025, 8000,
282 7350, 0, 0, 0 };
283
284 #if (NGX_DEBUG)
285 ngx_rtmp_codec_dump_header(s, "aac", in);
286 #endif
287
288 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
289
290 ngx_rtmp_bit_init_reader(&br, in->buf->pos, in->buf->last);
291
292 ngx_rtmp_bit_read(&br, 16);
293
294 ctx->aac_profile = (ngx_uint_t) ngx_rtmp_bit_read(&br, 5);
295 if (ctx->aac_profile == 31) {
296 ctx->aac_profile = (ngx_uint_t) ngx_rtmp_bit_read(&br, 6) + 32;
297 }
298
299 idx = (ngx_uint_t) ngx_rtmp_bit_read(&br, 4);
300 if (idx == 15) {
301 ctx->sample_rate = (ngx_uint_t) ngx_rtmp_bit_read(&br, 24);
302 } else {
303 ctx->sample_rate = aac_sample_rates[idx];
304 }
305
306 ctx->aac_chan_conf = (ngx_uint_t) ngx_rtmp_bit_read(&br, 4);
307
308 if (ctx->aac_profile == 5 || ctx->aac_profile == 29) {
309
310 if (ctx->aac_profile == 29) {
311 ctx->aac_ps = 1;
312 }
313
314 ctx->aac_sbr = 1;
315
316 idx = (ngx_uint_t) ngx_rtmp_bit_read(&br, 4);
317 if (idx == 15) {
318 ctx->sample_rate = (ngx_uint_t) ngx_rtmp_bit_read(&br, 24);
319 } else {
320 ctx->sample_rate = aac_sample_rates[idx];
321 }
322
323 ctx->aac_profile = (ngx_uint_t) ngx_rtmp_bit_read(&br, 5);
324 if (ctx->aac_profile == 31) {
325 ctx->aac_profile = (ngx_uint_t) ngx_rtmp_bit_read(&br, 6) + 32;
326 }
327 }
328
329 /* MPEG-4 Audio Specific Config
330
331 5 bits: object type
332 if (object type == 31)
333 6 bits + 32: object type
334 4 bits: frequency index
335 if (frequency index == 15)
336 24 bits: frequency
337 4 bits: channel configuration
338
339 if (object_type == 5)
340 4 bits: frequency index
341 if (frequency index == 15)
342 24 bits: frequency
343 5 bits: object type
344 if (object type == 31)
345 6 bits + 32: object type
346
347 var bits: AOT Specific Config
348 */
349
350 ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
351 "codec: aac header profile=%ui, "
352 "sample_rate=%ui, chan_conf=%ui",
353 ctx->aac_profile, ctx->sample_rate, ctx->aac_chan_conf);
354 }
355
356
357 static void
ngx_rtmp_codec_parse_avc_header(ngx_rtmp_session_t * s,ngx_chain_t * in)358 ngx_rtmp_codec_parse_avc_header(ngx_rtmp_session_t *s, ngx_chain_t *in)
359 {
360 ngx_uint_t profile_idc, width, height, crop_left, crop_right,
361 crop_top, crop_bottom, frame_mbs_only, n, cf_n, cf_idc,
362 // num_ref_frames;
363 num_ref_frames, sl_size, sl_index, sl_udelta;
364 ngx_int_t sl_last, sl_next, sl_delta;
365 ngx_rtmp_codec_ctx_t *ctx;
366 ngx_rtmp_bit_reader_t br;
367
368 #if (NGX_DEBUG)
369 ngx_rtmp_codec_dump_header(s, "avc", in);
370 #endif
371
372 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
373
374 ngx_rtmp_bit_init_reader(&br, in->buf->pos, in->buf->last);
375
376 ngx_rtmp_bit_read(&br, 48);
377
378 ctx->avc_profile = (ngx_uint_t) ngx_rtmp_bit_read_8(&br);
379 ctx->avc_compat = (ngx_uint_t) ngx_rtmp_bit_read_8(&br);
380 ctx->avc_level = (ngx_uint_t) ngx_rtmp_bit_read_8(&br);
381
382 /* nal bytes */
383 ctx->avc_nal_bytes = (ngx_uint_t) ((ngx_rtmp_bit_read_8(&br) & 0x03) + 1);
384
385 /* nnals */
386 if ((ngx_rtmp_bit_read_8(&br) & 0x1f) == 0) {
387 return;
388 }
389
390 /* nal size */
391 ngx_rtmp_bit_read(&br, 16);
392
393 /* nal type */
394 if (ngx_rtmp_bit_read_8(&br) != 0x67) {
395 return;
396 }
397
398 /* SPS */
399
400 /* profile idc */
401 profile_idc = (ngx_uint_t) ngx_rtmp_bit_read(&br, 8);
402
403 /* flags */
404 ngx_rtmp_bit_read(&br, 8);
405
406 /* level idc */
407 ngx_rtmp_bit_read(&br, 8);
408
409 /* SPS id */
410 ngx_rtmp_bit_read_golomb(&br);
411
412 if (profile_idc == 100 || profile_idc == 110 ||
413 profile_idc == 122 || profile_idc == 244 || profile_idc == 44 ||
414 profile_idc == 83 || profile_idc == 86 || profile_idc == 118)
415 {
416 /* chroma format idc */
417 cf_idc = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br);
418
419 if (cf_idc == 3) {
420
421 /* separate color plane */
422 ngx_rtmp_bit_read(&br, 1);
423 }
424
425 /* bit depth luma - 8 */
426 ngx_rtmp_bit_read_golomb(&br);
427
428 /* bit depth chroma - 8 */
429 ngx_rtmp_bit_read_golomb(&br);
430
431 /* qpprime y zero transform bypass */
432 ngx_rtmp_bit_read(&br, 1);
433
434 /* seq scaling matrix present */
435 if (ngx_rtmp_bit_read(&br, 1)) {
436
437 for (n = 0, cf_n = (cf_idc != 3 ? 8u : 12u); n < cf_n; n++) {
438
439 /* seq scaling list present */
440 if (ngx_rtmp_bit_read(&br, 1)) {
441
442 /* scaling list */
443 if (n < 6) {
444 sl_size = 16;
445 } else {
446 sl_size = 64;
447 }
448
449 sl_last = 8;
450 sl_next = 8;
451
452 for (sl_index = 0; sl_index < sl_size; sl_index++) {
453
454 if (sl_next != 0) {
455
456 /* convert to signed: (-1)**k+1 * ceil(k/2) */
457 sl_udelta = (ngx_uint_t)ngx_rtmp_bit_read_golomb(&br);
458 sl_delta = (sl_udelta + 1) >> 1;
459 if ((sl_udelta & 1) == 0) {
460 sl_delta = -sl_delta;
461 }
462
463 sl_next = (sl_last + sl_delta + 256) % 256;
464
465 if (sl_next != 0) {
466 sl_last = sl_next;
467 }
468 }
469 }
470 }
471 }
472 }
473 }
474
475 /* log2 max frame num */
476 ngx_rtmp_bit_read_golomb(&br);
477
478 /* pic order cnt type */
479 switch (ngx_rtmp_bit_read_golomb(&br)) {
480 case 0:
481
482 /* max pic order cnt */
483 ngx_rtmp_bit_read_golomb(&br);
484 break;
485
486 case 1:
487
488 /* delta pic order alwys zero */
489 ngx_rtmp_bit_read(&br, 1);
490
491 /* offset for non-ref pic */
492 ngx_rtmp_bit_read_golomb(&br);
493
494 /* offset for top to bottom field */
495 ngx_rtmp_bit_read_golomb(&br);
496
497 /* num ref frames in pic order */
498 num_ref_frames = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br);
499
500 for (n = 0; n < num_ref_frames; n++) {
501
502 /* offset for ref frame */
503 ngx_rtmp_bit_read_golomb(&br);
504 }
505 }
506
507 /* num ref frames */
508 ctx->avc_ref_frames = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br);
509
510 /* gaps in frame num allowed */
511 ngx_rtmp_bit_read(&br, 1);
512
513 /* pic width in mbs - 1 */
514 width = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br);
515
516 /* pic height in map units - 1 */
517 height = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br);
518
519 /* frame mbs only flag */
520 frame_mbs_only = (ngx_uint_t) ngx_rtmp_bit_read(&br, 1);
521
522 if (!frame_mbs_only) {
523
524 /* mbs adaprive frame field */
525 ngx_rtmp_bit_read(&br, 1);
526 }
527
528 /* direct 8x8 inference flag */
529 ngx_rtmp_bit_read(&br, 1);
530
531 /* frame cropping */
532 if (ngx_rtmp_bit_read(&br, 1)) {
533
534 crop_left = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br);
535 crop_right = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br);
536 crop_top = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br);
537 crop_bottom = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br);
538
539 } else {
540
541 crop_left = 0;
542 crop_right = 0;
543 crop_top = 0;
544 crop_bottom = 0;
545 }
546
547 ctx->width = (width + 1) * 16 - (crop_left + crop_right) * 2;
548 ctx->height = (2 - frame_mbs_only) * (height + 1) * 16 -
549 (crop_top + crop_bottom) * 2;
550
551 ngx_log_debug7(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
552 "codec: avc header "
553 "profile=%ui, compat=%ui, level=%ui, "
554 "nal_bytes=%ui, ref_frames=%ui, width=%ui, height=%ui",
555 ctx->avc_profile, ctx->avc_compat, ctx->avc_level,
556 ctx->avc_nal_bytes, ctx->avc_ref_frames,
557 ctx->width, ctx->height);
558 }
559
560
561 #if (NGX_DEBUG)
562 static void
ngx_rtmp_codec_dump_header(ngx_rtmp_session_t * s,const char * type,ngx_chain_t * in)563 ngx_rtmp_codec_dump_header(ngx_rtmp_session_t *s, const char *type,
564 ngx_chain_t *in)
565 {
566 u_char buf[256], *p, *pp;
567 u_char hex[] = "0123456789abcdef";
568
569 for (pp = buf, p = in->buf->pos;
570 p < in->buf->last && pp < buf + sizeof(buf) - 1;
571 ++p)
572 {
573 *pp++ = hex[*p >> 4];
574 *pp++ = hex[*p & 0x0f];
575 }
576
577 *pp = 0;
578
579 ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
580 "codec: %s header %s", type, buf);
581 }
582 #endif
583
584
585 static ngx_int_t
ngx_rtmp_codec_reconstruct_meta(ngx_rtmp_session_t * s)586 ngx_rtmp_codec_reconstruct_meta(ngx_rtmp_session_t *s)
587 {
588 ngx_rtmp_codec_ctx_t *ctx;
589 ngx_rtmp_core_srv_conf_t *cscf;
590 ngx_int_t rc;
591
592 static struct {
593 double width;
594 double height;
595 double duration;
596 double frame_rate;
597 double video_data_rate;
598 double video_keyframe_frequency;
599 double video_codec_id;
600 double audio_data_rate;
601 double audio_codec_id;
602 u_char profile[32];
603 u_char level[32];
604 } v;
605
606 static ngx_rtmp_amf_elt_t out_inf[] = {
607
608 { NGX_RTMP_AMF_STRING,
609 ngx_string("Server"),
610 "NGINX RTMP (github.com/sergey-dryabzhinsky/nginx-rtmp-module)", 0 },
611
612 { NGX_RTMP_AMF_NUMBER,
613 ngx_string("width"),
614 &v.width, 0 },
615
616 { NGX_RTMP_AMF_NUMBER,
617 ngx_string("height"),
618 &v.height, 0 },
619
620 { NGX_RTMP_AMF_NUMBER,
621 ngx_string("displayWidth"),
622 &v.width, 0 },
623
624 { NGX_RTMP_AMF_NUMBER,
625 ngx_string("displayHeight"),
626 &v.height, 0 },
627
628 { NGX_RTMP_AMF_NUMBER,
629 ngx_string("duration"),
630 &v.duration, 0 },
631
632 { NGX_RTMP_AMF_NUMBER,
633 ngx_string("framerate"),
634 &v.frame_rate, 0 },
635
636 { NGX_RTMP_AMF_NUMBER,
637 ngx_string("fps"),
638 &v.frame_rate, 0 },
639
640 { NGX_RTMP_AMF_NUMBER,
641 ngx_string("videodatarate"),
642 &v.video_data_rate, 0 },
643
644 { NGX_RTMP_AMF_NUMBER,
645 ngx_string("videokeyframe_frequency"),
646 &v.video_keyframe_frequency, 0 },
647
648 { NGX_RTMP_AMF_NUMBER,
649 ngx_string("videocodecid"),
650 &v.video_codec_id, 0 },
651
652 { NGX_RTMP_AMF_NUMBER,
653 ngx_string("audiodatarate"),
654 &v.audio_data_rate, 0 },
655
656 { NGX_RTMP_AMF_NUMBER,
657 ngx_string("audiocodecid"),
658 &v.audio_codec_id, 0 },
659
660 { NGX_RTMP_AMF_STRING,
661 ngx_string("profile"),
662 &v.profile, sizeof(v.profile) },
663
664 { NGX_RTMP_AMF_STRING,
665 ngx_string("level"),
666 &v.level, sizeof(v.level) },
667 };
668
669 static ngx_rtmp_amf_elt_t out_elts[] = {
670
671 { NGX_RTMP_AMF_STRING,
672 ngx_null_string,
673 "onMetaData", 0 },
674
675 { NGX_RTMP_AMF_OBJECT,
676 ngx_null_string,
677 out_inf, sizeof(out_inf) },
678 };
679
680 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
681 if (ctx == NULL) {
682 return NGX_OK;
683 }
684
685 cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
686
687 if (ctx->meta) {
688 ngx_rtmp_free_shared_chain(cscf, ctx->meta);
689 ctx->meta = NULL;
690 }
691
692 v.width = ctx->width;
693 v.height = ctx->height;
694 v.duration = ctx->duration;
695 v.frame_rate = ctx->frame_rate;
696 v.video_data_rate = ctx->video_data_rate;
697 v.video_keyframe_frequency = ctx->video_keyframe_frequency;
698 v.video_codec_id = ctx->video_codec_id;
699 v.audio_data_rate = ctx->audio_data_rate;
700 v.audio_codec_id = ctx->audio_codec_id;
701 ngx_memcpy(v.profile, ctx->profile, sizeof(ctx->profile));
702 ngx_memcpy(v.level, ctx->level, sizeof(ctx->level));
703
704 rc = ngx_rtmp_append_amf(s, &ctx->meta, NULL, out_elts,
705 sizeof(out_elts) / sizeof(out_elts[0]));
706 if (rc != NGX_OK || ctx->meta == NULL) {
707 return NGX_ERROR;
708 }
709
710 return ngx_rtmp_codec_prepare_meta(s, 0);
711 }
712
713
714 static ngx_int_t
ngx_rtmp_codec_copy_meta(ngx_rtmp_session_t * s,ngx_rtmp_header_t * h,ngx_chain_t * in)715 ngx_rtmp_codec_copy_meta(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
716 ngx_chain_t *in)
717 {
718 ngx_rtmp_codec_ctx_t *ctx;
719 ngx_rtmp_core_srv_conf_t *cscf;
720
721 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
722
723 cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
724
725 if (ctx->meta) {
726 ngx_rtmp_free_shared_chain(cscf, ctx->meta);
727 }
728
729 ctx->meta = ngx_rtmp_append_shared_bufs(cscf, NULL, in);
730
731 if (ctx->meta == NULL) {
732 return NGX_ERROR;
733 }
734
735 return ngx_rtmp_codec_prepare_meta(s, h->timestamp);
736 }
737
738
739 static ngx_int_t
ngx_rtmp_codec_prepare_meta(ngx_rtmp_session_t * s,uint32_t timestamp)740 ngx_rtmp_codec_prepare_meta(ngx_rtmp_session_t *s, uint32_t timestamp)
741 {
742 ngx_rtmp_header_t h;
743 ngx_rtmp_codec_ctx_t *ctx;
744
745 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
746
747 ngx_memzero(&h, sizeof(h));
748 h.csid = NGX_RTMP_CSID_AMF;
749 h.msid = NGX_RTMP_MSID;
750 h.type = NGX_RTMP_MSG_AMF_META;
751 h.timestamp = timestamp;
752 ngx_rtmp_prepare_message(s, &h, NULL, ctx->meta);
753
754 ctx->meta_version = ngx_rtmp_codec_get_next_version();
755
756 return NGX_OK;
757 }
758
759
760 static ngx_int_t
ngx_rtmp_codec_meta_data(ngx_rtmp_session_t * s,ngx_rtmp_header_t * h,ngx_chain_t * in)761 ngx_rtmp_codec_meta_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
762 ngx_chain_t *in)
763 {
764 ngx_rtmp_codec_app_conf_t *cacf;
765 ngx_rtmp_codec_ctx_t *ctx;
766 ngx_uint_t skip;
767
768 static struct {
769 double width;
770 double height;
771 double duration;
772 double frame_rate;
773 double video_data_rate;
774 double video_keyframe_frequency;
775 double video_codec_id_n;
776 u_char video_codec_id_s[32];
777 double audio_data_rate;
778 double audio_codec_id_n;
779 u_char audio_codec_id_s[32];
780 u_char profile[32];
781 u_char level[32];
782 } v;
783
784 static ngx_rtmp_amf_elt_t in_video_codec_id[] = {
785
786 { NGX_RTMP_AMF_NUMBER,
787 ngx_null_string,
788 &v.video_codec_id_n, 0 },
789
790 { NGX_RTMP_AMF_STRING,
791 ngx_null_string,
792 &v.video_codec_id_s, sizeof(v.video_codec_id_s) },
793 };
794
795 static ngx_rtmp_amf_elt_t in_audio_codec_id[] = {
796
797 { NGX_RTMP_AMF_NUMBER,
798 ngx_null_string,
799 &v.audio_codec_id_n, 0 },
800
801 { NGX_RTMP_AMF_STRING,
802 ngx_null_string,
803 &v.audio_codec_id_s, sizeof(v.audio_codec_id_s) },
804 };
805
806 static ngx_rtmp_amf_elt_t in_inf[] = {
807
808 { NGX_RTMP_AMF_NUMBER,
809 ngx_string("width"),
810 &v.width, 0 },
811
812 { NGX_RTMP_AMF_NUMBER,
813 ngx_string("height"),
814 &v.height, 0 },
815
816 { NGX_RTMP_AMF_NUMBER,
817 ngx_string("duration"),
818 &v.duration, 0 },
819
820 { NGX_RTMP_AMF_NUMBER,
821 ngx_string("framerate"),
822 &v.frame_rate, 0 },
823
824 { NGX_RTMP_AMF_NUMBER,
825 ngx_string("fps"),
826 &v.frame_rate, 0 },
827
828 { NGX_RTMP_AMF_NUMBER,
829 ngx_string("videodatarate"),
830 &v.video_data_rate, 0 },
831
832 { NGX_RTMP_AMF_NUMBER,
833 ngx_string("videokeyframe_frequency"),
834 &v.video_keyframe_frequency, 0 },
835
836 { NGX_RTMP_AMF_VARIANT,
837 ngx_string("videocodecid"),
838 in_video_codec_id, sizeof(in_video_codec_id) },
839
840 { NGX_RTMP_AMF_NUMBER,
841 ngx_string("audiodatarate"),
842 &v.audio_data_rate, 0 },
843
844 { NGX_RTMP_AMF_VARIANT,
845 ngx_string("audiocodecid"),
846 in_audio_codec_id, sizeof(in_audio_codec_id) },
847
848 { NGX_RTMP_AMF_STRING,
849 ngx_string("profile"),
850 &v.profile, sizeof(v.profile) },
851
852 { NGX_RTMP_AMF_STRING,
853 ngx_string("level"),
854 &v.level, sizeof(v.level) },
855 };
856
857 static ngx_rtmp_amf_elt_t in_elts[] = {
858
859 /* That string is passed by FFmpeg and possibly others (librtmp). It's skipped after at #880 */
860 { NGX_RTMP_AMF_STRING,
861 ngx_null_string,
862 NULL, 0 },
863
864 { NGX_RTMP_AMF_OBJECT,
865 ngx_null_string,
866 in_inf, sizeof(in_inf) },
867 };
868
869 cacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_codec_module);
870
871 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
872 if (ctx == NULL) {
873 ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_rtmp_codec_ctx_t));
874 ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_codec_module);
875 }
876
877 ngx_memzero(&v, sizeof(v));
878
879 /* use -1 as a sign of unchanged data */
880 v.width = -1;
881 v.height = -1;
882 v.duration = -1;
883 v.frame_rate = -1;
884 v.video_data_rate = -1;
885 v.video_keyframe_frequency = -1;
886 v.video_codec_id_n = -1;
887 v.audio_data_rate = -1;
888 v.audio_codec_id_n = -1;
889 v.profile[0] = '\0';
890 v.level[0] = '\0';
891
892 /* FFmpeg sends a string in front of actual metadata; ignore it */
893 skip = !(in->buf->last > in->buf->pos
894 && *in->buf->pos == NGX_RTMP_AMF_STRING);
895 if (ngx_rtmp_receive_amf(s, in, in_elts + skip,
896 sizeof(in_elts) / sizeof(in_elts[0]) - skip))
897 {
898 ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
899 "codec: error parsing data frame");
900 return NGX_OK;
901 }
902
903 if (v.width != -1) ctx->width = (ngx_uint_t) v.width;
904 if (v.height != -1) ctx->height = (ngx_uint_t) v.height;
905 if (v.duration != -1) ctx->duration = (double) v.duration;
906 if (v.frame_rate != -1) ctx->frame_rate = (double) v.frame_rate;
907 if (v.video_data_rate != -1) ctx->video_data_rate = v.video_data_rate;
908 if (v.video_codec_id_n != -1) ctx->video_codec_id = (ngx_uint_t) v.video_codec_id_n;
909 if (v.audio_data_rate != -1) ctx->audio_data_rate = v.audio_data_rate;
910 if (v.video_keyframe_frequency != -1) ctx->video_keyframe_frequency = v.video_keyframe_frequency;
911 if (v.audio_codec_id_n != -1) ctx->audio_codec_id = (v.audio_codec_id_n == 0
912 ? NGX_RTMP_AUDIO_UNCOMPRESSED : (ngx_uint_t) v.audio_codec_id_n);
913 if (v.profile[0] != '\0') ngx_memcpy(ctx->profile, v.profile, sizeof(v.profile));
914 if (v.level[0] != '\0') ngx_memcpy(ctx->level, v.level, sizeof(v.level));
915
916 ngx_log_debug8(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
917 "codec: data frame: "
918 "width=%ui height=%ui duration=%.3f frame_rate=%.3f "
919 "video=%s (%ui) audio=%s (%ui)",
920 ctx->width, ctx->height, ctx->duration, ctx->frame_rate,
921 ngx_rtmp_get_video_codec_name(ctx->video_codec_id),
922 ctx->video_codec_id,
923 ngx_rtmp_get_audio_codec_name(ctx->audio_codec_id),
924 ctx->audio_codec_id);
925
926 ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
927 "codec: data frame: "
928 "video_rate=%.3f audio_rate=%.3f ",
929 ctx->video_data_rate, ctx->audio_data_rate
930 );
931
932 switch (cacf->meta) {
933 case NGX_RTMP_CODEC_META_ON:
934 return ngx_rtmp_codec_reconstruct_meta(s);
935 case NGX_RTMP_CODEC_META_COPY:
936 return ngx_rtmp_codec_copy_meta(s, h, in);
937 }
938
939 /* NGX_RTMP_CODEC_META_OFF */
940
941 return NGX_OK;
942 }
943
944
945 static void *
ngx_rtmp_codec_create_app_conf(ngx_conf_t * cf)946 ngx_rtmp_codec_create_app_conf(ngx_conf_t *cf)
947 {
948 ngx_rtmp_codec_app_conf_t *cacf;
949
950 cacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_codec_app_conf_t));
951 if (cacf == NULL) {
952 return NULL;
953 }
954
955 cacf->meta = NGX_CONF_UNSET_UINT;
956
957 return cacf;
958 }
959
960
961 static char *
ngx_rtmp_codec_merge_app_conf(ngx_conf_t * cf,void * parent,void * child)962 ngx_rtmp_codec_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
963 {
964 ngx_rtmp_codec_app_conf_t *prev = parent;
965 ngx_rtmp_codec_app_conf_t *conf = child;
966
967 ngx_conf_merge_uint_value(conf->meta, prev->meta, NGX_RTMP_CODEC_META_ON);
968
969 return NGX_CONF_OK;
970 }
971
972
973 static ngx_int_t
ngx_rtmp_codec_postconfiguration(ngx_conf_t * cf)974 ngx_rtmp_codec_postconfiguration(ngx_conf_t *cf)
975 {
976 ngx_rtmp_core_main_conf_t *cmcf;
977 ngx_rtmp_handler_pt *h;
978 ngx_rtmp_amf_handler_t *ch;
979
980 cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);
981
982 h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]);
983 *h = ngx_rtmp_codec_av;
984
985 h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]);
986 *h = ngx_rtmp_codec_av;
987
988 h = ngx_array_push(&cmcf->events[NGX_RTMP_DISCONNECT]);
989 *h = ngx_rtmp_codec_disconnect;
990
991 /* register metadata handler */
992 ch = ngx_array_push(&cmcf->amf);
993 if (ch == NULL) {
994 return NGX_ERROR;
995 }
996 ngx_str_set(&ch->name, "@setDataFrame");
997 ch->handler = ngx_rtmp_codec_meta_data;
998
999 // some encoders send setDataFrame instead of @setDataFrame
1000 ch = ngx_array_push(&cmcf->amf);
1001 if (ch == NULL) {
1002 return NGX_ERROR;
1003 }
1004 ngx_str_set(&ch->name, "setDataFrame");
1005 ch->handler = ngx_rtmp_codec_meta_data;
1006
1007 ch = ngx_array_push(&cmcf->amf);
1008 if (ch == NULL) {
1009 return NGX_ERROR;
1010 }
1011 ngx_str_set(&ch->name, "onMetaData");
1012 ch->handler = ngx_rtmp_codec_meta_data;
1013
1014
1015 return NGX_OK;
1016 }
1017