diff options
Diffstat (limited to 'common')
-rw-r--r-- | common/common.h | 11 | ||||
-rw-r--r-- | common/encode.h | 11 | ||||
-rw-r--r-- | common/encode_lavc.c | 1136 | ||||
-rw-r--r-- | common/encode_lavc.h | 105 |
4 files changed, 557 insertions, 706 deletions
diff --git a/common/common.h b/common/common.h index 14a9973371..dfb2ba7b7a 100644 --- a/common/common.h +++ b/common/common.h @@ -106,4 +106,15 @@ char *mp_tprintf_buf(char *buf, size_t buf_size, const char *format, ...) char **mp_dup_str_array(void *tctx, char **s); +// We generally do not handle allocation failure of small malloc()s. This would +// create a large number of rarely tested code paths, which would probably +// regress and cause security issues. We prefer to fail fast. +// This macro generally behaves like an assert(), except it will make sure to +// kill the process even with NDEBUG. +#define MP_HANDLE_OOM(x) do { \ + assert(x); \ + if (!(x)) \ + abort(); \ + } while (0) + #endif /* MPLAYER_MPCOMMON_H */ diff --git a/common/encode.h b/common/encode.h index c29cb3bc67..fcf4a8317e 100644 --- a/common/encode.h +++ b/common/encode.h @@ -54,17 +54,16 @@ struct encode_opts { char **remove_metadata; }; -// interface for mplayer.c -struct encode_lavc_context *encode_lavc_init(struct encode_opts *options, - struct mpv_global *global); -void encode_lavc_free(struct encode_lavc_context *ctx); +// interface for player core +struct encode_lavc_context *encode_lavc_init(struct mpv_global *global); +bool encode_lavc_free(struct encode_lavc_context *ctx); void encode_lavc_discontinuity(struct encode_lavc_context *ctx); bool encode_lavc_showhelp(struct mp_log *log, struct encode_opts *options); int encode_lavc_getstatus(struct encode_lavc_context *ctx, char *buf, int bufsize, float relative_position); -void encode_lavc_expect_stream(struct encode_lavc_context *ctx, int mt); +void encode_lavc_expect_stream(struct encode_lavc_context *ctx, + enum stream_type type); void encode_lavc_set_metadata(struct encode_lavc_context *ctx, struct mp_tags *metadata); -void encode_lavc_set_video_fps(struct encode_lavc_context *ctx, float fps); void encode_lavc_set_audio_pts(struct encode_lavc_context *ctx, double pts); bool encode_lavc_didfail(struct encode_lavc_context *ctx); // check if encoding failed diff --git a/common/encode_lavc.c b/common/encode_lavc.c index 905c30dc54..6f8378ac7f 100644 --- a/common/encode_lavc.c +++ b/common/encode_lavc.c @@ -21,6 +21,7 @@ */ #include <libavutil/avutil.h> +#include <libavutil/timestamp.h> #include "config.h" #include "encode_lavc.h" @@ -28,6 +29,7 @@ #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" @@ -35,6 +37,44 @@ #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; + + // Temporary queue while muxer is not initialized. + AVPacket **packets; + int num_packets; + + // 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[] + struct encode_lavc_context *ctx; + enum AVMediaType codec_type; + AVRational encoder_timebase; // packet timestamps from encoder + AVStream *st; +}; + #define OPT_BASE_STRUCT struct encode_opts const struct m_sub_options encode_config = { .opts = (const m_option_t[]) { @@ -71,53 +111,23 @@ const struct m_sub_options encode_config = { }, }; -static bool value_has_flag(const char *value, const char *flag) -{ - bool state = true; - bool ret = false; - while (*value) { - size_t l = strcspn(value, "+-"); - if (l == 0) { - state = (*value == '+'); - ++value; - } else { - if (l == strlen(flag)) - if (!memcmp(value, flag, l)) - ret = state; - value += l; - } - } - return ret; -} - -#define CHECK_FAIL(ctx, val) \ - if (ctx && (ctx->failed)) { \ - MP_ERR(ctx, \ - "Called a function on a %s encoding context. Bailing out.\n", \ - ctx->failed ? "failed" : "finished"); \ - return val; \ - } +static void write_remaining_packets(struct encode_lavc_context *ctx); -#define CHECK_FAIL_UNLOCK(ctx, val) \ - if (ctx && (ctx->failed)) { \ - MP_ERR(ctx, \ - "Called a function on a %s encoding context. Bailing out.\n", \ - ctx->failed ? "failed" : "finished"); \ - pthread_mutex_unlock(&ctx->lock); \ - return val; \ - } - -int encode_lavc_available(struct encode_lavc_context *ctx) +struct encode_lavc_context *encode_lavc_init(struct mpv_global *global) { - CHECK_FAIL(ctx, 0); - return ctx && ctx->avc; -} + 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"), + }; + pthread_mutex_init(&ctx->lock, NULL); -struct encode_lavc_context *encode_lavc_init(struct encode_opts *options, - struct mpv_global *global) -{ - struct encode_lavc_context *ctx; - const char *filename = options->file; + 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 @@ -131,109 +141,46 @@ struct encode_lavc_context *encode_lavc_init(struct encode_opts *options, !strcmp(filename, "pipe:1"))) mp_msg_force_stderr(global, true); - ctx = talloc_zero(NULL, struct encode_lavc_context); - pthread_mutex_init(&ctx->lock, NULL); - ctx->log = mp_log_new(ctx, global->log, "encode-lavc"); - ctx->global = global; encode_lavc_discontinuity(ctx); - ctx->options = options; - - ctx->avc = avformat_alloc_context(); - - if (ctx->options->format) { - char *tok; - const char *in = ctx->options->format; - while (*in) { - tok = av_get_token(&in, ","); - ctx->avc->oformat = av_guess_format(tok, filename, NULL); - av_free(tok); - if (ctx->avc->oformat) - break; - if (*in) - ++in; - } - } else { - ctx->avc->oformat = av_guess_format(NULL, filename, NULL); - } - if (!ctx->avc->oformat) { - encode_lavc_fail(ctx, "format not found\n"); - return NULL; - } + p->muxer = avformat_alloc_context(); + MP_HANDLE_OOM(p->muxer); - ctx->avc->url = av_strdup(filename); - - mp_set_avdict(&ctx->foptions, ctx->options->fopts); - - if (ctx->options->vcodec) { - char *tok; - const char *in = ctx->options->vcodec; - while (*in) { - tok = av_get_token(&in, ","); - ctx->vc = avcodec_find_encoder_by_name(tok); - av_free(tok); - if (ctx->vc && ctx->vc->type != AVMEDIA_TYPE_VIDEO) - ctx->vc = NULL; - if (ctx->vc) - break; - if (*in) - ++in; - } + if (ctx->options->format && ctx->options->format[0]) { + ctx->oformat = av_guess_format(ctx->options->format, filename, NULL); } else { - ctx->vc = avcodec_find_encoder(av_guess_codec(ctx->avc->oformat, NULL, - ctx->avc->url, NULL, - AVMEDIA_TYPE_VIDEO)); + ctx->oformat = av_guess_format(NULL, filename, NULL); } - if (ctx->options->acodec) { - char *tok; - const char *in = ctx->options->acodec; - while (*in) { - tok = av_get_token(&in, ","); - ctx->ac = avcodec_find_encoder_by_name(tok); - av_free(tok); - if (ctx->ac && ctx->ac->type != AVMEDIA_TYPE_AUDIO) - ctx->ac = NULL; - if (ctx->ac) - break; - if (*in) - ++in; - } - } else { - ctx->ac = avcodec_find_encoder(av_guess_codec(ctx->avc->oformat, NULL, - ctx->avc->url, NULL, - AVMEDIA_TYPE_AUDIO)); - } - - if (!ctx->vc && !ctx->ac) { - encode_lavc_fail(ctx, "neither audio nor video codec was found\n"); - return NULL; + if (!ctx->oformat) { + MP_FATAL(ctx, "format not found\n"); + goto fail; } - /* taken from ffmpeg unchanged - * TODO turn this into an option if anyone needs this */ + p->muxer->oformat = ctx->oformat; - ctx->avc->max_delay = 0.7 * AV_TIME_BASE; - - ctx->abytes = 0; - ctx->vbytes = 0; - ctx->frames = 0; - - if (options->video_first) - ctx->video_first = true; - if (options->audio_first) - ctx->audio_first = true; + 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; + + pthread_mutex_lock(&ctx->lock); + if (ctx->options->copy_metadata) { - ctx->metadata = metadata; + p->metadata = mp_tags_dup(ctx, metadata); } else { - ctx->metadata = talloc_zero(ctx, struct mp_tags); + p->metadata = talloc_zero(ctx, struct mp_tags); } if (ctx->options->set_metadata) { @@ -242,7 +189,7 @@ void encode_lavc_set_metadata(struct encode_lavc_context *ctx, 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(ctx->metadata, kv[n*2 + 0], kv[n*2 +1]); + mp_tags_set_str(p->metadata, kv[n*2 + 0], kv[n*2 +1]); } } @@ -251,152 +198,52 @@ void encode_lavc_set_metadata(struct encode_lavc_context *ctx, // 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(ctx->metadata, k[n]); + mp_tags_remove_str(p->metadata, k[n]); } } + + pthread_mutex_unlock(&ctx->lock); } -int encode_lavc_start(struct encode_lavc_context *ctx) +bool encode_lavc_free(struct encode_lavc_context *ctx) { - AVDictionaryEntry *de; - - if (ctx->header_written < 0) - return 0; - if (ctx->header_written > 0) - return 1; - - CHECK_FAIL(ctx, 0); - - if (ctx->expect_video && ctx->vcc == NULL) { - if (ctx->avc->oformat->video_codec != AV_CODEC_ID_NONE || - ctx->options->vcodec) - { - encode_lavc_fail(ctx,"no video stream succeeded - invalid codec?\n"); - return 0; - } - } - - if (ctx->expect_audio && ctx->acc == NULL) { - if (ctx->avc->oformat->audio_codec != AV_CODEC_ID_NONE || - ctx->options->acodec) - { - encode_lavc_fail(ctx, "no audio stream succeeded - invalid codec?\n"); - return 0; - } - } - - ctx->header_written = -1; + bool res = true; + if (!ctx) + return res; - if (!(ctx->avc->oformat->flags & AVFMT_NOFILE)) { - MP_INFO(ctx, "Opening output file: %s\n", ctx->avc->url); + struct encode_priv *p = ctx->priv; - if (avio_open(&ctx->avc->pb, ctx->avc->url, AVIO_FLAG_WRITE) < 0) { - encode_lavc_fail(ctx, "could not open '%s'\n", - ctx->avc->url); - return 0; - } + if (!p->failed && !p->header_written) { + MP_FATAL(p, "no data written to target file\n"); + p->failed = true; } - ctx->t0 = mp_time_sec(); + write_remaining_packets(ctx); - MP_INFO(ctx, "Opening muxer: %s [%s]\n", - ctx->avc->oformat->long_name, ctx->avc->oformat->name); + if (!p->failed && p->header_written) { + if (av_write_trailer(p->muxer) < 0) + MP_ERR(p, "error writing trailer\n"); - if (ctx->metadata) { - for (int i = 0; i < ctx->metadata->num_keys; i++) { - av_dict_set(&ctx->avc->metadata, - ctx->metadata->keys[i], ctx->metadata->values[i], 0); - } - } + MP_INFO(p, "video: encoded %lld bytes\n", p->vbytes); + MP_INFO(p, "audio: encoded %lld bytes\n", p->abytes); - if (avformat_write_header(ctx->avc, &ctx->foptions) < 0) { - encode_lavc_fail(ctx, "could not write header\n"); - return 0; + MP_INFO(p, "muxing overhead %lld bytes\n", + (long long)(avio_size(p->muxer->pb) - p->vbytes - p->abytes)); } - for (de = NULL; (de = av_dict_get(ctx->foptions, "", de, - AV_DICT_IGNORE_SUFFIX));) - { - MP_WARN(ctx, "ofopts: key '%s' not found.\n", de->key); + if (avio_closep(&p->muxer->pb) < 0 && !p->failed) { + MP_ERR(p, "Closing file failed\n"); + p->failed = true; } - av_dict_free(&ctx->foptions); - ctx->header_written = 1; - return 1; -} + avformat_free_context(p->muxer); -void encode_lavc_free(struct encode_lavc_context *ctx) -{ - unsigned i; - - if (!ctx) - return; - - if (ctx->avc) { - if (ctx->header_written > 0) - av_write_trailer(ctx->avc); // this is allowed to fail - - if (ctx->vcc) { - if (ctx->twopass_bytebuffer_v) { - char *stats = ctx->vcc->stats_out; - if (stats) - stream_write_buffer(ctx->twopass_bytebuffer_v, - stats, strlen(stats)); - } - avcodec_free_context(&ctx->vcc); - } - - if (ctx->acc) { - if (ctx->twopass_bytebuffer_a) { - char *stats = ctx->acc->stats_out; - if (stats) - stream_write_buffer(ctx->twopass_bytebuffer_a, - stats, strlen(stats)); - } - avcodec_free_context(&ctx->acc); - } - - for (i = 0; i < ctx->avc->nb_streams; i++) { - av_free(ctx->avc->streams[i]->info); - av_free(ctx->avc->streams[i]); - } - ctx->vst = NULL; - ctx->ast = NULL; - - if (ctx->twopass_bytebuffer_v) { - free_stream(ctx->twopass_bytebuffer_v); - ctx->twopass_bytebuffer_v = NULL; - } - - if (ctx->twopass_bytebuffer_a) { - free_stream(ctx->twopass_bytebuffer_a); - ctx->twopass_bytebuffer_a = NULL; - } - - MP_INFO(ctx, "vo-lavc: encoded %lld bytes\n", - ctx->vbytes); - MP_INFO(ctx, "ao-lavc: encoded %lld bytes\n", - ctx->abytes); - if (ctx->avc->pb) { - MP_INFO(ctx, "muxing overhead %lld bytes\n", - (long long) (avio_size(ctx->avc->pb) - ctx->vbytes - - ctx->abytes)); - avio_close(ctx->avc->pb); - } - - av_free(ctx->avc); - ctx->avc = NULL; - } + res = !p->failed; pthread_mutex_destroy(&ctx->lock); talloc_free(ctx); -} -void encode_lavc_set_video_fps(struct encode_lavc_context *ctx, float fps) -{ - pthread_mutex_lock(&ctx->lock); - ctx->vo_fps = fps; - pthread_mutex_unlock(&ctx->lock); + return res; } void encode_lavc_set_audio_pts(struct encode_lavc_context *ctx, double pts) @@ -409,399 +256,230 @@ void encode_lavc_set_audio_pts(struct encode_lavc_context *ctx, double pts) } } -static void encode_2pass_prepare(struct encode_lavc_context *ctx, - AVDictionary **dictp, - AVStream *stream, - AVCodecContext *codec, - struct stream **bytebuf, - const char *prefix) +// called locked +static void write_remaining_packets(struct encode_lavc_context *ctx) { - if (!*bytebuf) { - char buf[1024]; - AVDictionaryEntry *de = av_dict_get(ctx->voptions, "flags", NULL, 0); - - snprintf(buf, sizeof(buf), "%s-%s-pass1.log", ctx->avc->url, prefix); - - if (value_has_flag(de ? de->value : "", "pass2")) { - 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 &= ~AV_CODEC_FLAG_PASS2; - av_dict_set(dictp, "flags", "-pass2", AV_DICT_APPEND); - } else { - struct bstr content = stream_read_complete(*bytebuf, NULL, - 1000000000); - if (content.start == NULL) { - MP_WARN(ctx, "%s: could not read '%s', " - "disabling 2-pass encoding at pass 1\n", - prefix, ctx->avc->url); - } else { - content.start[content.len] = 0; - codec->stats_in = content.start; - } - free_stream(*bytebuf); - *bytebuf = NULL; - } + struct encode_priv *p = ctx->priv; + + if (!p->header_written && !p->failed) + return; // wait until muxer initialization + + for (int n = 0; n < p->num_packets; n++) { + AVPacket *pkt = p->packets[n]; + MP_TARRAY_REMOVE_AT(p->packets, p->num_packets, 0); + + if (p->failed) { + av_packet_free(&pkt); + continue; } - if (value_has_flag(de ? de->value : "", "pass1")) { - if (!(*bytebuf = open_output_stream(buf, ctx->global))) { - MP_WARN(ctx, - "%s: could not open '%s', disabling " - "2-pass encoding at pass 1\n", - prefix, ctx->avc->url); - av_dict_set(dictp, "flags", "-pass1", AV_DICT_APPEND); - } + struct mux_stream *s = p->streams[pkt->stream_index]; + + pkt->stream_index = s->st->index; + assert(s->st == p->muxer->streams[pkt->stream_index]); + + av_packet_rescale_ts(pkt, s->encoder_timebase, s->st->time_base); + + switch (s->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)s->st->time_base.num + / (double)s->st->time_base.den; + break; + } + + if (av_interleaved_write_frame(p->muxer, pkt) < 0) { + MP_ERR(p, "Writing packet failed.\n"); + p->failed = true; } } + + p->num_packets = 0; } -int encode_lavc_alloc_stream(struct encode_lavc_context *ctx, - enum AVMediaType mt, - AVStream **stream_out, - AVCodecContext **codec_out) +// called locked +static void maybe_init_muxer(struct encode_lavc_context *ctx) { - AVDictionaryEntry *de; + struct encode_priv *p = ctx->priv; - *stream_out = NULL; - *codec_out = NULL; + if (p->header_written || p->failed) + return; - CHECK_FAIL(ctx, -1); + // 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) { + int max_num = 50; + if (p->num_packets > max_num) { + MP_FATAL(p, "no data on stream %d, even though other streams " + "produced more than %d packets.\n", n, p->num_packets); + goto failed; + } + return; + } + } - if (ctx->header_written) - return -1; + if (!(p->muxer->oformat->flags & AVFMT_NOFILE)) { + MP_INFO(p, "Opening output file: %s\n", p->muxer->url); - if (ctx->avc->nb_streams == 0) { - // if this stream isn't stream #0, allocate a dummy stream first for - // the next call to use - if (mt == AVMEDIA_TYPE_VIDEO && ctx->audio_first) { - MP_INFO(ctx, "vo-lavc: preallocated audio stream for later use\n"); - ctx->ast = avformat_new_stream( - ctx->avc, NULL); // this one is AVMEDIA_TYPE_UNKNOWN for now - } - if (mt == AVMEDIA_TYPE_AUDIO && ctx->video_first) { - MP_INFO(ctx, "ao-lavc: preallocated video stream for later use\n"); - ctx->vst = avformat_new_stream( - ctx->avc, NULL); // this one is AVMEDIA_TYPE_UNKNOWN for now + 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; } } - // already have a stream of that type (this cannot really happen)? - switch (mt) { - case AVMEDIA_TYPE_VIDEO: - if (ctx->vcc != NULL) - return -1; - if (ctx->vst == NULL) - ctx->vst = avformat_new_stream(ctx->avc, NULL); - break; - case AVMEDIA_TYPE_AUDIO: - if (ctx->acc != NULL) - return -1; - if (ctx->ast == NULL) - ctx->ast = avformat_new_stream(ctx->avc, NULL); - break; - default: - encode_lavc_fail(ctx, "requested invalid stream type\n"); - return -1; - } + p->t0 = mp_time_sec(); - if (ctx->timebase.den == 0) { - AVRational r; + MP_INFO(p, "Opening muxer: %s [%s]\n", + p->muxer->oformat->long_name, p->muxer->oformat->name); - if (ctx->options->fps > 0) - r = av_d2q(ctx->options->fps, ctx->options->fps * 1001 + 2); - else if (ctx->options->autofps && ctx->vo_fps > 0) { - r = av_d2q(ctx->vo_fps, ctx->vo_fps * 1001 + 2); - MP_INFO(ctx, "option --ofps not specified " - "but --oautofps is active, using guess of %u/%u\n", - (unsigned)r.num, (unsigned)r.den); - } else { - // we want to handle: - // 1/25 - // 1001/24000 - // 1001/30000 - // for this we would need 120000fps... - // however, mpeg-4 only allows 16bit values - // so let's take 1001/30000 out - r.num = 24000; - r.den = 1; - MP_INFO(ctx, "option --ofps not specified " - "and fps could not be inferred, using guess of %u/%u\n", - (unsigned)r.num, (unsigned)r.den); + 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); } - - if (ctx->vc && ctx->vc->supported_framerates) - r = ctx->vc->supported_framerates[av_find_nearest_q_idx(r, - ctx->vc->supported_framerates)]; - - ctx->timebase.num = r.den; - ctx->timebase.den = r.num; } - switch (mt) { - case AVMEDIA_TYPE_VIDEO: - if (!ctx->vc) { - if (ctx->avc->oformat->video_codec != AV_CODEC_ID_NONE || - ctx->options->vcodec) { - encode_lavc_fail(ctx, "vo-lavc: encoder not found\n"); - } - return -1; - } - ctx->vcc = avcodec_alloc_context3(ctx->vc); + AVDictionary *opts = NULL; + mp_set_avdict(&opts, ctx->options->fopts); - // Using codec->time_base is deprecated, but needed for older lavf. - ctx->vst->time_base = ctx->timebase; - ctx->vcc->time_base = ctx->timebase; + 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); + } - ctx->voptions = NULL; + av_dict_free(&opts); - mp_set_avdict(&ctx->voptions, ctx->options->vopts); + if (p->failed) + goto failed; - de = av_dict_get(ctx->voptions, "global_quality", NULL, 0); - if (de) - av_dict_set(&ctx->voptions, "flags", "+qscale", AV_DICT_APPEND); + p->header_written = true; - if (ctx->avc->oformat->flags & AVFMT_GLOBALHEADER) - av_dict_set(&ctx->voptions, "flags", "+global_header", AV_DICT_APPEND); + write_remaining_packets(ctx); + return; - encode_2pass_prepare(ctx, &ctx->voptions, ctx->vst, ctx->vcc, - &ctx->twopass_bytebuffer_v, - "vo-lavc"); - *stream_out = ctx->vst; - *codec_out = ctx->vcc; - return 0; +failed: + p->failed = true; +} - case AVMEDIA_TYPE_AUDIO: - if (!ctx->ac) { - if (ctx->avc->oformat->audio_codec != AV_CODEC_ID_NONE || - ctx->options->acodec) { - encode_lavc_fail(ctx, "ao-lavc: encoder not found\n"); - } - return -1; - } - ctx->acc = avcodec_alloc_context3(ctx->ac); +// called locked +static struct mux_stream *find_mux_stream(struct encode_lavc_context *ctx, + enum AVMediaType codec_type) +{ + struct encode_priv *p = ctx->priv; - // Using codec->time_base is deprecated, but needed for older lavf. - ctx->ast->time_base = ctx->timebase; - ctx->acc->time_base = ctx->timebase; + for (int n = 0; n < p->num_streams; n++) { + struct mux_stream *s = p->streams[n]; + if (s->codec_type == codec_type) + return s; + } - ctx->aoptions = 0; + return NULL; +} - mp_set_avdict(&ctx->aoptions, ctx->options->aopts); +void encode_lavc_expect_stream(struct encode_lavc_context *ctx, + enum stream_type type) +{ + struct encode_priv *p = ctx->priv; - de = av_dict_get(ctx->aoptions, "global_quality", NULL, 0); - if (de) - av_dict_set(&ctx->aoptions, "flags", "+qscale", AV_DICT_APPEND); + pthread_mutex_lock(&ctx->lock); - if (ctx->avc->oformat->flags & AVFMT_GLOBALHEADER) - av_dict_set(&ctx->aoptions, "flags", "+global_header", AV_DICT_APPEND); + enum AVMediaType codec_type = mp_to_av_stream_type(type); - encode_2pass_prepare(ctx, &ctx->aoptions, ctx->ast, ctx->acc, - &ctx->twopass_bytebuffer_a, - "ao-lavc"); + // These calls are idempotent. + if (find_mux_stream(ctx, codec_type)) + goto done; - *stream_out = ctx->ast; - *codec_out = ctx->acc; - return 0; + if (p->header_written) { + MP_ERR(p, "Cannot add a stream during encoding.\n"); + p->failed = true; + goto done; } - // Unreachable. - return -1; + 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), + }; + MP_TARRAY_APPEND(p, p->streams, p->num_streams, dst); + +done: + pthread_mutex_unlock(&ctx->lock); } -int encode_lavc_open_codec(struct encode_lavc_context *ctx, - AVCodecContext *codec) +// 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 struct mux_stream *encode_lavc_add_stream(struct encode_lavc_context *ctx, + struct encoder_stream_info *info) { - AVDictionaryEntry *de; - int ret; - - CHECK_FAIL(ctx, -1); - - switch (codec->codec_type) { - case AVMEDIA_TYPE_VIDEO: - MP_INFO(ctx, "Opening video encoder: %s [%s]\n", - ctx->vc->long_name, ctx->vc->name); - - if (ctx->vc->capabilities & AV_CODEC_CAP_EXPERIMENTAL) { - codec->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL; - MP_WARN(ctx, "\n\n" - " ********************************************\n" - " **** Experimental VIDEO 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 (--ovcopts) to maybe still get the\n" - " desired quality output at the expense of bitrate.\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", - ctx->vc->name); - } + struct encode_priv *p = ctx->priv; - ret = avcodec_open2(codec, ctx->vc, &ctx->voptions); - if (ret >= 0) - ret = avcodec_parameters_from_context(ctx->vst->codecpar, codec); - - // complain about all remaining options, then free the dict - for (de = NULL; (de = av_dict_get(ctx->voptions, "", de, - AV_DICT_IGNORE_SUFFIX));) - MP_WARN(ctx, "ovcopts: key '%s' not found.\n", - de->key); - av_dict_free(&ctx->voptions); - - break; - case AVMEDIA_TYPE_AUDIO: - MP_INFO(ctx, "Opening audio encoder: %s [%s]\n", - ctx->ac->long_name, ctx->ac->name); - - if (ctx->ac->capabilities & AV_CODEC_CAP_EXPERIMENTAL) { - codec->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL; - MP_WARN(ctx, "\n\n" - " ********************************************\n" - " **** Experimental AUDIO 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 sound quality, noise, clicking, whistles, choppiness.\n" - " Experiment with codec settings (--oacopts) to maybe still get the\n" - " desired quality output at the expense of bitrate.\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", - ctx->ac->name); - } + pthread_mutex_lock(&ctx->lock); - ret = avcodec_open2(codec, ctx->ac, &ctx->aoptions); - if (ret >= 0) - ret = avcodec_parameters_from_context(ctx->ast->codecpar, codec); - - // complain about all remaining options, then free the dict - for (de = NULL; (de = av_dict_get(ctx->aoptions, "", de, - AV_DICT_IGNORE_SUFFIX));) - MP_WARN(ctx, "oacopts: key '%s' not found.\n", - de->key); - av_dict_free(&ctx->aoptions); - - break; - default: - ret = -1; - break; + 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; } - if (ret < 0) - encode_lavc_fail(ctx, - "unable to open encoder (see above for the cause)\n"); + dst->st = avformat_new_stream(p->muxer, NULL); + MP_HANDLE_OOM(dst->st); - return ret; -} + dst->enc |