2019-10-02 22:22:18 +00:00
|
|
|
/*
|
|
|
|
* This file is part of mpv.
|
|
|
|
*
|
|
|
|
* mpv is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* mpv is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
#include <math.h>
|
|
|
|
#include <assert.h>
|
|
|
|
|
|
|
|
#include "common/common.h"
|
|
|
|
#include "common/msg.h"
|
|
|
|
|
|
|
|
#include "stream/stream.h"
|
|
|
|
#include "video/mp_image.h"
|
|
|
|
#include "demux.h"
|
|
|
|
#include "stheader.h"
|
|
|
|
|
|
|
|
#include "video/csputils.h"
|
|
|
|
|
|
|
|
struct priv {
|
|
|
|
struct demuxer *slave;
|
|
|
|
// streams[slave_stream_index] == our_stream
|
|
|
|
struct sh_stream **streams;
|
|
|
|
int num_streams;
|
|
|
|
// This contains each DVD sub stream, or NULL. Needed because DVD packets
|
|
|
|
// can come arbitrarily late in the MPEG stream, so the slave demuxer
|
|
|
|
// might add the streams only later.
|
|
|
|
struct sh_stream *dvd_subs[32];
|
|
|
|
// Used to rewrite the raw MPEG timestamps to playback time.
|
|
|
|
double base_time; // playback display start time of current segment
|
|
|
|
double base_dts; // packet DTS that maps to base_time
|
|
|
|
double last_dts; // DTS of previously demuxed packet
|
|
|
|
bool seek_reinit; // needs reinit after seek
|
|
|
|
|
|
|
|
bool is_dvd, is_cdda;
|
|
|
|
};
|
|
|
|
|
|
|
|
// If the timestamp difference between subsequent packets is this big, assume
|
|
|
|
// a reset. It should be big enough to account for 1. low video framerates and
|
|
|
|
// large audio frames, and 2. bad interleaving.
|
|
|
|
#define DTS_RESET_THRESHOLD 5.0
|
|
|
|
|
|
|
|
static void reselect_streams(demuxer_t *demuxer)
|
|
|
|
{
|
|
|
|
struct priv *p = demuxer->priv;
|
|
|
|
int num_slave = demux_get_num_stream(p->slave);
|
|
|
|
for (int n = 0; n < MPMIN(num_slave, p->num_streams); n++) {
|
|
|
|
if (p->streams[n]) {
|
|
|
|
demuxer_select_track(p->slave, demux_get_stream(p->slave, n),
|
2023-09-30 13:58:56 +00:00
|
|
|
MP_NOPTS_VALUE, demux_stream_is_selected(p->streams[n]));
|
2019-10-02 22:22:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void get_disc_lang(struct stream *stream, struct sh_stream *sh, bool dvd)
|
|
|
|
{
|
|
|
|
struct stream_lang_req req = {.type = sh->type, .id = sh->demuxer_id};
|
|
|
|
if (dvd && sh->type == STREAM_SUB)
|
|
|
|
req.id = req.id & 0x1F; // mpeg ID to index
|
|
|
|
stream_control(stream, STREAM_CTRL_GET_LANG, &req);
|
|
|
|
if (req.name[0])
|
|
|
|
sh->lang = talloc_strdup(sh, req.name);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void add_dvd_streams(demuxer_t *demuxer)
|
|
|
|
{
|
|
|
|
struct priv *p = demuxer->priv;
|
|
|
|
struct stream *stream = demuxer->stream;
|
|
|
|
if (!p->is_dvd)
|
|
|
|
return;
|
|
|
|
struct stream_dvd_info_req info;
|
|
|
|
if (stream_control(stream, STREAM_CTRL_GET_DVD_INFO, &info) > 0) {
|
|
|
|
for (int n = 0; n < MPMIN(32, info.num_subs); n++) {
|
|
|
|
struct sh_stream *sh = demux_alloc_sh_stream(STREAM_SUB);
|
|
|
|
sh->demuxer_id = n + 0x20;
|
|
|
|
sh->codec->codec = "dvd_subtitle";
|
|
|
|
get_disc_lang(stream, sh, true);
|
|
|
|
// p->streams _must_ match with p->slave->streams, so we can't add
|
|
|
|
// it yet - it has to be done when the real stream appears, which
|
|
|
|
// could be right on start, or any time later.
|
|
|
|
p->dvd_subs[n] = sh;
|
|
|
|
|
|
|
|
// emulate the extradata
|
|
|
|
struct mp_csp_params csp = MP_CSP_PARAMS_DEFAULTS;
|
|
|
|
struct mp_cmat cmatrix;
|
|
|
|
mp_get_csp_matrix(&csp, &cmatrix);
|
|
|
|
|
|
|
|
char *s = talloc_strdup(sh, "");
|
|
|
|
s = talloc_asprintf_append(s, "palette: ");
|
|
|
|
for (int i = 0; i < 16; i++) {
|
|
|
|
int color = info.palette[i];
|
|
|
|
int y[3] = {(color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff};
|
|
|
|
int c[3];
|
|
|
|
mp_map_fixp_color(&cmatrix, 8, y, 8, c);
|
|
|
|
color = (c[2] << 16) | (c[1] << 8) | c[0];
|
|
|
|
|
|
|
|
if (i != 0)
|
|
|
|
s = talloc_asprintf_append(s, ", ");
|
|
|
|
s = talloc_asprintf_append(s, "%06x", color);
|
|
|
|
}
|
|
|
|
s = talloc_asprintf_append(s, "\n");
|
|
|
|
|
|
|
|
sh->codec->extradata = s;
|
|
|
|
sh->codec->extradata_size = strlen(s);
|
|
|
|
|
|
|
|
demux_add_sh_stream(demuxer, sh);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void add_streams(demuxer_t *demuxer)
|
|
|
|
{
|
|
|
|
struct priv *p = demuxer->priv;
|
|
|
|
|
|
|
|
for (int n = p->num_streams; n < demux_get_num_stream(p->slave); n++) {
|
|
|
|
struct sh_stream *src = demux_get_stream(p->slave, n);
|
|
|
|
if (src->type == STREAM_SUB) {
|
|
|
|
struct sh_stream *sub = NULL;
|
|
|
|
if (src->demuxer_id >= 0x20 && src->demuxer_id <= 0x3F)
|
|
|
|
sub = p->dvd_subs[src->demuxer_id - 0x20];
|
|
|
|
if (sub) {
|
|
|
|
assert(p->num_streams == n); // directly mapped
|
|
|
|
MP_TARRAY_APPEND(p, p->streams, p->num_streams, sub);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
struct sh_stream *sh = demux_alloc_sh_stream(src->type);
|
|
|
|
assert(p->num_streams == n); // directly mapped
|
|
|
|
MP_TARRAY_APPEND(p, p->streams, p->num_streams, sh);
|
|
|
|
// Copy all stream fields that might be relevant
|
|
|
|
*sh->codec = *src->codec;
|
|
|
|
sh->demuxer_id = src->demuxer_id;
|
|
|
|
if (src->type == STREAM_VIDEO) {
|
|
|
|
double ar;
|
|
|
|
if (stream_control(demuxer->stream, STREAM_CTRL_GET_ASPECT_RATIO, &ar)
|
|
|
|
== STREAM_OK)
|
|
|
|
{
|
|
|
|
struct mp_image_params f = {.w = src->codec->disp_w,
|
|
|
|
.h = src->codec->disp_h};
|
|
|
|
mp_image_params_set_dsize(&f, 1728 * ar, 1728);
|
|
|
|
sh->codec->par_w = f.p_w;
|
|
|
|
sh->codec->par_h = f.p_h;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
get_disc_lang(demuxer->stream, sh, p->is_dvd);
|
|
|
|
demux_add_sh_stream(demuxer, sh);
|
|
|
|
}
|
|
|
|
reselect_streams(demuxer);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void d_seek(demuxer_t *demuxer, double seek_pts, int flags)
|
|
|
|
{
|
|
|
|
struct priv *p = demuxer->priv;
|
|
|
|
|
|
|
|
if (p->is_cdda) {
|
|
|
|
demux_seek(p->slave, seek_pts, flags);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (flags & SEEK_FACTOR) {
|
|
|
|
double tmp = 0;
|
|
|
|
stream_control(demuxer->stream, STREAM_CTRL_GET_TIME_LENGTH, &tmp);
|
|
|
|
seek_pts *= tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
MP_VERBOSE(demuxer, "seek to: %f\n", seek_pts);
|
|
|
|
|
|
|
|
// Supposed to induce a seek reset. Does it even work? I don't know.
|
|
|
|
// It will log some bogus error messages, since the demuxer will try a
|
|
|
|
// low level seek, which will obviously not work. But it will probably
|
|
|
|
// clear its internal buffers.
|
|
|
|
demux_seek(p->slave, 0, SEEK_FACTOR | SEEK_FORCE);
|
|
|
|
stream_drop_buffers(demuxer->stream);
|
|
|
|
|
|
|
|
double seek_arg[] = {seek_pts, flags};
|
|
|
|
stream_control(demuxer->stream, STREAM_CTRL_SEEK_TO_TIME, seek_arg);
|
|
|
|
|
|
|
|
p->seek_reinit = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void reset_pts(demuxer_t *demuxer)
|
|
|
|
{
|
|
|
|
struct priv *p = demuxer->priv;
|
|
|
|
|
|
|
|
double base;
|
|
|
|
if (stream_control(demuxer->stream, STREAM_CTRL_GET_CURRENT_TIME, &base) < 1)
|
|
|
|
base = 0;
|
|
|
|
|
|
|
|
MP_VERBOSE(demuxer, "reset to time: %f\n", base);
|
|
|
|
|
|
|
|
p->base_dts = p->last_dts = MP_NOPTS_VALUE;
|
|
|
|
p->base_time = base;
|
|
|
|
p->seek_reinit = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool d_read_packet(struct demuxer *demuxer, struct demux_packet **out_pkt)
|
|
|
|
{
|
|
|
|
struct priv *p = demuxer->priv;
|
|
|
|
|
|
|
|
struct demux_packet *pkt = demux_read_any_packet(p->slave);
|
|
|
|
if (!pkt)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
demux_update(p->slave, MP_NOPTS_VALUE);
|
|
|
|
|
|
|
|
if (p->seek_reinit)
|
|
|
|
reset_pts(demuxer);
|
|
|
|
|
|
|
|
add_streams(demuxer);
|
|
|
|
if (pkt->stream >= p->num_streams) { // out of memory?
|
|
|
|
talloc_free(pkt);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct sh_stream *sh = p->streams[pkt->stream];
|
|
|
|
if (!demux_stream_is_selected(sh)) {
|
|
|
|
talloc_free(pkt);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
pkt->stream = sh->index;
|
|
|
|
|
|
|
|
if (p->is_cdda) {
|
|
|
|
*out_pkt = pkt;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
MP_TRACE(demuxer, "ipts: %d %f %f\n", sh->type, pkt->pts, pkt->dts);
|
|
|
|
|
|
|
|
if (sh->type == STREAM_SUB) {
|
|
|
|
if (p->base_dts == MP_NOPTS_VALUE)
|
|
|
|
MP_WARN(demuxer, "subtitle packet along PTS reset\n");
|
|
|
|
} else if (pkt->dts != MP_NOPTS_VALUE) {
|
|
|
|
// Use the very first DTS to rebase the start time of the MPEG stream
|
|
|
|
// to the playback time.
|
|
|
|
if (p->base_dts == MP_NOPTS_VALUE)
|
|
|
|
p->base_dts = pkt->dts;
|
|
|
|
|
|
|
|
if (p->last_dts == MP_NOPTS_VALUE)
|
|
|
|
p->last_dts = pkt->dts;
|
|
|
|
|
|
|
|
if (fabs(p->last_dts - pkt->dts) >= DTS_RESET_THRESHOLD) {
|
|
|
|
MP_WARN(demuxer, "PTS discontinuity: %f->%f\n", p->last_dts, pkt->dts);
|
|
|
|
p->base_time += p->last_dts - p->base_dts;
|
|
|
|
p->base_dts = pkt->dts - pkt->duration;
|
|
|
|
}
|
|
|
|
p->last_dts = pkt->dts;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (p->base_dts != MP_NOPTS_VALUE) {
|
|
|
|
double delta = -p->base_dts + p->base_time;
|
|
|
|
if (pkt->pts != MP_NOPTS_VALUE)
|
|
|
|
pkt->pts += delta;
|
|
|
|
if (pkt->dts != MP_NOPTS_VALUE)
|
|
|
|
pkt->dts += delta;
|
|
|
|
}
|
|
|
|
|
|
|
|
MP_TRACE(demuxer, "opts: %d %f %f\n", sh->type, pkt->pts, pkt->dts);
|
|
|
|
|
|
|
|
*out_pkt = pkt;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void add_stream_chapters(struct demuxer *demuxer)
|
|
|
|
{
|
|
|
|
int num = 0;
|
|
|
|
if (stream_control(demuxer->stream, STREAM_CTRL_GET_NUM_CHAPTERS, &num) < 1)
|
|
|
|
return;
|
|
|
|
for (int n = 0; n < num; n++) {
|
|
|
|
double p = n;
|
|
|
|
if (stream_control(demuxer->stream, STREAM_CTRL_GET_CHAPTER_TIME, &p) < 1)
|
|
|
|
continue;
|
|
|
|
demuxer_add_chapter(demuxer, "", p, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int d_open(demuxer_t *demuxer, enum demux_check check)
|
|
|
|
{
|
|
|
|
struct priv *p = demuxer->priv = talloc_zero(demuxer, struct priv);
|
|
|
|
|
|
|
|
if (check != DEMUX_CHECK_FORCE)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
struct demuxer_params params = {
|
|
|
|
.force_format = "+lavf",
|
|
|
|
.external_stream = demuxer->stream,
|
stream, demux: redo origin policy thing
mpv has a very weak and very annoying policy that determines whether a
playlist should be used or not. For example, if you play a remote
playlist, you usually don't want it to be able to read local filesystem
entries. (Although for a media player the impact is small I guess.)
It's weak and annoying as in that it does not prevent certain cases
which could be interpreted as bad in some cases, such as allowing
playlists on the local filesystem to reference remote URLs. It probably
barely makes sense, but we just want to exclude some other "definitely
not a good idea" things, all while playlists generally just work, so
whatever.
The policy is:
- from the command line anything is played
- local playlists can reference anything except "unsafe" streams
("unsafe" means special stream inputs like libavfilter graphs)
- remote playlists can reference only remote URLs
- things like "memory://" and archives are "transparent" to this
This commit does... something. It replaces the weird stream flags with a
slightly clearer "origin" value, which is now consequently passed down
and used everywhere. It fixes some deviations from the described policy.
I wanted to force archives to reference only content within them, but
this would probably have been more complicated (or required different
abstractions), and I'm too lazy to figure it out, so archives are now
"transparent" (playlists within archives behave the same outside).
There may be a lot of bugs in this.
This is unfortunately a very noisy commit because:
- every stream open call now needs to pass the origin
- so does every demuxer open call (=> params param. gets mandatory)
- most stream were changed to provide the "origin" value
- the origin value needed to be passed along in a lot of places
- I was too lazy to split the commit
Fixes: #7274
2019-12-20 08:41:42 +00:00
|
|
|
.stream_flags = demuxer->stream_origin,
|
2019-10-02 22:22:18 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
struct stream *cur = demuxer->stream;
|
|
|
|
const char *sname = "";
|
|
|
|
if (cur->info)
|
|
|
|
sname = cur->info->name;
|
|
|
|
|
|
|
|
p->is_cdda = strcmp(sname, "cdda") == 0;
|
|
|
|
p->is_dvd = strcmp(sname, "dvd") == 0 ||
|
|
|
|
strcmp(sname, "ifo") == 0 ||
|
|
|
|
strcmp(sname, "dvdnav") == 0 ||
|
|
|
|
strcmp(sname, "ifo_dvdnav") == 0;
|
|
|
|
|
|
|
|
if (p->is_cdda)
|
|
|
|
params.force_format = "+rawaudio";
|
|
|
|
|
|
|
|
char *t = NULL;
|
|
|
|
stream_control(demuxer->stream, STREAM_CTRL_GET_DISC_NAME, &t);
|
|
|
|
if (t) {
|
|
|
|
mp_tags_set_str(demuxer->metadata, "TITLE", t);
|
|
|
|
talloc_free(t);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize the playback time. We need to read _some_ data to get the
|
|
|
|
// correct stream-layer time (at least with libdvdnav).
|
stream: turn into a ring buffer, make size configurable
In some corner cases (see #6802), it can be beneficial to use a larger
stream buffer size. Use this as argument to rewrite everything for no
reason.
Turn stream.c itself into a ring buffer, with configurable size. The
latter would have been easily achievable with minimal changes, and the
ring buffer is the hard part. There is no reason to have a ring buffer
at all, except possibly if ffmpeg don't fix their awful mp4 demuxer, and
some subtle issues with demux_mkv.c wanting to seek back by small
offsets (the latter was handled with small stream_peek() calls, which
are unneeded now).
In addition, this turns small forward seeks into reads (where data is
simply skipped). Before this commit, only stream_skip() did this (which
also mean that stream_skip() simply calls stream_seek() now).
Replace all stream_peek() calls with something else (usually
stream_read_peek()). The function was a problem, because it returned a
pointer to the internal buffer, which is now a ring buffer with
wrapping. The new function just copies the data into a buffer, and in
some cases requires callers to dynamically allocate memory. (The most
common case, demux_lavf.c, required a separate buffer allocation anyway
due to FFmpeg "idiosyncrasies".) This is the bulk of the demuxer_*
changes.
I'm not happy with this. There still isn't a good reason why there
should be a ring buffer, that is complex, and most of the time just
wastes half of the available memory. Maybe another rewrite soon.
It also contains bugs; you're an alpha tester now.
2019-11-06 20:36:02 +00:00
|
|
|
stream_read_peek(demuxer->stream, &(char){0}, 1);
|
2019-10-02 22:22:18 +00:00
|
|
|
reset_pts(demuxer);
|
|
|
|
|
|
|
|
p->slave = demux_open_url("-", ¶ms, demuxer->cancel, demuxer->global);
|
|
|
|
if (!p->slave)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
// Can be seekable even if the stream isn't.
|
|
|
|
demuxer->seekable = true;
|
|
|
|
|
|
|
|
add_dvd_streams(demuxer);
|
|
|
|
add_streams(demuxer);
|
|
|
|
add_stream_chapters(demuxer);
|
|
|
|
|
|
|
|
double len;
|
|
|
|
if (stream_control(demuxer->stream, STREAM_CTRL_GET_TIME_LENGTH, &len) >= 1)
|
|
|
|
demuxer->duration = len;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void d_close(demuxer_t *demuxer)
|
|
|
|
{
|
|
|
|
struct priv *p = demuxer->priv;
|
|
|
|
demux_free(p->slave);
|
|
|
|
}
|
|
|
|
|
|
|
|
const demuxer_desc_t demuxer_desc_disc = {
|
|
|
|
.name = "disc",
|
|
|
|
.desc = "CD/DVD/BD wrapper",
|
|
|
|
.read_packet = d_read_packet,
|
|
|
|
.open = d_open,
|
|
|
|
.close = d_close,
|
|
|
|
.seek = d_seek,
|
|
|
|
.switched_tracks = reselect_streams,
|
|
|
|
};
|