demux: runtime option changing for cache and stream recording

Make most of the demuxer options runtime-changeable. This includes the
cache options and stream recording. The manpage documents some of the
possibly weird issues related to this.

In particular, the disk cache isn't shuffled around if the setting
changes at runtime.
This commit is contained in:
wm4 2019-06-30 20:09:27 +02:00
parent c942178c92
commit b945952e0d
2 changed files with 106 additions and 35 deletions

View File

@ -4004,6 +4004,12 @@ Cache
generally worthless after the media is closed, and it's hard to retrieve
any media data from it (it's not supported by design).
If the option is enabled at runtime, the cache file is created, but old data
will remain in the memory cache. If the option is disabled at runtime, old
data remains in the disk cache, and the cache file is not closed until the
media is closed. If the option is disabled and enabled again, it will
continue to use the cache file that was opened first.
``--cache-dir=<path>``
Directory where to create temporary files (default: none).
@ -5863,8 +5869,14 @@ Miscellaneous
``--stream-record=<file>``
Similar to ``--record-file``, but write packets as they are received. The
implementation of this does not tolerate seeks (outside of demuxer cache),
or streams being selected/deselected during recording. Can not be set at
runtime. Use with care.
or streams being selected/deselected during recording. Use with care.
If this is set at runtime, the old file is closed, and the new file is
opened. Note that this will write only data that is appended at the end of
the cache, and the already cached data cannot be written. (A fix for that
would be a command that dumps the cache using a given time range, possibly
with the option to be open-ended, which would continue to write data
appended to the cache. Such a command doesn't exist yet.)
``--lavfi-complex=<string>``
Set a "complex" libavfilter filter, which means a single filter graph can

View File

@ -149,8 +149,10 @@ const struct m_sub_options demux_conf = {
struct demux_internal {
struct mp_log *log;
struct mpv_global *global;
struct demux_opts *opts;
bool can_cache; // not a slave demuxer; caching makes sense
bool can_record; // stream recording is allowed
// The demuxer runs potentially in another thread, so we keep two demuxer
// structs; the real demuxer can access the shadow struct only.
@ -165,6 +167,9 @@ struct demux_internal {
// -- All the following fields are protected by lock.
struct demux_opts *opts;
struct m_config_cache *opts_cache;
bool thread_terminate;
bool threading;
bool shutdown_async;
@ -190,6 +195,8 @@ struct demux_internal {
size_t max_bytes;
size_t max_bytes_bw;
bool seekable_cache;
bool using_network_cache_opts;
char *record_filename;
// At least one decoder actually requested data since init or the last seek.
// Do this to allow the decoder thread to select streams before starting.
@ -1962,7 +1969,7 @@ static void add_packet_locked(struct sh_stream *stream, demux_packet_t *dp)
record_packet(in, dp);
if (in->cache) {
if (in->cache && in->opts->disk_cache) {
int64_t pos = demux_cache_write(in->cache, dp);
if (pos >= 0) {
demux_packet_unref_contents(dp);
@ -2327,9 +2334,73 @@ static void execute_seek(struct demux_internal *in)
in->seeking_in_progress = MP_NOPTS_VALUE;
}
static void update_opts(struct demux_internal *in)
{
struct demux_opts *opts = in->opts;
in->min_secs = opts->min_secs;
in->max_bytes = opts->max_bytes;
in->max_bytes_bw = opts->max_bytes_bw;
int seekable = opts->seekable_cache;
bool is_streaming = in->d_thread->is_network ||
(in->d_thread->stream && in->d_thread->stream->streaming);
bool use_cache = is_streaming;
if (opts->enable_cache >= 0)
use_cache = opts->enable_cache == 1;
if (use_cache) {
in->min_secs = MPMAX(in->min_secs, opts->min_secs_cache);
if (seekable < 0)
seekable = 1;
}
in->seekable_cache = seekable == 1;
in->using_network_cache_opts = is_streaming && use_cache;
if (!in->can_cache) {
in->seekable_cache = false;
in->min_secs = 0;
in->max_bytes = 1;
in->max_bytes_bw = 0;
in->using_network_cache_opts = false;
}
if (in->seekable_cache && opts->disk_cache && !in->cache) {
in->cache = demux_cache_create(in->global, in->log);
if (!in->cache)
MP_ERR(in, "Failed to create file cache.\n");
}
// The filename option really decides whether recording should be active.
// So if the filename changes, act upon it.
char *old = in->record_filename ? in->record_filename : "";
char *new = opts->record_file ? opts->record_file : "";
if (strcmp(old, new) != 0) {
if (in->recorder) {
MP_WARN(in, "Stopping recording.\n");
mp_recorder_destroy(in->recorder);
in->recorder = NULL;
}
in->record_filename = talloc_strdup(in, opts->record_file);
talloc_free(in->record_filename);
// Note: actual recording only starts once packets are read. It may be
// important to delay creating in->recorder to that point, because the
// demuxer might detect more streams until finding the first packet.
in->enable_recording = in->can_record;
}
// In case the cache was reduced in size.
prune_old_packets(in);
// In case the seekable cache was disabled.
free_empty_cached_ranges(in);
}
// Make demuxing progress. Return whether progress was made.
static bool thread_work(struct demux_internal *in)
{
if (m_config_cache_update(in->opts_cache))
update_opts(in);
if (in->tracks_switched) {
execute_trackswitch(in);
return true;
@ -2994,11 +3065,10 @@ static void demux_init_ccs(struct demuxer *demuxer, struct demux_opts *opts)
bool demux_is_network_cached(demuxer_t *demuxer)
{
struct demux_internal *in = demuxer->in;
bool use_cache = demuxer->is_network ||
(demuxer->stream && demuxer->stream->streaming);
if (in->opts->enable_cache >= 0)
use_cache = in->opts->enable_cache == 1;
return use_cache;
pthread_mutex_lock(&in->lock);
bool r = in->using_network_cache_opts;
pthread_mutex_unlock(&in->lock);
return r;
}
struct parent_stream_info {
@ -3020,7 +3090,9 @@ static struct demuxer *open_given_type(struct mpv_global *global,
return NULL;
struct demuxer *demuxer = talloc_ptrtype(NULL, demuxer);
struct demux_opts *opts = mp_get_config_group(demuxer, global, &demux_conf);
struct m_config_cache *opts_cache =
m_config_cache_alloc(demuxer, global, &demux_conf);
struct demux_opts *opts = opts_cache->opts;
*demuxer = (struct demuxer) {
.desc = desc,
.stream = stream,
@ -3039,19 +3111,19 @@ static struct demuxer *open_given_type(struct mpv_global *global,
struct demux_internal *in = demuxer->in = talloc_ptrtype(demuxer, in);
*in = (struct demux_internal){
.global = global,
.log = demuxer->log,
.can_cache = params && params->is_top_level,
.can_record = params && params->stream_record,
.opts = opts,
.opts_cache = opts_cache,
.d_thread = talloc(demuxer, struct demuxer),
.d_user = demuxer,
.min_secs = opts->min_secs,
.max_bytes = opts->max_bytes,
.max_bytes_bw = opts->max_bytes_bw,
.after_seek = true, // (assumed identical to initial demuxer state)
.after_seek_to_start = true,
.highest_av_pts = MP_NOPTS_VALUE,
.seeking_in_progress = MP_NOPTS_VALUE,
.demux_ts = MP_NOPTS_VALUE,
.enable_recording = params && params->stream_record,
};
pthread_mutex_init(&in->lock, NULL);
pthread_cond_init(&in->wakeup, NULL);
@ -3085,13 +3157,7 @@ static struct demuxer *open_given_type(struct mpv_global *global,
in->duration = in->d_thread->duration;
demuxer_sort_chapters(demuxer);
in->events = DEMUX_EVENT_ALL;
int seekable = opts->seekable_cache;
if (demux_is_network_cached(demuxer)) {
in->min_secs = MPMAX(in->min_secs, opts->min_secs_cache);
if (seekable < 0)
seekable = 1;
}
in->seekable_cache = seekable == 1;
struct demuxer *sub = NULL;
if (!(params && params->disable_timeline)) {
struct timeline *tl = timeline_load(global, log, demuxer);
@ -3103,26 +3169,19 @@ static struct demuxer *open_given_type(struct mpv_global *global,
sub =
open_given_type(global, log, &demuxer_desc_timeline,
NULL, sinfo, &params2, DEMUX_CHECK_FORCE);
if (!sub)
if (sub) {
in->can_cache = false;
in->can_record = false;
} else {
timeline_destroy(tl);
}
}
}
if (!(params && params->is_top_level) || sub) {
in->seekable_cache = false;
in->min_secs = 0;
in->max_bytes = 1;
in->enable_recording = false;
}
if (in->seekable_cache && opts->disk_cache) {
in->cache = demux_cache_create(global, log);
if (!in->cache)
MP_ERR(in, "Failed to create file cache.\n");
}
switch_to_fresh_cache_range(in);
update_opts(in);
demux_update(demuxer, MP_NOPTS_VALUE);
demuxer = sub ? sub : demuxer;