1
0
mirror of https://github.com/mpv-player/mpv synced 2025-02-01 12:42:04 +00:00

vaapi: add vf_vavpp and use it for deinterlacing

Merged from pull request #246 by xylosper. Minor cosmetic changes, some
adjustments (compatibility with older libva versions), and manpage
additions by wm4.

Signed-off-by: wm4 <wm4@nowhere>
This commit is contained in:
xylosper 2013-09-20 22:55:13 +09:00 committed by wm4
parent 1ee8d0210d
commit 39d1ab82e5
13 changed files with 1143 additions and 522 deletions

View File

@ -861,3 +861,20 @@ Available filters are:
``a3=<string>``
Specify the fourth parameter to pass to the library.
``vavpp``
VA-API video post processing. Works with ``--vo=vaapi`` only. Currently
deinterlaces. This filter is automatically inserted if deinterlacing is
requested (either using the ``D`` key, by default mapped to the command
``cycle deinterlace``, or the ``--deinterlace`` option).
``deint=<method>``
Select the deinterlacing algorithm.
no
Don't perform deinterlacing.
first-field
Show only first field (going by ``--field-dominance``).
bob
bob deinterlacing (default).

View File

@ -701,12 +701,19 @@ Available video output drivers are:
initially always off, and needs to be enabled with the ``D`` key
(default key binding for ``cycle deinterlace``).
This option doesn't apply if libva supports video post processing (vpp).
In this case, the default for ``deint-mode`` is ``no``, and enabling
deinterlacing via user interaction using the methods mentioned above
actually inserts the ``vavpp`` video filter. If vpp is not actually
supported with the libva backend in use, you can use this option to
forcibly enable VO based deinterlacing.
no
Don't allow deinterlacing.
Don't allow deinterlacing (default for newer libva).
first-field
Show only first field (going by ``--field-dominance``).
bob
bob deinterlacing (default).
bob deinterlacing (default for older libva).
``scaled-osd=<yes|no>``
If enabled, then the OSD is rendered at video resolution and scaled to

View File

@ -112,7 +112,9 @@ 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 \
video/decode/vaapi.c
video/decode/vaapi.c \
video/vaapi.c
SOURCES-$(VAAPI_VPP) += video/filter/vf_vavpp.c
SOURCES-$(X11) += video/out/vo_x11.c video/out/x11_common.c
SOURCES-$(XV) += video/out/vo_xv.c

13
configure vendored
View File

@ -1869,6 +1869,8 @@ echores "$_vdpau"
echocheck "VAAPI"
_vaapi_vpp=no
def_vaapi_vpp='#define CONFIG_VAAPI_VPP 0'
if test "$_vaapi" = auto && test "$_x11" = yes ; then
_vaapi=no
if test "$_dl" = yes ; then
@ -1884,6 +1886,15 @@ else
fi
echores "$_vaapi"
if test "$_vaapi" = yes ; then
echocheck "VAAPI VPP"
if pkg-config 'libva >= 0.34.0' ; then
_vaapi_vpp=yes
def_vaapi_vpp='#define CONFIG_VAAPI_VPP 1'
fi
echores "$_vaapi_vpp"
fi
echocheck "Xinerama"
if test "$_xinerama" = auto && test "$_x11" = yes ; then
@ -3173,6 +3184,7 @@ VDPAU_DEC_OLD = $_vdpau_dec_old
VDA = $_vda
VDA_REFCOUNTING = $_vda_refcounting
VAAPI = $_vaapi
VAAPI_VPP = $_vaapi_vpp
WIN32 = $_win32
X11 = $_x11
WAYLAND = $_wayland
@ -3351,6 +3363,7 @@ $def_vdpau
$def_vda
$def_vda_refcounting
$def_vaapi
$def_vaapi_vpp
$def_vm
$def_x11
$def_wayland

View File

@ -1140,6 +1140,9 @@ static const char *deint_filters[] = {
"lavfi=yadif",
#endif
"yadif",
#if CONFIG_VAAPI_VPP
"vavpp",
#endif
NULL
};

View File

@ -31,6 +31,7 @@
#include "video/fmt-conversion.h"
#include "video/vaapi.h"
#include "video/decode/dec_video.h"
#include "video/filter/vf.h"
/*
* The VAAPI decoder can work only with surfaces passed to the decoder at
@ -60,6 +61,9 @@ struct priv {
int format, w, h;
VASurfaceID surfaces[MAX_SURFACES];
struct va_surface_pool *pool;
int rt_format;
};
struct profile_entry {
@ -159,36 +163,23 @@ static int is_direct_mapping(VADisplay display)
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)
// 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)
{
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];
if (!va_surface_pool_reserve(p->pool, num, p->w, p->h)) {
mp_msg(MSGT_VO, MSGL_ERR, "[vaapi] Could not allocate surfaces.\n");
return false;
}
res = 0;
done:
for (int n = 0; n < num; n++)
talloc_free(tmp_surfaces[n]);
return res;
for (int i = 0; i < num; i++) {
struct va_surface *s = va_surface_pool_get(p->pool, p->w, p->h);
p->surfaces[i] = s->id;
va_surface_release(s);
}
return true;
}
static void destroy_decoder(struct lavc_ctx *ctx)
@ -274,7 +265,7 @@ static int create_decoder(struct lavc_ctx *ctx)
goto error;
}
if (preallocate_surfaces(ctx, VA_RT_FORMAT_YUV420, num_surfaces) < 0) {
if (!preallocate_surfaces(ctx, num_surfaces)) {
mp_msg(MSGT_VO, MSGL_ERR, "[vaapi] Could not allocate surfaces.\n");
goto error;
}
@ -298,7 +289,7 @@ static int create_decoder(struct lavc_ctx *ctx)
&attrib, 1);
if (!check_va_status(status, "vaGetConfigAttributes()"))
goto error;
if ((attrib.value & VA_RT_FORMAT_YUV420) == 0) {
if ((attrib.value & p->rt_format) == 0) {
mp_msg(MSGT_VO, MSGL_ERR, "[vaapi] Chroma format not supported.\n");
goto error;
}
@ -339,14 +330,13 @@ static struct mp_image *allocate_image(struct lavc_ctx *ctx, int format,
return NULL;
}
struct mp_image *img = p->ctx->get_surface(p->ctx, VA_RT_FORMAT_YUV420,
format, p->w, p->h);
if (img) {
struct va_surface *s = va_surface_pool_get(p->pool, p->w, p->h);
if (s) {
for (int n = 0; n < MAX_SURFACES; n++) {
if (p->surfaces[n] == (uintptr_t)img->planes[3])
return img;
if (p->surfaces[n] == s->id)
return va_surface_wrap(s);
}
talloc_free(img);
va_surface_release(s);
}
mp_msg(MSGT_VO, MSGL_ERR, "[vaapi] Insufficient number of surfaces.\n");
return NULL;
@ -361,6 +351,7 @@ static void uninit(struct lavc_ctx *ctx)
destroy_decoder(ctx);
va_surface_pool_release(p->pool);
talloc_free(p);
ctx->hwdec_priv = NULL;
}
@ -371,16 +362,18 @@ static int init(struct lavc_ctx *ctx)
*p = (struct priv) {
.ctx = ctx->hwdec_info->vaapi_ctx,
.va_context = &p->va_context_storage,
.rt_format = VA_RT_FORMAT_YUV420
};
ctx->hwdec_priv = p;
p->display = p->ctx->display;
p->pool = va_surface_pool_alloc(p->display, p->rt_format);
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;
}

View File

@ -70,6 +70,7 @@ extern const vf_info_t vf_info_yadif;
extern const vf_info_t vf_info_stereo3d;
extern const vf_info_t vf_info_dlopen;
extern const vf_info_t vf_info_lavfi;
extern const vf_info_t vf_info_vaapi;
// list of available filters:
static const vf_info_t *const filter_list[] = {
@ -111,6 +112,9 @@ static const vf_info_t *const filter_list[] = {
&vf_info_stereo3d,
#ifdef CONFIG_DLOPEN
&vf_info_dlopen,
#endif
#if CONFIG_VAAPI_VPP
&vf_info_vaapi,
#endif
NULL
};

View File

@ -106,6 +106,7 @@ typedef struct vf_seteq {
/* Hack to make the OSD state object available to vf_sub which
* access OSD/subtitle state outside of normal OSD draw time. */
#define VFCTRL_SET_OSD_OBJ 20
#define VFCTRL_GET_HWDEC_INFO 21 // for hwdec filters
int vf_control(struct vf_instance *vf, int cmd, void *arg);

406
video/filter/vf_vavpp.c Normal file
View File

@ -0,0 +1,406 @@
/*
* This file is part of mpv.
*
* 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 <va/va.h>
#include <va/va_vpp.h>
#include "config.h"
#include "mpvcore/options.h"
#include "vf.h"
#include "video/vaapi.h"
#include "video/decode/dec_video.h"
static inline bool is_success(VAStatus status, const char *msg)
{
if (status == VA_STATUS_SUCCESS)
return true;
mp_msg(MSGT_VFILTER, MSGL_ERR, "[vavpp] %s: %s\n", msg, vaErrorStr(status));
return false;
}
struct surface_refs {
VASurfaceID *surfaces;
int num_allocated;
int num_required;
};
struct pipeline {
VABufferID *filters;
int num_filters;
VAProcColorStandardType input_colors[VAProcColorStandardCount];
VAProcColorStandardType output_colors[VAProcColorStandardCount];
int num_input_colors, num_output_colors;
struct surface_refs forward, backward;
};
struct vf_priv_s {
double prev_pts;
int deint_type; // 0: none, 1: discard, 2: double fps
bool do_deint;
VABufferID buffers[VAProcFilterCount];
int num_buffers;
VAConfigID config;
VAContextID context;
struct mp_image_params params;
VADisplay display;
struct mp_vaapi_ctx *va;
struct pipeline pipe;
struct va_surface_pool *pool;
};
static const struct vf_priv_s vf_priv_default = {
.prev_pts = MP_NOPTS_VALUE,
.config = VA_INVALID_ID,
.context = VA_INVALID_ID,
.deint_type = 2,
};
static inline void realloc_refs(struct surface_refs *refs, int num)
{
if (refs->num_allocated < num) {
refs->surfaces = realloc(refs->surfaces, sizeof(VASurfaceID)*num);
refs->num_allocated = num;
}
refs->num_required = num;
}
static bool update_pipeline(struct vf_priv_s *p, bool deint)
{
VABufferID *filters = p->buffers;
int num_filters = p->num_buffers;
if (p->deint_type && !deint) {
++filters;
--num_filters;
}
if (filters == p->pipe.filters && num_filters == p->pipe.num_filters)
return true;
p->pipe.forward.num_required = p->pipe.backward.num_required = 0;
p->pipe.num_input_colors = p->pipe.num_output_colors = 0;
p->pipe.num_filters = 0;
p->pipe.filters = NULL;
if (!num_filters)
return false;
VAProcPipelineCaps caps;
caps.input_color_standards = p->pipe.input_colors;
caps.output_color_standards = p->pipe.output_colors;
caps.num_input_color_standards = VAProcColorStandardCount;
caps.num_output_color_standards = VAProcColorStandardCount;
VAStatus status = vaQueryVideoProcPipelineCaps(p->display, p->context,
filters, num_filters, &caps);
if (!is_success(status, "vaQueryVideoProcPipelineCaps()"))
return false;
p->pipe.filters = filters;
p->pipe.num_filters = num_filters;
p->pipe.num_input_colors = caps.num_input_color_standards;
p->pipe.num_output_colors = caps.num_output_color_standards;
realloc_refs(&p->pipe.forward, caps.num_forward_references);
realloc_refs(&p->pipe.backward, caps.num_backward_references);
return true;
}
static inline int get_deint_field(struct vf_priv_s *p, int i,
const struct mp_image *mpi)
{
if (!p->do_deint || !(mpi->fields & MP_IMGFIELD_INTERLACED))
return VA_FRAME_PICTURE;
return !!(mpi->fields & MP_IMGFIELD_TOP_FIRST) ^ i ? VA_TOP_FIELD : VA_BOTTOM_FIELD;
}
static struct mp_image *render(struct vf_priv_s *p, struct va_surface *in,
unsigned int flags)
{
if (!p->pipe.filters || !in)
return NULL;
struct va_surface *out = va_surface_pool_get(p->pool, in->w, in->h);
if (!out)
return NULL;
enum {Begun = 1, Rendered = 2};
int state = 0;
do { // not a loop, just for break
VAStatus status = vaBeginPicture(p->display, p->context, out->id);
if (!is_success(status, "vaBeginPicture()"))
break;
state |= Begun;
VABufferID buffer = VA_INVALID_ID;
VAProcPipelineParameterBuffer *param = NULL;
status = vaCreateBuffer(p->display, p->context,
VAProcPipelineParameterBufferType,
sizeof(*param), 1, NULL, &buffer);
if (!is_success(status, "vaCreateBuffer()"))
break;
status = vaMapBuffer(p->display, buffer, (void**)&param);
if (!is_success(status, "vaMapBuffer()"))
break;
param->surface = in->id;
param->surface_region = NULL;
param->output_region = NULL;
param->output_background_color = 0;
param->filter_flags = flags;
param->filters = p->pipe.filters;
param->num_filters = p->pipe.num_filters;
vaUnmapBuffer(p->display, buffer);
param->forward_references = p->pipe.forward.surfaces;
param->backward_references = p->pipe.backward.surfaces;
param->num_forward_references = p->pipe.forward.num_required;
param->num_backward_references = p->pipe.backward.num_required;
status = vaRenderPicture(p->display, p->context, &buffer, 1);
if (!is_success(status, "vaRenderPicture()"))
break;
state |= Rendered;
} while (false);
if (state & Begun)
vaEndPicture(p->display, p->context);
if (state & Rendered)
return va_surface_wrap(out);
va_surface_release(out);
return NULL;
}
// return value: the number of created images
static int process(struct vf_priv_s *p, struct mp_image *in,
struct mp_image **out1, struct mp_image **out2)
{
const bool deint = p->do_deint && p->deint_type > 0;
if (!update_pipeline(p, deint) || !p->pipe.filters) // no filtering
return 0;
struct va_surface *surface = va_surface_in_mp_image(in);
const unsigned int csp = va_get_colorspace_flag(p->params.colorspace);
const unsigned int field = get_deint_field(p, 0, in);
*out1 = render(p, surface, field | csp);
if (!*out1) // cannot render
return 0;
mp_image_copy_attributes(*out1, in);
if (field == VA_FRAME_PICTURE || p->deint_type < 2) // first-field only
return 1;
const double add = (in->pts - p->prev_pts)*0.5;
if (p->prev_pts == MP_NOPTS_VALUE || add <= 0.0 || add > 0.5) // no pts, skip it
return 1;
*out2 = render(p, surface, get_deint_field(p, 1, in) | csp);
if (!*out2) // cannot render
return 1;
mp_image_copy_attributes(*out2, in);
(*out2)->pts = in->pts + add;
return 2;
}
static struct mp_image *upload(struct vf_priv_s *p, struct mp_image *in)
{
struct va_surface *surface =
va_surface_pool_get_by_imgfmt(p->pool, p->va->image_formats, in->imgfmt, in->w, in->h);
if (!surface)
surface = va_surface_pool_get(p->pool, in->w, in->h); // dummy
else
va_surface_upload(surface, in);
struct mp_image *out = va_surface_wrap(surface);
mp_image_copy_attributes(out, in);
return out;
}
static int filter_ext(struct vf_instance *vf, struct mp_image *in)
{
struct vf_priv_s *p = vf->priv;
struct va_surface *surface = va_surface_in_mp_image(in);
const int rt_format = surface ? surface->rt_format : VA_RT_FORMAT_YUV420;
if (!p->pool || va_surface_pool_rt_format(p->pool) != rt_format) {
va_surface_pool_release(p->pool);
p->pool = va_surface_pool_alloc(p->display, rt_format);
}
if (!surface) {
struct mp_image *tmp = upload(p, in);
talloc_free(in);
in = tmp;
}
struct mp_image *out1, *out2;
const double pts = in->pts;
const int num = process(p, in, &out1, &out2);
if (!num)
vf_add_output_frame(vf, in);
else {
vf_add_output_frame(vf, out1);
if (num > 1)
vf_add_output_frame(vf, out2);
talloc_free(in);
}
p->prev_pts = pts;
return 0;
}
static int reconfig(struct vf_instance *vf, struct mp_image_params *params,
int flags)
{
struct vf_priv_s *p = vf->priv;
p->prev_pts = MP_NOPTS_VALUE;
p->params = *params;
params->imgfmt = IMGFMT_VAAPI;
return vf_next_reconfig(vf, params, flags);
}
static void uninit(struct vf_instance *vf)
{
struct vf_priv_s *p = vf->priv;
for (int i=0; i<p->num_buffers; ++i)
vaDestroyBuffer(p->display, p->buffers[i]);
if (p->context != VA_INVALID_ID)
vaDestroyContext(p->display, p->context);
if (p->config != VA_INVALID_ID)
vaDestroyConfig(p->display, p->config);
free(p->pipe.forward.surfaces);
free(p->pipe.backward.surfaces);
va_surface_pool_release(p->pool);
}
static int query_format(struct vf_instance *vf, unsigned int imgfmt)
{
struct vf_priv_s *p = vf->priv;
if (IMGFMT_IS_VAAPI(imgfmt) || va_image_format_from_imgfmt(p->va->image_formats, imgfmt))
return vf_next_query_format(vf, IMGFMT_VAAPI);
return 0;
}
static int control(struct vf_instance *vf, int request, void* data)
{
struct vf_priv_s *p = vf->priv;
switch (request){
case VFCTRL_GET_DEINTERLACE:
*(int*)data = !!p->do_deint;
return true;
case VFCTRL_SET_DEINTERLACE:
p->do_deint = *(int*)data;
return true;
default:
return vf_next_control (vf, request, data);
}
}
static int va_query_filter_caps(struct vf_priv_s *p, VAProcFilterType type,
void *caps, unsigned int count)
{
VAStatus status = vaQueryVideoProcFilterCaps(p->display, p->context, type,
caps, &count);
return is_success(status, "vaQueryVideoProcFilterCaps()") ? count : 0;
}
static VABufferID va_create_filter_buffer(struct vf_priv_s *p, int bytes,
int num, void *data)
{
VABufferID buffer;
VAStatus status = vaCreateBuffer(p->display, p->context,
VAProcFilterParameterBufferType,
bytes, num, data, &buffer);
return is_success(status, "vaCreateBuffer()") ? buffer : VA_INVALID_ID;
}
static bool initialize(struct vf_priv_s *p)
{
VAStatus status;
VAConfigID config;
status = vaCreateConfig(p->display, VAProfileNone, VAEntrypointVideoProc,
NULL, 0, &config);
if (!is_success(status, "vaCreateConfig()")) // no entrypoint for video porc
return false;
p->config = config;
VAContextID context;
status = vaCreateContext(p->display, p->config, 0, 0, 0, NULL, 0, &context);
if (!is_success(status, "vaCreateContext()"))
return false;
p->context = context;
VAProcFilterType filters[VAProcFilterCount];
int num_filters = VAProcFilterCount;
status = vaQueryVideoProcFilters(p->display, p->context, filters, &num_filters);
if (!is_success(status, "vaQueryVideoProcFilters()"))
return false;
VABufferID buffers[VAProcFilterCount];
for (int i=0; i<VAProcFilterCount; ++i)
buffers[i] = VA_INVALID_ID;
for (int i=0; i<num_filters; ++i) {
if (filters[i] == VAProcFilterDeinterlacing) {
if (!p->deint_type)
continue;
VAProcFilterCapDeinterlacing caps[VAProcDeinterlacingCount];
int num = va_query_filter_caps(p, VAProcFilterDeinterlacing, caps,
VAProcDeinterlacingCount);
if (!num)
continue;
VAProcDeinterlacingType algorithm = VAProcDeinterlacingBob;
for (int i=0; i<num; ++i) { // find Bob
if (caps[i].type != algorithm)
continue;
VAProcFilterParameterBufferDeinterlacing param;
param.type = VAProcFilterDeinterlacing;
param.algorithm = algorithm;
buffers[VAProcFilterDeinterlacing] =
va_create_filter_buffer(p, sizeof(param), 1, &param);
}
} // check other filters
}
p->num_buffers = 0;
if (buffers[VAProcFilterDeinterlacing] != VA_INVALID_ID)
p->buffers[p->num_buffers++] = buffers[VAProcFilterDeinterlacing];
else
p->deint_type = 0;
p->do_deint = !!p->deint_type;
// next filters: p->buffers[p->num_buffers++] = buffers[next_filter];
return true;
}
static int vf_open(vf_instance_t *vf, char *args)
{
vf->reconfig = reconfig;
vf->filter_ext = filter_ext;
vf->query_format = query_format;
vf->uninit = uninit;
vf->control = control;
struct vf_priv_s *p = vf->priv;
struct mp_hwdec_info hwdec;
if (vf_control(vf->next, VFCTRL_GET_HWDEC_INFO, &hwdec) <= 0)
return false;
p->va = hwdec.vaapi_ctx;
if (!p->va || !p->va->display)
return false;
p->display = p->va->display;
if (initialize(p))
return true;
uninit(vf);
return false;
}
#define OPT_BASE_STRUCT struct vf_priv_s
static const m_option_t vf_opts_fields[] = {
OPT_CHOICE("deint", deint_type, 0,
({"no", 0},
{"first-field", 1},
{"bob", 2})),
{0}
};
const vf_info_t vf_info_vaapi = {
.info = "VA-API Video Post-Process Filter",
.name = "vavpp",
.author = "xylosper",
.comment = "",
.vf_open = vf_open,
.priv_size = sizeof(struct vf_priv_s),
.priv_defaults = &vf_priv_default,
.options = vf_opts_fields,
};

View File

@ -83,6 +83,8 @@ static int control(struct vf_instance *vf, int request, void *data)
};
return vo_control(video_out, VOCTRL_GET_EQUALIZER, &param) == VO_TRUE;
}
case VFCTRL_GET_HWDEC_INFO:
return vo_control(video_out, VOCTRL_GET_HWDEC_INFO, data) == VO_TRUE;
}
return CONTROL_UNKNOWN;
}

View File

@ -43,22 +43,6 @@
#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;
@ -86,7 +70,7 @@ struct priv {
struct mp_log *log;
struct vo *vo;
VADisplay display;
struct mp_vaapi_ctx mpvaapi;
struct mp_vaapi_ctx *mpvaapi;
struct mp_image_params image_params;
struct mp_rect src_rect;
@ -98,21 +82,18 @@ struct priv {
int output_surface;
int visible_surface;
int deint;
int deint_type;
int scaling;
int force_scaled_osd;
// with old libva versions only
int deint;
int deint_type;
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;
struct va_surface_pool *pool;
struct va_image_formats *va_image_formats;
VAImageFormat *va_subpic_formats;
unsigned int *va_subpic_flags;
int va_num_subpic_formats;
@ -128,187 +109,10 @@ static const bool osd_formats[SUBBITMAP_COUNT] = {
[SUBBITMAP_RGBA] = 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;
}
for (int n = 0; n < MAX_OUTPUT_SURFACES; n++)
mp_image_unrefp(&p->output_surfaces[n]);
p->output_surface = 0;
p->visible_surface = 0;
}
@ -318,60 +122,20 @@ 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);
for (int n = 0; n < MAX_OUTPUT_SURFACES; n++)
mp_image_unrefp(&p->swdec_surfaces[n]);
}
static int alloc_swdec_surfaces(struct priv *p, int w, int h, int format)
static bool alloc_swdec_surfaces(struct priv *p, int w, int h, int imgfmt)
{
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) {
vaDestroyImage(p->display, s->image.image_id);
s->image.image_id = VA_INVALID_ID;
}
status = vaDeriveImage(p->display, s->id, &s->image);
if (status == VA_STATUS_SUCCESS) {
/* vaDeriveImage() is supported, check format */
if (s->image.format.fourcc == image_format->fourcc &&
s->image.width == w && s->image.height == h)
{
s->is_bound = true;
MP_VERBOSE(p, "Using vaDeriveImage()\n");
} else {
vaDestroyImage(p->display, s->image.image_id);
s->image.image_id = VA_INVALID_ID;
status = VA_STATUS_ERROR_OPERATION_FAILED;
}
}
if (status != VA_STATUS_SUCCESS) {
status = vaCreateImage(p->display, image_format, w, h, &s->image);
if (!check_va_status(status, "vaCreateImage()")) {
talloc_free(img);
return -1;
}
}
p->swdec_surfaces[i] = img;
p->swdec_surfaces[i] =
va_surface_pool_get_wrapped(p->pool, p->va_image_formats, imgfmt, w, h);
if (!p->swdec_surfaces[i])
return false;
}
return 0;
return true;
}
static void resize(struct priv *p)
@ -392,7 +156,7 @@ static int reconfig(struct vo *vo, struct mp_image_params *params, int flags)
flags, "vaapi");
if (!IMGFMT_IS_VAAPI(params->imgfmt)) {
if (alloc_swdec_surfaces(p, params->w, params->h, params->imgfmt) < 0)
if (!alloc_swdec_surfaces(p, params->w, params->h, params->imgfmt))
return -1;
}
@ -401,42 +165,22 @@ static int reconfig(struct vo *vo, struct mp_image_params *params, int flags)
return 0;
}
static int query_format(struct vo *vo, uint32_t format)
static int query_format(struct vo *vo, uint32_t imgfmt)
{
struct priv *p = vo->priv;
if (IMGFMT_IS_VAAPI(format) || VAImageFormat_from_imgfmt(p, format))
if (IMGFMT_IS_VAAPI(imgfmt) || va_image_format_from_imgfmt(p->va_image_formats, imgfmt))
return VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW;
return 0;
}
static inline int get_field_flags(struct priv *p, int i, int flags)
{
return (p->deint && (flags & MP_IMGFIELD_INTERLACED) ?
(((!!(flags & MP_IMGFIELD_TOP_FIRST)) ^ i) == 0 ?
VA_BOTTOM_FIELD : VA_TOP_FIELD) : VA_FRAME_PICTURE);
}
static inline int get_colorspace_flags(struct priv *p)
{
#if USE_VAAPI_COLORSPACE
switch (p->image_params.colorspace) {
case MP_CSP_BT_601: return VA_SRC_BT601;
case MP_CSP_BT_709: return VA_SRC_BT709;
case MP_CSP_SMPTE_240M: return VA_SRC_SMPTE_240;
}
#endif
return 0;
}
static bool render_to_screen(struct priv *p, struct mp_image *mpi)
{
bool res = true;
VAStatus status;
struct vaapi_surface *surface = to_vaapi_surface(p, mpi);
if (!surface)
VASurfaceID surface = va_surface_id_in_mp_image(mpi);
if (surface == VA_INVALID_ID)
return false;
for (int n = 0; n < MAX_OSD_PARTS; n++) {
@ -447,7 +191,7 @@ static bool render_to_screen(struct priv *p, struct mp_image *mpi)
if (p->osd_screen)
flags |= VA_SUBPICTURE_DESTINATION_IS_SCREEN_COORD;
status = vaAssociateSubpicture2(p->display,
sp->id, &surface->id, 1,
sp->id, &surface, 1,
sp->src_x, sp->src_y,
sp->src_w, sp->src_h,
sp->dst_x, sp->dst_y,
@ -457,33 +201,34 @@ static bool render_to_screen(struct priv *p, struct mp_image *mpi)
}
}
for (int i = 0; i <= !!(p->deint > 1); i++) {
unsigned int flags = (get_field_flags(p, i, mpi->fields) |
get_colorspace_flags(p) |
p->scaling);
status = vaPutSurface(p->display,
surface->id,
p->vo->x11->window,
p->src_rect.x0,
p->src_rect.y0,
p->src_rect.x1 - p->src_rect.x0,
p->src_rect.y1 - p->src_rect.y0,
p->dst_rect.x0,
p->dst_rect.y0,
p->dst_rect.x1 - p->dst_rect.x0,
p->dst_rect.y1 - p->dst_rect.y0,
NULL, 0,
flags);
if (!check_va_status(status, "vaPutSurface()"))
res = false;
int flags = va_get_colorspace_flag(p->image_params.colorspace) | p->scaling;
if (p->deint && (mpi->fields & MP_IMGFIELD_INTERLACED)) {
flags |= (mpi->fields & MP_IMGFIELD_TOP_FIRST) ?
VA_BOTTOM_FIELD : VA_TOP_FIELD;
} else {
flags |= VA_FRAME_PICTURE;
}
status = vaPutSurface(p->display,
surface,
p->vo->x11->window,
p->src_rect.x0,
p->src_rect.y0,
p->src_rect.x1 - p->src_rect.x0,
p->src_rect.y1 - p->src_rect.y0,
p->dst_rect.x0,
p->dst_rect.y0,
p->dst_rect.x1 - p->dst_rect.x0,
p->dst_rect.y1 - p->dst_rect.y0,
NULL, 0,
flags);
check_va_status(status, "vaPutSurface()");
for (int n = 0; n < MAX_OSD_PARTS; n++) {
struct vaapi_osd_part *part = &p->osd_parts[n];
if (part->active) {
struct vaapi_subpic *sp = &part->subpic;
status = vaDeassociateSubpicture(p->display, sp->id,
&surface->id, 1);
&surface, 1);
check_va_status(status, "vaDeassociateSubpicture()");
}
}
@ -500,144 +245,19 @@ static void flip_page(struct vo *vo)
p->output_surface = (p->output_surface + 1) % MAX_OUTPUT_SURFACES;
}
static int map_image(struct priv *p, VAImage *va_image, int mpfmt,
struct mp_image *dst)
{
VAStatus status;
if (mpfmt != va_fourcc_to_imgfmt(va_image->format.fourcc))
return -1;
void *image_data = NULL;
status = vaMapBuffer(p->display, va_image->buf, &image_data);
if (!check_va_status(status, "vaMapBuffer()"))
return -1;
*dst = (struct mp_image) {0};
mp_image_setfmt(dst, mpfmt);
mp_image_set_size(dst, va_image->width, va_image->height);
for (int p = 0; p < va_image->num_planes; p++) {
dst->stride[p] = va_image->pitches[p];
dst->planes[p] = (uint8_t *)image_data + va_image->offsets[p];
}
if (va_image->format.fourcc == VA_FOURCC('Y','V','1','2')) {
FFSWAP(unsigned int, dst->stride[1], dst->stride[2]);
FFSWAP(uint8_t *, dst->planes[1], dst->planes[2]);
}
return 0;
}
static int unmap_image(struct priv *p, VAImage *va_image)
{
VAStatus status;
status = vaUnmapBuffer(p->display, va_image->buf);
return check_va_status(status, "vaUnmapBuffer()") ? 0 : -1;
}
static int upload_surface(struct priv *p, struct vaapi_surface *va_surface,
struct mp_image *mpi)
{
VAStatus status;
if (va_surface->image.image_id == VA_INVALID_ID)
return -1;
struct mp_image img;
if (map_image(p, &va_surface->image, mpi->imgfmt, &img) < 0)
return -1;
mp_image_copy(&img, mpi);
unmap_image(p, &va_surface->image);
if (!va_surface->is_bound) {
status = vaPutImage2(p->display, va_surface->id,
va_surface->image.image_id,
0, 0, mpi->w, mpi->h,
0, 0, mpi->w, mpi->h);
if (!check_va_status(status, "vaPutImage()"))
return -1;
}
return 0;
}
static int try_get_surface(struct priv *p, VAImageFormat *fmt,
struct vaapi_surface *va_surface,
VAImage *out_image)
{
VAStatus status;
status = vaSyncSurface(p->display, va_surface->id);
if (!check_va_status(status, "vaSyncSurface()"))
return -2;
int w = va_surface->w;
int h = va_surface->h;
status = vaCreateImage(p->display, fmt, w, h, out_image);
if (!check_va_status(status, "vaCreateImage()"))
return -2;
status = vaGetImage(p->display, va_surface->id, 0, 0, w, h,
out_image->image_id);
if (status != VA_STATUS_SUCCESS) {
vaDestroyImage(p->display, out_image->image_id);
return -1;
}
return 0;
}
static struct mp_image *download_surface(struct priv *p,
struct vaapi_surface *va_surface)
{
// We have no clue which format will work, so try them all.
// This code is just for screenshots, so it's ok not to cache the right
// format (to prevent unnecessary work), and we don't attempt to use
// vaDeriveImage() for direct access either.
for (int i = 0; i < p->va_num_image_formats; i++) {
VAImageFormat *fmt = &p->va_image_formats[i];
int mpfmt = va_fourcc_to_imgfmt(fmt->fourcc);
if (!mpfmt)
continue;
VAImage image;
int r = try_get_surface(p, fmt, va_surface, &image);
if (r == -1)
continue;
if (r < 0)
return NULL;
struct mp_image *res = NULL;
struct mp_image tmp;
if (map_image(p, &image, mpfmt, &tmp) >= 0) {
res = mp_image_alloc(mpfmt, tmp.w, tmp.h);
mp_image_copy(res, &tmp);
unmap_image(p, &image);
}
vaDestroyImage(p->display, image.image_id);
return res;
}
MP_ERR(p, "failed to get surface data.\n");
return NULL;
}
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;
if (!IMGFMT_IS_VAAPI(mpi->imgfmt)) {
struct mp_image *surface = p->swdec_surfaces[p->output_surface];
struct vaapi_surface *va_surface = to_vaapi_surface(p, surface);
if (!va_surface)
struct mp_image *wrapper = p->swdec_surfaces[p->output_surface];
struct va_surface *surface = va_surface_in_mp_image(wrapper);
if (!surface)
return;
if (upload_surface(p, va_surface, mpi) < 0)
if (!va_surface_upload(surface, mpi))
return;
mp_image_copy_attributes(surface, mpi);
mpi = surface;
mp_image_copy_attributes(wrapper, mpi);
mpi = wrapper;
}
mp_image_setrefp(&p->output_surfaces[p->output_surface], mpi);
@ -645,11 +265,11 @@ static void draw_image(struct vo *vo, mp_image_t *mpi)
static struct mp_image *get_screenshot(struct priv *p)
{
struct vaapi_surface *va_surface =
to_vaapi_surface(p, p->output_surfaces[p->visible_surface]);
if (!va_surface)
struct va_surface *surface =
va_surface_in_mp_image(p->output_surfaces[p->visible_surface]);
if (!surface)
return NULL;
struct mp_image *img = download_surface(p, va_surface);
struct mp_image *img = va_surface_download(surface, p->va_image_formats);
if (!img)
return NULL;
struct mp_image_params params = p->image_params;
@ -733,7 +353,7 @@ static void draw_osd_cb(void *pctx, struct sub_bitmaps *imgs)
struct vaapi_osd_image *img = &part->image;
struct mp_image vaimg;
if (map_image(p, &img->image, IMGFMT_BGRA, &vaimg) < 0)
if (va_image_map(p->display, &img->image, &vaimg) < 0)
goto error;
// Clear borders and regions uncovered by sub-bitmaps
@ -754,7 +374,7 @@ static void draw_osd_cb(void *pctx, struct sub_bitmaps *imgs)
vaimg.stride[0], sub->stride);
}
if (unmap_image(p, &img->image) < 0)
if (va_image_unmap(p->display, &img->image) < 0)
goto error;
part->subpic = (struct vaapi_subpic) {
@ -864,14 +484,18 @@ static int control(struct vo *vo, uint32_t request, void *data)
switch (request) {
case VOCTRL_GET_DEINTERLACE:
if (!p->deint_type)
break;
*(int*)data = !!p->deint;
return VO_TRUE;
case VOCTRL_SET_DEINTERLACE:
if (!p->deint_type)
break;
p->deint = *(int*)data ? p->deint_type : 0;
return VO_TRUE;
case VOCTRL_GET_HWDEC_INFO: {
struct mp_hwdec_info *arg = data;
arg->vaapi_ctx = &p->mpvaapi;
arg->vaapi_ctx = p->mpvaapi;
return true;
}
case VOCTRL_SET_EQUALIZER: {
@ -910,24 +534,14 @@ static void uninit(struct vo *vo)
struct priv *p = vo->priv;
free_video_specific(p);
for (int n = 0; n < p->num_video_surfaces; n++) {
struct vaapi_surface *surface = p->video_surfaces[n];
// Nothing is allowed to reference HW surfaces past VO lifetime.
assert(!surface->is_used);
talloc_free(surface);
}
p->num_video_surfaces = 0;
va_surface_pool_release(p->pool);
for (int n = 0; n < MAX_OSD_PARTS; n++) {
struct vaapi_osd_part *part = &p->osd_parts[n];
free_subpicture(p, &part->image);
}
if (p->display) {
vaTerminate(p->display);
p->display = NULL;
}
va_destroy(p->mpvaapi);
vo_x11_uninit(vo);
}
@ -947,26 +561,14 @@ static int preinit(struct vo *vo)
if (!p->display)
return -1;
int major_version, minor_version;
status = vaInitialize(p->display, &major_version, &minor_version);
if (!check_va_status(status, "vaInitialize()"))
p->mpvaapi = va_initialize(p->display);
if (!p->mpvaapi) {
vaTerminate(p->display);
return -1;
MP_VERBOSE(vo, "VA API version %d.%d\n", major_version, minor_version);
}
p->mpvaapi.display = p->display;
p->mpvaapi.priv = vo;
p->mpvaapi.flush = flush_surfaces;
p->mpvaapi.get_surface = get_surface;
int max_image_formats = vaMaxNumImageFormats(p->display);
p->va_image_formats = talloc_array(vo, VAImageFormat, max_image_formats);
status = vaQueryImageFormats(p->display, p->va_image_formats,
&p->va_num_image_formats);
if (!check_va_status(status, "vaQueryImageFormats()"))
return -1;
MP_VERBOSE(vo, "%d image formats available:\n", p->va_num_image_formats);
for (int i = 0; i < p->va_num_image_formats; i++)
MP_VERBOSE(vo, " %s\n", STR_FOURCC(p->va_image_formats[i].fourcc));
p->pool = va_surface_pool_alloc(p->display, VA_RT_FORMAT_YUV420);
p->va_image_formats = p->mpvaapi->image_formats;
int max_subpic_formats = vaMaxNumSubpictureFormats(p->display);
p->va_subpic_formats = talloc_array(vo, VAImageFormat, max_subpic_formats);
@ -982,7 +584,7 @@ static int preinit(struct vo *vo)
for (int i = 0; i < p->va_num_subpic_formats; i++) {
MP_VERBOSE(vo, " %s, flags 0x%x\n",
STR_FOURCC(p->va_subpic_formats[i].fourcc),
VA_STR_FOURCC(p->va_subpic_formats[i].fourcc),
p->va_subpic_flags[i]);
if (p->va_subpic_formats[i].fourcc == OSD_VA_FORMAT) {
p->osd_format = p->va_subpic_formats[i];
@ -1034,8 +636,10 @@ const struct vo_driver video_out_vaapi = {
.priv_size = sizeof(struct priv),
.priv_defaults = &(const struct priv) {
.scaling = VA_FILTER_SCALING_DEFAULT,
.deint_type = 2,
.deint = 0,
#if !CONFIG_VAAPI_VPP
.deint_type = 2,
#endif
},
.options = (const struct m_option[]) {
#if USE_VAAPI_SCALING

520
video/vaapi.c Normal file
View File

@ -0,0 +1,520 @@
/*
* This file is part of mpv.
*
* 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 <assert.h>
#include <libavutil/avutil.h>
#include "vaapi.h"
#include "mpvcore/mp_msg.h"
#include "mp_image.h"
#include "img_format.h"
#define VA_VERBOSE(...) mp_msg(MSGT_VO, MSGL_V, "[vaapi] " __VA_ARGS__)
#define VA_ERROR(...) mp_msg(MSGT_VO, MSGL_ERR, "[vaapi] " __VA_ARGS__)
bool check_va_status(VAStatus status, const char *msg)
{
if (status != VA_STATUS_SUCCESS) {
mp_msg(MSGT_VO, MSGL_ERR, "[vaapi] %s: %s\n", msg, vaErrorStr(status));
return false;
}
return true;
}
int va_get_colorspace_flag(enum mp_csp csp)
{
#if USE_VAAPI_COLORSPACE
switch (csp) {
case MP_CSP_BT_601: return VA_SRC_BT601;
case MP_CSP_BT_709: return VA_SRC_BT709;
case MP_CSP_SMPTE_240M: return VA_SRC_SMPTE_240;
}
#endif
return 0;
}
struct fmtentry {
uint32_t va;
enum mp_imgfmt mp;
};
static const struct fmtentry va_to_imgfmt[] = {
{VA_FOURCC_YV12, IMGFMT_420P},
{VA_FOURCC_I420, IMGFMT_420P},
{VA_FOURCC_IYUV, IMGFMT_420P},
{VA_FOURCC_NV12, IMGFMT_NV12},
{VA_FOURCC_UYVY, IMGFMT_UYVY},
{VA_FOURCC_YUY2, IMGFMT_YUYV},
// Note: not sure about endian issues (the mp formats are byte-addressed)
{VA_FOURCC_RGBA, IMGFMT_RGBA},
{VA_FOURCC_RGBX, IMGFMT_RGBA},
{VA_FOURCC_BGRA, IMGFMT_BGRA},
{VA_FOURCC_BGRX, IMGFMT_BGRA},
{0 , IMGFMT_NONE}
};
enum mp_imgfmt va_fourcc_to_imgfmt(uint32_t fourcc)
{
for (const struct fmtentry *entry = va_to_imgfmt; entry->va; ++entry) {
if (entry->va == fourcc)
return entry->mp;
}
return IMGFMT_NONE;
}
uint32_t va_fourcc_from_imgfmt(int imgfmt)
{
for (const struct fmtentry *entry = va_to_imgfmt; entry->va; ++entry) {
if (entry->mp == imgfmt)
return entry->va;
}
return 0;
}
struct va_image_formats {
VAImageFormat *entries;
int num;
};
static void va_get_formats(struct mp_vaapi_ctx *ctx)
{
int num = vaMaxNumImageFormats(ctx->display);
VAImageFormat entries[num];
VAStatus status = vaQueryImageFormats(ctx->display, entries, &num);
if (!check_va_status(status, "vaQueryImageFormats()"))
return;
struct va_image_formats *formats = talloc_ptrtype(ctx, formats);
formats->entries = talloc_array(formats, VAImageFormat, num);
formats->num = num;
VA_VERBOSE("%d image formats available:\n", num);
for (int i = 0; i < num; i++) {
formats->entries[i] = entries[i];
VA_VERBOSE(" %s\n", VA_STR_FOURCC(entries[i].fourcc));
}
ctx->image_formats = formats;
}
struct mp_vaapi_ctx *va_initialize(VADisplay *display)
{
int major_version, minor_version;
int status = vaInitialize(display, &major_version, &minor_version);
if (!check_va_status(status, "vaInitialize()"))
return NULL;
VA_VERBOSE("VA API version %d.%d\n", major_version, minor_version);
struct mp_vaapi_ctx *res = talloc_ptrtype(NULL, res);
*res = (struct mp_vaapi_ctx) {
.display = display,
};
va_get_formats(res);
if (!res->image_formats)
goto error;
return res;
error:
if (res->display)
vaTerminate(res->display);
talloc_free(res);
return NULL;
}
// Undo va_initialize, and close the VADisplay.
void va_destroy(struct mp_vaapi_ctx *ctx)
{
if (ctx) {
if (ctx->display)
vaTerminate(ctx->display);
talloc_free(ctx);
}
}
VAImageFormat *va_image_format_from_imgfmt(const struct va_image_formats *formats,
int imgfmt)
{
const int fourcc = va_fourcc_from_imgfmt(imgfmt);
if (!formats || !formats->num || !fourcc)
return NULL;
for (int i = 0; i < formats->num; i++) {
if (formats->entries[i].fourcc == fourcc)
return &formats->entries[i];
}
return NULL;
}
static void va_surface_destroy(struct va_surface *surface);
struct va_surface_pool {
VADisplay display;
int rt_format;
int num_surfaces, lru_counter;
struct va_surface **surfaces;
};
typedef struct va_surface_priv {
VADisplay display;
VAImage image; // used for sofwtare decoding case
bool is_derived; // is image derived by vaDeriveImage()?
bool is_used; // referenced
bool is_dead; // used, but deallocate VA objects as soon as possible
int order; // for LRU allocation
} va_surface_priv_t;
struct va_surface_pool *va_surface_pool_alloc(VADisplay display, int rt_format)
{
struct va_surface_pool *pool = talloc_ptrtype(NULL, pool);
*pool = (struct va_surface_pool) {
.display = display,
.rt_format = rt_format
};
return pool;
}
void va_surface_pool_release(struct va_surface_pool *pool)
{
if (!pool)
return;
va_surface_pool_clear(pool);
talloc_free(pool);
}
void va_surface_pool_releasep(struct va_surface_pool **pool) {
if (!pool)
return;
va_surface_pool_release(*pool);
*pool = NULL;
}
void va_surface_pool_clear(struct va_surface_pool *pool)
{
for (int i=0; i<pool->num_surfaces; ++i) {
struct va_surface *s = pool->surfaces[i];
if (s->p->is_used)
s->p->is_dead = true;
else
va_surface_destroy(s);
}
talloc_free(pool->surfaces);
pool->num_surfaces = 0;
}
void va_surface_destroy(struct va_surface *surface)
{
if (!surface)
return;
if (surface->id != VA_INVALID_ID) {
va_surface_priv_t *p = surface->p;
assert(!p->is_used);
if (p->image.image_id != VA_INVALID_ID)
vaDestroyImage(p->display, p->image.image_id);
vaDestroySurfaces(p->display, &surface->id, 1);
}
talloc_free(surface);
}
void va_surface_release(struct va_surface *surface)
{
if (!surface)
return;
surface->p->is_used = false;
if (surface->p->is_dead)
va_surface_destroy(surface);
}
void va_surface_releasep(struct va_surface **surface)
{
if (!surface)
return;
va_surface_release(*surface);
*surface = NULL;
}
static struct va_surface *va_surface_alloc(struct va_surface_pool *pool,
int w, int h)
{
VASurfaceID id = VA_INVALID_ID;
VAStatus status;
status = vaCreateSurfaces(pool->display, w, h, pool->rt_format, 1, &id);
if (!check_va_status(status, "vaCreateSurfaces()"))
return NULL;
struct va_surface *surface = talloc_ptrtype(NULL, surface);
if (!surface)
return NULL;
MP_TARRAY_APPEND(NULL, pool->surfaces, pool->num_surfaces, surface);
surface->id = id;
surface->w = w;
surface->h = h;
surface->rt_format = pool->rt_format;
surface->p = talloc_zero(surface, va_surface_priv_t);
surface->p->display = pool->display;
surface->p->image.image_id = surface->p->image.buf = VA_INVALID_ID;
return surface;
}
struct mp_image *va_surface_pool_get_wrapped(struct va_surface_pool *pool,
const struct va_image_formats *formats,
int imgfmt, int w, int h)
{
return va_surface_wrap(va_surface_pool_get_by_imgfmt(pool, formats, imgfmt,
w, h));
}
int va_surface_pool_rt_format(const struct va_surface_pool *pool)
{
return pool->rt_format;
}
bool va_surface_pool_reserve(struct va_surface_pool *pool, int count,
int w, int h)
{
for (int i=0; i<pool->num_surfaces && count > 0; ++i) {
const struct va_surface *s = pool->surfaces[i];
if (s->w == w && s->h == h && !s->p->is_used)
--count;
}
while (count > 0) {
if (!va_surface_alloc(pool, w, h))
break;
--count;
}
return !count;
}
struct va_surface *va_surface_pool_get(struct va_surface_pool *pool,
int w, int h)
{
struct va_surface *best = NULL;
for (int i=0; i<pool->num_surfaces; ++i) {
struct va_surface *s = pool->surfaces[i];
if (!s->p->is_used && s->w == w && s->h == h) {
if (!best || best->p->order > s->p->order)
best = s;
}
}
if (!best)
best = va_surface_alloc(pool, w, h);
if (best) {
best->p->is_used = true;
best->p->order = ++pool->lru_counter;
}
return best;
}
static void va_surface_image_destroy(struct va_surface *surface)
{
if (!surface || surface->p->image.image_id == VA_INVALID_ID)
return;
va_surface_priv_t *p = surface->p;
vaDestroyImage(p->display, p->image.image_id);
p->image.image_id = VA_INVALID_ID;
p->is_derived = false;
}
static VAImage *va_surface_image_alloc(struct va_surface *surface,
VAImageFormat *format)
{
if (!format || !surface)
return NULL;
va_surface_priv_t *p = surface->p;
if (p->image.image_id != VA_INVALID_ID &&
p->image.format.fourcc == format->fourcc)
return &p->image;
va_surface_image_destroy(surface);
VAStatus status = vaDeriveImage(p->display, surface->id, &p->image);
if (check_va_status(status, "vaDeriveImage()")) {
/* vaDeriveImage() is supported, check format */
if (p->image.format.fourcc == format->fourcc &&
p->image.width == surface->w && p->image.height == surface->h) {
p->is_derived = true;
VA_VERBOSE("Using vaDeriveImage()\n");
} else {
vaDestroyImage(p->display, p->image.image_id);
p->image.image_id = VA_INVALID_ID;
status = VA_STATUS_ERROR_OPERATION_FAILED;
}
}
if (status != VA_STATUS_SUCCESS) {
status = vaCreateImage(p->display, format, surface->w, surface->h,
&p->image);
if (!check_va_status(status, "vaCreateImage()")) {
p->image.image_id = VA_INVALID_ID;
return NULL;
}
}
return &surface->p->image;
}
struct va_surface *va_surface_pool_get_by_imgfmt(struct va_surface_pool *pool,
const struct va_image_formats *formats,
int imgfmt, int w, int h)
{
if (imgfmt == IMGFMT_VAAPI)
return va_surface_pool_get(pool, w, h);
VAImageFormat *format = va_image_format_from_imgfmt(formats, imgfmt);
if (!format)
return NULL;
// WTF: no mapping from VAImageFormat -> VA_RT_FORMAT_
struct va_surface *surface = va_surface_pool_get(pool, w, h);
if (!surface)
return NULL;
if (va_surface_image_alloc(surface, format))
return surface;
va_surface_release(surface);
return NULL;
}
static void free_va_surface(void *arg)
{
va_surface_release((struct va_surface*)arg);
}
struct mp_image *va_surface_wrap(struct va_surface *surface)
{
if (!surface)
return NULL;
struct mp_image img = {0};
mp_image_setfmt(&img, IMGFMT_VAAPI);
mp_image_set_size(&img, surface->w, surface->h);
img.planes[0] = (uint8_t*)surface;
img.planes[3] = (uint8_t*)(uintptr_t)surface->id;
return mp_image_new_custom_ref(&img, surface, free_va_surface);
}
VASurfaceID va_surface_id_in_mp_image(const struct mp_image *mpi)
{
return mpi && IMGFMT_IS_VAAPI(mpi->imgfmt) ?
(VASurfaceID)(uintptr_t)mpi->planes[3] : VA_INVALID_ID;
}
struct va_surface *va_surface_in_mp_image(struct mp_image *mpi)
{
return mpi && IMGFMT_IS_VAAPI(mpi->imgfmt) ?
(struct va_surface*)mpi->planes[0] : NULL;
}
VASurfaceID va_surface_id(const struct va_surface *surface)
{
return surface->id;
}
bool va_image_map(VADisplay display, VAImage *image, struct mp_image *mpi)
{
int imgfmt = va_fourcc_to_imgfmt(image->format.fourcc);
if (imgfmt == IMGFMT_NONE)
return false;
void *data = NULL;
const VAStatus status = vaMapBuffer(display, image->buf, &data);
if (!check_va_status(status, "vaMapBuffer()"))
return false;
*mpi = (struct mp_image) {0};
mp_image_setfmt(mpi, imgfmt);
mp_image_set_size(mpi, image->width, image->height);
for (int p = 0; p < image->num_planes; p++) {
mpi->stride[p] = image->pitches[p];
mpi->planes[p] = (uint8_t *)data + image->offsets[p];
}
if (image->format.fourcc == VA_FOURCC_YV12) {
FFSWAP(unsigned int, mpi->stride[1], mpi->stride[2]);
FFSWAP(uint8_t *, mpi->planes[1], mpi->planes[2]);
}
return true;
}
bool va_image_unmap(VADisplay display, VAImage *image)
{
const VAStatus status = vaUnmapBuffer(display, image->buf);
return check_va_status(status, "vaUnmapBuffer()");
}
bool va_surface_upload(struct va_surface *surface, const struct mp_image *mpi)
{
va_surface_priv_t *p = surface->p;
if (p->image.image_id == VA_INVALID_ID)
return false;
struct mp_image img;
if (!va_image_map(p->display, &p->image, &img))
return false;
mp_image_copy(&img, (struct mp_image*)mpi);
va_image_unmap(p->display, &p->image);
if (!p->is_derived) {
VAStatus status = vaPutImage2(p->display, surface->id,
p->image.image_id,
0, 0, mpi->w, mpi->h,
0, 0, mpi->w, mpi->h);
if (!check_va_status(status, "vaPutImage()"))
return false;
}
return true;
}
struct mp_image *va_surface_download(const struct va_surface *surface,
const struct va_image_formats *formats)
{
VAStatus status = vaSyncSurface(surface->p->display, surface->id);
if (!check_va_status(status, "vaSyncSurface()"))
return NULL;
// We have no clue which format will work, so try them all.
// This code is just for screenshots, so it's ok not to cache the right
// format (to prevent unnecessary work), and we don't attempt to use
// vaDeriveImage() for direct access either.
for (int i = 0; i < formats->num; i++) {
VAImageFormat *format = &formats->entries[i];
const enum mp_imgfmt imgfmt = va_fourcc_to_imgfmt(format->fourcc);
if (imgfmt == IMGFMT_NONE)
continue;
VAImage image;
status = vaCreateImage(surface->p->display, format,
surface->w, surface->h, &image);
if (!check_va_status(status, "vaCreateImage()"))
continue;
status = vaGetImage(surface->p->display, surface->id, 0, 0,
surface->w, surface->h, image.image_id);
if (status != VA_STATUS_SUCCESS) {
vaDestroyImage(surface->p->display, image.image_id);
continue;
}
struct mp_image *dst = NULL;
struct mp_image tmp;
if (va_image_map(surface->p->display, &image, &tmp)) {
assert(tmp.imgfmt == imgfmt);
dst = mp_image_alloc(imgfmt, tmp.w, tmp.h);
mp_image_copy(dst, &tmp);
va_image_unmap(surface->p->display, &image);
}
vaDestroyImage(surface->p->display, image.image_id);
return dst;
}
VA_ERROR("failed to get surface data.\n");
return NULL;
}

View File

@ -3,8 +3,8 @@
#include <stdbool.h>
#include <inttypes.h>
#include <va/va.h>
#include <va/va_x11.h>
/* Compatibility glue with VA-API >= 0.31 */
#if defined VA_CHECK_VERSION
@ -51,23 +51,72 @@
# define USE_VAAPI_SCALING 0
#endif
#include "mpvcore/mp_msg.h"
#ifndef VA_FOURCC_YV12
#define VA_FOURCC_YV12 0x32315659
#endif
#ifndef VA_FOURCC_IYUV
#define VA_FOURCC_IYUV 0x56555949
#endif
#ifndef VA_FOURCC_I420
#define VA_FOURCC_I420 VA_FOURCC('I', '4', '2', '0')
#endif
#ifndef VA_FOURCC_RGBX
#define VA_FOURCC_RGBX 0x58424752
#endif
#ifndef VA_FOURCC_BGRX
#define VA_FOURCC_BGRX 0x58524742
#endif
static inline bool check_va_status(VAStatus status, const char *msg)
{
if (status != VA_STATUS_SUCCESS) {
mp_msg(MSGT_VO, MSGL_ERR, "[vaapi] %s: %s\n", msg, vaErrorStr(status));
return false;
}
return true;
}
#define VA_STR_FOURCC(fcc) \
(const char[]){(fcc), (fcc) >> 8u, (fcc) >> 16u, (fcc) >> 24u, 0}
#include "mp_image.h"
struct mp_vaapi_ctx {
VADisplay display;
struct mp_image *(*get_surface)(struct mp_vaapi_ctx *ctx, int va_format,
int mp_format, int w, int h);
void (*flush)(struct mp_vaapi_ctx *ctx);
void *priv; // for VO
struct va_image_formats *image_formats;
};
struct va_surface_pool;
struct va_image_formats;
struct va_surface {
VASurfaceID id; // VA_INVALID_ID if unallocated
int w, h, rt_format; // parameters of allocated image (0/0/-1 unallocated)
struct va_surface_priv *p;
};
bool check_va_status(VAStatus status, const char *msg);
int va_get_colorspace_flag(enum mp_csp csp);
struct mp_vaapi_ctx *va_initialize(VADisplay *display);
void va_destroy(struct mp_vaapi_ctx *ctx);
enum mp_imgfmt va_fourcc_to_imgfmt(uint32_t fourcc);
uint32_t va_fourcc_from_imgfmt(int imgfmt);
VAImageFormat * va_image_format_from_imgfmt(const struct va_image_formats *formats, int imgfmt);
bool va_image_map(VADisplay display, VAImage *image, struct mp_image *mpi);
bool va_image_unmap(VADisplay display, VAImage *image);
struct va_surface_pool * va_surface_pool_alloc(VADisplay display, int rt_format);
void va_surface_pool_release(struct va_surface_pool *pool);
void va_surface_pool_releasep(struct va_surface_pool **pool);
void va_surface_pool_clear(struct va_surface_pool *pool);
bool va_surface_pool_reserve(struct va_surface_pool *pool, int count, int w, int h);
int va_surface_pool_rt_format(const struct va_surface_pool *pool);
struct va_surface * va_surface_pool_get(struct va_surface_pool *pool, int w, int h);
struct va_surface * va_surface_pool_get_by_imgfmt(struct va_surface_pool *pool, const struct va_image_formats *formats, int imgfmt, int w, int h);
struct mp_image * va_surface_pool_get_wrapped(struct va_surface_pool *pool, const struct va_image_formats *formats, int imgfmt, int w, int h);
void va_surface_release(struct va_surface *surface);
void va_surface_releasep(struct va_surface **surface);
struct va_surface * va_surface_in_mp_image(struct mp_image *mpi);
struct mp_image * va_surface_wrap(struct va_surface *surface); // takes ownership
VASurfaceID va_surface_id(const struct va_surface *surface);
VASurfaceID va_surface_id_in_mp_image(const struct mp_image *mpi);
bool va_surface_upload(struct va_surface *surface, const struct mp_image *mpi);
struct mp_image * va_surface_download(const struct va_surface *surface, const struct va_image_formats *formats);
#endif