summaryrefslogtreecommitdiffstats
path: root/common/encode_lavc.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 20:36:56 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 20:36:56 +0000
commit51de1d8436100f725f3576aefa24a2bd2057bc28 (patch)
treec6d1d5264b6d40a8d7ca34129f36b7d61e188af3 /common/encode_lavc.c
parentInitial commit. (diff)
downloadmpv-51de1d8436100f725f3576aefa24a2bd2057bc28.tar.xz
mpv-51de1d8436100f725f3576aefa24a2bd2057bc28.zip
Adding upstream version 0.37.0.upstream/0.37.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'common/encode_lavc.c')
-rw-r--r--common/encode_lavc.c949
1 files changed, 949 insertions, 0 deletions
diff --git a/common/encode_lavc.c b/common/encode_lavc.c
new file mode 100644
index 0000000..898545d
--- /dev/null
+++ b/common/encode_lavc.c
@@ -0,0 +1,949 @@
+/*
+ * muxing using libavformat
+ *
+ * Copyright (C) 2010 Nicolas George <george@nsup.org>
+ * Copyright (C) 2011-2012 Rudolf Polzer <divVerent@xonotic.org>
+ *
+ * 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 <libavutil/avutil.h>
+#include <libavutil/timestamp.h>
+
+#include "encode_lavc.h"
+#include "common/av_common.h"
+#include "common/global.h"
+#include "common/msg.h"
+#include "common/msg_control.h"
+#include "options/m_config.h"
+#include "options/m_option.h"
+#include "options/options.h"
+#include "osdep/timer.h"
+#include "video/out/vo.h"
+#include "mpv_talloc.h"
+#include "stream/stream.h"
+
+struct encode_priv {
+ struct mp_log *log;
+
+ // --- All fields are protected by encode_lavc_context.lock
+
+ bool failed;
+
+ struct mp_tags *metadata;
+
+ AVFormatContext *muxer;
+
+ bool header_written; // muxer was initialized
+
+ struct mux_stream **streams;
+ int num_streams;
+
+ // Statistics
+ double t0;
+
+ long long abytes;
+ long long vbytes;
+
+ unsigned int frames;
+ double audioseconds;
+};
+
+struct mux_stream {
+ int index; // index of this into p->streams[]
+ char name[80];
+ struct encode_lavc_context *ctx;
+ enum AVMediaType codec_type;
+ AVRational encoder_timebase; // packet timestamps from encoder
+ AVStream *st;
+ void (*on_ready)(void *ctx); // when finishing muxer init
+ void *on_ready_ctx;
+};
+
+#define OPT_BASE_STRUCT struct encode_opts
+const struct m_sub_options encode_config = {
+ .opts = (const m_option_t[]) {
+ {"o", OPT_STRING(file), .flags = CONF_NOCFG | CONF_PRE_PARSE | M_OPT_FILE},
+ {"of", OPT_STRING(format)},
+ {"ofopts", OPT_KEYVALUELIST(fopts), .flags = M_OPT_HAVE_HELP},
+ {"ovc", OPT_STRING(vcodec)},
+ {"ovcopts", OPT_KEYVALUELIST(vopts), .flags = M_OPT_HAVE_HELP},
+ {"oac", OPT_STRING(acodec)},
+ {"oacopts", OPT_KEYVALUELIST(aopts), .flags = M_OPT_HAVE_HELP},
+ {"orawts", OPT_BOOL(rawts)},
+ {"ocopy-metadata", OPT_BOOL(copy_metadata)},
+ {"oset-metadata", OPT_KEYVALUELIST(set_metadata)},
+ {"oremove-metadata", OPT_STRINGLIST(remove_metadata)},
+ {0}
+ },
+ .size = sizeof(struct encode_opts),
+ .defaults = &(const struct encode_opts){
+ .copy_metadata = true,
+ },
+};
+
+struct encode_lavc_context *encode_lavc_init(struct mpv_global *global)
+{
+ struct encode_lavc_context *ctx = talloc_ptrtype(NULL, ctx);
+ *ctx = (struct encode_lavc_context){
+ .global = global,
+ .options = mp_get_config_group(ctx, global, &encode_config),
+ .priv = talloc_zero(ctx, struct encode_priv),
+ .log = mp_log_new(ctx, global->log, "encode"),
+ };
+ mp_mutex_init(&ctx->lock);
+
+ struct encode_priv *p = ctx->priv;
+ p->log = ctx->log;
+
+ const char *filename = ctx->options->file;
+
+ // STUPID STUPID STUPID STUPID avio
+ // does not support "-" as file name to mean stdin/stdout
+ // ffmpeg.c works around this too, the same way
+ if (!strcmp(filename, "-"))
+ filename = "pipe:1";
+
+ if (filename && (
+ !strcmp(filename, "/dev/stdout") ||
+ !strcmp(filename, "pipe:") ||
+ !strcmp(filename, "pipe:1")))
+ mp_msg_force_stderr(global, true);
+
+ encode_lavc_discontinuity(ctx);
+
+ p->muxer = avformat_alloc_context();
+ MP_HANDLE_OOM(p->muxer);
+
+ if (ctx->options->format && ctx->options->format[0]) {
+ ctx->oformat = av_guess_format(ctx->options->format, filename, NULL);
+ } else {
+ ctx->oformat = av_guess_format(NULL, filename, NULL);
+ }
+
+ if (!ctx->oformat) {
+ MP_FATAL(ctx, "format not found\n");
+ goto fail;
+ }
+
+ p->muxer->oformat = ctx->oformat;
+
+ p->muxer->url = av_strdup(filename);
+ MP_HANDLE_OOM(p->muxer->url);
+
+ return ctx;
+
+fail:
+ p->failed = true;
+ encode_lavc_free(ctx);
+ return NULL;
+}
+
+void encode_lavc_set_metadata(struct encode_lavc_context *ctx,
+ struct mp_tags *metadata)
+{
+ struct encode_priv *p = ctx->priv;
+
+ mp_mutex_lock(&ctx->lock);
+
+ if (ctx->options->copy_metadata) {
+ p->metadata = mp_tags_dup(ctx, metadata);
+ } else {
+ p->metadata = talloc_zero(ctx, struct mp_tags);
+ }
+
+ if (ctx->options->set_metadata) {
+ char **kv = ctx->options->set_metadata;
+ // Set all user-provided metadata tags
+ for (int n = 0; kv[n * 2]; n++) {
+ MP_VERBOSE(ctx, "setting metadata value '%s' for key '%s'\n",
+ kv[n*2 + 0], kv[n*2 +1]);
+ mp_tags_set_str(p->metadata, kv[n*2 + 0], kv[n*2 +1]);
+ }
+ }
+
+ if (ctx->options->remove_metadata) {
+ char **k = ctx->options->remove_metadata;
+ // Remove all user-provided metadata tags
+ for (int n = 0; k[n]; n++) {
+ MP_VERBOSE(ctx, "removing metadata key '%s'\n", k[n]);
+ mp_tags_remove_str(p->metadata, k[n]);
+ }
+ }
+
+ mp_mutex_unlock(&ctx->lock);
+}
+
+bool encode_lavc_free(struct encode_lavc_context *ctx)
+{
+ bool res = true;
+ if (!ctx)
+ return res;
+
+ struct encode_priv *p = ctx->priv;
+
+ if (!p->failed && !p->header_written) {
+ MP_FATAL(p, "no data written to target file\n");
+ p->failed = true;
+ }
+
+ if (!p->failed && p->header_written) {
+ if (av_write_trailer(p->muxer) < 0)
+ MP_ERR(p, "error writing trailer\n");
+
+ MP_INFO(p, "video: encoded %lld bytes\n", p->vbytes);
+ MP_INFO(p, "audio: encoded %lld bytes\n", p->abytes);
+
+ MP_INFO(p, "muxing overhead %lld bytes\n",
+ (long long)(avio_size(p->muxer->pb) - p->vbytes - p->abytes));
+ }
+
+ if (avio_closep(&p->muxer->pb) < 0 && !p->failed) {
+ MP_ERR(p, "Closing file failed\n");
+ p->failed = true;
+ }
+
+ avformat_free_context(p->muxer);
+
+ res = !p->failed;
+
+ mp_mutex_destroy(&ctx->lock);
+ talloc_free(ctx);
+
+ return res;
+}
+
+// called locked
+static void maybe_init_muxer(struct encode_lavc_context *ctx)
+{
+ struct encode_priv *p = ctx->priv;
+
+ if (p->header_written || p->failed)
+ return;
+
+ // Check if all streams were initialized yet. We need data to know the
+ // AVStream parameters, so we wait for data from _all_ streams before
+ // starting.
+ for (int n = 0; n < p->num_streams; n++) {
+ if (!p->streams[n]->st)
+ return;
+ }
+
+ if (!(p->muxer->oformat->flags & AVFMT_NOFILE)) {
+ MP_INFO(p, "Opening output file: %s\n", p->muxer->url);
+
+ if (avio_open(&p->muxer->pb, p->muxer->url, AVIO_FLAG_WRITE) < 0) {
+ MP_FATAL(p, "could not open '%s'\n", p->muxer->url);
+ goto failed;
+ }
+ }
+
+ p->t0 = mp_time_sec();
+
+ MP_INFO(p, "Opening muxer: %s [%s]\n",
+ p->muxer->oformat->long_name, p->muxer->oformat->name);
+
+ if (p->metadata) {
+ for (int i = 0; i < p->metadata->num_keys; i++) {
+ av_dict_set(&p->muxer->metadata,
+ p->metadata->keys[i], p->metadata->values[i], 0);
+ }
+ }
+
+ AVDictionary *opts = NULL;
+ mp_set_avdict(&opts, ctx->options->fopts);
+
+ if (avformat_write_header(p->muxer, &opts) < 0) {
+ MP_FATAL(p, "Failed to initialize muxer.\n");
+ p->failed = true;
+ } else {
+ mp_avdict_print_unset(p->log, MSGL_WARN, opts);
+ }
+
+ av_dict_free(&opts);
+
+ if (p->failed)
+ goto failed;
+
+ p->header_written = true;
+
+ for (int n = 0; n < p->num_streams; n++) {
+ struct mux_stream *s = p->streams[n];
+
+ if (s->on_ready)
+ s->on_ready(s->on_ready_ctx);
+ }
+
+ return;
+
+failed:
+ p->failed = true;
+}
+
+// called locked
+static struct mux_stream *find_mux_stream(struct encode_lavc_context *ctx,
+ enum AVMediaType codec_type)
+{
+ struct encode_priv *p = ctx->priv;
+
+ for (int n = 0; n < p->num_streams; n++) {
+ struct mux_stream *s = p->streams[n];
+ if (s->codec_type == codec_type)
+ return s;
+ }
+
+ return NULL;
+}
+
+void encode_lavc_expect_stream(struct encode_lavc_context *ctx,
+ enum stream_type type)
+{
+ struct encode_priv *p = ctx->priv;
+
+ mp_mutex_lock(&ctx->lock);
+
+ enum AVMediaType codec_type = mp_to_av_stream_type(type);
+
+ // These calls are idempotent.
+ if (find_mux_stream(ctx, codec_type))
+ goto done;
+
+ if (p->header_written) {
+ MP_ERR(p, "Cannot add a stream during encoding.\n");
+ p->failed = true;
+ goto done;
+ }
+
+ struct mux_stream *dst = talloc_ptrtype(p, dst);
+ *dst = (struct mux_stream){
+ .index = p->num_streams,
+ .ctx = ctx,
+ .codec_type = mp_to_av_stream_type(type),
+ };
+ snprintf(dst->name, sizeof(dst->name), "%s", stream_type_name(type));
+ MP_TARRAY_APPEND(p, p->streams, p->num_streams, dst);
+
+done:
+ mp_mutex_unlock(&ctx->lock);
+}
+
+// Signal that you are ready to encode (you provide the codec params etc. too).
+// This returns a muxing handle which you can use to add encodec packets.
+// Can be called only once per stream. info is copied by callee as needed.
+static void encode_lavc_add_stream(struct encoder_context *enc,
+ struct encode_lavc_context *ctx,
+ struct encoder_stream_info *info,
+ void (*on_ready)(void *ctx),
+ void *on_ready_ctx)
+{
+ struct encode_priv *p = ctx->priv;
+
+ mp_mutex_lock(&ctx->lock);
+
+ struct mux_stream *dst = find_mux_stream(ctx, info->codecpar->codec_type);
+ if (!dst) {
+ MP_ERR(p, "Cannot add a stream at runtime.\n");
+ p->failed = true;
+ goto done;
+ }
+ if (dst->st) {
+ // Possibly via --gapless-audio, or explicitly recreating AO/VO.
+ MP_ERR(p, "Encoder was reinitialized; this is not allowed.\n");
+ p->failed = true;
+ dst = NULL;
+ goto done;
+ }
+
+ dst->st = avformat_new_stream(p->muxer, NULL);
+ MP_HANDLE_OOM(dst->st);
+
+ dst->encoder_timebase = info->timebase;
+ dst->st->time_base = info->timebase; // lavf will change this on muxer init
+ // Some muxers (e.g. Matroska one) expect the sample_aspect_ratio to be
+ // set on the AVStream.
+ if (info->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
+ dst->st->sample_aspect_ratio = info->codecpar->sample_aspect_ratio;
+
+ if (avcodec_parameters_copy(dst->st->codecpar, info->codecpar) < 0)
+ MP_HANDLE_OOM(0);
+
+ dst->on_ready = on_ready;
+ dst->on_ready_ctx = on_ready_ctx;
+ enc->mux_stream = dst;
+
+ maybe_init_muxer(ctx);
+
+done:
+ mp_mutex_unlock(&ctx->lock);
+}
+
+// Write a packet. This will take over ownership of `pkt`
+static void encode_lavc_add_packet(struct mux_stream *dst, AVPacket *pkt)
+{
+ struct encode_lavc_context *ctx = dst->ctx;
+ struct encode_priv *p = ctx->priv;
+
+ assert(dst->st);
+
+ mp_mutex_lock(&ctx->lock);
+
+ if (p->failed)
+ goto done;
+
+ if (!p->header_written) {
+ MP_ERR(p, "Encoder trying to write packet before muxer was initialized.\n");
+ p->failed = true;
+ goto done;
+ }
+
+ pkt->stream_index = dst->st->index;
+ assert(dst->st == p->muxer->streams[pkt->stream_index]);
+
+ av_packet_rescale_ts(pkt, dst->encoder_timebase, dst->st->time_base);
+
+ switch (dst->st->codecpar->codec_type) {
+ case AVMEDIA_TYPE_VIDEO:
+ p->vbytes += pkt->size;
+ p->frames += 1;
+ break;
+ case AVMEDIA_TYPE_AUDIO:
+ p->abytes += pkt->size;
+ p->audioseconds += pkt->duration
+ * (double)dst->st->time_base.num
+ / (double)dst->st->time_base.den;
+ break;
+ }
+
+ if (av_interleaved_write_frame(p->muxer, pkt) < 0) {
+ MP_ERR(p, "Writing packet failed.\n");
+ p->failed = true;
+ }
+
+ pkt = NULL;
+
+done:
+ mp_mutex_unlock(&ctx->lock);
+ if (pkt)
+ av_packet_unref(pkt);
+}
+
+AVRational encoder_get_mux_timebase_unlocked(struct encoder_context *p)
+{
+ return p->mux_stream->st->time_base;
+}
+
+void encode_lavc_discontinuity(struct encode_lavc_context *ctx)
+{
+ if (!ctx)
+ return;
+
+ mp_mutex_lock(&ctx->lock);
+ ctx->discontinuity_pts_offset = MP_NOPTS_VALUE;
+ mp_mutex_unlock(&ctx->lock);
+}
+
+static void encode_lavc_printoptions(struct mp_log *log, const void *obj,
+ const char *indent, const char *subindent,
+ const char *unit, int filter_and,
+ int filter_eq)
+{
+ const AVOption *opt = NULL;
+ char optbuf[32];
+ while ((opt = av_opt_next(obj, opt))) {
+ // if flags are 0, it simply hasn't been filled in yet and may be
+ // potentially useful
+ if (opt->flags)
+ if ((opt->flags & filter_and) != filter_eq)
+ continue;
+ /* Don't print CONST's on level one.
+ * Don't print anything but CONST's on level two.
+ * Only print items from the requested unit.
+ */
+ if (!unit && opt->type == AV_OPT_TYPE_CONST) {
+ continue;
+ } else if (unit && opt->type != AV_OPT_TYPE_CONST) {
+ continue;
+ } else if (unit && opt->type == AV_OPT_TYPE_CONST
+ && strcmp(unit, opt->unit))
+ {
+ continue;
+ } else if (unit && opt->type == AV_OPT_TYPE_CONST) {
+ mp_info(log, "%s", subindent);
+ } else {
+ mp_info(log, "%s", indent);
+ }
+
+ switch (opt->type) {
+ case AV_OPT_TYPE_FLAGS:
+ snprintf(optbuf, sizeof(optbuf), "%s=<flags>", opt->name);
+ break;
+ case AV_OPT_TYPE_INT:
+ snprintf(optbuf, sizeof(optbuf), "%s=<int>", opt->name);
+ break;
+ case AV_OPT_TYPE_INT64:
+ snprintf(optbuf, sizeof(optbuf), "%s=<int64>", opt->name);
+ break;
+ case AV_OPT_TYPE_DOUBLE:
+ snprintf(optbuf, sizeof(optbuf), "%s=<double>", opt->name);
+ break;
+ case AV_OPT_TYPE_FLOAT:
+ snprintf(optbuf, sizeof(optbuf), "%s=<float>", opt->name);
+ break;
+ case AV_OPT_TYPE_STRING:
+ snprintf(optbuf, sizeof(optbuf), "%s=<string>", opt->name);
+ break;
+ case AV_OPT_TYPE_RATIONAL:
+ snprintf(optbuf, sizeof(optbuf), "%s=<rational>", opt->name);
+ break;
+ case AV_OPT_TYPE_BINARY:
+ snprintf(optbuf, sizeof(optbuf), "%s=<binary>", opt->name);
+ break;
+ case AV_OPT_TYPE_CONST:
+ snprintf(optbuf, sizeof(optbuf), " [+-]%s", opt->name);
+ break;
+ default:
+ snprintf(optbuf, sizeof(optbuf), "%s", opt->name);
+ break;
+ }
+ optbuf[sizeof(optbuf) - 1] = 0;
+ mp_info(log, "%-32s ", optbuf);
+ if (opt->help)
+ mp_info(log, " %s", opt->help);
+ mp_info(log, "\n");
+ if (opt->unit && opt->type != AV_OPT_TYPE_CONST)
+ encode_lavc_printoptions(log, obj, indent, subindent, opt->unit,
+ filter_and, filter_eq);
+ }
+}
+
+bool encode_lavc_showhelp(struct mp_log *log, struct encode_opts *opts)
+{
+ bool help_output = false;
+#define CHECKS(str) ((str) && \
+ strcmp((str), "help") == 0 ? (help_output |= 1) : 0)
+#define CHECKV(strv) ((strv) && (strv)[0] && \
+ strcmp((strv)[0], "help") == 0 ? (help_output |= 1) : 0)
+ if (CHECKS(opts->format)) {
+ const AVOutputFormat *c = NULL;
+ void *iter = NULL;
+ mp_info(log, "Available output formats:\n");
+ while ((c = av_muxer_iterate(&iter))) {
+ mp_info(log, " --of=%-13s %s\n", c->name,
+ c->long_name ? c->long_name : "");
+ }
+ }
+ if (CHECKV(opts->fopts)) {
+ AVFormatContext *c = avformat_alloc_context();
+ const AVOutputFormat *format = NULL;
+ mp_info(log, "Available output format ctx->options:\n");
+ encode_lavc_printoptions(log, c, " --ofopts=", " ", NULL,
+ AV_OPT_FLAG_ENCODING_PARAM,
+ AV_OPT_FLAG_ENCODING_PARAM);
+ av_free(c);
+ void *iter = NULL;
+ while ((format = av_muxer_iterate(&iter))) {
+ if (format->priv_class) {
+ mp_info(log, "Additionally, for --of=%s:\n",
+ format->name);
+ encode_lavc_printoptions(log, &format->priv_class, " --ofopts=",
+ " ", NULL,
+ AV_OPT_FLAG_ENCODING_PARAM,
+ AV_OPT_FLAG_ENCODING_PARAM);
+ }
+ }
+ }
+ if (CHECKV(opts->vopts)) {
+ AVCodecContext *c = avcodec_alloc_context3(NULL);
+ const AVCodec *codec = NULL;
+ mp_info(log, "Available output video codec ctx->options:\n");
+ encode_lavc_printoptions(log,
+ c, " --ovcopts=", " ", NULL,
+ AV_OPT_FLAG_ENCODING_PARAM |
+ AV_OPT_FLAG_VIDEO_PARAM,
+ AV_OPT_FLAG_ENCODING_PARAM |
+ AV_OPT_FLAG_VIDEO_PARAM);
+ av_free(c);
+ void *iter = NULL;
+ while ((codec = av_codec_iterate(&iter))) {
+ if (!av_codec_is_encoder(codec))
+ continue;
+ if (codec->type != AVMEDIA_TYPE_VIDEO)
+ continue;
+ if (opts->vcodec && opts->vcodec[0] &&
+ strcmp(opts->vcodec, codec->name) != 0)
+ continue;
+ if (codec->priv_class) {
+ mp_info(log, "Additionally, for --ovc=%s:\n",
+ codec->name);
+ encode_lavc_printoptions(log,
+ &codec->priv_class, " --ovcopts=",
+ " ", NULL,
+ AV_OPT_FLAG_ENCODING_PARAM |
+ AV_OPT_FLAG_VIDEO_PARAM,
+ AV_OPT_FLAG_ENCODING_PARAM |
+ AV_OPT_FLAG_VIDEO_PARAM);
+ }
+ }
+ }
+ if (CHECKV(opts->aopts)) {
+ AVCodecContext *c = avcodec_alloc_context3(NULL);
+ const AVCodec *codec = NULL;
+ mp_info(log, "Available output audio codec ctx->options:\n");
+ encode_lavc_printoptions(log,
+ c, " --oacopts=", " ", NULL,
+ AV_OPT_FLAG_ENCODING_PARAM |
+ AV_OPT_FLAG_AUDIO_PARAM,
+ AV_OPT_FLAG_ENCODING_PARAM |
+ AV_OPT_FLAG_AUDIO_PARAM);
+ av_free(c);
+ void *iter = NULL;
+ while ((codec = av_codec_iterate(&iter))) {
+ if (!av_codec_is_encoder(codec))
+ continue;
+ if (codec->type != AVMEDIA_TYPE_AUDIO)
+ continue;
+ if (opts->acodec && opts->acodec[0] &&
+ strcmp(opts->acodec, codec->name) != 0)
+ continue;
+ if (codec->priv_class) {
+ mp_info(log, "Additionally, for --oac=%s:\n",
+ codec->name);
+ encode_lavc_printoptions(log,
+ &codec->priv_class, " --oacopts=",
+ " ", NULL,
+ AV_OPT_FLAG_ENCODING_PARAM |
+ AV_OPT_FLAG_AUDIO_PARAM,
+ AV_OPT_FLAG_ENCODING_PARAM |
+ AV_OPT_FLAG_AUDIO_PARAM);
+ }
+ }
+ }
+ if (CHECKS(opts->vcodec)) {
+ const AVCodec *c = NULL;
+ void *iter = NULL;
+ mp_info(log, "Available output video codecs:\n");
+ while ((c = av_codec_iterate(&iter))) {
+ if (!av_codec_is_encoder(c))
+ continue;
+ if (c->type != AVMEDIA_TYPE_VIDEO)
+ continue;
+ mp_info(log, " --ovc=%-12s %s\n", c->name,
+ c->long_name ? c->long_name : "");
+ }
+ }
+ if (CHECKS(opts->acodec)) {
+ const AVCodec *c = NULL;
+ void *iter = NULL;
+ mp_info(log, "Available output audio codecs:\n");
+ while ((c = av_codec_iterate(&iter))) {
+ if (!av_codec_is_encoder(c))
+ continue;
+ if (c->type != AVMEDIA_TYPE_AUDIO)
+ continue;
+ mp_info(log, " --oac=%-12s %s\n", c->name,
+ c->long_name ? c->long_name : "");
+ }
+ }
+ return help_output;
+}
+
+int encode_lavc_getstatus(struct encode_lavc_context *ctx,
+ char *buf, int bufsize,
+ float relative_position)
+{
+ if (!ctx)
+ return -1;
+
+ struct encode_priv *p = ctx->priv;
+
+ double now = mp_time_sec();
+ float minutes, megabytes, fps, x;
+ float f = MPMAX(0.0001, relative_position);
+
+ mp_mutex_lock(&ctx->lock);
+
+ if (p->failed) {
+ snprintf(buf, bufsize, "(failed)\n");
+ goto done;
+ }
+
+ minutes = (now - p->t0) / 60.0 * (1 - f) / f;
+ megabytes = p->muxer->pb ? (avio_size(p->muxer->pb) / 1048576.0 / f) : 0;
+ fps = p->frames / (now - p->t0);
+ x = p->audioseconds / (now - p->t0);
+ if (p->frames) {
+ snprintf(buf, bufsize, "{%.1fmin %.1ffps %.1fMB}",
+ minutes, fps, megabytes);
+ } else if (p->audioseconds) {
+ snprintf(buf, bufsize, "{%.1fmin %.2fx %.1fMB}",
+ minutes, x, megabytes);
+ } else {
+ snprintf(buf, bufsize, "{%.1fmin %.1fMB}",
+ minutes, megabytes);
+ }
+ buf[bufsize - 1] = 0;
+
+done:
+ mp_mutex_unlock(&ctx->lock);
+ return 0;
+}
+
+bool encode_lavc_didfail(struct encode_lavc_context *ctx)
+{
+ if (!ctx)
+ return false;
+ mp_mutex_lock(&ctx->lock);
+ bool fail = ctx->priv->failed;
+ mp_mutex_unlock(&ctx->lock);
+ return fail;
+}
+
+static void encoder_destroy(void *ptr)
+{
+ struct encoder_context *p = ptr;
+
+ av_packet_free(&p->pkt);
+ avcodec_parameters_free(&p->info.codecpar);
+ avcodec_free_context(&p->encoder);
+ free_stream(p->twopass_bytebuffer);
+}
+
+static const AVCodec *find_codec_for(struct encode_lavc_context *ctx,
+ enum stream_type type, bool *used_auto)
+{
+ char *codec_name = type == STREAM_VIDEO
+ ? ctx->options->vcodec
+ : ctx->options->acodec;
+ enum AVMediaType codec_type = mp_to_av_stream_type(type);
+ const char *tname = stream_type_name(type);
+
+ *used_auto = !(codec_name && codec_name[0]);
+
+ const AVCodec *codec;
+ if (*used_auto) {
+ codec = avcodec_find_encoder(av_guess_codec(ctx->oformat, NULL,
+ ctx->options->file, NULL,
+ codec_type));
+ } else {
+ codec = avcodec_find_encoder_by_name(codec_name);
+ if (!codec)
+ MP_FATAL(ctx, "codec '%s' not found.\n", codec_name);
+ }
+
+ if (codec && codec->type != codec_type) {
+ MP_FATAL(ctx, "codec for %s has wrong media type\n", tname);
+ codec = NULL;
+ }
+
+ return codec;
+}
+
+// Return whether the stream type is "supposed" to work.
+bool encode_lavc_stream_type_ok(struct encode_lavc_context *ctx,
+ enum stream_type type)
+{
+ // If a codec was forced, let it proceed to actual encoding, and then error
+ // if it doesn't work. (Worried that av_guess_codec() may return NULL for
+ // some formats where a specific codec works anyway.)
+ bool auto_codec;
+ return !!find_codec_for(ctx, type, &auto_codec) || !auto_codec;
+}
+
+struct encoder_context *encoder_context_alloc(struct encode_lavc_context *ctx,
+ enum stream_type type,
+ struct mp_log *log)
+{
+ if (!ctx) {
+ mp_err(log, "the option --o (output file) must be specified\n");
+ return NULL;
+ }
+
+ struct encoder_context *p = talloc_ptrtype(NULL, p);
+ talloc_set_destructor(p, encoder_destroy);
+ *p = (struct encoder_context){
+ .global = ctx->global,
+ .options = ctx->options,
+ .oformat = ctx->oformat,
+ .type = type,
+ .log = log,
+ .encode_lavc_ctx = ctx,
+ };
+
+ bool auto_codec;
+ const AVCodec *codec = find_codec_for(ctx, type, &auto_codec);
+ const char *tname = stream_type_name(type);
+
+ if (!codec) {
+ if (auto_codec)
+ MP_FATAL(p, "codec for %s not found\n", tname);
+ goto fail;
+ }
+
+ p->encoder = avcodec_alloc_context3(codec);
+ MP_HANDLE_OOM(p->encoder);
+
+ return p;
+
+fail:
+ talloc_free(p);
+ return NULL;
+}
+
+static void encoder_2pass_prepare(struct encoder_context *p)
+{
+ char *filename = talloc_asprintf(NULL, "%s-%s-pass1.log",
+ p->options->file,
+ stream_type_name(p->type));
+
+ if (p->encoder->flags & AV_CODEC_FLAG_PASS2) {
+ MP_INFO(p, "Reading 2-pass log: %s\n", filename);
+ struct stream *s = stream_create(filename,
+ STREAM_ORIGIN_DIRECT | STREAM_READ,
+ NULL, p->global);
+ if (s) {
+ struct bstr content = stream_read_complete(s, p, 1000000000);
+ if (content.start) {
+ p->encoder->stats_in = content.start;
+ } else {
+ MP_WARN(p, "could not read '%s', "
+ "disabling 2-pass encoding at pass 1\n", filename);
+ }
+ free_stream(s);
+ } else {
+ MP_WARN(p, "could not open '%s', "
+ "disabling 2-pass encoding at pass 2\n", filename);
+ p->encoder->flags &= ~(unsigned)AV_CODEC_FLAG_PASS2;
+ }
+ }
+
+ if (p->encoder->flags & AV_CODEC_FLAG_PASS1) {
+ MP_INFO(p, "Writing to 2-pass log: %s\n", filename);
+ p->twopass_bytebuffer = open_output_stream(filename, p->global);
+ if (!p->twopass_bytebuffer) {
+ MP_WARN(p, "could not open '%s', "
+ "disabling 2-pass encoding at pass 1\n", filename);
+ p->encoder->flags &= ~(unsigned)AV_CODEC_FLAG_PASS1;
+ }
+ }
+
+ talloc_free(filename);
+}
+
+bool encoder_init_codec_and_muxer(struct encoder_context *p,
+ void (*on_ready)(void *ctx), void *ctx)
+{
+ assert(!avcodec_is_open(p->encoder));
+
+ char **copts = p->type == STREAM_VIDEO
+ ? p->options->vopts
+ : p->options->aopts;
+
+ // Set these now, so the code below can read back parsed settings from it.
+ mp_set_avopts(p->log, p->encoder, copts);
+
+ encoder_2pass_prepare(p);
+
+ if (p->oformat->flags & AVFMT_GLOBALHEADER)
+ p->encoder->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
+
+ MP_INFO(p, "Opening encoder: %s [%s]\n",
+ p->encoder->codec->long_name, p->encoder->codec->name);
+
+ if (p->encoder->codec->capabilities & AV_CODEC_CAP_EXPERIMENTAL) {
+ p->encoder->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
+ MP_WARN(p, "\n\n"
+ " ********************************************\n"
+ " **** Experimental codec selected! ****\n"
+ " ********************************************\n\n"
+ "This means the output file may be broken or bad.\n"
+ "Possible reasons, problems, workarounds:\n"
+ "- Codec implementation in ffmpeg/libav is not finished yet.\n"
+ " Try updating ffmpeg or libav.\n"
+ "- Bad picture quality, blocks, blurriness.\n"
+ " Experiment with codec settings to maybe still get the\n"
+ " desired quality output at the expense of bitrate.\n"
+ "- Broken files.\n"
+ " May not work at all, or break with other software.\n"
+ "- Slow compression.\n"
+ " Bear with it.\n"
+ "- Crashes.\n"
+ " Happens. Try varying options to work around.\n"
+ "If none of this helps you, try another codec in place of %s.\n\n",
+ p->encoder->codec->name);
+ }
+
+ if (avcodec_open2(p->encoder, p->encoder->codec, NULL) < 0) {
+ MP_FATAL(p, "Could not initialize encoder.\n");
+ goto fail;
+ }
+
+ p->info.timebase = p->encoder->time_base; // (_not_ changed by enc. init)
+ p->info.codecpar = avcodec_parameters_alloc();
+ MP_HANDLE_OOM(p->info.codecpar);
+ if (avcodec_parameters_from_context(p->info.codecpar, p->encoder) < 0)
+ goto fail;
+
+ p->pkt = av_packet_alloc();
+ MP_HANDLE_OOM(p->pkt);
+
+ encode_lavc_add_stream(p, p->encode_lavc_ctx, &p->info, on_ready, ctx);
+ if (!p->mux_stream)
+ goto fail;
+
+ return true;
+
+fail:
+ avcodec_close(p->encoder);
+ return false;
+}
+
+bool encoder_encode(struct encoder_context *p, AVFrame *frame)
+{
+ int status = avcodec_send_frame(p->encoder, frame);
+ if (status < 0) {
+ if (frame && status == AVERROR_EOF)
+ MP_ERR(p, "new data after sending EOF to encoder\n");
+ goto fail;
+ }
+
+ AVPacket *packet = p->pkt;
+ for (;;) {
+ status = avcodec_receive_packet(p->encoder, packet);
+ if (status == AVERROR(EAGAIN))
+ break;
+ if (status < 0 && status != AVERROR_EOF)
+ goto fail;
+
+ if (p->twopass_bytebuffer && p->encoder->stats_out) {
+ stream_write_buffer(p->twopass_bytebuffer, p->encoder->stats_out,
+ strlen(p->encoder->stats_out));
+ }
+
+ if (status == AVERROR_EOF)
+ break;
+
+ encode_lavc_add_packet(p->mux_stream, packet);
+ }
+
+ return true;
+
+fail:
+ MP_ERR(p, "error encoding at %s\n",
+ frame ? av_ts2timestr(frame->pts, &p->encoder->time_base) : "EOF");
+ return false;
+}
+
+// vim: ts=4 sw=4 et