summaryrefslogtreecommitdiffstats
path: root/video/out/vo_lavc.c
diff options
context:
space:
mode:
Diffstat (limited to 'video/out/vo_lavc.c')
-rw-r--r--video/out/vo_lavc.c262
1 files changed, 262 insertions, 0 deletions
diff --git a/video/out/vo_lavc.c b/video/out/vo_lavc.c
new file mode 100644
index 0000000..7170c1d
--- /dev/null
+++ b/video/out/vo_lavc.c
@@ -0,0 +1,262 @@
+/*
+ * video encoding using libavformat
+ *
+ * Copyright (C) 2010 Nicolas George <george@nsup.org>
+ * Copyright (C) 2011-2012 Rudolf Polzer <divVerent@xonotic.org>
+ *
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "common/common.h"
+#include "options/options.h"
+#include "video/fmt-conversion.h"
+#include "video/mp_image.h"
+#include "mpv_talloc.h"
+#include "vo.h"
+
+#include "common/encode_lavc.h"
+
+#include "sub/osd.h"
+
+struct priv {
+ struct encoder_context *enc;
+
+ bool shutdown;
+};
+
+static int preinit(struct vo *vo)
+{
+ struct priv *vc = vo->priv;
+ vc->enc = encoder_context_alloc(vo->encode_lavc_ctx, STREAM_VIDEO, vo->log);
+ if (!vc->enc)
+ return -1;
+ talloc_steal(vc, vc->enc);
+ return 0;
+}
+
+static void uninit(struct vo *vo)
+{
+ struct priv *vc = vo->priv;
+ struct encoder_context *enc = vc->enc;
+
+ if (!vc->shutdown)
+ 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;
+ 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->shutdown)
+ return -1;
+
+ 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? */
+ MP_ERR(vo, "resolution changes not supported.\n");
+ goto error;
+ }
+
+ // When we get here, this must be the first call to reconfigure(). Thus, we
+ // can rely on no existing data in vc having been allocated yet.
+ // Reason:
+ // - 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 avcodec_is_open() check above).
+
+ if (pix_fmt == AV_PIX_FMT_NONE) {
+ MP_FATAL(vo, "Format %s not supported by lavc.\n",
+ mp_imgfmt_to_name(params->imgfmt));
+ goto error;
+ }
+
+ 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;
+
+ // 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;
+
+ 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);
+
+ // Used for rate control, level selection, etc.
+ // Usually it's not too catastrophic if this isn't exactly correct,
+ // as long as it's not off by orders of magnitude.
+ // If we don't set anything, encoders will use the time base,
+ // and 24000 is so high that the output can end up extremely screwy (see #11215),
+ // so we default to 240 if we don't have a real value.
+ if (img->nominal_fps > 0)
+ encoder->framerate = av_d2q(img->nominal_fps, img->nominal_fps * 1001 + 2); // Hopefully give exact results for NTSC rates
+ else
+ encoder->framerate = (AVRational){ 240, 1 };
+
+ if (!encoder_init_codec_and_muxer(vc->enc, on_ready, vo))
+ goto error;
+
+ return 0;
+
+error:
+ vc->shutdown = true;
+ return -1;
+}
+
+static int query_format(struct vo *vo, int format)
+{
+ struct priv *vc = vo->priv;
+
+ enum AVPixelFormat pix_fmt = imgfmt2pixfmt(format);
+ const enum AVPixelFormat *p = vc->enc->encoder->codec->pix_fmts;
+
+ if (!p)
+ return 1;
+
+ while (*p != AV_PIX_FMT_NONE) {
+ if (*p == pix_fmt)
+ return 1;
+ p++;
+ }
+
+ return 0;
+}
+
+static void draw_frame(struct vo *vo, struct vo_frame *voframe)
+{
+ struct priv *vc = vo->priv;
+ struct encoder_context *enc = vc->enc;
+ struct encode_lavc_context *ectx = enc->encode_lavc_ctx;
+ AVCodecContext *avc = enc->encoder;
+
+ if (voframe->redraw || voframe->repeat || voframe->num_frames < 1)
+ return;
+
+ struct mp_image *mpi = voframe->frames[0];
+
+ struct mp_osd_res dim = osd_res_from_image_params(vo->params);
+ osd_draw_on_image(vo->osd, dim, mpi->pts, OSD_DRAW_SUB_ONLY, mpi);
+
+ if (vc->shutdown)
+ return;
+
+ // Lock for shared timestamp fields.
+ mp_mutex_lock(&ectx->lock);
+
+ double pts = mpi->pts;
+ double outpts = pts;
+ if (!enc->options->rawts) {
+ // fix the discontinuity pts offset
+ if (ectx->discontinuity_pts_offset == MP_NOPTS_VALUE) {
+ ectx->discontinuity_pts_offset = ectx->next_in_pts - pts;
+ } else if (fabs(pts + ectx->discontinuity_pts_offset -
+ ectx->next_in_pts) > 30)
+ {
+ MP_WARN(vo, "detected an unexpected discontinuity (pts jumped by "
+ "%f seconds)\n",
+ pts + ectx->discontinuity_pts_offset - ectx->next_in_pts);
+ ectx->discontinuity_pts_offset = ectx->next_in_pts - pts;
+ }
+
+ outpts = pts + ectx->discontinuity_pts_offset;
+ }
+
+ if (!enc->options->rawts) {
+ // calculate expected pts of next video frame
+ double timeunit = av_q2d(avc->time_base);
+ double expected_next_pts = pts + timeunit;
+ // set next allowed output pts value
+ double nextpts = expected_next_pts + ectx->discontinuity_pts_offset;
+ if (nextpts > ectx->next_in_pts)
+ ectx->next_in_pts = nextpts;
+ }
+
+ mp_mutex_unlock(&ectx->lock);
+
+ AVFrame *frame = mp_image_to_av_frame(mpi);
+ MP_HANDLE_OOM(frame);
+
+ frame->pts = rint(outpts * av_q2d(av_inv_q(avc->time_base)));
+ frame->pict_type = 0; // keep this at unknown/undefined
+ frame->quality = avc->global_quality;
+ encoder_encode(enc, frame);
+ av_frame_free(&frame);
+}
+
+static void flip_page(struct vo *vo)
+{
+}
+
+static int control(struct vo *vo, uint32_t request, void *data)
+{
+ return VO_NOTIMPL;
+}
+
+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,
+ .query_format = query_format,
+ .reconfig2 = reconfig2,
+ .control = control,
+ .uninit = uninit,
+ .draw_frame = draw_frame,
+ .flip_page = flip_page,
+};
+
+// vim: sw=4 ts=4 et tw=80