mpv/video/vaapi.c

338 lines
8.6 KiB
C

/*
* 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 <assert.h>
#include "config.h"
#include "vaapi.h"
#include "common/common.h"
#include "common/global.h"
#include "common/msg.h"
#include "osdep/threads.h"
#include "mp_image.h"
#include "img_format.h"
#include "mp_image_pool.h"
#include "options/m_config.h"
#ifdef _WIN32
#include "osdep/windows_utils.h"
#include "out/d3d11/context.h"
#include "out/gpu/d3d11_helpers.h"
#endif
#include <libavutil/hwcontext.h>
#include <libavutil/hwcontext_vaapi.h>
#ifdef _WIN32
#define DEV_PATH_DEFAULT NULL
#define DEV_PATH_VALIDATE mp_dxgi_validate_adapter
#else
#define DEV_PATH_DEFAULT "/dev/dri/renderD128"
#define DEV_PATH_VALIDATE validate_path
static inline OPT_STRING_VALIDATE_FUNC(validate_path)
{
return (*value && **value) ? 0 : M_OPT_INVALID;
}
#endif
struct vaapi_opts {
char *path;
};
#define OPT_BASE_STRUCT struct vaapi_opts
const struct m_sub_options vaapi_conf = {
.opts = (const struct m_option[]) {
{"device", OPT_STRING_VALIDATE(path, DEV_PATH_VALIDATE)},
{0},
},
.defaults = &(const struct vaapi_opts) {
.path = DEV_PATH_DEFAULT,
},
.size = sizeof(struct vaapi_opts),
};
int va_get_colorspace_flag(enum pl_color_system csp)
{
switch (csp) {
case PL_COLOR_SYSTEM_BT_601: return VA_SRC_BT601;
case PL_COLOR_SYSTEM_BT_709: return VA_SRC_BT709;
case PL_COLOR_SYSTEM_SMPTE_240M: return VA_SRC_SMPTE_240;
}
return 0;
}
static void va_message_callback(void *context, const char *msg, int mp_level)
{
struct mp_vaapi_ctx *res = context;
mp_msg(res->log, mp_level, "libva: %s", msg);
}
static void va_error_callback(void *context, const char *msg)
{
va_message_callback(context, msg, MSGL_ERR);
}
static void va_info_callback(void *context, const char *msg)
{
va_message_callback(context, msg, MSGL_V);
}
static void free_device_ref(struct AVHWDeviceContext *hwctx)
{
struct mp_vaapi_ctx *ctx = hwctx->user_opaque;
if (ctx->display)
vaTerminate(ctx->display);
if (ctx->destroy_native_ctx)
ctx->destroy_native_ctx(ctx->native_ctx);
talloc_free(ctx);
}
struct mp_vaapi_ctx *va_initialize(VADisplay *display, struct mp_log *plog,
bool probing)
{
AVBufferRef *avref = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_VAAPI);
if (!avref)
return NULL;
AVHWDeviceContext *hwctx = (void *)avref->data;
AVVAAPIDeviceContext *vactx = hwctx->hwctx;
struct mp_vaapi_ctx *res = talloc_ptrtype(NULL, res);
*res = (struct mp_vaapi_ctx) {
.log = mp_log_new(res, plog, "/vaapi"),
.display = display,
.av_device_ref = avref,
.hwctx = {
.av_device_ref = avref,
},
};
hwctx->free = free_device_ref;
hwctx->user_opaque = res;
vaSetErrorCallback(display, va_error_callback, res);
vaSetInfoCallback(display, va_info_callback, res);
int major, minor;
int status = vaInitialize(display, &major, &minor);
if (status != VA_STATUS_SUCCESS) {
if (!probing)
MP_ERR(res, "Failed to initialize VAAPI: %s\n", vaErrorStr(status));
goto error;
}
MP_VERBOSE(res, "Initialized VAAPI: version %d.%d\n", major, minor);
vactx->display = res->display;
if (av_hwdevice_ctx_init(res->av_device_ref) < 0)
goto error;
return res;
error:
res->display = NULL; // do not vaTerminate this
va_destroy(res);
return NULL;
}
// Undo va_initialize, and close the VADisplay.
void va_destroy(struct mp_vaapi_ctx *ctx)
{
if (!ctx)
return;
AVBufferRef *ref = ctx->av_device_ref;
av_buffer_unref(&ref); // frees ctx as well
}
VASurfaceID va_surface_id(struct mp_image *mpi)
{
return mpi && mpi->imgfmt == IMGFMT_VAAPI ?
(VASurfaceID)(uintptr_t)mpi->planes[3] : VA_INVALID_ID;
}
static bool is_emulated(struct AVBufferRef *hw_device_ctx)
{
AVHWDeviceContext *hwctx = (void *)hw_device_ctx->data;
AVVAAPIDeviceContext *vactx = hwctx->hwctx;
const char *s = vaQueryVendorString(vactx->display);
return s && strstr(s, "VDPAU backend");
}
bool va_guess_if_emulated(struct mp_vaapi_ctx *ctx)
{
return is_emulated(ctx->av_device_ref);
}
struct va_native_display {
void (*create)(struct mp_log *log, VADisplay **out_display,
void **out_native_ctx, const char *path);
void (*destroy)(void *native_ctx);
};
#if HAVE_VAAPI_X11
#include <X11/Xlib.h>
#include <va/va_x11.h>
static void x11_destroy(void *native_ctx)
{
XCloseDisplay(native_ctx);
}
static void x11_create(struct mp_log *log, VADisplay **out_display,
void **out_native_ctx, const char *path)
{
void *native_display = XOpenDisplay(NULL);
if (!native_display)
return;
*out_display = vaGetDisplay(native_display);
if (*out_display) {
*out_native_ctx = native_display;
} else {
XCloseDisplay(native_display);
}
}
static const struct va_native_display disp_x11 = {
.create = x11_create,
.destroy = x11_destroy,
};
#endif
#if HAVE_VAAPI_WIN32
#include <va/va_win32.h>
static void win32_create(struct mp_log *log, VADisplay **out_display,
void **out_native_ctx, const char *path)
{
LUID *luid = NULL;
DXGI_ADAPTER_DESC1 desc = {0};
if (path && path[0]) {
IDXGIAdapter1 *adapter = mp_get_dxgi_adapter(log, bstr0(path), NULL);
if (!adapter || FAILED(IDXGIAdapter1_GetDesc1(adapter, &desc))) {
mp_err(log, "Failed to get adapter LUID for name: %s\n", path);
} else {
luid = &desc.AdapterLuid;
}
SAFE_RELEASE(adapter);
}
*out_display = vaGetDisplayWin32(luid);
}
static const struct va_native_display disp_win32 = {
.create = win32_create,
};
#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(void *native_ctx)
{
struct va_native_display_drm *ctx = native_ctx;
close(ctx->drm_fd);
talloc_free(ctx);
}
static void drm_create(struct mp_log *log, VADisplay **out_display,
void **out_native_ctx, const char *path)
{
int drm_fd = open(path, O_RDWR);
if (drm_fd < 0)
return;
struct va_native_display_drm *ctx = talloc_ptrtype(NULL, ctx);
ctx->drm_fd = drm_fd;
*out_display = vaGetDisplayDRM(drm_fd);
if (*out_display) {
*out_native_ctx = ctx;
return;
}
close(drm_fd);
talloc_free(ctx);
}
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_WIN32
&disp_win32,
#endif
#if HAVE_VAAPI_X11
&disp_x11,
#endif
NULL
};
static struct AVBufferRef *va_create_standalone(struct mpv_global *global,
struct mp_log *log, struct hwcontext_create_dev_params *params)
{
struct AVBufferRef *ret = NULL;
struct vaapi_opts *opts = mp_get_config_group(NULL, global, &vaapi_conf);
for (int n = 0; native_displays[n]; n++) {
VADisplay *display = NULL;
void *native_ctx = NULL;
native_displays[n]->create(global->log, &display, &native_ctx, opts->path);
if (display) {
struct mp_vaapi_ctx *ctx =
va_initialize(display, log, params->probing);
if (!ctx) {
vaTerminate(display);
if (native_displays[n]->destroy)
native_displays[n]->destroy(native_ctx);
goto end;
}
ctx->native_ctx = native_ctx;
ctx->destroy_native_ctx = native_displays[n]->destroy;
ret = ctx->hwctx.av_device_ref;
goto end;
}
}
end:
talloc_free(opts);
return ret;
}
const struct hwcontext_fns hwcontext_fns_vaapi = {
.av_hwdevice_type = AV_HWDEVICE_TYPE_VAAPI,
.create_dev = va_create_standalone,
.is_emulated = is_emulated,
};