blob: b2d986b71d47abb9e1c6129695f0c8e7237b6315 [file] [log] [blame]
// Copyright 2020 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/av1_vaapi_video_decoder_delegate.h"
#include <string.h>
#include <va/va.h>
#include <algorithm>
#include <vector>
#include "base/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "media/gpu/av1_picture.h"
#include "media/gpu/decode_surface_handler.h"
#include "media/gpu/vaapi/vaapi_common.h"
#include "media/gpu/vaapi/vaapi_wrapper.h"
#include "third_party/libgav1/src/src/obu_parser.h"
#include "third_party/libgav1/src/src/utils/types.h"
#include "third_party/libgav1/src/src/warp_prediction.h"
namespace media {
using DecodeStatus = AV1Decoder::AV1Accelerator::Status;
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
// SubmitDecode(). libgav1::ObuParser returns kStatusUnimplemented on
// ParseOneFrame(), a fallback to av1 software decoder happens in the large
// scale tile decoding.
// TODO(hiroh): Support the large scale tile decoding once libgav1::ObuParser
// supports it.
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 FillAV1PictureParameter(const AV1Picture& pic,
const libgav1::ObuSequenceHeader& sequence_header,
const AV1ReferenceFrameVector& ref_frames,
VADecPictureParameterBufferAV1& va_pic_param) {
memset(&va_pic_param, 0, sizeof(VADecPictureParameterBufferAV1));
DCHECK_LE(base::strict_cast<uint8_t>(sequence_header.profile), 2u)
<< "Unknown profile: " << base::strict_cast<int>(sequence_header.profile);
va_pic_param.profile = base::strict_cast<uint8_t>(sequence_header.profile);
if (sequence_header.enable_order_hint) {
DCHECK_GT(sequence_header.order_hint_bits, 0);
DCHECK_LE(sequence_header.order_hint_bits, 8);
va_pic_param.order_hint_bits_minus_1 = sequence_header.order_hint_bits - 1;
}
switch (sequence_header.color_config.bitdepth) {
case 8:
va_pic_param.bit_depth_idx = 0;
break;
case 10:
va_pic_param.bit_depth_idx = 1;
break;
case 12:
va_pic_param.bit_depth_idx = 2;
break;
default:
NOTREACHED() << "Unknown bit depth: "
<< base::strict_cast<int>(
sequence_header.color_config.bitdepth);
}
switch (sequence_header.color_config.matrix_coefficients) {
case libgav1::kMatrixCoefficientsIdentity:
case libgav1::kMatrixCoefficientsBt709:
case libgav1::kMatrixCoefficientsUnspecified:
case libgav1::kMatrixCoefficientsFcc:
case libgav1::kMatrixCoefficientsBt470BG:
case libgav1::kMatrixCoefficientsBt601:
case libgav1::kMatrixCoefficientsSmpte240:
case libgav1::kMatrixCoefficientsSmpteYcgco:
case libgav1::kMatrixCoefficientsBt2020Ncl:
case libgav1::kMatrixCoefficientsBt2020Cl:
case libgav1::kMatrixCoefficientsSmpte2085:
case libgav1::kMatrixCoefficientsChromatNcl:
case libgav1::kMatrixCoefficientsChromatCl:
case libgav1::kMatrixCoefficientsIctcp:
va_pic_param.matrix_coefficients = base::checked_cast<uint8_t>(
sequence_header.color_config.matrix_coefficients);
break;
default:
DLOG(ERROR) << "Invalid matrix coefficients: "
<< static_cast<int>(
sequence_header.color_config.matrix_coefficients);
return false;
}
DCHECK(!sequence_header.color_config.is_monochrome);
#define COPY_SEQ_FIELD(a) \
va_pic_param.seq_info_fields.fields.a = sequence_header.a
#define COPY_SEQ_FIELD2(a, b) va_pic_param.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, sequence_header.color_config.is_monochrome);
COPY_SEQ_FIELD2(subsampling_x, sequence_header.color_config.subsampling_x);
COPY_SEQ_FIELD2(subsampling_y, sequence_header.color_config.subsampling_y);
COPY_SEQ_FIELD(film_grain_params_present);
#undef COPY_SEQ_FIELD
switch (sequence_header.color_config.color_range) {
case libgav1::kColorRangeStudio:
case libgav1::kColorRangeFull:
COPY_SEQ_FIELD2(color_range,
base::strict_cast<uint32_t>(
sequence_header.color_config.color_range));
break;
default:
NOTREACHED() << "Unknown color range: "
<< static_cast<int>(
sequence_header.color_config.color_range);
}
#undef COPY_SEQ_FILED2
const libgav1::ObuFrameHeader& frame_header = pic.frame_header;
const auto* vaapi_pic = static_cast<const VaapiAV1Picture*>(&pic);
DCHECK(!!vaapi_pic->display_va_surface() &&
!!vaapi_pic->reconstruct_va_surface());
if (frame_header.film_grain_params.apply_grain) {
DCHECK_NE(vaapi_pic->display_va_surface()->id(),
vaapi_pic->reconstruct_va_surface()->id())
<< "When using film grain synthesis, the display and reconstruct "
"surfaces"
<< " should be different.";
va_pic_param.current_frame = vaapi_pic->reconstruct_va_surface()->id();
va_pic_param.current_display_picture =
vaapi_pic->display_va_surface()->id();
} else {
DCHECK_EQ(vaapi_pic->display_va_surface()->id(),
vaapi_pic->reconstruct_va_surface()->id())
<< "When not using film grain synthesis, the display and reconstruct"
<< " surfaces should be the same.";
va_pic_param.current_frame = vaapi_pic->display_va_surface()->id();
va_pic_param.current_display_picture = VA_INVALID_SURFACE;
}
if (!base::CheckSub<int32_t>(frame_header.width, 1)
.AssignIfValid(&va_pic_param.frame_width_minus1) ||
!base::CheckSub<int32_t>(frame_header.height, 1)
.AssignIfValid(&va_pic_param.frame_height_minus1)) {
DLOG(ERROR) << "Invalid frame width and height"
<< ", width=" << frame_header.width
<< ", height=" << frame_header.height;
return false;
}
static_assert(libgav1::kNumReferenceFrameTypes == 8 &&
ARRAY_SIZE(va_pic_param.ref_frame_map) ==
libgav1::kNumReferenceFrameTypes,
"Invalid size of reference frames");
static_assert(libgav1::kNumInterReferenceFrameTypes == 7 &&
ARRAY_SIZE(frame_header.reference_frame_index) ==
libgav1::kNumInterReferenceFrameTypes &&
ARRAY_SIZE(va_pic_param.ref_frame_idx) ==
libgav1::kNumInterReferenceFrameTypes,
"Invalid size of reference frame indices");
for (size_t i = 0; i < libgav1::kNumReferenceFrameTypes; ++i) {
const auto* ref_pic =
static_cast<const VaapiAV1Picture*>(ref_frames[i].get());
va_pic_param.ref_frame_map[i] =
ref_pic ? ref_pic->reconstruct_va_surface()->id() : VA_INVALID_SURFACE;
}
// |va_pic_param.ref_frame_idx| doesn't need to be filled in for intra frames
// (it can be left zero initialized).
if (!libgav1::IsIntraFrame(frame_header.frame_type)) {
for (size_t i = 0; i < libgav1::kNumInterReferenceFrameTypes; ++i) {
const int8_t index = frame_header.reference_frame_index[i];
CHECK_GE(index, 0);
CHECK_LT(index, libgav1::kNumReferenceFrameTypes);
// AV1Decoder::CheckAndCleanUpReferenceFrames() ensures that
// |ref_frames[index]| is valid for all the reference frames needed by the
// current frame.
DCHECK_NE(va_pic_param.ref_frame_map[index], VA_INVALID_SURFACE);
va_pic_param.ref_frame_idx[i] = base::checked_cast<uint8_t>(index);
}
}
va_pic_param.primary_ref_frame =
base::checked_cast<uint8_t>(frame_header.primary_reference_frame);
va_pic_param.order_hint = frame_header.order_hint;
FillSegmentInfo(va_pic_param.seg_info, frame_header.segmentation);
FillFilmGrainInfo(va_pic_param.film_grain_info,
frame_header.film_grain_params);
if (!FillTileInfo(va_pic_param, frame_header.tile_info))
return false;
if (frame_header.use_superres) {
DVLOG(2) << "Upscaling (use_superres=1) is not supported";
return false;
}
auto& va_pic_info_fields = va_pic_param.pic_info_fields.bits;
va_pic_info_fields.uniform_tile_spacing_flag =
frame_header.tile_info.uniform_spacing;
#define COPY_PIC_FIELD(a) va_pic_info_fields.a = 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
switch (frame_header.frame_type) {
case libgav1::FrameType::kFrameKey:
case libgav1::FrameType::kFrameInter:
case libgav1::FrameType::kFrameIntraOnly:
case libgav1::FrameType::kFrameSwitch:
va_pic_info_fields.frame_type =
base::strict_cast<uint32_t>(frame_header.frame_type);
break;
default:
NOTREACHED() << "Unknown frame type: "
<< base::strict_cast<int>(frame_header.frame_type);
}
va_pic_info_fields.disable_cdf_update = !frame_header.enable_cdf_update;
va_pic_info_fields.disable_frame_end_update_cdf =
!frame_header.enable_frame_end_update_cdf;
static_assert(libgav1::kSuperResScaleNumerator == 8,
"Invalid libgav1::kSuperResScaleNumerator value");
CHECK_EQ(frame_header.superres_scale_denominator,
libgav1::kSuperResScaleNumerator);
va_pic_param.superres_scale_denominator =
frame_header.superres_scale_denominator;
DCHECK_LE(base::strict_cast<uint8_t>(frame_header.interpolation_filter), 4u)
<< "Unknown interpolation filter: "
<< base::strict_cast<int>(frame_header.interpolation_filter);
va_pic_param.interp_filter =
base::strict_cast<uint8_t>(frame_header.interpolation_filter);
FillQuantizationInfo(va_pic_param, frame_header.quantizer);
FillLoopFilterInfo(va_pic_param, frame_header.loop_filter);
FillModeControlInfo(va_pic_param, frame_header);
FillLoopRestorationInfo(va_pic_param, frame_header.loop_restoration);
FillGlobalMotionInfo(va_pic_param.wm, frame_header.global_motion);
FillCdefInfo(
va_pic_param, frame_header.cdef,
base::checked_cast<uint8_t>(sequence_header.color_config.bitdepth));
return true;
}
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;
}
} // namespace
AV1VaapiVideoDecoderDelegate::AV1VaapiVideoDecoderDelegate(
DecodeSurfaceHandler<VASurface>* const vaapi_dec,
scoped_refptr<VaapiWrapper> vaapi_wrapper,
ProtectedSessionUpdateCB on_protected_session_update_cb,
CdmContext* cdm_context,
EncryptionScheme encryption_scheme)
: VaapiVideoDecoderDelegate(vaapi_dec,
std::move(vaapi_wrapper),
std::move(on_protected_session_update_cb),
cdm_context,
encryption_scheme) {}
AV1VaapiVideoDecoderDelegate::~AV1VaapiVideoDecoderDelegate() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!picture_params_);
DCHECK(slice_params_.empty());
DCHECK(!crypto_params_);
}
scoped_refptr<AV1Picture> AV1VaapiVideoDecoderDelegate::CreateAV1Picture(
bool apply_grain) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const auto display_va_surface = vaapi_dec_->CreateSurface();
if (!display_va_surface)
return nullptr;
auto reconstruct_va_surface = display_va_surface;
if (apply_grain) {
// TODO(hiroh): When no surface is available here, this returns nullptr and
// |display_va_surface| is released. Since the surface is back to the pool,
// VaapiVideoDecoder will detect that there are surfaces available and will
// start another decode task which means that CreateSurface() might fail
// again for |reconstruct_va_surface| since only one surface might have gone
// back to the pool (the one for |display_va_surface|). We should avoid this
// loop for the sake of efficiency.
reconstruct_va_surface = vaapi_dec_->CreateSurface();
if (!reconstruct_va_surface)
return nullptr;
}
return base::MakeRefCounted<VaapiAV1Picture>(
std::move(display_va_surface), std::move(reconstruct_va_surface));
}
bool AV1VaapiVideoDecoderDelegate::OutputPicture(const AV1Picture& pic) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const auto* vaapi_pic = static_cast<const VaapiAV1Picture*>(&pic);
vaapi_dec_->SurfaceReady(vaapi_pic->display_va_surface(),
vaapi_pic->bitstream_id(), vaapi_pic->visible_rect(),
vaapi_pic->get_colorspace());
return true;
}
DecodeStatus AV1VaapiVideoDecoderDelegate::SubmitDecode(
const AV1Picture& pic,
const libgav1::ObuSequenceHeader& seq_header,
const AV1ReferenceFrameVector& ref_frames,
const libgav1::Vector<libgav1::TileBuffer>& tile_buffers,
base::span<const uint8_t> data) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
#if BUILDFLAG(IS_CHROMEOS_ASH)
const DecryptConfig* decrypt_config = pic.decrypt_config();
if (decrypt_config && !SetDecryptConfig(decrypt_config->Clone()))
return DecodeStatus::kFail;
bool uses_crypto = false;
std::vector<VAEncryptionSegmentInfo> encryption_segment_info;
VAEncryptionParameters crypto_param{};
if (IsEncryptedSession()) {
const ProtectedSessionState state = SetupDecryptDecode(
/*full_sample=*/false, data.size_bytes(), &crypto_param,
&encryption_segment_info,
decrypt_config ? decrypt_config->subsamples()
: std::vector<SubsampleEntry>());
if (state == ProtectedSessionState::kFailed) {
LOG(ERROR)
<< "SubmitDecode fails because we couldn't setup the protected "
"session";
return DecodeStatus::kFail;
} else if (state != ProtectedSessionState::kCreated) {
return DecodeStatus::kTryAgain;
}
uses_crypto = true;
if (!crypto_params_) {
crypto_params_ = vaapi_wrapper_->CreateVABuffer(
VAEncryptionParameterBufferType, sizeof(crypto_param));
if (!crypto_params_)
return DecodeStatus::kFail;
}
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
// libgav1 ensures that tile_columns is >= 0 and <= MAX_TILE_COLS.
DCHECK_LE(0, pic.frame_header.tile_info.tile_columns);
DCHECK_LE(pic.frame_header.tile_info.tile_columns, libgav1::kMaxTileColumns);
const size_t tile_columns =
base::checked_cast<size_t>(pic.frame_header.tile_info.tile_columns);
VADecPictureParameterBufferAV1 pic_param;
std::vector<VASliceParameterBufferAV1> slice_params;
if (!FillAV1PictureParameter(pic, seq_header, ref_frames, pic_param) ||
!FillAV1SliceParameters(tile_buffers, tile_columns, data, slice_params)) {
return DecodeStatus::kFail;
}
if (!picture_params_) {
picture_params_ = vaapi_wrapper_->CreateVABuffer(
VAPictureParameterBufferType, sizeof(pic_param));
if (!picture_params_)
return DecodeStatus::kFail;
}
if (slice_params_.size() != slice_params.size()) {
while (slice_params_.size() < slice_params.size()) {
slice_params_.push_back(vaapi_wrapper_->CreateVABuffer(
VASliceParameterBufferType, sizeof(VASliceParameterBufferAV1)));
if (!slice_params_.back()) {
slice_params_.clear();
return DecodeStatus::kFail;
}
}
slice_params_.resize(slice_params.size());
slice_params_.shrink_to_fit();
}
// TODO(hiroh): Don't submit the entire coded data to the buffer. Instead,
// only pass the data starting from the tile list OBU to reduce the size of
// the VA buffer. When this is changed, the encrypted subsample ranges must
// also be adjusted.
// Always re-create |encoded_data| because reusing the buffer causes horrific
// artifacts in decoded buffers. TODO(b/177028692): This seems to be a driver
// bug, fix it and reuse the buffer.
auto encoded_data =
vaapi_wrapper_->CreateVABuffer(VASliceDataBufferType, data.size_bytes());
if (!encoded_data)
return DecodeStatus::kFail;
std::vector<std::pair<VABufferID, VaapiWrapper::VABufferDescriptor>> buffers =
{{picture_params_->id(),
{picture_params_->type(), picture_params_->size(), &pic_param}},
{encoded_data->id(),
{encoded_data->type(), encoded_data->size(), data.data()}}};
for (size_t i = 0; i < slice_params.size(); ++i) {
buffers.push_back({slice_params_[i]->id(),
{slice_params_[i]->type(), slice_params_[i]->size(),
&slice_params[i]}});
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
if (uses_crypto) {
buffers.push_back(
{crypto_params_->id(),
{crypto_params_->type(), crypto_params_->size(), &crypto_param}});
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
const auto* vaapi_pic = static_cast<const VaapiAV1Picture*>(&pic);
const bool success = vaapi_wrapper_->MapAndCopyAndExecute(
vaapi_pic->reconstruct_va_surface()->id(), buffers);
if (!success && NeedsProtectedSessionRecovery())
return DecodeStatus::kTryAgain;
if (success && IsEncryptedSession())
ProtectedDecodedSucceeded();
return success ? DecodeStatus::kOk : DecodeStatus::kFail;
}
void AV1VaapiVideoDecoderDelegate::OnVAContextDestructionSoon() {
// Destroy the member ScopedVABuffers below since they refer to a VAContextID
// that will be destroyed soon.
picture_params_.reset();
slice_params_.clear();
crypto_params_.reset();
}
} // namespace media