aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--DOCS/man/encode.rst15
-rw-r--r--audio/out/ao_lavc.c240
-rw-r--r--common/common.h11
-rw-r--r--common/encode.h11
-rw-r--r--common/encode_lavc.c1136
-rw-r--r--common/encode_lavc.h105
-rw-r--r--etc/encoding-profiles.conf6
-rw-r--r--player/loadfile.c18
-rw-r--r--player/main.c7
-rw-r--r--player/video.c7
-rw-r--r--video/out/vo_lavc.c312
11 files changed, 754 insertions, 1114 deletions
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=<format>``
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=<options>``
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=<codec>``
- 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=<value>``
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=<codec>``
- 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=<value>``
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;
-
- ao->priv = ac;
+ struct priv *ac = ao->priv;
- 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 (!ac->shutdown) {
+ double outpts = ac->expected_next_pts;
- if (!encode_lavc_start(ectx)) {
- MP_WARN(ao, "not even ready to encode audio at end -> dropped\n");
+ pthread_mutex_lock(&ectx->lock);
+ if (!ac->enc->options->rawts && ac->enc->options->copyts)
+ outpts += ectx->discontinuity_pts_offset;
pthread_mutex_unlock(&ectx->lock);
- return;
- }
- if (ac->stream) {
- double outpts = ac->expected_next_pts;
- if (!ectx->options->rawts && ectx->options->copyts)
- outpts += ectx->discontinuity_pts_offset;
- outpts += encode_lavc_getoffset(ectx, ac->codec);
+ 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 <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->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);
-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;
- }
+ maybe_init_muxer(ctx);
+
+done:
+ pthread_mutex_unlock(&ctx->lock);
+
+ return dst;
}
-int encode_lavc_write_frame(struct encode_lavc_context *ctx, AVStream *stream,
- AVPacket *packet)
+// Write a packet. Callee will create new pkt refs as needed.
+static void encode_lavc_add_packet(struct mux_stream *dst, AVPacket *pkt)
{
- int r;
+ struct encode_lavc_context *ctx = dst->ctx;
+ struct encode_priv *p = ctx->priv;
- CHECK_FAIL(ctx, -1);
+ assert(dst->st);
- if (stream->index != packet->stream_index) {
- MP_ERR(ctx, "Called encode_lavc_write_frame on the wrong stream\n");
- return -1;
- }
+ pthread_mutex_lock(&ctx->lock);
- if (ctx->header_written <= 0)
- return -1;
+ if (p->failed)
+ goto done;
- 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) {
- case AVMEDIA_TYPE_VIDEO:
- ctx->vbytes += packet->size;
- ctx->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;
- break;
- default:
- break;
- }
+ AVPacket *new_packet = av_packet_clone(pkt);
+ if (pkt && !new_packet)
+ MP_HANDLE_OOM(0);
- r = av_interleaved_write_frame(ctx->avc, packet);
+ new_packet->stream_index = dst->index; // not the lavf index (yet)
+ MP_TARRAY_APPEND(p, p->packets, p->num_packets, new_packet);
- return r;
-}
+ write_remaining_packets(ctx);
-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;
- }
- }
- return 0;
+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)
{
+ 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);
- if (!ctx)
- return -1;
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)
+bool encode_lavc_didfail(struct encode_lavc_context *ctx)
{
+ if (!ctx)
+ return false;
pthread_mutex_lock(&ctx->lock);
+ bool fail = ctx->priv->failed;
+ pthread_mutex_unlock(&ctx->lock);
+ return fail;
+}
- CHECK_FAIL_UNLOCK(ctx, );
+static void encoder_destroy(void *ptr)
+{
+ struct encoder_context *p = ptr;
- switch (mt) {
- case AVMEDIA_TYPE_VIDEO:
- ctx->expect_video = true;
- break;
- case AVMEDIA_TYPE_AUDIO:
- ctx->expect_audio = true;
- break;
+ 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;
}
- pthread_mutex_unlock(&ctx->lock);
+ 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;
}
-bool encode_lavc_didfail(struct encode_lavc_context *ctx)
+static void encoder_2pass_prepare(struct encoder_context *p)
{
- if (!ctx)
- return false;
- pthread_mutex_lock(&ctx->lock);
- bool fail = ctx && ctx->failed;
- pthread_mutex_unlock(&ctx->lock);
- return fail;
+ 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);
}
-void encode_lavc_fail(struct encode_lavc_context *ctx, const char *format, ...)
+bool encoder_init_codec_and_muxer(struct encoder_context *p)
{
- 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;
+ 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 <libavutil/opt.h>
#include <libavutil/mathematics.h>
+#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;
+};
+
+// --- 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;
- 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;
+ // (different access restrictions before/after encoder init)
+ struct encoder_stream_info info;
+ AVCodecContext *encoder;
+ struct mux_stream *mux_stream;
+
+ struct stream *twopass_bytebuffer;
};
-// 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
+// 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);
-
- if (encode_lavc_open_codec(vo->encode_lavc_ctx, vc->codec) < 0)
+ 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);
+
+ 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)
{
- enum AVPixelFormat pix_fmt = imgfmt2pixfmt(format);
-
- if (!vo->encode_lavc_ctx)
- return 0;
-
- 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;
-}
-
-static void write_packet(struct vo *vo, AVPacket *packet)
-{
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;
- }
+ enum AVPixelFormat pix_fmt = imgfmt2pixfmt(format);
+ const enum AVPixelFormat *p = vc->enc->encoder->codec->pix_fmts;
- vc->have_first_packet = 1;
-}
+ if (!p)
+ return 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);
+ while (*p != AV_PIX_FMT_NONE) {
+ if (*p == pix_fmt)
+ return 1;
+ p++;
}
+
+ return 0;
}
-static void draw_image_unlocked(struct vo *vo, mp_image_t *mpi)
+static void draw_image(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)
- goto done;
- if (!encode_lavc_start(ectx)) {
- MP_WARN(vo, "NOTE: skipped initial video frame (probably because audio is not there yet)\n");
+ if (vc->shutdown)
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,