diff options
Diffstat (limited to 'common')
-rw-r--r-- | common/av_common.c | 128 | ||||
-rw-r--r-- | common/av_common.h | 5 | ||||
-rw-r--r-- | common/av_log.c | 8 | ||||
-rw-r--r-- | common/codecs.c | 69 | ||||
-rw-r--r-- | common/encode_lavc.c | 2 | ||||
-rw-r--r-- | common/recorder.c | 384 | ||||
-rw-r--r-- | common/recorder.h | 21 |
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 |