blob: 262b3db1d7b597e84e459af6d578f4ea7b2e34c2 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// 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/vp8_decoder.h"
#include <va/va.h>
#include <algorithm>
#include <memory>
#include "media/filters/ivf_parser.h"
#include "media/gpu/vaapi/test/macros.h"
#include "media/parsers/vp8_parser.h"
namespace media {
namespace vaapi_test {
namespace {
template <typename To, typename From>
void CheckedMemcpy(To& to, From& from) {
static_assert(std::is_array<To>::value, "First parameter must be an array");
static_assert(std::is_array<From>::value,
"Second parameter must be an array");
static_assert(sizeof(to) == sizeof(from), "arrays must be of same size");
memcpy(&to, &from, sizeof(to));
}
} // namespace
Vp8Decoder::Vp8Decoder(std::unique_ptr<IvfParser> ivf_parser,
const VaapiDevice& va_device,
SharedVASurface::FetchPolicy fetch_policy)
: VideoDecoder(va_device, fetch_policy),
va_config_(
std::make_unique<ScopedVAConfig>(*va_device_,
VAProfile::VAProfileVP8Version0_3,
VA_RT_FORMAT_YUV420)),
vp8_parser_(std::make_unique<Vp8Parser>()),
ref_frames_(kNumVp8ReferenceBuffers),
ivf_parser_(std::move(ivf_parser)) {
std::fill(ref_frames_.begin(), ref_frames_.end(), nullptr);
}
Vp8Decoder::~Vp8Decoder() {
// 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();
last_decoded_surface_.reset();
ref_frames_.clear();
}
Vp8Decoder::ParseResult Vp8Decoder::ReadNextFrame(
Vp8FrameHeader& vp8_frame_header) {
IvfFrameHeader ivf_frame_header{};
const uint8_t* ivf_frame_data;
if (!ivf_parser_->ParseNextFrame(&ivf_frame_header, &ivf_frame_data))
return kEOStream;
const bool result = vp8_parser_->ParseFrame(
ivf_frame_data, ivf_frame_header.frame_size, &vp8_frame_header);
return result ? kOk : kError;
}
// The implementation of method is mostly lifted from vaapi_utils.h
// FillVP8DataStructures
// (https://source.chromium.org/chromium/chromium/src/+/main:media/gpu/vaapi/vaapi_utils.cc;l=195;drc=9d70e034c6a4c2b1ed56c94aace3f3c8d2b1f771).
void Vp8Decoder::FillVp8DataStructures(const Vp8FrameHeader& frame_hdr,
VAIQMatrixBufferVP8& iq_matrix_buf,
VAProbabilityDataBufferVP8& prob_buf,
VAPictureParameterBufferVP8& pic_param,
VASliceParameterBufferVP8& slice_param) {
const Vp8SegmentationHeader& sgmnt_hdr = frame_hdr.segmentation_hdr;
const Vp8QuantizationHeader& quant_hdr = frame_hdr.quantization_hdr;
static_assert(
std::size(decltype(iq_matrix_buf.quantization_index){}) == kMaxMBSegments,
"incorrect quantization matrix segment size");
static_assert(std::size(decltype(iq_matrix_buf.quantization_index){}[0]) == 6,
"incorrect quantization matrix Q index size");
for (size_t i = 0; i < kMaxMBSegments; ++i) {
int q = quant_hdr.y_ac_qi;
if (sgmnt_hdr.segmentation_enabled) {
if (sgmnt_hdr.segment_feature_mode ==
Vp8SegmentationHeader::FEATURE_MODE_ABSOLUTE) {
q = sgmnt_hdr.quantizer_update_value[i];
} else {
q += sgmnt_hdr.quantizer_update_value[i];
}
}
#define CLAMP_Q(q) std::clamp(q, 0, 127)
iq_matrix_buf.quantization_index[i][0] = CLAMP_Q(q);
iq_matrix_buf.quantization_index[i][1] = CLAMP_Q(q + quant_hdr.y_dc_delta);
iq_matrix_buf.quantization_index[i][2] = CLAMP_Q(q + quant_hdr.y2_dc_delta);
iq_matrix_buf.quantization_index[i][3] = CLAMP_Q(q + quant_hdr.y2_ac_delta);
iq_matrix_buf.quantization_index[i][4] = CLAMP_Q(q + quant_hdr.uv_dc_delta);
iq_matrix_buf.quantization_index[i][5] = CLAMP_Q(q + quant_hdr.uv_ac_delta);
#undef CLAMP_Q
}
const Vp8EntropyHeader& entr_hdr = frame_hdr.entropy_hdr;
CheckedMemcpy(prob_buf.dct_coeff_probs, entr_hdr.coeff_probs);
pic_param.frame_width = frame_hdr.width;
pic_param.frame_height = frame_hdr.height;
pic_param.last_ref_frame = ref_frames_[VP8_FRAME_LAST]
? ref_frames_[VP8_FRAME_LAST]->id()
: VA_INVALID_SURFACE;
pic_param.golden_ref_frame = ref_frames_[VP8_FRAME_GOLDEN]
? ref_frames_[VP8_FRAME_GOLDEN]->id()
: VA_INVALID_SURFACE;
pic_param.alt_ref_frame = ref_frames_[VP8_FRAME_ALTREF]
? ref_frames_[VP8_FRAME_ALTREF]->id()
: VA_INVALID_SURFACE;
const Vp8LoopFilterHeader& lf_hdr = frame_hdr.loopfilter_hdr;
#define FHDR_TO_PP_PF(a, b) pic_param.pic_fields.bits.a = (b)
FHDR_TO_PP_PF(key_frame, frame_hdr.IsKeyframe() ? 0 : 1);
FHDR_TO_PP_PF(version, frame_hdr.version);
FHDR_TO_PP_PF(segmentation_enabled, sgmnt_hdr.segmentation_enabled);
FHDR_TO_PP_PF(update_mb_segmentation_map,
sgmnt_hdr.update_mb_segmentation_map);
FHDR_TO_PP_PF(update_segment_feature_data,
sgmnt_hdr.update_segment_feature_data);
FHDR_TO_PP_PF(filter_type, lf_hdr.type);
FHDR_TO_PP_PF(sharpness_level, lf_hdr.sharpness_level);
FHDR_TO_PP_PF(loop_filter_adj_enable, lf_hdr.loop_filter_adj_enable);
FHDR_TO_PP_PF(mode_ref_lf_delta_update, lf_hdr.mode_ref_lf_delta_update);
FHDR_TO_PP_PF(sign_bias_golden, frame_hdr.sign_bias_golden);
FHDR_TO_PP_PF(sign_bias_alternate, frame_hdr.sign_bias_alternate);
FHDR_TO_PP_PF(mb_no_coeff_skip, frame_hdr.mb_no_skip_coeff);
FHDR_TO_PP_PF(loop_filter_disable, lf_hdr.level == 0);
#undef FHDR_TO_PP_PF
CheckedMemcpy(pic_param.mb_segment_tree_probs, sgmnt_hdr.segment_prob);
static_assert(std::extent<decltype(sgmnt_hdr.lf_update_value)>() ==
std::extent<decltype(pic_param.loop_filter_level)>(),
"loop filter level arrays mismatch");
for (size_t i = 0; i < std::size(sgmnt_hdr.lf_update_value); ++i) {
int lf_level = lf_hdr.level;
if (sgmnt_hdr.segmentation_enabled) {
if (sgmnt_hdr.segment_feature_mode ==
Vp8SegmentationHeader::FEATURE_MODE_ABSOLUTE) {
lf_level = sgmnt_hdr.lf_update_value[i];
} else {
lf_level += sgmnt_hdr.lf_update_value[i];
}
}
pic_param.loop_filter_level[i] = std::clamp(lf_level, 0, 63);
}
static_assert(
std::extent<decltype(lf_hdr.ref_frame_delta)>() ==
std::extent<decltype(pic_param.loop_filter_deltas_ref_frame)>(),
"loop filter deltas arrays size mismatch");
static_assert(std::extent<decltype(lf_hdr.mb_mode_delta)>() ==
std::extent<decltype(pic_param.loop_filter_deltas_mode)>(),
"loop filter deltas arrays size mismatch");
static_assert(std::extent<decltype(lf_hdr.ref_frame_delta)>() ==
std::extent<decltype(lf_hdr.mb_mode_delta)>(),
"loop filter deltas arrays size mismatch");
for (size_t i = 0; i < std::size(lf_hdr.ref_frame_delta); ++i) {
pic_param.loop_filter_deltas_ref_frame[i] = lf_hdr.ref_frame_delta[i];
pic_param.loop_filter_deltas_mode[i] = lf_hdr.mb_mode_delta[i];
}
#define FHDR_TO_PP(a) pic_param.a = frame_hdr.a
FHDR_TO_PP(prob_skip_false);
FHDR_TO_PP(prob_intra);
FHDR_TO_PP(prob_last);
FHDR_TO_PP(prob_gf);
#undef FHDR_TO_PP
CheckedMemcpy(pic_param.y_mode_probs, entr_hdr.y_mode_probs);
CheckedMemcpy(pic_param.uv_mode_probs, entr_hdr.uv_mode_probs);
CheckedMemcpy(pic_param.mv_probs, entr_hdr.mv_probs);
pic_param.bool_coder_ctx.range = frame_hdr.bool_dec_range;
pic_param.bool_coder_ctx.value = frame_hdr.bool_dec_value;
pic_param.bool_coder_ctx.count = frame_hdr.bool_dec_count;
slice_param.slice_data_size = frame_hdr.frame_size;
slice_param.slice_data_offset = frame_hdr.first_part_offset;
slice_param.slice_data_flag = VA_SLICE_DATA_FLAG_ALL;
slice_param.macroblock_offset = frame_hdr.macroblock_bit_offset;
// Number of DCT partitions plus control partition.
slice_param.num_of_partitions = frame_hdr.num_of_dct_partitions + 1;
// Per VAAPI, this size only includes the size of the macroblock data in
// the first partition (in bytes), so we have to subtract the header size.
slice_param.partition_size[0] =
frame_hdr.first_part_size - ((frame_hdr.macroblock_bit_offset + 7) / 8);
for (size_t i = 0; i < frame_hdr.num_of_dct_partitions; ++i)
slice_param.partition_size[i + 1] = frame_hdr.dct_partition_sizes[i];
}
// Based on update_reference_frames() in libvpx: vp8/encoder/onyx_if.c
void Vp8Decoder::RefreshReferenceSlots(Vp8FrameHeader& frame_hdr,
scoped_refptr<SharedVASurface> surface) {
if (frame_hdr.IsKeyframe()) {
ref_frames_[VP8_FRAME_LAST] = surface;
ref_frames_[VP8_FRAME_GOLDEN] = surface;
ref_frames_[VP8_FRAME_ALTREF] = surface;
return;
}
if (frame_hdr.refresh_alternate_frame) {
ref_frames_[VP8_FRAME_ALTREF] = surface;
} else {
switch (frame_hdr.copy_buffer_to_alternate) {
case Vp8FrameHeader::COPY_LAST_TO_ALT:
DCHECK(ref_frames_[Vp8RefType::VP8_FRAME_LAST]);
ref_frames_[VP8_FRAME_ALTREF] = ref_frames_[VP8_FRAME_LAST];
break;
case Vp8FrameHeader::COPY_GOLDEN_TO_ALT:
DCHECK(ref_frames_[Vp8RefType::VP8_FRAME_GOLDEN]);
ref_frames_[VP8_FRAME_ALTREF] = ref_frames_[VP8_FRAME_GOLDEN];
break;
case Vp8FrameHeader::NO_ALT_REFRESH:
DCHECK(ref_frames_[Vp8RefType::VP8_FRAME_ALTREF]);
break;
}
}
if (frame_hdr.refresh_golden_frame) {
ref_frames_[VP8_FRAME_GOLDEN] = surface;
} else {
switch (frame_hdr.copy_buffer_to_golden) {
case Vp8FrameHeader::COPY_LAST_TO_GOLDEN:
DCHECK(ref_frames_[Vp8RefType::VP8_FRAME_LAST]);
ref_frames_[VP8_FRAME_GOLDEN] = ref_frames_[VP8_FRAME_LAST];
break;
case Vp8FrameHeader::COPY_ALT_TO_GOLDEN:
DCHECK(ref_frames_[Vp8RefType::VP8_FRAME_ALTREF]);
ref_frames_[VP8_FRAME_GOLDEN] = ref_frames_[VP8_FRAME_ALTREF];
break;
case Vp8FrameHeader::NO_GOLDEN_REFRESH:
DCHECK(ref_frames_[Vp8RefType::VP8_FRAME_GOLDEN]);
break;
}
}
if (frame_hdr.refresh_last)
ref_frames_[VP8_FRAME_LAST] = surface;
else
DCHECK(ref_frames_[Vp8RefType::VP8_FRAME_LAST]);
}
VideoDecoder::Result Vp8Decoder::DecodeNextFrame() {
// Parse next frame from stream.
Vp8FrameHeader frame_hdr{};
const ParseResult parser_res = ReadNextFrame(frame_hdr);
if (parser_res == kEOStream)
return VideoDecoder::kEOStream;
LOG_ASSERT(parser_res == kOk) << "Failed to parse next frame.";
if (frame_hdr.IsKeyframe()) {
const gfx::Size new_size(frame_hdr.width, frame_hdr.height);
LOG_ASSERT(!new_size.IsEmpty()) << "New key frame size is empty.";
if (!va_context_ || new_size != va_context_->size()) {
va_context_ =
std::make_unique<ScopedVAContext>(*va_device_, *va_config_, new_size);
}
} else {
frame_hdr.height = va_context_->size().height();
frame_hdr.width = va_context_->size().width();
}
LOG_ASSERT(va_context_ != nullptr)
<< "VA Context not set. First frame was not a key frame.";
VLOG_IF(2, !frame_hdr.show_frame) << "not displaying frame";
last_decoded_frame_visible_ = frame_hdr.show_frame;
// Create surfaces for decode.
VASurfaceAttrib attribute{};
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);
// Create the VP8 data structures.
VAIQMatrixBufferVP8 iq_matrix_buf{};
VAProbabilityDataBufferVP8 prob_buf{};
VAPictureParameterBufferVP8 pic_param{};
VASliceParameterBufferVP8 slice_param{};
FillVp8DataStructures(frame_hdr, iq_matrix_buf, prob_buf, pic_param,
slice_param);
// Populate the VA API buffers.
std::vector<VABufferID> buffers;
VABufferID iq_matrix_id;
VAStatus res = vaCreateBuffer(va_device_->display(), va_context_->id(),
VAIQMatrixBufferType, sizeof(iq_matrix_buf), 1u,
nullptr, &iq_matrix_id);
VA_LOG_ASSERT(res, "vaCreateBuffer");
void* iq_matrix_data;
res = vaMapBuffer(va_device_->display(), iq_matrix_id, &iq_matrix_data);
VA_LOG_ASSERT(res, "vaMapBuffer");
memcpy(iq_matrix_data, &iq_matrix_buf, sizeof(iq_matrix_buf));
buffers.push_back(iq_matrix_id);
VABufferID prob_buffer_id;
res = vaCreateBuffer(va_device_->display(), va_context_->id(),
VAProbabilityBufferType, sizeof(prob_buf), 1u, nullptr,
&prob_buffer_id);
VA_LOG_ASSERT(res, "vaCreateBuffer");
void* prob_buffer_data;
res = vaMapBuffer(va_device_->display(), prob_buffer_id, &prob_buffer_data);
VA_LOG_ASSERT(res, "vaMapBuffer");
memcpy(prob_buffer_data, &prob_buf, sizeof(prob_buf));
buffers.push_back(prob_buffer_id);
VABufferID picture_params_id;
res = vaCreateBuffer(va_device_->display(), va_context_->id(),
VAPictureParameterBufferType, sizeof(pic_param), 1u,
nullptr, &picture_params_id);
VA_LOG_ASSERT(res, "vaCreateBuffer");
void* picture_params_data;
res = vaMapBuffer(va_device_->display(), picture_params_id,
&picture_params_data);
VA_LOG_ASSERT(res, "vaMapBuffer");
memcpy(picture_params_data, &pic_param, sizeof(pic_param));
buffers.push_back(picture_params_id);
VABufferID slice_params_id;
res = vaCreateBuffer(va_device_->display(), va_context_->id(),
VASliceParameterBufferType, sizeof(slice_param), 1u,
nullptr, &slice_params_id);
VA_LOG_ASSERT(res, "vaCreateBuffer");
void* slice_params_data;
res = vaMapBuffer(va_device_->display(), slice_params_id, &slice_params_data);
VA_LOG_ASSERT(res, "vaMapBuffer");
memcpy(slice_params_data, &slice_param, sizeof(pic_param));
buffers.push_back(slice_params_id);
VABufferID encoded_data_id;
res = vaCreateBuffer(va_device_->display(), va_context_->id(),
VASliceDataBufferType, frame_hdr.frame_size, 1u, nullptr,
&encoded_data_id);
VA_LOG_ASSERT(res, "vaCreateBuffer");
void* encoded_data;
res = vaMapBuffer(va_device_->display(), encoded_data_id, &encoded_data);
VA_LOG_ASSERT(res, "vaMapBuffer");
memcpy(encoded_data, frame_hdr.data, frame_hdr.frame_size);
buffers.push_back(encoded_data_id);
// Time to render!
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(),
base::checked_cast<int>(buffers.size()));
VA_LOG_ASSERT(res, "vaRenderPicture");
res = vaEndPicture(va_device_->display(), va_context_->id());
VA_LOG_ASSERT(res, "vaEndPicture");
RefreshReferenceSlots(frame_hdr, surface);
last_decoded_surface_ = surface;
for (const auto buffer_id : buffers) {
res = vaUnmapBuffer(va_device_->display(), buffer_id);
VA_LOG_ASSERT(res, "vaUnmapBuffer");
res = vaDestroyBuffer(va_device_->display(), buffer_id);
VA_LOG_ASSERT(res, "vaDestroyBuffer");
}
return VideoDecoder::kOk;
}
} // namespace vaapi_test
} // namespace media