summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2018-04-29 19:42:18 +0200
committerJan Ekström <jeebjp@gmail.com>2018-05-03 01:08:44 +0300
commit0ab3184526e7b9b95c06a3ec7a6674283a5922d0 (patch)
tree4c05b192e65b51c68f3024b4b7946dcb79caf404
parent958053ff56109a38e9f8e0a0aa8786a5f47adb7c (diff)
downloadmpv-0ab3184526e7b9b95c06a3ec7a6674283a5922d0.tar.bz2
mpv-0ab3184526e7b9b95c06a3ec7a6674283a5922d0.tar.xz
encode: get rid of the output packet queue
Until recently, ao_lavc and vo_lavc started encoding whenever the core happened to send them data. Since audio and video are not initialized at the same time, and the muxer was not necessarily opened when the first encoder started to produce data, the resulting packets were put into a queue. As soon as the muxer was opened, the queue was flushed. Change this to make the core wait with sending data until all encoders are initialized. This has the advantage that we don't need to queue up the packets.
-rw-r--r--audio/out/ao.c2
-rw-r--r--audio/out/ao.h3
-rw-r--r--audio/out/ao_lavc.c10
-rw-r--r--audio/out/internal.h4
-rw-r--r--audio/out/push.c17
-rw-r--r--common/encode.h2
-rw-r--r--common/encode_lavc.c149
-rw-r--r--common/encode_lavc.h7
-rw-r--r--player/audio.c6
-rw-r--r--player/video.c1
-rw-r--r--video/out/vo.c4
-rw-r--r--video/out/vo.h11
-rw-r--r--video/out/vo_lavc.c10
13 files changed, 147 insertions, 79 deletions
diff --git a/audio/out/ao.c b/audio/out/ao.c
index 8f6fc8ea3c..8fd24c2439 100644
--- a/audio/out/ao.c
+++ b/audio/out/ao.c
@@ -422,7 +422,7 @@ int ao_query_and_reset_events(struct ao *ao, int events)
return atomic_fetch_and(&ao->events_, ~(unsigned)events) & events;
}
-static void ao_add_events(struct ao *ao, int events)
+void ao_add_events(struct ao *ao, int events)
{
atomic_fetch_or(&ao->events_, events);
ao->wakeup_cb(ao->wakeup_ctx);
diff --git a/audio/out/ao.h b/audio/out/ao.h
index b9df4ebb00..99a3d0fae0 100644
--- a/audio/out/ao.h
+++ b/audio/out/ao.h
@@ -48,6 +48,7 @@ enum aocontrol {
enum {
AO_EVENT_RELOAD = 1,
AO_EVENT_HOTPLUG = 2,
+ AO_EVENT_INITIAL_UNBLOCK = 4,
};
enum {
@@ -104,6 +105,8 @@ void ao_resume(struct ao *ao);
void ao_drain(struct ao *ao);
bool ao_eof_reached(struct ao *ao);
int ao_query_and_reset_events(struct ao *ao, int events);
+void ao_add_events(struct ao *ao, int events);
+void ao_unblock(struct ao *ao);
void ao_request_reload(struct ao *ao);
void ao_hotplug_event(struct ao *ao);
diff --git a/audio/out/ao_lavc.c b/audio/out/ao_lavc.c
index bb86224229..974d9d0b63 100644
--- a/audio/out/ao_lavc.c
+++ b/audio/out/ao_lavc.c
@@ -84,6 +84,13 @@ static void select_format(struct ao *ao, const AVCodec *codec)
}
}
+static void on_ready(void *ptr)
+{
+ struct ao *ao = ptr;
+
+ ao_add_events(ao, AO_EVENT_INITIAL_UNBLOCK);
+}
+
// open & setup audio device
static int init(struct ao *ao)
{
@@ -123,7 +130,7 @@ static int init(struct ao *ao)
encoder->sample_fmt = af_to_avformat(ao->format);
encoder->bits_per_raw_sample = ac->sample_size * 8;
- if (!encoder_init_codec_and_muxer(ac->enc))
+ if (!encoder_init_codec_and_muxer(ac->enc, on_ready, ao))
goto fail;
ac->pcmhack = 0;
@@ -342,6 +349,7 @@ const struct ao_driver audio_out_lavc = {
.encode = true,
.description = "audio encoding using libavcodec",
.name = "lavc",
+ .initially_blocked = true,
.priv_size = sizeof(struct priv),
.init = init,
.uninit = uninit,
diff --git a/audio/out/internal.h b/audio/out/internal.h
index 33e8a8c6a9..bf769d7e1c 100644
--- a/audio/out/internal.h
+++ b/audio/out/internal.h
@@ -130,6 +130,10 @@ struct ao_driver {
const char *name;
// Description shown with --ao=help.
const char *description;
+ // This requires waiting for a AO_EVENT_INITIAL_UNBLOCK event before the
+ // first play() call is done. Encode mode uses this, and push mode
+ // respects it automatically (don't use with pull mode).
+ bool initially_blocked;
// Init the device using ao->format/ao->channels/ao->samplerate. If the
// device doesn't accept these parameters, you can attempt to negotiate
// fallback parameters, and set the ao format fields accordingly.
diff --git a/audio/out/push.c b/audio/out/push.c
index b198afef91..470f521c68 100644
--- a/audio/out/push.c
+++ b/audio/out/push.c
@@ -56,6 +56,7 @@ struct ao_push_state {
bool still_playing;
bool need_wakeup;
bool paused;
+ bool initial_unblocked;
// Whether the current buffer contains the complete audio.
bool final_chunk;
@@ -357,7 +358,8 @@ static void *playthread(void *arg)
mpthread_set_name("ao");
pthread_mutex_lock(&p->lock);
while (!p->terminate) {
- bool playing = !p->paused || ao->stream_silence;
+ bool blocked = ao->driver->initially_blocked && !p->initial_unblocked;
+ bool playing = (!p->paused || ao->stream_silence) && !blocked;
if (playing)
ao_play_data(ao);
@@ -502,6 +504,19 @@ int ao_play_silence(struct ao *ao, int samples)
return ao->driver->play(ao, (void **)p->silence, samples, 0);
}
+void ao_unblock(struct ao *ao)
+{
+ if (ao->api == &ao_api_push) {
+ struct ao_push_state *p = ao->api_priv;
+ pthread_mutex_lock(&p->lock);
+ p->need_wakeup = true;
+ p->initial_unblocked = true;
+ wakeup_playthread(ao);
+ pthread_cond_signal(&p->wakeup);
+ pthread_mutex_unlock(&p->lock);
+ }
+}
+
#ifndef __MINGW32__
#include <poll.h>
diff --git a/common/encode.h b/common/encode.h
index 4a09fedaa2..62a8b094e9 100644
--- a/common/encode.h
+++ b/common/encode.h
@@ -56,6 +56,8 @@ 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,
enum stream_type type);
+void encode_lavc_stream_eof(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_audio_pts(struct encode_lavc_context *ctx, double pts);
diff --git a/common/encode_lavc.c b/common/encode_lavc.c
index 2473609921..d8e875c58f 100644
--- a/common/encode_lavc.c
+++ b/common/encode_lavc.c
@@ -53,10 +53,6 @@ struct encode_priv {
struct mux_stream **streams;
int num_streams;
- // Temporary queue while muxer is not initialized.
- AVPacket **packets;
- int num_packets;
-
// Statistics
double t0;
@@ -69,10 +65,13 @@ struct encode_priv {
struct mux_stream {
int index; // index of this into p->streams[]
+ char name[80];
struct encode_lavc_context *ctx;
enum AVMediaType codec_type;
AVRational encoder_timebase; // packet timestamps from encoder
AVStream *st;
+ void (*on_ready)(void *ctx); // when finishing muxer init
+ void *on_ready_ctx;
};
#define OPT_BASE_STRUCT struct encode_opts
@@ -112,8 +111,6 @@ const struct m_sub_options encode_config = {
},
};
-static void write_remaining_packets(struct encode_lavc_context *ctx);
-
struct encode_lavc_context *encode_lavc_init(struct mpv_global *global)
{
struct encode_lavc_context *ctx = talloc_ptrtype(NULL, ctx);
@@ -219,8 +216,6 @@ bool encode_lavc_free(struct encode_lavc_context *ctx)
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");
@@ -258,52 +253,6 @@ void encode_lavc_set_audio_pts(struct encode_lavc_context *ctx, double pts)
}
// called locked
-static void write_remaining_packets(struct encode_lavc_context *ctx)
-{
- 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;
- }
-
- 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;
-}
-
-// called locked
static void maybe_init_muxer(struct encode_lavc_context *ctx)
{
struct encode_priv *p = ctx->priv;
@@ -315,15 +264,8 @@ static void maybe_init_muxer(struct encode_lavc_context *ctx)
// 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;
- }
+ if (!p->streams[n]->st)
return;
- }
}
if (!(p->muxer->oformat->flags & AVFMT_NOFILE)) {
@@ -364,7 +306,13 @@ static void maybe_init_muxer(struct encode_lavc_context *ctx)
p->header_written = true;
- write_remaining_packets(ctx);
+ for (int n = 0; n < p->num_streams; n++) {
+ struct mux_stream *s = p->streams[n];
+
+ if (s->on_ready)
+ s->on_ready(s->on_ready_ctx);
+ }
+
return;
failed:
@@ -411,17 +359,45 @@ void encode_lavc_expect_stream(struct encode_lavc_context *ctx,
.ctx = ctx,
.codec_type = mp_to_av_stream_type(type),
};
+ snprintf(dst->name, sizeof(dst->name), "%s", stream_type_name(type));
MP_TARRAY_APPEND(p, p->streams, p->num_streams, dst);
done:
pthread_mutex_unlock(&ctx->lock);
}
+void encode_lavc_stream_eof(struct encode_lavc_context *ctx,
+ enum stream_type type)
+{
+ if (!ctx)
+ return;
+
+ struct encode_priv *p = ctx->priv;
+
+ pthread_mutex_lock(&ctx->lock);
+
+ enum AVMediaType codec_type = mp_to_av_stream_type(type);
+ struct mux_stream *dst = find_mux_stream(ctx, codec_type);
+
+ // If we've reached EOF, even though the stream was selected, and we didn't
+ // ever initialize it, we have a problem. We could mux some sort of dummy
+ // stream (and could fail if actual data arrives later), or we bail out
+ // early.
+ if (dst && !dst->st) {
+ MP_ERR(p, "No data on stream %s.\n", dst->name);
+ p->failed = true;
+ }
+
+ 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 encoder_stream_info *info,
+ void (*on_ready)(void *ctx),
+ void *on_ready_ctx)
{
struct encode_priv *p = ctx->priv;
@@ -449,6 +425,9 @@ static struct mux_stream *encode_lavc_add_stream(struct encode_lavc_context *ctx
if (avcodec_parameters_copy(dst->st->codecpar, info->codecpar) < 0)
MP_HANDLE_OOM(0);
+ dst->on_ready = on_ready;
+ dst->on_ready_ctx = on_ready_ctx;
+
maybe_init_muxer(ctx);
done:
@@ -470,17 +449,41 @@ static void encode_lavc_add_packet(struct mux_stream *dst, AVPacket *pkt)
if (p->failed)
goto done;
- AVPacket *new_packet = av_packet_clone(pkt);
- if (pkt && !new_packet)
- MP_HANDLE_OOM(0);
+ if (!p->header_written) {
+ MP_ERR(p, "Encoder trying to write packet before muxer was initialized.\n");
+ p->failed = true;
+ goto done;
+ }
- new_packet->stream_index = dst->index; // not the lavf index (yet)
- MP_TARRAY_APPEND(p, p->packets, p->num_packets, new_packet);
+ pkt->stream_index = dst->st->index;
+ assert(dst->st == p->muxer->streams[pkt->stream_index]);
+
+ av_packet_rescale_ts(pkt, dst->encoder_timebase, dst->st->time_base);
+
+ switch (dst->st->codecpar->codec_type) {
+ case AVMEDIA_TYPE_VIDEO:
+ p->vbytes += pkt->size;
+ p->frames += 1;
+ break;
+ case AVMEDIA_TYPE_AUDIO:
+ p->abytes += pkt->size;
+ p->audioseconds += pkt->duration
+ * (double)dst->st->time_base.num
+ / (double)dst->st->time_base.den;
+ break;
+ }
+
+ if (av_interleaved_write_frame(p->muxer, pkt) < 0) {
+ MP_ERR(p, "Writing packet failed.\n");
+ p->failed = true;
+ goto done;
+ }
- write_remaining_packets(ctx);
+ pkt = NULL;
done:
pthread_mutex_unlock(&ctx->lock);
+ av_packet_free(&pkt);
}
void encode_lavc_discontinuity(struct encode_lavc_context *ctx)
@@ -852,7 +855,8 @@ static void encoder_2pass_prepare(struct encoder_context *p)
talloc_free(filename);
}
-bool encoder_init_codec_and_muxer(struct encoder_context *p)
+bool encoder_init_codec_and_muxer(struct encoder_context *p,
+ void (*on_ready)(void *ctx), void *ctx)
{
assert(!avcodec_is_open(p->encoder));
@@ -905,7 +909,8 @@ bool encoder_init_codec_and_muxer(struct encoder_context *p)
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);
+ p->mux_stream = encode_lavc_add_stream(p->encode_lavc_ctx, &p->info,
+ on_ready, ctx);
if (!p->mux_stream)
goto fail;
diff --git a/common/encode_lavc.h b/common/encode_lavc.h
index a689b1ea47..97c2cf01f1 100644
--- a/common/encode_lavc.h
+++ b/common/encode_lavc.h
@@ -101,7 +101,12 @@ struct encoder_context *encoder_context_alloc(struct encode_lavc_context *ctx,
// 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);
+// on_ready is called as soon as the muxer has been initialized. Then you are
+// allowed to write packets with encoder_encode().
+// Warning: the on_ready callback is called asynchronously, so you need to
+// make sure to properly synchronize everything.
+bool encoder_init_codec_and_muxer(struct encoder_context *p,
+ void (*on_ready)(void *ctx), void *ctx);
// Encode the frame and write the packet. frame is ref'ed as need.
bool encoder_encode(struct encoder_context *p, AVFrame *frame);
diff --git a/player/audio.c b/player/audio.c
index ce9e1cd06b..9b1340ec96 100644
--- a/player/audio.c
+++ b/player/audio.c
@@ -790,6 +790,10 @@ void fill_audio_out_buffers(struct MPContext *mpctx)
if (mpctx->ao && ao_query_and_reset_events(mpctx->ao, AO_EVENT_RELOAD))
reload_audio_output(mpctx);
+ if (mpctx->ao && ao_query_and_reset_events(mpctx->ao,
+ AO_EVENT_INITIAL_UNBLOCK))
+ ao_unblock(mpctx->ao);
+
struct ao_chain *ao_c = mpctx->ao_chain;
if (!ao_c)
return;
@@ -811,6 +815,7 @@ void fill_audio_out_buffers(struct MPContext *mpctx)
mpctx->audio_status = STATUS_EOF;
MP_VERBOSE(mpctx, "audio EOF without any data\n");
mp_filter_reset(ao_c->filter->f);
+ encode_lavc_stream_eof(mpctx->encode_lavc_ctx, STREAM_AUDIO);
}
return; // try again next iteration
}
@@ -994,6 +999,7 @@ void fill_audio_out_buffers(struct MPContext *mpctx)
if (!was_eof) {
MP_VERBOSE(mpctx, "audio EOF reached\n");
mp_wakeup_core(mpctx);
+ encode_lavc_stream_eof(mpctx->encode_lavc_ctx, STREAM_AUDIO);
}
}
}
diff --git a/player/video.c b/player/video.c
index 693454ecb0..17dff84984 100644
--- a/player/video.c
+++ b/player/video.c
@@ -1027,6 +1027,7 @@ void write_video(struct MPContext *mpctx)
if (mpctx->time_frame <= 0) {
MP_VERBOSE(mpctx, "video EOF reached\n");
mpctx->video_status = STATUS_EOF;
+ encode_lavc_stream_eof(mpctx->encode_lavc_ctx, STREAM_VIDEO);
}
}
diff --git a/video/out/vo.c b/video/out/vo.c
index 624136bd47..acae4f2acb 100644
--- a/video/out/vo.c
+++ b/video/out/vo.c
@@ -750,7 +750,9 @@ bool vo_is_ready_for_frame(struct vo *vo, int64_t next_pts)
{
struct vo_internal *in = vo->in;
pthread_mutex_lock(&in->lock);
- bool r = vo->config_ok && !in->frame_queued &&
+ bool blocked = vo->driver->initially_blocked &&
+ !(in->internal_events & VO_EVENT_INITIAL_UNBLOCK);
+ bool r = vo->config_ok && !in->frame_queued && !blocked &&
(!in->current_frame || in->current_frame->num_vsyncs < 1);
if (r && next_pts >= 0) {
// Don't show the frame too early - it would basically freeze the
diff --git a/video/out/vo.h b/video/out/vo.h
index 17cb692356..3c00bb988e 100644
--- a/video/out/vo.h
+++ b/video/out/vo.h
@@ -45,10 +45,13 @@ enum {
VO_EVENT_LIVE_RESIZING = 1 << 5,
// Window fullscreen state changed via external influence.
VO_EVENT_FULLSCREEN_STATE = 1 << 6,
+ // Special thing for encode mode (vo_driver.initially_blocked).
+ // Part of VO_EVENTS_USER to make vo_is_ready_for_frame() work properly.
+ VO_EVENT_INITIAL_UNBLOCK = 1 << 7,
// Set of events the player core may be interested in.
VO_EVENTS_USER = VO_EVENT_RESIZE | VO_EVENT_WIN_STATE |
- VO_EVENT_FULLSCREEN_STATE,
+ VO_EVENT_FULLSCREEN_STATE | VO_EVENT_INITIAL_UNBLOCK,
};
enum mp_voctrl {
@@ -264,6 +267,12 @@ struct vo_driver {
// Encoding functionality, which can be invoked via --o only.
bool encode;
+ // This requires waiting for a VO_EVENT_INITIAL_UNBLOCK event before the
+ // first frame can be sent. Doing vo_reconfig*() calls is allowed though.
+ // Encode mode uses this, the core uses vo_is_ready_for_frame() to
+ // implicitly check for this.
+ bool initially_blocked;
+
// VO_CAP_* bits
int caps;
diff --git a/video/out/vo_lavc.c b/video/out/vo_lavc.c
index 2f48e3f750..e817b530e0 100644
--- a/video/out/vo_lavc.c
+++ b/video/out/vo_lavc.c
@@ -60,6 +60,13 @@ static void uninit(struct vo *vo)
encoder_encode(enc, NULL); // finish encoding
}
+static void on_ready(void *ptr)
+{
+ struct vo *vo = ptr;
+
+ vo_event(vo, VO_EVENT_INITIAL_UNBLOCK);
+}
+
static int reconfig2(struct vo *vo, struct mp_image *img)
{
struct priv *vc = vo->priv;
@@ -127,7 +134,7 @@ static int reconfig2(struct vo *vo, struct mp_image *img)
encoder->time_base = av_inv_q(tb);
- if (!encoder_init_codec_and_muxer(vc->enc))
+ if (!encoder_init_codec_and_muxer(vc->enc, on_ready, vo))
goto error;
return 0;
@@ -233,6 +240,7 @@ const struct vo_driver video_out_lavc = {
.encode = true,
.description = "video encoding using libavcodec",
.name = "lavc",
+ .initially_blocked = true,
.untimed = true,
.priv_size = sizeof(struct priv),
.preinit = preinit,