blob: c7e2f7f649e4d43785d9dd07c3bf37d42d1f4f8b [file] [log] [blame]
// Copyright 2021 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/test/av1_decoder.h"
#include <va/va.h>
#include <va/va_dec_av1.h>
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/notreached.h"
#include "media/base/video_decoder.h"
#include "media/gpu/macros.h"
#include "media/gpu/vaapi/test/macros.h"
#include "media/gpu/vaapi/test/scoped_va_config.h"
#include "media/gpu/vaapi/test/scoped_va_context.h"
#include "media/gpu/vaapi/test/shared_va_surface.h"
#include "media/gpu/vaapi/test/vaapi_device.h"
#include "third_party/libgav1/src/src/warp_prediction.h"
namespace media {
namespace vaapi_test {
namespace {
#define ARRAY_SIZE(ar) (sizeof(ar) / sizeof(ar[0]))
#define STD_ARRAY_SIZE(ar) (std::tuple_size<decltype(ar)>::value)
void FillSegmentInfo(VASegmentationStructAV1& va_seg_info,
const libgav1::Segmentation& segmentation) {
auto& va_seg_info_fields = va_seg_info.segment_info_fields.bits;
va_seg_info_fields.enabled = segmentation.enabled;
va_seg_info_fields.update_map = segmentation.update_map;
va_seg_info_fields.temporal_update = segmentation.temporal_update;
va_seg_info_fields.update_data = segmentation.update_data;
static_assert(libgav1::kMaxSegments == 8 && libgav1::kSegmentFeatureMax == 8,
"Invalid Segment array size");
static_assert(ARRAY_SIZE(segmentation.feature_data) == 8 &&
ARRAY_SIZE(segmentation.feature_data[0]) == 8 &&
ARRAY_SIZE(segmentation.feature_enabled) == 8 &&
ARRAY_SIZE(segmentation.feature_enabled[0]) == 8,
"Invalid segmentation array size");
static_assert(ARRAY_SIZE(va_seg_info.feature_data) == 8 &&
ARRAY_SIZE(va_seg_info.feature_data[0]) == 8 &&
ARRAY_SIZE(va_seg_info.feature_mask) == 8,
"Invalid feature array size");
for (size_t i = 0; i < libgav1::kMaxSegments; ++i) {
for (size_t j = 0; j < libgav1::kSegmentFeatureMax; ++j)
va_seg_info.feature_data[i][j] = segmentation.feature_data[i][j];
}
for (size_t i = 0; i < libgav1::kMaxSegments; ++i) {
uint8_t feature_mask = 0;
for (size_t j = 0; j < libgav1::kSegmentFeatureMax; ++j) {
if (segmentation.feature_enabled[i][j])
feature_mask |= 1 << j;
}
va_seg_info.feature_mask[i] = feature_mask;
}
}
void FillFilmGrainInfo(VAFilmGrainStructAV1& va_film_grain_info,
const libgav1::FilmGrainParams& film_grain_params) {
if (!film_grain_params.apply_grain)
return;
#define COPY_FILM_GRAIN_FIELD(a) \
va_film_grain_info.film_grain_info_fields.bits.a = film_grain_params.a
COPY_FILM_GRAIN_FIELD(apply_grain);
COPY_FILM_GRAIN_FIELD(chroma_scaling_from_luma);
COPY_FILM_GRAIN_FIELD(grain_scale_shift);
COPY_FILM_GRAIN_FIELD(overlap_flag);
COPY_FILM_GRAIN_FIELD(clip_to_restricted_range);
#undef COPY_FILM_GRAIN_FIELD
va_film_grain_info.film_grain_info_fields.bits.ar_coeff_lag =
film_grain_params.auto_regression_coeff_lag;
DCHECK_GE(film_grain_params.chroma_scaling, 8u);
DCHECK_GE(film_grain_params.auto_regression_shift, 6u);
va_film_grain_info.film_grain_info_fields.bits.grain_scaling_minus_8 =
film_grain_params.chroma_scaling - 8;
va_film_grain_info.film_grain_info_fields.bits.ar_coeff_shift_minus_6 =
film_grain_params.auto_regression_shift - 6;
constexpr size_t kFilmGrainPointYSize = 14;
constexpr size_t kFilmGrainPointUVSize = 10;
static_assert(
ARRAY_SIZE(va_film_grain_info.point_y_value) == kFilmGrainPointYSize &&
ARRAY_SIZE(va_film_grain_info.point_y_scaling) ==
kFilmGrainPointYSize &&
ARRAY_SIZE(va_film_grain_info.point_cb_value) ==
kFilmGrainPointUVSize &&
ARRAY_SIZE(va_film_grain_info.point_cb_scaling) ==
kFilmGrainPointUVSize &&
ARRAY_SIZE(va_film_grain_info.point_cr_value) ==
kFilmGrainPointUVSize &&
ARRAY_SIZE(va_film_grain_info.point_cr_scaling) ==
kFilmGrainPointUVSize &&
ARRAY_SIZE(film_grain_params.point_y_value) == kFilmGrainPointYSize &&
ARRAY_SIZE(film_grain_params.point_y_scaling) ==
kFilmGrainPointYSize &&
ARRAY_SIZE(film_grain_params.point_u_value) ==
kFilmGrainPointUVSize &&
ARRAY_SIZE(film_grain_params.point_u_scaling) ==
kFilmGrainPointUVSize &&
ARRAY_SIZE(film_grain_params.point_v_value) ==
kFilmGrainPointUVSize &&
ARRAY_SIZE(film_grain_params.point_v_scaling) ==
kFilmGrainPointUVSize,
"Invalid array size of film grain values");
DCHECK_LE(film_grain_params.num_y_points, kFilmGrainPointYSize);
DCHECK_LE(film_grain_params.num_u_points, kFilmGrainPointUVSize);
DCHECK_LE(film_grain_params.num_v_points, kFilmGrainPointUVSize);
#define COPY_FILM_GRAIN_FIELD2(a, b) va_film_grain_info.a = film_grain_params.b
#define COPY_FILM_GRAIN_FIELD3(a) COPY_FILM_GRAIN_FIELD2(a, a)
COPY_FILM_GRAIN_FIELD3(grain_seed);
COPY_FILM_GRAIN_FIELD3(num_y_points);
for (uint8_t i = 0; i < film_grain_params.num_y_points; ++i) {
COPY_FILM_GRAIN_FIELD3(point_y_value[i]);
COPY_FILM_GRAIN_FIELD3(point_y_scaling[i]);
}
#undef COPY_FILM_GRAIN_FIELD3
COPY_FILM_GRAIN_FIELD2(num_cb_points, num_u_points);
for (uint8_t i = 0; i < film_grain_params.num_u_points; ++i) {
COPY_FILM_GRAIN_FIELD2(point_cb_value[i], point_u_value[i]);
COPY_FILM_GRAIN_FIELD2(point_cb_scaling[i], point_u_scaling[i]);
}
COPY_FILM_GRAIN_FIELD2(num_cr_points, num_v_points);
for (uint8_t i = 0; i < film_grain_params.num_v_points; ++i) {
COPY_FILM_GRAIN_FIELD2(point_cr_value[i], point_v_value[i]);
COPY_FILM_GRAIN_FIELD2(point_cr_scaling[i], point_v_scaling[i]);
}
constexpr size_t kAutoRegressionCoeffYSize = 24;
constexpr size_t kAutoRegressionCoeffUVSize = 25;
static_assert(
ARRAY_SIZE(va_film_grain_info.ar_coeffs_y) == kAutoRegressionCoeffYSize &&
ARRAY_SIZE(va_film_grain_info.ar_coeffs_cb) ==
kAutoRegressionCoeffUVSize &&
ARRAY_SIZE(va_film_grain_info.ar_coeffs_cr) ==
kAutoRegressionCoeffUVSize &&
ARRAY_SIZE(film_grain_params.auto_regression_coeff_y) ==
kAutoRegressionCoeffYSize &&
ARRAY_SIZE(film_grain_params.auto_regression_coeff_u) ==
kAutoRegressionCoeffUVSize &&
ARRAY_SIZE(film_grain_params.auto_regression_coeff_v) ==
kAutoRegressionCoeffUVSize,
"Invalid array size of auto-regressive coefficients");
const size_t num_pos_y = (film_grain_params.auto_regression_coeff_lag * 2) *
(film_grain_params.auto_regression_coeff_lag + 1);
const size_t num_pos_uv = num_pos_y + (film_grain_params.num_y_points > 0);
if (film_grain_params.num_y_points > 0) {
DCHECK_LE(num_pos_y, kAutoRegressionCoeffYSize);
for (size_t i = 0; i < num_pos_y; ++i)
COPY_FILM_GRAIN_FIELD2(ar_coeffs_y[i], auto_regression_coeff_y[i]);
}
if (film_grain_params.chroma_scaling_from_luma ||
film_grain_params.num_u_points > 0 ||
film_grain_params.num_v_points > 0) {
DCHECK_LE(num_pos_uv, kAutoRegressionCoeffUVSize);
for (size_t i = 0; i < num_pos_uv; ++i) {
if (film_grain_params.chroma_scaling_from_luma ||
film_grain_params.num_u_points > 0) {
COPY_FILM_GRAIN_FIELD2(ar_coeffs_cb[i], auto_regression_coeff_u[i]);
}
if (film_grain_params.chroma_scaling_from_luma ||
film_grain_params.num_v_points > 0) {
COPY_FILM_GRAIN_FIELD2(ar_coeffs_cr[i], auto_regression_coeff_v[i]);
}
}
}
if (film_grain_params.num_u_points > 0) {
COPY_FILM_GRAIN_FIELD2(cb_mult, u_multiplier + 128);
COPY_FILM_GRAIN_FIELD2(cb_luma_mult, u_luma_multiplier + 128);
COPY_FILM_GRAIN_FIELD2(cb_offset, u_offset + 256);
}
if (film_grain_params.num_v_points > 0) {
COPY_FILM_GRAIN_FIELD2(cr_mult, v_multiplier + 128);
COPY_FILM_GRAIN_FIELD2(cr_luma_mult, v_luma_multiplier + 128);
COPY_FILM_GRAIN_FIELD2(cr_offset, v_offset + 256);
}
#undef COPY_FILM_GRAIN_FIELD2
}
void FillGlobalMotionInfo(
VAWarpedMotionParamsAV1 va_warped_motion[7],
const std::array<libgav1::GlobalMotion, libgav1::kNumReferenceFrameTypes>&
global_motion) {
// global_motion[0] (for kReferenceFrameIntra) is not used.
constexpr size_t kWarpedMotionSize = libgav1::kNumReferenceFrameTypes - 1;
for (size_t i = 0; i < kWarpedMotionSize; ++i) {
// Copy |global_motion| because SetupShear updates the affine variables of
// the |global_motion|.
auto gm = global_motion[i + 1];
switch (gm.type) {
case libgav1::kGlobalMotionTransformationTypeIdentity:
va_warped_motion[i].wmtype = VAAV1TransformationIdentity;
break;
case libgav1::kGlobalMotionTransformationTypeTranslation:
va_warped_motion[i].wmtype = VAAV1TransformationTranslation;
break;
case libgav1::kGlobalMotionTransformationTypeRotZoom:
va_warped_motion[i].wmtype = VAAV1TransformationRotzoom;
break;
case libgav1::kGlobalMotionTransformationTypeAffine:
va_warped_motion[i].wmtype = VAAV1TransformationAffine;
break;
default:
NOTREACHED() << "Invalid global motion transformation type, "
<< va_warped_motion[i].wmtype;
}
static_assert(ARRAY_SIZE(va_warped_motion[i].wmmat) == 8 &&
ARRAY_SIZE(gm.params) == 6,
"Invalid size of warp motion parameters");
for (size_t j = 0; j < 6; ++j)
va_warped_motion[i].wmmat[j] = gm.params[j];
va_warped_motion[i].wmmat[6] = 0;
va_warped_motion[i].wmmat[7] = 0;
va_warped_motion[i].invalid = !libgav1::SetupShear(&gm);
}
}
bool FillTileInfo(VADecPictureParameterBufferAV1& va_pic_param,
const libgav1::TileInfo& tile_info) {
// Since gav1 decoder doesn't support decoding with tile lists (i.e. large
// scale tile decoding), libgav1::ObuParser doesn't parse tile list, so that
// we cannot acquire anchor_frames_num, anchor_frames_list, tile_count_minus_1
// and output_frame_width/height_in_tiles_minus_1, and thus must set them and
// large_scale_tile to 0 or false. This is already done by the memset in
// DecodeNextFrame(). libgav1::ObuParser returns kStatusUnimplemented on
// ParseOneFrame().
va_pic_param.tile_cols = base::checked_cast<uint8_t>(tile_info.tile_columns);
va_pic_param.tile_rows = base::checked_cast<uint8_t>(tile_info.tile_rows);
if (!tile_info.uniform_spacing) {
constexpr int kVaSizeOfTileWidthAndHeightArray = 63;
static_assert(
ARRAY_SIZE(tile_info.tile_column_width_in_superblocks) == 65 &&
ARRAY_SIZE(tile_info.tile_row_height_in_superblocks) == 65 &&
ARRAY_SIZE(va_pic_param.width_in_sbs_minus_1) ==
kVaSizeOfTileWidthAndHeightArray &&
ARRAY_SIZE(va_pic_param.height_in_sbs_minus_1) ==
kVaSizeOfTileWidthAndHeightArray,
"Invalid sizes of tile column widths and row heights");
const int tile_columns =
std::min(kVaSizeOfTileWidthAndHeightArray, tile_info.tile_columns);
for (int i = 0; i < tile_columns; i++) {
if (!base::CheckSub<int>(tile_info.tile_column_width_in_superblocks[i], 1)
.AssignIfValid(&va_pic_param.width_in_sbs_minus_1[i])) {
return false;
}
}
const int tile_rows =
std::min(kVaSizeOfTileWidthAndHeightArray, tile_info.tile_rows);
for (int i = 0; i < tile_rows; i++) {
if (!base::CheckSub<int>(tile_info.tile_row_height_in_superblocks[i], 1)
.AssignIfValid(&va_pic_param.height_in_sbs_minus_1[i])) {
return false;
}
}
}
va_pic_param.context_update_tile_id =
base::checked_cast<uint16_t>(tile_info.context_update_id);
return true;
}
void FillLoopFilterInfo(VADecPictureParameterBufferAV1& va_pic_param,
const libgav1::LoopFilter& loop_filter) {
static_assert(STD_ARRAY_SIZE(loop_filter.level) == libgav1::kFrameLfCount &&
libgav1::kFrameLfCount == 4 &&
ARRAY_SIZE(va_pic_param.filter_level) == 2,
"Invalid size of loop filter strength array");
va_pic_param.filter_level[0] =
base::checked_cast<uint8_t>(loop_filter.level[0]);
va_pic_param.filter_level[1] =
base::checked_cast<uint8_t>(loop_filter.level[1]);
va_pic_param.filter_level_u =
base::checked_cast<uint8_t>(loop_filter.level[2]);
va_pic_param.filter_level_v =
base::checked_cast<uint8_t>(loop_filter.level[3]);
va_pic_param.loop_filter_info_fields.bits.sharpness_level =
loop_filter.sharpness;
va_pic_param.loop_filter_info_fields.bits.mode_ref_delta_enabled =
loop_filter.delta_enabled;
va_pic_param.loop_filter_info_fields.bits.mode_ref_delta_update =
loop_filter.delta_update;
static_assert(libgav1::kNumReferenceFrameTypes == 8 &&
ARRAY_SIZE(va_pic_param.ref_deltas) ==
libgav1::kNumReferenceFrameTypes &&
STD_ARRAY_SIZE(loop_filter.ref_deltas) ==
libgav1::kNumReferenceFrameTypes,
"Invalid size of ref deltas array");
static_assert(libgav1::kLoopFilterMaxModeDeltas == 2 &&
ARRAY_SIZE(va_pic_param.mode_deltas) ==
libgav1::kLoopFilterMaxModeDeltas &&
STD_ARRAY_SIZE(loop_filter.mode_deltas) ==
libgav1::kLoopFilterMaxModeDeltas,
"Invalid size of mode deltas array");
for (size_t i = 0; i < libgav1::kNumReferenceFrameTypes; i++)
va_pic_param.ref_deltas[i] = loop_filter.ref_deltas[i];
for (size_t i = 0; i < libgav1::kLoopFilterMaxModeDeltas; i++)
va_pic_param.mode_deltas[i] = loop_filter.mode_deltas[i];
}
void FillQuantizationInfo(VADecPictureParameterBufferAV1& va_pic_param,
const libgav1::QuantizerParameters& quant_param) {
va_pic_param.base_qindex = quant_param.base_index;
static_assert(
libgav1::kPlaneY == 0 && libgav1::kPlaneU == 1 && libgav1::kPlaneV == 2,
"Invalid plane index");
static_assert(libgav1::kMaxPlanes == 3 &&
ARRAY_SIZE(quant_param.delta_dc) == libgav1::kMaxPlanes &&
ARRAY_SIZE(quant_param.delta_ac) == libgav1::kMaxPlanes,
"Invalid size of delta dc/ac array");
va_pic_param.y_dc_delta_q = quant_param.delta_dc[0];
va_pic_param.u_dc_delta_q = quant_param.delta_dc[1];
va_pic_param.v_dc_delta_q = quant_param.delta_dc[2];
// quant_param.delta_ac[0] is useless as it is always 0.
va_pic_param.u_ac_delta_q = quant_param.delta_ac[1];
va_pic_param.v_ac_delta_q = quant_param.delta_ac[2];
va_pic_param.qmatrix_fields.bits.using_qmatrix = quant_param.use_matrix;
if (!quant_param.use_matrix)
return;
static_assert(ARRAY_SIZE(quant_param.matrix_level) == libgav1::kMaxPlanes,
"Invalid size of matrix levels");
va_pic_param.qmatrix_fields.bits.qm_y =
base::checked_cast<uint16_t>(quant_param.matrix_level[0]);
va_pic_param.qmatrix_fields.bits.qm_u =
base::checked_cast<uint16_t>(quant_param.matrix_level[1]);
va_pic_param.qmatrix_fields.bits.qm_v =
base::checked_cast<uint16_t>(quant_param.matrix_level[2]);
}
void FillCdefInfo(VADecPictureParameterBufferAV1& va_pic_param,
const libgav1::Cdef& cdef,
uint8_t color_bitdepth) {
// Damping value parsed in libgav1 is from the spec + (bitdepth - 8).
// All the strength values parsed in libgav1 are from the spec and left
// shifted by (bitdepth - 8).
CHECK_GE(color_bitdepth, 8u);
const uint8_t coeff_shift = color_bitdepth - 8u;
va_pic_param.cdef_damping_minus_3 =
base::checked_cast<uint8_t>(cdef.damping - coeff_shift - 3u);
va_pic_param.cdef_bits = cdef.bits;
static_assert(
libgav1::kMaxCdefStrengths == 8 &&
ARRAY_SIZE(cdef.y_primary_strength) == libgav1::kMaxCdefStrengths &&
ARRAY_SIZE(cdef.y_secondary_strength) == libgav1::kMaxCdefStrengths &&
ARRAY_SIZE(cdef.uv_primary_strength) == libgav1::kMaxCdefStrengths &&
ARRAY_SIZE(cdef.uv_secondary_strength) ==
libgav1::kMaxCdefStrengths &&
ARRAY_SIZE(va_pic_param.cdef_y_strengths) ==
libgav1::kMaxCdefStrengths &&
ARRAY_SIZE(va_pic_param.cdef_uv_strengths) ==
libgav1::kMaxCdefStrengths,
"Invalid size of cdef strengths");
const size_t num_cdef_strengths = 1 << cdef.bits;
DCHECK_LE(num_cdef_strengths,
static_cast<size_t>(libgav1::kMaxCdefStrengths));
for (size_t i = 0; i < num_cdef_strengths; ++i) {
const uint8_t prim_strength = cdef.y_primary_strength[i] >> coeff_shift;
uint8_t sec_strength = cdef.y_secondary_strength[i] >> coeff_shift;
DCHECK_LE(sec_strength, 4u);
if (sec_strength == 4)
sec_strength--;
va_pic_param.cdef_y_strengths[i] =
((prim_strength & 0xf) << 2) | (sec_strength & 0x03);
}
for (size_t i = 0; i < num_cdef_strengths; ++i) {
const uint8_t prim_strength = cdef.uv_primary_strength[i] >> coeff_shift;
uint8_t sec_strength = cdef.uv_secondary_strength[i] >> coeff_shift;
DCHECK_LE(sec_strength, 4u);
if (sec_strength == 4)
sec_strength--;
va_pic_param.cdef_uv_strengths[i] =
((prim_strength & 0xf) << 2) | (sec_strength & 0x03);
}
}
void FillModeControlInfo(VADecPictureParameterBufferAV1& va_pic_param,
const libgav1::ObuFrameHeader& frame_header) {
auto& mode_control = va_pic_param.mode_control_fields.bits;
mode_control.delta_q_present_flag = frame_header.delta_q.present;
mode_control.log2_delta_q_res = frame_header.delta_q.scale;
mode_control.delta_lf_present_flag = frame_header.delta_lf.present;
mode_control.log2_delta_lf_res = frame_header.delta_lf.scale;
mode_control.delta_lf_multi = frame_header.delta_lf.multi;
DCHECK_LE(0u, frame_header.tx_mode);
DCHECK_LE(frame_header.tx_mode, 2u);
mode_control.tx_mode = frame_header.tx_mode;
mode_control.reference_select = frame_header.reference_mode_select;
mode_control.reduced_tx_set_used = frame_header.reduced_tx_set;
mode_control.skip_mode_present = frame_header.skip_mode_present;
}
void FillLoopRestorationInfo(VADecPictureParameterBufferAV1& va_pic_param,
const libgav1::LoopRestoration& loop_restoration) {
auto to_frame_restoration_type =
[](libgav1::LoopRestorationType lr_type) -> uint16_t {
// Spec. 6.10.15
switch (lr_type) {
case libgav1::LoopRestorationType::kLoopRestorationTypeNone:
return 0;
case libgav1::LoopRestorationType::kLoopRestorationTypeSwitchable:
return 3;
case libgav1::LoopRestorationType::kLoopRestorationTypeWiener:
return 1;
case libgav1::LoopRestorationType::kLoopRestorationTypeSgrProj:
return 2;
default:
NOTREACHED() << "Invalid restoration type"
<< base::strict_cast<int>(lr_type);
return 0;
}
};
static_assert(
libgav1::kMaxPlanes == 3 &&
ARRAY_SIZE(loop_restoration.type) == libgav1::kMaxPlanes &&
ARRAY_SIZE(loop_restoration.unit_size_log2) == libgav1::kMaxPlanes,
"Invalid size of loop restoration values");
auto& va_loop_restoration = va_pic_param.loop_restoration_fields.bits;
va_loop_restoration.yframe_restoration_type =
to_frame_restoration_type(loop_restoration.type[0]);
va_loop_restoration.cbframe_restoration_type =
to_frame_restoration_type(loop_restoration.type[1]);
va_loop_restoration.crframe_restoration_type =
to_frame_restoration_type(loop_restoration.type[2]);
const size_t num_planes = libgav1::kMaxPlanes;
const bool use_loop_restoration =
std::find_if(std::begin(loop_restoration.type),
std::begin(loop_restoration.type) + num_planes,
[](const auto type) {
return type != libgav1::kLoopRestorationTypeNone;
}) != (loop_restoration.type + num_planes);
if (!use_loop_restoration)
return;
static_assert(libgav1::kPlaneY == 0u && libgav1::kPlaneU == 1u,
"Invalid plane index");
DCHECK_GE(loop_restoration.unit_size_log2[0], 6);
DCHECK_GE(loop_restoration.unit_size_log2[0],
loop_restoration.unit_size_log2[1]);
DCHECK_LE(
loop_restoration.unit_size_log2[0] - loop_restoration.unit_size_log2[1],
1);
va_loop_restoration.lr_unit_shift = loop_restoration.unit_size_log2[0] - 6;
va_loop_restoration.lr_uv_shift =
loop_restoration.unit_size_log2[0] - loop_restoration.unit_size_log2[1];
}
bool FillAV1SliceParameters(
const libgav1::Vector<libgav1::TileBuffer>& tile_buffers,
const size_t tile_columns,
base::span<const uint8_t> data,
std::vector<VASliceParameterBufferAV1>& va_slice_params) {
CHECK_GT(tile_columns, 0u);
const uint16_t num_tiles = base::checked_cast<uint16_t>(tile_buffers.size());
va_slice_params.resize(num_tiles);
for (uint16_t tile = 0; tile < num_tiles; ++tile) {
VASliceParameterBufferAV1& va_tile_param = va_slice_params[tile];
memset(&va_tile_param, 0, sizeof(VASliceParameterBufferAV1));
va_tile_param.slice_data_flag = VA_SLICE_DATA_FLAG_ALL;
va_tile_param.tile_row = tile / base::checked_cast<uint16_t>(tile_columns);
va_tile_param.tile_column =
tile % base::checked_cast<uint16_t>(tile_columns);
if (!base::CheckedNumeric<size_t>(tile_buffers[tile].size)
.AssignIfValid(&va_tile_param.slice_data_size)) {
return false;
}
CHECK(tile_buffers[tile].data >= data.data());
va_tile_param.slice_data_offset =
base::checked_cast<uint32_t>(tile_buffers[tile].data - data.data());
base::CheckedNumeric<uint32_t> safe_va_slice_data_end(
va_tile_param.slice_data_offset);
safe_va_slice_data_end += va_tile_param.slice_data_size;
size_t va_slice_data_end;
if (!safe_va_slice_data_end.AssignIfValid(&va_slice_data_end) ||
va_slice_data_end > data.size()) {
DLOG(ERROR) << "Invalid tile offset and size"
<< ", offset=" << va_tile_param.slice_data_size
<< ", size=" << va_tile_param.slice_data_offset
<< ", entire data size=" << data.size();
return false;
}
}
return true;
}
// Returns the preferred VA_RT_FORMAT for the given |color_config|.
// Because we're limited to profile 0, we can have 8 or 10 bits. We do not
// support monochrome configs, but do support 4:2:0 Chroma subsampling.
unsigned int GetFormatForColorConfig(libgav1::ColorConfig color_config) {
LOG_ASSERT(!color_config.is_monochrome)
<< "Monochrome color config is not supported.";
if (color_config.subsampling_x == 1u && color_config.subsampling_y == 1u) {
// uses chroma subsampling
if (color_config.bitdepth == 8) {
return VA_RT_FORMAT_YUV420;
} else if (color_config.bitdepth == 10) {
return VA_RT_FORMAT_YUV420_10;
} else {
// GetFormatForColorConfig() is only called after we know we're dealing
// with an
// AV1 stream whose profile is 'main' - this profile only supports bit
// depths of 8 and 10 and libgav1 should guarantee that
// |color_config.bitdepth| meets that requirement at parsing time.
NOTREACHED()
<< "Unsupported color config with chroma subsampling of bitdepth %d"
<< color_config.bitdepth;
}
}
// If this AV1 stream has profile 'main', then libgav1 ensures that both
// |color_config.subsampling_x| and |color_config.subsampling_y| are 1.
NOTREACHED() << "Unsupported color config; only profile 0 with 4:2:0 Chroma "
"subsampling is supported.";
// There is no VA_RT_FORMAT_UNSUPPORTED; use a "default" value.
return 0u;
}
} // namespace
Av1Decoder::Av1Decoder(std::unique_ptr<IvfParser> ivf_parser,
const VaapiDevice& va_device,
SharedVASurface::FetchPolicy fetch_policy)
: VideoDecoder::VideoDecoder(std::move(ivf_parser),
va_device,
fetch_policy),
buffer_pool_(std::make_unique<libgav1::BufferPool>(
/*on_frame_buffer_size_changed=*/nullptr,
/*get_frame_buffer=*/nullptr,
/*release_frame_buffer=*/nullptr,
/*callback_private_data=*/nullptr)),
state_(std::make_unique<libgav1::DecoderState>()),
ref_frames_(kAv1NumRefFrames),
display_surfaces_(kAv1NumRefFrames) {}
Av1Decoder::~Av1Decoder() {
// We destroy the state explicitly to ensure it's destroyed before the
// |buffer_pool_|. The |buffer_pool_| checks that all the allocated frames
// are released in its destructor. Explicitly destructing |state_| releases
// frames in |reference_frame| in |state_|.
state_.reset();
// We destroy the VA handles explicitly to ensure the correct order.
// The configuration must be destroyed after the context so that the
// configuration reference remains valid in the context, and surfaces can only
// be destroyed after the context as per
// https://github.com/intel/libva/blob/8c6126e67c446f4c7808cb51b609077e4b9bd8fe/va/va.h#L1549
va_context_.reset();
va_config_.reset();
ref_frames_.clear();
display_surfaces_.clear();
last_decoded_surface_.reset();
}
Av1Decoder::ParsingResult Av1Decoder::ReadNextFrame(
libgav1::RefCountedBufferPtr& current_frame) {
if (!obu_parser_ || !obu_parser_->HasData()) {
if (!ivf_parser_->ParseNextFrame(&ivf_frame_header_, &ivf_frame_data_)) {
return ParsingResult::kEOStream;
}
// The ObuParser has run out of data or did not exist in the first place. It
// has no "replace the current buffer with a new buffer of a different size"
// method; we must make a new parser.
obu_parser_ = base::WrapUnique(new (std::nothrow) libgav1::ObuParser(
ivf_frame_data_, ivf_frame_header_.frame_size, /*operating_point=*/0,
buffer_pool_.get(), state_.get()));
if (current_sequence_header_) {
obu_parser_->set_sequence_header(*current_sequence_header_);
}
}
libgav1::StatusCode code = obu_parser_->ParseOneFrame(&current_frame);
if (code != libgav1::kStatusOk) {
LOG(ERROR) << "Error parsing OBU stream: " << code;
return ParsingResult::kFailed;
}
return ParsingResult::kOk;
}
void Av1Decoder::RefreshReferenceSlots(
const uint8_t refresh_frame_flags,
scoped_refptr<SharedVASurface> surface,
libgav1::RefCountedBufferPtr current_frame,
scoped_refptr<SharedVASurface> display_surface) {
const std::bitset<kAv1NumRefFrames> slots(refresh_frame_flags);
for (size_t i = 0; i < kAv1NumRefFrames; i++) {
if (slots[i]) {
ref_frames_[i] = surface;
display_surfaces_[i] = display_surface;
}
}
state_->UpdateReferenceFrames(current_frame,
base::strict_cast<int>(refresh_frame_flags));
}
VideoDecoder::Result Av1Decoder::DecodeNextFrame() {
// Parse next frame from stream.
libgav1::RefCountedBufferPtr current_frame;
ParsingResult parser_res = ReadNextFrame(current_frame);
if (parser_res != ParsingResult::kOk) {
LOG_ASSERT(parser_res == ParsingResult::kEOStream)
<< "Failed to parse next frame, got " << static_cast<int>(parser_res);
return VideoDecoder::kEOStream;
}
libgav1::ObuFrameHeader current_frame_header = obu_parser_->frame_header();
if (current_frame_header.show_existing_frame) {
last_decoded_frame_visible_ = true;
} else {
last_decoded_frame_visible_ = current_frame_header.show_frame;
}
if (obu_parser_->sequence_header_changed()) {
if (current_frame_header.frame_type != libgav1::kFrameKey ||
!current_frame_header.show_frame ||
current_frame_header.show_existing_frame ||
current_frame->temporal_id() != 0) {
// Section 7.5.
LOG_ASSERT(false)
<< "The first frame successive to sequence header OBU must be a "
<< "keyframe with show_frame=1, show_existing_frame=0 and "
<< "temporal_id=0";
}
current_sequence_header_.emplace(obu_parser_->sequence_header());
// TODO(clarissagarvey): Support other profiles once Chrome does.
LOG_ASSERT(current_sequence_header_->profile ==
libgav1::BitstreamProfile::kProfile0)
<< "Unsupported profile.";
const VAProfile new_profile = VAProfileAV1Profile0;
// (Section 6.4.1):
//
// - "An operating point specifies which spatial and temporal layers should
// be decoded."
//
// - "The order of operating points indicates the preferred order for
// producing an output: a decoder should select the earliest operating
// point in the list that meets its decoding capabilities as expressed by
// the level associated with each operating point."
//
// For simplicity, we always select operating point 0 and validate that it
// doesn't have scalability information.
LOG_ASSERT(current_sequence_header_->operating_point_idc[0] == 0)
<< "Either temporal or spatial layer decoding is not supported.";
if (!va_config_ || va_config_->profile() != new_profile) {
va_context_.reset();
libgav1::ColorConfig color_config =
current_sequence_header_.value().color_config;
va_config_ = std::make_unique<ScopedVAConfig>(
va_device_, new_profile, GetFormatForColorConfig(color_config));
}
ref_frames_.clear();
display_surfaces_.clear();
const gfx::Size new_frame_size(
base::strict_cast<int>(current_sequence_header_->max_frame_width),
base::strict_cast<int>(current_sequence_header_->max_frame_height));
if (!va_context_ || va_context_->size() != new_frame_size) {
va_context_ = std::make_unique<ScopedVAContext>(va_device_, *va_config_,
new_frame_size);
}
}
// Clean up reference frames.
for (size_t i = 0; i < kAv1NumRefFrames; ++i) {
if (state_->reference_frame[i] && !ref_frames_[i]) {
LOG_ASSERT(false) << "The state of the reference frames are different "
"between |ref_frames_| and |state_|";
}
if (!state_->reference_frame[i] && ref_frames_[i]) {
ref_frames_[i].reset();
display_surfaces_[i].reset();
}
}
if (current_frame_header.show_existing_frame) {
last_decoded_surface_ =
display_surfaces_[current_frame_header.frame_to_show];
RefreshReferenceSlots(current_frame_header.refresh_frame_flags,
last_decoded_surface_, current_frame,
last_decoded_surface_);
return VideoDecoder::kOk;
}
LOG_ASSERT(current_sequence_header_)
<< "Sequence header missing for decoding.";
// Create surfaces for decode.
VASurfaceAttrib attribute;
memset(&attribute, 0, sizeof(VASurfaceAttrib));
attribute.type = VASurfaceAttribUsageHint;
attribute.flags = VA_SURFACE_ATTRIB_SETTABLE;
attribute.value.type = VAGenericValueTypeInteger;
attribute.value.value.i = VA_SURFACE_ATTRIB_USAGE_HINT_DECODER;
scoped_refptr<SharedVASurface> surface = SharedVASurface::Create(
va_device_, va_config_->va_rt_format(), va_context_->size(), attribute);
// Set up buffer for pic parameters
VADecPictureParameterBufferAV1 pic_parameters;
memset(&pic_parameters, 0, sizeof(VADecPictureParameterBufferAV1));
pic_parameters.profile =
base::strict_cast<uint8_t>(current_sequence_header_->profile);
if (current_sequence_header_->enable_order_hint) {
pic_parameters.order_hint_bits_minus_1 =
current_sequence_header_->order_hint_bits - 1;
}
switch (current_sequence_header_->color_config.bitdepth) {
case 8:
pic_parameters.bit_depth_idx = 0;
break;
case 10:
pic_parameters.bit_depth_idx = 1;
break;
case 12:
// This is a valid bitdepth in streams, but we do not support it;
// GetFormatForColorConfig() only expects bit depths of 8 or 10.
NOTREACHED() << "12bpp color is not yet supported.";
break;
default:
// The OBU Parser can only produce bit depths of 8, 10, and 12; we should
// not hit any other cases. See
// https://source.chromium.org/chromium/chromium/src/+/main:third_party/libgav1/src/src/obu_parser.cc;l=144-150;drc=7880d0cc1d1976012dbec8a1bb982191ac49b7f4
NOTREACHED() << "Invalid color bit depth: "
<< current_sequence_header_->color_config.bitdepth;
}
pic_parameters.matrix_coefficients = base::checked_cast<uint8_t>(
current_sequence_header_->color_config.matrix_coefficients);
#define COPY_SEQ_FIELD(a) \
pic_parameters.seq_info_fields.fields.a = current_sequence_header_->a
#define COPY_SEQ_FIELD2(a, b) pic_parameters.seq_info_fields.fields.a = b
COPY_SEQ_FIELD(still_picture);
COPY_SEQ_FIELD(use_128x128_superblock);
COPY_SEQ_FIELD(enable_filter_intra);
COPY_SEQ_FIELD(enable_intra_edge_filter);
COPY_SEQ_FIELD(enable_interintra_compound);
COPY_SEQ_FIELD(enable_masked_compound);
COPY_SEQ_FIELD(enable_dual_filter);
COPY_SEQ_FIELD(enable_order_hint);
COPY_SEQ_FIELD(enable_jnt_comp);
COPY_SEQ_FIELD(enable_cdef);
COPY_SEQ_FIELD2(mono_chrome,
current_sequence_header_->color_config.is_monochrome);
COPY_SEQ_FIELD2(subsampling_x,
current_sequence_header_->color_config.subsampling_x);
COPY_SEQ_FIELD2(subsampling_y,
current_sequence_header_->color_config.subsampling_y);
COPY_SEQ_FIELD(film_grain_params_present);
#undef COPY_SEQ_FIELD
COPY_SEQ_FIELD2(color_range,
base::strict_cast<uint32_t>(
current_sequence_header_->color_config.color_range));
#undef COPY_SEQ_FIELD2
scoped_refptr<SharedVASurface> film_grain_surface;
if (current_frame_header.film_grain_params.apply_grain) {
pic_parameters.current_frame = surface->id();
film_grain_surface = SharedVASurface::Create(
va_device_, va_config_->va_rt_format(), va_context_->size(), attribute);
pic_parameters.current_display_picture = film_grain_surface->id();
} else {
pic_parameters.current_frame = surface->id();
pic_parameters.current_display_picture = VA_INVALID_SURFACE;
}
pic_parameters.frame_width_minus1 = current_frame_header.width - 1;
pic_parameters.frame_height_minus1 = current_frame_header.height - 1;
for (size_t i = 0; i < kAv1NumRefFrames; ++i) {
pic_parameters.ref_frame_map[i] =
ref_frames_[i] ? ref_frames_[i]->id() : VA_INVALID_SURFACE;
}
// |pic_parameters.ref_frame_idx| doesn't need to be filled in for intra
// frames (it can be left zero initialized).
if (!libgav1::IsIntraFrame(current_frame_header.frame_type)) {
for (size_t i = 0; i < kAv1NumRefFrames; ++i) {
const int8_t index = current_frame_header.reference_frame_index[i];
CHECK_GE(index, 0);
CHECK_LT(static_cast<size_t>(index), kAv1NumRefFrames);
pic_parameters.ref_frame_idx[i] = base::checked_cast<uint8_t>(index);
}
}
pic_parameters.primary_ref_frame =
base::checked_cast<uint8_t>(current_frame_header.primary_reference_frame);
pic_parameters.order_hint = current_frame_header.order_hint;
LOG_ASSERT(!current_frame_header.use_superres)
<< "Upscaling (use_superres=1) is not supported";
FillSegmentInfo(pic_parameters.seg_info, current_frame_header.segmentation);
FillFilmGrainInfo(pic_parameters.film_grain_info,
current_frame_header.film_grain_params);
const bool tile_info_success =
FillTileInfo(pic_parameters, current_frame_header.tile_info);
LOG_ASSERT(tile_info_success)
<< "Failed to fill tile info for current frame.";
auto& info_fields = pic_parameters.pic_info_fields.bits;
info_fields.uniform_tile_spacing_flag =
current_frame_header.tile_info.uniform_spacing;
#define COPY_PIC_FIELD(a) info_fields.a = current_frame_header.a
COPY_PIC_FIELD(show_frame);
COPY_PIC_FIELD(showable_frame);
COPY_PIC_FIELD(error_resilient_mode);
COPY_PIC_FIELD(allow_screen_content_tools);
COPY_PIC_FIELD(force_integer_mv);
COPY_PIC_FIELD(allow_intrabc);
COPY_PIC_FIELD(use_superres);
COPY_PIC_FIELD(allow_high_precision_mv);
COPY_PIC_FIELD(is_motion_mode_switchable);
COPY_PIC_FIELD(use_ref_frame_mvs);
COPY_PIC_FIELD(allow_warped_motion);
#undef COPY_PIC_FIELD
info_fields.frame_type =
base::strict_cast<uint32_t>(current_frame_header.frame_type);
info_fields.disable_cdf_update = !current_frame_header.enable_cdf_update;
info_fields.disable_frame_end_update_cdf =
!current_frame_header.enable_frame_end_update_cdf;
pic_parameters.superres_scale_denominator =
current_frame_header.superres_scale_denominator;
pic_parameters.interp_filter =
base::strict_cast<uint8_t>(current_frame_header.interpolation_filter);
FillLoopFilterInfo(pic_parameters, current_frame_header.loop_filter);
FillQuantizationInfo(pic_parameters, current_frame_header.quantizer);
const uint color_bitdepth = current_sequence_header_->color_config.bitdepth;
FillCdefInfo(pic_parameters, current_frame_header.cdef, color_bitdepth);
FillModeControlInfo(pic_parameters, current_frame_header);
FillLoopRestorationInfo(pic_parameters,
current_frame_header.loop_restoration);
FillGlobalMotionInfo(pic_parameters.wm, current_frame_header.global_motion);
std::vector<VABufferID> buffers;
VABufferID buffer_id;
VAStatus res = vaCreateBuffer(
va_device_.display(), va_context_->id(), VAPictureParameterBufferType,
sizeof(VADecPictureParameterBufferAV1), 1u, &pic_parameters, &buffer_id);
VA_LOG_ASSERT(res, "vaCreateBuffer");
buffers.push_back(buffer_id);
// Create buffer for "slice" (for AV1, this corresponds to tile) decoding.
std::vector<VASliceParameterBufferAV1> slice_params;
// The tiles are row-major; we pass in the number of columns for computation
// of the row and column for a given flattened index.
const size_t tile_columns = current_frame_header.tile_info.tile_columns;
const bool slice_parameters_success = FillAV1SliceParameters(
obu_parser_->tile_buffers(), tile_columns,
base::make_span(ivf_frame_data_, ivf_frame_header_.frame_size),
slice_params);
LOG_ASSERT(slice_parameters_success)
<< "Failed to fill slice parameters for current frame.";
// Set up a buffer for the slice parameters for each slice.
for (auto& slice_param : slice_params) {
res = vaCreateBuffer(
va_device_.display(), va_context_->id(), VASliceParameterBufferType,
sizeof(VASliceParameterBufferAV1), 1u, &slice_param, &buffer_id);
VA_LOG_ASSERT(res, "vaCreateBuffer");
buffers.push_back(buffer_id);
}
// Set up the slice data buffer.
res = vaCreateBuffer(va_device_.display(), va_context_->id(),
VASliceDataBufferType, ivf_frame_header_.frame_size, 1u,
const_cast<uint8_t*>(ivf_frame_data_), &buffer_id);
VA_LOG_ASSERT(res, "vaCreateBuffer");
buffers.push_back(buffer_id);
res = vaBeginPicture(va_device_.display(), va_context_->id(), surface->id());
VA_LOG_ASSERT(res, "vaBeginPicture");
res = vaRenderPicture(va_device_.display(), va_context_->id(), buffers.data(),
buffers.size());
VA_LOG_ASSERT(res, "vaRenderPicture");
res = vaEndPicture(va_device_.display(), va_context_->id());
VA_LOG_ASSERT(res, "vaEndPicture");
if (current_frame_header.film_grain_params.apply_grain) {
last_decoded_surface_ = film_grain_surface;
} else {
last_decoded_surface_ = surface;
}
RefreshReferenceSlots(current_frame_header.refresh_frame_flags, surface,
current_frame, last_decoded_surface_);
for (auto id : buffers) {
vaDestroyBuffer(va_device_.display(), id);
}
buffers.clear();
return VideoDecoder::kOk;
}
} // namespace vaapi_test
} // namespace media