mirror of
https://github.com/mpv-player/mpv
synced 2025-04-08 10:32:51 +00:00
demux_lavf: add terrible hack to make DVD playback just work
DVD playback had some trouble with PTS resets: libavformat's genpts feature would try reading until EOF (worst case) to find a new usable PTS in case a packet's PTS is not set correctly. Especially with slow DVD access, this would make the player to appear frozen. Reimplement it partially in demux_lavf.c, and use that code in the DVD case. This is heavily "inspired" by the code in av_read_frame from libavformat/utils.c. The difference is that we stop reading if no PTS has been found after 50 packets (consider this a heuristic). Also, we don't bother with the PTS wrapping and last-frame-before-EOF handling. Even with normal PTS wraps, the player frontend will go to hell for the duration of a frame anyway, and should recover quickly after that. The terribleness of this commit is mostly that we duplicate libavformat functionality, and that we suddenly need a packet queue.
This commit is contained in:
parent
e18ffd6b99
commit
65d8709152
@ -559,6 +559,16 @@
|
|||||||
``--demuxer-lavf-format=<value>``
|
``--demuxer-lavf-format=<value>``
|
||||||
Force a specific libavformat demuxer.
|
Force a specific libavformat demuxer.
|
||||||
|
|
||||||
|
``--demuxer-lavf-genpts-mode=<auto|lavf|builtin|no>``
|
||||||
|
Mode for deriving missing packet PTS values from packet DTS. ``lavf``
|
||||||
|
enables libavformat's ``genpts`` option. ``builtin`` enables equivalent
|
||||||
|
code in mpv. ``auto`` will enable either lavf (normal playback) or builtin
|
||||||
|
(DVD playback) in correct-pts mode. The difference between them is that
|
||||||
|
the builtin code will not potentially read until EOF trying to derive the
|
||||||
|
PTS (which is very bad for DVD playback). On the other hand, builtin might
|
||||||
|
give up too early, which is why lavf is preferred normally. ``no`` disables
|
||||||
|
both.
|
||||||
|
|
||||||
``--demuxer-lavf-o=<key>=<value>[,<key>=<value>[,...]]``
|
``--demuxer-lavf-o=<key>=<value>[,<key>=<value>[,...]]``
|
||||||
Pass AVOptions to libavformat demuxer.
|
Pass AVOptions to libavformat demuxer.
|
||||||
|
|
||||||
|
@ -223,6 +223,7 @@ typedef struct MPOpts {
|
|||||||
char *format;
|
char *format;
|
||||||
char *cryptokey;
|
char *cryptokey;
|
||||||
char *avopt;
|
char *avopt;
|
||||||
|
int genptsmode;
|
||||||
} lavfdopts;
|
} lavfdopts;
|
||||||
|
|
||||||
struct input_conf {
|
struct input_conf {
|
||||||
|
@ -58,6 +58,8 @@ const m_option_t lavfdopts_conf[] = {
|
|||||||
OPT_FLAG("allow-mimetype", lavfdopts.allow_mimetype, 0),
|
OPT_FLAG("allow-mimetype", lavfdopts.allow_mimetype, 0),
|
||||||
OPT_INTRANGE("probescore", lavfdopts.probescore, 0, 0, 100),
|
OPT_INTRANGE("probescore", lavfdopts.probescore, 0, 0, 100),
|
||||||
OPT_STRING("cryptokey", lavfdopts.cryptokey, 0),
|
OPT_STRING("cryptokey", lavfdopts.cryptokey, 0),
|
||||||
|
OPT_CHOICE("genpts-mode", lavfdopts.genptsmode, 0,
|
||||||
|
({"auto", 0}, {"lavf", 1}, {"builtin", 2}, {"no", 3})),
|
||||||
OPT_STRING("o", lavfdopts.avopt, 0),
|
OPT_STRING("o", lavfdopts.avopt, 0),
|
||||||
{NULL, NULL, 0, 0, 0, 0, NULL}
|
{NULL, NULL, 0, 0, 0, 0, NULL}
|
||||||
};
|
};
|
||||||
@ -66,6 +68,8 @@ const m_option_t lavfdopts_conf[] = {
|
|||||||
// libavformat (almost) always reads data in blocks of this size.
|
// libavformat (almost) always reads data in blocks of this size.
|
||||||
#define BIO_BUFFER_SIZE 32768
|
#define BIO_BUFFER_SIZE 32768
|
||||||
|
|
||||||
|
#define MAX_PKT_QUEUE 50
|
||||||
|
|
||||||
typedef struct lavf_priv {
|
typedef struct lavf_priv {
|
||||||
char *filename;
|
char *filename;
|
||||||
const struct format_hack *format_hack;
|
const struct format_hack *format_hack;
|
||||||
@ -81,6 +85,9 @@ typedef struct lavf_priv {
|
|||||||
bool seek_by_bytes;
|
bool seek_by_bytes;
|
||||||
int bitrate;
|
int bitrate;
|
||||||
char *mime_type;
|
char *mime_type;
|
||||||
|
bool genpts_hack;
|
||||||
|
AVPacket *packets[MAX_PKT_QUEUE];
|
||||||
|
int num_packets;
|
||||||
} lavf_priv_t;
|
} lavf_priv_t;
|
||||||
|
|
||||||
struct format_hack {
|
struct format_hack {
|
||||||
@ -522,8 +529,13 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
|||||||
priv->use_dts = true;
|
priv->use_dts = true;
|
||||||
demuxer->timestamp_type = TIMESTAMP_TYPE_SORT;
|
demuxer->timestamp_type = TIMESTAMP_TYPE_SORT;
|
||||||
} else {
|
} else {
|
||||||
if (opts->user_correct_pts != 0)
|
int mode = lavfdopts->genptsmode;
|
||||||
|
if (mode == 0 && opts->user_correct_pts != 0)
|
||||||
|
mode = demuxer->stream->uncached_type == STREAMTYPE_DVD ? 2 : 1;
|
||||||
|
if (mode == 1)
|
||||||
avfc->flags |= AVFMT_FLAG_GENPTS;
|
avfc->flags |= AVFMT_FLAG_GENPTS;
|
||||||
|
if (mode == 2)
|
||||||
|
priv->genpts_hack = true;
|
||||||
}
|
}
|
||||||
if (opts->index_mode == 0)
|
if (opts->index_mode == 0)
|
||||||
avfc->flags |= AVFMT_FLAG_IGNIDX;
|
avfc->flags |= AVFMT_FLAG_IGNIDX;
|
||||||
@ -646,45 +658,107 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void seek_reset(demuxer_t *demux)
|
||||||
|
{
|
||||||
|
lavf_priv_t *priv = demux->priv;
|
||||||
|
|
||||||
|
for (int n = 0; n < priv->num_packets; n++)
|
||||||
|
talloc_free(priv->packets[n]);
|
||||||
|
priv->num_packets = 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int destroy_avpacket(void *pkt)
|
static int destroy_avpacket(void *pkt)
|
||||||
{
|
{
|
||||||
av_free_packet(pkt);
|
av_free_packet(pkt);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int read_more_av_packets(demuxer_t *demux)
|
||||||
|
{
|
||||||
|
lavf_priv_t *priv = demux->priv;
|
||||||
|
|
||||||
|
if (priv->num_packets >= MAX_PKT_QUEUE)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
demux->filepos = stream_tell(demux->stream);
|
||||||
|
|
||||||
|
AVPacket *pkt = talloc(NULL, AVPacket);
|
||||||
|
if (av_read_frame(priv->avfc, pkt) < 0) {
|
||||||
|
talloc_free(pkt);
|
||||||
|
return -2; // eof
|
||||||
|
}
|
||||||
|
talloc_set_destructor(pkt, destroy_avpacket);
|
||||||
|
|
||||||
|
add_new_streams(demux);
|
||||||
|
|
||||||
|
assert(pkt->stream_index >= 0 && pkt->stream_index < priv->num_streams);
|
||||||
|
struct sh_stream *stream = priv->streams[pkt->stream_index];
|
||||||
|
|
||||||
|
if (!demuxer_stream_is_selected(demux, stream)) {
|
||||||
|
talloc_free(pkt);
|
||||||
|
return 0; // skip
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the packet has pointers to temporary fields that could be
|
||||||
|
// overwritten/freed by next av_read_frame(), copy them to persistent
|
||||||
|
// allocations so we can safely queue the packet for any length of time.
|
||||||
|
if (av_dup_packet(pkt) < 0)
|
||||||
|
abort();
|
||||||
|
|
||||||
|
priv->packets[priv->num_packets++] = pkt;
|
||||||
|
talloc_steal(priv, pkt);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int read_av_packet(demuxer_t *demux, AVPacket **pkt)
|
||||||
|
{
|
||||||
|
lavf_priv_t *priv = demux->priv;
|
||||||
|
|
||||||
|
if (priv->num_packets < 1) {
|
||||||
|
int r = read_more_av_packets(demux);
|
||||||
|
if (r <= 0)
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
AVPacket *next = priv->packets[0];
|
||||||
|
if (priv->genpts_hack && next->dts != AV_NOPTS_VALUE) {
|
||||||
|
int n = 1;
|
||||||
|
while (next->pts == AV_NOPTS_VALUE) {
|
||||||
|
while (n >= priv->num_packets) {
|
||||||
|
if (read_more_av_packets(demux) < 0)
|
||||||
|
goto end; // queue limit or EOF reached - just use as is
|
||||||
|
}
|
||||||
|
AVPacket *cur = priv->packets[n];
|
||||||
|
if (cur->stream_index == next->stream_index) {
|
||||||
|
if (next->dts < cur->dts && cur->pts != cur->dts)
|
||||||
|
next->pts = cur->dts;
|
||||||
|
}
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
end:
|
||||||
|
MP_TARRAY_REMOVE_AT(priv->packets, priv->num_packets, 0);
|
||||||
|
*pkt = next;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
static int demux_lavf_fill_buffer(demuxer_t *demux)
|
static int demux_lavf_fill_buffer(demuxer_t *demux)
|
||||||
{
|
{
|
||||||
lavf_priv_t *priv = demux->priv;
|
lavf_priv_t *priv = demux->priv;
|
||||||
demux_packet_t *dp;
|
demux_packet_t *dp;
|
||||||
mp_msg(MSGT_DEMUX, MSGL_DBG2, "demux_lavf_fill_buffer()\n");
|
mp_msg(MSGT_DEMUX, MSGL_DBG2, "demux_lavf_fill_buffer()\n");
|
||||||
|
|
||||||
demux->filepos = stream_tell(demux->stream);
|
AVPacket *pkt;
|
||||||
|
int r = read_av_packet(demux, &pkt);
|
||||||
|
if (r <= 0)
|
||||||
|
return r == 0; // don't signal EOF if skipping a packet
|
||||||
|
|
||||||
AVPacket *pkt = talloc(NULL, AVPacket);
|
|
||||||
if (av_read_frame(priv->avfc, pkt) < 0) {
|
|
||||||
talloc_free(pkt);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
talloc_set_destructor(pkt, destroy_avpacket);
|
|
||||||
|
|
||||||
add_new_streams(demux);
|
|
||||||
|
|
||||||
assert(pkt->stream_index >= 0 && pkt->stream_index < priv->num_streams);
|
|
||||||
AVStream *st = priv->avfc->streams[pkt->stream_index];
|
AVStream *st = priv->avfc->streams[pkt->stream_index];
|
||||||
struct sh_stream *stream = priv->streams[pkt->stream_index];
|
struct sh_stream *stream = priv->streams[pkt->stream_index];
|
||||||
|
|
||||||
if (!demuxer_stream_is_selected(demux, stream)) {
|
|
||||||
talloc_free(pkt);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the packet has pointers to temporary fields that could be
|
|
||||||
// overwritten/freed by next av_read_frame(), copy them to persistent
|
|
||||||
// allocations so we can safely queue the packet for any length of time.
|
|
||||||
if (av_dup_packet(pkt) < 0)
|
|
||||||
abort();
|
|
||||||
dp = new_demux_packet_fromdata(pkt->data, pkt->size);
|
dp = new_demux_packet_fromdata(pkt->data, pkt->size);
|
||||||
dp->avpacket = pkt;
|
dp->avpacket = talloc_steal(dp, pkt);
|
||||||
|
|
||||||
int64_t ts = priv->use_dts ? pkt->dts : pkt->pts;
|
int64_t ts = priv->use_dts ? pkt->dts : pkt->pts;
|
||||||
if (ts != AV_NOPTS_VALUE) {
|
if (ts != AV_NOPTS_VALUE) {
|
||||||
@ -714,6 +788,8 @@ static void demux_seek_lavf(demuxer_t *demuxer, float rel_seek_secs,
|
|||||||
mp_msg(MSGT_DEMUX, MSGL_DBG2, "demux_seek_lavf(%p, %f, %f, %d)\n",
|
mp_msg(MSGT_DEMUX, MSGL_DBG2, "demux_seek_lavf(%p, %f, %f, %d)\n",
|
||||||
demuxer, rel_seek_secs, audio_delay, flags);
|
demuxer, rel_seek_secs, audio_delay, flags);
|
||||||
|
|
||||||
|
seek_reset(demuxer);
|
||||||
|
|
||||||
if (priv->seek_by_bytes) {
|
if (priv->seek_by_bytes) {
|
||||||
int64_t pos = demuxer->filepos;
|
int64_t pos = demuxer->filepos;
|
||||||
rel_seek_secs *= priv->bitrate / 8;
|
rel_seek_secs *= priv->bitrate / 8;
|
||||||
@ -877,6 +953,7 @@ redo:
|
|||||||
avio_flush(priv->avfc->pb);
|
avio_flush(priv->avfc->pb);
|
||||||
av_seek_frame(priv->avfc, 0, stream_tell(demuxer->stream),
|
av_seek_frame(priv->avfc, 0, stream_tell(demuxer->stream),
|
||||||
AVSEEK_FLAG_BYTE);
|
AVSEEK_FLAG_BYTE);
|
||||||
|
seek_reset(demuxer);
|
||||||
avio_flush(priv->avfc->pb);
|
avio_flush(priv->avfc->pb);
|
||||||
return DEMUXER_CTRL_OK;
|
return DEMUXER_CTRL_OK;
|
||||||
default:
|
default:
|
||||||
|
Loading…
Reference in New Issue
Block a user