vulkan-playground/vaapi-recorder.cpp

1536 lines
46 KiB
C++

/*
* Copyright (c) 2012 Intel Corporation. All Rights Reserved.
* Copyright © 2013 Intel Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <va/va.h>
#include <va/va_drm.h>
#include <va/va_drmcommon.h>
#include <va/va_enc_h264.h>
#include <va/va_vpp.h>
#define NAL_REF_IDC_NONE 0
#define NAL_REF_IDC_LOW 1
#define NAL_REF_IDC_MEDIUM 2
#define NAL_REF_IDC_HIGH 3
#define NAL_NON_IDR 1
#define NAL_IDR 5
#define NAL_SPS 7
#define NAL_PPS 8
#define NAL_SEI 6
#define SLICE_TYPE_P 0
#define SLICE_TYPE_B 1
#define SLICE_TYPE_I 2
#define SLICE_TYPE_P_ONLY 5
#define IS_P_SLICE(type) (SLICE_TYPE_P == (type) || SLICE_TYPE_P_ONLY == (type))
#define IS_B_SLICE(type) (SLICE_TYPE_B == (type))
#define IS_I_SLICE(type) (SLICE_TYPE_I == (type))
#define ENTROPY_MODE_CAVLC 0
#define ENTROPY_MODE_CABAC 1
#define PROFILE_IDC_BASELINE 66
#define PROFILE_IDC_MAIN 77
#define PROFILE_IDC_HIGH 100
struct vaapi_recorder {
int drm_fd, output_fd;
int width, height;
int frame_count;
int error;
int destroying;
pthread_t worker_thread;
pthread_mutex_t mutex;
pthread_cond_t input_cond;
struct {
int valid;
int prime_fd, stride;
} input;
VADisplay va_dpy;
/* video post processing is used for colorspace conversion */
struct {
VAConfigID cfg;
VAContextID ctx;
VABufferID pipeline_buf;
VASurfaceID output;
} vpp;
struct {
VAConfigID cfg;
VAContextID ctx;
VASurfaceID reference_picture[3];
int intra_period;
int output_size;
int constraint_set_flag;
struct {
VAEncSequenceParameterBufferH264 seq;
VAEncPictureParameterBufferH264 pic;
VAEncSliceParameterBufferH264 slice;
} param;
} encoder;
};
/*
* {surface = 4, surface_region = 0x7fffe8003e38, surface_color_standard = VAProcColorStandardNone, output_region = 0x0,
output_background_color = 4278190080, output_color_standard = VAProcColorStandardNone, pipeline_flags = 0, filter_flags = 512, filters = 0x0,
num_filters = 0, forward_references = 0x0, num_forward_references = 0, backward_references = 0x0, num_backward_references = 0, rotation_state = 0,
blend_state = 0x0, mirror_state = 0, additional_outputs = 0x0, num_additional_outputs = 0, input_surface_flag = 0, output_surface_flag = 0,
input_color_properties = {chroma_sample_location = 0 '\000', color_range = 1 '\001', colour_primaries = 2 '\002',
transfer_characteristics = 2 '\002', matrix_coefficients = 0 '\000', reserved = "\000\000"}, output_color_properties = {
chroma_sample_location = 0 '\000', color_range = 1 '\001', colour_primaries = 2 '\002', transfer_characteristics = 2 '\002',
matrix_coefficients = 2 '\002', reserved = "\000\000"}, processing_mode = VAProcDefaultMode, output_hdr_metadata = 0x0, va_reserved = {
0 <repeats 16 times>}}
{surface = 7, surface_region = 0x7fffe8003e38, surface_color_standard = VAProcColorStandardNone, output_region = 0x0,
output_background_color = 4278190080, output_color_standard = VAProcColorStandardNone, pipeline_flags = 0, filter_flags = 512, filters = 0x0,
num_filters = 0, forward_references = 0x0, num_forward_references = 0, backward_references = 0x0, num_backward_references = 0, rotation_state = 0,
blend_state = 0x0, mirror_state = 0, additional_outputs = 0x0, num_additional_outputs = 0, input_surface_flag = 0, output_surface_flag = 0,
input_color_properties = {chroma_sample_location = 0 '\000', color_range = 1 '\001', colour_primaries = 2 '\002',
transfer_characteristics = 2 '\002', matrix_coefficients = 0 '\000', reserved = "\000\000"}, output_color_properties = {
chroma_sample_location = 0 '\000', color_range = 1 '\001', colour_primaries = 2 '\002', transfer_characteristics = 2 '\002',
matrix_coefficients = 2 '\002', reserved = "\000\000"}, processing_mode = VAProcDefaultMode, output_hdr_metadata = 0x0, va_reserved = {
0 <repeats 16 times>}}
* SPS
* {seq_parameter_set_id = 0 '\000', level_idc = 60 '<', intra_period = 32767, intra_idr_period = 32767, ip_period = 1, bits_per_second = 0,
max_num_ref_frames = 1, picture_width_in_mbs = 200, picture_height_in_mbs = 138, seq_fields = {bits = {chroma_format_idc = 1,
frame_mbs_only_flag = 1, mb_adaptive_frame_field_flag = 0, seq_scaling_matrix_present_flag = 0, direct_8x8_inference_flag = 1,
log2_max_frame_num_minus4 = 4, pic_order_cnt_type = 2, log2_max_pic_order_cnt_lsb_minus4 = 0, delta_pic_order_always_zero_flag = 0},
value = 2341}, bit_depth_luma_minus8 = 0 '\000', bit_depth_chroma_minus8 = 0 '\000', num_ref_frames_in_pic_order_cnt_cycle = 0 '\000',
offset_for_non_ref_pic = 0, offset_for_top_to_bottom_field = 0, offset_for_ref_frame = {0 <repeats 256 times>}, frame_cropping_flag = 1 '\001',
frame_crop_left_offset = 0, frame_crop_right_offset = 0, frame_crop_top_offset = 0, frame_crop_bottom_offset = 4,
vui_parameters_present_flag = 1 '\001', vui_fields = {bits = {aspect_ratio_info_present_flag = 0, timing_info_present_flag = 1,
bitstream_restriction_flag = 1, log2_max_mv_length_horizontal = 15, log2_max_mv_length_vertical = 15, fixed_frame_rate_flag = 0,
low_delay_hrd_flag = 0, motion_vectors_over_pic_boundaries_flag = 0, reserved = 0}, value = 3966}, aspect_ratio_idc = 0 '\000', sar_width = 0,
sar_height = 0, num_units_in_tick = 1, time_scale = 180, va_reserved = {0, 0, 0, 0}}
* PPS
* {CurrPic = {picture_id = 5, frame_idx = 0, flags = 0, TopFieldOrderCnt = 0, BottomFieldOrderCnt = 0, va_reserved = {0, 0, 0, 0}},
ReferenceFrames = {{picture_id = 4294967295, frame_idx = 0, flags = 1, TopFieldOrderCnt = 0, BottomFieldOrderCnt = 0, va_reserved = {0, 0, 0,
0}} <repeats 16 times>}, coded_buf = 9, pic_parameter_set_id = 0 '\000', seq_parameter_set_id = 0 '\000', last_picture = 0 '\000',
frame_num = 0, pic_init_qp = 20 '\024', num_ref_idx_l0_active_minus1 = 0 '\000', num_ref_idx_l1_active_minus1 = 0 '\000',
chroma_qp_index_offset = 0 '\000', second_chroma_qp_index_offset = 0 '\000', pic_fields = {bits = {idr_pic_flag = 1, reference_pic_flag = 1,
entropy_coding_mode_flag = 1, weighted_pred_flag = 0, weighted_bipred_idc = 0, constrained_intra_pred_flag = 0, transform_8x8_mode_flag = 1,
deblocking_filter_control_present_flag = 0, redundant_pic_cnt_present_flag = 0, pic_order_present_flag = 0,
pic_scaling_matrix_present_flag = 0}, value = 267}, va_reserved = {0, 0, 0, 0}}
* I
* {macroblock_address = 0, num_macroblocks = 27600, macroblock_info = 4294967295, slice_type = 2 '\002', pic_parameter_set_id = 0 '\000',
idr_pic_id = 0, pic_order_cnt_lsb = 0, delta_pic_order_cnt_bottom = 0, delta_pic_order_cnt = {0, 0}, direct_spatial_mv_pred_flag = 1 '\001',
num_ref_idx_active_override_flag = 0 '\000', num_ref_idx_l0_active_minus1 = 0 '\000', num_ref_idx_l1_active_minus1 = 0 '\000', RefPicList0 = {{
picture_id = 4294967295, frame_idx = 0, flags = 1, TopFieldOrderCnt = 0, BottomFieldOrderCnt = 0, va_reserved = {0, 0, 0,
0}} <repeats 32 times>}, RefPicList1 = {{picture_id = 4294967295, frame_idx = 0, flags = 1, TopFieldOrderCnt = 0, BottomFieldOrderCnt = 0,
va_reserved = {0, 0, 0, 0}} <repeats 32 times>}, luma_log2_weight_denom = 0 '\000', chroma_log2_weight_denom = 0 '\000',
luma_weight_l0_flag = 0 '\000', luma_weight_l0 = {0 <repeats 32 times>}, luma_offset_l0 = {0 <repeats 32 times>}, chroma_weight_l0_flag = 0 '\000',
chroma_weight_l0 = {{0, 0} <repeats 32 times>}, chroma_offset_l0 = {{0, 0} <repeats 32 times>}, luma_weight_l1_flag = 0 '\000', luma_weight_l1 = {
0 <repeats 32 times>}, luma_offset_l1 = {0 <repeats 32 times>}, chroma_weight_l1_flag = 0 '\000', chroma_weight_l1 = {{0, 0} <repeats 32 times>},
chroma_offset_l1 = {{0, 0} <repeats 32 times>}, cabac_init_idc = 0 '\000', slice_qp_delta = 0 '\000', disable_deblocking_filter_idc = 0 '\000',
slice_alpha_c0_offset_div2 = 0 '\000', slice_beta_offset_div2 = 0 '\000', va_reserved = {0, 0, 0, 0}}
{CurrPic = {picture_id = 1, frame_idx = 1, flags = 0, TopFieldOrderCnt = 2, BottomFieldOrderCnt = 2, va_reserved = {0, 0, 0, 0}},
ReferenceFrames = {{picture_id = 5, frame_idx = 0, flags = 8, TopFieldOrderCnt = 0, BottomFieldOrderCnt = 0, va_reserved = {0, 0, 0, 0}}, {
picture_id = 4294967295, frame_idx = 0, flags = 1, TopFieldOrderCnt = 0, BottomFieldOrderCnt = 0, va_reserved = {0, 0, 0,
0}} <repeats 15 times>}, coded_buf = 3, pic_parameter_set_id = 0 '\000', seq_parameter_set_id = 0 '\000', last_picture = 0 '\000',
frame_num = 1, pic_init_qp = 20 '\024', num_ref_idx_l0_active_minus1 = 0 '\000', num_ref_idx_l1_active_minus1 = 0 '\000',
chroma_qp_index_offset = 0 '\000', second_chroma_qp_index_offset = 0 '\000', pic_fields = {bits = {idr_pic_flag = 0, reference_pic_flag = 1,
entropy_coding_mode_flag = 1, weighted_pred_flag = 0, weighted_bipred_idc = 0, constrained_intra_pred_flag = 0, transform_8x8_mode_flag = 1,
deblocking_filter_control_present_flag = 0, redundant_pic_cnt_present_flag = 0, pic_order_present_flag = 0,
pic_scaling_matrix_present_flag = 0}, value = 266}, va_reserved = {0, 0, 0, 0}}
*P
{macroblock_address = 0, num_macroblocks = 27600, macroblock_info = 4294967295, slice_type = 0 '\000', pic_parameter_set_id = 0 '\000',
idr_pic_id = 0, pic_order_cnt_lsb = 2, delta_pic_order_cnt_bottom = 0, delta_pic_order_cnt = {0, 0}, direct_spatial_mv_pred_flag = 1 '\001',
num_ref_idx_active_override_flag = 0 '\000', num_ref_idx_l0_active_minus1 = 0 '\000', num_ref_idx_l1_active_minus1 = 0 '\000', RefPicList0 = {{
picture_id = 5, frame_idx = 0, flags = 8, TopFieldOrderCnt = 0, BottomFieldOrderCnt = 0, va_reserved = {0, 0, 0, 0}}, {picture_id = 4294967295,
frame_idx = 0, flags = 1, TopFieldOrderCnt = 0, BottomFieldOrderCnt = 0, va_reserved = {0, 0, 0, 0}} <repeats 31 times>}, RefPicList1 = {{
picture_id = 4294967295, frame_idx = 0, flags = 1, TopFieldOrderCnt = 0, BottomFieldOrderCnt = 0, va_reserved = {0, 0, 0,
0}} <repeats 32 times>}, luma_log2_weight_denom = 0 '\000', chroma_log2_weight_denom = 0 '\000', luma_weight_l0_flag = 0 '\000',
luma_weight_l0 = {0 <repeats 32 times>}, luma_offset_l0 = {0 <repeats 32 times>}, chroma_weight_l0_flag = 0 '\000', chroma_weight_l0 = {{0,
0} <repeats 32 times>}, chroma_offset_l0 = {{0, 0} <repeats 32 times>}, luma_weight_l1_flag = 0 '\000', luma_weight_l1 = {
0 <repeats 32 times>}, luma_offset_l1 = {0 <repeats 32 times>}, chroma_weight_l1_flag = 0 '\000', chroma_weight_l1 = {{0, 0} <repeats 32 times>},
chroma_offset_l1 = {{0, 0} <repeats 32 times>}, cabac_init_idc = 0 '\000', slice_qp_delta = 0 '\000', disable_deblocking_filter_idc = 0 '\000',
slice_alpha_c0_offset_div2 = 0 '\000', slice_beta_offset_div2 = 0 '\000', va_reserved = {0, 0, 0, 0}}
*/
static void *
worker_thread_function(void *);
/* bitstream code used for writing the packed headers */
#define BITSTREAM_ALLOCATE_STEPPING 4096
struct bitstream {
unsigned int *buffer;
int bit_offset;
int max_size_in_dword;
};
static unsigned int
va_swap32(unsigned int val)
{
unsigned char *pval = (unsigned char *)&val;
return ((pval[0] << 24) |
(pval[1] << 16) |
(pval[2] << 8) |
(pval[3] << 0));
}
static void
bitstream_start(struct bitstream *bs)
{
bs->max_size_in_dword = BITSTREAM_ALLOCATE_STEPPING;
bs->buffer = (unsigned int*)calloc(bs->max_size_in_dword * sizeof(unsigned int), 1);
bs->bit_offset = 0;
}
static void
bitstream_end(struct bitstream *bs)
{
int pos = (bs->bit_offset >> 5);
int bit_offset = (bs->bit_offset & 0x1f);
int bit_left = 32 - bit_offset;
if (bit_offset) {
bs->buffer[pos] = va_swap32((bs->buffer[pos] << bit_left));
}
}
static void
bitstream_put_ui(struct bitstream *bs, unsigned int val, int size_in_bits)
{
int pos = (bs->bit_offset >> 5);
int bit_offset = (bs->bit_offset & 0x1f);
int bit_left = 32 - bit_offset;
if (!size_in_bits)
return;
bs->bit_offset += size_in_bits;
if (bit_left > size_in_bits) {
bs->buffer[pos] = (bs->buffer[pos] << size_in_bits | val);
} else {
size_in_bits -= bit_left;
bs->buffer[pos] = (bs->buffer[pos] << bit_left) | (val >> size_in_bits);
bs->buffer[pos] = va_swap32(bs->buffer[pos]);
if (pos + 1 == bs->max_size_in_dword) {
bs->max_size_in_dword += BITSTREAM_ALLOCATE_STEPPING;
bs->buffer = (unsigned int*)realloc(bs->buffer, bs->max_size_in_dword * sizeof(unsigned int));
assert(bs->buffer);
}
bs->buffer[pos + 1] = val;
}
}
static void
bitstream_put_ue(struct bitstream *bs, unsigned int val)
{
int size_in_bits = 0;
int tmp_val = ++val;
while (tmp_val) {
tmp_val >>= 1;
size_in_bits++;
}
bitstream_put_ui(bs, 0, size_in_bits - 1); /* leading zero */
bitstream_put_ui(bs, val, size_in_bits);
}
static void
bitstream_put_se(struct bitstream *bs, int val)
{
unsigned int new_val;
if (val <= 0)
new_val = -2 * val;
else
new_val = 2 * val - 1;
bitstream_put_ue(bs, new_val);
}
static void
bitstream_byte_aligning(struct bitstream *bs, int bit)
{
int bit_offset = (bs->bit_offset & 0x7);
int bit_left = 8 - bit_offset;
int new_val;
if (!bit_offset)
return;
if (bit)
new_val = (1 << bit_left) - 1;
else
new_val = 0;
bitstream_put_ui(bs, new_val, bit_left);
}
static VAStatus
encoder_create_config(struct vaapi_recorder *r)
{
VAConfigAttrib attrib[2];
VAStatus status;
/* FIXME: should check if VAEntrypointEncSlice is supported */
/* FIXME: should check if specified attributes are supported */
attrib[0].type = VAConfigAttribRTFormat;
attrib[0].value = VA_RT_FORMAT_YUV420;
attrib[1].type = VAConfigAttribRateControl;
attrib[1].value = VA_RC_CQP;
status = vaCreateConfig(r->va_dpy, VAProfileH264Main,
VAEntrypointEncSlice, attrib, 2,
&r->encoder.cfg);
if (status != VA_STATUS_SUCCESS)
return status;
VASurfaceID ctx_surfaces[4];
memcpy(ctx_surfaces, r->encoder.reference_picture, sizeof(VASurfaceID) * 3);
ctx_surfaces[3] = r->vpp.output;
status = vaCreateContext(r->va_dpy, r->encoder.cfg,
r->width, r->height, VA_PROGRESSIVE, ctx_surfaces, 4,
&r->encoder.ctx);
if (status != VA_STATUS_SUCCESS) {
vaDestroyConfig(r->va_dpy, r->encoder.cfg);
return status;
}
return VA_STATUS_SUCCESS;
}
static void
encoder_destroy_config(struct vaapi_recorder *r)
{
vaDestroyContext(r->va_dpy, r->encoder.ctx);
vaDestroyConfig(r->va_dpy, r->encoder.cfg);
}
static void
encoder_init_seq_parameters(struct vaapi_recorder *r)
{
int width_in_mbs, height_in_mbs;
int frame_cropping_flag = 0;
int frame_crop_bottom_offset = 0;
width_in_mbs = (r->width + 15) / 16;
height_in_mbs = (r->height + 15) / 16;
r->encoder.param.seq.level_idc = 60;
r->encoder.param.seq.intra_period = 32767; //r->encoder.intra_period;
r->encoder.param.seq.intra_idr_period = 32767;
r->encoder.param.seq.max_num_ref_frames = 1;
r->encoder.param.seq.picture_width_in_mbs = width_in_mbs;
r->encoder.param.seq.picture_height_in_mbs = height_in_mbs;
r->encoder.param.seq.seq_fields.bits.chroma_format_idc = 1;
r->encoder.param.seq.seq_fields.bits.frame_mbs_only_flag = 1;
r->encoder.param.seq.ip_period = 1;
/* Tc = num_units_in_tick / time_scale */
r->encoder.param.seq.time_scale = 180;
r->encoder.param.seq.num_units_in_tick = 15;
if (height_in_mbs * 16 - r->height > 0) {
frame_cropping_flag = 1;
frame_crop_bottom_offset = (height_in_mbs * 16 - r->height) / 2;
}
r->encoder.param.seq.frame_cropping_flag = frame_cropping_flag;
r->encoder.param.seq.frame_crop_bottom_offset = frame_crop_bottom_offset;
r->encoder.param.seq.seq_fields.bits.log2_max_pic_order_cnt_lsb_minus4 = 0;//12;
r->encoder.param.seq.seq_fields.bits.log2_max_frame_num_minus4 = 4;
r->encoder.param.seq.seq_fields.bits.pic_order_cnt_type = 2;
}
static VABufferID
encoder_update_seq_parameters(struct vaapi_recorder *r)
{
VABufferID seq_buf;
VAStatus status;
status = vaCreateBuffer(r->va_dpy, r->encoder.ctx,
VAEncSequenceParameterBufferType,
sizeof(r->encoder.param.seq),
1, &r->encoder.param.seq,
&seq_buf);
if (status == VA_STATUS_SUCCESS)
return seq_buf;
else
{
printf("%s: %d\n", __PRETTY_FUNCTION__, status);
return VA_INVALID_ID;
}
}
static void
encoder_init_pic_parameters(struct vaapi_recorder *r)
{
VAEncPictureParameterBufferH264 *pic = &r->encoder.param.pic;
pic->pic_init_qp = 11;
/* ENTROPY_MODE_CABAC */
pic->pic_fields.bits.entropy_coding_mode_flag = 1;
pic->pic_fields.bits.deblocking_filter_control_present_flag = 0;
}
static VABufferID
encoder_update_pic_parameters(struct vaapi_recorder *r,
VABufferID output_buf)
{
VAEncPictureParameterBufferH264 *pic = &r->encoder.param.pic;
VAStatus status;
VABufferID pic_param_buf;
VASurfaceID curr_pic, pic0;
curr_pic = r->encoder.reference_picture[r->frame_count % 2];
pic0 = r->encoder.reference_picture[(r->frame_count + 1) % 2];
pic->CurrPic.picture_id = curr_pic;
pic->CurrPic.frame_idx = r->frame_count;
pic->CurrPic.TopFieldOrderCnt = r->encoder.param.seq.seq_fields.bits.pic_order_cnt_type == 0? r->frame_count:0;
for(int i = 0; i < 16; i++)
{
pic->ReferenceFrames[i].picture_id = VA_INVALID_ID;
pic->ReferenceFrames[i].flags = VA_PICTURE_H264_INVALID;
pic->ReferenceFrames[i].frame_idx = 0;
}
if(r->frame_count)
{
pic->ReferenceFrames[0].picture_id = pic0;
pic->ReferenceFrames[0].flags = VA_PICTURE_H264_SHORT_TERM_REFERENCE;
pic->ReferenceFrames[0].frame_idx = r->frame_count - 1;
}
//pic->ReferenceFrames[1].picture_id = VA_INVALID_ID; // TODOr->frame_count > 1?r->encoder.reference_picture[2]:VA_INVALID_ID;
//pic->ReferenceFrames[2].picture_id = VA_INVALID_ID;
pic->coded_buf = output_buf;
pic->frame_num = r->frame_count;
pic->pic_fields.bits.idr_pic_flag = (r->frame_count == 0);
pic->pic_fields.bits.reference_pic_flag = 1;
status = vaCreateBuffer(r->va_dpy, r->encoder.ctx,
VAEncPictureParameterBufferType,
sizeof(VAEncPictureParameterBufferH264), 1,
pic, &pic_param_buf);
if (status == VA_STATUS_SUCCESS)
return pic_param_buf;
else
{
printf("%s: %d\n", __PRETTY_FUNCTION__, status);
return VA_INVALID_ID;
}
}
static VABufferID
encoder_update_slice_parameter(struct vaapi_recorder *r, int slice_type)
{
VABufferID slice_param_buf;
VAStatus status;
int width_in_mbs = (r->width + 15) / 16;
int height_in_mbs = (r->height + 15) / 16;
memset(&r->encoder.param.slice, 0, sizeof r->encoder.param.slice);
r->encoder.param.slice.num_macroblocks = width_in_mbs * height_in_mbs;
r->encoder.param.slice.macroblock_info = VA_INVALID_ID;
r->encoder.param.slice.slice_type = slice_type;
r->encoder.param.slice.direct_spatial_mv_pred_flag = 1;
for(int i = 0; i < 32; i++)
{
r->encoder.param.slice.RefPicList0[i].picture_id = VA_INVALID_ID;
r->encoder.param.slice.RefPicList0[i].flags = VA_PICTURE_H264_INVALID;
r->encoder.param.slice.RefPicList0[i].frame_idx = 0;
}
for(int i = 0; i < 32; i++)
{
r->encoder.param.slice.RefPicList1[i].picture_id = VA_INVALID_ID;
r->encoder.param.slice.RefPicList1[i].flags = VA_PICTURE_H264_INVALID;
r->encoder.param.slice.RefPicList1[i].frame_idx = 0;
}
if(r->frame_count)
{
r->encoder.param.slice.RefPicList0[0].frame_idx = r->frame_count - 1;
r->encoder.param.slice.RefPicList0[0].picture_id = r->encoder.reference_picture[(r->frame_count - 1)% 2];
r->encoder.param.slice.RefPicList0[0].flags = VA_PICTURE_H264_SHORT_TERM_REFERENCE;
}
r->encoder.param.slice.pic_order_cnt_lsb = r->encoder.param.seq.seq_fields.bits.pic_order_cnt_type == 0? r->frame_count: 0;
//r->encoder.param.slice.RefPicList0
//r->encoder.param.slice.slice_alpha_c0_offset_div2 = 2;
//r->encoder.param.slice.slice_beta_offset_div2 = 2;
status = vaCreateBuffer(r->va_dpy, r->encoder.ctx,
VAEncSliceParameterBufferType,
sizeof(r->encoder.param.slice), 1,
&r->encoder.param.slice,
&slice_param_buf);
if (status == VA_STATUS_SUCCESS)
return slice_param_buf;
else
{
printf("%s: %d\n", __PRETTY_FUNCTION__, status);
return VA_INVALID_ID;
}
}
static VABufferID
encoder_update_misc_hdr_parameter(struct vaapi_recorder *r)
{
VAEncMiscParameterBuffer *misc_param;
VAEncMiscParameterHRD *hrd;
VABufferID buffer;
VAStatus status;
int total_size =
sizeof(VAEncMiscParameterBuffer) +
sizeof(VAEncMiscParameterRateControl);
status = vaCreateBuffer(r->va_dpy, r->encoder.ctx,
VAEncMiscParameterBufferType, total_size,
1, NULL, &buffer);
if (status != VA_STATUS_SUCCESS)
return VA_INVALID_ID;
status = vaMapBuffer(r->va_dpy, buffer, (void **) &misc_param);
if (status != VA_STATUS_SUCCESS) {
vaDestroyBuffer(r->va_dpy, buffer);
return VA_INVALID_ID;
}
misc_param->type = VAEncMiscParameterTypeHRD;
hrd = (VAEncMiscParameterHRD *) misc_param->data;
hrd->initial_buffer_fullness = 0;
hrd->buffer_size = 0;
vaUnmapBuffer(r->va_dpy, buffer);
return buffer;
}
static int
setup_encoder(struct vaapi_recorder *r)
{
VAStatus status;
status = vaCreateSurfaces(r->va_dpy, VA_RT_FORMAT_YUV420,
r->width, r->height,
r->encoder.reference_picture, 3,
NULL, 0);
status = encoder_create_config(r);
if (status != VA_STATUS_SUCCESS) {
return -1;
}
if (status != VA_STATUS_SUCCESS) {
encoder_destroy_config(r);
return -1;
}
/* VAProfileH264Main */
r->encoder.constraint_set_flag |= (1 << 1); /* Annex A.2.2 */
r->encoder.output_size = r->width * r->height;
r->encoder.intra_period = 32767;
encoder_init_seq_parameters(r);
encoder_init_pic_parameters(r);
return 0;
}
static void
encoder_destroy(struct vaapi_recorder *r)
{
vaDestroySurfaces(r->va_dpy, r->encoder.reference_picture, 3);
encoder_destroy_config(r);
}
static void
nal_start_code_prefix(struct bitstream *bs)
{
bitstream_put_ui(bs, 0x00000001, 32);
}
static void
nal_header(struct bitstream *bs, int nal_ref_idc, int nal_unit_type)
{
/* forbidden_zero_bit: 0 */
bitstream_put_ui(bs, 0, 1);
bitstream_put_ui(bs, nal_ref_idc, 2);
bitstream_put_ui(bs, nal_unit_type, 5);
}
static void
rbsp_trailing_bits(struct bitstream *bs)
{
bitstream_put_ui(bs, 1, 1);
bitstream_byte_aligning(bs, 0);
}
static void sps_rbsp(struct bitstream *bs,
VAEncSequenceParameterBufferH264 *seq,
int constraint_set_flag)
{
int i;
bitstream_put_ui(bs, PROFILE_IDC_BASELINE, 8);
/* constraint_set[0-3] flag */
for (i = 0; i < 4; i++) {
int set = (constraint_set_flag & (1 << i)) ? 1 : 0;
bitstream_put_ui(bs, set, 1);
}
/* reserved_zero_4bits */
bitstream_put_ui(bs, 0, 4);
bitstream_put_ui(bs, seq->level_idc, 8);
bitstream_put_ue(bs, seq->seq_parameter_set_id);
bitstream_put_ue(bs, seq->seq_fields.bits.log2_max_frame_num_minus4);
bitstream_put_ue(bs, seq->seq_fields.bits.pic_order_cnt_type);
if(seq->seq_fields.bits.pic_order_cnt_type != 2)
bitstream_put_ue(bs,
seq->seq_fields.bits.log2_max_pic_order_cnt_lsb_minus4);
bitstream_put_ue(bs, seq->max_num_ref_frames);
/* gaps_in_frame_num_value_allowed_flag */
bitstream_put_ui(bs, 0, 1);
/* pic_width_in_mbs_minus1, pic_height_in_map_units_minus1 */
bitstream_put_ue(bs, seq->picture_width_in_mbs - 1);
bitstream_put_ue(bs, seq->picture_height_in_mbs - 1);
bitstream_put_ui(bs, seq->seq_fields.bits.frame_mbs_only_flag, 1);
bitstream_put_ui(bs, seq->seq_fields.bits.direct_8x8_inference_flag, 1);
bitstream_put_ui(bs, seq->frame_cropping_flag, 1);
if (seq->frame_cropping_flag) {
bitstream_put_ue(bs, seq->frame_crop_left_offset);
bitstream_put_ue(bs, seq->frame_crop_right_offset);
bitstream_put_ue(bs, seq->frame_crop_top_offset);
bitstream_put_ue(bs, seq->frame_crop_bottom_offset);
}
/* vui_parameters_present_flag */
bitstream_put_ui(bs, 0, 1);
#if 0
/* aspect_ratio_info_present_flag */
bitstream_put_ui(bs, 0, 1);
/* overscan_info_present_flag */
bitstream_put_ui(bs, 0, 1);
/* video_signal_type_present_flag */
bitstream_put_ui(bs, 0, 1);
/* chroma_loc_info_present_flag */
bitstream_put_ui(bs, 0, 1);
/* timing_info_present_flag */
bitstream_put_ui(bs, 1, 1);
bitstream_put_ui(bs, seq->num_units_in_tick, 32);
bitstream_put_ui(bs, seq->time_scale, 32);
/* fixed_frame_rate_flag */
bitstream_put_ui(bs, 1, 1);
/* nal_hrd_parameters_present_flag */
bitstream_put_ui(bs, 0, 1);
/* vcl_hrd_parameters_present_flag */
bitstream_put_ui(bs, 0, 1);
/* low_delay_hrd_flag */
bitstream_put_ui(bs, 0, 1);
/* pic_struct_present_flag */
bitstream_put_ui(bs, 0, 1);
/* bitstream_restriction_flag */
bitstream_put_ui(bs, 0, 1);
#endif
rbsp_trailing_bits(bs);
}
static void pps_rbsp(struct bitstream *bs,
VAEncPictureParameterBufferH264 *pic)
{
/* pic_parameter_set_id, seq_parameter_set_id */
bitstream_put_ue(bs, pic->pic_parameter_set_id);
bitstream_put_ue(bs, pic->seq_parameter_set_id);
bitstream_put_ui(bs, pic->pic_fields.bits.entropy_coding_mode_flag, 1);
/* pic_order_present_flag: 0 */
bitstream_put_ui(bs, 0, 1);
/* num_slice_groups_minus1 */
bitstream_put_ue(bs, 0);
bitstream_put_ue(bs, pic->num_ref_idx_l0_active_minus1);
bitstream_put_ue(bs, pic->num_ref_idx_l1_active_minus1);
bitstream_put_ui(bs, pic->pic_fields.bits.weighted_pred_flag, 1);
bitstream_put_ui(bs, pic->pic_fields.bits.weighted_bipred_idc, 2);
/* pic_init_qp_minus26, pic_init_qs_minus26, chroma_qp_index_offset */
bitstream_put_se(bs, pic->pic_init_qp - 26);
bitstream_put_se(bs, 0);
bitstream_put_se(bs, 0);
bitstream_put_ui(bs, pic->pic_fields.bits.deblocking_filter_control_present_flag, 1);
/* constrained_intra_pred_flag, redundant_pic_cnt_present_flag */
bitstream_put_ui(bs, 0, 1);
bitstream_put_ui(bs, 0, 1);
bitstream_put_ui(bs, pic->pic_fields.bits.transform_8x8_mode_flag, 1);
/* pic_scaling_matrix_present_flag */
bitstream_put_ui(bs, 0, 1);
bitstream_put_se(bs, pic->second_chroma_qp_index_offset );
rbsp_trailing_bits(bs);
}
static void slice_header(bitstream *bs, VAEncSequenceParameterBufferH264 *seq,VAEncPictureParameterBufferH264 *pic, VAEncSliceParameterBufferH264 *slice )
{
int first_mb_in_slice = slice->macroblock_address;
bitstream_put_ue(bs, first_mb_in_slice); /* first_mb_in_slice: 0 */
bitstream_put_ue(bs, slice->slice_type); /* slice_type */
bitstream_put_ue(bs, slice->pic_parameter_set_id); /* pic_parameter_set_id: 0 */
bitstream_put_ui(bs, pic->frame_num, seq->seq_fields.bits.log2_max_frame_num_minus4 + 4); /* frame_num */
/* frame_mbs_only_flag == 1 */
if (!seq->seq_fields.bits.frame_mbs_only_flag) {
/* FIXME: */
assert(0);
}
if (pic->pic_fields.bits.idr_pic_flag)
bitstream_put_ue(bs, slice->idr_pic_id); /* idr_pic_id: 0 */
if (seq->seq_fields.bits.pic_order_cnt_type == 0) {
bitstream_put_ui(bs, pic->CurrPic.TopFieldOrderCnt, seq->seq_fields.bits.log2_max_pic_order_cnt_lsb_minus4 + 4);
/* pic_order_present_flag == 0 */
} else {
/* FIXME: */
// assert(0);
}
/* redundant_pic_cnt_present_flag == 0 */
/* slice type */
if (IS_P_SLICE(slice->slice_type)) {
bitstream_put_ui(bs, slice->num_ref_idx_active_override_flag, 1); /* num_ref_idx_active_override_flag: */
if (slice->num_ref_idx_active_override_flag)
bitstream_put_ue(bs, slice->num_ref_idx_l0_active_minus1);
/* ref_pic_list_reordering */
bitstream_put_ui(bs, 0, 1); /* ref_pic_list_reordering_flag_l0: 0 */
} else if (IS_B_SLICE(slice->slice_type)) {
bitstream_put_ui(bs, slice->direct_spatial_mv_pred_flag, 1); /* direct_spatial_mv_pred: 1 */
bitstream_put_ui(bs, slice->num_ref_idx_active_override_flag, 1); /* num_ref_idx_active_override_flag: */
if (slice->num_ref_idx_active_override_flag) {
bitstream_put_ue(bs, slice->num_ref_idx_l0_active_minus1);
bitstream_put_ue(bs, slice->num_ref_idx_l1_active_minus1);
}
/* ref_pic_list_reordering */
bitstream_put_ui(bs, 0, 1); /* ref_pic_list_reordering_flag_l0: 0 */
bitstream_put_ui(bs, 0, 1); /* ref_pic_list_reordering_flag_l1: 0 */
}
if ((pic->pic_fields.bits.weighted_pred_flag &&
IS_P_SLICE(slice->slice_type)) ||
((pic->pic_fields.bits.weighted_bipred_idc == 1) &&
IS_B_SLICE(slice->slice_type))) {
/* FIXME: fill weight/offset table */
assert(0);
}
/* dec_ref_pic_marking */
if (pic->pic_fields.bits.reference_pic_flag) { /* nal_ref_idc != 0 */
unsigned char no_output_of_prior_pics_flag = 0;
unsigned char long_term_reference_flag = 0;
unsigned char adaptive_ref_pic_marking_mode_flag = 0;
if (pic->pic_fields.bits.idr_pic_flag) {
bitstream_put_ui(bs, no_output_of_prior_pics_flag, 1); /* no_output_of_prior_pics_flag: 0 */
bitstream_put_ui(bs, long_term_reference_flag, 1); /* long_term_reference_flag: 0 */
} else {
bitstream_put_ui(bs, adaptive_ref_pic_marking_mode_flag, 1); /* adaptive_ref_pic_marking_mode_flag: 0 */
}
}
if (pic->pic_fields.bits.entropy_coding_mode_flag &&
!IS_I_SLICE(slice->slice_type))
bitstream_put_ue(bs, slice->cabac_init_idc); /* cabac_init_idc: 0 */
bitstream_put_se(bs, slice->slice_qp_delta); /* slice_qp_delta: 0 */
/* ignore for SP/SI */
if (pic->pic_fields.bits.deblocking_filter_control_present_flag) {
bitstream_put_ue(bs, slice->disable_deblocking_filter_idc); /* disable_deblocking_filter_idc: 0 */
if (slice->disable_deblocking_filter_idc != 1) {
bitstream_put_se(bs, slice->slice_alpha_c0_offset_div2); /* slice_alpha_c0_offset_div2: 2 */
bitstream_put_se(bs, slice->slice_beta_offset_div2); /* slice_beta_offset_div2: 2 */
}
}
if (pic->pic_fields.bits.entropy_coding_mode_flag) {
bitstream_byte_aligning(bs, 1);
}
}
static int
build_packed_pic_buffer(struct vaapi_recorder *r,
void **header_buffer)
{
struct bitstream bs;
bitstream_start(&bs);
nal_start_code_prefix(&bs);
nal_header(&bs, NAL_REF_IDC_HIGH, NAL_PPS);
pps_rbsp(&bs, &r->encoder.param.pic);
bitstream_end(&bs);
*header_buffer = bs.buffer;
return bs.bit_offset;
}
static int
build_packed_seq_buffer(struct vaapi_recorder *r,
void **header_buffer)
{
struct bitstream bs;
bitstream_start(&bs);
nal_start_code_prefix(&bs);
nal_header(&bs, NAL_REF_IDC_HIGH, NAL_SPS);
sps_rbsp(&bs, &r->encoder.param.seq, r->encoder.constraint_set_flag);
bitstream_end(&bs);
*header_buffer = bs.buffer;
return bs.bit_offset;
}
static int
create_packed_header_buffers(struct vaapi_recorder *r, VABufferID *buffers,
VAEncPackedHeaderType type,
void *data, int bit_length)
{
VAEncPackedHeaderParameterBuffer packed_header;
VAStatus status;
packed_header.type = type;
packed_header.bit_length = bit_length;
packed_header.has_emulation_bytes = 0;
status = vaCreateBuffer(r->va_dpy, r->encoder.ctx,
VAEncPackedHeaderParameterBufferType,
sizeof packed_header, 1, &packed_header,
&buffers[0]);
if (status != VA_STATUS_SUCCESS)
{
printf("%s: %d\n", __PRETTY_FUNCTION__, status);
return 0;
}
status = vaCreateBuffer(r->va_dpy, r->encoder.ctx,
VAEncPackedHeaderDataBufferType,
(bit_length + 7) / 8, 1, data, &buffers[1]);
if (status != VA_STATUS_SUCCESS) {
printf("%s: %d\n", __PRETTY_FUNCTION__, status);
vaDestroyBuffer(r->va_dpy, buffers[0]);
return 0;
}
return 2;
}
static int build_packed_slice_buffer(unsigned char **header_buffer, VAEncSequenceParameterBufferH264 *seq,VAEncPictureParameterBufferH264 *pic, VAEncSliceParameterBufferH264 *slice )
{
bitstream bs;
int is_idr = !!pic->pic_fields.bits.idr_pic_flag;
int is_ref = !!pic->pic_fields.bits.reference_pic_flag;
bitstream_start(&bs);
nal_start_code_prefix(&bs);
if (IS_I_SLICE(slice->slice_type)) {
nal_header(&bs, NAL_REF_IDC_HIGH, is_idr ? NAL_IDR : NAL_NON_IDR);
} else if (IS_P_SLICE(slice->slice_type)) {
nal_header(&bs, NAL_REF_IDC_MEDIUM, NAL_NON_IDR);
} else {
assert(IS_B_SLICE(slice->slice_type));
nal_header(&bs, is_ref ? NAL_REF_IDC_LOW : NAL_REF_IDC_NONE, NAL_NON_IDR);
}
slice_header(&bs, seq, pic, slice );
bitstream_end(&bs);
*header_buffer = (unsigned char *)bs.buffer;
return bs.bit_offset;
}
static int
encoder_prepare_headers(struct vaapi_recorder *r, VABufferID *buffers)
{
VABufferID *p;
int bit_length;
void *data;
p = buffers;
bit_length = build_packed_seq_buffer(r, &data);
p += create_packed_header_buffers(r, p, VAEncPackedHeaderSequence,
data, bit_length);
free(data);
bit_length = build_packed_pic_buffer(r, &data);
p += create_packed_header_buffers(r, p, VAEncPackedHeaderPicture,
data, bit_length);
free(data);
return p - buffers;
}
static VAStatus
encoder_render_picture(struct vaapi_recorder *r, VASurfaceID input,
VABufferID *buffers, int count)
{
}
static VABufferID
encoder_create_output_buffer(struct vaapi_recorder *r)
{
VABufferID output_buf;
VAStatus status;
status = vaCreateBuffer(r->va_dpy, r->encoder.ctx,
VAEncCodedBufferType, r->encoder.output_size,
1, NULL, &output_buf);
if (status == VA_STATUS_SUCCESS)
return output_buf;
else
return VA_INVALID_ID;
}
enum output_write_status {
OUTPUT_WRITE_SUCCESS,
OUTPUT_WRITE_OVERFLOW,
OUTPUT_WRITE_FATAL
};
static enum output_write_status
encoder_write_output(struct vaapi_recorder *r, VABufferID output_buf)
{
VACodedBufferSegment *segment;
VAStatus status;
int count = 0;
status = vaMapBuffer(r->va_dpy, output_buf, (void **) &segment);
if (status != VA_STATUS_SUCCESS)
return OUTPUT_WRITE_FATAL;
if (segment->status & VA_CODED_BUF_STATUS_SLICE_OVERFLOW_MASK) {
r->encoder.output_size *= 2;
vaUnmapBuffer(r->va_dpy, output_buf);
return OUTPUT_WRITE_OVERFLOW;
}
do
{
count += write(r->output_fd, segment->buf, segment->size);
segment = (VACodedBufferSegment *)segment->next;
}
while(segment);
vaUnmapBuffer(r->va_dpy, output_buf);
if (count < 0)
return OUTPUT_WRITE_FATAL;
return OUTPUT_WRITE_SUCCESS;
}
static void
encoder_encode(struct vaapi_recorder *r, VASurfaceID input)
{
VABufferID output_buf = VA_INVALID_ID;
VABufferID buffers[12];
int count = 0;
int i, slice_type;
enum output_write_status ret;
if ((r->frame_count % r->encoder.intra_period) == 0)
slice_type = SLICE_TYPE_I;
else
slice_type = SLICE_TYPE_P;
if (r->frame_count == 0)
buffers[count++] = encoder_update_seq_parameters(r);
//buffers[count++] = encoder_update_misc_hdr_parameter(r);
VABufferID slice_buf = encoder_update_slice_parameter(r, slice_type);
for (i = 0; i < count; i++)
if (buffers[i] == VA_INVALID_ID)
goto bail;
VAStatus status;
status = vaBeginPicture(r->va_dpy, r->encoder.ctx, input);
if (status != VA_STATUS_SUCCESS)
goto bail;
//status = vaRenderPicture(r->va_dpy, r->encoder.ctx, &buffers[0], 1);
if (status != VA_STATUS_SUCCESS)
goto bail;
//status = vaRenderPicture(r->va_dpy, r->encoder.ctx, &buffers[2], 1);
do {
output_buf = encoder_create_output_buffer(r);
if (output_buf == VA_INVALID_ID)
goto bail;
buffers[count++] =
encoder_update_pic_parameters(r, output_buf);
if (buffers[count - 1] == VA_INVALID_ID)
goto bail;
if (r->frame_count == 0)
count += encoder_prepare_headers(r, buffers + count);
// todo: this might be required in every frame
//if(r->frame_count == 0)
{
void *data;
uint32_t bit_length = build_packed_slice_buffer((unsigned char **)&data, &r->encoder.param.seq, &r->encoder.param.pic, &r->encoder.param.slice );
count += create_packed_header_buffers(r, buffers + count, VAEncPackedHeaderSlice,
data, bit_length);
free(data);
}
// its important to push slice after packed slice
// otherwise intel encoder will crash on packed slice encoding
// or produce invalid slice header
buffers[count++] = slice_buf;
status = vaRenderPicture(r->va_dpy, r->encoder.ctx, buffers, count);
/*VAStatus er = encoder_render_picture(r, input, buffers, count);
if(er != VA_STATUS_SUCCESS)
{
printf("render error %d\n", er);
goto bail;
}*/
status = vaEndPicture(r->va_dpy, r->encoder.ctx);
if (status != VA_STATUS_SUCCESS)
goto bail;
status = vaSyncSurface(r->va_dpy, input);
ret = encoder_write_output(r, output_buf);
vaDestroyBuffer(r->va_dpy, output_buf);
output_buf = VA_INVALID_ID;
vaDestroyBuffer(r->va_dpy, buffers[--count]);
if(ret == OUTPUT_WRITE_OVERFLOW)
exit(1);
} while (ret == OUTPUT_WRITE_OVERFLOW);
if (ret == OUTPUT_WRITE_FATAL)
r->error = errno;
for (i = 0; i < count; i++)
vaDestroyBuffer(r->va_dpy, buffers[i]);
r->frame_count++;
return;
bail:
printf("buffer errors?\n");
for (i = 0; i < count; i++)
vaDestroyBuffer(r->va_dpy, buffers[i]);
if (output_buf != VA_INVALID_ID)
vaDestroyBuffer(r->va_dpy, output_buf);
}
static int
setup_vpp(struct vaapi_recorder *r)
{
VAStatus status;
status = vaCreateConfig(r->va_dpy, VAProfileNone,
VAEntrypointVideoProc, NULL, 0,
&r->vpp.cfg);
if (status != VA_STATUS_SUCCESS) {
printf("vaapi: failed to create VPP config\n");
return -1;
}
status = vaCreateContext(r->va_dpy, r->vpp.cfg, r->width, r->height,
0, NULL, 0, &r->vpp.ctx);
if (status != VA_STATUS_SUCCESS) {
printf("vaapi: failed to create VPP context\n");
goto err_cfg;
}
status = vaCreateBuffer(r->va_dpy, r->vpp.ctx,
VAProcPipelineParameterBufferType,
sizeof(VAProcPipelineParameterBuffer),
1, NULL, &r->vpp.pipeline_buf);
if (status != VA_STATUS_SUCCESS) {
printf("vaapi: failed to create VPP pipeline buffer\n");
goto err_ctx;
}
status = vaCreateSurfaces(r->va_dpy, VA_RT_FORMAT_YUV420,
r->width, r->height, &r->vpp.output, 1,
NULL, 0);
if (status != VA_STATUS_SUCCESS) {
printf("vaapi: failed to create YUV surface\n");
goto err_buf;
}
return 0;
err_buf:
vaDestroyBuffer(r->va_dpy, r->vpp.pipeline_buf);
err_ctx:
vaDestroyConfig(r->va_dpy, r->vpp.ctx);
err_cfg:
vaDestroyConfig(r->va_dpy, r->vpp.cfg);
return -1;
}
static void
vpp_destroy(struct vaapi_recorder *r)
{
vaDestroySurfaces(r->va_dpy, &r->vpp.output, 1);
vaDestroyBuffer(r->va_dpy, r->vpp.pipeline_buf);
vaDestroyConfig(r->va_dpy, r->vpp.ctx);
vaDestroyConfig(r->va_dpy, r->vpp.cfg);
}
static int
setup_worker_thread(struct vaapi_recorder *r)
{
pthread_mutex_init(&r->mutex, NULL);
pthread_cond_init(&r->input_cond, NULL);
pthread_create(&r->worker_thread, NULL, worker_thread_function, r);
return 1;
}
static void
destroy_worker_thread(struct vaapi_recorder *r)
{
pthread_mutex_lock(&r->mutex);
/* Make sure the worker thread finishes */
r->destroying = 1;
pthread_cond_signal(&r->input_cond);
pthread_mutex_unlock(&r->mutex);
pthread_join(r->worker_thread, NULL);
pthread_mutex_destroy(&r->mutex);
pthread_cond_destroy(&r->input_cond);
}
struct vaapi_recorder *
vaapi_recorder_create(int drm_fd, int width, int height, const char *filename)
{
struct vaapi_recorder *r;
VAStatus status;
int major, minor;
int flags;
r = (vaapi_recorder*)calloc(sizeof *r,1);
if (r == NULL)
return NULL;
r->width = width;
r->height = height;
r->drm_fd = drm_fd;
if (setup_worker_thread(r) < 0)
goto err_free;
flags = O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC;
r->output_fd = open(filename, flags, 0644);
if (r->output_fd < 0)
goto err_thread;
r->va_dpy = vaGetDisplayDRM(drm_fd);
if (!r->va_dpy) {
printf("failed to create VA display\n");
goto err_fd;
}
status = vaInitialize(r->va_dpy, &major, &minor);
if (status != VA_STATUS_SUCCESS) {
printf("vaapi: failed to initialize display\n");
goto err_fd;
}
if (setup_vpp(r) < 0) {
printf("vaapi: failed to initialize VPP pipeline\n");
goto err_va_dpy;
}
if (setup_encoder(r) < 0) {
goto err_vpp;
}
return r;
err_vpp:
vpp_destroy(r);
err_va_dpy:
vaTerminate(r->va_dpy);
err_fd:
close(r->output_fd);
err_thread:
destroy_worker_thread(r);
err_free:
free(r);
return NULL;
}
static VASurfaceID gInputRGBA;
static VAStatus
create_surface_from_fd(struct vaapi_recorder *r, int prime_fd,
int stride, VASurfaceID *surface);
struct vaapi_recorder *
vaapi_recorder_create2(int drm_fd, int width, int height, const char *filename, int dmabuf_fd, int dmabuf_stride)
{
struct vaapi_recorder *r;
VAStatus status;
int major, minor;
int flags;
r = (vaapi_recorder*)calloc(sizeof *r,1);
if (r == NULL)
return NULL;
r->width = width;
r->height = height;
r->drm_fd = drm_fd;
flags = O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC;
r->output_fd = open(filename, flags, 0644);
if (r->output_fd < 0)
goto err_thread;
r->va_dpy = vaGetDisplayDRM(drm_fd);
if (!r->va_dpy) {
printf("failed to create VA display\n");
goto err_fd;
}
status = vaInitialize(r->va_dpy, &major, &minor);
if (status != VA_STATUS_SUCCESS) {
printf("vaapi: failed to initialize display\n");
goto err_fd;
}
if (setup_vpp(r) < 0) {
printf("vaapi: failed to initialize VPP pipeline\n");
goto err_va_dpy;
}
if (setup_encoder(r) < 0) {
goto err_vpp;
}
create_surface_from_fd(r, dmabuf_fd, dmabuf_stride, &gInputRGBA);
return r;
err_vpp:
vpp_destroy(r);
err_va_dpy:
vaTerminate(r->va_dpy);
err_fd:
close(r->output_fd);
err_thread:
destroy_worker_thread(r);
err_free:
free(r);
return NULL;
}
void
vaapi_recorder_destroy(struct vaapi_recorder *r)
{
destroy_worker_thread(r);
encoder_destroy(r);
vpp_destroy(r);
vaTerminate(r->va_dpy);
close(r->output_fd);
close(r->drm_fd);
free(r);
}
static VAStatus
create_surface_from_fd(struct vaapi_recorder *r, int prime_fd,
int stride, VASurfaceID *surface)
{
VASurfaceAttrib va_attribs[2];
VASurfaceAttribExternalBuffers va_attrib_extbuf;
VAStatus status;
unsigned long buffer_fd = prime_fd;
va_attrib_extbuf.pixel_format = VA_FOURCC_BGRX;
va_attrib_extbuf.width = r->width;
va_attrib_extbuf.height = r->height;
va_attrib_extbuf.data_size = r->height * stride;
va_attrib_extbuf.num_planes = 1;
va_attrib_extbuf.pitches[0] = stride;
va_attrib_extbuf.offsets[0] = 0;
va_attrib_extbuf.buffers = &buffer_fd;
va_attrib_extbuf.num_buffers = 1;
va_attrib_extbuf.flags = 0;
va_attrib_extbuf.private_data = NULL;
va_attribs[0].type = VASurfaceAttribMemoryType;
va_attribs[0].flags = VA_SURFACE_ATTRIB_SETTABLE;
va_attribs[0].value.type = VAGenericValueTypeInteger;
va_attribs[0].value.value.i = VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME;
va_attribs[1].type = VASurfaceAttribExternalBufferDescriptor;
va_attribs[1].flags = VA_SURFACE_ATTRIB_SETTABLE;
va_attribs[1].value.type = VAGenericValueTypePointer;
va_attribs[1].value.value.p = &va_attrib_extbuf;
status = vaCreateSurfaces(r->va_dpy, VA_RT_FORMAT_RGB32,
r->width, r->height, surface, 1,
va_attribs, 2);
return status;
}
static VAStatus
convert_rgb_to_yuv(struct vaapi_recorder *r, VASurfaceID rgb_surface)
{
VAProcPipelineParameterBuffer *pipeline_param;
VAStatus status;
status = vaMapBuffer(r->va_dpy, r->vpp.pipeline_buf,
(void **) &pipeline_param);
if (status != VA_STATUS_SUCCESS)
return status;
memset(pipeline_param, 0, sizeof *pipeline_param);
pipeline_param->surface = rgb_surface;
pipeline_param->surface_color_standard = VAProcColorStandardNone;
pipeline_param->output_background_color = 0xff000000;
pipeline_param->output_color_standard = VAProcColorStandardNone;
status = vaUnmapBuffer(r->va_dpy, r->vpp.pipeline_buf);
if (status != VA_STATUS_SUCCESS)
return status;
status = vaBeginPicture(r->va_dpy, r->vpp.ctx, r->vpp.output);
if (status != VA_STATUS_SUCCESS)
return status;
status = vaRenderPicture(r->va_dpy, r->vpp.ctx,
&r->vpp.pipeline_buf, 1);
if (status != VA_STATUS_SUCCESS)
return status;
status = vaEndPicture(r->va_dpy, r->vpp.ctx);
if (status != VA_STATUS_SUCCESS)
return status;
return status;
}
static void
recorder_frame(struct vaapi_recorder *r)
{
VASurfaceID rgb_surface;
VAStatus status;
status = create_surface_from_fd(r, r->input.prime_fd,
r->input.stride, &rgb_surface);
if (status != VA_STATUS_SUCCESS) {
printf("[libva recorder] "
"failed to create surface from bo\n");
return;
}
close(r->input.prime_fd);
status = convert_rgb_to_yuv(r, rgb_surface);
if (status != VA_STATUS_SUCCESS) {
printf("[libva recorder] "
"color space conversion failed\n");
return;
}
encoder_encode(r, r->vpp.output);
vaDestroySurfaces(r->va_dpy, &rgb_surface, 1);
}
void
recorder_frame2(struct vaapi_recorder *r)
{
VAStatus status;
status = convert_rgb_to_yuv(r, gInputRGBA);
if (status != VA_STATUS_SUCCESS) {
printf("[libva recorder] "
"color space conversion failed\n");
return;
}
encoder_encode(r, r->vpp.output);
}
static void *
worker_thread_function(void *data)
{
struct vaapi_recorder *r = (vaapi_recorder*)data;
pthread_mutex_lock(&r->mutex);
while (!r->destroying) {
if (!r->input.valid)
pthread_cond_wait(&r->input_cond, &r->mutex);
/* If the thread is awaken by destroy_worker_thread(),
* there might not be valid input */
if (!r->input.valid)
continue;
recorder_frame(r);
r->input.valid = 0;
}
pthread_mutex_unlock(&r->mutex);
return NULL;
}
int
vaapi_recorder_frame(struct vaapi_recorder *r, int prime_fd, int stride)
{
int ret = 0;
pthread_mutex_lock(&r->mutex);
if (r->error) {
errno = r->error;
ret = -1;
goto unlock;
}
/* The mutex is never released while encoding, so this point should
* never be reached if input.valid is true. */
assert(!r->input.valid);
r->input.prime_fd = prime_fd;
r->input.stride = stride;
r->input.valid = 1;
pthread_cond_signal(&r->input_cond);
unlock:
pthread_mutex_unlock(&r->mutex);
return ret;
}