diff --git a/doc/demuxers.texi b/doc/demuxers.texi index 1d2ee5bf37..072918be28 100644 --- a/doc/demuxers.texi +++ b/doc/demuxers.texi @@ -555,6 +555,10 @@ Show the detected raw packet size, cannot be set by the user. Scan and combine all PMTs. The value is an integer with value from -1 to 1 (-1 means automatic setting, 1 means enabled, 0 means disabled). Default value is -1. + +@item merge_pmt_versions +Re-use existing streams when a PMT's version is updated and elementary +streams move to different PIDs. Default value is 0. @end table @section mpjpeg diff --git a/libavformat/mpegts.c b/libavformat/mpegts.c index b21e55d3a7..0c9847f85b 100644 --- a/libavformat/mpegts.c +++ b/libavformat/mpegts.c @@ -148,6 +148,7 @@ struct MpegTSContext { int scan_all_pmts; int resync_size; + int merge_pmt_versions; /******************************************/ /* private mpegts data */ @@ -175,6 +176,8 @@ static const AVOption options[] = { {.i64 = -1}, -1, 1, AV_OPT_FLAG_DECODING_PARAM }, {"skip_unknown_pmt", "skip PMTs for programs not advertised in the PAT", offsetof(MpegTSContext, skip_unknown_pmt), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, AV_OPT_FLAG_DECODING_PARAM }, + {"merge_pmt_versions", "re-use streams when PMT's version/pids change", offsetof(MpegTSContext, merge_pmt_versions), AV_OPT_TYPE_BOOL, + {.i64 = 0}, 0, 1, AV_OPT_FLAG_DECODING_PARAM }, {"skip_changes", "skip changing / adding streams / programs", offsetof(MpegTSContext, skip_changes), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, 0 }, {"skip_clear", "skip clearing programs", offsetof(MpegTSContext, skip_clear), AV_OPT_TYPE_BOOL, @@ -1086,6 +1089,8 @@ static int mpegts_push_data(MpegTSFilter *filter, if (!pes->st) { if (ts->skip_changes) goto skip; + if (ts->merge_pmt_versions) + goto skip; /* wait for PMT to merge new stream */ pes->st = avformat_new_stream(ts->stream, NULL); if (!pes->st) @@ -2002,6 +2007,72 @@ int ff_parse_mpeg2_descriptor(AVFormatContext *fc, AVStream *st, int stream_type return 0; } +static AVStream *find_matching_stream(MpegTSContext *ts, int pid, + int stream_id, int pmt_stream_idx) +{ + AVFormatContext *s = ts->stream; + int i; + AVStream *found = NULL; + + for (i = 0; i < s->nb_streams; i++) { + AVStream *st = s->streams[i]; + if (stream_id != -1) { /* match based on "stream identifier descriptor" if present */ + if (st->stream_identifier == stream_id+1) { + found = st; + break; + } + } else if (st->pmt_stream_idx == pmt_stream_idx) { /* match based on position within the PMT */ + found = st; + break; + } + } + + if (found) { + av_log(ts->stream, AV_LOG_VERBOSE, + "re-using existing %s stream %d (pid=0x%x) for new pid=0x%x\n", + av_get_media_type_string(found->codecpar->codec_type), + i, found->id, pid); + } + + return found; +} + +static int parse_stream_identifier_desc(const uint8_t *p, const uint8_t *p_end) +{ + const uint8_t **pp = &p; + const uint8_t *desc_list_end; + const uint8_t *desc_end; + int desc_list_len; + int desc_len, desc_tag; + + desc_list_len = get16(pp, p_end); + if (desc_list_len < 0) + return -1; + desc_list_len &= 0xfff; + desc_list_end = p + desc_list_len; + if (desc_list_end > p_end) + return -1; + + while (1) { + desc_tag = get8(pp, desc_list_end); + if (desc_tag < 0) + return -1; + desc_len = get8(pp, desc_list_end); + if (desc_len < 0) + return -1; + desc_end = *pp + desc_len; + if (desc_end > desc_list_end) + return -1; + + if (desc_tag == 0x52) { + return get8(pp, desc_end); + } + *pp = desc_end; + } + + return -1; +} + static int is_pes_stream(int stream_type, uint32_t prog_reg_desc) { return !(stream_type == 0x13 || @@ -2019,6 +2090,7 @@ static void pmt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len int program_info_length, pcr_pid, pid, stream_type; int desc_list_len; uint32_t prog_reg_desc = 0; /* registration descriptor */ + int stream_id = -1; int mp4_descr_count = 0; Mp4Descr mp4_descr[MAX_MP4_DESCR_COUNT] = { { 0 } }; @@ -2107,11 +2179,21 @@ static void pmt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len if (pid == ts->current_pid) goto out; + if (ts->merge_pmt_versions) + stream_id = parse_stream_identifier_desc(p, p_end); + /* now create stream */ if (ts->pids[pid] && ts->pids[pid]->type == MPEGTS_PES) { pes = ts->pids[pid]->u.pes_filter.opaque; + if (ts->merge_pmt_versions && !pes->st) { + st = find_matching_stream(ts, pid, stream_id, i); + if (st) { + pes->st = st; + pes->stream_type = stream_type; + } + } if (!pes->st) { - pes->st = avformat_new_stream(pes->stream, NULL); + pes->st = avformat_new_stream(pes->stream, NULL); if (!pes->st) goto out; pes->st->id = pes->pid; @@ -2124,7 +2206,14 @@ static void pmt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len if (ts->pids[pid]) mpegts_close_filter(ts, ts->pids[pid]); // wrongly added sdt filter probably pes = add_pes_stream(ts, pid, pcr_pid); - if (pes) { + if (ts->merge_pmt_versions && pes && !pes->st) { + st = find_matching_stream(ts, pid, stream_id, i); + if (st) { + pes->st = st; + pes->stream_type = stream_type; + } + } + if (pes && !pes->st) { st = avformat_new_stream(pes->stream, NULL); if (!st) goto out; @@ -2137,7 +2226,11 @@ static void pmt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len int idx = ff_find_stream_index(ts->stream, pid); if (idx >= 0) { st = ts->stream->streams[idx]; - } else { + } + if (ts->merge_pmt_versions && !st) { + st = find_matching_stream(ts, pid, stream_id, i); + } + if (!st) { st = avformat_new_stream(ts->stream, NULL); if (!st) goto out; diff --git a/tests/fate/mpegts.mak b/tests/fate/mpegts.mak index 2b128492e0..bbcbfc47b2 100644 --- a/tests/fate/mpegts.mak +++ b/tests/fate/mpegts.mak @@ -15,6 +15,11 @@ fate-mpegts-probe-program: SRC = $(TARGET_SAMPLES)/mpegts/loewe.ts fate-mpegts-probe-program: CMD = run $(PROBE_CODEC_NAME_COMMAND) -select_streams p:769:v:0 -i "$(SRC)" +FATE_MPEGTS_PROBE-$(call DEMDEC, MPEGTS) += fate-mpegts-probe-pmt-merge +fate-mpegts-probe-pmt-merge: SRC = $(TARGET_SAMPLES)/mpegts/pmtchange.ts +fate-mpegts-probe-pmt-merge: CMD = run $(PROBE_CODEC_NAME_COMMAND) -merge_pmt_versions 1 -i "$(SRC)" + + FATE_SAMPLES_FFPROBE += $(FATE_MPEGTS_PROBE-yes) fate-mpegts: $(FATE_MPEGTS_PROBE-yes) diff --git a/tests/ref/fate/mpegts-probe-pmt-merge b/tests/ref/fate/mpegts-probe-pmt-merge new file mode 100644 index 0000000000..6e424af5d2 --- /dev/null +++ b/tests/ref/fate/mpegts-probe-pmt-merge @@ -0,0 +1,32 @@ +[PROGRAM] +[STREAM] +codec_name=ac3 +[/STREAM] +[STREAM] +codec_name=ac3 +[/STREAM] +[STREAM] +codec_name=ac3 +[/STREAM] +[STREAM] +codec_name=mpeg2video +[/STREAM] +[STREAM] +codec_name=scte_35 +[/STREAM] +[/PROGRAM] +[STREAM] +codec_name=ac3 +[/STREAM] +[STREAM] +codec_name=ac3 +[/STREAM] +[STREAM] +codec_name=ac3 +[/STREAM] +[STREAM] +codec_name=mpeg2video +[/STREAM] +[STREAM] +codec_name=scte_35 +[/STREAM]