summaryrefslogtreecommitdiffstats
path: root/video/vaapi.c
diff options
context:
space:
mode:
Diffstat (limited to 'video/vaapi.c')
-rw-r--r--video/vaapi.c315
1 files changed, 249 insertions, 66 deletions
diff --git a/video/vaapi.c b/video/vaapi.c
index 604fffa738..5c8ce4c693 100644
--- a/video/vaapi.c
+++ b/video/vaapi.c
@@ -17,6 +17,8 @@
#include <assert.h>
+#include "config.h"
+
#include "vaapi.h"
#include "common/common.h"
#include "common/msg.h"
@@ -25,6 +27,9 @@
#include "img_format.h"
#include "mp_image_pool.h"
+#include <libavutil/hwcontext.h>
+#include <libavutil/hwcontext_vaapi.h>
+
bool check_va_status(struct mp_log *log, VAStatus status, const char *msg)
{
if (status != VA_STATUS_SUCCESS) {
@@ -108,23 +113,67 @@ static void va_get_formats(struct mp_vaapi_ctx *ctx)
ctx->image_formats = formats;
}
-struct mp_vaapi_ctx *va_initialize(VADisplay *display, struct mp_log *plog,
- bool probing)
+// VA message callbacks are global and do not have a context parameter, so it's
+// impossible to know from which VADisplay they originate. Try to route them
+// to existing mpv/libmpv instances within this process.
+static pthread_mutex_t va_log_mutex = PTHREAD_MUTEX_INITIALIZER;
+static struct mp_vaapi_ctx **va_mpv_clients;
+static int num_va_mpv_clients;
+
+static void va_message_callback(const char *msg, int mp_level)
{
- struct mp_vaapi_ctx *res = NULL;
- struct mp_log *log = mp_log_new(NULL, plog, "/vaapi");
- int major_version, minor_version;
- int status = vaInitialize(display, &major_version, &minor_version);
- if (status != VA_STATUS_SUCCESS && probing)
- goto error;
- if (!check_va_status(log, status, "vaInitialize()"))
- goto error;
+ pthread_mutex_lock(&va_log_mutex);
+
+ if (num_va_mpv_clients) {
+ struct mp_log *dst = va_mpv_clients[num_va_mpv_clients - 1]->log;
+ mp_msg(dst, mp_level, "libva: %s", msg);
+ } else {
+ // We can't get or call the original libva handler (vaSet... return
+ // them, but it might be from some other lib etc.). So just do what
+ // libva happened to do at the time of this writing.
+ if (mp_level <= MSGL_ERR) {
+ fprintf(stderr, "libva error: %s", msg);
+ } else {
+ fprintf(stderr, "libva info: %s", msg);
+ }
+ }
+
+ pthread_mutex_unlock(&va_log_mutex);
+}
+
+static void va_error_callback(const char *msg)
+{
+ va_message_callback(msg, MSGL_ERR);
+}
+
+static void va_info_callback(const char *msg)
+{
+ va_message_callback(msg, MSGL_V);
+}
+
+static void open_lavu_vaapi_device(struct mp_vaapi_ctx *ctx)
+{
+ ctx->av_device_ref = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_VAAPI);
+ if (!ctx->av_device_ref)
+ return;
+
+ AVHWDeviceContext *hwctx = (void *)ctx->av_device_ref->data;
+ AVVAAPIDeviceContext *vactx = hwctx->hwctx;
- mp_verbose(log, "VA API version %d.%d\n", major_version, minor_version);
+ vactx->display = ctx->display;
- res = talloc_ptrtype(NULL, res);
+ if (av_hwdevice_ctx_init(ctx->av_device_ref) < 0)
+ av_buffer_unref(&ctx->av_device_ref);
+
+ ctx->hwctx.av_device_ref = ctx->av_device_ref;
+}
+
+struct mp_vaapi_ctx *va_initialize(VADisplay *display, struct mp_log *plog,
+ bool probing)
+{
+ struct mp_vaapi_ctx *res = talloc_ptrtype(NULL, res);
*res = (struct mp_vaapi_ctx) {
- .log = talloc_steal(res, log),
+ .log = mp_log_new(res, plog, "/vaapi"),
.display = display,
.hwctx = {
.type = HWDEC_VAAPI,
@@ -132,18 +181,40 @@ struct mp_vaapi_ctx *va_initialize(VADisplay *display, struct mp_log *plog,
.download_image = ctx_download_image,
},
};
- mpthread_mutex_init_recursive(&res->lock);
+
+ pthread_mutex_lock(&va_log_mutex);
+ MP_TARRAY_APPEND(NULL, va_mpv_clients, num_va_mpv_clients, res);
+ pthread_mutex_unlock(&va_log_mutex);
+
+ // Check some random symbol added after message callbacks.
+ // VA_MICRO_VERSION wasn't bumped at the time.
+#ifdef VA_FOURCC_I010
+ vaSetErrorCallback(va_error_callback);
+ vaSetInfoCallback(va_info_callback);
+#endif
+
+ int major_version, minor_version;
+ int status = vaInitialize(display, &major_version, &minor_version);
+ if (status != VA_STATUS_SUCCESS && probing)
+ goto error;
+ if (!check_va_status(res->log, status, "vaInitialize()"))
+ goto error;
+
+ MP_VERBOSE(res, "VA API version %d.%d\n", major_version, minor_version);
va_get_formats(res);
if (!res->image_formats)
goto error;
+
+ // For now, some code will still work even if libavutil fails on old crap
+ // libva drivers (such as the vdpau wraper). So don't error out on failure.
+ open_lavu_vaapi_device(res);
+
return res;
error:
- if (res && res->display)
- vaTerminate(res->display);
- talloc_free(log);
- talloc_free(res);
+ res->display = NULL; // do not vaTerminate this
+ va_destroy(res);
return NULL;
}
@@ -153,7 +224,21 @@ void va_destroy(struct mp_vaapi_ctx *ctx)
if (ctx) {
if (ctx->display)
vaTerminate(ctx->display);
- pthread_mutex_destroy(&ctx->lock);
+
+ if (ctx->destroy_native_ctx)
+ ctx->destroy_native_ctx(ctx->native_ctx);
+
+ pthread_mutex_lock(&va_log_mutex);
+ for (int n = 0; n < num_va_mpv_clients; n++) {
+ if (va_mpv_clients[n] == ctx) {
+ MP_TARRAY_REMOVE_AT(va_mpv_clients, num_va_mpv_clients, n);
+ break;
+ }
+ }
+ if (num_va_mpv_clients == 0)
+ TA_FREEP(&va_mpv_clients); // avoid triggering leak detectors
+ pthread_mutex_unlock(&va_log_mutex);
+
talloc_free(ctx);
}
}
@@ -209,22 +294,27 @@ int va_surface_rt_format(struct mp_image *mpi)
// padded surfaces for example.)
void va_surface_get_uncropped_size(struct mp_image *mpi, int *out_w, int *out_h)
{
- struct va_surface *s = va_surface_in_mp_image(mpi);
- *out_w = s ? s->w : 0;
- *out_h = s ? s->h : 0;
+ if (mpi->hwctx) {
+ AVHWFramesContext *fctx = (void *)mpi->hwctx->data;
+ *out_w = fctx->width;
+ *out_h = fctx->height;
+ } else {
+ struct va_surface *s = va_surface_in_mp_image(mpi);
+ *out_w = s ? s->w : 0;
+ *out_h = s ? s->h : 0;
+ }
}
static void release_va_surface(void *arg)
{
struct va_surface *surface = arg;
- va_lock(surface->ctx);
if (surface->id != VA_INVALID_ID) {
if (surface->image.image_id != VA_INVALID_ID)
vaDestroyImage(surface->display, surface->image.image_id);
vaDestroySurfaces(surface->display, &surface->id, 1);
}
- va_unlock(surface->ctx);
+
talloc_free(surface);
}
@@ -233,9 +323,7 @@ static struct mp_image *alloc_surface(struct mp_vaapi_ctx *ctx, int rt_format,
{
VASurfaceID id = VA_INVALID_ID;
VAStatus status;
- va_lock(ctx);
status = vaCreateSurfaces(ctx->display, rt_format, w, h, &id, 1, NULL, 0);
- va_unlock(ctx);
if (!CHECK_VA_STATUS(ctx, "vaCreateSurfaces()"))
return NULL;
@@ -270,11 +358,8 @@ static void va_surface_image_destroy(struct va_surface *surface)
surface->is_derived = false;
}
-static int va_surface_image_alloc(struct mp_image *img, VAImageFormat *format)
+static int va_surface_image_alloc(struct va_surface *p, VAImageFormat *format)
{
- struct va_surface *p = va_surface_in_mp_image(img);
- if (!format || !p)
- return -1;
VADisplay *display = p->display;
if (p->image.image_id != VA_INVALID_ID &&
@@ -282,7 +367,6 @@ static int va_surface_image_alloc(struct mp_image *img, VAImageFormat *format)
return 0;
int r = 0;
- va_lock(p->ctx);
va_surface_image_destroy(p);
@@ -308,7 +392,6 @@ static int va_surface_image_alloc(struct mp_image *img, VAImageFormat *format)
}
}
- va_unlock(p->ctx);
return r;
}
@@ -327,7 +410,7 @@ int va_surface_alloc_imgfmt(struct mp_image *img, int imgfmt)
VAImageFormat *format = va_image_format_from_imgfmt(p->ctx, imgfmt);
if (!format)
return -1;
- if (va_surface_image_alloc(img, format) < 0)
+ if (va_surface_image_alloc(p, format) < 0)
return -1;
return 0;
}
@@ -338,9 +421,7 @@ bool va_image_map(struct mp_vaapi_ctx *ctx, VAImage *image, struct mp_image *mpi
if (imgfmt == IMGFMT_NONE)
return false;
void *data = NULL;
- va_lock(ctx);
const VAStatus status = vaMapBuffer(ctx->display, image->buf, &data);
- va_unlock(ctx);
if (!CHECK_VA_STATUS(ctx, "vaMapBuffer()"))
return false;
@@ -363,9 +444,7 @@ bool va_image_map(struct mp_vaapi_ctx *ctx, VAImage *image, struct mp_image *mpi
bool va_image_unmap(struct mp_vaapi_ctx *ctx, VAImage *image)
{
- va_lock(ctx);
const VAStatus status = vaUnmapBuffer(ctx->display, image->buf);
- va_unlock(ctx);
return CHECK_VA_STATUS(ctx, "vaUnmapBuffer()");
}
@@ -389,12 +468,10 @@ int va_surface_upload(struct mp_image *va_dst, struct mp_image *sw_src)
va_image_unmap(p->ctx, &p->image);
if (!p->is_derived) {
- va_lock(p->ctx);
VAStatus status = vaPutImage(p->display, p->id,
p->image.image_id,
0, 0, sw_src->w, sw_src->h,
0, 0, sw_src->w, sw_src->h);
- va_unlock(p->ctx);
if (!CHECK_VA_STATUS(p->ctx, "vaPutImage()"))
return -1;
}
@@ -404,14 +481,10 @@ int va_surface_upload(struct mp_image *va_dst, struct mp_image *sw_src)
return 0;
}
-static struct mp_image *try_download(struct mp_image *src,
+static struct mp_image *try_download(struct va_surface *p, struct mp_image *src,
struct mp_image_pool *pool)
{
VAStatus status;
- struct va_surface *p = va_surface_in_mp_image(src);
- if (!p)
- return NULL;
-
VAImage *image = &p->image;
if (image->image_id == VA_INVALID_ID ||
@@ -419,10 +492,8 @@ static struct mp_image *try_download(struct mp_image *src,
return NULL;
if (!p->is_derived) {
- va_lock(p->ctx);
status = vaGetImage(p->display, p->id, 0, 0,
p->w, p->h, image->image_id);
- va_unlock(p->ctx);
if (status != VA_STATUS_SUCCESS)
return NULL;
}
@@ -434,9 +505,7 @@ static struct mp_image *try_download(struct mp_image *src,
mp_image_set_size(&tmp, src->w, src->h); // copy only visible part
dst = mp_image_pool_get(pool, tmp.imgfmt, tmp.w, tmp.h);
if (dst) {
- va_lock(p->ctx);
mp_check_gpu_memcpy(p->ctx->log, &p->ctx->gpu_memcpy_message);
- va_unlock(p->ctx);
mp_image_copy_gpu(dst, &tmp);
mp_image_copy_attributes(dst, src);
@@ -453,19 +522,23 @@ static struct mp_image *try_download(struct mp_image *src,
struct mp_image *va_surface_download(struct mp_image *src,
struct mp_image_pool *pool)
{
- struct va_surface *p = va_surface_in_mp_image(src);
- if (!p)
+ if (!src || src->imgfmt != IMGFMT_VAAPI)
return NULL;
+ struct va_surface *p = va_surface_in_mp_image(src);
+ if (!p) {
+ // We might still be able to get to the cheese if this is a surface
+ // produced by libavutil's vaapi glue code.
+ return mp_image_hw_download(src, pool);
+ }
+ struct mp_image *mpi = NULL;
struct mp_vaapi_ctx *ctx = p->ctx;
- va_lock(ctx);
VAStatus status = vaSyncSurface(p->display, p->id);
- va_unlock(ctx);
if (!CHECK_VA_STATUS(ctx, "vaSyncSurface()"))
- return NULL;
+ goto done;
- struct mp_image *mpi = try_download(src, pool);
+ mpi = try_download(p, src, pool);
if (mpi)
- return mpi;
+ goto done;
// We have no clue which format will work, so try them all.
// Make sure to start with the most preferred format (nv12), to avoid
@@ -474,16 +547,19 @@ struct mp_image *va_surface_download(struct mp_image *src,
VAImageFormat *format =
va_image_format_from_imgfmt(ctx, va_to_imgfmt[n].mp);
if (format) {
- if (va_surface_image_alloc(src, format) < 0)
+ if (va_surface_image_alloc(p, format) < 0)
continue;
- mpi = try_download(src, pool);
+ mpi = try_download(p, src, pool);
if (mpi)
- return mpi;
+ goto done;
}
}
- MP_ERR(ctx, "failed to get surface data.\n");
- return NULL;
+done:
+
+ if (!mpi)
+ MP_ERR(ctx, "failed to get surface data.\n");
+ return mpi;
}
// Set the hw_subfmt from the surface's real format. Because of this bug:
@@ -503,8 +579,6 @@ void va_surface_init_subformat(struct mp_image *mpi)
VAImage va_image = { .image_id = VA_INVALID_ID };
- va_lock(p->ctx);
-
status = vaDeriveImage(p->display, va_surface_id(mpi), &va_image);
if (status != VA_STATUS_SUCCESS)
goto err;
@@ -514,8 +588,7 @@ void va_surface_init_subformat(struct mp_image *mpi)
status = vaDestroyImage(p->display, va_image.image_id);
CHECK_VA_STATUS(p->ctx, "vaDestroyImage()");
-err:
- va_unlock(p->ctx);
+err: ;
}
struct pool_alloc_ctx {
@@ -548,8 +621,118 @@ void va_pool_set_allocator(struct mp_image_pool *pool, struct mp_vaapi_ctx *ctx,
bool va_guess_if_emulated(struct mp_vaapi_ctx *ctx)
{
- va_lock(ctx);
const char *s = vaQueryVendorString(ctx->display);
- va_unlock(ctx);
return s && strstr(s, "VDPAU backend");
}
+
+struct va_native_display {
+ void (*create)(VADisplay **out_display, void **out_native_ctx);
+ 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(VADisplay **out_display, void **out_native_ctx)
+{
+ 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_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(VADisplay **out_display, void **out_native_ctx)
+{
+ 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 *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_X11
+ &disp_x11,
+#endif
+ NULL
+};
+
+struct mp_vaapi_ctx *va_create_standalone(struct mp_log *plog, bool probing)
+{
+ for (int n = 0; native_displays[n]; n++) {
+ VADisplay *display = NULL;
+ void *native_ctx = NULL;
+ native_displays[n]->create(&display, &native_ctx);
+ if (display) {
+ struct mp_vaapi_ctx *ctx = va_initialize(display, plog, probing);
+ if (!ctx) {
+ vaTerminate(display);
+ native_displays[n]->destroy(native_ctx);
+ return NULL;
+ }
+ ctx->native_ctx = native_ctx;
+ ctx->destroy_native_ctx = native_displays[n]->destroy;
+ return ctx;
+ }
+ }
+ return NULL;
+}