[FFmpeg-devel] libavformat/mpegtsenc: fix incorrect PCR with multiple programs [v3]

Andriy Gelman andriy.gelman at gmail.com
Mon Jul 29 09:46:37 EEST 2019


Andreas,

On Sun, 28. Jul 19:07, Andreas HÃ¥kon wrote:
> Hi,
> 
> This last version fixes the small bug discovered by Michael Niedermayer:
> https://patchwork.ffmpeg.org/patch/14099/
> 
> This version is finally clean.
> 
> Regards.
> A.H.
> 
> ---

> From 08565b81aa2b6d75043e5e984da143480891c3b0 Mon Sep 17 00:00:00 2001
> From: Andreas Hakon <andreas.hakon at protonmail.com>
> Date: Sun, 28 Jul 2019 19:59:51 +0100
> Subject: [PATCH] libavformat/mpegtsenc: fix incorrect PCR with multiple
>  programs [v3]
> 
> The MPEG-TS muxer has a serious bug related to the PCR pid selection.
> This bug appears when more than one program is used. The root cause is because
> the current code targets only one program when selecting the stream for the PCR.

I'd suggest to update the commit message because pid selection is not the main
issue. 

> 
> This patch solves this problem and select correct streams for each program.
> Furthermore, it passes all the checks and doesn't generate any regression.
> 
> You can check it with this command:
> 
> $ ./ffmpeg -loglevel verbose -y -f data -i /dev/zero \
>  -filter_complex "nullsrc=s=60x60,split=2[v0][v1],anullsrc[a]" \
>  -map [v0] -c:v:0 rawvideo \
>  -map [v1] -c:v:1 rawvideo \
>  -map [a]  -c:a:0 pcm_s8 \
>  -program st=0 -program st=1 -program st=2 -f mpegts out.ts
> 
> And you will see something like:
> 
> [mpegts @ 0x562f388cd800] service 1 using PCR in pid=256
> [mpegts @ 0x562f388cd800] service 2 using PCR in pid=257
> [mpegts @ 0x562f388cd800] service 3 using PCR in pid=258
> 
> 
> Signed-off-by: Andreas Hakon <andreas.hakon at protonmail.com>
> ---
>  libavformat/mpegtsenc.c |  103 ++++++++++++++++++++++++++++-------------------
>  1 file changed, 62 insertions(+), 41 deletions(-)
> 
> diff --git a/libavformat/mpegtsenc.c b/libavformat/mpegtsenc.c
> index fc0ea22..fc340cd 100644
> --- a/libavformat/mpegtsenc.c
> +++ b/libavformat/mpegtsenc.c
> @@ -60,6 +60,7 @@ typedef struct MpegTSService {
>      int pcr_packet_count;
>      int pcr_packet_period;
>      AVProgram *program;
> +    AVStream *pcr_st;
>  } MpegTSService;
>  
>  // service_type values as defined in ETSI 300 468
> @@ -774,7 +775,7 @@ static int mpegts_init(AVFormatContext *s)
>      MpegTSWrite *ts = s->priv_data;
>      MpegTSWriteStream *ts_st;
>      MpegTSService *service;
> -    AVStream *st, *pcr_st = NULL;
> +    AVStream *st;
>      AVDictionaryEntry *title, *provider;
>      int i, j;
>      const char *service_name;
> @@ -831,6 +832,11 @@ static int mpegts_init(AVFormatContext *s)
>          }
>      }
>  
> +    for (i = 0; i < ts->nb_services; i++) {
> +        service = ts->services[i];
> +        service->pcr_st = NULL;
> +    }
> +
>      ts->pat.pid          = PAT_PID;
>      /* Initialize at 15 so that it wraps and is equal to 0 for the
>       * first packet we write. */
> @@ -917,7 +923,12 @@ static int mpegts_init(AVFormatContext *s)
>          if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO &&
>              service->pcr_pid == 0x1fff) {
>              service->pcr_pid = ts_st->pid;
> -            pcr_st           = st;
> +            service->pcr_st  = st;
> +        }
> +        /* store this stream as a candidate PCR if the service doesn't have any */
> +        if (service->pcr_pid == 0x1fff &&
> +            !service->pcr_st) {
> +            service->pcr_st  = st;
>          }
>          if (st->codecpar->codec_id == AV_CODEC_ID_AAC &&
>              st->codecpar->extradata_size > 0) {
> @@ -951,50 +962,62 @@ static int mpegts_init(AVFormatContext *s)
>          }
>      }
>  
> -    av_freep(&pids);
> +    for (i = 0; i < ts->nb_services; i++) {
> +        service = ts->services[i];
>  
> -    /* if no video stream, use the first stream as PCR */
> -    if (service->pcr_pid == 0x1fff && s->nb_streams > 0) {
> -        pcr_st           = s->streams[0];
> -        ts_st            = pcr_st->priv_data;
> -        service->pcr_pid = ts_st->pid;
> -    } else
> -        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);
> -
> -        if (ts->copyts < 1)
> -            ts->first_pcr = av_rescale(s->max_delay, PCR_TIME_BASE, AV_TIME_BASE);
> -    } else {
> -        /* Arbitrary values, PAT/PMT will also be written on video key frames */
> -        ts->sdt_packet_period = 200;
> -        ts->pat_packet_period = 40;
> -        if (pcr_st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
> -            int frame_size = av_get_audio_frame_duration2(pcr_st->codecpar, 0);
> -            if (!frame_size) {
> -                av_log(s, AV_LOG_WARNING, "frame size not set\n");
> -                service->pcr_packet_period =
> -                    pcr_st->codecpar->sample_rate / (10 * 512);
> +        /* get the PCR stream (real or candidate) */

> +        if (!service->pcr_st) {
> +            av_log(s, AV_LOG_ERROR, "no PCR selected for the service %i", service->sid);
> +            ret = AVERROR(EINVAL);
> +            goto fail;
> +        }

Do you have to consider an edge case where the streams are not unique to each
program? 
For example the following will return an error, whereas it works before the
patch:

./ffmpeg -loglevel verbose -y -f mpegts -i day_flight.mpg \
-map i:0x1e1 -c:v:0 copy \
-program st=0 -program st=0 -f mpegts out-error.ts 

Also, you are missing \n at the end of your error message. 

> +        ts_st = service->pcr_st->priv_data;
> +
> +        /* if no video stream, use the pid of the candidate PCR */
> +        if (service->pcr_pid == 0x1fff)
> +            service->pcr_pid = ts_st->pid;
> +
> +        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);
> +            if (ts->copyts < 1)
> +                ts->first_pcr = av_rescale(s->max_delay, PCR_TIME_BASE, AV_TIME_BASE);
> +        } else {
> +            /* Arbitrary values, PAT/PMT will also be written on video key frames */
> +            ts->sdt_packet_period = 200;
> +            ts->pat_packet_period = 40;
> +            if (service->pcr_st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
> +                int frame_size = av_get_audio_frame_duration2(service->pcr_st->codecpar, 0);
> +                if (!frame_size) {
> +                    av_log(s, AV_LOG_WARNING, "frame size not set\n");
> +                    service->pcr_packet_period =
> +                        service->pcr_st->codecpar->sample_rate / (10 * 512);
> +                } else {
> +                    service->pcr_packet_period =
> +                        service->pcr_st->codecpar->sample_rate / (10 * frame_size);
> +                }
>              } else {
> +                // max delta PCR 0.1s
> +                // TODO: should be avg_frame_rate
>                  service->pcr_packet_period =
> -                    pcr_st->codecpar->sample_rate / (10 * frame_size);
> +                    ts_st->user_tb.den / (10 * ts_st->user_tb.num);
>              }
> -        } else {
> -            // max delta PCR 0.1s
> -            // TODO: should be avg_frame_rate
> -            service->pcr_packet_period =
> -                ts_st->user_tb.den / (10 * ts_st->user_tb.num);
> +            if (!service->pcr_packet_period)
> +                service->pcr_packet_period = 1;
>          }
> -        if (!service->pcr_packet_period)
> -            service->pcr_packet_period = 1;
> +
> +        // output a PCR as soon as possible
> +        service->pcr_packet_count = service->pcr_packet_period;
> +
> +        av_log(s, AV_LOG_VERBOSE, "service %i using PCR in pid=%i\n", service->sid, service->pcr_pid);
>      }
>  
> +    av_freep(&pids);
> +
>      ts->last_pat_ts = AV_NOPTS_VALUE;
>      ts->last_sdt_ts = AV_NOPTS_VALUE;
>      // The user specified a period, use only it
> @@ -1005,8 +1028,6 @@ static int mpegts_init(AVFormatContext *s)
>          ts->sdt_packet_period = INT_MAX;
>      }
>  
> -    // 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;
>  
> -- 
> 1.7.10.4
> 

> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel at ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
> 
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request at ffmpeg.org with subject "unsubscribe".



More information about the ffmpeg-devel mailing list