Something went wrong. Try again.
🎥 Command line media player
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946/* * 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 "options/path.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;};
#define OPT_BASE_STRUCT struct encode_optsconst struct m_sub_options encode_config = { .opts = (const m_option_t[]) { {"o", OPT_STRING(file), .flags = M_OPT_NOCFG | M_OPT_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";
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;
char *path = mp_get_user_path(NULL, global, filename); p->muxer->url = av_strdup(path); talloc_free(path); 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 lockedstatic 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;
return;
failed: p->failed = true;}
// called lockedstatic 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){ 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(NULL);
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;
mp_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; mp_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); avformat_free_context(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){ mp_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 is not finished yet.\n" " Try updating ffmpeg.\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); if (!p->mux_stream) goto fail;
return true;
fail: avcodec_free_context(&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;}
void encoder_update_log(struct mpv_global *global){ struct encode_opts *options = mp_get_config_group(NULL, global, &encode_config); if (options->file && (!strcmp(options->file, "-") || !strcmp(options->file, "/dev/stdout") || !strcmp(options->file, "pipe:") || !strcmp(options->file, "pipe:1"))) { mp_msg_force_stderr(global, true); } talloc_free(options);}
// vim: ts=4 sw=4 et