blob: 7f932014575f8bc8ddac70a244e2791475a7d0ed [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/test/vp9_decoder.h"
#include <va/va.h>
#include "media/filters/ivf_parser.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"
namespace media {
namespace vaapi_test {
namespace {
// Returns the VAProfile from |frame_hdr|.
VAProfile GetProfile(Vp9FrameHeader frame_hdr) {
switch (frame_hdr.profile) {
case 0:
return VAProfileVP9Profile0;
case 1:
break;
case 2:
LOG_ASSERT(frame_hdr.bit_depth == 10)
<< "Only 10-bit streams are supported for VP9 profile 2";
return VAProfileVP9Profile2;
case 3:
break;
default:
break;
}
LOG_ASSERT(false) << "Unsupported VP9 profile " << frame_hdr.profile;
return VAProfileNone;
}
// Returns the preferred VA_RT_FORMAT for the given |profile|.
// TODO(jchinlee): Have format dependent on bit depth, not profile.
unsigned int GetFormatForProfile(VAProfile profile) {
if (profile == VAProfileVP9Profile2 || profile == VAProfileVP9Profile3)
return VA_RT_FORMAT_YUV420_10;
return VA_RT_FORMAT_YUV420;
}
} // namespace
Vp9Decoder::Vp9Decoder(std::unique_ptr<IvfParser> ivf_parser,
const VaapiDevice& va_device,
SharedVASurface::FetchPolicy fetch_policy)
: VideoDecoder::VideoDecoder(std::move(ivf_parser),
va_device,
fetch_policy),
vp9_parser_(
std::make_unique<Vp9Parser>(/*parsing_compressed_header=*/false)),
ref_frames_(kVp9NumRefFrames) {}
Vp9Decoder::~Vp9Decoder() {
// 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();
last_decoded_surface_.reset();
}
Vp9Parser::Result Vp9Decoder::ReadNextFrame(Vp9FrameHeader& vp9_frame_header,
gfx::Size& size) {
while (true) {
std::unique_ptr<DecryptConfig> null_config;
Vp9Parser::Result res =
vp9_parser_->ParseNextFrame(&vp9_frame_header, &size, &null_config);
if (res == Vp9Parser::kEOStream) {
IvfFrameHeader ivf_frame_header{};
const uint8_t* ivf_frame_data;
if (!ivf_parser_->ParseNextFrame(&ivf_frame_header, &ivf_frame_data))
return Vp9Parser::kEOStream;
vp9_parser_->SetStream(ivf_frame_data, ivf_frame_header.frame_size,
/*stream_config=*/nullptr);
continue;
}
return res;
}
}
void Vp9Decoder::RefreshReferenceSlots(uint8_t refresh_frame_flags,
scoped_refptr<SharedVASurface> surface) {
const std::bitset<kVp9NumRefFrames> slots(refresh_frame_flags);
for (size_t i = 0; i < kVp9NumRefFrames; i++) {
if (slots[i])
ref_frames_[i] = surface;
}
}
VideoDecoder::Result Vp9Decoder::DecodeNextFrame() {
// Parse next frame from stream.
gfx::Size size;
Vp9FrameHeader frame_hdr{};
Vp9Parser::Result parser_res = ReadNextFrame(frame_hdr, size);
if (parser_res == Vp9Parser::kEOStream)
return VideoDecoder::kEOStream;
LOG_ASSERT(parser_res == Vp9Parser::kOk)
<< "Failed to parse next frame, got " << parser_res;
VLOG_IF(2, !frame_hdr.show_frame) << "not displaying frame";
last_decoded_frame_visible_ = frame_hdr.show_frame;
if (frame_hdr.show_existing_frame) {
last_decoded_surface_ = ref_frames_[frame_hdr.frame_to_show_map_idx];
LOG_ASSERT(last_decoded_surface_)
<< "got invalid surface as existing frame to decode";
VLOG(2) << "ref_frame: " << last_decoded_surface_->id();
return VideoDecoder::kOk;
}
const VAProfile profile = GetProfile(frame_hdr);
// Note: some streams may fail to decode; see
// https://source.chromium.org/chromium/chromium/src/+/main:media/gpu/vp9_decoder.cc;l=249-285;drc=3893688a88eb1b4cf39e346fd8f8c743ad255469
if (!va_config_ || va_config_->profile() != profile) {
va_context_.reset();
va_config_ = std::make_unique<ScopedVAConfig>(va_device_, profile,
GetFormatForProfile(profile));
}
// [Re]create context for decode.
// A resolution change may occur on a frame that is neither keyframe nor
// intra-only, i.e. may refer to earlier frames. If the earlier referred frame
// is larger than the new frame, consequently, do *not* recreate the context.
// See also
// https://cgit.freedesktop.org/gstreamer/gstreamer-vaapi/tree/gst-libs/gst/vaapi/gstvaapidecoder_vp9.c?h=1.18#n652
if (!va_context_ || va_context_->size().width() < size.width() ||
va_context_->size().height() < size.height()) {
va_context_ =
std::make_unique<ScopedVAContext>(va_device_, *va_config_, size);
}
// 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(), size, attribute);
const Vp9Parser::Context& context = vp9_parser_->context();
const Vp9SegmentationParams& seg = context.segmentation();
const Vp9LoopFilterParams& lf = context.loop_filter();
// Set up buffer for picture level parameters.
VADecPictureParameterBufferVP9 pic_param{};
pic_param.frame_width = base::checked_cast<uint16_t>(frame_hdr.frame_width);
pic_param.frame_height = base::checked_cast<uint16_t>(frame_hdr.frame_height);
CHECK_EQ(kVp9NumRefFrames, base::size(pic_param.reference_frames));
CHECK_EQ(kVp9NumRefFrames, ref_frames_.size());
for (size_t i = 0; i < base::size(pic_param.reference_frames); ++i) {
pic_param.reference_frames[i] =
ref_frames_[i] ? ref_frames_[i]->id() : VA_INVALID_SURFACE;
}
#define FHDR_TO_PP_PF1(a) pic_param.pic_fields.bits.a = frame_hdr.a
#define FHDR_TO_PP_PF2(a, b) pic_param.pic_fields.bits.a = b
FHDR_TO_PP_PF2(subsampling_x, frame_hdr.subsampling_x == 1);
FHDR_TO_PP_PF2(subsampling_y, frame_hdr.subsampling_y == 1);
FHDR_TO_PP_PF2(frame_type, frame_hdr.IsKeyframe() ? 0 : 1);
FHDR_TO_PP_PF1(show_frame);
FHDR_TO_PP_PF1(error_resilient_mode);
FHDR_TO_PP_PF1(intra_only);
FHDR_TO_PP_PF1(allow_high_precision_mv);
FHDR_TO_PP_PF2(mcomp_filter_type, frame_hdr.interpolation_filter);
FHDR_TO_PP_PF1(frame_parallel_decoding_mode);
FHDR_TO_PP_PF1(reset_frame_context);
FHDR_TO_PP_PF1(refresh_frame_context);
FHDR_TO_PP_PF2(frame_context_idx, frame_hdr.frame_context_idx_to_save_probs);
FHDR_TO_PP_PF2(segmentation_enabled, seg.enabled);
FHDR_TO_PP_PF2(segmentation_temporal_update, seg.temporal_update);
FHDR_TO_PP_PF2(segmentation_update_map, seg.update_map);
FHDR_TO_PP_PF2(last_ref_frame, frame_hdr.ref_frame_idx[0]);
FHDR_TO_PP_PF2(last_ref_frame_sign_bias,
frame_hdr.ref_frame_sign_bias[Vp9RefType::VP9_FRAME_LAST]);
FHDR_TO_PP_PF2(golden_ref_frame, frame_hdr.ref_frame_idx[1]);
FHDR_TO_PP_PF2(golden_ref_frame_sign_bias,
frame_hdr.ref_frame_sign_bias[Vp9RefType::VP9_FRAME_GOLDEN]);
FHDR_TO_PP_PF2(alt_ref_frame, frame_hdr.ref_frame_idx[2]);
FHDR_TO_PP_PF2(alt_ref_frame_sign_bias,
frame_hdr.ref_frame_sign_bias[Vp9RefType::VP9_FRAME_ALTREF]);
FHDR_TO_PP_PF2(lossless_flag, frame_hdr.quant_params.IsLossless());
#undef FHDR_TO_PP_PF2
#undef FHDR_TO_PP_PF1
pic_param.filter_level = lf.level;
pic_param.sharpness_level = lf.sharpness;
pic_param.log2_tile_rows = frame_hdr.tile_rows_log2;
pic_param.log2_tile_columns = frame_hdr.tile_cols_log2;
pic_param.frame_header_length_in_bytes = frame_hdr.uncompressed_header_size;
pic_param.first_partition_size = frame_hdr.header_size_in_bytes;
SafeArrayMemcpy(pic_param.mb_segment_tree_probs, seg.tree_probs);
SafeArrayMemcpy(pic_param.segment_pred_probs, seg.pred_probs);
pic_param.profile = frame_hdr.profile;
pic_param.bit_depth = frame_hdr.bit_depth;
DCHECK((pic_param.profile == 0 && pic_param.bit_depth == 8) ||
(pic_param.profile == 2 && pic_param.bit_depth == 10));
std::vector<VABufferID> buffers;
VABufferID buffer_id;
VAStatus res = vaCreateBuffer(
va_device_.display(), va_context_->id(), VAPictureParameterBufferType,
sizeof(VADecPictureParameterBufferVP9), 1u, &pic_param, &buffer_id);
VA_LOG_ASSERT(res, "vaCreateBuffer");
buffers.push_back(buffer_id);
// Set up buffer for slice decoding.
VASliceParameterBufferVP9 slice_param{};
slice_param.slice_data_size = frame_hdr.frame_size;
slice_param.slice_data_offset = 0;
slice_param.slice_data_flag = VA_SLICE_DATA_FLAG_ALL;
for (size_t i = 0; i < base::size(slice_param.seg_param); ++i) {
VASegmentParameterVP9& seg_param = slice_param.seg_param[i];
#define SEG_TO_SP_SF(a, b) seg_param.segment_flags.fields.a = b
SEG_TO_SP_SF(
segment_reference_enabled,
seg.FeatureEnabled(i, Vp9SegmentationParams::SEG_LVL_REF_FRAME));
SEG_TO_SP_SF(segment_reference,
seg.FeatureData(i, Vp9SegmentationParams::SEG_LVL_REF_FRAME));
SEG_TO_SP_SF(segment_reference_skipped,
seg.FeatureEnabled(i, Vp9SegmentationParams::SEG_LVL_SKIP));
#undef SEG_TO_SP_SF
SafeArrayMemcpy(seg_param.filter_level, lf.lvl[i]);
seg_param.luma_dc_quant_scale = seg.y_dequant[i][0];
seg_param.luma_ac_quant_scale = seg.y_dequant[i][1];
seg_param.chroma_dc_quant_scale = seg.uv_dequant[i][0];
seg_param.chroma_ac_quant_scale = seg.uv_dequant[i][1];
}
res = vaCreateBuffer(
va_device_.display(), va_context_->id(), VASliceParameterBufferType,
sizeof(VASliceParameterBufferVP9), 1u, &slice_param, &buffer_id);
VA_LOG_ASSERT(res, "vaCreateBuffer");
buffers.push_back(buffer_id);
// Set up buffer for frame header.
res = vaCreateBuffer(va_device_.display(), va_context_->id(),
VASliceDataBufferType, frame_hdr.frame_size, 1u,
const_cast<uint8_t*>(frame_hdr.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");
last_decoded_surface_ = surface;
RefreshReferenceSlots(frame_hdr.refresh_frame_flags, surface);
for (auto id : buffers)
vaDestroyBuffer(va_device_.display(), id);
buffers.clear();
return VideoDecoder::kOk;
}
} // namespace vaapi_test
} // namespace media