text: /*
   * Copyright (c) 2012 Roman Arutyunyan
   */
  
  
  #include "ngx_rtmp_codec_module.h"
  #include "ngx_rtmp_live_module.h"
  #include "ngx_rtmp_cmd_module.h"
  
  
  static ngx_int_t ngx_rtmp_codec_postconfiguration(ngx_conf_t *cf);
  
  
  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 */
      NULL,                                   /* create app configuration */
      NULL                                    /* merge app configuration */
  };
  
  
  ngx_module_t  ngx_rtmp_codec_module = {
      NGX_MODULE_V1,
      &ngx_rtmp_codec_module_ctx,             /* module context */
      NULL,                                   /* 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;
      ngx_uint_t                          idx;
      u_char                             *p;
      static ngx_uint_t                   sample_rates[] = 
                                          { 5512, 11025, 22050, 44100 };
  
      static ngx_uint_t                   aac_sample_rates[] = 
                                          { 96000, 88200, 64000, 48000,
                                            44100, 32000, 24000, 22050,
                                            16000, 12000, 11025,  8000,
                                             7350,     0,     0,    0 };
  
      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->aac_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;
              
              if (in->buf->last - in->buf->pos > 3) {
                  p = in->buf->pos + 2;
  
                  /* 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
                     var bits: AOT Specific Config
                   */
  
                  if ((p[0] >> 3) == 0x1f) {
                      idx = (p[1] >> 1) & 0x0f;
                  } else {
                      idx = ((p[0] << 1) & 0x0f) | (p[1] >> 7);
                  }
  
  #ifdef NGX_DEBUG
                  {
                      u_char buf[256], *p, *pp;
                      u_char hex[] = "01234567890abcdef";
  
                      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_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                              "codec: AAC header: %s", buf);
                  }
  #endif
  
                  ctx->aac_sample_rate = aac_sample_rates[idx];
                  ctx->sample_rate = ctx->aac_sample_rate;
              }
  
              ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                             "codec: AAC header arrived, sample_rate=%ui", 
                             ctx->aac_sample_rate);
          }
      } else {
          if (ctx->video_codec_id == NGX_RTMP_VIDEO_H264) {
              header = &ctx->avc_header;
              ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                      "codec: AVC/H264 header arrived");
          }
      }
  
      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 ngx_int_t
  ngx_rtmp_codec_update_meta(ngx_rtmp_session_t *s)
  {
      ngx_rtmp_codec_ctx_t           *ctx;
      ngx_rtmp_core_srv_conf_t       *cscf;
      ngx_int_t                       rc;
      ngx_rtmp_header_t               h;
  
      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("Server"),
            "NGINX RTMP (github.com/arut/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;
      }
  
      ngx_memzero(&h, sizeof(h));
      h.csid = NGX_RTMP_CSID_AMF;
      h.msid = NGX_RTMP_MSID;
      h.type = NGX_RTMP_MSG_AMF_META;
      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_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[] = {
  
          { NGX_RTMP_AMF_STRING, 
            ngx_null_string,
            NULL, 0 },
  
          { NGX_RTMP_AMF_OBJECT, 
            ngx_null_string,
            in_inf, sizeof(in_inf) },
      };
  
      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;
       * 0 is a valid value for uncompressed audio */
      v.audio_codec_id_n = -1; 
  
      /* FFmpeg sends a string in front of actal 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;
      }
  
      ctx->width = v.width;
      ctx->height = v.height;
      ctx->duration = v.duration;
      ctx->frame_rate = v.frame_rate;
      ctx->video_data_rate = v.video_data_rate;
      ctx->video_codec_id = v.video_codec_id_n;
      ctx->audio_data_rate = v.audio_data_rate;
      ctx->audio_codec_id = (v.audio_codec_id_n == -1
              ? 0 : v.audio_codec_id_n == 0
              ? NGX_RTMP_AUDIO_UNCOMPRESSED : v.audio_codec_id_n);
      ngx_memcpy(ctx->profile, v.profile, sizeof(v.profile));
      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=%ui frame_rate=%ui "
              "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_rtmp_codec_update_meta(s);
  
      return NGX_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;
  
      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;
  }
  