summaryrefslogtreecommitdiffstats
path: root/video
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2013-08-09 14:01:30 +0200
committerwm4 <wm4@nowhere>2013-08-12 01:12:02 +0200
commit2827295703c74e3c119df9a435aa856e268c2ea9 (patch)
tree7464b1630d76e84f1abdf53680544a74b7dab300 /video
parentc7da4ba74469bc6c404c396340ffadc748535f6e (diff)
downloadmpv-2827295703c74e3c119df9a435aa856e268c2ea9.tar.bz2
mpv-2827295703c74e3c119df9a435aa856e268c2ea9.tar.xz
video: add vaapi decode and output support
This is based on the MPlayer VA API patches. To be exact it's based on a very stripped down version of commit f1ad459a263f8537f6c from git://gitorious.org/vaapi/mplayer.git. This doesn't contain useless things like benchmarking hacks and the demo code for GLX interop. Also, unlike in the original patch, decoding and video output are split into separate source files (the separation between decoding and display also makes pixel format hacks unnecessary). On the other hand, some features not present in the original patch were added, like screenshot support. VA API is rather bad for actual video output. Dealing with older libva versions or the completely broken vdpau backend doesn't help. OSD is low quality and should be rather slow. In some cases, only either OSD or subtitles can be shown at the same time (because OSD is drawn first, OSD is prefered). Also, libva can't decide whether it accepts straight or premultiplied alpha for OSD sub-pictures: the vdpau backend seems to assume premultiplied, while a native vaapi driver uses straight. So I picked straight alpha. It doesn't matter much, because the blending code for straight alpha I added to img_convert.c is probably buggy, and ASS subtitles might be blended incorrectly. Really good video output with VA API would probably use OpenGL and the GL interop features, but at this point you might just use vo_opengl. (Patches for making HW decoding with vo_opengl have a chance of being accepted.) Despite these issues, decoding seems to work ok. I still got tearing on the Intel system I tested (Intel(R) Core(TM) i3-2350M). It was also tested with the vdpau vaapi wrapper on a nvidia system; however this was rather broken. (Fortunately, there is no reason to use mpv's VAAPI support over native VDPAU.)
Diffstat (limited to 'video')
-rw-r--r--video/decode/dec_video.h1
-rw-r--r--video/decode/lavc.h1
-rw-r--r--video/decode/vaapi.c414
-rw-r--r--video/decode/vd_lavc.c4
-rw-r--r--video/fmt-conversion.c5
-rw-r--r--video/img_format.c3
-rw-r--r--video/img_format.h14
-rw-r--r--video/out/vo.c4
-rw-r--r--video/out/vo.h2
-rw-r--r--video/out/vo_vaapi.c1054
-rw-r--r--video/vaapi.h73
11 files changed, 1572 insertions, 3 deletions
diff --git a/video/decode/dec_video.h b/video/decode/dec_video.h
index 4ba052afd1..021abaaf22 100644
--- a/video/decode/dec_video.h
+++ b/video/decode/dec_video.h
@@ -51,6 +51,7 @@ extern int divx_quality;
// The VO can set the context pointer for supported APIs.
struct mp_hwdec_info {
struct mp_vdpau_ctx *vdpau_ctx;
+ struct mp_vaapi_ctx *vaapi_ctx;
};
#endif /* MPLAYER_DEC_VIDEO_H */
diff --git a/video/decode/lavc.h b/video/decode/lavc.h
index 94973cb2d8..4252034b2c 100644
--- a/video/decode/lavc.h
+++ b/video/decode/lavc.h
@@ -17,6 +17,7 @@ enum hwdec_type {
HWDEC_VDPAU = 1,
HWDEC_VDA = 2,
HWDEC_CRYSTALHD = 3,
+ HWDEC_VAAPI = 4,
};
typedef struct lavc_ctx {
diff --git a/video/decode/vaapi.c b/video/decode/vaapi.c
new file mode 100644
index 0000000000..baa13588c9
--- /dev/null
+++ b/video/decode/vaapi.c
@@ -0,0 +1,414 @@
+/*
+ * This file is part of mpv.
+ *
+ * With some chunks from original MPlayer VAAPI patch:
+ * Copyright (C) 2008-2009 Splitted-Desktop Systems
+ *
+ * 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 <stddef.h>
+#include <assert.h>
+
+#include <libavcodec/avcodec.h>
+#include <libavcodec/vaapi.h>
+#include <libavutil/common.h>
+
+#include "lavc.h"
+#include "mpvcore/mp_common.h"
+#include "mpvcore/av_common.h"
+#include "video/fmt-conversion.h"
+#include "video/vaapi.h"
+#include "video/decode/dec_video.h"
+
+/*
+ * The VAAPI decoder can work only with surfaces passed to the decoder at
+ * creation time. This means all surfaces have to be created in advance.
+ * So, additionally to the maximum number of reference frames, we need
+ * surfaces for:
+ * - 1 decode frame
+ * - decoding 1 frame ahead (done by generic playback code)
+ * - keeping the reference to the previous frame (done by vo_vaapi.c)
+ * Note that redundant additional surfaces also might allow for some
+ * buffering (i.e. not trying to reuse a surface while it's busy).
+ */
+#define ADDTIONAL_SURFACES 3
+
+// Magic number taken from original MPlayer vaapi patch.
+#define MAX_DECODER_SURFACES 21
+
+#define MAX_SURFACES (MAX_DECODER_SURFACES + ADDTIONAL_SURFACES)
+
+struct priv {
+ struct mp_vaapi_ctx *ctx;
+ VADisplay display;
+
+ // libavcodec shared struct
+ struct vaapi_context *va_context;
+ struct vaapi_context va_context_storage;
+
+ int format, w, h;
+ VASurfaceID surfaces[MAX_SURFACES];
+};
+
+struct profile_entry {
+ enum AVCodecID av_codec;
+ int ff_profile;
+ VAProfile va_profile;
+ int maxrefs;
+};
+
+#define PE(av_codec_id, ff_profile, va_dcoder_profile, maxrefs) \
+ {AV_CODEC_ID_ ## av_codec_id, \
+ FF_PROFILE_ ## ff_profile, \
+ VAProfile ## va_dcoder_profile, \
+ maxrefs}
+
+static const struct profile_entry profiles[] = {
+ PE(MPEG2VIDEO, MPEG2_SIMPLE, MPEG2Simple, 2),
+ PE(MPEG2VIDEO, UNKNOWN, MPEG2Main, 2),
+ PE(H264, H264_BASELINE, H264Baseline, 16),
+ PE(H264, H264_CONSTRAINED_BASELINE, H264ConstrainedBaseline, 16),
+ PE(H264, H264_MAIN, H264Main, 16),
+ PE(H264, UNKNOWN, H264High, 16),
+ PE(WMV3, VC1_SIMPLE, VC1Simple, 2),
+ PE(WMV3, VC1_MAIN, VC1Main, 2),
+ PE(WMV3, UNKNOWN, VC1Advanced, 2),
+ PE(VC1, VC1_SIMPLE, VC1Simple, 2),
+ PE(VC1, VC1_MAIN, VC1Main, 2),
+ PE(VC1, UNKNOWN, VC1Advanced, 2),
+ // No idea whether these are correct
+ PE(MPEG4, MPEG4_SIMPLE, MPEG4Simple, 2),
+ PE(MPEG4, MPEG4_MAIN, MPEG4Main, 2),
+ PE(MPEG4, UNKNOWN, MPEG4AdvancedSimple, 2),
+};
+
+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;
+}
+
+
+static const char *str_va_profile(VAProfile profile)
+{
+ switch (profile) {
+#define PROFILE(profile) \
+ case VAProfile##profile: return "VAProfile" #profile
+ PROFILE(MPEG2Simple);
+ PROFILE(MPEG2Main);
+ PROFILE(MPEG4Simple);
+ PROFILE(MPEG4AdvancedSimple);
+ PROFILE(MPEG4Main);
+ PROFILE(H264Baseline);
+ PROFILE(H264Main);
+ PROFILE(H264High);
+ PROFILE(VC1Simple);
+ PROFILE(VC1Main);
+ PROFILE(VC1Advanced);
+#undef PROFILE
+ }
+ return "<unknown>";
+}
+
+static int find_entrypoint(int format, VAEntrypoint *ep, int num_ep)
+{
+ int entrypoint = -1;
+ switch (format) {
+ case IMGFMT_VAAPI: entrypoint = VAEntrypointVLD; break;
+ case IMGFMT_VAAPI_MPEG2_IDCT: entrypoint = VAEntrypointIDCT; break;
+ case IMGFMT_VAAPI_MPEG2_MOCO: entrypoint = VAEntrypointMoComp; break;
+ }
+ for (int n = 0; n < num_ep; n++) {
+ if (ep[n] == entrypoint)
+ return entrypoint;
+ }
+ return -1;
+}
+
+static int is_direct_mapping(VADisplay display)
+{
+ VADisplayAttribute attr;
+ VAStatus status;
+
+#if VA_CHECK_VERSION(0,34,0)
+ attr.type = VADisplayAttribRenderMode;
+ attr.flags = VA_DISPLAY_ATTRIB_GETTABLE;
+
+ status = vaGetDisplayAttributes(display, &attr, 1);
+ if (status == VA_STATUS_SUCCESS)
+ return !(attr.value & (VA_RENDER_MODE_LOCAL_OVERLAY|
+ VA_RENDER_MODE_EXTERNAL_OVERLAY));
+#else
+ /* If the driver doesn't make a copy of the VA surface for
+ display, then we have to retain it until it's no longer the
+ visible surface. In other words, if the driver is using
+ DirectSurface mode, we don't want to decode the new surface
+ into the previous one that was used for display. */
+ attr.type = VADisplayAttribDirectSurface;
+ attr.flags = VA_DISPLAY_ATTRIB_GETTABLE;
+
+ status = vaGetDisplayAttributes(display, &attr, 1);
+ if (status == VA_STATUS_SUCCESS)
+ return !attr.value;
+#endif
+ return 0;
+}
+
+// Make vo_vaapi.c pool the required number of surfaces.
+// This is very touchy: vo_vaapi.c must not free surfaces while we decode,
+// and we must allocate only surfaces that were passed to the decoder on
+// creation.
+// We achieve this by deleting all previous surfaces, then allocate every
+// surface needed. Then we free these surfaces, and rely on the fact that
+// vo_vaapi.c keeps the released surfaces in the pool, and only allocates
+// new surfaces out of that pool.
+static int preallocate_surfaces(struct lavc_ctx *ctx, int va_rt_format, int num)
+{
+ struct priv *p = ctx->hwdec_priv;
+ int res = -1;
+
+ struct mp_image *tmp_surfaces[MAX_SURFACES] = {0};
+
+ p->ctx->flush(p->ctx); // free previously allocated surfaces
+
+ for (int n = 0; n < num; n++) {
+ tmp_surfaces[n] = p->ctx->get_surface(p->ctx, va_rt_format, p->format,
+ p->w, p->h);
+ if (!tmp_surfaces[n])
+ goto done;
+ p->surfaces[n] = (uintptr_t)tmp_surfaces[n]->planes[3];
+ }
+ res = 0;
+
+done:
+ for (int n = 0; n < num; n++)
+ talloc_free(tmp_surfaces[n]);
+ return res;
+}
+
+static void destroy_decoder(struct lavc_ctx *ctx)
+{
+ struct priv *p = ctx->hwdec_priv;
+
+ if (p->va_context->context_id != VA_INVALID_ID) {
+ vaDestroyContext(p->display, p->va_context->context_id);
+ p->va_context->context_id = VA_INVALID_ID;
+ }
+
+ if (p->va_context->config_id != VA_INVALID_ID) {
+ vaDestroyConfig(p->display, p->va_context->config_id);
+ p->va_context->config_id = VA_INVALID_ID;
+ }
+
+ for (int n = 0; n < MAX_SURFACES; n++)
+ p->surfaces[n] = VA_INVALID_ID;
+}
+
+static int create_decoder(struct lavc_ctx *ctx)
+{
+ void *tmp = talloc_new(NULL);
+
+ struct priv *p = ctx->hwdec_priv;
+ VAStatus status;
+ int res = -1;
+
+ assert(IMGFMT_IS_VAAPI(p->format));
+
+ destroy_decoder(ctx);
+
+ const struct profile_entry *pe = find_codec(ctx->avctx->codec_id,
+ ctx->avctx->profile);
+ if (!pe) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[vaapi] Unknown codec!\n");
+ goto error;
+ }
+
+ int num_profiles = vaMaxNumProfiles(p->display);
+ VAProfile *va_profiles = talloc_zero_array(tmp, VAProfile, num_profiles);
+ status = vaQueryConfigProfiles(p->display, va_profiles, &num_profiles);
+ if (!check_va_status(status, "vaQueryConfigProfiles()"))
+ goto error;
+ mp_msg(MSGT_VO, MSGL_DBG2, "[vaapi] %d profiles available:\n", num_profiles);
+ for (int i = 0; i < num_profiles; i++)
+ mp_msg(MSGT_VO, MSGL_DBG2, " %s\n", str_va_profile(va_profiles[i]));
+
+ bool profile_found = false;
+ for (int i = 0; i < num_profiles; i++) {
+ if (pe->va_profile == va_profiles[i]) {
+ profile_found = true;
+ break;
+ }
+ }
+ if (!profile_found) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[vaapi] Profile '%s' not available.\n",
+ str_va_profile(pe->va_profile));
+ goto error;
+ }
+
+ int num_surfaces = pe->maxrefs;
+ if (!is_direct_mapping(p->display)) {
+ mp_msg(MSGT_VO, MSGL_V, "[vaapi] No direct mapping.\n");
+ // Note: not sure why it has to be *=2 rather than +=1.
+ num_surfaces *= 2;
+ }
+ num_surfaces = MPMIN(num_surfaces, MAX_DECODER_SURFACES) + ADDTIONAL_SURFACES;
+
+ if (num_surfaces > MAX_SURFACES) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[vaapi] Internal error: too many surfaces.\n");
+ goto error;
+ }
+
+ if (preallocate_surfaces(ctx, VA_RT_FORMAT_YUV420, num_surfaces) < 0) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[vaapi] Could not allocate surfaces.\n");
+ goto error;
+ }
+
+ int num_ep = vaMaxNumEntrypoints(p->display);
+ VAEntrypoint *ep = talloc_zero_array(tmp, VAEntrypoint, num_ep);
+ status = vaQueryConfigEntrypoints(p->display, pe->va_profile, ep, &num_ep);
+ if (!check_va_status(status, "vaQueryConfigEntrypoints()"))
+ goto error;
+
+ VAEntrypoint entrypoint = find_entrypoint(p->format, ep, num_ep);
+ if (entrypoint < 0) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[vaapi] Could not find VA entrypoint.\n");
+ goto error;
+ }
+
+ VAConfigAttrib attrib = {
+ .type = VAConfigAttribRTFormat,
+ };
+ status = vaGetConfigAttributes(p->display, pe->va_profile, entrypoint,
+ &attrib, 1);
+ if (!check_va_status(status, "vaGetConfigAttributes()"))
+ goto error;
+ if ((attrib.value & VA_RT_FORMAT_YUV420) == 0) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[vaapi] Chroma format not supported.\n");
+ goto error;
+ }
+
+ status = vaCreateConfig(p->display, pe->va_profile, entrypoint, &attrib, 1,
+ &p->va_context->config_id);
+ if (!check_va_status(status, "vaCreateConfig()"))
+ goto error;
+
+ status = vaCreateContext(p->display, p->va_context->config_id,
+ p->w, p->h, VA_PROGRESSIVE,
+ p->surfaces, num_surfaces,
+ &p->va_context->context_id);
+ if (!check_va_status(status, "vaCreateContext()"))
+ goto error;
+
+ res = 0;
+error:
+ talloc_free(tmp);
+ return res;
+}
+
+static struct mp_image *allocate_image(struct lavc_ctx *ctx, AVFrame *frame)
+{
+ struct priv *p = ctx->hwdec_priv;
+ int format = pixfmt2imgfmt(frame->format);
+
+ if (!IMGFMT_IS_VAAPI(format))
+ return NULL;
+
+ // frame->width/height lie. Using them breaks with non-mod 16 video.
+ int w = ctx->avctx->width;
+ int h = ctx->avctx->height;
+
+ if (format != p->format || w != p->w || h != p->h ||
+ p->va_context->context_id == VA_INVALID_ID)
+ {
+ p->format = format;
+ p->w = w;
+ p->h = h;
+ if (create_decoder(ctx) < 0)
+ return NULL;
+ }
+
+ struct mp_image *img = p->ctx->get_surface(p->ctx, VA_RT_FORMAT_YUV420,
+ format, p->w, p->h);
+ if (img) {
+ for (int n = 0; n < MAX_SURFACES; n++) {
+ if (p->surfaces[n] == (uintptr_t)img->planes[3])
+ return img;
+ }
+ talloc_free(img);
+ }
+ mp_msg(MSGT_VO, MSGL_ERR, "[vaapi] Insufficient number of surfaces.\n");
+ return NULL;
+}
+
+static void uninit(struct lavc_ctx *ctx)
+{
+ struct priv *p = ctx->hwdec_priv;
+
+ if (!p)
+ return;
+
+ destroy_decoder(ctx);
+
+ talloc_free(p);
+ ctx->hwdec_priv = NULL;
+}
+
+static int init(struct lavc_ctx *ctx)
+{
+ struct priv *p = talloc_ptrtype(NULL, p);
+ *p = (struct priv) {
+ .ctx = ctx->hwdec_info->vaapi_ctx,
+ .va_context = &p->va_context_storage,
+ };
+ ctx->hwdec_priv = p;
+
+ p->display = p->ctx->display;
+
+ p->va_context->display = p->display;
+ p->va_context->config_id = VA_INVALID_ID;
+ p->va_context->context_id = VA_INVALID_ID;
+
+ ctx->avctx->hwaccel_context = p->va_context;
+
+ return 0;
+}
+
+
+static int probe(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info,
+ const char *decoder)
+{
+ if (!info || !info->vaapi_ctx)
+ return HWDEC_ERR_NO_CTX;
+ if (!find_codec(mp_codec_to_av_codec_id(decoder), FF_PROFILE_UNKNOWN))
+ return HWDEC_ERR_NO_CODEC;
+ return 0;
+}
+
+const struct vd_lavc_hwdec mp_vd_lavc_vaapi = {
+ .type = HWDEC_VAAPI,
+ .image_formats = (const int[]) {IMGFMT_VAAPI, IMGFMT_VAAPI_MPEG2_IDCT,
+ IMGFMT_VAAPI_MPEG2_MOCO, 0},
+ .probe = probe,
+ .init = init,
+ .uninit = uninit,
+ .allocate_image = allocate_image,
+};
diff --git a/video/decode/vd_lavc.c b/video/decode/vd_lavc.c
index 2fc7a1ea4c..639e46ebcc 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_vaapi;
static const struct vd_lavc_hwdec mp_vd_lavc_crystalhd = {
.type = HWDEC_CRYSTALHD,
@@ -113,6 +114,9 @@ static const struct vd_lavc_hwdec *hwdec_list[] = {
#endif // CONFIG_VDPAU
&mp_vd_lavc_vda,
&mp_vd_lavc_crystalhd,
+#if CONFIG_VAAPI
+ &mp_vd_lavc_vaapi,
+#endif
NULL
};
diff --git a/video/fmt-conversion.c b/video/fmt-conversion.c
index 509ff31535..842d95ce89 100644
--- a/video/fmt-conversion.c
+++ b/video/fmt-conversion.c
@@ -183,6 +183,11 @@ static const struct {
// map to an arbitrary but existing vdpau format
{IMGFMT_VDPAU, PIX_FMT_VDPAU_H264},
#endif
+
+ {IMGFMT_VAAPI, PIX_FMT_VAAPI_VLD},
+ {IMGFMT_VAAPI_MPEG2_IDCT, PIX_FMT_VAAPI_IDCT},
+ {IMGFMT_VAAPI_MPEG2_MOCO, PIX_FMT_VAAPI_MOCO},
+
{0, PIX_FMT_NONE}
};
diff --git a/video/img_format.c b/video/img_format.c
index 9422fbdd27..56d5ddc412 100644
--- a/video/img_format.c
+++ b/video/img_format.c
@@ -119,6 +119,9 @@ struct mp_imgfmt_entry mp_imgfmt_list[] = {
FMT("vdpau_vc1", IMGFMT_VDPAU_VC1)
FMT("vdpau_mpeg4", IMGFMT_VDPAU_MPEG4)
FMT("vdpau", IMGFMT_VDPAU)
+ FMT("vaapi", IMGFMT_VAAPI)
+ FMT("vaapi_mpeg2_idct", IMGFMT_VAAPI_MPEG2_IDCT)
+ FMT("vaapi_mpeg2_moco", IMGFMT_VAAPI_MPEG2_MOCO)
{0}
};
diff --git a/video/img_format.h b/video/img_format.h
index 7261971e43..e83f5ec25b 100644
--- a/video/img_format.h
+++ b/video/img_format.h
@@ -87,7 +87,6 @@ enum mp_imgfmt {
IMGFMT_START = 1000,
// Planar YUV formats
-
IMGFMT_444P, // 1x1
IMGFMT_422P, // 2x1
IMGFMT_440P, // 1x2
@@ -253,6 +252,14 @@ enum mp_imgfmt {
IMGFMT_VDPAU_FIRST = IMGFMT_VDPAU,
IMGFMT_VDPAU_LAST = IMGFMT_VDPAU_MPEG4,
+ IMGFMT_VAAPI,
+ IMGFMT_VAAPI_MPEG2_IDCT,
+ IMGFMT_VAAPI_MPEG2_MOCO,
+
+ IMGFMT_VAAPI_FIRST = IMGFMT_VAAPI,
+ IMGFMT_VAAPI_LAST = IMGFMT_VAAPI_MPEG2_MOCO,
+
+
IMGFMT_END,
// Redundant format aliases for native endian access
@@ -328,7 +335,10 @@ 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_HWACCEL(fmt) IMGFMT_IS_VDPAU(fmt)
+#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))
struct mp_imgfmt_entry {
diff --git a/video/out/vo.c b/video/out/vo.c
index 5a1b6f6fcd..592dfe6243 100644
--- a/video/out/vo.c
+++ b/video/out/vo.c
@@ -61,6 +61,7 @@ extern struct vo_driver video_out_direct3d;
extern struct vo_driver video_out_direct3d_shaders;
extern struct vo_driver video_out_sdl;
extern struct vo_driver video_out_corevideo;
+extern struct vo_driver video_out_vaapi;
const struct vo_driver *video_out_drivers[] =
{
@@ -86,6 +87,9 @@ const struct vo_driver *video_out_drivers[] =
#ifdef CONFIG_GL
&video_out_opengl_old,
#endif
+#ifdef CONFIG_VAAPI
+ &video_out_vaapi,
+#endif
#ifdef CONFIG_X11
&video_out_x11,
#endif
diff --git a/video/out/vo.h b/video/out/vo.h
index 4052d11773..8bb1bd5617 100644
--- a/video/out/vo.h
+++ b/video/out/vo.h
@@ -50,7 +50,7 @@ enum mp_voctrl {
VOCTRL_SET_EQUALIZER, // struct voctrl_set_equalizer_args*
VOCTRL_GET_EQUALIZER, // struct voctrl_get_equalizer_args*
- /* for vdpau hardware decoding */
+ /* for hardware decoding */
VOCTRL_GET_HWDEC_INFO, // struct mp_hwdec_info*
VOCTRL_NEWFRAME,
diff --git a/video/out/vo_vaapi.c b/video/out/vo_vaapi.c
new file mode 100644
index 0000000000..48eac61b9b
--- /dev/null
+++ b/video/out/vo_vaapi.c
@@ -0,0 +1,1054 @@
+/*
+ * VA API output module
+ *
+ * Copyright (C) 2008-2009 Splitted-Desktop Systems
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer 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.
+ *
+ * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <assert.h>
+#include <stdarg.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <va/va_x11.h>
+
+#include <libavutil/common.h>
+#include <libavcodec/vaapi.h>
+
+#include "config.h"
+#include "mpvcore/mp_msg.h"
+#include "video/out/vo.h"
+#include "video/memcpy_pic.h"
+#include "sub/sub.h"
+#include "sub/img_convert.h"
+#include "x11_common.h"
+
+#include "video/vfcap.h"
+#include "video/mp_image.h"
+#include "video/vaapi.h"
+#include "video/decode/dec_video.h"
+
+#define STR_FOURCC(fcc) \
+ (const char[]){(fcc), (fcc) >> 8u, (fcc) >> 16u, (fcc) >> 24u, 0}
+
+struct vaapi_surface {
+ VASurfaceID id; // VA_INVALID_ID if unallocated
+ int w, h, va_format; // parameters of allocated image (0/0/-1 unallocated)
+ VAImage image; // used for sofwtare decoding case
+ bool is_bound; // image bound to the surface?
+ bool is_used; // referenced by a mp_image
+ bool is_dead; // used, but deallocate VA objects as soon as possible
+ int order; // for LRU allocation
+
+ // convenience shortcut for mp_image deallocation callback
+ struct priv *p;
+};
+
+struct vaapi_osd_image {
+ int w, h;
+ VAImage image;
+ VASubpictureID subpic_id;
+ bool is_used;
+};
+
+struct vaapi_subpic {
+ VASubpictureID id;
+ int src_x, src_y, src_w, src_h;
+ int dst_x, dst_y, dst_w, dst_h;
+};
+
+struct vaapi_osd_part {
+ bool active;
+ int bitmap_pos_id;
+ struct vaapi_osd_image image;
+ struct vaapi_subpic subpic;
+ struct osd_conv_cache *conv_cache;
+};
+
+#define MAX_OUTPUT_SURFACES 2
+
+struct priv {
+ struct mp_log *log;
+ struct vo *vo;
+ VADisplay display;
+ struct mp_vaapi_ctx mpvaapi;
+
+ struct mp_image_params image_params;
+ struct mp_rect src_rect;
+ struct mp_rect dst_rect;
+ struct mp_osd_res screen_osd_res;
+
+ struct mp_image *output_surfaces[MAX_OUTPUT_SURFACES];
+ struct mp_image *swdec_surfaces[MAX_OUTPUT_SURFACES];
+
+ int output_surface;
+ int visible_surface;
+ int deint;
+ int deint_type;
+ int scaling;
+ int force_scaled_osd;
+
+ VAImageFormat osd_format; // corresponds to OSD_VA_FORMAT
+ struct vaapi_osd_part osd_parts[MAX_OSD_PARTS];
+ bool osd_screen;
+
+ int num_video_surfaces;
+ struct vaapi_surface **video_surfaces;
+ int video_surface_lru_counter;
+
+ VAImageFormat *va_image_formats;
+ int va_num_image_formats;
+ VAImageFormat *va_subpic_formats;
+ unsigned int *va_subpic_flags;
+ int va_num_subpic_formats;
+ VADisplayAttribute *va_display_attrs;
+ int va_num_display_attrs;
+};
+
+#define OSD_VA_FORMAT VA_FOURCC_BGRA
+
+static const bool osd_formats[SUBBITMAP_COUNT] = {
+ // Actually BGRA, but only on little endian.
+ // This will break on big endian, I think.
+ [SUBBITMAP_RGBA_STR] = true,
+};
+
+struct fmtentry {
+ uint32_t va;
+ int mp;
+};
+static struct fmtentry va_to_imgfmt[] = {
+ {VA_FOURCC('Y','V','1','2'), IMGFMT_420P},
+ {VA_FOURCC('I','4','2','0'), IMGFMT_420P},
+ {VA_FOURCC('I','Y','U','V'), IMGFMT_420P},
+ {VA_FOURCC('N','V','1','2'), IMGFMT_NV12},
+ // Note: not sure about endian issues (the mp formats are byte-addressed)
+ {VA_FOURCC_RGBA, IMGFMT_RGBA},
+ {VA_FOURCC_BGRA, IMGFMT_BGRA},
+ // Untested.
+ //{VA_FOURCC_UYVY, IMGFMT_UYVY},
+ //{VA_FOURCC_YUY2, IMGFMT_YUYV},
+ {0}
+};
+
+
+static int va_fourcc_to_imgfmt(uint32_t fourcc)
+{
+ for (int n = 0; va_to_imgfmt[n].mp; n++) {
+ if (va_to_imgfmt[n].va == fourcc)
+ return va_to_imgfmt[n].mp;
+ }
+ return 0;
+}
+
+static VAImageFormat *VAImageFormat_from_imgfmt(struct priv *p, int format)
+{
+ for (int i = 0; i < p->va_num_image_formats; i++) {
+ if (va_fourcc_to_imgfmt(p->va_image_formats[i].fourcc) == format)
+ return &p->va_image_formats[i];
+ }
+ return NULL;
+}
+
+static struct vaapi_surface *to_vaapi_surface(struct priv *p,
+ struct mp_image *img)
+{
+ if (!img || !IMGFMT_IS_VAAPI(img->imgfmt))
+ return NULL;
+ // Note: we _could_ use planes[1] or planes[2] to store a vaapi_surface
+ // pointer, but I just don't trust libavcodec enough.
+ VASurfaceID id = (uintptr_t)img->planes[3];
+ for (int n = 0; n < p->num_video_surfaces; n++) {
+ struct vaapi_surface *s = p->video_surfaces[n];
+ if (s->id == id)
+ return s;
+ }
+ return NULL;
+}
+
+static struct vaapi_surface *alloc_vaapi_surface(struct priv *p, int w, int h,
+ int va_format)
+{
+ VAStatus status;
+
+ VASurfaceID id = VA_INVALID_ID;
+ status = vaCreateSurfaces(p->display, w, h, va_format, 1, &id);
+ if (!check_va_status(status, "vaCreateSurfaces()"))
+ return NULL;
+
+ struct vaapi_surface *surface = NULL;
+ for (int n = 0; n < p->num_video_surfaces; n++) {
+ struct vaapi_surface *s = p->video_surfaces[n];
+ if (s->id == VA_INVALID_ID) {
+ surface = s;
+ break;
+ }
+ }
+ if (!surface) {
+ surface = talloc_ptrtype(NULL, surface);
+ MP_TARRAY_APPEND(p, p->video_surfaces, p->num_video_surfaces, surface);
+ }
+
+ *surface = (struct vaapi_surface) {
+ .id = id,
+ .image = { .image_id = VA_INVALID_ID, .buf = VA_INVALID_ID },
+ .w = w,
+ .h = h,
+ .va_format = va_format,
+ .p = p,
+ };
+ return surface;
+}
+
+static void destroy_vaapi_surface(struct priv *p, struct vaapi_surface *s)
+{
+ if (!s || s->id == VA_INVALID_ID)
+ return;
+ assert(!s->is_used);
+
+ if (s->image.image_id != VA_INVALID_ID)
+ vaDestroyImage(p->display, s->image.image_id);
+ vaDestroySurfaces(p->display, &s->id, 1);
+ s->id = VA_INVALID_ID;
+ s->w = 0;
+ s->h = 0;
+ s->va_format = -1;
+}
+
+static struct vaapi_surface *get_vaapi_surface(struct priv *p, int w, int h,
+ int va_format)
+{
+ struct vaapi_surface *best = NULL;
+
+ for (int n = 0; n < p->num_video_surfaces; n++) {
+ struct vaapi_surface *s = p->video_surfaces[n];
+ if (!s->is_used && s->w == w && s->h == h && s->va_format == va_format) {
+ if (!best || best->order > s->order)
+ best = s;
+ }
+ }
+
+ if (!best)
+ best = alloc_vaapi_surface(p, w, h, va_format);
+
+ if (best) {
+ best->is_used = true;
+ best->order = ++p->video_surface_lru_counter;
+ }
+ return best;
+}
+
+static void release_video_surface(void *ptr)
+{
+ struct vaapi_surface *surface = ptr;
+ surface->is_used = false;
+ if (surface->is_dead)
+ destroy_vaapi_surface(surface->p, surface);
+}
+
+static struct mp_image *get_surface(struct mp_vaapi_ctx *ctx, int va_rt_format,
+ int mp_format, int w, int h)
+{
+ assert(IMGFMT_IS_VAAPI(mp_format));
+
+ struct vo *vo = ctx->priv;
+ struct priv *p = vo->priv;
+
+ struct mp_image img = {0};
+ mp_image_setfmt(&img, mp_format);
+ mp_image_set_size(&img, w, h);
+
+ struct vaapi_surface *surface = get_vaapi_surface(p, w, h, va_rt_format);
+ if (!surface)
+ return NULL;
+
+ // libavcodec probably wants it at [0] and [3]
+ // [1] and [2] are possibly free for own use.
+ for (int n = 0; n < 4; n++)
+ img.planes[n] = (void *)(uintptr_t)surface->id;
+
+ return mp_image_new_custom_ref(&img, surface, release_video_surface);
+}
+
+// This should be called only by code that is going to preallocate surfaces
+// (and by uninit). Otherwise, hw decoder init might get confused by
+// accidentally releasing hw decoder preallocated surfaces.
+static void flush_surfaces(struct mp_vaapi_ctx *ctx)
+{
+ struct vo *vo = ctx->priv;
+ struct priv *p = vo->priv;
+
+ for (int n = 0; n < p->num_video_surfaces; n++) {
+ struct vaapi_surface *s = p->video_surfaces[n];
+ if (s->is_used) {
+ s->is_dead = true;
+ } else {
+ destroy_vaapi_surface(p, s);
+ }
+ }
+}
+
+static void flush_output_surfaces(struct priv *p)
+{
+ for (int n = 0; n < MAX_OUTPUT_SURFACES; n++) {
+ talloc_free(p->output_surfaces[n]);
+ p->output_surfaces[n] = NULL;
+ }
+ p->output_surface = 0;
+ p->visible_surface = 0;
+}
+
+// See flush_surfaces() remarks - the same applies.
+static void free_video_specific(struct priv *p)
+{
+ flush_output_surfaces(p);
+
+ for (int n = 0; n < MAX_OUTPUT_SURFACES; n++) {
+ talloc_free(p->swdec_surfaces[n]);
+ p->swdec_surfaces[n] = NULL;
+ }
+
+ flush_surfaces(&p->mpvaapi);
+}
+
+static int alloc_swdec_surfaces(struct priv *p, int w, int h, int format)
+{
+ VAStatus status;
+
+ free_video_specific(p);
+
+ VAImageFormat *image_format = VAImageFormat_from_imgfmt(p, format);
+ if (!image_format)
+ return -1;
+ for (int i = 0; i < MAX_OUTPUT_SURFACES; i++) {
+ // WTF: no mapping from VAImageFormat -> VA_RT_FORMAT_
+ struct mp_image *img =
+ get_surface(&p->mpvaapi, VA_RT_FORMAT_YUV420, IMGFMT_VAAPI, w, h);
+ struct vaapi_surface *s = to_vaapi_surface(p, img);
+ if (!s)
+ return -1;
+
+ if (s->image.image_id != VA_INVALID_ID) {
+ vaDes