summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2017-02-07 17:05:17 +0100
committerwm4 <wm4@nowhere>2017-02-07 17:05:17 +0100
commit96a45a16af5594900ca94e7d4abb18d1e6d5ed4a (patch)
treed08473b07adfa7963ce6735c72af1e5d220930a9
parent061b752217d15d41496ca6e4777835fcd945e237 (diff)
downloadmpv-96a45a16af5594900ca94e7d4abb18d1e6d5ed4a.tar.bz2
mpv-96a45a16af5594900ca94e7d4abb18d1e6d5ed4a.tar.xz
player: add experimental stream recording feature
This is basically a WIP, but it can't remain in a branch forever. A warning is print when using it as it's still a bit "shaky".
-rw-r--r--DOCS/interface-changes.rst1
-rw-r--r--DOCS/man/options.rst23
-rw-r--r--audio/decode/dec_audio.c4
-rw-r--r--audio/decode/dec_audio.h2
-rw-r--r--common/recorder.c384
-rw-r--r--common/recorder.h21
-rw-r--r--options/options.c2
-rw-r--r--options/options.h1
-rw-r--r--player/command.c22
-rw-r--r--player/core.h8
-rw-r--r--player/loadfile.c93
-rw-r--r--player/playloop.c3
-rw-r--r--sub/dec_sub.c14
-rw-r--r--sub/dec_sub.h2
-rw-r--r--video/decode/dec_video.c19
-rw-r--r--video/decode/dec_video.h2
-rw-r--r--wscript_build.py1
17 files changed, 597 insertions, 5 deletions
diff --git a/DOCS/interface-changes.rst b/DOCS/interface-changes.rst
index c04b62a422..a032e7c721 100644
--- a/DOCS/interface-changes.rst
+++ b/DOCS/interface-changes.rst
@@ -32,6 +32,7 @@ Interface changes
- "vo-drop-frame-count" to "frame-drop-count"
The old names still work, but are deprecated.
- remove the --stream-capture option and property. No replacement.
+ (--stream-record might serve as alternative)
- add --sub-justify
- add --sub-ass-justify
- internally there's a different way to enable the demuxer cache now
diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst
index 13fd4351ca..afdf5b2dd1 100644
--- a/DOCS/man/options.rst
+++ b/DOCS/man/options.rst
@@ -4863,6 +4863,29 @@ Miscellaneous
This does not affect playlist expansion, redirection, or other loading of
referenced files like with ordered chapters.
+``--record-stream=<file>``
+ Record the current stream to the given target file. The target file will
+ always be overwritten without asking.
+
+ This remuxes the source stream without reencoding, which makes this a
+ highly fragile and experimental feature. It's entirely possible that this
+ writes files which are broken, not standards compliant, not playable with
+ all players (including mpv), or incomplete.
+
+ The target file format is determined by the file extension of the target
+ filename. It is recommended to use the same target container as the source
+ container if possible, and preferring Matroska as fallback.
+
+ Seeking during stream recording, or enabling/disabling stream recording
+ during playback, can cut off data, or produce "holes" in the output file.
+ These are technical restrictions. In particular, video data or subtitles
+ which were read ahead can produce such holes, which might cause playback
+ problems with various players (including mpv).
+
+ The behavior of this option might changed in the future, such as changing
+ it to a template (similar to ``--screenshot-template``), being renamed,
+ removed, or anything else, until it is declared semi-stable.
+
``--lavfi-complex=<string>``
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/audio/decode/dec_audio.c b/audio/decode/dec_audio.c
index 56f0fe59ad..5a2735ef20 100644
--- a/audio/decode/dec_audio.c
+++ b/audio/decode/dec_audio.c
@@ -27,6 +27,7 @@
#include "common/codecs.h"
#include "common/msg.h"
+#include "common/recorder.h"
#include "misc/bstr.h"
#include "stream/stream.h"
@@ -218,6 +219,9 @@ void audio_work(struct dec_audio *da)
}
if (da->ad_driver->send_packet(da, da->packet)) {
+ if (da->recorder_sink)
+ mp_recorder_feed_packet(da->recorder_sink, da->packet);
+
talloc_free(da->packet);
da->packet = NULL;
}
diff --git a/audio/decode/dec_audio.h b/audio/decode/dec_audio.h
index ebe7c8ae5b..02447d6742 100644
--- a/audio/decode/dec_audio.h
+++ b/audio/decode/dec_audio.h
@@ -37,6 +37,8 @@ struct dec_audio {
bool try_spdif;
+ struct mp_recorder_sink *recorder_sink;
+
// For free use by the ad_driver
void *priv;
diff --git a/common/recorder.c b/common/recorder.c
new file mode 100644
index 0000000000..d8f937b902
--- /dev/null
+++ b/common/recorder.c
@@ -0,0 +1,384 @@
+/*
+ * 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 <math.h>
+
+#include <libavformat/avformat.h>
+
+#include "common/av_common.h"
+#include "common/common.h"
+#include "common/global.h"
+#include "common/msg.h"
+#include "demux/packet.h"
+#include "demux/stheader.h"
+
+#include "recorder.h"
+
+#define PTS_ADD(a, b) ((a) == MP_NOPTS_VALUE ? (a) : ((a) + (b)))
+
+// Maximum number of packets we buffer at most to attempt to resync streams.
+// Essentially, this should be higher than the highest supported keyframe
+// interval.
+#define QUEUE_MAX_PACKETS 256
+// Number of packets we should buffer at least to determine timestamps (due to
+// codec delay and frame reordering, and potentially lack of DTS).
+// Keyframe flags can trigger this earlier.
+#define QUEUE_MIN_PACKETS 16
+
+struct mp_recorder {
+ struct mpv_global *global;
+ struct mp_log *log;
+
+ struct mp_recorder_sink **streams;
+ int num_streams;
+
+ bool opened; // mux context is valid
+ bool muxing; // we're currently recording (instead of preparing)
+ bool muxing_from_start; // no discontinuity at start
+ bool dts_warning;
+
+ // The start timestamp of the currently recorded segment (the timestamp of
+ // the first packet of the incoming packet stream).
+ double base_ts;
+ // The output packet timestamp corresponding to base_ts. It's the timestamp
+ // of the first packet of the current segment written to the output.
+ double rebase_ts;
+
+ AVFormatContext *mux;
+};
+
+struct mp_recorder_sink {
+ struct mp_recorder *owner;
+ struct sh_stream *sh;
+ AVStream *av_stream;
+ double max_out_pts;
+ bool discont;
+ bool proper_eof;
+ struct demux_packet **packets;
+ int num_packets;
+};
+
+static int add_stream(struct mp_recorder *priv, struct sh_stream *sh)
+{
+ enum AVMediaType av_type = mp_to_av_stream_type(sh->type);
+ if (av_type == AVMEDIA_TYPE_UNKNOWN)
+ return -1;
+
+ struct mp_recorder_sink *rst = talloc(priv, struct mp_recorder_sink);
+ *rst = (struct mp_recorder_sink) {
+ .owner = priv,
+ .sh = sh,
+ .av_stream = avformat_new_stream(priv->mux, NULL),
+ .max_out_pts = MP_NOPTS_VALUE,
+ };
+
+ if (!rst->av_stream)
+ return -1;
+
+ AVCodecParameters *avp = mp_codec_params_to_av(sh->codec);
+ if (!avp)
+ return -1;
+
+#if LIBAVCODEC_VERSION_MICRO >= 100
+ // We don't know the delay, so make something up. If the format requires
+ // DTS, the result will probably be broken. FFmpeg provides nothing better
+ // yet (unless you demux with libavformat, which contains tons of hacks
+ // that try to determine a PTS).
+ if (!sh->codec->lav_codecpar)
+ avp->video_delay = 16;
+#endif
+
+ if (avp->codec_id == AV_CODEC_ID_NONE)
+ return -1;
+
+ if (avcodec_parameters_copy(rst->av_stream->codecpar, avp) < 0)
+ return -1;
+
+ rst->av_stream->time_base = mp_get_codec_timebase(sh->codec);
+
+ MP_TARRAY_APPEND(priv, priv->streams, priv->num_streams, rst);
+ return 0;
+}
+
+struct mp_recorder *mp_recorder_create(struct mpv_global *global,
+ const char *target_file,
+ struct sh_stream **streams,
+ int num_streams)
+{
+ struct mp_recorder *priv = talloc_zero(NULL, struct mp_recorder);
+
+ priv->global = global;
+ priv->log = mp_log_new(priv, global->log, "recorder");
+
+ if (!num_streams) {
+ MP_ERR(priv, "No streams.\n");
+ goto error;
+ }
+
+ priv->mux = avformat_alloc_context();
+ if (!priv->mux)
+ goto error;
+
+ priv->mux->oformat = av_guess_format(NULL, target_file, NULL);
+ if (!priv->mux->oformat) {
+ MP_ERR(priv, "Output format not found.\n");
+ goto error;
+ }
+
+ if (avio_open2(&priv->mux->pb, target_file, AVIO_FLAG_WRITE, NULL, NULL) < 0) {
+ MP_ERR(priv, "Failed opening output file.\n");
+ goto error;
+ }
+
+ for (int n = 0; n < num_streams; n++) {
+ if (add_stream(priv, streams[n]) < 0) {
+ MP_ERR(priv, "Can't mux one of the input streams.\n");
+ goto error;
+ }
+ }
+
+ // Not sure how to write this in a "standard" way. It appears only mkv
+ // and mp4 support this directly.
+ char version[200];
+ snprintf(version, sizeof(version), "%s experimental stream recording "
+ "feature (can generate broken files - please report bugs)",
+ mpv_version);
+ av_dict_set(&priv->mux->metadata, "encoding_tool", version, 0);
+
+ if (avformat_write_header(priv->mux, NULL) < 0) {
+ MP_ERR(priv, "Write header failed.\n");
+ goto error;
+ }
+
+ priv->opened = true;
+ priv->muxing_from_start = true;
+
+ priv->base_ts = MP_NOPTS_VALUE;
+ priv->rebase_ts = 0;
+
+ MP_WARN(priv, "This is an experimental feature. Output files might be "
+ "broken or not play correctly with various players "
+ "(including mpv itself).\n");
+
+ return priv;
+
+error:
+ mp_recorder_destroy(priv);
+ return NULL;
+}
+
+static void flush_packets(struct mp_recorder *priv)
+{
+ for (int n = 0; n < priv->num_streams; n++) {
+ struct mp_recorder_sink *rst = priv->streams[n];
+ for (int i = 0; i < rst->num_packets; i++)
+ talloc_free(rst->packets[i]);
+ rst->num_packets = 0;
+ }
+}
+
+static void mux_packet(struct mp_recorder_sink *rst,
+ struct demux_packet *pkt)
+{
+ struct mp_recorder *priv = rst->owner;
+ struct demux_packet mpkt = *pkt;
+
+ double diff = priv->rebase_ts - priv->base_ts;
+ mpkt.pts = PTS_ADD(mpkt.pts, diff);
+ mpkt.dts = PTS_ADD(mpkt.dts, diff);
+
+ rst->max_out_pts = MPMAX(rst->max_out_pts, pkt->pts);
+
+ AVPacket avpkt;
+ mp_set_av_packet(&avpkt, &mpkt, &rst->av_stream->time_base);
+
+ avpkt.stream_index = rst->av_stream->index;
+
+ if (avpkt.duration < 0 && rst->sh->type != STREAM_SUB)
+ avpkt.duration = 0;
+
+ AVPacket *new_packet = av_packet_clone(&avpkt);
+ if (!new_packet) {
+ MP_ERR(priv, "Failed to allocate packet.\n");
+ return;
+ }
+
+ if (av_interleaved_write_frame(priv->mux, new_packet) < 0)
+ MP_ERR(priv, "Failed writing packet.\n");
+}
+
+// Write all packets that currently can be written.
+static void mux_packets(struct mp_recorder_sink *rst, bool force)
+{
+ struct mp_recorder *priv = rst->owner;
+ if (!priv->muxing || !rst->num_packets)
+ return;
+
+ int safe_count = 0;
+ for (int n = 0; n < rst->num_packets; n++) {
+ if (rst->packets[n]->keyframe)
+ safe_count = n;
+ }
+ if (force)
+ safe_count = rst->num_packets;
+
+ for (int n = 0; n < safe_count; n++) {
+ mux_packet(rst, rst->packets[n]);
+ talloc_free(rst->packets[n]);
+ }
+
+ // Remove packets[0..safe_count]
+ memmove(&rst->packets[0], &rst->packets[safe_count],
+ (rst->num_packets - safe_count) * sizeof(rst->packets[0]));
+ rst->num_packets -= safe_count;
+}
+
+// If there was a discontinuity, check whether we can resume muxing (and from
+// where).
+static void check_restart(struct mp_recorder *priv)
+{
+ if (priv->muxing)
+ return;
+
+ double min_ts = INFINITY;
+ double rebase_ts = 0;
+ for (int n = 0; n < priv->num_streams; n++) {
+ struct mp_recorder_sink *rst = priv->streams[n];
+ int min_packets = rst->sh->type == STREAM_VIDEO ? QUEUE_MIN_PACKETS : 1;
+
+ rebase_ts = MPMAX(rebase_ts, rst->max_out_pts);
+
+ if (rst->num_packets < min_packets) {
+ if (!rst->proper_eof && rst->sh->type != STREAM_SUB)
+ return;
+ continue;
+ }
+
+ for (int i = 0; i < min_packets; i++)
+ min_ts = MPMIN(min_ts, rst->packets[i]->pts);
+ }
+
+ // Subtitle only stream (wait longer) or stream without any PTS (fuck it).
+ if (!isfinite(min_ts))
+ return;
+
+ priv->rebase_ts = rebase_ts;
+ priv->base_ts = min_ts;
+
+ for (int n = 0; n < priv->num_streams; n++) {
+ struct mp_recorder_sink *rst = priv->streams[n];
+ rst->max_out_pts = min_ts;
+ }
+
+ priv->muxing = true;
+
+ if (!priv->muxing_from_start)
+ MP_WARN(priv, "Discontinuity at timestamp %f.\n", priv->rebase_ts);
+}
+
+void mp_recorder_destroy(struct mp_recorder *priv)
+{
+ if (priv->opened) {
+ for (int n = 0; n < priv->num_streams; n++) {
+ struct mp_recorder_sink *rst = priv->streams[n];
+ if (!rst->proper_eof)
+ continue;
+ mux_packets(rst, true);
+ }
+
+ if (av_write_trailer(priv->mux) < 0)
+ MP_ERR(priv, "Writing trailer failed.\n");
+ }
+
+ if (priv->mux) {
+ if (avio_closep(&priv->mux->pb) < 0)
+ MP_ERR(priv, "Closing file failed\n");
+
+ avformat_free_context(priv->mux);
+ }
+
+ flush_packets(priv);
+ talloc_free(priv);
+}
+
+// This is called on a seek, or when recording was started mid-stream.
+void mp_recorder_mark_discontinuity(struct mp_recorder *priv)
+{
+ flush_packets(priv);
+
+ for (int n = 0; n < priv->num_streams; n++) {
+ struct mp_recorder_sink *rst = priv->streams[n];
+ rst->discont = true;
+ rst->proper_eof = false;
+ }
+
+ priv->muxing = false;
+ priv->muxing_from_start = false;
+}
+
+// Get a stream for writing. The pointer is valid until mp_recorder is
+// destroyed. The stream is the index referencing the stream passed to
+// 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];
+}
+
+// Pass a packet to the given stream. The function does not own the packet, but
+// can create a new reference to it if it needs to retain it. Can be NULL to
+// signal proper end of stream.
+void mp_recorder_feed_packet(struct mp_recorder_sink *rst,
+ struct demux_packet *pkt)
+{
+ struct mp_recorder *priv = rst->owner;
+
+ if (!pkt) {
+ rst->proper_eof = true;
+ check_restart(priv);
+ mux_packets(rst, false);
+ return;
+ }
+
+ if (pkt->dts == MP_NOPTS_VALUE && !priv->dts_warning) {
+ // No, FFmpeg has no actually usable helpers to generate correct DTS.
+ // No, FFmpeg doesn't tell us which formats need DTS at all.
+ // No, we can not shut up the FFmpeg warning, which will follow.
+ MP_WARN(priv, "Source stream misses DTS on at least some packets!\n"
+ "If the target file format requires DTS, the written\n"
+ "file will be invalid.\n");
+ priv->dts_warning = true;
+ }
+
+ if (rst->discont && !pkt->keyframe)
+ return;
+ rst->discont = false;
+
+ if (rst->num_packets >= QUEUE_MAX_PACKETS) {
+ MP_ERR(priv, "Stream %d has too many queued packets; dropping.\n",
+ rst->av_stream->index);
+ return;
+ }
+
+ pkt = demux_copy_packet(pkt);
+ if (!pkt)
+ return;
+ MP_TARRAY_APPEND(rst, rst->packets, rst->num_packets, pkt);
+
+ check_restart(priv);
+ mux_packets(rst, false);
+}
diff --git a/common/recorder.h b/common/recorder.h
new file mode 100644
index 0000000000..a6c8635c01
--- /dev/null
+++ b/common/recorder.h
@@ -0,0 +1,21 @@
+#ifndef MP_RECORDER_H_
+#define MP_RECORDER_H_
+
+struct mp_recorder;
+struct mpv_global;
+struct demux_packet;
+struct sh_stream;
+struct mp_recorder_sink;
+
+struct mp_recorder *mp_recorder_create(struct mpv_global *global,
+ const char *target_file,
+ struct sh_stream **streams,
+ int num_streams);
+void mp_recorder_destroy(struct mp_recorder *r);
+void mp_recorder_mark_discontinuity(struct mp_recorder *r);
+
+struct mp_recorder_sink *mp_recorder_get_sink(struct mp_recorder *r, int stream);
+void mp_recorder_feed_packet(struct mp_recorder_sink *s,
+ struct demux_packet *pkt);
+
+#endif
diff --git a/options/options.c b/options/options.c
index 5166ef6940..b711156c18 100644
--- a/options/options.c
+++ b/options/options.c
@@ -681,6 +681,8 @@ const m_option_t mp_opts[] = {
OPT_STRING("screenshot-template", screenshot_template, 0),
OPT_STRING("screenshot-directory", screenshot_directory, 0),
+ OPT_STRING("record-file", record_file, M_OPT_FILE),
+
OPT_SUBSTRUCT("", input_opts, input_config, 0),
OPT_PRINT("list-protocols", stream_print_proto_list),
diff --git a/options/options.h b/options/options.h
index 82a8f92d48..f465c0f862 100644
--- a/options/options.h
+++ b/options/options.h
@@ -138,6 +138,7 @@ typedef struct MPOpts {
int untimed;
char *stream_dump;
+ char *record_file;
int stop_playback_on_init_failure;
int loop_times;
int loop_file;
diff --git a/player/command.c b/player/command.c
index 6d335e535a..cde67ebe51 100644
--- a/player/command.c
+++ b/player/command.c
@@ -3549,6 +3549,26 @@ static int mp_property_cwd(void *ctx, struct m_property *prop,
return M_PROPERTY_NOT_IMPLEMENTED;
}
+static int mp_property_record_file(void *ctx, struct m_property *prop,
+ int action, void *arg)
+{
+ struct MPContext *mpctx = ctx;
+ struct MPOpts *opts = mpctx->opts;
+ if (action == M_PROPERTY_SET) {
+ char *new = *(char **)arg;
+ if (!bstr_equals(bstr0(new), bstr0(opts->record_file))) {
+ talloc_free(opts->record_file);
+ opts->record_file = talloc_strdup(NULL, new);
+ open_recorder(mpctx, false);
+ // open_recorder() unsets it on failure.
+ if (new && !opts->record_file)
+ return M_PROPERTY_ERROR;
+ }
+ return M_PROPERTY_OK;
+ }
+ return mp_property_generic_option(mpctx, prop, action, arg);
+}
+
static int mp_property_protocols(void *ctx, struct m_property *prop,
int action, void *arg)
{
@@ -4020,6 +4040,8 @@ static const struct m_property mp_properties_base[] = {
{"working-directory", mp_property_cwd},
+ {"record-file", mp_property_record_file},
+
{"protocol-list", mp_property_protocols},
{"decoder-list", mp_property_decoders},
{"encoder-list", mp_property_encoders},
diff --git a/player/core.h b/player/core.h
index 4767830bc9..79120decd3 100644
--- a/player/core.h
+++ b/player/core.h
@@ -153,6 +153,9 @@ struct track {
struct vo_chain *vo_c;
struct ao_chain *ao_c;
struct lavfi_pad *sink;
+
+ // For stream recording (remuxing mode).
+ struct mp_recorder_sink *remux_sink;
};
// Summarizes video filtering and output.
@@ -421,6 +424,8 @@ typedef struct MPContext {
// playback rate. Used to avoid showing it multiple times.
bool drop_message_shown;
+ struct mp_recorder *recorder;
+
char *cached_watch_later_configdir;
struct screenshot_ctx *screenshot_ctx;
@@ -506,6 +511,9 @@ void autoload_external_files(struct MPContext *mpctx);
struct track *select_default_track(struct MPContext *mpctx, int order,
enum stream_type type);
void prefetch_next(struct MPContext *mpctx);
+void close_recorder(struct MPContext *mpctx);
+void close_recorder_and_error(struct MPContext *mpctx);
+void open_recorder(struct MPContext *mpctx, bool on_init);
// main.c
int mp_initialize(struct MPContext *mpctx, char **argv);
diff --git a/player/loadfile.c b/player/loadfile.c
index 524ff8f1b2..dba02ee828 100644
--- a/player/loadfile.c
+++ b/player/loadfile.c
@@ -40,6 +40,7 @@
#include "options/m_property.h"
#include "common/common.h"
#include "common/encode.h"
+#include "common/recorder.h"
#include "input/input.h"
#include "audio/audio.h"
@@ -469,6 +470,8 @@ void mp_switch_track_n(struct MPContext *mpctx, int order, enum stream_type type
uninit_sub(mpctx, current);
if (current) {
+ if (current->remux_sink)
+ close_recorder_and_error(mpctx);
current->selected = false;
reselect_demux_stream(mpctx, current);
}
@@ -1256,6 +1259,8 @@ reopen_file:
if (mpctx->opts->pause)
pause_player(mpctx);
+ open_recorder(mpctx, true);
+
playback_start = mp_time_sec();
mpctx->error_playing = 0;
while (!mpctx->stop_play)
@@ -1278,6 +1283,8 @@ terminate_playback:
mp_abort_playback_async(mpctx);
+ close_recorder(mpctx);
+
// time to uninit all, except global stuff:
uninit_complex_filters(mpctx);
uninit_audio_chain(mpctx);
@@ -1449,3 +1456,89 @@ void mp_set_playlist_entry(struct MPContext *mpctx, struct playlist_entry *e)
mpctx->stop_play = PT_CURRENT_ENTRY;
mp_wakeup_core(mpctx);
}
+
+static void set_track_recorder_sink(struct track *track,
+ struct mp_recorder_sink *sink)
+{
+ if (track->d_sub)
+ sub_set_recorder_sink(track->d_sub, sink);
+ if (track->d_video)
+ track->d_video->recorder_sink = sink;
+ if (track->d_audio)
+ track->d_audio->recorder_sink = sink;
+ track->remux_sink = sink;
+}
+
+void close_recorder(struct MPContext *mpctx)
+{
+ if (!mpctx->recorder)
+ return;
+
+ for (int n = 0; n < mpctx->num_tracks; n++)
+ set_track_recorder_sink(mpctx->tracks[n], NULL);
+
+ mp_recorder_destroy(mpctx->recorder);
+ mpctx->recorder = NULL;
+}
+
+// Like close_recorder(), but also unset the option. Intended for use on errors.
+void close_recorder_and_error(struct MPContext *mpctx)
+{
+ close_recorder(mpctx);
+ talloc_free(mpctx->opts->record_file);
+ mpctx->opts->record_file = NULL;
+ mp_notify_property(mpctx, "record-file");
+ MP_ERR(mpctx, "Disabling stream recording.\n");
+}
+
+void open_recorder(struct MPContext *mpctx, bool on_init)
+{
+ if (!mpctx->playback_initialized)
+ return;
+
+ close_recorder(mpctx);
+
+ char *target = mpctx->opts->record_file;
+ if (!target || !target[0])
+ return;
+
+ struct sh_stream **streams = NULL;
+ int num_streams = 0;
+
+ for (int n = 0; n < mpctx->num_tracks; n++) {
+ struct track *track = mpctx->tracks[n];
+ if (track->stream && track->selected &&
+ (track->d_sub || track->d_video || track->d_audio))
+ {
+ MP_TARRAY_APPEND(NULL, streams, num_streams, track->stream);
+ }
+ }
+
+ mpctx->recorder = mp_recorder_create(mpctx->global, mpctx->opts->record_file,
+ streams, num_streams);
+
+ if (!mpctx->recorder) {
+ talloc_free(streams);
+ close_recorder_and_error(mpctx);
+ return;
+ }
+
+ if (!on_init)
+ mp_recorder_mark_discontinuity(mpctx->recorder);
+
+ int n_stream = 0;
+ for (int n = 0; n < mpctx->num_tracks; n++) {
+ struct track *track = mpctx->tracks[n];
+ if (n_stream >= num_streams)
+ 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));
+ n_stream++;
+ }
+ }
+
+ talloc_free(streams);
+}
+
diff --git a/player/playloop.c b/player/playloop.c
index 232a75f814..e73ad61788 100644
--- a/player/playloop.c
+++ b/player/playloop.c
@@ -28,6 +28,7 @@
#include "options/options.h"
#include "common/common.h"
#include "common/encode.h"
+#include "common/recorder.h"
#include "options/m_config.h"
#include "options/m_property.h"
#include "common/playlist.h"
@@ -330,6 +331,8 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek)
clear_audio_output_buffers(mpctx);
reset_playback_state(mpctx);
+ if (mpctx->recorder)
+ mp_recorder_mark_discontinuity(mpctx->recorder);
/* Use the target time as "current position" for further relative
* seeks etc until a new video frame has been decoded */
diff --git a/sub/dec_sub.c b/sub/dec_sub.c
index b9f04b3123..743a06ed14 100644
--- a/sub/dec_sub.c
+++ b/sub/dec_sub.c
@@ -29,6 +29,7 @@
#include "options/options.h"
#include "common/global.h"
#include "common/msg.h"
+#include "common/recorder.h"
#include "osdep/threads.h"
extern const struct sd_functions sd_ass;
@@ -49,6 +50,8 @@ struct dec_sub {
struct mpv_global *global;
struct MPOpts *opts;
+ struct mp_recorder_sink *recorder_sink;
+
struct attachment_list *attachments;
struct sh_stream *sh;
@@ -240,6 +243,9 @@ bool sub_read_packets(struct dec_sub *sub, double video_pts)
break;
}
+ if (sub->recorder_sink)
+ mp_recorder_feed_packet(sub->recorder_sink, pkt);
+
sub->last_pkt_pts = pkt->pts;
if (is_new_segment(sub, pkt)) {
@@ -323,3 +329,11 @@ int sub_control(struct dec_sub *sub, enum sd_ctrl cmd, void *arg)
pthread_mutex_unlock(&sub->lock);
return r;
}
+
+void sub_set_recorder_sink(struct dec_sub *sub, struct mp_recorder_sink *sink)
+{
+ pthread_mutex_lock(&sub->lock);
+ sub->recorder_sink = sink;
+ pthread_mutex_unlock(&sub->lock);
+}
+
diff --git a/sub/dec_sub.h b/sub/dec_sub.h
index bdf6e35492..26781fd99f 100644
--- a/sub/dec_sub.h
+++ b/sub/dec_sub.h
@@ -9,6 +9,7 @@
struct sh_stream;
struct mpv_global;
struct demux_packet;
+struct mp_recorder_sink;
struct dec_sub;
struct sd;
@@ -40,6 +41,7 @@ void sub_get_bitmaps(struct dec_sub *sub, struct mp_osd_res dim, int format,
char *sub_get_text(struct dec_sub *sub, double pts);
void sub_reset(struct dec_sub *sub);
void sub_select(struct dec_sub *sub, bool selected);
+void sub_set_recorder_sink(struct dec_sub *sub, struct mp_recorder_sink *sink);
int sub_control(struct dec_sub *sub, enum sd_ctrl cmd, void *arg);
diff --git a/video/decode/dec_video.c b/video/decode/dec_video.c
index 273b80c3c9..23aba81709 100644
--- a/video/decode/dec_video.c
+++ b/video/decode/dec_video.c
@@ -33,6 +33,7 @@
#include "demux/packet.h"
#include "common/codecs.h"
+#include "common/recorder.h"
#include "video/out/vo.h"
#include "video/csputils.h"
@@ -259,6 +260,12 @@ static bool send_packet(struct dec_video *d_video, struct demux_packet *packet)
if (pkt_pts == MP_NOPTS_VALUE)
d_video->has_broken_packet_pts = 1;
+ bool dts_replaced = false;
+ if (packet && packet->dts == MP_NOPTS_VALUE && !d_video->codec->avi_dts) {
+ packet->dts = packet->pts;
+ dts_replaced = true;
+ }
+
double pkt_pdts = pkt_pts == MP_NOPTS_VALUE ? pkt_dts : pkt_pts;
if (pkt_pdts != MP_NOPTS_VALUE && d_video->first_packet_pdts == MP_NOPTS_VALUE)
d_video->first_packet_pdts = pkt_pdts;
@@ -269,6 +276,10 @@ static bool send_packet(struct dec_video *d_video, struct demux_packet *packet)
MP_STATS(d_video, "end decode video");
+ // Stream recording can't deal with almost surely wrong fake DTS.
+ if (dts_replaced)
+ packet->dts = MP_NOPTS_VALUE;
+
return res;
}
@@ -396,11 +407,6 @@ void video_work(struct dec_video *d_video)
return;
}
- if (d_video->packet) {
- if (d_video->packet->dts == MP_NOPTS_VALUE && !d_video->codec->avi_dts)
- d_video->packet->dts = d_video->packet->pts;
- }
-
if (d_video->packet && d_video->packet->new_segment) {
assert(!d_video->new_segment);
d_video->new_segment = d_video->packet;
@@ -423,6 +429,9 @@ void video_work(struct dec_video *d_video)
d_video->vd_driver->control(d_video, VDCTRL_SET_FRAMEDROP, &framedrop_type);
if (send_packet(d_video, d_video->packet)) {
+ if (d_video->recorder_sink)
+ mp_recorder_feed_packet(d_video->recorder_sink, d_video->packet);
+
talloc_free(d_video->packet);
d_video->packet = NULL;
}
diff --git a/video/decode/dec_video.h b/video/decode/dec_video.h
index 9155a76155..5ef1f9252a 100644
--- a/video/decode/dec_video.h
+++ b/video/decode/dec_video.h
@@ -42,6 +42,8 @@ struct dec_video {
int dropped_frames;
+ struct mp_recorder_sink *recorder_sink;
+
// Internal (shared with vd_lavc.c).