diff mbox series

[FFmpeg-devel] Add optional NIT table generation

Message ID 20210427151520.10044-1-ubaldo@eja.it
State New
Headers show
Series [FFmpeg-devel] Add optional NIT table generation
Related show

Checks

Context Check Description
andriy/x86_make success Make finished
andriy/x86_make_fate success Make fate finished
andriy/PPC64_make success Make finished
andriy/PPC64_make_fate success Make fate finished

Commit Message

Ubaldo Porcheddu April 27, 2021, 3:15 p.m. UTC
Signed-off-by: Ubaldo Porcheddu <ubaldo@eja.it>
---
 libavformat/mpegtsenc.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 106 insertions(+), 4 deletions(-)

Comments

Marton Balint May 2, 2021, 8:11 p.m. UTC | #1
On Tue, 27 Apr 2021, Ubaldo Porcheddu wrote:

> Signed-off-by: Ubaldo Porcheddu <ubaldo@eja.it>
> ---
> libavformat/mpegtsenc.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++--
> 1 file changed, 106 insertions(+), 4 deletions(-)
>
> diff --git a/libavformat/mpegtsenc.c b/libavformat/mpegtsenc.c
> index a357f3a6aa..d03eaaa009 100644
> --- a/libavformat/mpegtsenc.c
> +++ b/libavformat/mpegtsenc.c
> @@ -76,10 +76,12 @@ typedef struct MpegTSWrite {
>     const AVClass *av_class;
>     MpegTSSection pat; /* MPEG-2 PAT table */
>     MpegTSSection sdt; /* MPEG-2 SDT table context */
> +    MpegTSSection nit; /* MPEG-2 NIT table context */
>     MpegTSService **services;
>     AVPacket *pkt;
>     int64_t sdt_period; /* SDT period in PCR time base */
>     int64_t pat_period; /* PAT/PMT period in PCR time base */
> +    int64_t nit_period; /* NIT period in PCR time base */
>     int nb_services;
>     int64_t first_pcr;
>     int first_dts_checked;
> @@ -112,10 +114,13 @@ typedef struct MpegTSWrite {
>     int tables_version;
>     int64_t pat_period_us;
>     int64_t sdt_period_us;
> +    int64_t nit_period_us;
>     int64_t last_pat_ts;
>     int64_t last_sdt_ts;
> +    int64_t last_nit_ts;
>
>     int omit_video_pes_length;
> +    int nit_enable;

Instead of this, you should add a new MPEGTS_FLAG.

> } MpegTSWrite;
>
> /* a PES packet header is generated every DEFAULT_PES_HEADER_FREQ packets */
> @@ -227,6 +232,7 @@ static int mpegts_write_section1(MpegTSSection *s, int tid, int id,
> #define SDT_RETRANS_TIME 500
> #define PAT_RETRANS_TIME 100
> #define PCR_RETRANS_TIME 20
> +#define NIT_RETRANS_TIME 500
>
> typedef struct MpegTSWriteStream {
>     int pid; /* stream associated pid */
> @@ -796,6 +802,75 @@ static void mpegts_write_sdt(AVFormatContext *s)
>                           data, q - data);
> }
>
> +static void mpegts_write_nit(AVFormatContext *s)
> +{
> +    int i, len=0, provider_len=0, desc_len=0, virtual_channel_value;
> +    MpegTSWrite *ts = s->priv_data;
> +    uint8_t data[SECTION_LENGTH], *q, *desc_len_ptr, *loop_len_ptr;
> +    AVDictionaryEntry *provider, *virtual_channel;
> +    AVProgram *program;
> +    const char *provider_name;
> +
> +    q = data;
> +
> +    //network name
> +    provider = av_dict_get(s->metadata, "service_provider", NULL, 0);
> +    provider_name = provider ? provider->value : DEFAULT_PROVIDER_NAME;
> +    provider_len = strlen(provider_name);
> +    put16(&q, 0xf000 | provider_len);
> +    *q++ = 0x40;
> +    *q++ = provider_len;
> +    putbuf(&q, provider_name, provider_len);

encode_str8 should be used instead. Better yet, you should pre-calculate 
that at init time, and used the already encoded value here directly.

> +
> +    //TS loop
> +    loop_len_ptr = q;
> +    q += 2;
> +    put16(&q, ts->transport_stream_id);
> +    put16(&q, ts->original_network_id);
> +
> +    //transport descriptor
> +    desc_len_ptr = q;
> +    q += 2;
> +
> +    //service list
> +    len = 3 * ts->nb_services;
> +    desc_len += len;
> +    *q++ = 0x41; //tag
> +    *q++ = len;
> +    for(i = 0; i < ts->nb_services; i++) {
> +      put16(&q, ts->services[i]->sid);//service_ID
> +      *q++ = 0x01; //service type 0x01 for Digital TV Service

ts->service_type

Preferably you should use 4 space indentation.

> +    }
> +
> +    //private data
> +    desc_len += 6 + 2;
> +    *q++ = 0x5F;
> +    *q++ = 4;
> +    *q++ = 0x00;
> +    *q++ = 0x00;
> +    put16(&q, 40);

What are these?

> +
> +    //virtual channel
> +    len = 4 * ts->nb_services;
> +    desc_len += len + 2;
> +    *q++ = 0x83;
> +    *q++ = len;

Simply *q++ = 4 * ts->nb_services

> +    for (i = 0; i < ts->nb_services; i++) {
> +      program = s->programs[i];
> +      virtual_channel = av_dict_get(program->metadata, "virtual_channel", NULL, 0);
> +      virtual_channel_value = virtual_channel ? atoi(virtual_channel->value) : 1000+i;
> +      put16(&q, ts->services[i]->sid);
> +      put16(&q, 0xfc00 | virtual_channel_value);

indentation

> +    }
> +
> +    //calculate lengths
> +    put16(&desc_len_ptr, 0xf000 | desc_len);
> +    put16(&loop_len_ptr, 0xf000 | desc_len+6);

You should not calculate desc_len, you should use
(q - desc_len_ptr) directly, it is less error-prone that way.

> +
> +    mpegts_write_section1(&ts->nit, NIT_TID, ts->original_network_id, ts->tables_version, 0, 0,
> +                          data, q - data);
> +}
> +
> /* This stores a string in buf with the correct encoding and also sets the
>  * first byte as the length. !str is accepted for an empty string.
>  * If the string is already encoded, invalid UTF-8 or has no multibyte sequence
> @@ -1022,6 +1097,12 @@ static int mpegts_init(AVFormatContext *s)
>     ts->sdt.write_packet = section_write_packet;
>     ts->sdt.opaque       = s;
>
> +    ts->nit.pid          = NIT_PID;
> +    ts->nit.cc           = 15;
> +    ts->nit.discontinuity= ts->flags & MPEGTS_FLAG_DISCONT;
> +    ts->nit.write_packet = section_write_packet;
> +    ts->nit.opaque       = s;
> +
>     ts->pkt = av_packet_alloc();
>     if (!ts->pkt)
>         return AVERROR(ENOMEM);
> @@ -1143,8 +1224,10 @@ static int mpegts_init(AVFormatContext *s)
>
>     ts->last_pat_ts = AV_NOPTS_VALUE;
>     ts->last_sdt_ts = AV_NOPTS_VALUE;
> +    ts->last_nit_ts = AV_NOPTS_VALUE;
>     ts->pat_period = av_rescale(ts->pat_period_us, PCR_TIME_BASE, AV_TIME_BASE);
>     ts->sdt_period = av_rescale(ts->sdt_period_us, PCR_TIME_BASE, AV_TIME_BASE);
> +    ts->nit_period = av_rescale(ts->nit_period_us, PCR_TIME_BASE, AV_TIME_BASE);
>
>     if (ts->mux_rate == 1)
>         av_log(s, AV_LOG_VERBOSE, "muxrate VBR, ");
> @@ -1154,12 +1237,14 @@ static int mpegts_init(AVFormatContext *s)
>            "sdt every %"PRId64" ms, pat/pmt every %"PRId64" ms\n",
>            av_rescale(ts->sdt_period, 1000, PCR_TIME_BASE),
>            av_rescale(ts->pat_period, 1000, PCR_TIME_BASE));
> +    if (ts->nit_enable > 0)
> +        av_log(s, AV_LOG_VERBOSE, "nit every %"PRId64" ms\n", av_rescale(ts->nit_period, 1000, PCR_TIME_BASE));

Maybe you should continue the last log line which already describes the 
interval of various tables, and not start a new line.

>
>     return 0;
> }
>
> -/* send SDT, PAT and PMT tables regularly */
> -static void retransmit_si_info(AVFormatContext *s, int force_pat, int force_sdt, int64_t pcr)
> +/* send SDT, NIT, PAT and PMT tables regularly */
> +static void retransmit_si_info(AVFormatContext *s, int force_pat, int force_sdt, int force_nit, int64_t pcr)
> {
>     MpegTSWrite *ts = s->priv_data;
>     int i;
> @@ -1172,6 +1257,16 @@ static void retransmit_si_info(AVFormatContext *s, int force_pat, int force_sdt,
>             ts->last_sdt_ts = FFMAX(pcr, ts->last_sdt_ts);
>         mpegts_write_sdt(s);
>     }
> +    if ((pcr != AV_NOPTS_VALUE && ts->last_nit_ts == AV_NOPTS_VALUE) ||
> +        (pcr != AV_NOPTS_VALUE && pcr - ts->last_nit_ts >= ts->nit_period) ||
> +        force_nit
> +    ) {
> +        if (pcr != AV_NOPTS_VALUE)
> +          ts->last_nit_ts = FFMAX(pcr, ts->last_nit_ts);
> +        if (ts->nit_enable > 0) {
> +          mpegts_write_nit(s);
> +        }
> +    }

indentation

>     if ((pcr != AV_NOPTS_VALUE && ts->last_pat_ts == AV_NOPTS_VALUE) ||
>         (pcr != AV_NOPTS_VALUE && pcr - ts->last_pat_ts >= ts->pat_period) ||
>         force_pat) {
> @@ -1305,6 +1400,7 @@ static void mpegts_write_pes(AVFormatContext *s, AVStream *st,
>     int64_t delay = av_rescale(s->max_delay, 90000, AV_TIME_BASE);
>     int force_pat = st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && key && !ts_st->prev_payload_key;
>     int force_sdt = 0;
> +    int force_nit = 0;
>
>     av_assert0(ts_st->payload != buf || st->codecpar->codec_type != AVMEDIA_TYPE_VIDEO);
>     if (ts->flags & MPEGTS_FLAG_PAT_PMT_AT_FRAMES && st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
> @@ -1314,6 +1410,7 @@ static void mpegts_write_pes(AVFormatContext *s, AVStream *st,
>     if (ts->flags & MPEGTS_FLAG_REEMIT_PAT_PMT) {
>         force_pat = 1;
>         force_sdt = 1;
> +        force_nit = 1;
>         ts->flags &= ~MPEGTS_FLAG_REEMIT_PAT_PMT;
>     }
>
> @@ -1325,9 +1422,10 @@ static void mpegts_write_pes(AVFormatContext *s, AVStream *st,
>         else if (dts != AV_NOPTS_VALUE)
>             pcr = (dts - delay) * 300;
>
> -        retransmit_si_info(s, force_pat, force_sdt, pcr);
> +        retransmit_si_info(s, force_pat, force_sdt, force_nit, pcr);
>         force_pat = 0;
>         force_sdt = 0;
> +        force_nit = 0;
>
>         write_pcr = 0;
>         if (ts->mux_rate > 1) {
> @@ -2115,7 +2213,7 @@ static const AVOption options[] = {
>     { "initial_discontinuity", "Mark initial packets as discontinuous",
>       0, AV_OPT_TYPE_CONST, { .i64 = MPEGTS_FLAG_DISCONT }, 0, INT_MAX, ENC, "mpegts_flags" },
>     { "mpegts_copyts", "don't offset dts/pts", OFFSET(copyts), AV_OPT_TYPE_BOOL, { .i64 = -1 }, -1, 1, ENC },
> -    { "tables_version", "set PAT, PMT and SDT version", OFFSET(tables_version), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 31, ENC },
> +    { "tables_version", "set PAT, PMT, SDT and NIT version", OFFSET(tables_version), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 31, ENC },
>     { "omit_video_pes_length", "Omit the PES packet length for video packets",
>       OFFSET(omit_video_pes_length), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, ENC },
>     { "pcr_period", "PCR retransmission time in milliseconds",
> @@ -2124,6 +2222,10 @@ static const AVOption options[] = {
>       OFFSET(pat_period_us), AV_OPT_TYPE_DURATION, { .i64 = PAT_RETRANS_TIME * 1000LL }, 0, INT64_MAX, ENC },
>     { "sdt_period", "SDT retransmission time limit in seconds",
>       OFFSET(sdt_period_us), AV_OPT_TYPE_DURATION, { .i64 = SDT_RETRANS_TIME * 1000LL }, 0, INT64_MAX, ENC },
> +    { "nit_period", "NIT retransmission time limit in seconds",
> +      OFFSET(nit_period_us), AV_OPT_TYPE_DURATION, { .i64 = NIT_RETRANS_TIME * 1000LL }, 0, INT64_MAX, ENC },
> +    { "nit_enable", "Enable NIT transmission",
> +      OFFSET(nit_enable), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC },

As I wrote earlier, instead of a separate option, you should introduce a 
new flag in mpegts_flags.

Also docs/muxers.texi update is missing for the new feature.

Regards,
Marton
diff mbox series

Patch

diff --git a/libavformat/mpegtsenc.c b/libavformat/mpegtsenc.c
index a357f3a6aa..d03eaaa009 100644
--- a/libavformat/mpegtsenc.c
+++ b/libavformat/mpegtsenc.c
@@ -76,10 +76,12 @@  typedef struct MpegTSWrite {
     const AVClass *av_class;
     MpegTSSection pat; /* MPEG-2 PAT table */
     MpegTSSection sdt; /* MPEG-2 SDT table context */
+    MpegTSSection nit; /* MPEG-2 NIT table context */
     MpegTSService **services;
     AVPacket *pkt;
     int64_t sdt_period; /* SDT period in PCR time base */
     int64_t pat_period; /* PAT/PMT period in PCR time base */
+    int64_t nit_period; /* NIT period in PCR time base */
     int nb_services;
     int64_t first_pcr;
     int first_dts_checked;
@@ -112,10 +114,13 @@  typedef struct MpegTSWrite {
     int tables_version;
     int64_t pat_period_us;
     int64_t sdt_period_us;
+    int64_t nit_period_us;
     int64_t last_pat_ts;
     int64_t last_sdt_ts;
+    int64_t last_nit_ts;
 
     int omit_video_pes_length;
+    int nit_enable;
 } MpegTSWrite;
 
 /* a PES packet header is generated every DEFAULT_PES_HEADER_FREQ packets */
@@ -227,6 +232,7 @@  static int mpegts_write_section1(MpegTSSection *s, int tid, int id,
 #define SDT_RETRANS_TIME 500
 #define PAT_RETRANS_TIME 100
 #define PCR_RETRANS_TIME 20
+#define NIT_RETRANS_TIME 500
 
 typedef struct MpegTSWriteStream {
     int pid; /* stream associated pid */
@@ -796,6 +802,75 @@  static void mpegts_write_sdt(AVFormatContext *s)
                           data, q - data);
 }
 
+static void mpegts_write_nit(AVFormatContext *s)
+{
+    int i, len=0, provider_len=0, desc_len=0, virtual_channel_value;
+    MpegTSWrite *ts = s->priv_data;
+    uint8_t data[SECTION_LENGTH], *q, *desc_len_ptr, *loop_len_ptr;
+    AVDictionaryEntry *provider, *virtual_channel;
+    AVProgram *program;
+    const char *provider_name;
+    
+    q = data;
+    
+    //network name
+    provider = av_dict_get(s->metadata, "service_provider", NULL, 0);
+    provider_name = provider ? provider->value : DEFAULT_PROVIDER_NAME;
+    provider_len = strlen(provider_name);
+    put16(&q, 0xf000 | provider_len);
+    *q++ = 0x40;
+    *q++ = provider_len;
+    putbuf(&q, provider_name, provider_len);
+    
+    //TS loop
+    loop_len_ptr = q;	
+    q += 2;
+    put16(&q, ts->transport_stream_id);
+    put16(&q, ts->original_network_id);
+    	
+    //transport descriptor
+    desc_len_ptr = q;	
+    q += 2;
+    
+    //service list
+    len = 3 * ts->nb_services;
+    desc_len += len;
+    *q++ = 0x41; //tag
+    *q++ = len;
+    for(i = 0; i < ts->nb_services; i++) {
+      put16(&q, ts->services[i]->sid);//service_ID
+      *q++ = 0x01; //service type 0x01 for Digital TV Service
+    }
+	
+    //private data
+    desc_len += 6 + 2;
+    *q++ = 0x5F;
+    *q++ = 4;
+    *q++ = 0x00;
+    *q++ = 0x00;
+    put16(&q, 40);
+
+    //virtual channel
+    len = 4 * ts->nb_services;
+    desc_len += len + 2;
+    *q++ = 0x83;
+    *q++ = len;    
+    for (i = 0; i < ts->nb_services; i++) {
+      program = s->programs[i];
+      virtual_channel = av_dict_get(program->metadata, "virtual_channel", NULL, 0);
+      virtual_channel_value = virtual_channel ? atoi(virtual_channel->value) : 1000+i;
+      put16(&q, ts->services[i]->sid); 
+      put16(&q, 0xfc00 | virtual_channel_value);
+    }
+
+    //calculate lengths
+    put16(&desc_len_ptr, 0xf000 | desc_len);
+    put16(&loop_len_ptr, 0xf000 | desc_len+6);
+
+    mpegts_write_section1(&ts->nit, NIT_TID, ts->original_network_id, ts->tables_version, 0, 0,
+                          data, q - data);
+}
+
 /* This stores a string in buf with the correct encoding and also sets the
  * first byte as the length. !str is accepted for an empty string.
  * If the string is already encoded, invalid UTF-8 or has no multibyte sequence
@@ -1022,6 +1097,12 @@  static int mpegts_init(AVFormatContext *s)
     ts->sdt.write_packet = section_write_packet;
     ts->sdt.opaque       = s;
 
+    ts->nit.pid          = NIT_PID;
+    ts->nit.cc           = 15;
+    ts->nit.discontinuity= ts->flags & MPEGTS_FLAG_DISCONT;
+    ts->nit.write_packet = section_write_packet;
+    ts->nit.opaque       = s;
+
     ts->pkt = av_packet_alloc();
     if (!ts->pkt)
         return AVERROR(ENOMEM);
@@ -1143,8 +1224,10 @@  static int mpegts_init(AVFormatContext *s)
 
     ts->last_pat_ts = AV_NOPTS_VALUE;
     ts->last_sdt_ts = AV_NOPTS_VALUE;
+    ts->last_nit_ts = AV_NOPTS_VALUE;
     ts->pat_period = av_rescale(ts->pat_period_us, PCR_TIME_BASE, AV_TIME_BASE);
     ts->sdt_period = av_rescale(ts->sdt_period_us, PCR_TIME_BASE, AV_TIME_BASE);
+    ts->nit_period = av_rescale(ts->nit_period_us, PCR_TIME_BASE, AV_TIME_BASE);
 
     if (ts->mux_rate == 1)
         av_log(s, AV_LOG_VERBOSE, "muxrate VBR, ");
@@ -1154,12 +1237,14 @@  static int mpegts_init(AVFormatContext *s)
            "sdt every %"PRId64" ms, pat/pmt every %"PRId64" ms\n",
            av_rescale(ts->sdt_period, 1000, PCR_TIME_BASE),
            av_rescale(ts->pat_period, 1000, PCR_TIME_BASE));
+    if (ts->nit_enable > 0)
+        av_log(s, AV_LOG_VERBOSE, "nit every %"PRId64" ms\n", av_rescale(ts->nit_period, 1000, PCR_TIME_BASE));
 
     return 0;
 }
 
-/* send SDT, PAT and PMT tables regularly */
-static void retransmit_si_info(AVFormatContext *s, int force_pat, int force_sdt, int64_t pcr)
+/* send SDT, NIT, PAT and PMT tables regularly */
+static void retransmit_si_info(AVFormatContext *s, int force_pat, int force_sdt, int force_nit, int64_t pcr)
 {
     MpegTSWrite *ts = s->priv_data;
     int i;
@@ -1172,6 +1257,16 @@  static void retransmit_si_info(AVFormatContext *s, int force_pat, int force_sdt,
             ts->last_sdt_ts = FFMAX(pcr, ts->last_sdt_ts);
         mpegts_write_sdt(s);
     }
+    if ((pcr != AV_NOPTS_VALUE && ts->last_nit_ts == AV_NOPTS_VALUE) ||
+        (pcr != AV_NOPTS_VALUE && pcr - ts->last_nit_ts >= ts->nit_period) ||
+        force_nit
+    ) {
+        if (pcr != AV_NOPTS_VALUE)
+          ts->last_nit_ts = FFMAX(pcr, ts->last_nit_ts);
+        if (ts->nit_enable > 0) {
+          mpegts_write_nit(s);
+        }
+    }
     if ((pcr != AV_NOPTS_VALUE && ts->last_pat_ts == AV_NOPTS_VALUE) ||
         (pcr != AV_NOPTS_VALUE && pcr - ts->last_pat_ts >= ts->pat_period) ||
         force_pat) {
@@ -1305,6 +1400,7 @@  static void mpegts_write_pes(AVFormatContext *s, AVStream *st,
     int64_t delay = av_rescale(s->max_delay, 90000, AV_TIME_BASE);
     int force_pat = st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && key && !ts_st->prev_payload_key;
     int force_sdt = 0;
+    int force_nit = 0;
 
     av_assert0(ts_st->payload != buf || st->codecpar->codec_type != AVMEDIA_TYPE_VIDEO);
     if (ts->flags & MPEGTS_FLAG_PAT_PMT_AT_FRAMES && st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
@@ -1314,6 +1410,7 @@  static void mpegts_write_pes(AVFormatContext *s, AVStream *st,
     if (ts->flags & MPEGTS_FLAG_REEMIT_PAT_PMT) {
         force_pat = 1;
         force_sdt = 1;
+        force_nit = 1;
         ts->flags &= ~MPEGTS_FLAG_REEMIT_PAT_PMT;
     }
 
@@ -1325,9 +1422,10 @@  static void mpegts_write_pes(AVFormatContext *s, AVStream *st,
         else if (dts != AV_NOPTS_VALUE)
             pcr = (dts - delay) * 300;
 
-        retransmit_si_info(s, force_pat, force_sdt, pcr);
+        retransmit_si_info(s, force_pat, force_sdt, force_nit, pcr);
         force_pat = 0;
         force_sdt = 0;
+        force_nit = 0;
 
         write_pcr = 0;
         if (ts->mux_rate > 1) {
@@ -2115,7 +2213,7 @@  static const AVOption options[] = {
     { "initial_discontinuity", "Mark initial packets as discontinuous",
       0, AV_OPT_TYPE_CONST, { .i64 = MPEGTS_FLAG_DISCONT }, 0, INT_MAX, ENC, "mpegts_flags" },
     { "mpegts_copyts", "don't offset dts/pts", OFFSET(copyts), AV_OPT_TYPE_BOOL, { .i64 = -1 }, -1, 1, ENC },
-    { "tables_version", "set PAT, PMT and SDT version", OFFSET(tables_version), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 31, ENC },
+    { "tables_version", "set PAT, PMT, SDT and NIT version", OFFSET(tables_version), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 31, ENC },
     { "omit_video_pes_length", "Omit the PES packet length for video packets",
       OFFSET(omit_video_pes_length), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, ENC },
     { "pcr_period", "PCR retransmission time in milliseconds",
@@ -2124,6 +2222,10 @@  static const AVOption options[] = {
       OFFSET(pat_period_us), AV_OPT_TYPE_DURATION, { .i64 = PAT_RETRANS_TIME * 1000LL }, 0, INT64_MAX, ENC },
     { "sdt_period", "SDT retransmission time limit in seconds",
       OFFSET(sdt_period_us), AV_OPT_TYPE_DURATION, { .i64 = SDT_RETRANS_TIME * 1000LL }, 0, INT64_MAX, ENC },
+    { "nit_period", "NIT retransmission time limit in seconds",
+      OFFSET(nit_period_us), AV_OPT_TYPE_DURATION, { .i64 = NIT_RETRANS_TIME * 1000LL }, 0, INT64_MAX, ENC },
+    { "nit_enable", "Enable NIT transmission",
+      OFFSET(nit_enable), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC },
     { NULL },
 };