dvd: try to improve seeking

libdvdnav is garbage. Seeking by time is incredibly inexact, which is in
part due to the fact that it does not use the DVD seek tables. Instead,
it assumes CBR for certain ranges within the DVD, which makes especially
small seeks unreliable.

I have no good fix for this, other than hacking libdvdnav (I'd rather
prefer to remove mpv DVD support completely than doing this). So here's
a shitty hack that tries to workaround these problems. A basic
observation is that seeking in VLC seems to work quite well; however it
seems to be based on seeking by blocks (unless there is a subtle "trick"
I didn't see in the source code). mpv usually seeks by timestamps, so
this is not an option for us. However, we can pretend we are doing this
in the DVD layer.

The previous commit added a way to pass through relative seeks. This
commit uses the relative seek. STREAM_CTRL_SEEK_TO_TIME is backwards
compatible (there's still dvdread and bluray), so most code is about
extracing the relative seek information and turning it into a block
seek.

(Another way would have been using SEEK_FACTOR stuff, but that would
probably make for a less reliable way to handle this situation.)

Additionally, if a hr-seek is done, add an offset by 10 seconds. As long
as the error done by libdvdnav is not worse, this should help with hr-
seeks - although it makes them much slower.
This commit is contained in:
wm4 2015-01-19 21:25:30 +01:00
parent 966f0a41a4
commit 7a7d8d50e2
1 changed files with 41 additions and 3 deletions

View File

@ -565,18 +565,56 @@ static int control(stream_t *stream, int cmd, void *arg)
return STREAM_OK;
}
case STREAM_CTRL_SEEK_TO_TIME: {
double d = *(double *)arg;
double *args = arg;
double d = args[0]; // absolute target timestamp
double r = args[1]; // if not SEEK_ABSOLUTE, the base time for d
int flags = args[2]; // from SEEK_* flags (demux.h)
if (flags & SEEK_HR)
d -= 10; // fudge offset; it's a hack, because fuck libdvd*
int64_t tm = (int64_t)(d * 90000);
if (tm < 0)
tm = 0;
if (priv->duration && tm >= (priv->duration * 90))
tm = priv->duration * 90 - 1;
MP_VERBOSE(stream, "seek to PTS %f (%"PRId64")\n", d, tm);
if (dvdnav_time_search(dvdnav, tm) != DVDNAV_STATUS_OK)
uint32_t pos, len;
if (dvdnav_get_position(dvdnav, &pos, &len) != DVDNAV_STATUS_OK)
break;
// The following is convoluted, because we have to translate between
// dvdnav's block/CBR-based seeking bullshit, and the player's
// timestamp-based high-level machinery.
if (!(flags & SEEK_ABSOLUTE) && !(flags & SEEK_HR) && priv->duration > 0)
{
int dir = (flags & SEEK_BACKWARD) ? -1 : 1;
// The user is making a relative seek (translated to absolute),
// and we try not to get the user stuck on "boundaries". So try
// to do block based seeks, which should workaround libdvdnav's
// terrible CBR-based seeking.
d -= r; // relative seek amount in seconds
d = d / (priv->duration / 1000.0) * len; // d is now in blocks
d += pos; // absolute target in blocks
if (dir > 0)
d = MPMAX(d, pos + 1.0);
if (dir < 0)
d = MPMIN(d, pos - 1.0);
d += 0.5; // round
uint32_t target = MPCLAMP(d, 0, len);
MP_VERBOSE(stream, "seek from block %lu to %lu, dir=%d\n",
(unsigned long)pos, (unsigned long)target, dir);
if (dvdnav_sector_search(dvdnav, target, SEEK_SET) != DVDNAV_STATUS_OK)
break;
} else {
// "old" method, should be good enough for large seeks. Used for
// hr-seeks (with fudge offset), because I fear that block-based
// seeking might be off too far for large jumps.
MP_VERBOSE(stream, "seek to PTS %f (%"PRId64")\n", d, tm);
if (dvdnav_time_search(dvdnav, tm) != DVDNAV_STATUS_OK)
break;
}
stream_drop_buffers(stream);
d = dvdnav_get_current_time(dvdnav) / 90000.0f;
MP_VERBOSE(stream, "landed at: %f\n", d);
if (dvdnav_get_position(dvdnav, &pos, &len) == DVDNAV_STATUS_OK)
MP_VERBOSE(stream, "block: %lu\n", (unsigned long)pos);
return STREAM_OK;
}
case STREAM_CTRL_GET_NUM_ANGLES: {