libva使用笔记


参考:link

流程

针解码
buff
vaUnMapBuffer
vaCreateBuffer
vaMapBuffer
pic
vaEndPicture
vaBeginPicture
vaRenderPicture
export
vaPutSurface
vaSyncSurface
解码上下文
vaCreateContext
vaCreateSurfaces
创建配置
vaCreateConfig
vaQueryConfigEntrypoints
vaGetConfigAttributes
初始化
render_fd
vaDisplay
vaInitialize
open render_dev
vaGetDisplay

初始化阶段,Setting display

X11:

x11_display = XOpenDisplay(NULL);

vaDisplay = vaGetDisplay(x11_display);

aStatus = vaInitialize(vaDisplay, &major_ver, &minor_ver);

drm:

static const char *drm_device_paths[] = { "/dev/dri/renderD128",
                                              "/dev/dri/card0",
                                              "/dev/dri/renderD129",
                                              "/dev/dri/card1"};

drm_fd = open(drm_device_paths[i], O_RDWR);

va_dpy = vaGetDisplayDRM(drm_fd);

vas = vaInitialize(ctx->va_dpy, &major_ver, &minor_ver);

协商和创建配置

大为了确定特定平台上支持的硬件加速级别,客户端需要确保硬件支持所需的视频配置文件(格式)以及该配置文件可用的入口点。为此,客户端使用 vaQueryConfigEntrypoints 查询驱动程序的功能。根据驱动程序的回答,客户端可以采取适当的操作。

X11:

vaQueryConfigEntrypoints(vaDisplay, VAProfileMPEG2Main, entrypoints, &num_entrypoints);

for(vld_entrypoint = 0; vld_entrypoint < num_entrypoints;vld_entrypoint++) {
    if ( entrypoints[vld_entrypoint] == VAEntrypointVLD )
        break;
    if (vld_entrypoint == num_entrypoints) {
        /* not find VLD entry point */
        exit(-1);
    }
    /* Assuming finding VLD, find out the format for the render target */
    attrib.type = VAConfigAttribRTFormat;
    vaGetConfigAttributes(vaDisplay, VAProfileMPEG2Main, VAEntrypointVLD,&attrib, 1);
    if((attrib.value & VA_RT_FORMAT_YUV420) == 0) {
        /* not find desired YUV420 RT format */
        exit(-1);
    }
    vaStatus = vaCreateConfig(vaDisplay, VAProfileMPEG2Main, VAEntrypointVLD,&attrib, 1,&config_id);

drm:

VAEntrypoint *entrypoints = NULL;
num_entry = vaMaxNumEntrypoints(ctx->va_dpy);
entrypoints = malloc(num_entry * sizeof(*entrypoints));

VAProfile profile_list[] = { VAProfileNone,
                                 VAProfileH264ConstrainedBaseline,
                                 VAProfileH264Main,
                                 VAProfileH264High,
                                 VAProfileHEVCMain };
for (i = 0; i < ARRAY_SIZE(profile_list); i++) {
    VAStatus vas;
    vas = vaQueryConfigEntrypoints(ctx->va_dpy, profile_list[i],
                                   entrypoints, &num_entry);
    for (slice_entry = 0; slice_entry < num_entry; slice_entry++) {
            if (entrypoints[slice_entry] == VAEntrypointEncSlice ||
                entrypoints[slice_entry] == VAEntrypointVideoProc) {
                support_profile |= 1 << i;
                break;
            }
        }
    }

VAConfigAttrib pp_attrib = { VAConfigAttribRTFormat, 0 };
vas = vaGetConfigAttributes(ctx->va_dpy, VAProfileNone,
                                VAEntrypointVideoProc, &pp_attrib, 1);
if ((pp_attrib.value & VA_RT_FORMAT_YUV420) == 0)
        goto fail;

free(entrypoints);

PProcContext *pp_ctx = NULL;
pp_ctx = calloc(1, sizeof(*pp_ctx));
vas = vaCreateConfig(ctx->va_dpy, VAProfileNone, VAEntrypointVideoProc, NULL, 0, &pp_ctx->vpp_config);

解码上下文

一旦创建了解码配置,下一步就是创建一个解码上下文,它代表一个虚拟硬件解码管道。此虚拟解码管道将解码像素输出到称为“surface”的渲染目标。解码后的帧存储在 Surfaces 中,随后可以渲染到在第一阶段定义的 X 个可绘制对象。

客户端创建两个对象。它首先创建一个 Surface 对象。该对象收集要由驱动程序创建的渲染目标的参数,如图片宽度、高度和格式。第二个对象是“上下文”对象。Context 对象在创建时与 Surface 对象绑定。一旦一个表面绑定到一个给定的上下文,它就不能用于创建另一个上下文。当上下文被销毁时,关联被删除。上下文和表面都由唯一的 ID 标识,并且其实现特定的内部结构对客户端保持不透明。任何操作,无论是数据传输还是帧解码,都会将此上下文 ID 作为参数,以确定使用哪个虚拟解码管道。请参阅下面显示如何设置解码上下文的代码示例。
X11:

/* 
* create surfaces for the current target as well as reference frames
*/
VASurfaceID vaSurface;
vaStatus = vaCreateSurfaces(vaDisplay,surf_width,surf_height,
							VA_RT_FORMAT_YUV420, 1, &vaSurface);

/*
* Create a context for this decode pipe
*/
VAContextID vaContext;
vaStatus = vaCreateContext(vaDisplay, config_id,
							CLIP_WIDTH,((CLIP_HEIGHT+15)/16)*16,
							VA_PROGRESSIVE,
							&vaSurface,
							1,&vaContext);

drm:

vas = vaCreateContext(ctx->va_dpy, pp_ctx->vpp_config, 0,
                          0, // is ok in gallium va
                          0, // is ok when vpp
                          NULL, 0, // is ok in gallium va
                          &pp_ctx->vpp_context);
                          
 /* vpp param buffer */
vas = vaCreateBuffer(ctx->va_dpy, pp_ctx->vpp_context,
                     VAProcPipelineParameterBufferType,
                     sizeof(VAProcPipelineParameterBuffer), 1, NULL,
                     &pp_ctx->vpp_param);

/* create yuv */
VASurfaceAttrib surface_attrib;
YUV_Surf *yuv;
yuv = calloc(1, sizeof(*yuv));
surface_attrib.type = VASurfaceAttribPixelFormat;
surface_attrib.flags = VA_SURFACE_ATTRIB_SETTABLE;
surface_attrib.value.type = VAGenericValueTypeInteger;
surface_attrib.value.value.i = VA_FOURCC_NV12;
vas = vaCreateSurfaces(ctx->va_dpy, VA_RT_FORMAT_YUV420, width, height,
                       &yuv->id, 1, &surface_attrib, 1);

针解码

对于解码帧,我们需要向虚拟管道提供参数和比特流数据,以便它可以解码压缩的视频帧。有几种类型的数据要发送:

  • 一些配置数据,如逆量化矩阵缓冲区、图片参数缓冲区、切片缓冲区参数或支持的不同格式所需的其他数据结构。该数据在发送实际数据流进行解码之前参数化虚拟管道。
  • 比特流数据。它需要以结构化的方式发送,以便驱动程序可以正确解释和解码。

1、创建缓冲区

向驱动程序发送参数和位流数据的方式是通过“缓冲区”。缓冲区数据存储由库管理,而客户端使用驱动程序分配的唯一 ID 标识每个缓冲区。

有两种方法可以设置保存参数或位流数据的缓冲区的内容。第一个实际上将数据复制到驱动程序数据存储。为此,您需要使用非空的“数据”参数调用 vaCreateBuffer。在这种情况下,在服务器端的数据存储中分配一个内存空间,并将数据从该内存空间复制。这是它在提供的示例代码中的使用方式:

static VAPictureParameterBufferMPEG2 pic_param={
	horizontal_size:16,
	vertical_size:16,
	forward_reference_picture:0xffffffff,
	backward_reference_picture:0xffffffff,
	picture_coding_type:1,
	f_code:0xffff,
	{
		{
			intra_dc_precision:0,
			picture_structure:3,
			top_field_first:0,
			frame_pred_frame_dct:1,
			concealment_motion_vectors:0,
			q_scale_type:0,
			intra_vlc_format:0,
			alternate_scan:0,
			repeat_first_field:0,
			progressive_frame:1 ,
			is_first_field:1
		},
	}
};

/* buff的ID将通过vaPicParamBuf返回 */
vaStatus = vaCreateBuffer(vaDisplay, vaContext,
							VAPictureParameterBufferType,
							sizeof(VAPictureParameterBufferMPEG2),
							1, &pic_param,&vaPicParamBuf);

如果您使用空的“数据”参数调用它,则会创建缓冲区对象,但不会在数据存储中分配内存空间。通过调用 vaMapBuffer(),客户端可以访问数据存储中的缓冲区地址空间。这可以防止将数据从客户端内存复制到服务器地址空间。然后客户端可以用数据填充缓冲区。在缓冲区填满数据之后,在实际传输到虚拟管道之前,必须调用 vaUnmapBuffer() 取消映射。在这里找到一个代码示例:

/* Create a picture parameter buffer for this frame */
VABufferID picture_buf;
VAPictureParameterBufferMPEG2 *picture_param;
vaCreateBuffer(dpy, context, 
				VAPictureParameterBufferType,
				sizeof(VAPictureParameterBufferMPEG2), 
				1, NULL, &picture_buf);
				
vaMapBuffer(dpy, picture_buf, &picture_param);
picture_param->horizontal_size = 720;
picture_param->vertical_size = 480;
picture_param->picture_coding_type = 1;
/* I-frame */
vaUnmapBuffer(dpy, picture_buf);

2、发送解码参数和比特流

对于解码帧,我们首先需要发送流参数:逆量化矩阵缓冲区、图片参数缓冲区、切片缓冲区参数或给定格式所需的其他数据结构。然后可以将数据流发送到虚拟管道。这些数据是使用前一章中描述的数据传输机制传递的。通过 vaRenderPicture 调用数据传输。

对于要渲染的每一帧,您需要经过 vaBeginPicture/vaRenderPicture/vaEndPicture 序列。在这个序列中,一旦设置了必要的参数,如逆量化矩阵或图片参数缓冲区或取决于格式所需的任何其他参数,就可以将数据流发送到驱动程序进行解码。由于 vaRenderPicture 调用,解码缓冲区被发送到虚拟管道。当所有与帧相关的数据都发送完毕后,vaEndPicture() 调用结束对图片的渲染。这是一个非阻塞调用,因此客户端可以在硬件解码已提交的当前帧时启动另一个

vaBeginPicture/vaRenderPicture/vaEndPicture 序列。vaPutSurface 调用会将解码输出表面发送到 X 可绘制对象。它执行去隔行(如果需要)颜色空间转换并缩放到目标矩形。在此处找到描述解码序列的代码示例。

vaBeginPicture(vaDisplay, vaContext, vaSurface);
vaStatus = vaRenderPicture(vaDisplay,vaContext, &vaPicParamBuf, 1);
vaStatus = vaRenderPicture(vaDisplay,vaContext, &vaIQMatrixBuf, 1);
vaStatus = vaRenderPicture(vaDisplay,vaContext, &vaSliceParamBuf, 1);
vaStatus = vaRenderPicture(vaDisplay,vaContext, &vaSliceDataBuf, 1);
vaEndPicture(vaDisplay,vaContext);
vaStatus = vaSyncSurface(vaDisplay, vaContext, vaSurface);
if(putsurface) {
	win = XCreateSimpleWindow(x11_display, RootWindow(x11_display, 0), 0, 0,
	win_width,win_height, 0, 0, WhitePixel(x11_display, 0));
	XMapWindow(x11_display, win);
	XSync(x11_display, True);
	
	vaStatus = vaPutSurface(vaDisplay, vaSurface, win,
							0,0,surf_width,surf_height,
							0,0,win_width,win_height,
							NULL,0,0);
}

前面一种方法是通过Buffer来传递流数据,也可以通过Imag输入流数据,Image的使用方法于Buff类似,通过vaCreateImage创建一个Image,通过vaMapBuffer映射Image到用户空间,vaPutImage将Image数据发送到解码管道,参考下面代码:

int hwenc_upload_surface(VAAPIContext *ctx, YUV_Surf *surf, unsigned char *buf)
{
    VAImageFormat format;
    VAImage image;
    void *surface_p = NULL;
    unsigned char *y_src, *v_src;
    unsigned char *y_dst, *v_dst;

    format.fourcc = VA_FOURCC_NV12;
    vas = vaCreateImage(ctx->va_dpy, &format, surf->width, surf->height,
                        &image);
                        
    vas = vaMapBuffer(ctx->va_dpy, image.buf, &surface_p);

    y_size = image.width * image.height;
    y_dst = (unsigned char *)((unsigned char *)surface_p + image.offsets[0]);
    v_dst = (unsigned char *)surface_p + image.offsets[1];
    y_src = buf;
    v_src = buf + y_size;

    /* Y plane copy */
    for (int i = 0; i < image.height; i++) {
        memcpy(y_dst, y_src, image.width);
        y_src += image.pitches[0];
        y_dst += image.width;
    }

    /* UV plane copy */
    for (int i = 0; i < image.height / 2; i++) {
        memcpy(v_dst, v_src, image.width);
        v_dst += image.width;
        v_src += image.pitches[1];
    }

    vaUnmapBuffer(ctx->va_dpy, image.buf);
    vas = vaPutImage(ctx->va_dpy, surf->id, image.image_id,
                     0, 0, surf->width, surf->height,
                     0, 0, surf->width, surf->height);
    vaDestroyImage(ctx->va_dpy, image.image_id);

    return 1;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值