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.b...@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.b...@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 > -@code{service_provider} is @samp{FFmpeg} and the default for > -@code{service_name} is @samp{Service01}. > +@subsection Metadata > + > +The recognized metadata settings in this muxer are: > + > +@table @option > +@item service_name @var{string} > +Set the @code{service_provider}. Default is @samp{FFmpeg}. > + > +@item service_name @var{string} > +Set the @code{service_name}. Default is @samp{Service01}. > + > +@item atsc_name @var{string} > +Set the @code{atsc_name} for the stream. This is the ATSC short > +channel name for the stream. > + > +@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}. > + > +@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 > +@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 > > _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".