参考:link
流程
初始化阶段,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;
}