diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index dee217bcf2..4afe352271 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -5229,6 +5229,12 @@ Miscellaneous it to a template (similar to ``--screenshot-template``), being renamed, removed, or anything else, until it is declared semi-stable. +``--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. + ``--lavfi-complex=`` Set a "complex" libavfilter filter, which means a single filter graph can take input from multiple source audio and video tracks. The graph can result diff --git a/common/recorder.c b/common/recorder.c index 9970c05c32..6b233d41bb 100644 --- a/common/recorder.c +++ b/common/recorder.c @@ -335,8 +335,7 @@ void mp_recorder_mark_discontinuity(struct mp_recorder *priv) // mp_recorder_create(). struct mp_recorder_sink *mp_recorder_get_sink(struct mp_recorder *r, int stream) { - assert(stream >= 0 && stream < r->num_streams); - return r->streams[stream]; + return stream >= 0 && stream < r->num_streams ? r->streams[stream] : NULL; } // Pass a packet to the given stream. The function does not own the packet, but diff --git a/demux/demux.c b/demux/demux.c index 10dc1cb036..b3ab812892 100644 --- a/demux/demux.c +++ b/demux/demux.c @@ -34,6 +34,7 @@ #include "mpv_talloc.h" #include "common/msg.h" #include "common/global.h" +#include "common/recorder.h" #include "misc/thread_tools.h" #include "osdep/atomic.h" #include "osdep/timer.h" @@ -89,6 +90,7 @@ struct demux_opts { int access_references; int seekable_cache; int create_ccs; + char *record_file; }; #define OPT_BASE_STRUCT struct demux_opts @@ -110,6 +112,7 @@ const struct m_sub_options demux_conf = { OPT_CHOICE("demuxer-seekable-cache", seekable_cache, 0, ({"auto", -1}, {"no", 0}, {"yes", 1})), OPT_FLAG("sub-create-cc-track", create_ccs, 0), + OPT_STRING("stream-record", record_file, 0), {0} }, .size = sizeof(struct demux_opts), @@ -221,6 +224,10 @@ struct demux_internal { int64_t next_cache_update; // Updated during init only. char *stream_base_filename; + + // -- Access from demuxer thread only + bool enable_recording; + struct mp_recorder *recorder; }; // A continuous range of cached packets for all enabled streams. @@ -955,6 +962,11 @@ static void demux_shutdown(struct demux_internal *in) { struct demuxer *demuxer = in->d_user; + if (in->recorder) { + mp_recorder_destroy(in->recorder); + in->recorder = NULL; + } + if (demuxer->desc->close) demuxer->desc->close(in->d_thread); demuxer->priv = NULL; @@ -1501,6 +1513,33 @@ void demux_add_packet(struct sh_stream *stream, demux_packet_t *dp) } } + // (should preferable be outside of the lock) + if (in->enable_recording && !in->recorder && + in->opts->record_file && in->opts->record_file[0]) + { + // Later failures shouldn't make it retry and overwrite the previously + // recorded file. + in->enable_recording = false; + + in->recorder = + mp_recorder_create(in->d_thread->global, in->opts->record_file, + in->streams, in->num_streams); + if (!in->recorder) + MP_ERR(in, "Disabling recording.\n"); + } + + if (in->recorder) { + struct mp_recorder_sink *sink = + mp_recorder_get_sink(in->recorder, dp->stream); + if (sink) { + mp_recorder_feed_packet(sink, dp); + } else { + MP_ERR(in, "New stream appeared; stopping recording.\n"); + mp_recorder_destroy(in->recorder); + in->recorder = NULL; + } + } + wakeup_ds(ds); pthread_mutex_unlock(&in->lock); } @@ -2329,6 +2368,7 @@ static struct demuxer *open_given_type(struct mpv_global *global, .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); diff --git a/demux/demux.h b/demux/demux.h index 44567658fa..2fb3fbfb73 100644 --- a/demux/demux.h +++ b/demux/demux.h @@ -176,6 +176,7 @@ struct demuxer_params { bstr init_fragment; bool skip_lavf_probing; bool does_not_own_stream; // if false, stream is free'd on demux_free() + bool stream_record; // if true, enable stream recording if option is set // -- demux_open_url() only int stream_flags; // result diff --git a/player/loadfile.c b/player/loadfile.c index 2fb69eb635..18dce1dbc2 100644 --- a/player/loadfile.c +++ b/player/loadfile.c @@ -971,6 +971,7 @@ static void *open_demux_thread(void *ctx) struct demuxer_params p = { .force_format = mpctx->open_format, .stream_flags = mpctx->open_url_flags, + .stream_record = true, }; mpctx->open_res_demuxer = demux_open_url(mpctx->open_url, &p, mpctx->open_cancel, mpctx->global); @@ -1845,8 +1846,10 @@ void open_recorder(struct MPContext *mpctx, bool on_init) break; // (We expect track->stream not to be reused on other tracks.) if (track->stream == streams[n_stream]) { - set_track_recorder_sink(track, - mp_recorder_get_sink(mpctx->recorder, n_stream)); + struct mp_recorder_sink * sink = + mp_recorder_get_sink(mpctx->recorder, n_stream); + assert(sink); + set_track_recorder_sink(track, sink); n_stream++; } }