summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefano Pigozzi <stefano.pigozzi@gmail.com>2013-08-14 15:47:18 +0200
committerStefano Pigozzi <stefano.pigozzi@gmail.com>2013-08-22 12:13:30 +0200
commita9cb2dc1b86c0f3e12231abc776009fe0a8bcf0e (patch)
treec08b89a3fd99fdd7dcfa3cc86fe597eac6357e52
parent94b4a80d455ee30e258b761290f8a87c57c7bd29 (diff)
downloadmpv-a9cb2dc1b86c0f3e12231abc776009fe0a8bcf0e.tar.bz2
mpv-a9cb2dc1b86c0f3e12231abc776009fe0a8bcf0e.tar.xz
video: add vda decode support (with hwaccel) and direct rendering
Decoding H264 using Video Decode Acceleration used the custom 'vda_h264_dec' decoder in FFmpeg. The Good: This new implementation has some advantages over the previous one: - It works with Libav: vda_h264_dec never got into Libav since they prefer client applications to use the hwaccel API. - It is way more efficient: in my tests this implementation yields a reduction of CPU usage of roughly ~50% compared to using `vda_h264_dec` and ~65-75% compared to h264 software decoding. This is mainly because `vo_corevideo` was adapted to perform direct rendering of the `CVPixelBufferRefs` created by the Video Decode Acceleration API Framework. The Bad: - `vo_corevideo` is required to use VDA decoding acceleration. - only works with versions of ffmpeg/libav new enough (needs reference refcounting). That is FFmpeg 2.0+ and Libav's git master currently. The Ugly: VDA was hardcoded to use UYVY (2vuy) for the uploaded video texture. One one end this makes the code simple since Apple's OpenGL implementation actually supports this out of the box. It would be nice to support other output image formats and choose the best format depending on the input, or at least making it configurable. My tests indicate that CPU usage actually increases with a 420p IMGFMT output which is not what I would have expected. NOTE: There is a small memory leak with old versions of FFmpeg and with Libav since the CVPixelBufferRef is not automatically released when the AVFrame is deallocated. This can cause leaks inside libavcodec for decoded frames that are discarded before mpv wraps them inside a refcounted mp_image (this only happens on seeks). For frames that enter mpv's refcounting facilities, this is not a problem since we rewrap the CVPixelBufferRef in our mp_image that properly forwards CVPixelBufferRetain/CvPixelBufferRelease calls to the underying CVPixelBufferRef. So, for FFmpeg use something more recent than `b3d63995` for Libav the patch was posted to the dev ML in July and in review since, apparently, the proposed fix is rather hacky.
-rw-r--r--DOCS/man/en/changes.rst2
-rw-r--r--DOCS/man/en/options.rst2
-rw-r--r--Makefile1
-rwxr-xr-xconfigure46
-rw-r--r--video/decode/vd_lavc.c10
-rw-r--r--video/decode/vda.c219
-rw-r--r--video/fmt-conversion.c2
-rw-r--r--video/img_format.c1
-rw-r--r--video/img_format.h7
-rw-r--r--video/out/vo_corevideo.c414
10 files changed, 591 insertions, 113 deletions
diff --git a/DOCS/man/en/changes.rst b/DOCS/man/en/changes.rst
index 4a843cb882..386275377d 100644
--- a/DOCS/man/en/changes.rst
+++ b/DOCS/man/en/changes.rst
@@ -100,6 +100,8 @@ General Changes for mplayer2 to mpv
* Allow changing/adjusting video filters at runtime. (This is also used to make
the ``D`` key insert vf_yadif if deinterlacing is not supported otherwise.)
* Native VAAPI support
+* OSX: VDA support using libavcodec hwaccel API insted of FFmpeg's decoder. Up
+ to 2-2.5x reduction in CPU usage.
* General bug fixes and removal of long-standing issues
* General code cleanups (including refactoring or rewrites of many parts)
* Many more changes
diff --git a/DOCS/man/en/options.rst b/DOCS/man/en/options.rst
index a275c5da34..37335154f4 100644
--- a/DOCS/man/en/options.rst
+++ b/DOCS/man/en/options.rst
@@ -1062,7 +1062,7 @@
:auto: see below
:vdpau: requires ``--vo=vdpau``
:vaapi: requires ``--vo=vaapi``
- :vda: OSX
+ :vda: requires ``--vo=corevideo`` (OSX only)
:crystalhd: Broadcom Crystal HD
``auto`` tries to automatically enable hardware decoding using the first
diff --git a/Makefile b/Makefile
index 4d3a179d13..290700774d 100644
--- a/Makefile
+++ b/Makefile
@@ -107,6 +107,7 @@ SOURCES-$(PULSE) += audio/out/ao_pulse.c
SOURCES-$(PORTAUDIO) += audio/out/ao_portaudio.c
SOURCES-$(RSOUND) += audio/out/ao_rsound.c
SOURCES-$(VDPAU) += video/vdpau.c video/out/vo_vdpau.c
+SOURCES-$(VDA) += video/decode/vda.c
SOURCES-$(VDPAU_DEC) += video/decode/vdpau.c
SOURCES-$(VDPAU_DEC_OLD) += video/decode/vdpau_old.c
SOURCES-$(VAAPI) += video/out/vo_vaapi.c \
diff --git a/configure b/configure
index 06f871b32b..95da109f4f 100755
--- a/configure
+++ b/configure
@@ -347,6 +347,7 @@ Video output:
--enable-sdl2 enable SDL 2.0+ audio and video output [disable]
--enable-xv enable Xv video output [autodetect]
--enable-vdpau enable VDPAU acceleration [autodetect]
+ --enable-vda enable VDA acceleration [autodetect]
--enable-vaapi enable VAAPI acceleration [autodetect]
--enable-vm enable XF86VidMode support [autodetect]
--enable-xinerama enable Xinerama support [autodetect]
@@ -422,6 +423,8 @@ _wayland=auto
_xss=auto
_xv=auto
_vdpau=auto
+_vda=auto
+_vda_refcounting=auto
_vaapi=auto
_direct3d=auto
_sdl=no
@@ -584,6 +587,8 @@ for ac_option do
--disable-xv) _xv=no ;;
--enable-vdpau) _vdpau=yes ;;
--disable-vdpau) _vdpau=no ;;
+ --enable-vda) _vda=yes ;;
+ --disable-vda) _vda=no ;;
--enable-vaapi) _vaapi=yes ;;
--disable-vaapi) _vaapi=no ;;
--enable-direct3d) _direct3d=yes ;;
@@ -2815,6 +2820,42 @@ fi
echores "$libpostproc"
+if darwin ; then
+
+echocheck "VDA"
+if test "$_vda" = auto ; then
+ _vda=no
+ if test "$_avutil_has_refcounting" = "yes" ; then
+ header_check VideoDecodeAcceleration/VDADecoder.h &&
+ header_check libavcodec/vda.h && _vda=yes
+ else
+ res_comment="libavutil too old"
+ fi
+fi
+if test "$_vda" = yes ; then
+ def_vda='#define CONFIG_VDA 1'
+ libs_mplayer="$libs_mplayer -framework VideoDecodeAcceleration -framework QuartzCore -framework IOSurface"
+else
+ def_vda='#define CONFIG_VDA 0'
+fi
+echores "$_vda"
+
+echocheck "VDA libavcodec refcounting"
+_vda_refcounting=no
+if test "$_vda" = yes ; then
+ statement_check libavcodec/vda.h 'struct vda_context a = (struct vda_context) { .use_ref_buffer = 1 }' &&
+ _vda_refcounting=yes
+fi
+if test "$_vda_refcounting" = "yes" ; then
+ def_vda_refcounting='#define HAVE_VDA_LIBAVCODEC_REFCOUNTING 1'
+else
+ def_vda_refcounting='#define HAVE_VDA_LIBAVCODEC_REFCOUNTING 0'
+fi
+echores "$_vda_refcounting"
+
+fi
+
+
echocheck "TV interface"
if test "$_tv" = yes ; then
def_tv='#define CONFIG_TV 1'
@@ -3110,6 +3151,8 @@ VCD = $_vcd
VDPAU = $_vdpau
VDPAU_DEC = $_vdpau_dec
VDPAU_DEC_OLD = $_vdpau_dec_old
+VDA = $_vda
+VDA_REFCOUNTING = $_vda_refcounting
VAAPI = $_vaapi
WIN32 = $_win32
X11 = $_x11
@@ -3120,6 +3163,7 @@ XV = $_xv
ENCODING = $_encoding
CONFIG_VDPAU = $_vdpau
+CONFIG_VDA = $_vda
CONFIG_VAAPI = $_vaapi
CONFIG_ZLIB = $_zlib
@@ -3286,6 +3330,8 @@ $def_jpeg
$def_mng
$def_v4l2
$def_vdpau
+$def_vda
+$def_vda_refcounting
$def_vaapi
$def_vm
$def_x11
diff --git a/video/decode/vd_lavc.c b/video/decode/vd_lavc.c
index b77e540168..5303d1f748 100644
--- a/video/decode/vd_lavc.c
+++ b/video/decode/vd_lavc.c
@@ -84,6 +84,7 @@ const m_option_t lavc_decode_opts_conf[] = {
const struct vd_lavc_hwdec mp_vd_lavc_vdpau;
const struct vd_lavc_hwdec mp_vd_lavc_vdpau_old;
+const struct vd_lavc_hwdec mp_vd_lavc_vda;
const struct vd_lavc_hwdec mp_vd_lavc_vaapi;
static const struct vd_lavc_hwdec mp_vd_lavc_crystalhd = {
@@ -99,11 +100,6 @@ static const struct vd_lavc_hwdec mp_vd_lavc_crystalhd = {
},
};
-static const struct vd_lavc_hwdec mp_vd_lavc_vda = {
- .type = HWDEC_VDA,
- .codec_pairs = (const char *[]) {"h264", "h264_vda", NULL},
-};
-
static const struct vd_lavc_hwdec *hwdec_list[] = {
#if CONFIG_VDPAU
#if HAVE_AV_CODEC_NEW_VDPAU_API
@@ -112,7 +108,9 @@ static const struct vd_lavc_hwdec *hwdec_list[] = {
&mp_vd_lavc_vdpau_old,
#endif
#endif // CONFIG_VDPAU
+#if CONFIG_VDA
&mp_vd_lavc_vda,
+#endif
&mp_vd_lavc_crystalhd,
#if CONFIG_VAAPI
&mp_vd_lavc_vaapi,
@@ -748,7 +746,7 @@ static int decode(struct sh_video *sh, struct demux_packet *packet,
assert(mpi->planes[0]);
if (ctx->hwdec && ctx->hwdec->process_image)
- ctx->hwdec->process_image(ctx, mpi);
+ mpi = ctx->hwdec->process_image(ctx, mpi);
mpi->colorspace = ctx->image_params.colorspace;
mpi->levels = ctx->image_params.colorlevels;
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,
+};
diff --git a/video/fmt-conversion.c b/video/fmt-conversion.c
index 842d95ce89..8bb119553f 100644
--- a/video/fmt-conversion.c
+++ b/video/fmt-conversion.c
@@ -184,6 +184,8 @@ static const struct {
{IMGFMT_VDPAU, PIX_FMT_VDPAU_H264},
#endif
+ { IMGFMT_VDA, PIX_FMT_VDA_VLD},
+
{IMGFMT_VAAPI, PIX_FMT_VAAPI_VLD},
{IMGFMT_VAAPI_MPEG2_IDCT, PIX_FMT_VAAPI_IDCT},
{IMGFMT_VAAPI_MPEG2_MOCO, PIX_FMT_VAAPI_MOCO},
diff --git a/video/img_format.c b/video/img_format.c
index 56d5ddc412..0316261393 100644
--- a/video/img_format.c
+++ b/video/img_format.c
@@ -119,6 +119,7 @@ struct mp_imgfmt_entry mp_imgfmt_list[] = {
FMT("vdpau_vc1", IMGFMT_VDPAU_VC1)
FMT("vdpau_mpeg4", IMGFMT_VDPAU_MPEG4)
FMT("vdpau", IMGFMT_VDPAU)
+ FMT("vda", IMGFMT_VDA)
FMT("vaapi", IMGFMT_VAAPI)
FMT("vaapi_mpeg2_idct", IMGFMT_VAAPI_MPEG2_IDCT)
FMT("vaapi_mpeg2_moco", IMGFMT_VAAPI_MPEG2_MOCO)
diff --git a/video/img_format.h b/video/img_format.h
index e83f5ec25b..4d876304a0 100644
--- a/video/img_format.h
+++ b/video/img_format.h
@@ -252,6 +252,8 @@ enum mp_imgfmt {
IMGFMT_VDPAU_FIRST = IMGFMT_VDPAU,
IMGFMT_VDPAU_LAST = IMGFMT_VDPAU_MPEG4,
+ IMGFMT_VDA,
+
IMGFMT_VAAPI,
IMGFMT_VAAPI_MPEG2_IDCT,
IMGFMT_VAAPI_MPEG2_MOCO,
@@ -335,10 +337,13 @@ static inline bool IMGFMT_IS_RGB(unsigned int fmt)
#define IMGFMT_IS_VDPAU(fmt) \
(((fmt) >= IMGFMT_VDPAU_FIRST) && ((fmt) <= IMGFMT_VDPAU_LAST))
+#define IMGFMT_IS_VDA(fmt) (((fmt) == IMGFMT_VDA))
+
#define IMGFMT_IS_VAAPI(fmt) \
(((fmt) >= IMGFMT_VAAPI_FIRST) && ((fmt) <= IMGFMT_VAAPI_LAST))
-#define IMGFMT_IS_HWACCEL(fmt) (IMGFMT_IS_VDPAU(fmt) || IMGFMT_IS_VAAPI(fmt))
+#define IMGFMT_IS_HWACCEL(fmt) \
+ (IMGFMT_IS_VDPAU(fmt) || IMGFMT_IS_VAAPI(fmt) || IMGFMT_IS_VDA(fmt))
struct mp_imgfmt_entry {
diff --git a/video/out/vo_corevideo.c b/video/out/vo_corevideo.c
index bad282436f..cd225aa17f 100644
--- a/video/out/vo_corevideo.c
+++ b/video/out/vo_corevideo.c
@@ -1,6 +1,7 @@
/*
* CoreVideo video output driver
* Copyright (c) 2005 Nicolas Plourde <nicolasplourde@gmail.com>
+ * Copyright (c) 2012-2013 Stefano Pigozzi <stefano.pigozzi@gmail.com>
*
* This file is part of MPlayer.
*
@@ -19,7 +20,13 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
+
#include <QuartzCore/QuartzCore.h>
+#if CONFIG_VDA
+#include <IOSurface/IOSurface.h>
+#endif
+
#include <assert.h>
#include "talloc.h"
@@ -42,9 +49,32 @@ struct quad {
GLfloat upperLeft[2];
};
+struct cv_priv {
+ CVPixelBufferRef pbuf;
+ CVOpenGLTextureCacheRef texture_cache;
+ CVOpenGLTextureRef texture;
+ OSType pixfmt;
+};
+
+struct dr_priv {
+ CVPixelBufferRef pbuf;
+ bool texture_allocated;
+ GLuint texture;
+ GLuint texture_target;
+};
+
+struct cv_functions {
+ void (*init)(struct vo *vo);
+ void (*uninit)(struct vo *vo);
+ void (*prepare_texture)(struct vo *vo, struct mp_image *mpi);
+ void (*bind_texture)(struct vo *vo);
+ void (*unbind_texture)(struct vo *vo);
+ mp_image_t *(*get_screenshot)(struct vo *vo);
+ int (*set_colormatrix)(struct vo *vo, struct mp_csp_details *csp);
+};
+
struct priv {
MPGLContext *mpglctx;
- OSType pixelFormat;
unsigned int image_width;
unsigned int image_height;
struct mp_csp_details colorspace;
@@ -52,12 +82,21 @@ struct priv {
struct mp_rect dst_rect;
struct mp_osd_res osd_res;
- CVPixelBufferRef pixelBuffer;
- CVOpenGLTextureCacheRef textureCache;
- CVOpenGLTextureRef texture;
- struct quad *quad;
+ // state for normal CoreVideo rendering path: uploads mp_image data as
+ // OpenGL textures.
+ struct cv_priv cv;
+
+ // state for IOSurface based direct rendering path: accesses the IOSurface
+ // wrapped by the CVPixelBuffer returned by VDADecoder and directly
+ // renders it to the screen.
+ struct dr_priv dr;
+ struct quad *quad;
struct mpgl_osd *osd;
+
+ // functions to to deal with the the OpenGL texture for containing the
+ // video frame (behaviour changes depending on the rendering path).
+ struct cv_functions fns;
};
static void resize(struct vo *vo)
@@ -98,17 +137,8 @@ static int init_gl(struct vo *vo, uint32_t d_width, uint32_t d_height)
gl->Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if (gl->SwapInterval)
gl->SwapInterval(1);
- return 1;
-}
-static void release_cv_entities(struct vo *vo) {
- struct priv *p = vo->priv;
- CVPixelBufferRelease(p->pixelBuffer);
- p->pixelBuffer = NULL;
- CVOpenGLTextureRelease(p->texture);
- p->texture = NULL;
- CVOpenGLTextureCacheRelease(p->textureCache);
- p->textureCache = NULL;
+ return 1;
}
static int config(struct vo *vo, uint32_t width, uint32_t height,
@@ -116,7 +146,8 @@ static int config(struct vo *vo, uint32_t width, uint32_t height,
uint32_t format)
{
struct priv *p = vo->priv;
- release_cv_entities(vo);
+ p->fns.uninit(vo);
+
p->image_width = width;
p->image_height = height;
@@ -125,26 +156,11 @@ static int config(struct vo *vo, uint32_t width, uint32_t height,
return -1;
init_gl(vo, vo->dwidth, vo->dheight);
+ p->fns.init(vo);
return 0;
}
-static void prepare_texture(struct vo *vo)
-{
- struct priv *p = vo->priv;
- struct quad *q = p->quad;
- CVReturn error;
-
- CVOpenGLTextureRelease(p->texture);
- error = CVOpenGLTextureCacheCreateTextureFromImage(NULL,
- p->textureCache, p->pixelBuffer, 0, &p->texture);
- if(error != kCVReturnSuccess)
- MP_ERR(vo, "Failed to create OpenGL texture(%d)\n", error);
-
- CVOpenGLTextureGetCleanTexCoords(p->texture, q->lowerLeft, q->lowerRight,
- q->upperRight, q->upperLeft);
-}
-
// map x/y (in range 0..1) to the video texture, and emit OpenGL vertexes
static void video_vertex(struct vo *vo, float x, float y)
{
@@ -172,12 +188,8 @@ static void do_render(struct vo *vo)
{
struct priv *p = vo->priv;
GL *gl = p->mpglctx->gl;
- prepare_texture(vo);
- gl->Enable(CVOpenGLTextureGetTarget(p->texture));
- gl->BindTexture(
- CVOpenGLTextureGetTarget(p->texture),
- CVOpenGLTextureGetName(p->texture));
+ p->fns.bind_texture(vo);
gl->Begin(GL_QUADS);
video_vertex(vo, 0, 0);
@@ -186,7 +198,7 @@ static void do_render(struct vo *vo)
video_vertex(vo, 1, 0);
gl->End();
- gl->Disable(CVOpenGLTextureGetTarget(p->texture));
+ p->fns.unbind_texture(vo);
}
static void flip_page(struct vo *vo)
@@ -196,61 +208,19 @@ static void flip_page(struct vo *vo)
p->mpglctx->gl->Clear(GL_COLOR_BUFFER_BIT);
}
-static void draw_image(struct vo *vo, mp_image_t *mpi)
+static void draw_image(struct vo *vo, struct mp_image *mpi)
{
struct priv *p = vo->priv;
- CVReturn error;
-
- if (!p->textureCache || !p->pixelBuffer) {
- error = CVOpenGLTextureCacheCreate(NULL, 0, vo_cocoa_cgl_context(vo),
- vo_cocoa_cgl_pixel_format(vo), 0, &p->textureCache);
- if(error != kCVReturnSuccess)
- MP_ERR(vo, "Failed to create OpenGL texture Cache(%d)\n", error);
-
- error = CVPixelBufferCreateWithBytes(NULL, mpi->w, mpi->h,
- p->pixelFormat, mpi->planes[0], mpi->stride[0],
- NULL, NULL, NULL, &p->pixelBuffer);
- if(error != kCVReturnSuccess)
- MP_ERR(vo, "Failed to create PixelBuffer(%d)\n", error);
- }
-
+ p->fns.prepare_texture(vo, mpi);
do_render(vo);
}
-static int query_format(struct vo *vo, uint32_t format)
-{
- struct priv *p = vo->priv;
- const int flags = VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW;
- switch (format) {
- case IMGFMT_YUYV:
- p->pixelFormat = kYUVSPixelFormat;
- return flags;
-
- case IMGFMT_UYVY:
- p->pixelFormat = k2vuyPixelFormat;
- return flags;
-
- case IMGFMT_RGB24:
- p->pixelFormat = k24RGBPixelFormat;
- return flags;
-
- case IMGFMT_ARGB:
- p->pixelFormat = k32ARGBPixelFormat;
- return flags;
-
- case IMGFMT_BGRA:
- p->pixelFormat = k32BGRAPixelFormat;
- return flags;
- }
- return 0;
-}
-
static void uninit(struct vo *vo)
{
struct priv *p = vo->priv;
if (p->osd)
mpgl_osd_destroy(p->osd);
- release_cv_entities(vo);
+ p->fns.uninit(vo);
mpgl_uninit(p->mpglctx);
}
@@ -287,23 +257,38 @@ static CFStringRef get_cv_csp_matrix(struct vo *vo)
case MP_CSP_SMPTE_240M:
return kCVImageBufferYCbCrMatrix_SMPTE_240M_1995;
default:
- return kCVImageBufferYCbCrMatrix_ITU_R_601_4;
+ return NULL;
}
}
-static void set_yuv_colorspace(struct vo *vo)
+static int set_yuv_colorspace(struct vo *vo, CVPixelBufferRef pbuf,
+ struct mp_csp_details *csp)
{
struct priv *p = vo->priv;
- CVBufferSetAttachment(p->pixelBuffer,
- kCVImageBufferYCbCrMatrixKey, get_cv_csp_matrix(vo),
- kCVAttachmentMode_ShouldPropagate);
- vo->want_redraw = true;
+ p->colorspace = *csp;
+ CFStringRef cv_csp = get_cv_csp_matrix(vo);
+
+ if (cv_csp) {
+ CVBufferSetAttachment(p->cv.pbuf, kCVImageBufferYCbCrMatrixKey, cv_csp,
+ kCVAttachmentMode_ShouldNotPropagate);
+ vo->want_redraw = true;
+ return VO_TRUE;
+ } else {
+ return VO_NOTIMPL;
+ }
}
-static int get_image_fmt(struct vo *vo)
+static int get_yuv_colorspace(struct vo *vo, struct mp_csp_details *csp)
{
struct priv *p = vo->priv;
- switch (p->pixelFormat) {
+ *csp = p->colorspace;
+ return VO_TRUE;
+}
+
+static int get_image_fmt(struct vo *vo, CVPixelBufferRef pbuf)
+{
+ OSType pixfmt = CVPixelBufferGetPixelFormatType(pbuf);
+ switch (pixfmt) {
case kYUVSPixelFormat: return IMGFMT_YUYV;
case k2vuyPixelFormat: return IMGFMT_UYVY;
case k24RGBPixelFormat: return IMGFMT_RGB24;
@@ -311,21 +296,21 @@ static int get_image_fmt(struct vo *vo)
case k32BGRAPixelFormat: return IMGFMT_BGRA;
}
MP_ERR(vo, "Failed to convert pixel format. Please contact the "
- "developers. PixelFormat: %d\n", p->pixelFormat);
+ "developers. PixelFormat: %d\n", pixfmt);
return -1;
}
-static mp_image_t *get_screenshot(struct vo *vo)
+static mp_image_t *get_screenshot(struct vo *vo, CVPixelBufferRef pbuf)
{
- int img_fmt = get_image_fmt(vo);
+ int img_fmt = get_image_fmt(vo, pbuf);
if (img_fmt < 0) return NULL;
struct priv *p = vo->priv;
- void *base = CVPixelBufferGetBaseAddress(p->pixelBuffer);
-
- size_t width = CVPixelBufferGetWidth(p->pixelBuffer);
- size_t height = CVPixelBufferGetHeight(p->pixelBuffer);
- size_t stride = CVPixelBufferGetBytesPerRow(p->pixelBuffer);
+ CVPixelBufferLockBaseAddress(pbuf, 0);
+ void *base = CVPixelBufferGetBaseAddress(pbuf);
+ size_t width = CVPixelBufferGetWidth(pbuf);
+ size_t height = CVPixelBufferGetHeight(pbuf);
+ size_t stride = CVPixelBufferGetBytesPerRow(pbuf);
struct mp_image img = {0};
mp_image_setfmt(&img, img_fmt);
@@ -336,6 +321,7 @@ static mp_image_t *get_screenshot(struct vo *vo)
struct mp_image *image = mp_image_new_copy(&img);
mp_image_set_display_size(image, vo->aspdat.prew, vo->aspdat.preh);
mp_image_set_colorspace_details(image, &p->colorspace);
+ CVPixelBufferUnlockBaseAddress(pbuf, 0);
return image;
}
@@ -353,18 +339,15 @@ static int control(struct vo *vo, uint32_t request, void *data)
do_render(vo);
return VO_TRUE;
case VOCTRL_SET_YUV_COLORSPACE:
- p->colorspace.format = ((struct mp_csp_details *)data)->format;
- set_yuv_colorspace(vo);
- return VO_TRUE;
+ return p->fns.set_colormatrix(vo, data);
case VOCTRL_GET_YUV_COLORSPACE:
- *(struct mp_csp_details *)data = p->colorspace;
- return VO_TRUE;
+ return get_yuv_colorspace(vo, data);
case VOCTRL_SCREENSHOT: {
struct voctrl_screenshot_args *args = data;
if (args->full_window)
args->out_image = glGetWindowScreenshot(p->mpglctx->gl);
else
- args->out_image = get_screenshot(vo);
+ args->out_image = p->fns.get_screenshot(vo);
return VO_TRUE;
}
}
@@ -377,6 +360,227 @@ static int control(struct vo *vo, uint32_t request, void *data)
return r;
}
+static void dummy_cb(struct vo *vo) { }
+
+static void cv_uninit(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+ CVPixelBufferRelease(p->cv.pbuf);
+ p->cv.pbuf = NULL;
+ CVOpenGLTextureRelease(p->cv.texture);
+ p->cv.texture = NULL;
+ CVOpenGLTextureCacheRelease(p->cv.texture_cache);
+ p->cv.texture_cache = NULL;
+}
+
+static void cv_bind_texture(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+ GL *gl = p->mpglctx->gl;
+
+ gl->Enable(CVOpenGLTextureGetTarget(p->cv.texture));
+ gl->BindTexture(CVOpenGLTextureGetTarget(p->cv.texture),
+ CVOpenGLTextureGetName(p->cv.texture));
+
+}
+
+static void cv_unbind_texture(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+ GL *gl = p->mpglctx->gl;
+
+ gl->Disable(CVOpenGLTextureGetTarget(p->cv.texture));
+}
+
+static void upload_opengl_texture(struct vo *vo, struct mp_image *mpi)
+{
+ struct priv *p = vo->priv;
+ if (!p->cv.texture_cache || !p->cv.pbuf) {
+ CVReturn error;
+ error = CVOpenGLTextureCacheCreate(NULL, 0, vo_cocoa_cgl_context(vo),
+ vo_cocoa_cgl_pixel_format(vo), 0, &p->cv.texture_cache);
+ if(error != kCVReturnSuccess)
+ MP_ERR(vo, "Failed to create OpenGL texture Cache(%d)\n", error);
+
+ error = CVPixelBufferCreateWithBytes(NULL, mpi->w, mpi->h,
+ p->cv.pixfmt, mpi->planes[0], mpi->stride[0],
+ NULL, NULL, NULL, &p->cv.pbuf);
+ if(error != kCVReturnSuccess)
+ MP_ERR(vo, "Failed to create PixelBuffer(%d)\n", error);
+ }
+
+ struct quad *q = p->quad;
+ CVReturn error;
+
+ CVOpenGLTextureRelease(p->cv.texture);
+ error = CVOpenGLTextureCacheCreateTextureFromImage(NULL,
+ p->cv.texture_cache, p->cv.pbuf, 0, &p->cv.texture);
+ if(error != kCVReturnSuccess)
+ MP_ERR(vo, "Failed to create OpenGL texture(%d)\n", error);
+
+ CVOpenGLTextureGetCleanTexCoords(p->cv.texture,
+ q->lowerLeft, q->lowerRight, q->upperRight, q->upperLeft);
+}
+
+static mp_image_t *cv_get_screenshot(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+ return get_screenshot(vo, p->cv.pbuf);
+}
+
+static int cv_set_colormatrix(struct vo *vo, struct mp_csp_details *csp)
+{
+ struct priv *p = vo->priv;
+ return set_yuv_colorspace(vo, p->cv.pbuf, csp);
+}
+
+static struct cv_functions cv_functions = {
+ .init = dummy_cb,
+ .uninit = cv_uninit,
+ .bind_texture = cv_bind_texture,
+ .unbind_texture = cv_unbind_texture,
+ .prepare_texture = upload_opengl_texture,
+ .get_screenshot = cv_get_screenshot,
+ .set_colormatrix = cv_set_colormatrix,
+};
+
+#if CONFIG_VDA
+static void iosurface_init(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+ GL *gl = p->mpglctx->gl;
+
+ p->dr.texture_target = GL_TEXTURE_RECTANGLE_ARB;
+ p->fns.bind_texture(vo);
+ gl->GenTextures(1, &p->dr.texture);
+ p->fns.unbind_texture(vo);
+
+ p->dr.texture_allocated = true;
+}
+
+static void iosurface_uninit(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+ GL *gl = p->mpglctx->gl;
+ if (p->dr.texture_allocated) {
+ gl->DeleteTextures(1, &p->dr.texture);
+ p->dr.texture_allocated = false;
+ }
+}
+
+static void iosurface_bind_texture(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+ GL *gl = p->mpglctx->gl;
+