blob: de90d430740950226ac6c3d786d8b3af5c68d29a [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/gpu/vaapi/vaapi_h264_accelerator.h"
#include <va/va.h>
#include "media/gpu/decode_surface_handler.h"
#include "media/gpu/h264_dpb.h"
#include "media/gpu/vaapi/vaapi_common.h"
#include "media/gpu/vaapi/vaapi_wrapper.h"
#define ARRAY_MEMCPY_CHECKED(to, from) \
do { \
static_assert(sizeof(to) == sizeof(from), \
#from " and " #to " arrays must be of same size"); \
memcpy(to, from, sizeof(to)); \
} while (0)
namespace media {
using Status = H264Decoder::H264Accelerator::Status;
namespace {
// from ITU-T REC H.264 spec
// section 8.5.6
// "Inverse scanning process for 4x4 transform coefficients and scaling lists"
static constexpr int kZigzagScan4x4[16] = {0, 1, 4, 8, 5, 2, 3, 6,
9, 12, 13, 10, 7, 11, 14, 15};
// section 8.5.7
// "Inverse scanning process for 8x8 transform coefficients and scaling lists"
static constexpr uint8_t kZigzagScan8x8[64] = {
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5,
12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28,
35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51,
58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63};
} // namespace
VaapiH264Accelerator::VaapiH264Accelerator(
DecodeSurfaceHandler<VASurface>* vaapi_dec,
scoped_refptr<VaapiWrapper> vaapi_wrapper)
: vaapi_wrapper_(vaapi_wrapper), vaapi_dec_(vaapi_dec) {
DCHECK(vaapi_wrapper_);
DCHECK(vaapi_dec_);
DETACH_FROM_SEQUENCE(sequence_checker_);
}
VaapiH264Accelerator::~VaapiH264Accelerator() {
// TODO(mcasas): consider enabling the checker, https://crbug.com/789160
// DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
scoped_refptr<H264Picture> VaapiH264Accelerator::CreateH264Picture() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const auto va_surface = vaapi_dec_->CreateSurface();
if (!va_surface)
return nullptr;
return new VaapiH264Picture(std::move(va_surface));
}
// Fill |va_pic| with default/neutral values.
static void InitVAPicture(VAPictureH264* va_pic) {
memset(va_pic, 0, sizeof(*va_pic));
va_pic->picture_id = VA_INVALID_ID;
va_pic->flags = VA_PICTURE_H264_INVALID;
}
Status VaapiH264Accelerator::SubmitFrameMetadata(
const H264SPS* sps,
const H264PPS* pps,
const H264DPB& dpb,
const H264Picture::Vector& ref_pic_listp0,
const H264Picture::Vector& ref_pic_listb0,
const H264Picture::Vector& ref_pic_listb1,
const scoped_refptr<H264Picture>& pic) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
VAPictureParameterBufferH264 pic_param;
memset(&pic_param, 0, sizeof(pic_param));
#define FROM_SPS_TO_PP(a) pic_param.a = sps->a
#define FROM_SPS_TO_PP2(a, b) pic_param.b = sps->a
FROM_SPS_TO_PP2(pic_width_in_mbs_minus1, picture_width_in_mbs_minus1);
// This assumes non-interlaced video
FROM_SPS_TO_PP2(pic_height_in_map_units_minus1, picture_height_in_mbs_minus1);
FROM_SPS_TO_PP(bit_depth_luma_minus8);
FROM_SPS_TO_PP(bit_depth_chroma_minus8);
#undef FROM_SPS_TO_PP
#undef FROM_SPS_TO_PP2
#define FROM_SPS_TO_PP_SF(a) pic_param.seq_fields.bits.a = sps->a
#define FROM_SPS_TO_PP_SF2(a, b) pic_param.seq_fields.bits.b = sps->a
FROM_SPS_TO_PP_SF(chroma_format_idc);
FROM_SPS_TO_PP_SF2(separate_colour_plane_flag,
residual_colour_transform_flag);
FROM_SPS_TO_PP_SF(gaps_in_frame_num_value_allowed_flag);
FROM_SPS_TO_PP_SF(frame_mbs_only_flag);
FROM_SPS_TO_PP_SF(mb_adaptive_frame_field_flag);
FROM_SPS_TO_PP_SF(direct_8x8_inference_flag);
pic_param.seq_fields.bits.MinLumaBiPredSize8x8 = (sps->level_idc >= 31);
FROM_SPS_TO_PP_SF(log2_max_frame_num_minus4);
FROM_SPS_TO_PP_SF(pic_order_cnt_type);
FROM_SPS_TO_PP_SF(log2_max_pic_order_cnt_lsb_minus4);
FROM_SPS_TO_PP_SF(delta_pic_order_always_zero_flag);
#undef FROM_SPS_TO_PP_SF
#undef FROM_SPS_TO_PP_SF2
#define FROM_PPS_TO_PP(a) pic_param.a = pps->a
FROM_PPS_TO_PP(pic_init_qp_minus26);
FROM_PPS_TO_PP(pic_init_qs_minus26);
FROM_PPS_TO_PP(chroma_qp_index_offset);
FROM_PPS_TO_PP(second_chroma_qp_index_offset);
#undef FROM_PPS_TO_PP
#define FROM_PPS_TO_PP_PF(a) pic_param.pic_fields.bits.a = pps->a
#define FROM_PPS_TO_PP_PF2(a, b) pic_param.pic_fields.bits.b = pps->a
FROM_PPS_TO_PP_PF(entropy_coding_mode_flag);
FROM_PPS_TO_PP_PF(weighted_pred_flag);
FROM_PPS_TO_PP_PF(weighted_bipred_idc);
FROM_PPS_TO_PP_PF(transform_8x8_mode_flag);
pic_param.pic_fields.bits.field_pic_flag = 0;
FROM_PPS_TO_PP_PF(constrained_intra_pred_flag);
FROM_PPS_TO_PP_PF2(bottom_field_pic_order_in_frame_present_flag,
pic_order_present_flag);
FROM_PPS_TO_PP_PF(deblocking_filter_control_present_flag);
FROM_PPS_TO_PP_PF(redundant_pic_cnt_present_flag);
pic_param.pic_fields.bits.reference_pic_flag = pic->ref;
#undef FROM_PPS_TO_PP_PF
#undef FROM_PPS_TO_PP_PF2
pic_param.frame_num = pic->frame_num;
InitVAPicture(&pic_param.CurrPic);
FillVAPicture(&pic_param.CurrPic, pic);
// Init reference pictures' array.
for (int i = 0; i < 16; ++i)
InitVAPicture(&pic_param.ReferenceFrames[i]);
// And fill it with picture info from DPB.
FillVARefFramesFromDPB(dpb, pic_param.ReferenceFrames,
arraysize(pic_param.ReferenceFrames));
pic_param.num_ref_frames = sps->max_num_ref_frames;
if (!vaapi_wrapper_->SubmitBuffer(VAPictureParameterBufferType, &pic_param))
return Status::kFail;
VAIQMatrixBufferH264 iq_matrix_buf;
memset(&iq_matrix_buf, 0, sizeof(iq_matrix_buf));
if (pps->pic_scaling_matrix_present_flag) {
for (int i = 0; i < 6; ++i) {
for (int j = 0; j < 16; ++j)
iq_matrix_buf.ScalingList4x4[i][kZigzagScan4x4[j]] =
pps->scaling_list4x4[i][j];
}
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 64; ++j)
iq_matrix_buf.ScalingList8x8[i][kZigzagScan8x8[j]] =
pps->scaling_list8x8[i][j];
}
} else {
for (int i = 0; i < 6; ++i) {
for (int j = 0; j < 16; ++j)
iq_matrix_buf.ScalingList4x4[i][kZigzagScan4x4[j]] =
sps->scaling_list4x4[i][j];
}
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 64; ++j)
iq_matrix_buf.ScalingList8x8[i][kZigzagScan8x8[j]] =
sps->scaling_list8x8[i][j];
}
}
return vaapi_wrapper_->SubmitBuffer(VAIQMatrixBufferType, &iq_matrix_buf)
? Status::kOk
: Status::kFail;
}
Status VaapiH264Accelerator::SubmitSlice(
const H264PPS* pps,
const H264SliceHeader* slice_hdr,
const H264Picture::Vector& ref_pic_list0,
const H264Picture::Vector& ref_pic_list1,
const scoped_refptr<H264Picture>& pic,
const uint8_t* data,
size_t size,
const std::vector<SubsampleEntry>& subsamples) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
VASliceParameterBufferH264 slice_param;
memset(&slice_param, 0, sizeof(slice_param));
slice_param.slice_data_size = slice_hdr->nalu_size;
slice_param.slice_data_offset = 0;
slice_param.slice_data_flag = VA_SLICE_DATA_FLAG_ALL;
slice_param.slice_data_bit_offset = slice_hdr->header_bit_size;
#define SHDRToSP(a) slice_param.a = slice_hdr->a
SHDRToSP(first_mb_in_slice);
slice_param.slice_type = slice_hdr->slice_type % 5;
SHDRToSP(direct_spatial_mv_pred_flag);
// TODO posciak: make sure parser sets those even when override flags
// in slice header is off.
SHDRToSP(num_ref_idx_l0_active_minus1);
SHDRToSP(num_ref_idx_l1_active_minus1);
SHDRToSP(cabac_init_idc);
SHDRToSP(slice_qp_delta);
SHDRToSP(disable_deblocking_filter_idc);
SHDRToSP(slice_alpha_c0_offset_div2);
SHDRToSP(slice_beta_offset_div2);
if (((slice_hdr->IsPSlice() || slice_hdr->IsSPSlice()) &&
pps->weighted_pred_flag) ||
(slice_hdr->IsBSlice() && pps->weighted_bipred_idc == 1)) {
SHDRToSP(luma_log2_weight_denom);
SHDRToSP(chroma_log2_weight_denom);
SHDRToSP(luma_weight_l0_flag);
SHDRToSP(luma_weight_l1_flag);
SHDRToSP(chroma_weight_l0_flag);
SHDRToSP(chroma_weight_l1_flag);
for (int i = 0; i <= slice_param.num_ref_idx_l0_active_minus1; ++i) {
slice_param.luma_weight_l0[i] =
slice_hdr->pred_weight_table_l0.luma_weight[i];
slice_param.luma_offset_l0[i] =
slice_hdr->pred_weight_table_l0.luma_offset[i];
for (int j = 0; j < 2; ++j) {
slice_param.chroma_weight_l0[i][j] =
slice_hdr->pred_weight_table_l0.chroma_weight[i][j];
slice_param.chroma_offset_l0[i][j] =
slice_hdr->pred_weight_table_l0.chroma_offset[i][j];
}
}
if (slice_hdr->IsBSlice()) {
for (int i = 0; i <= slice_param.num_ref_idx_l1_active_minus1; ++i) {
slice_param.luma_weight_l1[i] =
slice_hdr->pred_weight_table_l1.luma_weight[i];
slice_param.luma_offset_l1[i] =
slice_hdr->pred_weight_table_l1.luma_offset[i];
for (int j = 0; j < 2; ++j) {
slice_param.chroma_weight_l1[i][j] =
slice_hdr->pred_weight_table_l1.chroma_weight[i][j];
slice_param.chroma_offset_l1[i][j] =
slice_hdr->pred_weight_table_l1.chroma_offset[i][j];
}
}
}
}
static_assert(
arraysize(slice_param.RefPicList0) == arraysize(slice_param.RefPicList1),
"Invalid RefPicList sizes");
for (size_t i = 0; i < arraysize(slice_param.RefPicList0); ++i) {
InitVAPicture(&slice_param.RefPicList0[i]);
InitVAPicture(&slice_param.RefPicList1[i]);
}
for (size_t i = 0;
i < ref_pic_list0.size() && i < arraysize(slice_param.RefPicList0);
++i) {
if (ref_pic_list0[i])
FillVAPicture(&slice_param.RefPicList0[i], ref_pic_list0[i]);
}
for (size_t i = 0;
i < ref_pic_list1.size() && i < arraysize(slice_param.RefPicList1);
++i) {
if (ref_pic_list1[i])
FillVAPicture(&slice_param.RefPicList1[i], ref_pic_list1[i]);
}
if (!vaapi_wrapper_->SubmitBuffer(VASliceParameterBufferType, &slice_param))
return Status::kFail;
return vaapi_wrapper_->SubmitBuffer(VASliceDataBufferType, size, data)
? Status::kOk
: Status::kFail;
}
Status VaapiH264Accelerator::SubmitDecode(
const scoped_refptr<H264Picture>& pic) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const bool success = vaapi_wrapper_->ExecuteAndDestroyPendingBuffers(
pic->AsVaapiH264Picture()->va_surface()->id());
return success ? Status::kOk : Status::kFail;
}
bool VaapiH264Accelerator::OutputPicture(
const scoped_refptr<H264Picture>& pic) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const VaapiH264Picture* vaapi_pic = pic->AsVaapiH264Picture();
vaapi_dec_->SurfaceReady(vaapi_pic->va_surface(), vaapi_pic->bitstream_id(),
vaapi_pic->visible_rect(),
vaapi_pic->get_colorspace());
return true;
}
void VaapiH264Accelerator::Reset() {
DETACH_FROM_SEQUENCE(sequence_checker_);
vaapi_wrapper_->DestroyPendingBuffers();
}
void VaapiH264Accelerator::FillVAPicture(VAPictureH264* va_pic,
scoped_refptr<H264Picture> pic) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
VASurfaceID va_surface_id = VA_INVALID_SURFACE;
if (!pic->nonexisting)
va_surface_id = pic->AsVaapiH264Picture()->va_surface()->id();
va_pic->picture_id = va_surface_id;
va_pic->frame_idx = pic->frame_num;
va_pic->flags = 0;
switch (pic->field) {
case H264Picture::FIELD_NONE:
break;
case H264Picture::FIELD_TOP:
va_pic->flags |= VA_PICTURE_H264_TOP_FIELD;
break;
case H264Picture::FIELD_BOTTOM:
va_pic->flags |= VA_PICTURE_H264_BOTTOM_FIELD;
break;
}
if (pic->ref) {
va_pic->flags |= pic->long_term ? VA_PICTURE_H264_LONG_TERM_REFERENCE
: VA_PICTURE_H264_SHORT_TERM_REFERENCE;
}
va_pic->TopFieldOrderCnt = pic->top_field_order_cnt;
va_pic->BottomFieldOrderCnt = pic->bottom_field_order_cnt;
}
int VaapiH264Accelerator::FillVARefFramesFromDPB(const H264DPB& dpb,
VAPictureH264* va_pics,
int num_pics) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
H264Picture::Vector::const_reverse_iterator rit;
int i;
// Return reference frames in reverse order of insertion.
// Libva does not document this, but other implementations (e.g. mplayer)
// do it this way as well.
for (rit = dpb.rbegin(), i = 0; rit != dpb.rend() && i < num_pics; ++rit) {
if ((*rit)->ref)
FillVAPicture(&va_pics[i++], *rit);
}
return i;
}
} // namespace media