weston/libweston/vaapi-recorder.c
Pekka Paalanen 3d5d9476e3 Rename compositor.h to libweston/libweston.h
The main idea is to make libweston users use the form

 #include <libweston/libweston.h>

instead of the plain

 #include <compositor.h>

which is prone to name conflicts. This is reflected both in the installed
files, and the internal header search paths so that Weston would use the exact
same form as an external project using libweston would.

The public headers are moved under a new top-level directory include/ to make
them clearly stand out as special (public API).

Signed-off-by: Pekka Paalanen <pekka.paalanen@collabora.com>
2019-04-18 12:31:46 +03:00

1162 lines
27 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 "config.h"
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <errno.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>
#include <libweston/libweston.h>
#include "vaapi-recorder.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 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;
};
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 = 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);
return;
}
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 =
realloc(bs->buffer,
bs->max_size_in_dword * sizeof(unsigned int));
}
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;
status = vaCreateContext(r->va_dpy, r->encoder.cfg,
r->width, r->height, VA_PROGRESSIVE, 0, 0,
&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 = 41;
r->encoder.param.seq.intra_period = r->encoder.intra_period;
r->encoder.param.seq.max_num_ref_frames = 4;
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.frame_mbs_only_flag = 1;
/* Tc = num_units_in_tick / time_scale */
r->encoder.param.seq.time_scale = 1800;
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 = 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
return VA_INVALID_ID;
}
static void
encoder_init_pic_parameters(struct vaapi_recorder *r)
{
VAEncPictureParameterBufferH264 *pic = &r->encoder.param.pic;
pic->pic_init_qp = 0;
/* ENTROPY_MODE_CABAC */
pic->pic_fields.bits.entropy_coding_mode_flag = 1;
pic->pic_fields.bits.deblocking_filter_control_present_flag = 1;
}
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.TopFieldOrderCnt = r->frame_count * 2;
pic->ReferenceFrames[0].picture_id = pic0;
pic->ReferenceFrames[1].picture_id = r->encoder.reference_picture[2];
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
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.slice_type = slice_type;
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
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 = encoder_create_config(r);
if (status != VA_STATUS_SUCCESS) {
return -1;
}
status = vaCreateSurfaces(r->va_dpy, VA_RT_FORMAT_YUV420,
r->width, r->height,
r->encoder.reference_picture, 3,
NULL, 0);
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 = 30;
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_MAIN, 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);
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, 1, 1);
/* 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);
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 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)
return 0;
status = vaCreateBuffer(r->va_dpy, r->encoder.ctx,
VAEncPackedHeaderDataBufferType,
(bit_length + 7) / 8, 1, data, &buffers[1]);
if (status != VA_STATUS_SUCCESS) {
vaDestroyBuffer(r->va_dpy, buffers[0]);
return 0;
}
return 2;
}
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)
{
VAStatus status;
status = vaBeginPicture(r->va_dpy, r->encoder.ctx, input);
if (status != VA_STATUS_SUCCESS)
return status;
status = vaRenderPicture(r->va_dpy, r->encoder.ctx, buffers, count);
if (status != VA_STATUS_SUCCESS)
return status;
status = vaEndPicture(r->va_dpy, r->encoder.ctx);
if (status != VA_STATUS_SUCCESS)
return status;
return vaSyncSurface(r->va_dpy, input);
}
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;
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;
}
count = write(r->output_fd, segment->buf, segment->size);
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[8];
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;
buffers[count++] = encoder_update_seq_parameters(r);
buffers[count++] = encoder_update_misc_hdr_parameter(r);
buffers[count++] = encoder_update_slice_parameter(r, slice_type);
for (i = 0; i < count; i++)
if (buffers[i] == VA_INVALID_ID)
goto bail;
if (r->frame_count == 0)
count += encoder_prepare_headers(r, buffers + count);
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;
encoder_render_picture(r, input, buffers, count);
ret = encoder_write_output(r, output_buf);
vaDestroyBuffer(r->va_dpy, output_buf);
output_buf = VA_INVALID_ID;
vaDestroyBuffer(r->va_dpy, buffers[--count]);
} 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:
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) {
weston_log("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) {
weston_log("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) {
weston_log("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) {
weston_log("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 = zalloc(sizeof *r);
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) {
weston_log("failed to create VA display\n");
goto err_fd;
}
status = vaInitialize(r->va_dpy, &major, &minor);
if (status != VA_STATUS_SUCCESS) {
weston_log("vaapi: failed to initialize display\n");
goto err_fd;
}
if (setup_vpp(r) < 0) {
weston_log("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;
}
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) {
weston_log("[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) {
weston_log("[libva recorder] "
"color space conversion failed\n");
return;
}
encoder_encode(r, r->vpp.output);
vaDestroySurfaces(r->va_dpy, &rgb_surface, 1);
}
static void *
worker_thread_function(void *data)
{
struct vaapi_recorder *r = 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;
}