summaryrefslogtreecommitdiffstats
path: root/common
diff options
context:
space:
mode:
Diffstat (limited to 'common')
-rw-r--r--common/av_common.c128
-rw-r--r--common/av_common.h5
-rw-r--r--common/av_log.c8
-rw-r--r--common/codecs.c69
-rw-r--r--common/encode_lavc.c2
-rw-r--r--common/recorder.c384
-rw-r--r--common/recorder.h21
7 files changed, 505 insertions, 112 deletions
diff --git a/common/av_common.c b/common/av_common.c
index f2f43498e3..5c58f3fea8 100644
--- a/common/av_common.c
+++ b/common/av_common.c
@@ -33,6 +33,7 @@
#include "common/msg.h"
#include "demux/packet.h"
#include "demux/stheader.h"
+#include "video/fmt-conversion.h"
#include "av_common.h"
#include "codecs.h"
@@ -41,7 +42,7 @@ int mp_lavc_set_extradata(AVCodecContext *avctx, void *ptr, int size)
if (size) {
av_free(avctx->extradata);
avctx->extradata_size = 0;
- avctx->extradata = av_mallocz(size + FF_INPUT_BUFFER_PADDING_SIZE);
+ avctx->extradata = av_mallocz(size + AV_INPUT_BUFFER_PADDING_SIZE);
if (!avctx->extradata)
return -1;
avctx->extradata_size = size;
@@ -50,35 +51,81 @@ int mp_lavc_set_extradata(AVCodecContext *avctx, void *ptr, int size)
return 0;
}
-// Copy the codec-related fields from st into avctx. This does not set the
-// codec itself, only codec related header data provided by libavformat.
-// The goal is to initialize a new decoder with the header data provided by
-// libavformat, and unlike avcodec_copy_context(), allow the user to create
-// a clean AVCodecContext for a manually selected AVCodec.
-// This is strictly for decoding only.
-void mp_copy_lav_codec_headers(AVCodecContext *avctx, AVCodecContext *st)
+enum AVMediaType mp_to_av_stream_type(int type)
{
- mp_lavc_set_extradata(avctx, st->extradata, st->extradata_size);
- avctx->codec_tag = st->codec_tag;
- avctx->bit_rate = st->bit_rate;
- avctx->width = st->width;
- avctx->height = st->height;
- avctx->pix_fmt = st->pix_fmt;
- avctx->chroma_sample_location = st->chroma_sample_location;
- avctx->sample_rate = st->sample_rate;
- avctx->channels = st->channels;
- avctx->block_align = st->block_align;
- avctx->channel_layout = st->channel_layout;
- avctx->bits_per_coded_sample = st->bits_per_coded_sample;
- avctx->has_b_frames = st->has_b_frames;
+ switch (type) {
+ case STREAM_VIDEO: return AVMEDIA_TYPE_VIDEO;
+ case STREAM_AUDIO: return AVMEDIA_TYPE_AUDIO;
+ case STREAM_SUB: return AVMEDIA_TYPE_SUBTITLE;
+ default: return AVMEDIA_TYPE_UNKNOWN;
+ }
}
-// This only copies ffmpeg-native codec parameters. Parameters produced by
-// other demuxers must be handled manually.
-void mp_set_lav_codec_headers(AVCodecContext *avctx, struct mp_codec_params *c)
+AVCodecParameters *mp_codec_params_to_av(struct mp_codec_params *c)
{
- if (c->lav_codecpar)
- avcodec_parameters_to_context(avctx, c->lav_codecpar);
+ AVCodecParameters *avp = avcodec_parameters_alloc();
+ if (!avp)
+ return NULL;
+
+ // If we have lavf demuxer params, they overwrite by definition any others.
+ if (c->lav_codecpar) {
+ if (avcodec_parameters_copy(avp, c->lav_codecpar) < 0)
+ goto error;
+ return avp;
+ }
+
+ avp->codec_type = mp_to_av_stream_type(c->type);
+ avp->codec_id = mp_codec_to_av_codec_id(c->codec);
+ avp->codec_tag = c->codec_tag;
+ if (c->extradata_size) {
+ avp->extradata =
+ av_mallocz(c->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
+ if (!avp->extradata)
+ goto error;
+ avp->extradata_size = c->extradata_size;
+ memcpy(avp->extradata, c->extradata, avp->extradata_size);
+ }
+ avp->bits_per_coded_sample = c->bits_per_coded_sample;
+
+ // Video only
+ avp->width = c->disp_w;
+ avp->height = c->disp_h;
+ if (c->codec && strcmp(c->codec, "mp-rawvideo") == 0) {
+ avp->format = imgfmt2pixfmt(c->codec_tag);
+ avp->codec_tag = 0;
+ }
+
+ // Audio only
+ avp->sample_rate = c->samplerate;
+ avp->bit_rate = c->bitrate;
+ avp->block_align = c->block_align;
+ avp->channels = c->channels.num;
+ if (!mp_chmap_is_unknown(&c->channels))
+ avp->channel_layout = mp_chmap_to_lavc(&c->channels);
+
+ return avp;
+error:
+ avcodec_parameters_free(&avp);
+ return NULL;
+}
+
+// Set avctx codec headers for decoding. Returns <0 on failure.
+int mp_set_avctx_codec_headers(AVCodecContext *avctx, struct mp_codec_params *c)
+{
+ enum AVMediaType codec_type = avctx->codec_type;
+ enum AVCodecID codec_id = avctx->codec_id;
+ AVCodecParameters *avp = mp_codec_params_to_av(c);
+ if (!avp)
+ return -1;
+
+ int r = avcodec_parameters_to_context(avctx, avp) < 0 ? -1 : 0;
+ avcodec_parameters_free(&avp);
+
+ if (avctx->codec_type != AVMEDIA_TYPE_UNKNOWN)
+ avctx->codec_type = codec_type;
+ if (avctx->codec_id != AV_CODEC_ID_NONE)
+ avctx->codec_id = codec_id;
+ return r;
}
// Pick a "good" timebase, which will be used to convert double timestamps
@@ -108,37 +155,24 @@ AVRational mp_get_codec_timebase(struct mp_codec_params *c)
return tb;
}
-// We merely pass-through our PTS/DTS as an int64_t; libavcodec won't use it.
-union pts { int64_t i; double d; };
+static AVRational get_def_tb(AVRational *tb)
+{
+ return tb && tb->num > 0 && tb->den > 0 ? *tb : AV_TIME_BASE_Q;
+}
// Convert the mpv style timestamp (seconds as double) to a libavcodec style
// timestamp (integer units in a given timebase).
-//
-// If the given timebase is NULL or invalid, pass through the mpv timestamp by
-// reinterpret casting them to int64_t. In this case, the timestamps will be
-// non-sense for libavcodec, but we expect that it doesn't interpret them,
-// and treats them as opaque.
int64_t mp_pts_to_av(double mp_pts, AVRational *tb)
{
- assert(sizeof(int64_t) >= sizeof(double));
- if (tb && tb->num > 0 && tb->den > 0) {
- return mp_pts == MP_NOPTS_VALUE ?
- AV_NOPTS_VALUE : llrint(mp_pts / av_q2d(*tb));
- }
- // The + 0.0 is to squash possible negative zero mp_pts, which would
- // happen to end up as AV_NOPTS_VALUE.
- return (union pts){.d = mp_pts + 0.0}.i;
+ AVRational b = get_def_tb(tb);
+ return mp_pts == MP_NOPTS_VALUE ? AV_NOPTS_VALUE : llrint(mp_pts / av_q2d(b));
}
// Inverse of mp_pts_to_av(). (The timebases must be exactly the same.)
double mp_pts_from_av(int64_t av_pts, AVRational *tb)
{
- assert(sizeof(int64_t) >= sizeof(double));
- if (tb && tb->num > 0 && tb->den > 0)
- return av_pts == AV_NOPTS_VALUE ? MP_NOPTS_VALUE : av_pts * av_q2d(*tb);
- // Should libavcodec set the PTS to AV_NOPTS_VALUE, it would end up as
- // non-sense (usually negative zero) when unwrapped to double.
- return av_pts == AV_NOPTS_VALUE ? MP_NOPTS_VALUE : (union pts){.i = av_pts}.d;
+ AVRational b = get_def_tb(tb);
+ return av_pts == AV_NOPTS_VALUE ? MP_NOPTS_VALUE : av_pts * av_q2d(b);
}
// Set dst from mpkt. Note that dst is not refcountable.
diff --git a/common/av_common.h b/common/av_common.h
index 4b13dcdd0c..1d30fab71f 100644
--- a/common/av_common.h
+++ b/common/av_common.h
@@ -31,8 +31,9 @@ struct AVDictionary;
struct mp_log;
int mp_lavc_set_extradata(AVCodecContext *avctx, void *ptr, int size);
-void mp_copy_lav_codec_headers(AVCodecContext *avctx, AVCodecContext *st);
-void mp_set_lav_codec_headers(AVCodecContext *avctx, struct mp_codec_params *c);
+enum AVMediaType mp_to_av_stream_type(int type);
+AVCodecParameters *mp_codec_params_to_av(struct mp_codec_params *c);
+int mp_set_avctx_codec_headers(AVCodecContext *avctx, struct mp_codec_params *c);
AVRational mp_get_codec_timebase(struct mp_codec_params *c);
void mp_set_av_packet(AVPacket *dst, struct demux_packet *mpkt, AVRational *tb);
int64_t mp_pts_to_av(double mp_pts, AVRational *tb);
diff --git a/common/av_log.c b/common/av_log.c
index e2a4c3316e..c779fe9c8d 100644
--- a/common/av_log.c
+++ b/common/av_log.c
@@ -42,10 +42,10 @@
#include <libavdevice/avdevice.h>
#endif
-#if HAVE_LIBAVRESAMPLE
+#if HAVE_IS_LIBAV
#include <libavresample/avresample.h>
#endif
-#if HAVE_LIBSWRESAMPLE
+#if HAVE_IS_FFMPEG
#include <libswresample/swresample.h>
#endif
@@ -197,10 +197,10 @@ bool print_libav_versions(struct mp_log *log, int v)
{"libavformat", LIBAVFORMAT_VERSION_INT, avformat_version()},
{"libswscale", LIBSWSCALE_VERSION_INT, swscale_version()},
{"libavfilter", LIBAVFILTER_VERSION_INT, avfilter_version()},
-#if HAVE_LIBAVRESAMPLE
+#if HAVE_IS_LIBAV
{"libavresample", LIBAVRESAMPLE_VERSION_INT, avresample_version()},
#endif
-#if HAVE_LIBSWRESAMPLE
+#if HAVE_IS_FFMPEG
{"libswresample", LIBSWRESAMPLE_VERSION_INT, swresample_version()},
#endif
};
diff --git a/common/codecs.c b/common/codecs.c
index 5e744da5de..7985501869 100644
--- a/common/codecs.c
+++ b/common/codecs.c
@@ -33,26 +33,6 @@ void mp_add_decoder(struct mp_decoder_list *list, const char *family,
MP_TARRAY_APPEND(list, list->entries, list->num_entries, entry);
}
-static void mp_add_decoder_entry(struct mp_decoder_list *list,
- struct mp_decoder_entry *entry)
-{
- mp_add_decoder(list, entry->family, entry->codec, entry->decoder,
- entry->desc);
-}
-
-static struct mp_decoder_entry *find_decoder(struct mp_decoder_list *list,
- bstr family, bstr decoder)
-{
- for (int n = 0; n < list->num_entries; n++) {
- struct mp_decoder_entry *cur = &list->entries[n];
- if (bstr_equals0(decoder, cur->decoder)) {
- if (bstr_equals0(family, "*") || bstr_equals0(family, cur->family))
- return cur;
- }
- }
- return NULL;
-}
-
// Add entry, but only if it's not yet on the list, and if the codec matches.
// If codec == NULL, don't compare codecs.
static void add_new(struct mp_decoder_list *to, struct mp_decoder_entry *entry,
@@ -60,8 +40,7 @@ static void add_new(struct mp_decoder_list *to, struct mp_decoder_entry *entry,
{
if (!entry || (codec && strcmp(entry->codec, codec) != 0))
return;
- if (!find_decoder(to, bstr0(entry->family), bstr0(entry->decoder)))
- mp_add_decoder_entry(to, entry);
+ mp_add_decoder(to, entry->family, entry->codec, entry->decoder, entry->desc);
}
// Select a decoder from the given list for the given codec. The selection
@@ -71,10 +50,8 @@ static void add_new(struct mp_decoder_list *to, struct mp_decoder_entry *entry,
// The selection string corresponds to --vd/--ad directly, and has the
// following syntax:
// selection = [<entry> ("," <entry>)*]
-// entry = [<family> ":"] <decoder> // prefer decoder
-// entry = <family> ":*" // prefer all decoders
-// entry = "+" [<family> ":"] <decoder> // force a decoder
-// entry = "-" [<family> ":"] <decoder> // exclude a decoder
+// entry = <decoder> // prefer decoder
+// entry = "-" <decoder> // exclude a decoder
// entry = "-" // don't add fallback decoders
// Forcing a decoder means it's added even if the codec mismatches.
struct mp_decoder_list *mp_select_decoders(struct mp_log *log,
@@ -83,7 +60,6 @@ struct mp_decoder_list *mp_select_decoders(struct mp_log *log,
const char *selection)
{
struct mp_decoder_list *list = talloc_zero(NULL, struct mp_decoder_list);
- struct mp_decoder_list *remove = talloc_zero(NULL, struct mp_decoder_list);
if (!codec)
codec = "unknown";
bool stop = false;
@@ -96,28 +72,15 @@ struct mp_decoder_list *mp_select_decoders(struct mp_log *log,
stop = true;
break;
}
- bool force = bstr_eatstart0(&entry, "+");
- bool exclude = !force && bstr_eatstart0(&entry, "-");
- if (exclude || force)
- mp_warn(log, "Forcing or excluding codecs is deprecated.\n");
- struct mp_decoder_list *dest = exclude ? remove : list;
- bstr family, decoder;
- if (bstr_split_tok(entry, ":", &family, &decoder)) {
- mp_warn(log, "Codec family selection is deprecated. "
- "Pass the codec name directly.\n");
- } else {
- family = bstr0("*");
- decoder = entry;
+ if (bstr_find0(entry, ":") >= 0) {
+ mp_err(log, "Codec family selection was removed. "
+ "Pass the codec name directly.\n");
+ break;
}
- if (bstr_equals0(decoder, "*")) {
- for (int n = 0; n < all->num_entries; n++) {
- struct mp_decoder_entry *cur = &all->entries[n];
- if (bstr_equals0(family, cur->family))
- add_new(dest, cur, codec);
- }
- } else {
- add_new(dest, find_decoder(all, family, decoder),
- force ? NULL : codec);
+ for (int n = 0; n < all->num_entries; n++) {
+ struct mp_decoder_entry *cur = &all->entries[n];
+ if (bstr_equals0(entry, cur->decoder))
+ add_new(list, cur, codec);
}
}
if (!stop) {
@@ -125,16 +88,6 @@ struct mp_decoder_list *mp_select_decoders(struct mp_log *log,
for (int n = 0; n < all->num_entries; n++)
add_new(list, &all->entries[n], codec);
}
- for (int n = 0; n < remove->num_entries; n++) {
- struct mp_decoder_entry *ex = &remove->entries[n];
- struct mp_decoder_entry *del =
- find_decoder(list, bstr0(ex->family), bstr0(ex->decoder));
- if (del) {
- int index = del - &list->entries[0];
- MP_TARRAY_REMOVE_AT(list->entries, list->num_entries, index);
- }
- }
- talloc_free(remove);
return list;
}
diff --git a/common/encode_lavc.c b/common/encode_lavc.c
index 7e116e3b0c..ee870a015a 100644
--- a/common/encode_lavc.c
+++ b/common/encode_lavc.c
@@ -473,7 +473,7 @@ static void encode_2pass_prepare(struct encode_lavc_context *ctx,
if (!(*bytebuf = stream_open(buf, ctx->global))) {
MP_WARN(ctx, "%s: could not open '%s', "
"disabling 2-pass encoding at pass 2\n", prefix, buf);
- codec->flags &= ~CODEC_FLAG_PASS2;
+ codec->flags &= ~AV_CODEC_FLAG_PASS2;
set_to_avdictionary(ctx, dictp, "flags", "-pass2");
} else {
struct bstr content = stream_read_complete(*bytebuf, NULL,
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