1
0
mirror of https://github.com/FFmpeg/FFmpeg.git synced 2025-08-10 06:10:52 +02:00

avformat/dvdvideodec: fix seeking on multi-angle discs

When seeking on multi-angle titles, libdvdnav does not lock on
to the correct sectors initially as it seeks to find the right NAV packet.

This manifests itself as two bugs:
(1) When seeking on the first angle in a multi-angle segment,
frames from another angle will appear (for example in intro
or credits scenes). This issue is present in VLC also.

(2) When seeking during a segment on angle n+1, the demuxer
cannot deduce the right position from dvdnav and does not allow
seeking within the segment (due to it maintaining a strict state).

Correct the issue by switching to angle 1 before doing the seek
operation, and skipping 3 VOBUs (NAV packet led segments) ahead
where dvdnav will have positioned itself correctly.

Reported-by: Kacper Michajlow <kasper93@gmail.com>
Signed-off-by: Marth64 <marth64@proxyid.net>
This commit is contained in:
Marth64
2025-02-01 13:01:42 -06:00
parent 693703bcdb
commit f8c8e1f39d

View File

@@ -110,6 +110,7 @@ typedef struct DVDVideoPlaybackState {
int in_vts; /* if our navigator is in the VTS */
int is_seeking; /* relax navigation path while seeking */
int64_t nav_pts; /* PTS according to IFO, not frame-accurate */
int nb_vobu_skip; /* number of VOBUs we should skip */
uint64_t pgc_duration_est; /* estimated duration as reported by IFO */
uint64_t pgc_elapsed; /* the elapsed time of the PGC, cell-relative */
int pgc_nb_pg_est; /* number of PGs as reported by IFOs */
@@ -164,6 +165,7 @@ typedef struct DVDVideoDemuxContext {
/* playback control */
int64_t first_pts; /* the PTS of the first video keyframe */
int nb_angles; /* number of angles in the current title */
int play_started; /* signal that playback has started */
DVDVideoPlaybackState play_state; /* the active playback state */
int64_t *prev_pts; /* track the previous PTS emitted per stream */
@@ -297,6 +299,8 @@ static int dvdvideo_ifo_open(AVFormatContext *s)
return AVERROR_INVALIDDATA;
}
c->nb_angles = title_info.nr_of_angles;
return 0;
}
@@ -758,6 +762,13 @@ static int dvdvideo_play_next_ps_block(AVFormatContext *s, DVDVideoPlaybackState
return AVERROR_INVALIDDATA;
}
if (state->nb_vobu_skip > 0) {
av_log(s, AV_LOG_VERBOSE, "Skipping VOBU at SCR %d\n",
e_dsi->dsi_gi.nv_pck_scr);
state->nb_vobu_skip -= 1;
continue;
}
state->vobu_duration = e_pci->pci_gi.vobu_e_ptm - e_pci->pci_gi.vobu_s_ptm;
state->pgc_elapsed += state->vobu_duration;
state->nav_pts = dvdnav_get_current_time(state->dvdnav);
@@ -1734,6 +1745,7 @@ static int dvdvideo_read_seek(AVFormatContext *s, int stream_index, int64_t time
int64_t new_nav_pts;
pci_t* new_nav_pci;
dsi_t* new_nav_dsi;
int seek_failed = 0;
if (c->opt_menu || c->opt_chapter_start > 1) {
av_log(s, AV_LOG_ERROR, "Seeking is not compatible with menus or chapter extraction\n");
@@ -1753,9 +1765,30 @@ static int dvdvideo_read_seek(AVFormatContext *s, int stream_index, int64_t time
c->seek_warned = 1;
}
/* dvdnav loses NAV packets when seeking on multi-angle discs, so enforce angle 1 then revert */
if (c->nb_angles > 1) {
if (dvdnav_angle_change(c->play_state.dvdnav, 1) != DVDNAV_STATUS_OK) {
av_log(s, AV_LOG_ERROR, "Unable to open angle 1 for seeking\n");
return AVERROR_EXTERNAL;
}
}
/* XXX(PATCHWELCOME): use dvdnav_jump_to_sector_by_time(c->play_state.dvdnav, timestamp, 0)
* when it is available in a released version of libdvdnav; it is more accurate */
if (dvdnav_time_search(c->play_state.dvdnav, timestamp) != DVDNAV_STATUS_OK) {
seek_failed = 1;
}
if (c->nb_angles > 1) {
if (dvdnav_angle_change(c->play_state.dvdnav, c->opt_angle) != DVDNAV_STATUS_OK) {
av_log(s, AV_LOG_ERROR, "Unable to revert to angle %d after seeking\n", c->opt_angle);
return AVERROR_EXTERNAL;
}
}
if (seek_failed) {
av_log(s, AV_LOG_ERROR, "libdvdnav: seeking to %" PRId64 " failed\n", timestamp);
return AVERROR_EXTERNAL;
@@ -1779,6 +1812,9 @@ static int dvdvideo_read_seek(AVFormatContext *s, int stream_index, int64_t time
c->play_state.ptm_discont = 0;
c->play_state.vobu_e_ptm = new_nav_pci->pci_gi.vobu_s_ptm;
/* if there are multiple angles, skip the next 3 VOBUs as dvdnav will be at the wrong angle */
c->play_state.nb_vobu_skip = c->nb_angles > 1 ? 3 : 0;
c->first_pts = 0;
c->play_started = 0;
c->pts_offset = timestamp;