diff options
Diffstat (limited to 'video/decode/vaapi_old.c')
-rw-r--r-- | video/decode/vaapi_old.c | 570 |
1 files changed, 570 insertions, 0 deletions
diff --git a/video/decode/vaapi_old.c b/video/decode/vaapi_old.c new file mode 100644 index 0000000000..c095868816 --- /dev/null +++ b/video/decode/vaapi_old.c @@ -0,0 +1,570 @@ +/* + * 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 "config.h" + +#include "lavc.h" +#include "common/common.h" +#include "common/av_common.h" +#include "video/fmt-conversion.h" +#include "video/vaapi.h" +#include "video/mp_image_pool.h" +#include "video/hwdec.h" +#include "video/filter/vf.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 all kinds of buffering between decoder and VO. + * 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 MPMAX(6, HWDEC_DELAY_QUEUE_COUNT) + +// Some upper bound. +#define MAX_SURFACES 25 + +struct priv { + struct mp_log *log; + struct mp_vaapi_ctx *ctx; + VADisplay display; + + const struct va_native_display *native_display_fns; + void *native_display; + + // libavcodec shared struct + struct vaapi_context *va_context; + struct vaapi_context va_context_storage; + + struct mp_image_pool *pool; + int rt_format; + + struct mp_image_pool *sw_pool; +}; + +struct va_native_display { + void (*create)(struct priv *p); + void (*destroy)(struct priv *p); +}; + +#if HAVE_VAAPI_X11 +#include <X11/Xlib.h> +#include <va/va_x11.h> + +static void x11_destroy(struct priv *p) +{ + if (p->native_display) + XCloseDisplay(p->native_display); + p->native_display = NULL; +} + +static void x11_create(struct priv *p) +{ + p->native_display = XOpenDisplay(NULL); + if (!p->native_display) + return; + p->display = vaGetDisplay(p->native_display); + if (!p->display) + x11_destroy(p); +} + +static const struct va_native_display disp_x11 = { + .create = x11_create, + .destroy = x11_destroy, +}; +#endif + +#if HAVE_VAAPI_DRM +#include <unistd.h> +#include <fcntl.h> +#include <va/va_drm.h> + +struct va_native_display_drm { + int drm_fd; +}; + +static void drm_destroy(struct priv *p) +{ + struct va_native_display_drm *native_display = p->native_display; + if (native_display) { + if (native_display->drm_fd >= 0) + close(native_display->drm_fd); + talloc_free(native_display); + p->native_display = NULL; + } +} + +static void drm_create(struct priv *p) +{ + static const char *drm_device_paths[] = { + "/dev/dri/renderD128", + "/dev/dri/card0", + NULL + }; + + for (int i = 0; drm_device_paths[i]; i++) { + int drm_fd = open(drm_device_paths[i], O_RDWR); + if (drm_fd < 0) + continue; + + struct va_native_display_drm *native_display = talloc_ptrtype(NULL, native_display); + native_display->drm_fd = drm_fd; + p->native_display = native_display; + p->display = vaGetDisplayDRM(drm_fd); + if (p->display) + return; + + drm_destroy(p); + } +} + +static const struct va_native_display disp_drm = { + .create = drm_create, + .destroy = drm_destroy, +}; +#endif + +static const struct va_native_display *const native_displays[] = { +#if HAVE_VAAPI_DRM + &disp_drm, +#endif +#if HAVE_VAAPI_X11 + &disp_x11, +#endif + NULL +}; + +#define HAS_HEVC VA_CHECK_VERSION(0, 38, 0) +#define HAS_VP9 (VA_CHECK_VERSION(0, 38, 1) && defined(FF_PROFILE_VP9_0)) + +#define PE(av_codec_id, ff_profile, vdp_profile) \ + {AV_CODEC_ID_ ## av_codec_id, FF_PROFILE_ ## ff_profile, \ + VAProfile ## vdp_profile} + +static const struct hwdec_profile_entry profiles[] = { + PE(MPEG2VIDEO, MPEG2_MAIN, MPEG2Main), + PE(MPEG2VIDEO, MPEG2_SIMPLE, MPEG2Simple), + PE(MPEG4, MPEG4_ADVANCED_SIMPLE, MPEG4AdvancedSimple), + PE(MPEG4, MPEG4_MAIN, MPEG4Main), + PE(MPEG4, MPEG4_SIMPLE, MPEG4Simple), + PE(H264, H264_HIGH, H264High), + PE(H264, H264_MAIN, H264Main), + PE(H264, H264_BASELINE, H264Baseline), + PE(VC1, VC1_ADVANCED, VC1Advanced), + PE(VC1, VC1_MAIN, VC1Main), + PE(VC1, VC1_SIMPLE, VC1Simple), + PE(WMV3, VC1_ADVANCED, VC1Advanced), + PE(WMV3, VC1_MAIN, VC1Main), + PE(WMV3, VC1_SIMPLE, VC1Simple), +#if HAS_HEVC + PE(HEVC, HEVC_MAIN, HEVCMain), + PE(HEVC, HEVC_MAIN_10, HEVCMain10), +#endif +#if HAS_VP9 + PE(VP9, VP9_0, VP9Profile0), +#endif + {0} +}; + +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); +#if HAS_HEVC + PROFILE(HEVCMain); + PROFILE(HEVCMain10); +#endif +#if HAS_VP9 + PROFILE(VP9Profile0); +#endif +#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; + } + for (int n = 0; n < num_ep; n++) { + if (ep[n] == entrypoint) + return entrypoint; + } + return -1; +} + +// We must allocate only surfaces that were passed to the decoder on creation. +// We achieve this by reserving surfaces in the pool as needed. +// Releasing surfaces is necessary after filling the surface id list so +// that reserved surfaces can be reused for decoding. +static bool preallocate_surfaces(struct lavc_ctx *ctx, int num, int w, int h, + VASurfaceID out_surfaces[MAX_SURFACES]) +{ + struct priv *p = ctx->hwdec_priv; + struct mp_image *reserve[MAX_SURFACES] = {0}; + bool res = true; + + if (num > MAX_SURFACES) + return false; + + for (int n = 0; n < num; n++) { + reserve[n] = mp_image_pool_get(p->pool, IMGFMT_VAAPI, w, h); + out_surfaces[n] = va_surface_id(reserve[n]); + if (out_surfaces[n] == VA_INVALID_ID) { + MP_ERR(p, "Could not allocate surfaces.\n"); + res = false; + break; + } + } + for (int i = 0; i < num; i++) + talloc_free(reserve[i]); + return res; +} + +static void destroy_decoder(struct lavc_ctx *ctx) +{ + struct priv *p = ctx->hwdec_priv; + + va_lock(p->ctx); + + 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; + } + + va_unlock(p->ctx); + + mp_image_pool_clear(p->pool); +} + +static bool has_profile(VAProfile *va_profiles, int num_profiles, VAProfile p) +{ + for (int i = 0; i < num_profiles; i++) { + if (va_profiles[i] == p) + return true; + } + return false; +} + +static int init_decoder(struct lavc_ctx *ctx, int w, int h) +{ + void *tmp = talloc_new(NULL); + + struct priv *p = ctx->hwdec_priv; + VAStatus status; + int res = -1; + + destroy_decoder(ctx); + + va_lock(p->ctx); + + const struct hwdec_profile_entry *pe = hwdec_find_profile(ctx, profiles); + if (!pe) { + MP_ERR(p, "Unsupported codec or profile.\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(p, "vaQueryConfigProfiles()")) + goto error; + MP_DBG(p, "%d profiles available:\n", num_profiles); + for (int i = 0; i < num_profiles; i++) + MP_DBG(p, " %s\n", str_va_profile(va_profiles[i])); + + VAProfile va_profile = pe->hw_profile; + if (!has_profile(va_profiles, num_profiles, va_profile)) { + MP_ERR(p, "Decoder profile '%s' not available.\n", + str_va_profile(va_profile)); + goto error; + } + + MP_VERBOSE(p, "Using profile '%s'.\n", str_va_profile(va_profile)); + + int num_surfaces = hwdec_get_max_refs(ctx) + ADDTIONAL_SURFACES; + if (num_surfaces > MAX_SURFACES) { + MP_ERR(p, "Internal error: too many surfaces.\n"); + goto error; + } + + VASurfaceID surfaces[MAX_SURFACES]; + if (!preallocate_surfaces(ctx, num_surfaces, w, h, surfaces)) { + MP_ERR(p, "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, va_profile, ep, &num_ep); + if (!CHECK_VA_STATUS(p, "vaQueryConfigEntrypoints()")) + goto error; + + int entrypoint = find_entrypoint(IMGFMT_VAAPI, ep, num_ep); + if (entrypoint < 0) { + MP_ERR(p, "Could not find VA entrypoint.\n"); + goto error; + } + + VAConfigAttrib attrib = { + .type = VAConfigAttribRTFormat, + }; + status = vaGetConfigAttributes(p->display, va_profile, entrypoint, + &attrib, 1); + if (!CHECK_VA_STATUS(p, "vaGetConfigAttributes()")) + goto error; + if ((attrib.value & p->rt_format) == 0) { + MP_ERR(p, "Chroma format not supported.\n"); + goto error; + } + + status = vaCreateConfig(p->display, va_profile, entrypoint, &attrib, 1, + &p->va_context->config_id); + if (!CHECK_VA_STATUS(p, "vaCreateConfig()")) + goto error; + + status = vaCreateContext(p->display, p->va_context->config_id, + w, h, VA_PROGRESSIVE, + surfaces, num_surfaces, + &p->va_context->context_id); + if (!CHECK_VA_STATUS(p, "vaCreateContext()")) + goto error; + + res = 0; +error: + va_unlock(p->ctx); + talloc_free(tmp); + return res; +} + +static struct mp_image *allocate_image(struct lavc_ctx *ctx, int w, int h) +{ + struct priv *p = ctx->hwdec_priv; + + struct mp_image *img = mp_image_pool_get(p->pool, IMGFMT_VAAPI, w, h); + if (!img) + MP_ERR(p, "Failed to allocate additional VAAPI surface.\n"); + return img; +} + +static struct mp_image *update_format(struct lavc_ctx *ctx, struct mp_image *img) +{ + va_surface_init_subformat(img); + return img; +} + +static void destroy_va_dummy_ctx(struct priv *p) +{ + va_destroy(p->ctx); + p->ctx = NULL; + p->display = NULL; + if (p->native_display_fns) + p->native_display_fns->destroy(p); +} + +// Creates a "private" VADisplay, disconnected from the VO. We just create a +// new X connection, because that's simpler. (We could also pass the X +// connection along with struct mp_hwdec_devices, if we wanted.) +static bool create_va_dummy_ctx(struct priv *p) +{ + for (int n = 0; native_displays[n]; n++) { + native_displays[n]->create(p); + if (p->display) { + p->native_display_fns = native_displays[n]; + break; + } + } + if (!p->display) + goto destroy_ctx; + p->ctx = va_initialize(p->display, p->log, true); + if (!p->ctx) { + vaTerminate(p->display); + goto destroy_ctx; + } + return true; +destroy_ctx: + destroy_va_dummy_ctx(p); + return false; +} + +static void uninit(struct lavc_ctx *ctx) +{ + struct priv *p = ctx->hwdec_priv; + + if (!p) + return; + + destroy_decoder(ctx); + + talloc_free(p->pool); + p->pool = NULL; + + if (p->native_display_fns) + destroy_va_dummy_ctx(p); + + talloc_free(p); + ctx->hwdec_priv = NULL; +} + +static int init(struct lavc_ctx *ctx, bool direct) +{ + struct priv *p = talloc_ptrtype(NULL, p); + *p = (struct priv) { + .log = mp_log_new(p, ctx->log, "vaapi"), + .va_context = &p->va_context_storage, + .rt_format = VA_RT_FORMAT_YUV420 + }; + + if (direct) { + p->ctx = hwdec_devices_get(ctx->hwdec_devs, HWDEC_VAAPI)->ctx; + } else { + create_va_dummy_ctx(p); + if (!p->ctx) { + talloc_free(p); + return -1; + } + } + + p->display = p->ctx->display; + p->pool = talloc_steal(p, mp_image_pool_new(MAX_SURFACES)); + va_pool_set_allocator(p->pool, p->ctx, p->rt_format); + p->sw_pool = talloc_steal(p, mp_image_pool_new(17)); + + 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; + ctx->hwdec_priv = p; + + return 0; +} + +static int init_direct(struct lavc_ctx *ctx) +{ + return init(ctx, true); +} + +static int probe(struct lavc_ctx *ctx, struct vd_lavc_hwdec *hwdec, + const char *codec) +{ + if (!hwdec_devices_load(ctx->hwdec_devs, HWDEC_VAAPI)) + return HWDEC_ERR_NO_CTX; + if (!hwdec_check_codec_support(codec, profiles)) + return HWDEC_ERR_NO_CODEC; + return 0; +} + +static int probe_copy(struct lavc_ctx *ctx, struct vd_lavc_hwdec *hwdec, + const char *codec) +{ + struct priv dummy = {mp_null_log}; + if (!create_va_dummy_ctx(&dummy)) + return HWDEC_ERR_NO_CTX; + bool emulated = va_guess_if_emulated(dummy.ctx); + destroy_va_dummy_ctx(&dummy); + if (!hwdec_check_codec_support(codec, profiles)) + return HWDEC_ERR_NO_CODEC; + if (emulated) + return HWDEC_ERR_EMULATED; + return 0; +} + +static int init_copy(struct lavc_ctx *ctx) +{ + return init(ctx, false); +} + +static struct mp_image *copy_image(struct lavc_ctx *ctx, struct mp_image *img) +{ + struct priv *p = ctx->hwdec_priv; + + struct mp_image *simg = va_surface_download(img, p->sw_pool); + if (simg) { + talloc_free(img); + return simg; + } + return img; +} + +static void intel_shit_lock(struct lavc_ctx *ctx) +{ + struct priv *p = ctx->hwdec_priv; + va_lock(p->ctx); +} + +static void intel_crap_unlock(struct lavc_ctx *ctx) +{ + struct priv *p = ctx->hwdec_priv; + va_unlock(p->ctx); +} + +const struct vd_lavc_hwdec mp_vd_lavc_vaapi = { + .type = HWDEC_VAAPI, + .image_format = IMGFMT_VAAPI, + .probe = probe, + .init = init_direct, + .uninit = uninit, + .init_decoder = init_decoder, + .allocate_image = allocate_image, + .lock = intel_shit_lock, + .unlock = intel_crap_unlock, + .process_image = update_format, +}; + +const struct vd_lavc_hwdec mp_vd_lavc_vaapi_copy = { + .type = HWDEC_VAAPI_COPY, + .copying = true, + .image_format = IMGFMT_VAAPI, + .probe = probe_copy, + .init = init_copy, + .uninit = uninit, + .init_decoder = init_decoder, + .allocate_image = allocate_image, + .process_image = copy_image, + .delay_queue = HWDEC_DELAY_QUEUE_COUNT, +}; |