summaryrefslogtreecommitdiffstats
path: root/video/decode/vda.c
diff options
context:
space:
mode:
Diffstat (limited to 'video/decode/vda.c')
-rw-r--r--video/decode/vda.c219
1 files changed, 219 insertions, 0 deletions
diff --git a/video/decode/vda.c b/video/decode/vda.c
new file mode 100644
index 0000000000..2aba19111f
--- /dev/null
+++ b/video/decode/vda.c
@@ -0,0 +1,219 @@
+/*
+ * This file is part of mpv.
+ *
+ * Copyright (c) 2013 Stefano Pigozzi <stefano.pigozzi@gmail.com>
+ *
+ * mpv is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <libavcodec/version.h>
+#include <libavcodec/vda.h>
+
+#include "mpvcore/av_common.h"
+#include "mpvcore/mp_msg.h"
+#include "video/mp_image.h"
+#include "video/decode/lavc.h"
+#include "config.h"
+
+struct priv {
+ struct vda_context vda_ctx;
+};
+
+struct profile_entry {
+ enum AVCodecID av_codec;
+ int ff_profile;
+ uint32_t vda_codec;
+};
+
+static const struct profile_entry profiles[] = {
+ { AV_CODEC_ID_H264, FF_PROFILE_UNKNOWN, 'avc1' },
+};
+
+static const struct profile_entry *find_codec(enum AVCodecID id, int ff_profile)
+{
+ for (int n = 0; n < MP_ARRAY_SIZE(profiles); n++) {
+ if (profiles[n].av_codec == id &&
+ (profiles[n].ff_profile == ff_profile ||
+ profiles[n].ff_profile == FF_PROFILE_UNKNOWN))
+ {
+ return &profiles[n];
+ }
+ }
+ return NULL;
+}
+
+struct vda_error {
+ int code;
+ char *reason;
+};
+
+static const struct vda_error vda_errors[] = {
+ { kVDADecoderHardwareNotSupportedErr,
+ "Hardware doesn't support accelerated decoding" },
+ { kVDADecoderFormatNotSupportedErr,
+ "Hardware doesn't support requested output format" },
+ { kVDADecoderConfigurationError,
+ "Invalid configuration provided to VDADecoderCreate" },
+ { kVDADecoderDecoderFailedErr,
+ "Generic error returned by the decoder layer. The cause can range from"
+ " VDADecoder finding errors in the bitstream to another application"
+ " using VDA at the moment. Only one application can use VDA at a"
+ " givent time." },
+ { 0, NULL },
+};
+
+static void print_vda_error(int lev, char *message, int error_code)
+{
+ for (int n = 0; vda_errors[n].code < 0; n++)
+ if (vda_errors[n].code == error_code) {
+ mp_msg(MSGT_DECVIDEO, lev, "%s: %s (%d)\n",
+ message, vda_errors[n].reason, error_code);
+ return;
+ }
+
+ mp_msg(MSGT_DECVIDEO, lev, "%s: %d\n", message, error_code);
+}
+
+static int probe(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info,
+ const char *decoder)
+{
+ if (!find_codec(mp_codec_to_av_codec_id(decoder), FF_PROFILE_UNKNOWN))
+ return HWDEC_ERR_NO_CODEC;
+ return 0;
+}
+
+static int init_vda_decoder(struct lavc_ctx *ctx)
+{
+ struct priv *p = ctx->hwdec_priv;
+
+ if (p->vda_ctx.decoder)
+ ff_vda_destroy_decoder(&p->vda_ctx);
+
+ const struct profile_entry *pe =
+ find_codec(ctx->avctx->codec_id, ctx->avctx->profile);
+
+ p->vda_ctx = (struct vda_context) {
+ .width = ctx->avctx->width,
+ .height = ctx->avctx->height,
+ .format = pe->vda_codec,
+ .cv_pix_fmt_type = kCVPixelFormatType_422YpCbCr8,
+
+#if HAVE_VDA_LIBAVCODEC_REFCOUNTING
+ .use_ref_buffer = 1,
+#endif
+ // use_ref_buffer is 1 in ffmpeg (while libav doesn't support this
+ // feature). This means that in the libav case, libavcodec returns us
+ // a CVPixelBuffer with refcount=1 AND hands over ownership of that
+ // reference.
+
+ // This is slightly different from a typical refcounted situation
+ // where the API would return something that we need to to retain
+ // for it to stay around (ffmpeg behaves like expected when using
+ // use_ref_buffer = 1).
+
+ // If mpv doesn't properly free CVPixelBufferRefs that are no longer
+ // used, the wrapped IOSurface ids increase monotonically hinting at
+ // a leaking of both CVPixelBuffers and IOSurfaces.
+ };
+
+ int status = ff_vda_create_decoder(
+ &p->vda_ctx, ctx->avctx->extradata, ctx->avctx->extradata_size);
+
+ if (status) {
+ print_vda_error(MSGL_ERR, "[vda] failed to init decoder", status);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int init(struct lavc_ctx *ctx)
+{
+ struct priv *p = talloc_zero(NULL, struct priv);
+ ctx->hwdec_priv = p;
+ ctx->avctx->hwaccel_context = &p->vda_ctx;
+ return 0;
+}
+
+static void uninit(struct lavc_ctx *ctx) {
+ struct priv *p = ctx->hwdec_priv;
+ if (p->vda_ctx.decoder)
+ ff_vda_destroy_decoder(&p->vda_ctx);
+}
+
+static void cv_retain(void *pbuf)
+{
+ CVPixelBufferRetain((CVPixelBufferRef)pbuf);
+}
+
+static void cv_release(void *pbuf)
+{
+ CVPixelBufferRelease((CVPixelBufferRef)pbuf);
+}
+
+static struct mp_image *mp_image_new_cv_ref(struct mp_image *mpi)
+{
+ CVPixelBufferRef pbuf = (CVPixelBufferRef)mpi->planes[3];
+ // mp_image_new_external_ref assumes the external reference count is
+ // already 1 so the calls to cv_retain and cv_release are unbalanced (
+ // in favor of cv_release). To balance out the retain count we need to
+ // retain the CVPixelBufferRef if ffmpeg is set to automatically release
+ // it when the AVFrame is unreffed.
+#if HAVE_VDA_LIBAVCODEC_REFCOUNTING
+ cv_retain(pbuf);
+#endif
+ return mp_image_new_external_ref(mpi,
+ pbuf, cv_retain, cv_release, NULL, NULL);
+}
+
+static struct mp_image *process_image(struct lavc_ctx *ctx, struct mp_image *mpi)
+{
+ struct mp_image *cv_mpi = mp_image_new_cv_ref(mpi);
+ mp_image_unrefp(&mpi);
+ return cv_mpi;
+}
+
+// This actually returns dummy images, since vda_264 creates it's own AVFrames
+// to wrap CVPixelBuffers in planes[3].
+static struct mp_image *allocate_image(struct lavc_ctx *ctx, int fmt,
+ int w, int h)
+{
+ struct priv *p = ctx->hwdec_priv;
+
+ if (fmt != IMGFMT_VDA)
+ return NULL;
+
+ if (w != p->vda_ctx.width || h != p->vda_ctx.height)
+ init_vda_decoder(ctx);
+
+ struct mp_image img = {0};
+ mp_image_setfmt(&img, fmt);
+ mp_image_set_size(&img, w, h);
+
+ // There is an `assert(!dst->f.buf[0])` in libavcodec/h264.c
+ // Setting the first plane to some dummy value allows to satisfy it
+ img.planes[0] = (void*)"dummy";
+
+ return mp_image_new_custom_ref(&img, NULL, NULL);
+}
+
+const struct vd_lavc_hwdec mp_vd_lavc_vda = {
+ .type = HWDEC_VDA,
+ .image_formats = (const int[]) { IMGFMT_VDA, 0 },
+ .probe = probe,
+ .init = init,
+ .uninit = uninit,
+ .allocate_image = allocate_image,
+ .process_image = process_image,
+};