[FFmpeg-devel,v2] libavformat/mpegtsenc: Add minimal support for ATSC PSIP tables

Submitted by Phillip Burr on May 16, 2019, 1:31 p.m.

Details

Message ID 20190516133135.6680-1-phil.burr@gmail.com
State New
Headers show

Commit Message

Phillip Burr May 16, 2019, 1:31 p.m.
Minimal support for ATSC PSIP tables.  Does not support STT or
EIT tables and so is not compliant with terrestrial ATSC.
ATSC tables are not created by default, and will only be transmitted
if either "atsc_name" or "atsc_channel" metadata is supplied.

Signed-off-by: Phillip Burr <phil.burr@gmail.com>
---
 libavformat/mpegts.h    |   8 +
 libavformat/mpegtsenc.c | 414 ++++++++++++++++++++++++++++++++--------
 2 files changed, 345 insertions(+), 77 deletions(-)

Comments

Devin Heitmueller May 19, 2019, 5:08 p.m.
Hello Phillip,

On Thu, May 16, 2019 at 9:32 AM Phillip Burr <phil.burr@gmail.com> wrote:
>
> Minimal support for ATSC PSIP tables.  Does not support STT or
> EIT tables and so is not compliant with terrestrial ATSC.
> ATSC tables are not created by default, and will only be transmitted
> if either "atsc_name" or "atsc_channel" metadata is supplied.

In general I am supportive of this effort (producing ATSC compliant
streams).  Thanks for your work to improve the TS mux support in this
area.

While I haven't looked that closely at your implementation, one key
thing missing is updated documentation that describes the new
parameters, and in particular something that notes that streams
generated are currently not fully compliant with the spec (as you
noted, the lack of STT).  While it's good to have that in the patch
description, that's something that really needs to be known to end
users who might try to use the functionality.

Regards,

Devin
Phillip Burr May 20, 2019, 12:30 p.m.
Thank you for the feedback.  I will look into adding documentation for the
atsc metadata.  ATSC requires in addition to the tables I've added, the STT
and EIT0-EIT3 tables.  I'm thinking of adding support for STT and at least
producing empty EIT tables so that the stream would be minimally compliant.

Phil

On Sun, May 19, 2019 at 11:08 AM Devin Heitmueller <
dheitmueller@kernellabs.com> wrote:

> Hello Phillip,
>
> On Thu, May 16, 2019 at 9:32 AM Phillip Burr <phil.burr@gmail.com> wrote:
> >
> > Minimal support for ATSC PSIP tables.  Does not support STT or
> > EIT tables and so is not compliant with terrestrial ATSC.
> > ATSC tables are not created by default, and will only be transmitted
> > if either "atsc_name" or "atsc_channel" metadata is supplied.
>
> In general I am supportive of this effort (producing ATSC compliant
> streams).  Thanks for your work to improve the TS mux support in this
> area.
>
> While I haven't looked that closely at your implementation, one key
> thing missing is updated documentation that describes the new
> parameters, and in particular something that notes that streams
> generated are currently not fully compliant with the spec (as you
> noted, the lack of STT).  While it's good to have that in the patch
> description, that's something that really needs to be known to end
> users who might try to use the functionality.
>
> Regards,
>
> Devin
>
> --
> Devin J. Heitmueller - Kernel Labs
> http://www.kernellabs.com
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
Devin Heitmueller May 20, 2019, 1:23 p.m.
On Mon, May 20, 2019 at 8:36 AM Phil Burr <phil.burr@gmail.com> wrote:
>
> Thank you for the feedback.  I will look into adding documentation for the
> atsc metadata.  ATSC requires in addition to the tables I've added, the STT
> and EIT0-EIT3 tables.  I'm thinking of adding support for STT and at least
> producing empty EIT tables so that the stream would be minimally compliant.

I would have no problem with them being excluded in a first version of
the patch, as long as it's documented that they are not expected to be
present in the resulting streams.

Your patch is already a huge improvement over the current state of
affairs, and I don't want to see your patch not get merged because it
lacks two features that I think the vast majority of people won't care
about.

Devin
Phillip Burr May 20, 2019, 4:07 p.m.
Thank you for your suggestions.  I will push an update to the patch with
documentation and will hold off on STT and EIT[0-3] tables for a later
patch.

On Mon, May 20, 2019 at 7:24 AM Devin Heitmueller <
dheitmueller@kernellabs.com> wrote:

> On Mon, May 20, 2019 at 8:36 AM Phil Burr <phil.burr@gmail.com> wrote:
> >
> > Thank you for the feedback.  I will look into adding documentation for
> the
> > atsc metadata.  ATSC requires in addition to the tables I've added, the
> STT
> > and EIT0-EIT3 tables.  I'm thinking of adding support for STT and at
> least
> > producing empty EIT tables so that the stream would be minimally
> compliant.
>
> I would have no problem with them being excluded in a first version of
> the patch, as long as it's documented that they are not expected to be
> present in the resulting streams.
>
> Your patch is already a huge improvement over the current state of
> affairs, and I don't want to see your patch not get merged because it
> lacks two features that I think the vast majority of people won't care
> about.
>
> Devin
>
> --
> Devin J. Heitmueller - Kernel Labs
> http://www.kernellabs.com
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

Patch hide | download patch | download mbox

diff --git a/libavformat/mpegts.h b/libavformat/mpegts.h
index 272e2be4f7..ca6943b1ba 100644
--- a/libavformat/mpegts.h
+++ b/libavformat/mpegts.h
@@ -35,12 +35,20 @@ 
 /* pids */
 #define PAT_PID                 0x0000
 #define SDT_PID                 0x0011
+#define ATSC_PID                0x1ffb
 
 /* table ids */
 #define PAT_TID   0x00
 #define PMT_TID   0x02
 #define M4OD_TID  0x05
 #define SDT_TID   0x42
+#define MGT_TID   0xc7
+#define TVCT_TID  0xc8
+#define CVCT_TID  0xc9
+#define RRT_TID   0xca
+#define EIT_TID   0xcb
+#define ETT_TID   0xcc
+#define STT_TID   0xcd
 
 #define STREAM_TYPE_VIDEO_MPEG1     0x01
 #define STREAM_TYPE_VIDEO_MPEG2     0x02
diff --git a/libavformat/mpegtsenc.c b/libavformat/mpegtsenc.c
index fc0ea225c6..6707efb913 100644
--- a/libavformat/mpegtsenc.c
+++ b/libavformat/mpegtsenc.c
@@ -56,6 +56,11 @@  typedef struct MpegTSService {
     int sid;           /* service ID */
     uint8_t name[256];
     uint8_t provider_name[256];
+
+    uint16_t atsc_name[7]; /* ATSC VCT fields */
+    int atsc_mj_channel;
+    int atsc_mn_channel;
+
     int pcr_pid;
     int pcr_packet_count;
     int pcr_packet_period;
@@ -77,11 +82,16 @@  typedef struct MpegTSWrite {
     const AVClass *av_class;
     MpegTSSection pat; /* MPEG-2 PAT table */
     MpegTSSection sdt; /* MPEG-2 SDT table context */
+    MpegTSSection atsc; /* ATSC tables */
     MpegTSService **services;
     int sdt_packet_count;
     int sdt_packet_period;
     int pat_packet_count;
     int pat_packet_period;
+    int mgt_packet_count;
+    int mgt_packet_period;
+    int tvct_packet_count;
+    int tvct_packet_period;
     int nb_services;
     int onid;
     int tsid;
@@ -113,6 +123,9 @@  typedef struct MpegTSWrite {
     double sdt_period;
     int64_t last_pat_ts;
     int64_t last_sdt_ts;
+    int64_t last_mgt_ts;
+    int64_t last_tvct_ts;
+    int xmit_atsc;
 
     int omit_video_pes_length;
 } MpegTSWrite;
@@ -189,14 +202,24 @@  static inline void put16(uint8_t **q_ptr, int val)
     *q_ptr = q;
 }
 
+static inline void put32(uint8_t **q_ptr, int val)
+{
+    uint8_t *q;
+    q      = *q_ptr;
+    *q++   = val >> 24;
+    *q++   = val >> 16;
+    *q++   = val >> 8;
+    *q++   = val;
+    *q_ptr = q;
+}
+
 static int mpegts_write_section1(MpegTSSection *s, int tid, int id,
                                  int version, int sec_num, int last_sec_num,
-                                 uint8_t *buf, int len)
+                                 int private, uint8_t *buf, int len)
 {
     uint8_t section[1024], *q;
     unsigned int tot_len;
-    /* reserved_future_use field must be set to 1 for SDT */
-    unsigned int flags = tid == SDT_TID ? 0xf000 : 0xb000;
+    unsigned int flags = private ? 0xf000 : 0xb000;
 
     tot_len = 3 + 5 + len + 4;
     /* check if not too big */
@@ -226,6 +249,12 @@  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 MGT_RETRANS_TIME 150
+#define TVCT_RETRANS_TIME 400
+#define EIT0_RETRANS_TIME 500
+#define EIT1_RETRANS_TIME 3000
+#define EIT23_RETRANS_TIME 60000
+#define STT_RETRANS_TIME 1000
 
 typedef struct MpegTSWriteStream {
     struct MpegTSService *service;
@@ -260,7 +289,7 @@  static void mpegts_write_pat(AVFormatContext *s)
         put16(&q, service->sid);
         put16(&q, 0xe000 | service->pmt.pid);
     }
-    mpegts_write_section1(&ts->pat, PAT_TID, ts->tsid, ts->tables_version, 0, 0,
+    mpegts_write_section1(&ts->pat, PAT_TID, ts->tsid, ts->tables_version, 0, 0, 0,
                           data, q - data);
 }
 
@@ -282,6 +311,79 @@  static void put_registration_descriptor(uint8_t **q_ptr, uint32_t tag)
     *q_ptr = q;
 }
 
+static int extract_stream_type(AVStream *st, MpegTSWrite *ts)
+{
+    int stream_type;
+
+    switch (st->codecpar->codec_id) {
+    case AV_CODEC_ID_MPEG1VIDEO:
+    case AV_CODEC_ID_MPEG2VIDEO:
+        stream_type = STREAM_TYPE_VIDEO_MPEG2;
+        break;
+    case AV_CODEC_ID_MPEG4:
+        stream_type = STREAM_TYPE_VIDEO_MPEG4;
+        break;
+    case AV_CODEC_ID_H264:
+        stream_type = STREAM_TYPE_VIDEO_H264;
+        break;
+    case AV_CODEC_ID_HEVC:
+        stream_type = STREAM_TYPE_VIDEO_HEVC;
+        break;
+    case AV_CODEC_ID_CAVS:
+        stream_type = STREAM_TYPE_VIDEO_CAVS;
+        break;
+    case AV_CODEC_ID_DIRAC:
+        stream_type = STREAM_TYPE_VIDEO_DIRAC;
+        break;
+    case AV_CODEC_ID_VC1:
+        stream_type = STREAM_TYPE_VIDEO_VC1;
+        break;
+    case AV_CODEC_ID_MP2:
+    case AV_CODEC_ID_MP3:
+        if (   st->codecpar->sample_rate > 0
+            && st->codecpar->sample_rate < 32000) {
+            stream_type = STREAM_TYPE_AUDIO_MPEG2;
+        } else {
+            stream_type = STREAM_TYPE_AUDIO_MPEG1;
+        }
+        break;
+    case AV_CODEC_ID_AAC:
+        stream_type = (ts->flags & MPEGTS_FLAG_AAC_LATM)
+                        ? STREAM_TYPE_AUDIO_AAC_LATM
+                        : STREAM_TYPE_AUDIO_AAC;
+        break;
+    case AV_CODEC_ID_AAC_LATM:
+        stream_type = STREAM_TYPE_AUDIO_AAC_LATM;
+        break;
+    case AV_CODEC_ID_AC3:
+        stream_type = (ts->flags & MPEGTS_FLAG_SYSTEM_B)
+                        ? STREAM_TYPE_PRIVATE_DATA
+                        : STREAM_TYPE_AUDIO_AC3;
+        break;
+    case AV_CODEC_ID_EAC3:
+        stream_type = (ts->flags & MPEGTS_FLAG_SYSTEM_B)
+                        ? STREAM_TYPE_PRIVATE_DATA
+                        : STREAM_TYPE_AUDIO_EAC3;
+        break;
+    case AV_CODEC_ID_DTS:
+        stream_type = STREAM_TYPE_AUDIO_DTS;
+        break;
+    case AV_CODEC_ID_TRUEHD:
+        stream_type = STREAM_TYPE_AUDIO_TRUEHD;
+        break;
+    case AV_CODEC_ID_OPUS:
+        stream_type = STREAM_TYPE_PRIVATE_DATA;
+        break;
+    case AV_CODEC_ID_TIMED_ID3:
+        stream_type = STREAM_TYPE_METADATA;
+        break;
+    default:
+        stream_type = STREAM_TYPE_PRIVATE_DATA;
+        break;
+    }
+    return stream_type;
+}
+
 static int mpegts_write_pmt(AVFormatContext *s, MpegTSService *service)
 {
     MpegTSWrite *ts = s->priv_data;
@@ -323,73 +425,7 @@  static int mpegts_write_pmt(AVFormatContext *s, MpegTSService *service)
             err = 1;
             break;
         }
-        switch (st->codecpar->codec_id) {
-        case AV_CODEC_ID_MPEG1VIDEO:
-        case AV_CODEC_ID_MPEG2VIDEO:
-            stream_type = STREAM_TYPE_VIDEO_MPEG2;
-            break;
-        case AV_CODEC_ID_MPEG4:
-            stream_type = STREAM_TYPE_VIDEO_MPEG4;
-            break;
-        case AV_CODEC_ID_H264:
-            stream_type = STREAM_TYPE_VIDEO_H264;
-            break;
-        case AV_CODEC_ID_HEVC:
-            stream_type = STREAM_TYPE_VIDEO_HEVC;
-            break;
-        case AV_CODEC_ID_CAVS:
-            stream_type = STREAM_TYPE_VIDEO_CAVS;
-            break;
-        case AV_CODEC_ID_DIRAC:
-            stream_type = STREAM_TYPE_VIDEO_DIRAC;
-            break;
-        case AV_CODEC_ID_VC1:
-            stream_type = STREAM_TYPE_VIDEO_VC1;
-            break;
-        case AV_CODEC_ID_MP2:
-        case AV_CODEC_ID_MP3:
-            if (   st->codecpar->sample_rate > 0
-                && st->codecpar->sample_rate < 32000) {
-                stream_type = STREAM_TYPE_AUDIO_MPEG2;
-            } else {
-                stream_type = STREAM_TYPE_AUDIO_MPEG1;
-            }
-            break;
-        case AV_CODEC_ID_AAC:
-            stream_type = (ts->flags & MPEGTS_FLAG_AAC_LATM)
-                          ? STREAM_TYPE_AUDIO_AAC_LATM
-                          : STREAM_TYPE_AUDIO_AAC;
-            break;
-        case AV_CODEC_ID_AAC_LATM:
-            stream_type = STREAM_TYPE_AUDIO_AAC_LATM;
-            break;
-        case AV_CODEC_ID_AC3:
-            stream_type = (ts->flags & MPEGTS_FLAG_SYSTEM_B)
-                          ? STREAM_TYPE_PRIVATE_DATA
-                          : STREAM_TYPE_AUDIO_AC3;
-            break;
-        case AV_CODEC_ID_EAC3:
-            stream_type = (ts->flags & MPEGTS_FLAG_SYSTEM_B)
-                          ? STREAM_TYPE_PRIVATE_DATA
-                          : STREAM_TYPE_AUDIO_EAC3;
-            break;
-        case AV_CODEC_ID_DTS:
-            stream_type = STREAM_TYPE_AUDIO_DTS;
-            break;
-        case AV_CODEC_ID_TRUEHD:
-            stream_type = STREAM_TYPE_AUDIO_TRUEHD;
-            break;
-        case AV_CODEC_ID_OPUS:
-            stream_type = STREAM_TYPE_PRIVATE_DATA;
-            break;
-        case AV_CODEC_ID_TIMED_ID3:
-            stream_type = STREAM_TYPE_METADATA;
-            break;
-        default:
-            stream_type = STREAM_TYPE_PRIVATE_DATA;
-            break;
-        }
-
+        stream_type = extract_stream_type(st, ts);
         *q++ = stream_type;
         put16(&q, 0xe000 | ts_st->pid);
         desc_length_ptr = q;
@@ -638,7 +674,7 @@  static int mpegts_write_pmt(AVFormatContext *s, MpegTSService *service)
                "Try reducing the number of languages in the audio streams "
                "or the total number of streams.\n", i);
 
-    mpegts_write_section1(&service->pmt, PMT_TID, service->sid, ts->tables_version, 0, 0,
+    mpegts_write_section1(&service->pmt, PMT_TID, service->sid, ts->tables_version, 0, 0, 0,
                           data, q - data);
     return 0;
 }
@@ -677,7 +713,119 @@  static void mpegts_write_sdt(AVFormatContext *s)
         desc_list_len_ptr[0] = val >> 8;
         desc_list_len_ptr[1] = val;
     }
-    mpegts_write_section1(&ts->sdt, SDT_TID, ts->tsid, ts->tables_version, 0, 0,
+    mpegts_write_section1(&ts->sdt, SDT_TID, ts->tsid, ts->tables_version, 0, 0, 1,
+                          data, q - data);
+}
+
+static void mpegts_write_mgt(AVFormatContext *s)
+{
+    MpegTSWrite *ts = s->priv_data;
+    //MpegTSService *service;
+    uint8_t data[SECTION_LENGTH], *q, *table_start;
+
+
+    q = data;
+
+    *q++ = 0; /* protocol_version == 0 */
+    put16(&q, 1);   /* only one table defined :( */
+    table_start = q;
+    put16(&q, 0);   /* current tvct */
+    put16(&q, 0xE000 | ATSC_PID);
+    *q++ = 0xE0 | ts->tables_version;
+    put32(&q, (q - table_start) + 6); /* wag */
+    put16(&q, 0xF000);
+    put16(&q, 0xF000);
+    mpegts_write_section1(&ts->sdt, MGT_TID, ts->tsid, ts->tables_version, 0, 0, 1,
+                          data, q - data);
+}
+
+static void mpegts_write_tvct(AVFormatContext *s)
+{
+    MpegTSWrite *ts = s->priv_data;
+    MpegTSService *service;
+    uint8_t data[SECTION_LENGTH], *q, *num_services_ptr;
+    int i, remaining, err = 0;
+    unsigned stream_count;
+
+    q = data;
+    *q++ = 0; /* protocol_version == 0 */
+    num_services_ptr = q;
+    *q++ = 0;
+    remaining = SECTION_LENGTH - 4;  /* the two just placed, plus two bytes for additional_descriptors_length at end */
+
+    for (i = 0; i < ts->nb_services; i++) {
+        int j;
+        int stream_type;
+        service = ts->services[i];
+
+        // we need 18 bytes for this service + 5 bytes for service location + 6 bytes per stream
+        stream_count = service->program ? service->program->nb_stream_indexes : s->nb_streams;
+        if (remaining < 18+5+6*stream_count) {
+            err = 1;
+            break;
+        }
+
+        memcpy(q, service->atsc_name, sizeof(service->atsc_name));
+        q += sizeof(service->atsc_name);
+        put32(&q, 0xF0000000 | ((service->atsc_mj_channel & 0x3FF) << 18) | ((service->atsc_mn_channel & 0x3FF) << 8) | 4);
+        put16(&q, 0);
+        put16(&q, 0);
+        put16(&q, ts->tsid);
+        put16(&q, service->sid);
+        put16(&q, 0x0dc2);
+        put16(&q, 1);
+        /* one descriptor (service location) */
+        put16(&q, 0xfc00);
+
+        /* service location descriptor */
+        *q++ = 0xa1;
+        *q++ = 5+6*stream_count;
+        put16(&q, 0xe000 | service->pcr_pid);
+        *q++ = stream_count;
+        for (j = 0; j < stream_count; j++)
+        {
+            AVStream *st = s->streams[service->program ? service->program->stream_index[j] : j];
+            MpegTSWriteStream *ts_st = st->priv_data;
+            AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", NULL, 0);
+
+            stream_type = extract_stream_type(st, ts);
+            *q++ = stream_type;
+            put16(&q, 0xe000 | ts_st->pid);
+
+            if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && lang) {
+                char *p = lang->value;
+                char *next;
+
+                next = strchr(p, ',');
+                if (strlen(p) != 3 && (!next || next != p + 3)) {
+                    /* not a 3-letter code */
+                    *q++ = 0;
+                    *q++ = 0;
+                    *q++ = 0;
+                } else {
+                    *q++ = *p++;
+                    *q++ = *p++;
+                    *q++ = *p++;
+                }
+            } else {
+                *q++ = 0;
+                *q++ = 0;
+                *q++ = 0;
+            }
+        }
+    }
+    /* no additional descriptors*/
+    put16(&q, 0xfc00);
+
+    *num_services_ptr = i;
+    if (err) {
+        av_log(s, AV_LOG_ERROR,
+               "The TVCT section cannot fit service %d and all following services.\n"
+               "Try reducing the number of languages in the audio streams "
+               "or the total number of streams.\n", i);
+    }
+
+    mpegts_write_section1(&ts->atsc, TVCT_TID, ts->tsid, ts->tables_version, 0, 0, 1,
                           data, q - data);
 }
 
@@ -717,9 +865,42 @@  invalid:
     return 0;
 }
 
+static int encode_str16(uint16_t *buf, const char *str)
+{
+    const uint8_t *q = str;
+    uint8_t *curr = (uint8_t*)buf;
+    uint8_t *end = (uint8_t*)(buf + 7);
+    if (!str)
+        str = "";
+    while (*q) {
+        uint32_t code;
+        GET_UTF16(code, *q++, goto invalid;)    /* Is it valid UTF-16? */
+        if (code <= UINT16_MAX) {
+            if (curr == end) {
+                goto invalid;
+            }
+            put16(&curr, code);
+        } else {
+            if (curr >= end - 1) {
+                goto invalid;
+            }
+            put32(&curr, code);
+        }
+    }
+    while (curr < end) {
+        *curr++ = 0;
+    }
+    return 0;
+invalid:
+    return AVERROR(EINVAL);
+}
+
 static MpegTSService *mpegts_add_service(AVFormatContext *s, int sid,
                                          const char *provider_name,
-                                         const char *name)
+                                         const char *name,
+                                         const char *atsc_name,
+                                         int major_channel,
+                                         int minor_channel)
 {
     MpegTSWrite *ts = s->priv_data;
     MpegTSService *service;
@@ -735,8 +916,14 @@  static MpegTSService *mpegts_add_service(AVFormatContext *s, int sid,
         av_log(s, AV_LOG_ERROR, "Too long service or provider name\n");
         goto fail;
     }
+    if (encode_str16(service->atsc_name, atsc_name) < 0) {
+        av_log(s, AV_LOG_ERROR, "Too long atsc short name\n");
+        goto fail;
+    }
     if (av_dynarray_add_nofree(&ts->services, &ts->nb_services, service) < 0)
         goto fail;
+    service->atsc_mj_channel = major_channel;
+    service->atsc_mn_channel = minor_channel;
 
     return service;
 fail:
@@ -775,11 +962,13 @@  static int mpegts_init(AVFormatContext *s)
     MpegTSWriteStream *ts_st;
     MpegTSService *service;
     AVStream *st, *pcr_st = NULL;
-    AVDictionaryEntry *title, *provider;
+    AVDictionaryEntry *title, *provider, *name, *channel;
     int i, j;
     const char *service_name;
     const char *provider_name;
+    const char *atsc_name = DEFAULT_PROVIDER_NAME;
     int *pids;
+    int major_channel = 0, minor_channel = 0;
     int ret;
 
     if (s->max_delay < 0) /* Not set by the caller */
@@ -790,6 +979,7 @@  static int mpegts_init(AVFormatContext *s)
 
     ts->tsid = ts->transport_stream_id;
     ts->onid = ts->original_network_id;
+    ts->xmit_atsc = 0;
     if (!s->nb_programs) {
         /* allocate a single DVB service */
         title = av_dict_get(s->metadata, "service_name", NULL, 0);
@@ -798,8 +988,25 @@  static int mpegts_init(AVFormatContext *s)
         service_name  = title ? title->value : DEFAULT_SERVICE_NAME;
         provider      = av_dict_get(s->metadata, "service_provider", NULL, 0);
         provider_name = provider ? provider->value : DEFAULT_PROVIDER_NAME;
+        name          = av_dict_get(s->metadata, "atsc_name", NULL, 0);
+        if (name) {
+            atsc_name = name->value;
+            ts->xmit_atsc = 1;
+        }
+        channel       = av_dict_get(s->metadata, "atsc_channel", NULL, 0);
+        if (channel) {
+            char *endp;
+            major_channel = strtol(channel->value, &endp, 10);
+            minor_channel = 1;
+            if (*endp == '.') {
+                minor_channel = strtol(endp+1, NULL, 10);
+            }
+            ts->xmit_atsc = 1;
+        }
+
         service       = mpegts_add_service(s, ts->service_id,
-                                           provider_name, service_name);
+                                           provider_name, service_name,
+                                           atsc_name, major_channel, minor_channel);
 
         if (!service)
             return AVERROR(ENOMEM);
@@ -817,8 +1024,25 @@  static int mpegts_init(AVFormatContext *s)
             service_name  = title ? title->value : DEFAULT_SERVICE_NAME;
             provider      = av_dict_get(program->metadata, "service_provider", NULL, 0);
             provider_name = provider ? provider->value : DEFAULT_PROVIDER_NAME;
+            name          = av_dict_get(program->metadata, "atsc_name", NULL, 0);
+            if (name) {
+                atsc_name = name->value;
+                ts->xmit_atsc = 1;
+            }
+            channel       = av_dict_get(program->metadata, "atsc_channel", NULL, 0);
+            if (channel) {
+                char *endp;
+                major_channel = strtol(channel->value, &endp, 10);
+                minor_channel = 1;
+                if (*endp == '.') {
+                    minor_channel = strtol(endp+1, NULL, 10);
+                }
+                ts->xmit_atsc = 1;
+            }
+
             service       = mpegts_add_service(s, program->id,
-                                               provider_name, service_name);
+                                               provider_name, service_name,
+                                               atsc_name, major_channel, minor_channel);
 
             if (!service)
                 return AVERROR(ENOMEM);
@@ -845,6 +1069,12 @@  static int mpegts_init(AVFormatContext *s)
     ts->sdt.write_packet = section_write_packet;
     ts->sdt.opaque       = s;
 
+    ts->atsc.pid         = ATSC_PID;
+    ts->atsc.cc          = 15;
+    ts->atsc.discontinuity= ts->flags & MPEGTS_FLAG_DISCONT;
+    ts->atsc.write_packet = section_write_packet;
+    ts->atsc.opaque       = s;
+
     pids = av_malloc_array(s->nb_streams, sizeof(*pids));
     if (!pids) {
         ret = AVERROR(ENOMEM);
@@ -968,6 +1198,10 @@  static int mpegts_init(AVFormatContext *s)
                                      (TS_PACKET_SIZE * 8 * 1000);
         ts->pat_packet_period      = (int64_t)ts->mux_rate * PAT_RETRANS_TIME /
                                      (TS_PACKET_SIZE * 8 * 1000);
+        ts->mgt_packet_period      = (int64_t)ts->mux_rate * MGT_RETRANS_TIME /
+                                     (TS_PACKET_SIZE * 8 * 1000);
+        ts->tvct_packet_period     = (int64_t)ts->mux_rate * TVCT_RETRANS_TIME /
+                                     (TS_PACKET_SIZE * 8 * 1000);
 
         if (ts->copyts < 1)
             ts->first_pcr = av_rescale(s->max_delay, PCR_TIME_BASE, AV_TIME_BASE);
@@ -975,6 +1209,8 @@  static int mpegts_init(AVFormatContext *s)
         /* Arbitrary values, PAT/PMT will also be written on video key frames */
         ts->sdt_packet_period = 200;
         ts->pat_packet_period = 40;
+        ts->mgt_packet_period = INT_MAX;
+        ts->tvct_packet_period = INT_MAX;
         if (pcr_st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
             int frame_size = av_get_audio_frame_duration2(pcr_st->codecpar, 0);
             if (!frame_size) {
@@ -997,6 +1233,8 @@  static int mpegts_init(AVFormatContext *s)
 
     ts->last_pat_ts = AV_NOPTS_VALUE;
     ts->last_sdt_ts = AV_NOPTS_VALUE;
+    ts->last_mgt_ts = AV_NOPTS_VALUE;
+    ts->last_tvct_ts = AV_NOPTS_VALUE;
     // The user specified a period, use only it
     if (ts->pat_period < INT_MAX/2) {
         ts->pat_packet_period = INT_MAX;
@@ -1009,6 +1247,8 @@  static int mpegts_init(AVFormatContext *s)
     service->pcr_packet_count = service->pcr_packet_period;
     ts->pat_packet_count      = ts->pat_packet_period - 1;
     ts->sdt_packet_count      = ts->sdt_packet_period - 1;
+    ts->mgt_packet_count      = ts->mgt_packet_period - 1;
+    ts->tvct_packet_count     = ts->tvct_packet_period - 1;
 
     if (ts->mux_rate == 1)
         av_log(s, AV_LOG_VERBOSE, "muxrate VBR, ");
@@ -1060,6 +1300,26 @@  static void retransmit_si_info(AVFormatContext *s, int force_pat, int64_t dts)
         for (i = 0; i < ts->nb_services; i++)
             mpegts_write_pmt(s, ts->services[i]);
     }
+
+    if (!ts->xmit_atsc) {
+        return;
+    }
+    if (++ts->mgt_packet_count == ts->mgt_packet_period ||
+        (dts != AV_NOPTS_VALUE && ts->last_mgt_ts == AV_NOPTS_VALUE)
+    ) {
+        ts->mgt_packet_count = 0;
+        if (dts != AV_NOPTS_VALUE)
+            ts->last_mgt_ts = FFMAX(dts, ts->last_mgt_ts);
+        mpegts_write_mgt(s);
+    }
+    if (++ts->tvct_packet_count == ts->tvct_packet_period ||
+        (dts != AV_NOPTS_VALUE && ts->last_tvct_ts == AV_NOPTS_VALUE)
+    ) {
+        ts->tvct_packet_count = 0;
+        if (dts != AV_NOPTS_VALUE)
+            ts->last_tvct_ts = FFMAX(dts, ts->last_tvct_ts);
+        mpegts_write_tvct(s);
+    }
 }
 
 static int write_pcr_bits(uint8_t *buf, int64_t pcr)