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

Phil Burr phil.burr at gmail.com
Tue May 21 17:56:42 EEST 2019


This patch has a bit more restructuring to consolidate the ATSC specific
fields.  I fixed some bugs where a few fields were set incorrectly.
Specifically, the MGT transmits the length of each table it describes and
so each table it describes is pre-populated.  Testing last night involved
taking an OTA ATSC broadcast, remuxing with ffmpeg and transmitting to a TV
for two hours.

On Tue, May 21, 2019 at 8:40 AM Phillip Burr <phil.burr at 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.
>
> Signed-off-by: Phillip Burr <phil.burr at gmail.com>
> ---
>  doc/muxers.texi         |  33 ++-
>  libavformat/mpegts.h    |   8 +
>  libavformat/mpegtsenc.c | 468 ++++++++++++++++++++++++++++++++--------
>  3 files changed, 415 insertions(+), 94 deletions(-)
>
> diff --git a/doc/muxers.texi b/doc/muxers.texi
> index 83ae017d6c..dd68eec362 100644
> --- a/doc/muxers.texi
> +++ b/doc/muxers.texi
> @@ -1500,10 +1500,35 @@ MPEG transport stream muxer.
>
>  This muxer implements ISO 13818-1 and part of ETSI EN 300 468.
>
> -The recognized metadata settings in mpegts muxer are
> @code{service_provider}
> -and @code{service_name}. If they are not set the default for
> - at code{service_provider} is @samp{FFmpeg} and the default for
> - at code{service_name} is @samp{Service01}.
> + at subsection Metadata
> +
> +The recognized metadata settings in this muxer are:
> +
> + at table @option
> + at item service_name @var{string}
> +Set the @code{service_provider}.  Default is @samp{FFmpeg}.
> +
> + at item service_name @var{string}
> +Set the @code{service_name}. Default is @samp{Service01}.
> +
> + at item atsc_name @var{string}
> +Set the @code{atsc_name} for the stream.  This is the ATSC short
> +channel name for the stream.
> +
> + at item atsc_channel @var{string}
> +Set the @code{atsc_channel} virtual channel for the stream.  This
> +is parsed as @samp{Channel[.SubChannel]} format where the subchannel
> +is optional and defaults to @code{1}.
> +
> + at end table
> +
> +ATSC tables will @emph{not} be generated unless either @code{atsc_name}
> +or @code{atsc_channel} are provided @emph{and} @code{muxrate} is provided.
> +In the event that either @code{atsc_name} or @code{atsc_channel} is
> provided
> +but not both, the default for @code{atsc_name} is @samp{FFmpeg}
> +and the default for @code{atsc_channel} is @samp{1.1}.
> +ATSC tables generated include @code{MGT} and @code{TVCT} tables but are
> lacking the mandatory
> + at code{STT}, @code{EIT0}, @code{EIT1}, @code{EIT2} and @code{EIT3} tables
> needed for terrestrial ATC compliance.
>
>  @subsection Options
>
> 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..335ff254d8 100644
> --- a/libavformat/mpegtsenc.c
> +++ b/libavformat/mpegtsenc.c
> @@ -56,12 +56,40 @@ 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;
>      AVProgram *program;
>  } MpegTSService;
>
> +/* The section length is 12 bits. The first 2 are set to 0, the remaining
> + * 10 bits should not exceed 1021. */
> +#define SECTION_LENGTH 1020
> +
> +typedef struct MpegTSAtsc {
> +    int enabled;
> +    int regenerate;
> +
> +    MpegTSSection section; /* ATSC tables */
> +
> +    int mgt_packet_count;
> +    int mgt_packet_period;
> +    int tvct_packet_count;
> +    int tvct_packet_period;
> +    int64_t last_mgt_ts;
> +    int64_t last_tvct_ts;
> +
> +    uint32_t tvct_length;
> +    uint32_t mgt_length;
> +    uint8_t tvct_data[SECTION_LENGTH];
> +    uint8_t mgt_data[SECTION_LENGTH];
> +} MpegTSAtsc;
> +
>  // service_type values as defined in ETSI 300 468
>  enum {
>      MPEGTS_SERVICE_TYPE_DIGITAL_TV                   = 0x01,
> @@ -78,6 +106,7 @@ typedef struct MpegTSWrite {
>      MpegTSSection pat; /* MPEG-2 PAT table */
>      MpegTSSection sdt; /* MPEG-2 SDT table context */
>      MpegTSService **services;
> +    MpegTSAtsc atsc;
>      int sdt_packet_count;
>      int sdt_packet_period;
>      int pat_packet_count;
> @@ -121,10 +150,6 @@ typedef struct MpegTSWrite {
>  #define DEFAULT_PES_HEADER_FREQ  16
>  #define DEFAULT_PES_PAYLOAD_SIZE ((DEFAULT_PES_HEADER_FREQ - 1) * 184 +
> 170)
>
> -/* The section length is 12 bits. The first 2 are set to 0, the remaining
> - * 10 bits should not exceed 1021. */
> -#define SECTION_LENGTH 1020
> -
>  /* NOTE: 4 bytes must be left at the end for the crc32 */
>  static void mpegts_write_section(MpegTSSection *s, uint8_t *buf, int len)
>  {
> @@ -189,14 +214,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 +261,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 +301,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 +323,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 +437,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 +686,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,10 +725,140 @@ 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_generate_mgt(AVFormatContext *s)
> +{
> +    MpegTSWrite *ts = s->priv_data;
> +    uint8_t *q;
> +
> +    q = ts->atsc.mgt_data;
> +
> +    *q++ = 0; /* protocol_version == 0 */
> +    put16(&q, 1);   /* only one table defined */
> +    put16(&q, 0);   /* current tvct */
> +    put16(&q, 0xE000 | ATSC_PID);
> +    *q++ = 0xE0 | ts->tables_version;
> +    put32(&q, ts->atsc.tvct_length + 3+5+4);
> +    put16(&q, 0xF000);
> +    put16(&q, 0xF000);
> +    ts->atsc.mgt_length = q - ts->atsc.mgt_data;
> +}
> +
> +static void mpegts_generate_tvct(AVFormatContext *s)
> +{
> +    MpegTSWrite *ts = s->priv_data;
> +    MpegTSService *service;
> +    uint8_t *q, *num_services_ptr;
> +    int i, remaining, err = 0;
> +    unsigned stream_count;
> +
> +    q = ts->atsc.tvct_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+i);
> +        /* 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);
> +        ts->atsc.enabled = 0;
> +    }
> +    /* length + section headers */
> +    ts->atsc.tvct_length = q - ts->atsc.tvct_data;
> +}
> +
> +static void mpegts_write_mgt(AVFormatContext *s)
> +{
> +    MpegTSWrite *ts = s->priv_data;
> +
> +    if (ts->atsc.regenerate) {
> +        mpegts_generate_tvct(s);
> +        mpegts_generate_mgt(s);
> +        ts->atsc.regenerate = 0;
> +    }
> +
> +    mpegts_write_section1(&ts->atsc.section, MGT_TID, 0,
> ts->tables_version, 0, 0, 1,
> +                          ts->atsc.mgt_data, ts->atsc.mgt_length);
> +}
> +
> +static void mpegts_write_tvct(AVFormatContext *s)
> +{
> +    MpegTSWrite *ts = s->priv_data;
> +
> +    mpegts_write_section1(&ts->atsc.section, TVCT_TID, ts->tsid,
> ts->tables_version, 0, 0, 1,
> +                          ts->atsc.tvct_data, ts->atsc.tvct_length);
> +}
> +
>  /* 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
> @@ -717,9 +895,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 +946,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 +992,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 = 1, minor_channel = 1;
>      int ret;
>
>      if (s->max_delay < 0) /* Not set by the caller */
> @@ -790,6 +1009,8 @@ static int mpegts_init(AVFormatContext *s)
>
>      ts->tsid = ts->transport_stream_id;
>      ts->onid = ts->original_network_id;
> +    ts->atsc.enabled = 0;
> +    ts->atsc.regenerate = 1;
>      if (!s->nb_programs) {
>          /* allocate a single DVB service */
>          title = av_dict_get(s->metadata, "service_name", NULL, 0);
> @@ -798,8 +1019,24 @@ 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->atsc.enabled = 1;
> +        }
> +        channel       = av_dict_get(s->metadata, "atsc_channel", NULL, 0);
> +        if (channel) {
> +            char *endp;
> +            major_channel = strtol(channel->value, &endp, 10);
> +            if (*endp == '.') {
> +                minor_channel = strtol(endp+1, NULL, 10);
> +            }
> +            ts->atsc.enabled = 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 +1054,24 @@ 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->atsc.enabled = 1;
> +            }
> +            channel       = av_dict_get(program->metadata,
> "atsc_channel", NULL, 0);
> +            if (channel) {
> +                char *endp;
> +                major_channel = strtol(channel->value, &endp, 10);
> +                if (*endp == '.') {
> +                    minor_channel = strtol(endp+1, NULL, 10);
> +                }
> +                ts->atsc.enabled = 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 +1098,12 @@ static int mpegts_init(AVFormatContext *s)
>      ts->sdt.write_packet = section_write_packet;
>      ts->sdt.opaque       = s;
>
> +    ts->atsc.section.pid         = ATSC_PID;
> +    ts->atsc.section.cc          = 15;
> +    ts->atsc.section.discontinuity= ts->flags & MPEGTS_FLAG_DISCONT;
> +    ts->atsc.section.write_packet = section_write_packet;
> +    ts->atsc.section.opaque       = s;
> +
>      pids = av_malloc_array(s->nb_streams, sizeof(*pids));
>      if (!pids) {
>          ret = AVERROR(ENOMEM);
> @@ -962,12 +1221,16 @@ static int mpegts_init(AVFormatContext *s)
>          ts_st = pcr_st->priv_data;
>
>      if (ts->mux_rate > 1) {
> -        service->pcr_packet_period = (int64_t)ts->mux_rate *
> ts->pcr_period /
> -                                     (TS_PACKET_SIZE * 8 * 1000);
> -        ts->sdt_packet_period      = (int64_t)ts->mux_rate *
> SDT_RETRANS_TIME /
> -                                     (TS_PACKET_SIZE * 8 * 1000);
> -        ts->pat_packet_period      = (int64_t)ts->mux_rate *
> PAT_RETRANS_TIME /
> -                                     (TS_PACKET_SIZE * 8 * 1000);
> +        service->pcr_packet_period  = (int64_t)ts->mux_rate *
> ts->pcr_period /
> +                                      (TS_PACKET_SIZE * 8 * 1000);
> +        ts->sdt_packet_period       = (int64_t)ts->mux_rate *
> SDT_RETRANS_TIME /
> +                                      (TS_PACKET_SIZE * 8 * 1000);
> +        ts->pat_packet_period       = (int64_t)ts->mux_rate *
> PAT_RETRANS_TIME /
> +                                      (TS_PACKET_SIZE * 8 * 1000);
> +        ts->atsc.mgt_packet_period  = (int64_t)ts->mux_rate *
> MGT_RETRANS_TIME /
> +                                      (TS_PACKET_SIZE * 8 * 1000);
> +        ts->atsc.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 +1238,7 @@ 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->atsc.enabled = 0;
>          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 +1261,8 @@ static int mpegts_init(AVFormatContext *s)
>
>      ts->last_pat_ts = AV_NOPTS_VALUE;
>      ts->last_sdt_ts = AV_NOPTS_VALUE;
> +    ts->atsc.last_mgt_ts = AV_NOPTS_VALUE;
> +    ts->atsc.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;
> @@ -1006,9 +1272,11 @@ static int mpegts_init(AVFormatContext *s)
>      }
>
>      // output a PCR as soon as possible
> -    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;
> +    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->atsc.mgt_packet_count  = ts->atsc.mgt_packet_period - 1;
> +    ts->atsc.tvct_packet_count = ts->atsc.tvct_packet_period - 1;
>
>      if (ts->mux_rate == 1)
>          av_log(s, AV_LOG_VERBOSE, "muxrate VBR, ");
> @@ -1060,6 +1328,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->atsc.enabled) {
> +        return;
> +    }
> +    if (++ts->atsc.mgt_packet_count == ts->atsc.mgt_packet_period ||
> +        (dts != AV_NOPTS_VALUE && ts->atsc.last_mgt_ts == AV_NOPTS_VALUE)
> +    ) {
> +        ts->atsc.mgt_packet_count = 0;
> +        if (dts != AV_NOPTS_VALUE)
> +            ts->atsc.last_mgt_ts = FFMAX(dts, ts->atsc.last_mgt_ts);
> +        mpegts_write_mgt(s);
> +    }
> +    if (++ts->atsc.tvct_packet_count == ts->atsc.tvct_packet_period ||
> +        (dts != AV_NOPTS_VALUE && ts->atsc.last_tvct_ts == AV_NOPTS_VALUE)
> +    ) {
> +        ts->atsc.tvct_packet_count = 0;
> +        if (dts != AV_NOPTS_VALUE)
> +            ts->atsc.last_tvct_ts = FFMAX(dts, ts->atsc.last_tvct_ts);
> +        mpegts_write_tvct(s);
> +    }
>  }
>
>  static int write_pcr_bits(uint8_t *buf, int64_t pcr)
> --
> 2.20.1
>
>


More information about the ffmpeg-devel mailing list