text: 
  /*
   * Copyright (C) Roman Arutyunyan
   */
  
  
  #include <ngx_config.h>
  #include <ngx_core.h>
  #include "ngx_rtmp_codec_module.h"
  #include "ngx_rtmp_live_module.h"
  #include "ngx_rtmp_cmd_module.h"
  #include "ngx_rtmp_bitop.h"
  
  
  #define NGX_RTMP_CODEC_META_OFF     0
  #define NGX_RTMP_CODEC_META_ON      1
  #define NGX_RTMP_CODEC_META_COPY    2
  
  
  static void * ngx_rtmp_codec_create_app_conf(ngx_conf_t *cf);
  static char * ngx_rtmp_codec_merge_app_conf(ngx_conf_t *cf,
         void *parent, void *child);
  static ngx_int_t ngx_rtmp_codec_postconfiguration(ngx_conf_t *cf);
  static ngx_int_t ngx_rtmp_codec_reconstruct_meta(ngx_rtmp_session_t *s);
  static ngx_int_t ngx_rtmp_codec_copy_meta(ngx_rtmp_session_t *s,
         ngx_rtmp_header_t *h, ngx_chain_t *in);
  static ngx_int_t ngx_rtmp_codec_prepare_meta(ngx_rtmp_session_t *s,
         uint32_t timestamp);
  static void ngx_rtmp_codec_parse_aac_header(ngx_rtmp_session_t *s,
         ngx_chain_t *in);
  static void ngx_rtmp_codec_parse_avc_header(ngx_rtmp_session_t *s,
         ngx_chain_t *in);
  #if (NGX_DEBUG)
  static void ngx_rtmp_codec_dump_header(ngx_rtmp_session_t *s, const char *type,
         ngx_chain_t *in);
  #endif
  
  
  typedef struct {
      ngx_uint_t                      meta;
  } ngx_rtmp_codec_app_conf_t;
  
  
  static ngx_conf_enum_t ngx_rtmp_codec_meta_slots[] = {
      { ngx_string("off"),            NGX_RTMP_CODEC_META_OFF  },
      { ngx_string("on"),             NGX_RTMP_CODEC_META_ON   },
      { ngx_string("copy"),           NGX_RTMP_CODEC_META_COPY },
      { ngx_null_string,              0 }
  };
  
  
  static ngx_command_t  ngx_rtmp_codec_commands[] = {
  
      { ngx_string("meta"),
        NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
        ngx_conf_set_enum_slot,
        NGX_RTMP_APP_CONF_OFFSET,
        offsetof(ngx_rtmp_codec_app_conf_t, meta),
        &ngx_rtmp_codec_meta_slots },
  
        ngx_null_command
  };
  
  
  static ngx_rtmp_module_t  ngx_rtmp_codec_module_ctx = {
      NULL,                                   /* preconfiguration */
      ngx_rtmp_codec_postconfiguration,       /* postconfiguration */
      NULL,                                   /* create main configuration */
      NULL,                                   /* init main configuration */
      NULL,                                   /* create server configuration */
      NULL,                                   /* merge server configuration */
      ngx_rtmp_codec_create_app_conf,         /* create app configuration */
      ngx_rtmp_codec_merge_app_conf           /* merge app configuration */
  };
  
  
  ngx_module_t  ngx_rtmp_codec_module = {
      NGX_MODULE_V1,
      &ngx_rtmp_codec_module_ctx,             /* module context */
      ngx_rtmp_codec_commands,                /* module directives */
      NGX_RTMP_MODULE,                        /* module type */
      NULL,                                   /* init master */
      NULL,                                   /* init module */
      NULL,                                   /* init process */
      NULL,                                   /* init thread */
      NULL,                                   /* exit thread */
      NULL,                                   /* exit process */
      NULL,                                   /* exit master */
      NGX_MODULE_V1_PADDING
  };
  
  
  static const char *
  audio_codecs[] = {
      "",
      "ADPCM",
      "MP3",
      "LinearLE",
      "Nellymoser16",
      "Nellymoser8",
      "Nellymoser",
      "G711A",
      "G711U",
      "",
      "AAC",
      "Speex",
      "",
      "",
      "MP3-8K",
      "DeviceSpecific",
      "Uncompressed"
  };
  
  
  static const char *
  video_codecs[] = {
      "",
      "Jpeg",
      "Sorenson-H263",
      "ScreenVideo",
      "On2-VP6",
      "On2-VP6-Alpha",
      "ScreenVideo2",
      "H264",
  };
  
  
  u_char *
  ngx_rtmp_get_audio_codec_name(ngx_uint_t id)
  {
      return (u_char *)(id < sizeof(audio_codecs) / sizeof(audio_codecs[0])
          ? audio_codecs[id]
          : "");
  }
  
  
  u_char *
  ngx_rtmp_get_video_codec_name(ngx_uint_t id)
  {
      return (u_char *)(id < sizeof(video_codecs) / sizeof(video_codecs[0])
          ? video_codecs[id]
          : "");
  }
  
  
  static ngx_uint_t
  ngx_rtmp_codec_get_next_version()
  {
      ngx_uint_t          v;
      static ngx_uint_t   version;
  
      do {
          v = ++version;
      } while (v == 0);
  
      return v;
  }
  
  
  static ngx_int_t
  ngx_rtmp_codec_disconnect(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
          ngx_chain_t *in)
  {
      ngx_rtmp_codec_ctx_t               *ctx;
      ngx_rtmp_core_srv_conf_t           *cscf;
  
      ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
      if (ctx == NULL) {
          return NGX_OK;
      }
  
      cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
  
      if (ctx->avc_header) {
          ngx_rtmp_free_shared_chain(cscf, ctx->avc_header);
          ctx->avc_header = NULL;
      }
  
      if (ctx->aac_header) {
          ngx_rtmp_free_shared_chain(cscf, ctx->aac_header);
          ctx->aac_header = NULL;
      }
  
      if (ctx->meta) {
          ngx_rtmp_free_shared_chain(cscf, ctx->meta);
          ctx->meta = NULL;
      }
  
      return NGX_OK;
  }
  
  
  static ngx_int_t
  ngx_rtmp_codec_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
          ngx_chain_t *in)
  {
      ngx_rtmp_core_srv_conf_t           *cscf;
      ngx_rtmp_codec_ctx_t               *ctx;
      ngx_chain_t                       **header;
      uint8_t                             fmt;
      static ngx_uint_t                   sample_rates[] =
                                          { 5512, 11025, 22050, 44100 };
  
      if (h->type != NGX_RTMP_MSG_AUDIO && h->type != NGX_RTMP_MSG_VIDEO) {
          return NGX_OK;
      }
  
      ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
      if (ctx == NULL) {
          ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_rtmp_codec_ctx_t));
          ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_codec_module);
      }
  
      /* save codec */
      if (in->buf->last - in->buf->pos < 1) {
          return NGX_OK;
      }
  
      fmt =  in->buf->pos[0];
      if (h->type == NGX_RTMP_MSG_AUDIO) {
          ctx->audio_codec_id = (fmt & 0xf0) >> 4;
          ctx->audio_channels = (fmt & 0x01) + 1;
          ctx->sample_size = (fmt & 0x02) ? 2 : 1;
  
          if (ctx->sample_rate == 0) {
              ctx->sample_rate = sample_rates[(fmt & 0x0c) >> 2];
          }
      } else {
          ctx->video_codec_id = (fmt & 0x0f);
      }
  
      /* save AVC/AAC header */
      if (in->buf->last - in->buf->pos < 3) {
          return NGX_OK;
      }
  
      /* no conf */
      if (!ngx_rtmp_is_codec_header(in)) {
          return NGX_OK;
      }
  
      cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
      header = NULL;
  
      if (h->type == NGX_RTMP_MSG_AUDIO) {
          if (ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC) {
              header = &ctx->aac_header;
              ngx_rtmp_codec_parse_aac_header(s, in);
          }
      } else {
          if (ctx->video_codec_id == NGX_RTMP_VIDEO_H264) {
              header = &ctx->avc_header;
              ngx_rtmp_codec_parse_avc_header(s, in);
          }
      }
  
      if (header == NULL) {
          return NGX_OK;
      }
  
      if (*header) {
          ngx_rtmp_free_shared_chain(cscf, *header);
      }
  
      *header = ngx_rtmp_append_shared_bufs(cscf, NULL, in);
  
      return NGX_OK;
  }
  
  
  static void
  ngx_rtmp_codec_parse_aac_header(ngx_rtmp_session_t *s, ngx_chain_t *in)
  {
      ngx_uint_t              idx;
      ngx_rtmp_codec_ctx_t   *ctx;
      ngx_rtmp_bit_reader_t   br;
  
      static ngx_uint_t      aac_sample_rates[] =
          { 96000, 88200, 64000, 48000,
            44100, 32000, 24000, 22050,
            16000, 12000, 11025,  8000,
             7350,     0,     0,     0 };
  
  #if (NGX_DEBUG)
      ngx_rtmp_codec_dump_header(s, "aac", in);
  #endif
  
      ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
  
      ngx_rtmp_bit_init_reader(&br, in->buf->pos, in->buf->last);
  
      ngx_rtmp_bit_read(&br, 16);
  
      ctx->aac_profile = (ngx_uint_t) ngx_rtmp_bit_read(&br, 5);
      if (ctx->aac_profile == 31) {
          ctx->aac_profile = (ngx_uint_t) ngx_rtmp_bit_read(&br, 6) + 32;
      }
  
      idx = (ngx_uint_t) ngx_rtmp_bit_read(&br, 4);
      if (idx == 15) {
          ctx->sample_rate = (ngx_uint_t) ngx_rtmp_bit_read(&br, 24);
      } else {
          ctx->sample_rate = aac_sample_rates[idx];
      }
  
      ctx->aac_chan_conf = (ngx_uint_t) ngx_rtmp_bit_read(&br, 4);
  
      if (ctx->aac_profile == 5 || ctx->aac_profile == 29) {
          
          if (ctx->aac_profile == 29) {
              ctx->aac_ps = 1;
          }
  
          ctx->aac_sbr = 1;
  
          idx = (ngx_uint_t) ngx_rtmp_bit_read(&br, 4);
          if (idx == 15) {
              ctx->sample_rate = (ngx_uint_t) ngx_rtmp_bit_read(&br, 24);
          } else {
              ctx->sample_rate = aac_sample_rates[idx];
          }
  
          ctx->aac_profile = (ngx_uint_t) ngx_rtmp_bit_read(&br, 5);
          if (ctx->aac_profile == 31) {
              ctx->aac_profile = (ngx_uint_t) ngx_rtmp_bit_read(&br, 6) + 32;
          }
      }
  
      /* MPEG-4 Audio Specific Config
  
         5 bits: object type
         if (object type == 31)
           6 bits + 32: object type
         4 bits: frequency index
         if (frequency index == 15)
           24 bits: frequency
         4 bits: channel configuration
  
         if (object_type == 5)
             4 bits: frequency index
             if (frequency index == 15)
               24 bits: frequency
             5 bits: object type
             if (object type == 31)
               6 bits + 32: object type
               
         var bits: AOT Specific Config
       */
  
      ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                     "codec: aac header profile=%ui, "
                     "sample_rate=%ui, chan_conf=%ui",
                     ctx->aac_profile, ctx->sample_rate, ctx->aac_chan_conf);
  }
  
  
  static void
  ngx_rtmp_codec_parse_avc_header(ngx_rtmp_session_t *s, ngx_chain_t *in)
  {
      ngx_uint_t              profile_idc, width, height, crop_left, crop_right,
                              crop_top, crop_bottom, frame_mbs_only, n, cf_n, cf_idc,
  //                            num_ref_frames;
                              num_ref_frames, sl_size, sl_index, sl_udelta;
      ngx_int_t               sl_last, sl_next, sl_delta;
      ngx_rtmp_codec_ctx_t   *ctx;
      ngx_rtmp_bit_reader_t   br;
  
  #if (NGX_DEBUG)
      ngx_rtmp_codec_dump_header(s, "avc", in);
  #endif
  
      ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
  
      ngx_rtmp_bit_init_reader(&br, in->buf->pos, in->buf->last);
  
      ngx_rtmp_bit_read(&br, 48);
  
      ctx->avc_profile = (ngx_uint_t) ngx_rtmp_bit_read_8(&br);
      ctx->avc_compat = (ngx_uint_t) ngx_rtmp_bit_read_8(&br);
      ctx->avc_level = (ngx_uint_t) ngx_rtmp_bit_read_8(&br);
  
      /* nal bytes */
      ctx->avc_nal_bytes = (ngx_uint_t) ((ngx_rtmp_bit_read_8(&br) & 0x03) + 1);
  
      /* nnals */
      if ((ngx_rtmp_bit_read_8(&br) & 0x1f) == 0) {
          return;
      }
  
      /* nal size */
      ngx_rtmp_bit_read(&br, 16);
  
      /* nal type */
      if (ngx_rtmp_bit_read_8(&br) != 0x67) {
          return;
      }
  
      /* SPS */
  
      /* profile idc */
      profile_idc = (ngx_uint_t) ngx_rtmp_bit_read(&br, 8);
  
      /* flags */
      ngx_rtmp_bit_read(&br, 8);
  
      /* level idc */
      ngx_rtmp_bit_read(&br, 8);
  
      /* SPS id */
      ngx_rtmp_bit_read_golomb(&br);
  
      if (profile_idc == 100 || profile_idc == 110 ||
          profile_idc == 122 || profile_idc == 244 || profile_idc == 44 ||
          profile_idc == 83 || profile_idc == 86 || profile_idc == 118)
      {
          /* chroma format idc */
          cf_idc = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br);
          
          if (cf_idc == 3) {
  
              /* separate color plane */
              ngx_rtmp_bit_read(&br, 1);
          }
  
          /* bit depth luma - 8 */
          ngx_rtmp_bit_read_golomb(&br);
  
          /* bit depth chroma - 8 */
          ngx_rtmp_bit_read_golomb(&br);
  
          /* qpprime y zero transform bypass */
          ngx_rtmp_bit_read(&br, 1);
  
          /* seq scaling matrix present */
          if (ngx_rtmp_bit_read(&br, 1)) {
  
              for (n = 0, cf_n = (cf_idc != 3 ? 8u : 12u); n < cf_n; n++) {
  
                  /* seq scaling list present */
                  if (ngx_rtmp_bit_read(&br, 1)) {
  
                      /* scaling list */
                      if (n < 6) {
                          sl_size = 16;
                      } else {
                          sl_size = 64;
                      }
  
                      sl_last = 8;
                      sl_next = 8;
  
                      for (sl_index = 0; sl_index < sl_size; sl_index++) {
  
                          if (sl_next != 0) {
  
                              /* convert to signed: (-1)**k+1 * ceil(k/2) */
                              sl_udelta = (ngx_uint_t)ngx_rtmp_bit_read_golomb(&br);
                              sl_delta = (sl_udelta + 1) >> 1;
                              if ((sl_udelta & 1) == 0) {
                                  sl_delta = -sl_delta;
                              }
  
                              sl_next = (sl_last + sl_delta + 256) % 256;
  
                              if (sl_next != 0) {
                                  sl_last = sl_next;
                              }
                          }
                      }
                  }
              }
          }
      }
  
      /* log2 max frame num */
      ngx_rtmp_bit_read_golomb(&br);
  
      /* pic order cnt type */
      switch (ngx_rtmp_bit_read_golomb(&br)) {
      case 0:
  
          /* max pic order cnt */
          ngx_rtmp_bit_read_golomb(&br);
          break;
  
      case 1:
  
          /* delta pic order alwys zero */
          ngx_rtmp_bit_read(&br, 1);
  
          /* offset for non-ref pic */
          ngx_rtmp_bit_read_golomb(&br);
  
          /* offset for top to bottom field */
          ngx_rtmp_bit_read_golomb(&br);
  
          /* num ref frames in pic order */
          num_ref_frames = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br);
  
          for (n = 0; n < num_ref_frames; n++) {
  
              /* offset for ref frame */
              ngx_rtmp_bit_read_golomb(&br);
          }
      }
  
      /* num ref frames */
      ctx->avc_ref_frames = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br);
  
      /* gaps in frame num allowed */
      ngx_rtmp_bit_read(&br, 1);
  
      /* pic width in mbs - 1 */
      width = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br);
  
      /* pic height in map units - 1 */
      height = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br);
  
      /* frame mbs only flag */
      frame_mbs_only = (ngx_uint_t) ngx_rtmp_bit_read(&br, 1);
  
      if (!frame_mbs_only) {
  
          /* mbs adaprive frame field */
          ngx_rtmp_bit_read(&br, 1);
      }
  
      /* direct 8x8 inference flag */
      ngx_rtmp_bit_read(&br, 1);
  
      /* frame cropping */
      if (ngx_rtmp_bit_read(&br, 1)) {
  
          crop_left = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br);
          crop_right = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br);
          crop_top = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br);
          crop_bottom = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br);
  
      } else {
  
          crop_left = 0;
          crop_right = 0;
          crop_top = 0;
          crop_bottom = 0;
      }
  
      ctx->width = (width + 1) * 16 - (crop_left + crop_right) * 2;
      ctx->height = (2 - frame_mbs_only) * (height + 1) * 16 -
                    (crop_top + crop_bottom) * 2;
  
      ngx_log_debug7(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                     "codec: avc header "
                     "profile=%ui, compat=%ui, level=%ui, "
                     "nal_bytes=%ui, ref_frames=%ui, width=%ui, height=%ui",
                     ctx->avc_profile, ctx->avc_compat, ctx->avc_level,
                     ctx->avc_nal_bytes, ctx->avc_ref_frames,
                     ctx->width, ctx->height);
  }
  
  
  #if (NGX_DEBUG)
  static void
  ngx_rtmp_codec_dump_header(ngx_rtmp_session_t *s, const char *type,
      ngx_chain_t *in)
  {
      u_char buf[256], *p, *pp;
      u_char hex[] = "0123456789abcdef";
  
      for (pp = buf, p = in->buf->pos;
           p < in->buf->last && pp < buf + sizeof(buf) - 1;
           ++p)
      {
          *pp++ = hex[*p >> 4];
          *pp++ = hex[*p & 0x0f];
      }
  
      *pp = 0;
  
      ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                     "codec: %s header %s", type, buf);
  }
  #endif
  
  
  static ngx_int_t
  ngx_rtmp_codec_reconstruct_meta(ngx_rtmp_session_t *s)
  {
      ngx_rtmp_codec_ctx_t           *ctx;
      ngx_rtmp_core_srv_conf_t       *cscf;
      ngx_int_t                       rc;
  
      static struct {
          double                      width;
          double                      height;
          double                      duration;
          double                      frame_rate;
          double                      video_data_rate;
          double                      video_codec_id;
          double                      audio_data_rate;
          double                      audio_codec_id;
          u_char                      profile[32];
          u_char                      level[32];
      }                               v;
  
      static ngx_rtmp_amf_elt_t       out_inf[] = {
  
          { NGX_RTMP_AMF_STRING,
            ngx_string("xtradata"),
            "NGINX RTMP (github.com/sergey-dryabzhinsky/nginx-rtmp-module)", 0 },
  
          { NGX_RTMP_AMF_NUMBER,
            ngx_string("width"),
            &v.width, 0 },
  
          { NGX_RTMP_AMF_NUMBER,
            ngx_string("height"),
            &v.height, 0 },
  
          { NGX_RTMP_AMF_NUMBER,
            ngx_string("displayWidth"),
            &v.width, 0 },
  
          { NGX_RTMP_AMF_NUMBER,
            ngx_string("displayHeight"),
            &v.height, 0 },
  
          { NGX_RTMP_AMF_NUMBER,
            ngx_string("duration"),
            &v.duration, 0 },
  
          { NGX_RTMP_AMF_NUMBER,
            ngx_string("framerate"),
            &v.frame_rate, 0 },
  
          { NGX_RTMP_AMF_NUMBER,
            ngx_string("fps"),
            &v.frame_rate, 0 },
  
          { NGX_RTMP_AMF_NUMBER,
            ngx_string("videodatarate"),
            &v.video_data_rate, 0 },
  
          { NGX_RTMP_AMF_NUMBER,
            ngx_string("videocodecid"),
            &v.video_codec_id, 0 },
  
          { NGX_RTMP_AMF_NUMBER,
            ngx_string("audiodatarate"),
            &v.audio_data_rate, 0 },
  
          { NGX_RTMP_AMF_NUMBER,
            ngx_string("audiocodecid"),
            &v.audio_codec_id, 0 },
  
          { NGX_RTMP_AMF_STRING,
            ngx_string("profile"),
            &v.profile, sizeof(v.profile) },
  
          { NGX_RTMP_AMF_STRING,
            ngx_string("level"),
            &v.level, sizeof(v.level) },
      };
  
      static ngx_rtmp_amf_elt_t       out_elts[] = {
  
          { NGX_RTMP_AMF_STRING,
            ngx_null_string,
            "onMetaData", 0 },
  
          { NGX_RTMP_AMF_OBJECT,
            ngx_null_string,
            out_inf, sizeof(out_inf) },
      };
  
      ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
      if (ctx == NULL) {
          return NGX_OK;
      }
  
      cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
  
      if (ctx->meta) {
          ngx_rtmp_free_shared_chain(cscf, ctx->meta);
          ctx->meta = NULL;
      }
  
      v.width = ctx->width;
      v.height = ctx->height;
      v.duration = ctx->duration;
      v.frame_rate = ctx->frame_rate;
      v.video_data_rate = ctx->video_data_rate;
      v.video_codec_id = ctx->video_codec_id;
      v.audio_data_rate = ctx->audio_data_rate;
      v.audio_codec_id = ctx->audio_codec_id;
      ngx_memcpy(v.profile, ctx->profile, sizeof(ctx->profile));
      ngx_memcpy(v.level, ctx->level, sizeof(ctx->level));
  
      rc = ngx_rtmp_append_amf(s, &ctx->meta, NULL, out_elts,
                               sizeof(out_elts) / sizeof(out_elts[0]));
      if (rc != NGX_OK || ctx->meta == NULL) {
          return NGX_ERROR;
      }
  
      return ngx_rtmp_codec_prepare_meta(s, 0);
  }
  
  
  static ngx_int_t
  ngx_rtmp_codec_copy_meta(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
          ngx_chain_t *in)
  {
      ngx_rtmp_codec_ctx_t      *ctx;
      ngx_rtmp_core_srv_conf_t  *cscf;
  
      ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
  
      cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
  
      if (ctx->meta) {
          ngx_rtmp_free_shared_chain(cscf, ctx->meta);
      }
  
      ctx->meta = ngx_rtmp_append_shared_bufs(cscf, NULL, in);
  
      if (ctx->meta == NULL) {
          return NGX_ERROR;
      }
  
      return ngx_rtmp_codec_prepare_meta(s, h->timestamp);
  }
  
  
  static ngx_int_t
  ngx_rtmp_codec_prepare_meta(ngx_rtmp_session_t *s, uint32_t timestamp)
  {
      ngx_rtmp_header_t      h;
      ngx_rtmp_codec_ctx_t  *ctx;
  
      ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
  
      ngx_memzero(&h, sizeof(h));
      h.csid = NGX_RTMP_CSID_AMF;
      h.msid = NGX_RTMP_MSID;
      h.type = NGX_RTMP_MSG_AMF_META;
      h.timestamp = timestamp;
      ngx_rtmp_prepare_message(s, &h, NULL, ctx->meta);
  
      ctx->meta_version = ngx_rtmp_codec_get_next_version();
  
      return NGX_OK;
  }
  
  
  static ngx_int_t
  ngx_rtmp_codec_meta_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
          ngx_chain_t *in)
  {
      ngx_rtmp_codec_app_conf_t      *cacf;
      ngx_rtmp_codec_ctx_t           *ctx;
      ngx_uint_t                      skip;
  
      static struct {
          double                      width;
          double                      height;
          double                      duration;
          double                      frame_rate;
          double                      video_data_rate;
          double                      video_codec_id_n;
          u_char                      video_codec_id_s[32];
          double                      audio_data_rate;
          double                      audio_codec_id_n;
          u_char                      audio_codec_id_s[32];
          u_char                      profile[32];
          u_char                      level[32];
      }                               v;
  
      static ngx_rtmp_amf_elt_t       in_video_codec_id[] = {
  
          { NGX_RTMP_AMF_NUMBER,
            ngx_null_string,
            &v.video_codec_id_n, 0 },
  
          { NGX_RTMP_AMF_STRING,
            ngx_null_string,
            &v.video_codec_id_s, sizeof(v.video_codec_id_s) },
      };
  
      static ngx_rtmp_amf_elt_t       in_audio_codec_id[] = {
  
          { NGX_RTMP_AMF_NUMBER,
            ngx_null_string,
            &v.audio_codec_id_n, 0 },
  
          { NGX_RTMP_AMF_STRING,
            ngx_null_string,
            &v.audio_codec_id_s, sizeof(v.audio_codec_id_s) },
      };
  
      static ngx_rtmp_amf_elt_t       in_inf[] = {
  
          { NGX_RTMP_AMF_NUMBER,
            ngx_string("width"),
            &v.width, 0 },
  
          { NGX_RTMP_AMF_NUMBER,
            ngx_string("height"),
            &v.height, 0 },
  
          { NGX_RTMP_AMF_NUMBER,
            ngx_string("duration"),
            &v.duration, 0 },
  
          { NGX_RTMP_AMF_NUMBER,
            ngx_string("framerate"),
            &v.frame_rate, 0 },
  
          { NGX_RTMP_AMF_NUMBER,
            ngx_string("fps"),
            &v.frame_rate, 0 },
  
          { NGX_RTMP_AMF_NUMBER,
            ngx_string("videodatarate"),
            &v.video_data_rate, 0 },
  
          { NGX_RTMP_AMF_VARIANT,
            ngx_string("videocodecid"),
            in_video_codec_id, sizeof(in_video_codec_id) },
  
          { NGX_RTMP_AMF_NUMBER,
            ngx_string("audiodatarate"),
            &v.audio_data_rate, 0 },
  
          { NGX_RTMP_AMF_VARIANT,
            ngx_string("audiocodecid"),
            in_audio_codec_id, sizeof(in_audio_codec_id) },
  
          { NGX_RTMP_AMF_STRING,
            ngx_string("profile"),
            &v.profile, sizeof(v.profile) },
  
          { NGX_RTMP_AMF_STRING,
            ngx_string("level"),
            &v.level, sizeof(v.level) },
      };
  
      static ngx_rtmp_amf_elt_t       in_elts[] = {
  
         /* That string is passed by FFmpeg and possibly others (librtmp). It's skipped after at #880 */
         { NGX_RTMP_AMF_STRING,
            ngx_null_string,
            NULL, 0 },
  
          { NGX_RTMP_AMF_OBJECT,
            ngx_null_string,
            in_inf, sizeof(in_inf) },
      };
  
      cacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_codec_module);
  
      ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
      if (ctx == NULL) {
          ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_rtmp_codec_ctx_t));
          ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_codec_module);
      }
  
      ngx_memzero(&v, sizeof(v));
  
      /* use -1 as a sign of unchanged data */
      v.width = -1;
      v.height = -1;
      v.duration = -1;
      v.frame_rate = -1;
      v.video_data_rate = -1;
      v.video_codec_id_n = -1;
      v.audio_data_rate = -1;
      v.audio_codec_id_n = -1;
      v.profile[0] = '\0';
      v.level[0] = '\0';
  
      /* FFmpeg sends a string in front of actual metadata; ignore it */
      skip = !(in->buf->last > in->buf->pos
              && *in->buf->pos == NGX_RTMP_AMF_STRING);
      if (ngx_rtmp_receive_amf(s, in, in_elts + skip,
                  sizeof(in_elts) / sizeof(in_elts[0]) - skip))
      {
          ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                  "codec: error parsing data frame");
          return NGX_OK;
      }
  
      if (v.width != -1) ctx->width = (ngx_uint_t) v.width;
      if (v.height != -1) ctx->height = (ngx_uint_t) v.height;
      if (v.duration != -1) ctx->duration = (double) v.duration;
      if (v.frame_rate != -1) ctx->frame_rate = (double) v.frame_rate;
      if (v.video_data_rate != -1) ctx->video_data_rate = v.video_data_rate;
      if (v.video_codec_id_n != -1) ctx->video_codec_id = (ngx_uint_t) v.video_codec_id_n;
      if (v.audio_data_rate != -1) ctx->audio_data_rate = v.audio_data_rate;
      if (v.audio_codec_id_n != -1) ctx->audio_codec_id = (v.audio_codec_id_n == 0
              ? NGX_RTMP_AUDIO_UNCOMPRESSED : (ngx_uint_t) v.audio_codec_id_n);
      if (v.profile[0] != '\0') ngx_memcpy(ctx->profile, v.profile, sizeof(v.profile));
      if (v.level[0] != '\0') ngx_memcpy(ctx->level, v.level, sizeof(v.level));
  
      ngx_log_debug8(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
              "codec: data frame: "
              "width=%ui height=%ui duration=%.3f frame_rate=%.3f "
              "video=%s (%ui) audio=%s (%ui)",
              ctx->width, ctx->height, ctx->duration, ctx->frame_rate,
              ngx_rtmp_get_video_codec_name(ctx->video_codec_id),
              ctx->video_codec_id,
              ngx_rtmp_get_audio_codec_name(ctx->audio_codec_id),
              ctx->audio_codec_id);
  
      ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
              "codec: data frame: "
              "video_rate=%.3f audio_rate=%.3f ",
              ctx->video_data_rate, ctx->audio_data_rate
              );
  
      switch (cacf->meta) {
          case NGX_RTMP_CODEC_META_ON:
              return ngx_rtmp_codec_reconstruct_meta(s);
          case NGX_RTMP_CODEC_META_COPY:
              return ngx_rtmp_codec_copy_meta(s, h, in);
      }
  
      /* NGX_RTMP_CODEC_META_OFF */
  
      return NGX_OK;
  }
  
  
  static void *
  ngx_rtmp_codec_create_app_conf(ngx_conf_t *cf)
  {
      ngx_rtmp_codec_app_conf_t  *cacf;
  
      cacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_codec_app_conf_t));
      if (cacf == NULL) {
          return NULL;
      }
  
      cacf->meta = NGX_CONF_UNSET_UINT;
  
      return cacf;
  }
  
  
  static char *
  ngx_rtmp_codec_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
  {
      ngx_rtmp_codec_app_conf_t *prev = parent;
      ngx_rtmp_codec_app_conf_t *conf = child;
  
      ngx_conf_merge_uint_value(conf->meta, prev->meta, NGX_RTMP_CODEC_META_ON);
  
      return NGX_CONF_OK;
  }
  
  
  static ngx_int_t
  ngx_rtmp_codec_postconfiguration(ngx_conf_t *cf)
  {
      ngx_rtmp_core_main_conf_t          *cmcf;
      ngx_rtmp_handler_pt                *h;
      ngx_rtmp_amf_handler_t             *ch;
  
      cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);
  
      h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]);
      *h = ngx_rtmp_codec_av;
  
      h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]);
      *h = ngx_rtmp_codec_av;
  
      h = ngx_array_push(&cmcf->events[NGX_RTMP_DISCONNECT]);
      *h = ngx_rtmp_codec_disconnect;
  
      /* register metadata handler */
      ch = ngx_array_push(&cmcf->amf);
      if (ch == NULL) {
          return NGX_ERROR;
      }
      ngx_str_set(&ch->name, "@setDataFrame");
      ch->handler = ngx_rtmp_codec_meta_data;
      
      // some encoders send setDataFrame instead of @setDataFrame
      ch = ngx_array_push(&cmcf->amf);
      if (ch == NULL) {
          return NGX_ERROR;
      }
      ngx_str_set(&ch->name, "setDataFrame");
      ch->handler = ngx_rtmp_codec_meta_data;
      
      ch = ngx_array_push(&cmcf->amf);
      if (ch == NULL) {
          return NGX_ERROR;
      }
      ngx_str_set(&ch->name, "onMetaData");
      ch->handler = ngx_rtmp_codec_meta_data;
  
  
      return NGX_OK;
  }
  