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

Andriy Gelman andriy.gelman at gmail.com
Sat Jul 27 10:05:17 EEST 2019


Andreas,

On Mon, 22. Jul 12:22, Andreas HÃ¥kon wrote:
> Hi,
> 
> Based on the discussion of my previous patch https://patchwork.ffmpeg.org/patch/13487/
> Here I publish a first part of the patch that only addresses the PCR problem.

Thanks for splitting the patch set. 

> 
> This supersedes too: https://patchwork.ffmpeg.org/patch/13745/
> 
> Regards.
> A.H.
> 
> ---

> From a9d51e71c546986c8ea3bd111d16fc81354ffa3d Mon Sep 17 00:00:00 2001
> From: Andreas Hakon <andreas.hakon at protonmail.com>
> Date: Mon, 22 Jul 2019 13:10:20 +0100
> Subject: [PATCH] libavformat/mpegtsenc: fix incorrect PCR with multiple
>  programs
> 
> 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.

The PCR pid selection is probably fine.  
The issue is that pcr_packet_period is zero for N-1 out of N programs. 
That's why in your tests you see the insertion of the pcr adaptation field in each 
frame for 1/2 programs. 

> 
> This patch solves this problem. And you can check it with these commands:
> 
> - First, make sure you have "ffmpeg-old" (a binary without the patch),
> "ffmpeg-new" (a binary with the patch), and the test file "Day_Flight.mpg" from
> https://samples.ffmpeg.org/MPEG2/mpegts-klv/Day%20Flight.mpg
> 
> - Execute these commands:
> 
> $ ffmpeg-old -loglevel verbose -y -f mpegts -i Day_Flight.mpg \
>  -map i:0x1e1 -c:v:0 copy -map i:0x1e1 -c:v:1 copy \
>  -program st=0 -program st=1 -f mpegts out-error.ts
> 
> $ ffmpeg-new -loglevel verbose -y -f mpegts -i Day_Flight.mpg \
>  -map i:0x1e1 -c:v:0 copy -map i:0x1e1 -c:v:1 copy \
>  -program st=0 -program st=1 -f mpegts out-ok.ts
> 
> - As a result, you have two files "out-error.ts" and "out-ok.ts". Both files
> with the same content (but not identical): two services, and each one with one
> video stream. However, the first file has errors in the PCR timestamps.
> 
> - If you analyze the files, you can see that the file "out-error.ts" has PCR 
> marks in every packet of the pid 256 (a total of 561862 packets with PCR marks).
> However, the pid 257 has 783 packets only with PCR marks. On the other hand,
> the file "out-ok.ts" has correct PCR marks, and both pids (256 & 257) only have
> 783 packets with PCR timestamps.
> 
> - You can do additional checks with the tool "tsreport" from the tstools package.
> Try this to see the difference:
> 
> $ tsreport -b -v out-ok.ts | more
> $ tsreport -b -v out-error.ts | more
> 
> 
> Signed-off-by: Andreas Hakon <andreas.hakon at protonmail.com>
> ---
>  libavformat/mpegtsenc.c |   82 +++++++++++++++++++++++++----------------------
>  1 file changed, 44 insertions(+), 38 deletions(-)
> 
> diff --git a/libavformat/mpegtsenc.c b/libavformat/mpegtsenc.c
> index fc0ea22..4a978ed 100644
> --- a/libavformat/mpegtsenc.c
> +++ b/libavformat/mpegtsenc.c
> @@ -953,46 +953,54 @@ static int mpegts_init(AVFormatContext *s)
>  
>      av_freep(&pids);
>  
> -    /* 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);
> +    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];

How do you know that s->streams[0] belongs to this particular program? 

> +            ts_st            = pcr_st->priv_data;
> +            service->pcr_pid = ts_st->pid;
> +        } else

> +            ts_st = pcr_st->priv_data;

pcr_st stream is set outside the for loop. No reason that it's part of this program. 

> +
> +        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;

These assignments do not change for each service/program. They should probably
be outside of the for loop.


> +            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);
> +                } else {
> +                    service->pcr_packet_period =
> +                        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);

pcr_packet_period may be incorrect here because ts_st may be coming from a stream 
that's not part of this program.

>              }
> -        } 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);
>      }
>  
>      ts->last_pat_ts = AV_NOPTS_VALUE;
> @@ -1005,8 +1013,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;
>  

Andriy


More information about the ffmpeg-devel mailing list