[FFmpeg-devel] [PATCH 1/7] avformat/dvdvideodec: Fix racy PTS calculation and frame drops

Anton Khirnov anton at khirnov.net
Wed Aug 28 21:24:57 EEST 2024


Quoting Marth64 (2024-07-28 09:34:39)
> DVDs naturally consist of segmented MPEG-PS blobs within a VOB
> (i.e. VOBs are not linear). NAV packs set the segment boundaries.
> When switching between segments, discontinuities occur and thus
> the subdemuxer needs to be reset. The current approach to manage
> this is by invoking ff_read_frame_flush() on the subdemuxer context,
> via a callback function which is invoked during the menu or dvdnav
> block functions. The same subdemuxer context is used throughout
> the demux, with a stretched PTS wrap bits value (64) + disabled
> overflow correction, and then flushed on each segment. Eventually,
> a play_end context variable is set to declare EOF.
> 
> However, this approach is wrong and racy. The block read flushes the
> demuxer before the frame read is complete, causing frames to drop
> on discontinuity. The play_end signal likewise ends playback before
> the frame read is complete, causing frames to drop at end of the title.
> To compound the issue, the PTS wrap bits value of 64 is wrong;
> the VOBU limit is actually 32 and the overflow correction should work.
> 
> Instead, EOF the MPEG-PS subdemuxer organically when each VOB segment
> ends, and re-open it if needed with the offset after the full frame read
> is complete. In doing so, correct the PTS wrap behavior to 32 bits,
> remove the racy play_end/segment_started signals and the callback pattern.
> The behavior is now more similar to the HLS/DASH demuxers.
> 
> This commit fixes five intertwined issues, yielding an accurate demux:
> (1) Racy segment switching
> (2) Racy EOF signaling
> (3) Off-by-one leading to missed packets at start of menus
> (4) Incorrect PTS wrap behavior
> (5) Unnecessary frame discard workarounds removed
> 
> Signed-off-by: Marth64 <marth64 at proxyid.net>
> ---
>  libavformat/dvdvideodec.c | 198 +++++++++++++++++++-------------------
>  1 file changed, 100 insertions(+), 98 deletions(-)

I wouldn't call it a 'race', since that implies concurrency, while all
this should be strictly single-threaded. IIUC the problem is more about
various demuxer pieces getting desynchronized.

Besides that, the commit message looks generally sensible, but the diff
itself seems like it mixes a bit too many changes that could be split
into separate patches to make it more reviewable. E.g. renaming
ts_offset to ptm_offset, moving some error messages around, renaming
found_stream to st_matched, changing p_nav_event to p_is_nav_packet,
etc. - are all cosmetics that obscure the actual functional changes.

> @@ -1469,7 +1466,9 @@ static void dvdvideo_subdemux_close(AVFormatContext *s)
>      DVDVideoDemuxContext *c = s->priv_data;
>  
>      av_freep(&c->mpeg_pb.pub.buffer);
> +    memset(&c->mpeg_pb, 0x00, sizeof(c->mpeg_pb));

Why this change?

>      avformat_close_input(&c->mpeg_ctx);
> +    c->mpeg_ctx = NULL;

Does this do anything? The pointer should be NULLed by
avformat_close_input(), that's why it takes a double pointer.

>  }
>  
>  static int dvdvideo_subdemux_open(AVFormatContext *s)
> @@ -1501,12 +1500,23 @@ static int dvdvideo_subdemux_open(AVFormatContext *s)
>      c->mpeg_ctx->max_analyze_duration = 0;
>      c->mpeg_ctx->interrupt_callback = s->interrupt_callback;
>      c->mpeg_ctx->pb = &c->mpeg_pb.pub;
> -    c->mpeg_ctx->correct_ts_overflow = 0;

> -    c->mpeg_ctx->io_open = NULL;

Why?

> @@ -1604,72 +1614,64 @@ static int dvdvideo_read_packet(AVFormatContext *s, AVPacket *pkt)
>      DVDVideoDemuxContext *c = s->priv_data;
>  
>      int ret;
> -    enum AVMediaType st_type;
> -    int found_stream = 0;
> -
> -    if (c->play_end)
> -        return AVERROR_EOF;
> +    int st_matched = 0;
> +    AVStream *st_subdemux;
>  
>      ret = av_read_frame(c->mpeg_ctx, pkt);
> +    if (ret < 0) {
> +        if (c->subdemux_reset) {
> +            c->subdemux_reset = 0;
> +            c->pts_offset     = c->play_state.ptm_offset;
>  
> -    if (ret < 0)
> -        return ret;
> +            if ((ret = dvdvideo_subdemux_reset(s)) < 0)
> +                return ret;
> +
> +            return FFERROR_REDO;
> +        }
>  
> -    if (!c->segment_started)
> -        c->segment_started = 1;
> +        return ret;
> +    }
>  
> -    st_type = c->mpeg_ctx->streams[pkt->stream_index]->codecpar->codec_type;
> +    st_subdemux = c->mpeg_ctx->streams[pkt->stream_index];
>  
>      /* map the subdemuxer stream to the parent demuxer's stream (by startcode) */
>      for (int i = 0; i < s->nb_streams; i++) {
> -        if (s->streams[i]->id == c->mpeg_ctx->streams[pkt->stream_index]->id) {
> +        if (s->streams[i]->id == st_subdemux->id) {
>              pkt->stream_index = s->streams[i]->index;
> -            found_stream = 1;
> +            st_matched = 1;
>              break;
>          }
>      }
>  
> -    if (!found_stream) {
> -        av_log(s, AV_LOG_DEBUG, "discarding frame with stream that was not in IFO headers "
> -                                "(stream id=%d)\n", c->mpeg_ctx->streams[pkt->stream_index]->id);
> -
> -        return FFERROR_REDO;
> -    }
> +    if (!st_matched)
> +        goto discard;
>  
>      if (pkt->pts != AV_NOPTS_VALUE && pkt->dts != AV_NOPTS_VALUE) {
>          if (!c->play_started) {
> -            /* try to start at the beginning of a GOP */
> -            if (st_type != AVMEDIA_TYPE_VIDEO || !(pkt->flags & AV_PKT_FLAG_KEY)) {
> -                av_log(s, AV_LOG_VERBOSE, "Discarding packet which is not a video keyframe or "
> -                                          "with unset PTS/DTS at start\n");
> -                return FFERROR_REDO;
> -            }
> -
>              c->first_pts = pkt->pts;
>              c->play_started = 1;
>          }
>  
> -        pkt->pts += c->play_state.ts_offset - c->first_pts;
> -        pkt->dts += c->play_state.ts_offset - c->first_pts;
> -
> -        if (pkt->pts < 0) {
> -            av_log(s, AV_LOG_VERBOSE, "Discarding packet with negative PTS (st=%d pts=%" PRId64 "), "
> -                                      "this is OK at start of playback\n",
> -                                      pkt->stream_index, pkt->pts);
> -
> -            return FFERROR_REDO;
> -        }

Any reason you're not dropping pts<0 packets anymore?

-- 
Anton Khirnov


More information about the ffmpeg-devel mailing list