diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index 8439a00163..4900ccc0e8 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -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=`` Directory where to create temporary files (default: none). @@ -5863,8 +5869,14 @@ Miscellaneous ``--stream-record=`` 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=`` Set a "complex" libavfilter filter, which means a single filter graph can diff --git a/demux/demux.c b/demux/demux.c index f646158e22..d3bea4b841 100644 --- a/demux/demux.c +++ b/demux/demux.c @@ -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, ¶ms2, 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;