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