diff --git a/DOCS/man/encode.rst b/DOCS/man/encode.rst index 0edf7e7215..9169f4781b 100644 --- a/DOCS/man/encode.rst +++ b/DOCS/man/encode.rst @@ -8,9 +8,8 @@ You can encode files from one format/codec to another using this facility. ``--of=`` Specifies the output format (overrides autodetection by the file name - extension of the file specified by ``-o``). This can be a comma separated - list of possible formats to try. See ``--of=help`` for a full list of - supported formats. + extension of the file specified by ``-o``). See ``--of=help`` for a full + list of supported formats. ``--ofopts=`` Specifies the output format options for libavformat. @@ -59,9 +58,8 @@ You can encode files from one format/codec to another using this facility. avoid ``--oautofps``. ``--oac=`` - Specifies the output audio codec. This can be a comma separated list of - possible codecs to try. See ``--oac=help`` for a full list of supported - codecs. + Specifies the output audio codec. See ``--oac=help`` for a full list of + supported codecs. ``--oaoffset=`` Shifts audio data by the given time (in seconds) by adding/removing @@ -97,9 +95,8 @@ You can encode files from one format/codec to another using this facility. By default, the order is unspecified. Deprecated. ``--ovc=`` - Specifies the output video codec. This can be a comma separated list of - possible codecs to try. See ``--ovc=help`` for a full list of supported - codecs. + Specifies the output video codec. See ``--ovc=help`` for a full list of + supported codecs. ``--ovoffset=`` Shifts video data by the given time (in seconds) by shifting the pts diff --git a/audio/out/ao_lavc.c b/audio/out/ao_lavc.c index aaa9e6422c..e18db667a3 100644 --- a/audio/out/ao_lavc.c +++ b/audio/out/ao_lavc.c @@ -40,8 +40,8 @@ #include "common/encode_lavc.h" struct priv { - AVStream *stream; - AVCodecContext *codec; + struct encoder_context *enc; + int pcmhack; int aframesize; int aframecount; @@ -53,14 +53,13 @@ struct priv { double expected_next_pts; AVRational worst_time_base; - int worst_time_base_is_stream; bool shutdown; }; static void encode(struct ao *ao, double apts, void **data); -static bool supports_format(AVCodec *codec, int format) +static bool supports_format(const AVCodec *codec, int format) { for (const enum AVSampleFormat *sampleformat = codec->sample_fmts; sampleformat && *sampleformat != AV_SAMPLE_FMT_NONE; @@ -72,7 +71,7 @@ static bool supports_format(AVCodec *codec, int format) return false; } -static void select_format(struct ao *ao, AVCodec *codec) +static void select_format(struct ao *ao, const AVCodec *codec) { int formats[AF_FORMAT_COUNT + 1]; af_get_best_sample_formats(ao->format, formats); @@ -88,70 +87,53 @@ static void select_format(struct ao *ao, AVCodec *codec) // open & setup audio device static int init(struct ao *ao) { - struct priv *ac = talloc_zero(ao, struct priv); - AVCodec *codec; + struct priv *ac = ao->priv; - ao->priv = ac; - - if (!encode_lavc_available(ao->encode_lavc_ctx)) { - MP_ERR(ao, "the option --o (output file) must be specified\n"); + ac->enc = encoder_context_alloc(ao->encode_lavc_ctx, STREAM_AUDIO, ao->log); + if (!ac->enc) return -1; - } + talloc_steal(ac, ac->enc); - pthread_mutex_lock(&ao->encode_lavc_ctx->lock); - - if (encode_lavc_alloc_stream(ao->encode_lavc_ctx, - AVMEDIA_TYPE_AUDIO, - &ac->stream, &ac->codec) < 0) - { - MP_ERR(ao, "could not get a new audio stream\n"); - goto fail; - } - - codec = ao->encode_lavc_ctx->ac; + AVCodecContext *encoder = ac->enc->encoder; + const AVCodec *codec = encoder->codec; int samplerate = af_select_best_samplerate(ao->samplerate, codec->supported_samplerates); if (samplerate > 0) ao->samplerate = samplerate; - // TODO: Remove this redundancy with encode_lavc_alloc_stream also - // setting the time base. - // Using codec->time_base is deprecated, but needed for older lavf. - ac->stream->time_base.num = 1; - ac->stream->time_base.den = ao->samplerate; - ac->codec->time_base.num = 1; - ac->codec->time_base.den = ao->samplerate; + encoder->time_base.num = 1; + encoder->time_base.den = ao->samplerate; - ac->codec->sample_rate = ao->samplerate; + encoder->sample_rate = ao->samplerate; struct mp_chmap_sel sel = {0}; mp_chmap_sel_add_any(&sel); if (!ao_chmap_sel_adjust2(ao, &sel, &ao->channels, false)) goto fail; mp_chmap_reorder_to_lavc(&ao->channels); - ac->codec->channels = ao->channels.num; - ac->codec->channel_layout = mp_chmap_to_lavc(&ao->channels); + encoder->channels = ao->channels.num; + encoder->channel_layout = mp_chmap_to_lavc(&ao->channels); - ac->codec->sample_fmt = AV_SAMPLE_FMT_NONE; + encoder->sample_fmt = AV_SAMPLE_FMT_NONE; select_format(ao, codec); ac->sample_size = af_fmt_to_bytes(ao->format); - ac->codec->sample_fmt = af_to_avformat(ao->format); - ac->codec->bits_per_raw_sample = ac->sample_size * 8; + encoder->sample_fmt = af_to_avformat(ao->format); + encoder->bits_per_raw_sample = ac->sample_size * 8; - if (encode_lavc_open_codec(ao->encode_lavc_ctx, ac->codec) < 0) + if (!encoder_init_codec_and_muxer(ac->enc)) goto fail; ac->pcmhack = 0; - if (ac->codec->frame_size <= 1) - ac->pcmhack = av_get_bits_per_sample(ac->codec->codec_id) / 8; + if (encoder->frame_size <= 1) + ac->pcmhack = av_get_bits_per_sample(encoder->codec_id) / 8; if (ac->pcmhack) { ac->aframesize = 16384; // "enough" } else { - ac->aframesize = ac->codec->frame_size; + ac->aframesize = encoder->frame_size; } // enough frames for at least 0.25 seconds @@ -169,7 +151,6 @@ static int init(struct ao *ao) if (ao->channels.num > AV_NUM_DATA_POINTERS) goto fail; - pthread_mutex_unlock(&ao->encode_lavc_ctx->lock); return 0; fail: @@ -184,28 +165,17 @@ static void uninit(struct ao *ao) struct priv *ac = ao->priv; struct encode_lavc_context *ectx = ao->encode_lavc_ctx; - if (!ac || ac->shutdown) - return; - - pthread_mutex_lock(&ectx->lock); - - if (!encode_lavc_start(ectx)) { - MP_WARN(ao, "not even ready to encode audio at end -> dropped\n"); - pthread_mutex_unlock(&ectx->lock); - return; - } - - if (ac->stream) { + if (!ac->shutdown) { double outpts = ac->expected_next_pts; - if (!ectx->options->rawts && ectx->options->copyts) + + pthread_mutex_lock(&ectx->lock); + if (!ac->enc->options->rawts && ac->enc->options->copyts) outpts += ectx->discontinuity_pts_offset; - outpts += encode_lavc_getoffset(ectx, ac->codec); + pthread_mutex_unlock(&ectx->lock); + + outpts += encoder_get_offset(ac->enc); encode(ao, outpts, NULL); } - - pthread_mutex_unlock(&ectx->lock); - - ac->shutdown = true; } // return: how many samples can be played without blocking @@ -216,106 +186,21 @@ static int get_space(struct ao *ao) return ac->aframesize * ac->framecount; } -static void write_packet(struct ao *ao, AVPacket *packet) -{ - // TODO: Can we unify this with the equivalent video code path? - struct priv *ac = ao->priv; - - packet->stream_index = ac->stream->index; - if (packet->pts != AV_NOPTS_VALUE) { - packet->pts = av_rescale_q(packet->pts, - ac->codec->time_base, - ac->stream->time_base); - } else { - // Do we need this at all? Better be safe than sorry... - MP_WARN(ao, "encoder lost pts, why?\n"); - if (ac->savepts != MP_NOPTS_VALUE) { - packet->pts = av_rescale_q(ac->savepts, - ac->codec->time_base, - ac->stream->time_base); - } - } - if (packet->dts != AV_NOPTS_VALUE) { - packet->dts = av_rescale_q(packet->dts, - ac->codec->time_base, - ac->stream->time_base); - } - if (packet->duration > 0) { - packet->duration = av_rescale_q(packet->duration, - ac->codec->time_base, - ac->stream->time_base); - } - - ac->savepts = AV_NOPTS_VALUE; - - if (encode_lavc_write_frame(ao->encode_lavc_ctx, ac->stream, packet) < 0) { - MP_ERR(ao, "error writing at %d %d/%d\n", - (int) packet->pts, - ac->stream->time_base.num, - ac->stream->time_base.den); - return; - } -} - -static void encode_audio_and_write(struct ao *ao, AVFrame *frame) -{ - // TODO: Can we unify this with the equivalent video code path? - struct priv *ac = ao->priv; - AVPacket packet = {0}; - - int status = avcodec_send_frame(ac->codec, frame); - if (status < 0) { - MP_ERR(ao, "error encoding at %d %d/%d\n", - frame ? (int) frame->pts : -1, - ac->codec->time_base.num, - ac->codec->time_base.den); - return; - } - - for (;;) { - av_init_packet(&packet); - status = avcodec_receive_packet(ac->codec, &packet); - if (status == AVERROR(EAGAIN)) { // No more packets for now. - if (frame == NULL) { - MP_ERR(ao, "sent flush frame, got EAGAIN"); - } - break; - } - if (status == AVERROR_EOF) { // No more packets, ever. - if (frame != NULL) { - MP_ERR(ao, "sent audio frame, got EOF"); - } - break; - } - if (status < 0) { - MP_ERR(ao, "error encoding at %d %d/%d\n", - frame ? (int) frame->pts : -1, - ac->codec->time_base.num, - ac->codec->time_base.den); - break; - } - if (frame) { - if (ac->savepts == AV_NOPTS_VALUE) - ac->savepts = frame->pts; - } - encode_lavc_write_stats(ao->encode_lavc_ctx, ac->codec); - write_packet(ao, &packet); - av_packet_unref(&packet); - } -} - // must get exactly ac->aframesize amount of data static void encode(struct ao *ao, double apts, void **data) { struct priv *ac = ao->priv; struct encode_lavc_context *ectx = ao->encode_lavc_ctx; + AVCodecContext *encoder = ac->enc->encoder; double realapts = ac->aframecount * (double) ac->aframesize / ao->samplerate; ac->aframecount++; + pthread_mutex_lock(&ectx->lock); if (data) ectx->audio_pts_offset = realapts - apts; + pthread_mutex_unlock(&ectx->lock); if(data) { AVFrame *frame = av_frame_alloc(); @@ -329,17 +214,17 @@ static void encode(struct ao *ao, double apts, void **data) frame->linesize[0] = frame->nb_samples * ao->sstride; - if (ectx->options->rawts || ectx->options->copyts) { + if (ac->enc->options->rawts || ac->enc->options->copyts) { // real audio pts - frame->pts = floor(apts * ac->codec->time_base.den / - ac->codec->time_base.num + 0.5); + frame->pts = floor(apts * encoder->time_base.den / + encoder->time_base.num + 0.5); } else { // audio playback time - frame->pts = floor(realapts * ac->codec->time_base.den / - ac->codec->time_base.num + 0.5); + frame->pts = floor(realapts * encoder->time_base.den / + encoder->time_base.num + 0.5); } - int64_t frame_pts = av_rescale_q(frame->pts, ac->codec->time_base, + int64_t frame_pts = av_rescale_q(frame->pts, encoder->time_base, ac->worst_time_base); if (ac->lastpts != AV_NOPTS_VALUE && frame_pts <= ac->lastpts) { // this indicates broken video @@ -348,15 +233,15 @@ static void encode(struct ao *ao, double apts, void **data) (int)frame->pts, (int)ac->lastpts); frame_pts = ac->lastpts + 1; frame->pts = av_rescale_q(frame_pts, ac->worst_time_base, - ac->codec->time_base); + encoder->time_base); } ac->lastpts = frame_pts; - frame->quality = ac->codec->global_quality; - encode_audio_and_write(ao, frame); + frame->quality = encoder->global_quality; + encoder_encode(ac->enc, frame); av_frame_free(&frame); } else { - encode_audio_and_write(ao, NULL); + encoder_encode(ac->enc, NULL); } } @@ -365,20 +250,16 @@ static void encode(struct ao *ao, double apts, void **data) static int play(struct ao *ao, void **data, int samples, int flags) { struct priv *ac = ao->priv; + struct encoder_context *enc = ac->enc; struct encode_lavc_context *ectx = ao->encode_lavc_ctx; int bufpos = 0; double nextpts; double outpts; int orig_samples = samples; + // for ectx PTS fields pthread_mutex_lock(&ectx->lock); - if (!encode_lavc_start(ectx)) { - MP_WARN(ao, "not ready yet for encoding audio\n"); - pthread_mutex_unlock(&ectx->lock); - return 0; - } - double pts = ectx->last_audio_in_pts; pts += ectx->samples_since_last_pts / (double)ao->samplerate; @@ -407,26 +288,10 @@ static int play(struct ao *ao, void **data, int samples, int flags) } if (ac->worst_time_base.den == 0) { - if (ac->codec->time_base.num * (double) ac->stream->time_base.den >= - ac->stream->time_base.num * (double) ac->codec->time_base.den) { - MP_VERBOSE(ao, "NOTE: using codec time base (%d/%d) for pts " - "adjustment; the stream base (%d/%d) is not worse.\n", - (int)ac->codec->time_base.num, - (int)ac->codec->time_base.den, - (int)ac->stream->time_base.num, - (int)ac->stream->time_base.den); - ac->worst_time_base = ac->codec->time_base; - ac->worst_time_base_is_stream = 0; - } else { - MP_WARN(ao, "NOTE: not using codec time base (%d/%d) for pts " - "adjustment; the stream base (%d/%d) is worse.\n", - (int)ac->codec->time_base.num, - (int)ac->codec->time_base.den, - (int)ac->stream->time_base.num, - (int)ac->stream->time_base.den); - ac->worst_time_base = ac->stream->time_base; - ac->worst_time_base_is_stream = 1; - } + // We don't know the muxer time_base anymore, and can't, because we + // might start encoding before the muxer is opened. (The muxer decides + // the final AVStream.time_base when opening the muxer.) + ac->worst_time_base = enc->encoder->time_base; // NOTE: we use the following "axiom" of av_rescale_q: // if time base A is worse than time base B, then @@ -446,7 +311,7 @@ static int play(struct ao *ao, void **data, int samples, int flags) } // Fix and apply the discontinuity pts offset. - if (!ectx->options->rawts && ectx->options->copyts) { + if (!enc->options->rawts && enc->options->copyts) { // fix the discontinuity pts offset nextpts = pts; if (ectx->discontinuity_pts_offset == MP_NOPTS_VALUE) { @@ -465,8 +330,10 @@ static int play(struct ao *ao, void **data, int samples, int flags) outpts = pts; } + pthread_mutex_unlock(&ectx->lock); + // Shift pts by the pts offset first. - outpts += encode_lavc_getoffset(ectx, ac->codec); + outpts += encoder_get_offset(enc); while (samples - bufpos >= ac->aframesize) { void *start[MP_NUM_CHANNELS] = {0}; @@ -479,8 +346,10 @@ static int play(struct ao *ao, void **data, int samples, int flags) // Calculate expected pts of next audio frame (input side). ac->expected_next_pts = pts + bufpos / (double) ao->samplerate; + pthread_mutex_lock(&ectx->lock); + // Set next allowed input pts value (input side). - if (!ectx->options->rawts && ectx->options->copyts) { + if (!enc->options->rawts && enc->options->copyts) { nextpts = ac->expected_next_pts + ectx->discontinuity_pts_offset; if (nextpts > ectx->next_in_pts) ectx->next_in_pts = nextpts; @@ -513,6 +382,7 @@ const struct ao_driver audio_out_lavc = { .encode = true, .description = "audio encoding using libavcodec", .name = "lavc", + .priv_size = sizeof(struct priv), .init = init, .uninit = uninit, .get_space = get_space, 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 +#include #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) +static void write_remaining_packets(struct encode_lavc_context *ctx); + +struct encode_lavc_context *encode_lavc_init(struct mpv_global *global) { - 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; -} + 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); -#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; \ - } + struct encode_priv *p = ctx->priv; + p->log = ctx->log; -#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) -{ - CHECK_FAIL(ctx, 0); - return ctx && ctx->avc; -} - -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; + 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(); + p->muxer = avformat_alloc_context(); + MP_HANDLE_OOM(p->muxer); - 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; - } + if (ctx->options->format && ctx->options->format[0]) { + ctx->oformat = av_guess_format(ctx->options->format, filename, NULL); } else { - ctx->avc->oformat = av_guess_format(NULL, filename, NULL); + ctx->oformat = av_guess_format(NULL, filename, NULL); } - if (!ctx->avc->oformat) { - encode_lavc_fail(ctx, "format not found\n"); - return NULL; + if (!ctx->oformat) { + MP_FATAL(ctx, "format not found\n"); + goto fail; } - ctx->avc->url = av_strdup(filename); + p->muxer->oformat = ctx->oformat; - 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; - } - } else { - ctx->vc = avcodec_find_encoder(av_guess_codec(ctx->avc->oformat, NULL, - ctx->avc->url, NULL, - AVMEDIA_TYPE_VIDEO)); - } - - 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; - } - - /* taken from ffmpeg unchanged - * TODO turn this into an option if anyone needs this */ - - 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; - - if (!(ctx->avc->oformat->flags & AVFMT_NOFILE)) { - MP_INFO(ctx, "Opening output file: %s\n", ctx->avc->url); - - 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; - } - } - - ctx->t0 = mp_time_sec(); - - MP_INFO(ctx, "Opening muxer: %s [%s]\n", - ctx->avc->oformat->long_name, ctx->avc->oformat->name); - - 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); - } - } - - if (avformat_write_header(ctx->avc, &ctx->foptions) < 0) { - encode_lavc_fail(ctx, "could not write header\n"); - return 0; - } - - 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); - } - av_dict_free(&ctx->foptions); - - ctx->header_written = 1; - return 1; -} - -void encode_lavc_free(struct encode_lavc_context *ctx) -{ - unsigned i; - + bool res = true; if (!ctx) - return; + return res; - if (ctx->avc) { - if (ctx->header_written > 0) - av_write_trailer(ctx->avc); // this is allowed to fail + struct encode_priv *p = ctx->priv; - 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; + if (!p->failed && !p->header_written) { + MP_FATAL(p, "no data written to target file\n"); + p->failed = true; } + write_remaining_packets(ctx); + + 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; + 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); + struct encode_priv *p = ctx->priv; - snprintf(buf, sizeof(buf), "%s-%s-pass1.log", ctx->avc->url, prefix); + if (!p->header_written && !p->failed) + return; // wait until muxer initialization - 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; - } + 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]; -int encode_lavc_alloc_stream(struct encode_lavc_context *ctx, - enum AVMediaType mt, - AVStream **stream_out, - AVCodecContext **codec_out) -{ - AVDictionaryEntry *de; + pkt->stream_index = s->st->index; + assert(s->st == p->muxer->streams[pkt->stream_index]); - *stream_out = NULL; - *codec_out = NULL; + av_packet_rescale_ts(pkt, s->encoder_timebase, s->st->time_base); - CHECK_FAIL(ctx, -1); - - if (ctx->header_written) - return -1; - - 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 - } - } - - // 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; - } - - if (ctx->timebase.den == 0) { - AVRational r; - - 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 (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); - - // Using codec->time_base is deprecated, but needed for older lavf. - ctx->vst->time_base = ctx->timebase; - ctx->vcc->time_base = ctx->timebase; - - ctx->voptions = NULL; - - mp_set_avdict(&ctx->voptions, ctx->options->vopts); - - de = av_dict_get(ctx->voptions, "global_quality", NULL, 0); - if (de) - av_dict_set(&ctx->voptions, "flags", "+qscale", AV_DICT_APPEND); - - if (ctx->avc->oformat->flags & AVFMT_GLOBALHEADER) - av_dict_set(&ctx->voptions, "flags", "+global_header", AV_DICT_APPEND); - - 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; - - 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); - - // Using codec->time_base is deprecated, but needed for older lavf. - ctx->ast->time_base = ctx->timebase; - ctx->acc->time_base = ctx->timebase; - - ctx->aoptions = 0; - - mp_set_avdict(&ctx->aoptions, ctx->options->aopts); - - de = av_dict_get(ctx->aoptions, "global_quality", NULL, 0); - if (de) - av_dict_set(&ctx->aoptions, "flags", "+qscale", AV_DICT_APPEND); - - if (ctx->avc->oformat->flags & AVFMT_GLOBALHEADER) - av_dict_set(&ctx->aoptions, "flags", "+global_header", AV_DICT_APPEND); - - encode_2pass_prepare(ctx, &ctx->aoptions, ctx->ast, ctx->acc, - &ctx->twopass_bytebuffer_a, - "ao-lavc"); - - *stream_out = ctx->ast; - *codec_out = ctx->acc; - return 0; - } - - // Unreachable. - return -1; -} - -int encode_lavc_open_codec(struct encode_lavc_context *ctx, - AVCodecContext *codec) -{ - 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); - } - - 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); - } - - 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; - } - - if (ret < 0) - encode_lavc_fail(ctx, - "unable to open encoder (see above for the cause)\n"); - - return ret; -} - -void encode_lavc_write_stats(struct encode_lavc_context *ctx, - AVCodecContext *codec) -{ - CHECK_FAIL(ctx, ); - - switch (codec->codec_type) { - case AVMEDIA_TYPE_VIDEO: - if (ctx->twopass_bytebuffer_v) { - if (codec->stats_out) { - stream_write_buffer(ctx->twopass_bytebuffer_v, - codec->stats_out, - strlen(codec->stats_out)); - } - } - break; - case AVMEDIA_TYPE_AUDIO: - if (ctx->twopass_bytebuffer_a) { - if (codec->stats_out) { - stream_write_buffer(ctx->twopass_bytebuffer_a, - codec->stats_out, - strlen(codec->stats_out)); - } - } - break; - default: - break; - } -} - -int encode_lavc_write_frame(struct encode_lavc_context *ctx, AVStream *stream, - AVPacket *packet) -{ - int r; - - CHECK_FAIL(ctx, -1); - - if (stream->index != packet->stream_index) { - MP_ERR(ctx, "Called encode_lavc_write_frame on the wrong stream\n"); - return -1; - } - - if (ctx->header_written <= 0) - return -1; - - MP_TRACE(ctx, - "write frame: stream %d ptsi %d (%f) dtsi %d (%f) size %d\n", - (int)packet->stream_index, - (int)packet->pts, - packet->pts - * (double)stream->time_base.num - / (double)stream->time_base.den, - (int)packet->dts, - packet->dts - * (double)stream->time_base.num - / (double)stream->time_base.den, - (int)packet->size); - - - switch (stream->codecpar->codec_type) { + switch (s->st->codecpar->codec_type) { case AVMEDIA_TYPE_VIDEO: - ctx->vbytes += packet->size; - ctx->frames += 1; + p->vbytes += pkt->size; + p->frames += 1; break; case AVMEDIA_TYPE_AUDIO: - ctx->abytes += packet->size; - ctx->audioseconds += packet->duration - * (double)stream->time_base.num - / (double)stream->time_base.den; + p->abytes += pkt->size; + p->audioseconds += pkt->duration + * (double)s->st->time_base.num + / (double)s->st->time_base.den; break; - default: - break; - } + } - r = av_interleaved_write_frame(ctx->avc, packet); - - return r; -} - -int encode_lavc_supports_pixfmt(struct encode_lavc_context *ctx, - enum AVPixelFormat pix_fmt) -{ - CHECK_FAIL(ctx, 0); - - if (!ctx->vc) - return 0; - if (pix_fmt == AV_PIX_FMT_NONE) - return 0; - - if (!ctx->vc->pix_fmts) - return 1; - else { - const enum AVPixelFormat *p; - for (p = ctx->vc->pix_fmts; *p >= 0; ++p) { - if (pix_fmt == *p) - return 1; + if (av_interleaved_write_frame(p->muxer, pkt) < 0) { + MP_ERR(p, "Writing packet failed.\n"); + p->failed = true; } } - return 0; + + p->num_packets = 0; +} + +// 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) { + 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 (!(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; + + write_remaining_packets(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; + + pthread_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), + }; + MP_TARRAY_APPEND(p, p->streams, p->num_streams, dst); + +done: + pthread_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 struct mux_stream *encode_lavc_add_stream(struct encode_lavc_context *ctx, + struct encoder_stream_info *info) +{ + struct encode_priv *p = ctx->priv; + + pthread_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 + if (avcodec_parameters_copy(dst->st->codecpar, info->codecpar) < 0) + MP_HANDLE_OOM(0); + + maybe_init_muxer(ctx); + +done: + pthread_mutex_unlock(&ctx->lock); + + return dst; +} + +// Write a packet. Callee will create new pkt refs as needed. +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); + + pthread_mutex_lock(&ctx->lock); + + if (p->failed) + goto done; + + AVPacket *new_packet = av_packet_clone(pkt); + if (pkt && !new_packet) + MP_HANDLE_OOM(0); + + new_packet->stream_index = dst->index; // not the lavf index (yet) + MP_TARRAY_APPEND(p, p->packets, p->num_packets, new_packet); + + write_remaining_packets(ctx); + +done: + pthread_mutex_unlock(&ctx->lock); } void encode_lavc_discontinuity(struct encode_lavc_context *ctx) @@ -811,10 +489,7 @@ void encode_lavc_discontinuity(struct encode_lavc_context *ctx) pthread_mutex_lock(&ctx->lock); - CHECK_FAIL_UNLOCK(ctx, ); - ctx->audio_pts_offset = MP_NOPTS_VALUE; - ctx->last_video_in_pts = MP_NOPTS_VALUE; ctx->discontinuity_pts_offset = MP_NOPTS_VALUE; pthread_mutex_unlock(&ctx->lock); @@ -1025,44 +700,34 @@ bool encode_lavc_showhelp(struct mp_log *log, struct encode_opts *opts) return help_output; } -double encode_lavc_getoffset(struct encode_lavc_context *ctx, - AVCodecContext *codec) -{ - CHECK_FAIL(ctx, 0); - - switch (codec->codec_type) { - case AVMEDIA_TYPE_VIDEO: - return ctx->options->voffset; - case AVMEDIA_TYPE_AUDIO: - return ctx->options->aoffset; - default: - break; - } - return 0; -} - int encode_lavc_getstatus(struct encode_lavc_context *ctx, char *buf, int bufsize, float relative_position) { - double now = mp_time_sec(); - float minutes, megabytes, fps, x; - float f = FFMAX(0.0001, relative_position); if (!ctx) return -1; + struct encode_priv *p = ctx->priv; + + double now = mp_time_sec(); + float minutes, megabytes, fps, x; + float f = FFMAX(0.0001, relative_position); + pthread_mutex_lock(&ctx->lock); - CHECK_FAIL_UNLOCK(ctx, -1); + if (p->failed) { + snprintf(buf, bufsize, "(failed)\n"); + goto done; + } - minutes = (now - ctx->t0) / 60.0 * (1 - f) / f; - megabytes = ctx->avc->pb ? (avio_size(ctx->avc->pb) / 1048576.0 / f) : 0; - fps = ctx->frames / (now - ctx->t0); - x = ctx->audioseconds / (now - ctx->t0); - if (ctx->frames) { + 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 (ctx->audioseconds) { + } else if (p->audioseconds) { snprintf(buf, bufsize, "{%.1fmin %.2fx %.1fMB}", minutes, x, megabytes); } else { @@ -1071,47 +736,226 @@ int encode_lavc_getstatus(struct encode_lavc_context *ctx, } buf[bufsize - 1] = 0; +done: pthread_mutex_unlock(&ctx->lock); return 0; } -void encode_lavc_expect_stream(struct encode_lavc_context *ctx, int mt) -{ - pthread_mutex_lock(&ctx->lock); - - CHECK_FAIL_UNLOCK(ctx, ); - - switch (mt) { - case AVMEDIA_TYPE_VIDEO: - ctx->expect_video = true; - break; - case AVMEDIA_TYPE_AUDIO: - ctx->expect_audio = true; - break; - } - - pthread_mutex_unlock(&ctx->lock); -} - bool encode_lavc_didfail(struct encode_lavc_context *ctx) { if (!ctx) return false; pthread_mutex_lock(&ctx->lock); - bool fail = ctx && ctx->failed; + bool fail = ctx->priv->failed; pthread_mutex_unlock(&ctx->lock); return fail; } -void encode_lavc_fail(struct encode_lavc_context *ctx, const char *format, ...) +static void encoder_destroy(void *ptr) { - va_list va; - va_start(va, format); - mp_msg_va(ctx->log, MSGL_ERR, format, va); - va_end(va); - if (ctx->failed) - return; - ctx->failed = true; + struct encoder_context *p = ptr; + + avcodec_free_context(&p->encoder); + free_stream(p->twopass_bytebuffer); +} + +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, + }; + + char *codec_name = type == STREAM_VIDEO + ? p->options->vcodec + : p->options->acodec; + enum AVMediaType codec_type = mp_to_av_stream_type(type); + const char *tname = stream_type_name(type); + + AVCodec *codec; + if (codec_name&& codec_name[0]) { + codec = avcodec_find_encoder_by_name(codec_name); + } else { + codec = avcodec_find_encoder(av_guess_codec(p->oformat, NULL, + p->options->file, NULL, + codec_type)); + } + + if (!codec) { + MP_FATAL(p, "codec for %s not found\n", tname); + goto fail; + } + if (codec->type != codec_type) { + MP_FATAL(p, "codec for %s has wrong media type\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) { + struct stream *s = stream_open(filename, 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) { + 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) +{ + 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->mux_stream = encode_lavc_add_stream(p->encode_lavc_ctx, &p->info); + 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; + } + + for (;;) { + AVPacket packet = {0}; + av_init_packet(&packet); + + status = avcodec_receive_packet(p->encoder, &packet); + if (status == AVERROR(EAGAIN) || status == AVERROR_EOF) + break; + if (status < 0) + 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)); + } + + encode_lavc_add_packet(p->mux_stream, &packet); + av_packet_unref(&packet); + } + + return true; + +fail: + MP_ERR(p, "error encoding at %s\n", + frame ? av_ts2timestr(frame->pts, &p->encoder->time_base) : "EOF"); + return false; +} + +double encoder_get_offset(struct encoder_context *p) +{ + switch (p->encoder->codec_type) { + case AVMEDIA_TYPE_VIDEO: return p->options->voffset; + case AVMEDIA_TYPE_AUDIO: return p->options->aoffset; + default: return 0; + } } // vim: ts=4 sw=4 et diff --git a/common/encode_lavc.h b/common/encode_lavc.h index 467520cc6d..a689b1ea47 100644 --- a/common/encode_lavc.h +++ b/common/encode_lavc.h @@ -31,43 +31,26 @@ #include #include +#include "common/common.h" #include "encode.h" #include "video/csputils.h" struct encode_lavc_context { + // --- Immutable after init struct mpv_global *global; struct encode_opts *options; struct mp_log *log; - struct mp_tags *metadata; + struct encode_priv *priv; + AVOutputFormat *oformat; + const char *filename; // All entry points must be guarded with the lock. Functions called by // the playback core lock this automatically, but ao_lavc.c and vo_lavc.c // must lock manually before accessing state. pthread_mutex_t lock; - float vo_fps; - - // FFmpeg contexts. - AVFormatContext *avc; - AVStream *vst; - AVStream *ast; - AVCodecContext *vcc; - AVCodecContext *acc; - - // these are processed from the options - AVRational timebase; - AVCodec *vc; - AVCodec *ac; - AVDictionary *foptions; - AVDictionary *aoptions; - AVDictionary *voptions; - - // values created during encoding - int header_written; // -1 means currently writing - // sync to audio mode double audio_pts_offset; - double last_video_in_pts; double last_audio_in_pts; int64_t samples_since_last_pts; @@ -75,40 +58,54 @@ struct encode_lavc_context { // anti discontinuity mode double next_in_pts; double discontinuity_pts_offset; - - long long abytes; - long long vbytes; - struct stream *twopass_bytebuffer_a; - struct stream *twopass_bytebuffer_v; - double t0; - unsigned int frames; - double audioseconds; - - bool expect_video; - bool expect_audio; - bool video_first; - bool audio_first; - - // has encoding failed? - bool failed; }; -// interface for vo/ao drivers -int encode_lavc_alloc_stream(struct encode_lavc_context *ctx, - enum AVMediaType mt, AVStream **stream_out, - AVCodecContext **codec_out); -void encode_lavc_write_stats(struct encode_lavc_context *ctx, - AVCodecContext *stream); -int encode_lavc_write_frame(struct encode_lavc_context *ctx, AVStream *stream, - AVPacket *packet); -int encode_lavc_supports_pixfmt(struct encode_lavc_context *ctx, enum AVPixelFormat format); -int encode_lavc_open_codec(struct encode_lavc_context *ctx, - AVCodecContext *codec); -int encode_lavc_available(struct encode_lavc_context *ctx); -int encode_lavc_timesyncfailed(struct encode_lavc_context *ctx); -int encode_lavc_start(struct encode_lavc_context *ctx); // returns 1 on success -double encode_lavc_getoffset(struct encode_lavc_context *ctx, - AVCodecContext *codec); -void encode_lavc_fail(struct encode_lavc_context *ctx, const char *format, ...); // report failure of encoding +// --- interface for vo/ao drivers + +// Static information after encoder init. This never changes (even if there are +// dynamic runtime changes, they have to work over AVPacket side data). +// For use in encoder_context, most fields are copied from encoder_context.encoder +// by encoder_init_codec_and_muxer(). +struct encoder_stream_info { + AVRational timebase; // timebase used by the encoder (in frames/out packets) + AVCodecParameters *codecpar; +}; + +// The encoder parts for each stream (no muxing parts included). +// This is private to each stream. +struct encoder_context { + struct mpv_global *global; + struct encode_opts *options; + struct mp_log *log; + AVOutputFormat *oformat; + + // (avoid using this) + struct encode_lavc_context *encode_lavc_ctx; + + enum stream_type type; + + // (different access restrictions before/after encoder init) + struct encoder_stream_info info; + AVCodecContext *encoder; + struct mux_stream *mux_stream; + + struct stream *twopass_bytebuffer; +}; + +// Free with talloc_free(). (Keep in mind actual deinitialization requires +// sending a flush packet.) +// This can fail and return NULL. +struct encoder_context *encoder_context_alloc(struct encode_lavc_context *ctx, + enum stream_type type, + struct mp_log *log); + +// After setting your codec parameters on p->encoder, you call this to "open" +// the encoder. This also initializes p->mux_stream. Returns false on failure. +bool encoder_init_codec_and_muxer(struct encoder_context *p); + +// Encode the frame and write the packet. frame is ref'ed as need. +bool encoder_encode(struct encoder_context *p, AVFrame *frame); + +double encoder_get_offset(struct encoder_context *p); #endif diff --git a/etc/encoding-profiles.conf b/etc/encoding-profiles.conf index c6f716a8bb..5f09faa6c8 100644 --- a/etc/encoding-profiles.conf +++ b/etc/encoding-profiles.conf @@ -108,7 +108,7 @@ of = 3gp ocopyts = yes profile = enc-v-h263 profile = enc-a-aac -ofopts-clr = yes +ofopts = "" [enc-f-avi] profile-desc = "MPEG-4 + MP3 (for AVI)" @@ -117,7 +117,7 @@ ocopyts = no oautofps = yes profile = enc-v-mpeg4 profile = enc-a-mp3 -ofopts-clr = yes +ofopts = "" [enc-f-mp4] profile-desc = "H.264 + AAC (for MP4)" @@ -135,7 +135,7 @@ of = webm ocopyts = yes profile = enc-v-vp9 profile = enc-a-opus -ofopts-clr = yes +ofopts = "" ################## # target devices # diff --git a/player/loadfile.c b/player/loadfile.c index 8065b6f007..cf3c1eb013 100644 --- a/player/loadfile.c +++ b/player/loadfile.c @@ -1302,9 +1302,9 @@ reopen_file: #if HAVE_ENCODING if (mpctx->encode_lavc_ctx && mpctx->current_track[0][STREAM_VIDEO]) - encode_lavc_expect_stream(mpctx->encode_lavc_ctx, AVMEDIA_TYPE_VIDEO); + encode_lavc_expect_stream(mpctx->encode_lavc_ctx, STREAM_VIDEO); if (mpctx->encode_lavc_ctx && mpctx->current_track[0][STREAM_AUDIO]) - encode_lavc_expect_stream(mpctx->encode_lavc_ctx, AVMEDIA_TYPE_AUDIO); + encode_lavc_expect_stream(mpctx->encode_lavc_ctx, STREAM_AUDIO); if (mpctx->encode_lavc_ctx) { encode_lavc_set_metadata(mpctx->encode_lavc_ctx, mpctx->demuxer->metadata); @@ -1556,6 +1556,20 @@ void mp_play_files(struct MPContext *mpctx) } cancel_open(mpctx); + +#if HAVE_ENCODING + if (mpctx->encode_lavc_ctx) { + // Make sure all streams get finished. + uninit_audio_out(mpctx); + uninit_video_out(mpctx); + + if (!encode_lavc_free(mpctx->encode_lavc_ctx)) + mpctx->stop_play = PT_ERROR; + + mpctx->encode_lavc_ctx = NULL; + } +#endif + } // Abort current playback and set the given entry to play next. diff --git a/player/main.c b/player/main.c index b070b1aabe..ef54a0ac4d 100644 --- a/player/main.c +++ b/player/main.c @@ -165,10 +165,10 @@ void mp_destroy(struct MPContext *mpctx) uninit_video_out(mpctx); #if HAVE_ENCODING + // If it's still set here, it's an error. encode_lavc_free(mpctx->encode_lavc_ctx); -#endif - mpctx->encode_lavc_ctx = NULL; +#endif command_uninit(mpctx); @@ -416,8 +416,7 @@ int mp_initialize(struct MPContext *mpctx, char **options) #if HAVE_ENCODING if (opts->encode_opts->file && opts->encode_opts->file[0]) { - mpctx->encode_lavc_ctx = encode_lavc_init(opts->encode_opts, - mpctx->global); + mpctx->encode_lavc_ctx = encode_lavc_init(mpctx->global); if(!mpctx->encode_lavc_ctx) { MP_INFO(mpctx, "Encoding initialization failed.\n"); return -1; diff --git a/player/video.c b/player/video.c index 1a85cf511f..f4623f29e8 100644 --- a/player/video.c +++ b/player/video.c @@ -263,13 +263,6 @@ void reinit_video_chain_src(struct MPContext *mpctx, struct track *track) mp_pin_connect(vo_c->filter->f->pins[0], vo_c->dec_src); } -#if HAVE_ENCODING - if (mpctx->encode_lavc_ctx) { - encode_lavc_set_video_fps(mpctx->encode_lavc_ctx, - vo_c->filter->container_fps); - } -#endif - if (!recreate_video_filters(mpctx)) goto err_out; diff --git a/video/out/vo_lavc.c b/video/out/vo_lavc.c index bc8c51d180..c6d363eed7 100644 --- a/video/out/vo_lavc.c +++ b/video/out/vo_lavc.c @@ -36,9 +36,7 @@ #include "sub/osd.h" struct priv { - AVStream *stream; - AVCodecContext *codec; - int have_first_packet; + struct encoder_context *enc; int harddup; @@ -51,70 +49,59 @@ struct priv { mp_image_t *lastimg; int lastdisplaycount; + double last_video_in_pts; + AVRational worst_time_base; - int worst_time_base_is_stream; bool shutdown; }; -static void draw_image_unlocked(struct vo *vo, mp_image_t *mpi); +static void draw_image(struct vo *vo, mp_image_t *mpi); static int preinit(struct vo *vo) { - struct priv *vc; - if (!encode_lavc_available(vo->encode_lavc_ctx)) { - MP_ERR(vo, "the option --o (output file) must be specified\n"); + struct priv *vc = vo->priv; + vc->enc = encoder_context_alloc(vo->encode_lavc_ctx, STREAM_VIDEO, vo->log); + if (!vc->enc) return -1; - } - vo->priv = talloc_zero(vo, struct priv); - vc = vo->priv; - vc->harddup = vo->encode_lavc_ctx->options->harddup; + talloc_steal(vc, vc->enc); + vc->harddup = vc->enc->options->harddup; + vc->last_video_in_pts = MP_NOPTS_VALUE; return 0; } static void uninit(struct vo *vo) { struct priv *vc = vo->priv; - if (!vc || vc->shutdown) - return; - pthread_mutex_lock(&vo->encode_lavc_ctx->lock); - - if (vc->lastipts >= 0 && vc->stream) - draw_image_unlocked(vo, NULL); + if (vc->lastipts >= 0 && !vc->shutdown) + draw_image(vo, NULL); mp_image_unrefp(&vc->lastimg); - - pthread_mutex_unlock(&vo->encode_lavc_ctx->lock); - - vc->shutdown = true; } -static int reconfig(struct vo *vo, struct mp_image_params *params) +static int reconfig2(struct vo *vo, struct mp_image *img) { struct priv *vc = vo->priv; + struct encode_lavc_context *ctx = vo->encode_lavc_ctx; + AVCodecContext *encoder = vc->enc->encoder; + + struct mp_image_params *params = &img->params; enum AVPixelFormat pix_fmt = imgfmt2pixfmt(params->imgfmt); AVRational aspect = {params->p_w, params->p_h}; int width = params->w; int height = params->h; - if (!vc || vc->shutdown) + if (vc->shutdown) return -1; - pthread_mutex_lock(&vo->encode_lavc_ctx->lock); - - if (vc->stream) { - if (width == vc->codec->width && height == vc->codec->height) { - if (aspect.num != vc->codec->sample_aspect_ratio.num || - aspect.den != vc->codec->sample_aspect_ratio.den) - { - /* aspect-only changes are not critical */ - MP_WARN(vo, "unsupported pixel aspect ratio change from %d:%d to %d:%d\n", - vc->codec->sample_aspect_ratio.num, - vc->codec->sample_aspect_ratio.den, - aspect.num, aspect.den); - } - goto done; + if (avcodec_is_open(encoder)) { + if (width == encoder->width && height == encoder->height && + pix_fmt == encoder->pix_fmt) + { + // consider these changes not critical + MP_ERR(vo, "Ignoring mid-stream parameter changes!\n"); + return 0; } /* FIXME Is it possible with raw video? */ @@ -128,7 +115,7 @@ static int reconfig(struct vo *vo, struct mp_image_params *params) // - Second calls after reconfigure() already failed once fail (due to the // vc->shutdown check above). // - Second calls after reconfigure() already succeeded once return early - // (due to the vc->stream check above). + // (due to the avcodec_is_open() check above). vc->lastipts = AV_NOPTS_VALUE; vc->lastframeipts = AV_NOPTS_VALUE; @@ -140,138 +127,78 @@ static int reconfig(struct vo *vo, struct mp_image_params *params) goto error; } - if (encode_lavc_alloc_stream(vo->encode_lavc_ctx, - AVMEDIA_TYPE_VIDEO, - &vc->stream, &vc->codec) < 0) - goto error; - vc->stream->sample_aspect_ratio = vc->codec->sample_aspect_ratio = aspect; - vc->codec->width = width; - vc->codec->height = height; - vc->codec->pix_fmt = pix_fmt; - vc->codec->colorspace = mp_csp_to_avcol_spc(params->color.space); - vc->codec->color_range = mp_csp_levels_to_avcol_range(params->color.levels); + encoder->sample_aspect_ratio = aspect; + encoder->width = width; + encoder->height = height; + encoder->pix_fmt = pix_fmt; + encoder->colorspace = mp_csp_to_avcol_spc(params->color.space); + encoder->color_range = mp_csp_levels_to_avcol_range(params->color.levels); - if (encode_lavc_open_codec(vo->encode_lavc_ctx, vc->codec) < 0) + AVRational tb; + + if (ctx->options->fps > 0) { + tb = av_d2q(ctx->options->fps, ctx->options->fps * 1001 + 2); + } else if (ctx->options->autofps && img->nominal_fps > 0) { + tb = av_d2q(img->nominal_fps, img->nominal_fps * 1001 + 2); + MP_INFO(vo, "option --ofps not specified " + "but --oautofps is active, using guess of %u/%u\n", + (unsigned)tb.num, (unsigned)tb.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 + tb.num = 24000; + tb.den = 1; + MP_INFO(vo, "option --ofps not specified " + "and fps could not be inferred, using guess of %u/%u\n", + (unsigned)tb.num, (unsigned)tb.den); + } + + const AVRational *rates = encoder->codec->supported_framerates; + if (rates && rates[0].den) + tb = rates[av_find_nearest_q_idx(tb, rates)]; + + encoder->time_base = av_inv_q(tb); + + if (!encoder_init_codec_and_muxer(vc->enc)) goto error; -done: - pthread_mutex_unlock(&vo->encode_lavc_ctx->lock); return 0; error: - pthread_mutex_unlock(&vo->encode_lavc_ctx->lock); vc->shutdown = true; return -1; } static int query_format(struct vo *vo, int format) { + struct priv *vc = vo->priv; + enum AVPixelFormat pix_fmt = imgfmt2pixfmt(format); + const enum AVPixelFormat *p = vc->enc->encoder->codec->pix_fmts; - if (!vo->encode_lavc_ctx) - return 0; + if (!p) + return 1; - pthread_mutex_lock(&vo->encode_lavc_ctx->lock); - int flags = 0; - if (encode_lavc_supports_pixfmt(vo->encode_lavc_ctx, pix_fmt)) - flags = 1; - pthread_mutex_unlock(&vo->encode_lavc_ctx->lock); - return flags; + while (*p != AV_PIX_FMT_NONE) { + if (*p == pix_fmt) + return 1; + p++; + } + + return 0; } -static void write_packet(struct vo *vo, AVPacket *packet) +static void draw_image(struct vo *vo, mp_image_t *mpi) { struct priv *vc = vo->priv; - - packet->stream_index = vc->stream->index; - if (packet->pts != AV_NOPTS_VALUE) { - packet->pts = av_rescale_q(packet->pts, - vc->codec->time_base, - vc->stream->time_base); - } else { - MP_VERBOSE(vo, "codec did not provide pts\n"); - packet->pts = av_rescale_q(vc->lastipts, - vc->worst_time_base, - vc->stream->time_base); - } - if (packet->dts != AV_NOPTS_VALUE) { - packet->dts = av_rescale_q(packet->dts, - vc->codec->time_base, - vc->stream->time_base); - } - if (packet->duration > 0) { - packet->duration = av_rescale_q(packet->duration, - vc->codec->time_base, - vc->stream->time_base); - } else { - // HACK: libavformat calculates dts wrong if the initial packet - // duration is not set, but ONLY if the time base is "high" and if we - // have b-frames! - if (!packet->duration && !vc->have_first_packet && - (vc->codec->has_b_frames || vc->codec->max_b_frames) && - (vc->stream->time_base.num * 1000LL <= vc->stream->time_base.den)) - { - packet->duration = FFMAX(1, av_rescale_q(1, - vc->codec->time_base, vc->stream->time_base)); - } - } - - if (encode_lavc_write_frame(vo->encode_lavc_ctx, - vc->stream, packet) < 0) { - MP_ERR(vo, "error writing at %d %d/%d\n", - (int) packet->pts, - vc->stream->time_base.num, - vc->stream->time_base.den); - return; - } - - vc->have_first_packet = 1; -} - -static void encode_video_and_write(struct vo *vo, AVFrame *frame) -{ - struct priv *vc = vo->priv; - AVPacket packet = {0}; - - int status = avcodec_send_frame(vc->codec, frame); - if (status < 0) { - MP_ERR(vo, "error encoding at %d %d/%d\n", - frame ? (int) frame->pts : -1, - vc->codec->time_base.num, - vc->codec->time_base.den); - return; - } - for (;;) { - av_init_packet(&packet); - status = avcodec_receive_packet(vc->codec, &packet); - if (status == AVERROR(EAGAIN)) { // No more packets for now. - if (frame == NULL) - MP_ERR(vo, "sent flush frame, got EAGAIN"); - break; - } - if (status == AVERROR_EOF) { // No more packets, ever. - if (frame != NULL) - MP_ERR(vo, "sent image frame, got EOF"); - break; - } - if (status < 0) { - MP_ERR(vo, "error encoding at %d %d/%d\n", - frame ? (int) frame->pts : -1, - vc->codec->time_base.num, - vc->codec->time_base.den); - break; - } - encode_lavc_write_stats(vo->encode_lavc_ctx, vc->codec); - write_packet(vo, &packet); - av_packet_unref(&packet); - } -} - -static void draw_image_unlocked(struct vo *vo, mp_image_t *mpi) -{ - struct priv *vc = vo->priv; - struct encode_lavc_context *ectx = vo->encode_lavc_ctx; - AVCodecContext *avc; + struct encoder_context *enc = vc->enc; + struct encode_lavc_context *ectx = enc->encode_lavc_ctx; + AVCodecContext *avc = enc->encoder; int64_t frameipts; double nextpts; @@ -285,41 +212,24 @@ static void draw_image_unlocked(struct vo *vo, mp_image_t *mpi) osd_draw_on_image(vo->osd, dim, mpi->pts, OSD_DRAW_SUB_ONLY, mpi); } - if (!vc || vc->shutdown) + if (vc->shutdown) goto done; - if (!encode_lavc_start(ectx)) { - MP_WARN(vo, "NOTE: skipped initial video frame (probably because audio is not there yet)\n"); - goto done; - } + if (pts == MP_NOPTS_VALUE) { if (mpi) MP_WARN(vo, "frame without pts, please report; synthesizing pts instead\n"); pts = vc->expected_next_pts; } - avc = vc->codec; - if (vc->worst_time_base.den == 0) { - if (avc->time_base.num * (double) vc->stream->time_base.den >= - vc->stream->time_base.num * (double) avc->time_base.den) { - MP_VERBOSE(vo, "NOTE: using codec time base " - "(%d/%d) for frame dropping; the stream base (%d/%d) is " - "not worse.\n", (int)avc->time_base.num, - (int)avc->time_base.den, (int)vc->stream->time_base.num, - (int)vc->stream->time_base.den); - vc->worst_time_base = avc->time_base; - vc->worst_time_base_is_stream = 0; - } else { - MP_WARN(vo, "NOTE: not using codec time base (%d/%d) for frame " - "dropping; the stream base (%d/%d) is worse.\n", - (int)avc->time_base.num, (int)avc->time_base.den, - (int)vc->stream->time_base.num, (int)vc->stream->time_base.den); - vc->worst_time_base = vc->stream->time_base; - vc->worst_time_base_is_stream = 1; - } - if (ectx->options->maxfps) { + // We don't know the muxer time_base anymore, and can't, because we + // might start encoding before the muxer is opened. (The muxer decides + // the final AVStream.time_base when opening the muxer.) + vc->worst_time_base = avc->time_base; + + if (enc->options->maxfps) { vc->mindeltapts = ceil(vc->worst_time_base.den / - (vc->worst_time_base.num * ectx->options->maxfps)); + (vc->worst_time_base.num * enc->options->maxfps)); } else { vc->mindeltapts = 0; } @@ -343,10 +253,13 @@ static void draw_image_unlocked(struct vo *vo, mp_image_t *mpi) double timeunit = (double)vc->worst_time_base.num / vc->worst_time_base.den; + // Lock for shared timestamp fields. + pthread_mutex_lock(&ectx->lock); + double outpts; - if (ectx->options->rawts) + if (enc->options->rawts) { outpts = pts; - else if (ectx->options->copyts) { + } else if (enc->options->copyts) { // fix the discontinuity pts offset nextpts = pts; if (ectx->discontinuity_pts_offset == MP_NOPTS_VALUE) { @@ -364,8 +277,8 @@ static void draw_image_unlocked(struct vo *vo, mp_image_t *mpi) } else { // adjust pts by knowledge of audio pts vs audio playback time double duration = 0; - if (ectx->last_video_in_pts != MP_NOPTS_VALUE) - duration = pts - ectx->last_video_in_pts; + if (vc->last_video_in_pts != MP_NOPTS_VALUE) + duration = pts - vc->last_video_in_pts; if (duration < 0) duration = timeunit; // XXX warn about discontinuity? outpts = vc->lastpts + duration; @@ -377,28 +290,29 @@ static void draw_image_unlocked(struct vo *vo, mp_image_t *mpi) } } vc->lastpts = outpts; - ectx->last_video_in_pts = pts; - frameipts = floor((outpts + encode_lavc_getoffset(ectx, vc->codec)) - / timeunit + 0.5); + vc->last_video_in_pts = pts; + frameipts = floor((outpts + encoder_get_offset(enc)) / timeunit + 0.5); // calculate expected pts of next video frame vc->expected_next_pts = pts + timeunit; - if (!ectx->options->rawts && ectx->options->copyts) { + if (!enc->options->rawts && enc->options->copyts) { // set next allowed output pts value nextpts = vc->expected_next_pts + ectx->discontinuity_pts_offset; if (nextpts > ectx->next_in_pts) ectx->next_in_pts = nextpts; } + pthread_mutex_unlock(&ectx->lock); + // never-drop mode - if (ectx->options->neverdrop) { + if (enc->options->neverdrop) { int64_t step = vc->mindeltapts ? vc->mindeltapts : 1; if (frameipts < vc->lastipts + step) { MP_INFO(vo, "--oneverdrop increased pts by %d\n", (int) (vc->lastipts - frameipts + step)); frameipts = vc->lastipts + step; - vc->lastpts = frameipts * timeunit - encode_lavc_getoffset(ectx, vc->codec); + vc->lastpts = frameipts * timeunit - encoder_get_offset(enc); } } @@ -424,7 +338,7 @@ static void draw_image_unlocked(struct vo *vo, mp_image_t *mpi) vc->worst_time_base, avc->time_base); frame->pict_type = 0; // keep this at unknown/undefined frame->quality = avc->global_quality; - encode_video_and_write(vo, frame); + encoder_encode(enc, frame); av_frame_free(&frame); vc->lastdisplaycount += 1; @@ -437,7 +351,7 @@ static void draw_image_unlocked(struct vo *vo, mp_image_t *mpi) if (!mpi) { // finish encoding - encode_video_and_write(vo, NULL); + encoder_encode(enc, NULL); } else { if (frameipts >= vc->lastframeipts) { if (vc->lastframeipts != AV_NOPTS_VALUE && vc->lastdisplaycount != 1) @@ -448,7 +362,7 @@ static void draw_image_unlocked(struct vo *vo, mp_image_t *mpi) mpi = NULL; vc->lastframeipts = vc->lastipts = frameipts; - if (ectx->options->rawts && vc->lastipts < 0) { + if (enc->options->rawts && vc->lastipts < 0) { MP_ERR(vo, "why does this happen? DEBUG THIS! vc->lastipts = %lld\n", (long long) vc->lastipts); vc->lastipts = -1; @@ -464,19 +378,20 @@ done: talloc_free(mpi); } -static void draw_image(struct vo *vo, mp_image_t *mpi) -{ - pthread_mutex_lock(&vo->encode_lavc_ctx->lock); - draw_image_unlocked(vo, mpi); - pthread_mutex_unlock(&vo->encode_lavc_ctx->lock); -} - static void flip_page(struct vo *vo) { } static int control(struct vo *vo, uint32_t request, void *data) { + struct priv *vc = vo->priv; + + switch (request) { + case VOCTRL_RESET: + vc->last_video_in_pts = MP_NOPTS_VALUE; + break; + } + return VO_NOTIMPL; } @@ -485,9 +400,10 @@ const struct vo_driver video_out_lavc = { .description = "video encoding using libavcodec", .name = "lavc", .untimed = true, + .priv_size = sizeof(struct priv), .preinit = preinit, .query_format = query_format, - .reconfig = reconfig, + .reconfig2 = reconfig2, .control = control, .uninit = uninit, .draw_image = draw_image,