#ifdef UNSAFE_BUFFERS_BUILD
#pragma allow_unsafe_buffers
#endif
#include "media/gpu/test/video_test_helpers.h"
#include <array>
#include <limits>
#include <numeric>
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/containers/span_writer.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/memory/shared_memory_mapping.h"
#include "base/numerics/byte_conversions.h"
#include "base/stl_util.h"
#include "base/types/to_address.h"
#include "components/viz/common/resources/shared_image_format.h"
#include "components/viz/common/resources/shared_image_format_utils.h"
#include "gpu/command_buffer/client/test_shared_image_interface.h"
#include "media/base/format_utils.h"
#include "media/base/video_codecs.h"
#include "media/base/video_frame_layout.h"
#include "media/gpu/test/raw_video.h"
#include "media/gpu/test/video_frame_helpers.h"
#include "media/media_buildflags.h"
#include "mojo/public/cpp/system/buffer.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/libyuv/include/libyuv/planar_functions.h"
#if BUILDFLAG(USE_LINUX_VIDEO_ACCELERATION)
#include "media/gpu/chromeos/platform_video_frame_utils.h"
#endif
namespace media::test {
namespace {
constexpr uint16_t kIvfFileHeaderSize = 32;
constexpr size_t kIvfFrameHeaderSize = 12;
constexpr size_t kNALUHeaderSize = 4;
constexpr size_t kNALUReducedHeaderSize = 3;
uint32_t GetReadFrameIndex(uint32_t frame_index,
bool reverse,
uint32_t num_frames) {
CHECK_GT(num_frames, 1u);
if (!reverse)
return frame_index % num_frames;
const size_t num_frames_in_loop = num_frames + num_frames - 2;
frame_index = frame_index % num_frames_in_loop;
if (frame_index < num_frames) {
return frame_index;
}
frame_index -= num_frames;
return num_frames - 2 - frame_index;
}
}
IvfFileHeader GetIvfFileHeader(base::span<const uint8_t> data) {
LOG_ASSERT(data.size_bytes() == 32u);
IvfFileHeader file_header;
base::byte_span_from_ref(file_header).copy_from(data.first<32u>());
return file_header;
}
IvfFrameHeader GetIvfFrameHeader(base::span<const uint8_t> data) {
LOG_ASSERT(data.size_bytes() == 12u);
IvfFrameHeader frame_header;
auto [frame_size, timestamp] = data.first<12u>().split_at<4u>();
frame_header.frame_size = base::U32FromLittleEndian(frame_size);
frame_header.timestamp = base::U64FromLittleEndian(timestamp);
return frame_header;
}
IvfWriter::IvfWriter(base::FilePath output_filepath) {
output_file_ = base::File(
output_filepath, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
LOG_ASSERT(output_file_.IsValid());
}
bool IvfWriter::WriteFileHeader(VideoCodec codec,
const gfx::Size& resolution,
uint32_t frame_rate,
uint32_t num_frames) {
constexpr uint16_t kVersion = 0;
char ivf_header[kIvfFileHeaderSize] = {};
auto writer = base::SpanWriter(base::as_writable_byte_span(ivf_header));
writer.Write(base::as_byte_span({'D', 'K', 'I', 'F'}));
writer.WriteU16LittleEndian(kVersion);
writer.WriteU16LittleEndian(kIvfFileHeaderSize);
switch (codec) {
case VideoCodec::kVP8:
writer.Write(base::as_byte_span({'V', 'P', '8', '0'}));
break;
case VideoCodec::kVP9:
writer.Write(base::as_byte_span({'V', 'P', '9', '0'}));
break;
case VideoCodec::kAV1:
writer.Write(base::as_byte_span({'A', 'V', '0', '1'}));
break;
default:
LOG(ERROR) << "Unknown codec: " << GetCodecName(codec);
return false;
}
writer.WriteU16LittleEndian(resolution.width());
writer.WriteU16LittleEndian(resolution.height());
writer.WriteU32LittleEndian(frame_rate);
writer.WriteU32LittleEndian(1u);
writer.WriteU32LittleEndian(num_frames);
writer.WriteU32LittleEndian(0u);
CHECK_EQ(writer.remaining(), 0u);
return output_file_.WriteAtCurrentPosAndCheck(base::as_byte_span(ivf_header));
}
bool IvfWriter::WriteFrame(uint32_t data_size,
uint64_t timestamp,
const uint8_t* data) {
std::array<char, kIvfFrameHeaderSize> ivf_frame_header = {};
memcpy(&ivf_frame_header[0], &data_size, sizeof(data_size));
memcpy(&ivf_frame_header[4], ×tamp, sizeof(timestamp));
if (!output_file_.WriteAtCurrentPosAndCheck(
base::as_byte_span(ivf_frame_header))) {
return false;
}
auto data_span = UNSAFE_TODO(base::span(data, data_size));
return output_file_.WriteAtCurrentPosAndCheck(data_span);
}
std::unique_ptr<EncodedDataHelper> EncodedDataHelper::Create(
base::span<const uint8_t> stream,
VideoCodec codec) {
if (codec == VideoCodec::kH264) {
return std::make_unique<EncodedDataHelperH26x>(std::move(stream), codec);
}
if (codec == VideoCodec::kHEVC) {
#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
return std::make_unique<EncodedDataHelperH265>(std::move(stream), codec);
#else
return std::make_unique<EncodedDataHelperH26x>(std::move(stream), codec);
#endif
}
if (codec == VideoCodec::kVP8 || codec == VideoCodec::kVP9 ||
codec == VideoCodec::kAV1) {
return std::make_unique<EncodedDataHelperIVF>(std::move(stream), codec);
}
NOTREACHED() << "Unsupported codec " << GetCodecName(codec);
}
bool EncodedDataHelper::HasConfigInfo(base::span<const uint8_t> data,
VideoCodec codec) {
CHECK(codec == media::VideoCodec::kH264 || codec == media::VideoCodec::kHEVC)
<< "Unsupported codec " << GetCodecName(codec);
return EncodedDataHelperH26x::HasConfigInfo(data, codec);
}
EncodedDataHelper::EncodedDataHelper(base::span<const uint8_t> stream,
VideoCodec codec)
: data_(std::string(reinterpret_cast<const char*>(stream.data()),
stream.size())),
codec_(codec) {}
EncodedDataHelper::~EncodedDataHelper() {
base::STLClearObject(&data_);
}
void EncodedDataHelper::Rewind() {
next_pos_to_parse_ = 0;
}
bool EncodedDataHelper::ReachEndOfStream() const {
return next_pos_to_parse_ == data_.size();
}
EncodedDataHelperH26x::EncodedDataHelperH26x(base::span<const uint8_t> stream,
VideoCodec codec)
: EncodedDataHelper(std::move(stream), codec) {}
bool EncodedDataHelperH26x::HasConfigInfo(base::span<const uint8_t> data,
VideoCodec codec) {
if (codec == media::VideoCodec::kH264) {
return (data.size() > kNALUReducedHeaderSize && data[0] == 0x0 &&
data[1] == 0x0 && data[2] == 0x1 &&
(data[kNALUReducedHeaderSize] & 0x1f) == 0x7) ||
(data.size() > kNALUHeaderSize && data[0] == 0x0 && data[1] == 0x0 &&
data[2] == 0x0 && data[3] == 0x1 &&
(data[kNALUHeaderSize] & 0x1f) == 0x7);
}
CHECK_EQ(codec, media::VideoCodec::kHEVC);
return (data.size() > kNALUReducedHeaderSize && data[0] == 0x0 &&
data[1] == 0x0 && data[2] == 0x1 &&
(data[kNALUReducedHeaderSize] & 0x7e) == 0x42) ||
(data.size() > kNALUHeaderSize && data[0] == 0x0 && data[1] == 0x0 &&
data[2] == 0x0 && data[3] == 0x1 &&
(data[kNALUHeaderSize] & 0x7e) == 0x42);
}
scoped_refptr<DecoderBuffer> EncodedDataHelperH26x::GetNextBuffer() {
if (next_pos_to_parse_ == 0) {
if (!LookForSPS()) {
next_pos_to_parse_ = 0;
return nullptr;
}
}
size_t start_pos = next_pos_to_parse_;
size_t next_nalu_pos = GetBytesForNextNALU(start_pos);
next_pos_to_parse_ = next_nalu_pos;
return DecoderBuffer::CopyFrom(
base::as_byte_span(data_).subspan(start_pos, next_nalu_pos - start_pos));
}
size_t EncodedDataHelperH26x::GetBytesForNextNALU(size_t start_pos) {
size_t pos = start_pos;
if (pos + kNALUHeaderSize > data_.size()) {
return pos;
}
if (!IsNALHeader(data_, pos)) {
ADD_FAILURE();
return std::numeric_limits<std::size_t>::max();
}
pos += kNALUHeaderSize;
while (pos + kNALUHeaderSize <= data_.size() && !IsNALHeader(data_, pos)) {
++pos;
}
if (pos + kNALUReducedHeaderSize >= data_.size()) {
pos = data_.size();
}
return pos;
}
bool EncodedDataHelperH26x::IsNALHeader(const std::string& data, size_t pos) {
return data[pos] == 0 && data[pos + 1] == 0 && data[pos + 2] == 0 &&
data[pos + 3] == 1;
}
bool EncodedDataHelperH26x::LookForSPS() {
while (next_pos_to_parse_ + kNALUHeaderSize < data_.size()) {
if (codec_ == VideoCodec::kH264 &&
(data_[next_pos_to_parse_ + kNALUHeaderSize] & 0x1f) == 0x7) {
return true;
} else if (codec_ == VideoCodec::kHEVC &&
(data_[next_pos_to_parse_ + kNALUHeaderSize] & 0x7e) == 0x42) {
return true;
}
next_pos_to_parse_ = GetBytesForNextNALU(next_pos_to_parse_);
}
return false;
}
#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
EncodedDataHelperH265::EncodedDataHelperH265(base::span<const uint8_t> stream,
VideoCodec codec)
: EncodedDataHelper(std::move(stream), codec),
h265_parser_(std::make_unique<H265Parser>()) {
h265_parser_->SetStream(base::as_byte_span(data_));
}
EncodedDataHelperH265::~EncodedDataHelperH265() = default;
scoped_refptr<DecoderBuffer> EncodedDataHelperH265::GetNextBuffer() {
CHECK(h265_parser_);
while (true) {
H265NALU nalu;
{
const auto result = h265_parser_->AdvanceToNextNALU(&nalu);
if (result == H265Parser::kEOStream) {
auto decoder_buffer = ReassembleNALUs(previous_nalus_);
previous_nalus_.clear();
return decoder_buffer;
}
if (result == H265Parser::kInvalidStream ||
result == H265Parser::kUnsupportedStream) {
LOG(ERROR) << __func__ << " Invalid or unsupported bitstream";
return nullptr;
}
CHECK_EQ(result, H265Parser::kOk);
}
CHECK_LE(nalu.data.data(),
reinterpret_cast<uint8_t*>(data_.data()) + data_.size());
CHECK_LE(nalu.data.data() + nalu.data.size(),
reinterpret_cast<uint8_t*>(data_.data()) + data_.size());
struct NALUMetadata nalu_metadata;
nalu_metadata.start_pointer =
reinterpret_cast<uint8_t*>(data_.data()) + next_pos_to_parse_;
nalu_metadata.start_index = next_pos_to_parse_;
nalu_metadata.header_size = nalu.data.data() - nalu_metadata.start_pointer;
nalu_metadata.size_with_header =
nalu_metadata.header_size + nalu.data.size();
VLOG(2) << "NALU (" << nalu.nal_unit_type << ") found " << nalu_metadata
<< " next_pos_to_parse_=" << next_pos_to_parse_;
next_pos_to_parse_ += nalu_metadata.size_with_header;
bool is_new_frame_boundary = false;
switch (nalu.nal_unit_type) {
case H265NALU::SPS_NUT: {
int sps_id;
const auto result = h265_parser_->ParseSPS(&sps_id);
if (result != H265Parser::kOk) {
LOG(ERROR) << __func__ << "Error parsing SPS";
return nullptr;
}
is_new_frame_boundary = true;
break;
}
case H265NALU::PPS_NUT: {
int pps_id;
const auto result = h265_parser_->ParsePPS(nalu, &pps_id);
if (result != H265Parser::kOk) {
LOG(ERROR) << __func__ << "Error parsing PPS";
return nullptr;
}
is_new_frame_boundary = true;
break;
}
case H265NALU::BLA_W_LP:
case H265NALU::BLA_W_RADL:
case H265NALU::BLA_N_LP:
case H265NALU::IDR_W_RADL:
case H265NALU::IDR_N_LP:
case H265NALU::TRAIL_N:
case H265NALU::TRAIL_R:
case H265NALU::TSA_N:
case H265NALU::TSA_R:
case H265NALU::STSA_N:
case H265NALU::STSA_R:
case H265NALU::RADL_N:
case H265NALU::RADL_R:
case H265NALU::RASL_N:
case H265NALU::RASL_R:
case H265NALU::CRA_NUT: {
auto current_slice_header = std::make_unique<H265SliceHeader>();
const auto result = h265_parser_->ParseSliceHeader(
nalu, current_slice_header.get(), previous_slice_header_.get());
if (result != H265Parser::kOk) {
LOG(ERROR) << __func__ << "Error parsing slice header";
return nullptr;
}
is_new_frame_boundary =
current_slice_header->first_slice_segment_in_pic_flag;
VLOG_IF(4, is_new_frame_boundary) << "|is_new_frame_boundary|";
previous_slice_header_ = std::move(current_slice_header);
break;
}
default:
is_new_frame_boundary = true;
break;
}
if (!is_new_frame_boundary) {
VLOG(3) << "Storing current NALU " << nalu_metadata;
previous_nalus_.push_back(std::move(nalu_metadata));
continue;
}
const bool is_stand_alone_NALU = nalu.nal_unit_type >= H265NALU::VPS_NUT;
if (previous_nalus_.empty() && is_stand_alone_NALU) {
VLOG(3) << "Returning current NALU " << nalu_metadata;
return DecoderBuffer::CopyFrom(base::as_byte_span(data_).subspan(
nalu_metadata.start_index, nalu_metadata.size_with_header));
}
if (previous_nalus_.empty()) {
VLOG(3) << "Storing current NALU " << nalu_metadata;
previous_nalus_.push_back(std::move(nalu_metadata));
continue;
}
auto decoder_buffer = ReassembleNALUs(previous_nalus_);
previous_nalus_.clear();
VLOG(3) << "Storing current NALU " << nalu_metadata;
previous_nalus_.push_back(std::move(nalu_metadata));
return decoder_buffer;
}
}
bool EncodedDataHelperH265::ReachEndOfStream() const {
return EncodedDataHelper::ReachEndOfStream() && previous_nalus_.empty();
}
void EncodedDataHelperH265::Rewind() {
h265_parser_->Reset();
h265_parser_->SetStream(base::as_byte_span(data_));
previous_nalus_.clear();
EncodedDataHelper::Rewind();
}
scoped_refptr<DecoderBuffer> EncodedDataHelperH265::ReassembleNALUs(
const std::vector<struct NALUMetadata>& nalus) {
if (nalus.empty()) {
return nullptr;
}
const size_t total_size = std::accumulate(
nalus.begin(), nalus.end(), 0, [](size_t total, NALUMetadata metadata) {
return total + metadata.size_with_header;
});
VLOG(4) << "Reassembling " << nalus.size() << " NALUs, " << total_size << "B";
return DecoderBuffer::CopyFrom(base::as_byte_span(data_).subspan(
nalus.begin()->start_index, total_size));
}
#endif
EncodedDataHelperIVF::EncodedDataHelperIVF(base::span<const uint8_t> stream,
VideoCodec codec)
: EncodedDataHelper(std::move(stream), codec) {}
scoped_refptr<DecoderBuffer> EncodedDataHelperIVF::GetNextBuffer() {
if (next_pos_to_parse_ == 0) {
if (data_.size() < kIvfFileHeaderSize) {
LOG(ERROR) << "data is too small";
return nullptr;
}
auto ivf_header = GetIvfFileHeader(base::span<const uint8_t>(
reinterpret_cast<const uint8_t*>(&data_[0]), kIvfFileHeaderSize));
if (strncmp(ivf_header.signature, "DKIF", kNALUHeaderSize) != 0) {
LOG(ERROR) << "Unexpected data encountered while parsing IVF header";
return nullptr;
}
next_pos_to_parse_ = kIvfFileHeaderSize;
}
std::vector<IvfFrame> ivf_frames;
auto frame_data = ReadNextIvfFrame();
if (!frame_data) {
LOG(ERROR) << "No IVF frame is available";
return nullptr;
}
ivf_frames.push_back(*frame_data);
if (codec_ == VideoCodec::kVP9 || codec_ == VideoCodec::kAV1) {
while (!ReachEndOfStream()) {
auto frame_header = GetNextIvfFrameHeader();
if (!frame_header) {
LOG(ERROR) << "No IVF frame header is available";
return nullptr;
}
if (frame_header->timestamp != ivf_frames[0].header.timestamp)
break;
frame_data = ReadNextIvfFrame();
if (!frame_data) {
LOG(ERROR) << "No IVF frame is available";
return nullptr;
}
ivf_frames.push_back(*frame_data);
}
}
if (ivf_frames.size() == 1) {
return DecoderBuffer::CopyFrom(
UNSAFE_TODO(base::span(ivf_frames[0].data.get(),
ivf_frames[0].header.frame_size)));
}
if (ivf_frames.size() > 3) {
LOG(ERROR) << "Number of IVF frames with same timestamps exceeds maximum of"
<< "3: ivf_frames.size()=" << ivf_frames.size();
return nullptr;
}
std::string data;
std::vector<uint32_t> frame_sizes;
frame_sizes.reserve(ivf_frames.size());
for (const IvfFrame& ivf : ivf_frames) {
data.append(reinterpret_cast<const char*>(ivf.data.get()),
ivf.header.frame_size);
frame_sizes.push_back(ivf.header.frame_size);
}
auto buffer = DecoderBuffer::CopyFrom(base::as_byte_span(data));
buffer->WritableSideData().spatial_layers = frame_sizes;
return buffer;
}
std::optional<IvfFrameHeader> EncodedDataHelperIVF::GetNextIvfFrameHeader()
const {
const size_t pos = next_pos_to_parse_;
if (pos + kIvfFrameHeaderSize > data_.size()) {
LOG(ERROR) << "Unexpected data encountered while parsing IVF frame header";
return std::nullopt;
}
return GetIvfFrameHeader(base::span<const uint8_t>(
reinterpret_cast<const uint8_t*>(&data_[pos]), kIvfFrameHeaderSize));
}
std::optional<IvfFrame> EncodedDataHelperIVF::ReadNextIvfFrame() {
auto frame_header = GetNextIvfFrameHeader();
if (!frame_header)
return std::nullopt;
const size_t pos = next_pos_to_parse_ + kIvfFrameHeaderSize;
if (pos + frame_header->frame_size > data_.size()) {
LOG(ERROR) << "Unexpected data encountered while parsing IVF frame header";
next_pos_to_parse_ = data_.size();
return std::nullopt;
}
next_pos_to_parse_ = pos + frame_header->frame_size;
return IvfFrame{*frame_header, reinterpret_cast<uint8_t*>(&data_[pos])};
}
struct AlignedDataHelper::VideoFrameData {
VideoFrameData() = default;
explicit VideoFrameData(base::ReadOnlySharedMemoryRegion shmem_region)
: shmem_region(std::move(shmem_region)) {}
explicit VideoFrameData(gfx::GpuMemoryBufferHandle gmb_handle)
: gmb_handle(std::move(gmb_handle)) {}
VideoFrameData(VideoFrameData&&) = default;
VideoFrameData& operator=(VideoFrameData&&) = default;
VideoFrameData(const VideoFrameData&) = delete;
VideoFrameData& operator=(const VideoFrameData&) = delete;
base::ReadOnlySharedMemoryRegion shmem_region;
gfx::GpuMemoryBufferHandle gmb_handle;
};
AlignedDataHelper::AlignedDataHelper(const RawVideo* video,
uint32_t num_read_frames,
bool reverse,
const gfx::Size& aligned_coded_size,
const gfx::Size& natural_size,
uint32_t frame_rate,
VideoFrame::StorageType storage_type)
: video_(video),
num_frames_(video_->NumFrames()),
num_read_frames_(num_read_frames),
reverse_(reverse),
create_frame_mode_(num_frames_ > RawVideo::kLimitedReadFrames
? CreateFrameMode::kOnDemand
: CreateFrameMode::kAllAtOnce),
storage_type_(storage_type),
visible_rect_(video_->VisibleRect()),
natural_size_(natural_size),
time_stamp_interval_(base::Seconds(0u)),
elapsed_frame_time_(base::Seconds(0u)),
test_sii_(base::MakeRefCounted<gpu::TestSharedImageInterface>()) {
UpdateFrameRate(frame_rate);
if (storage_type_ == VideoFrame::STORAGE_GPU_MEMORY_BUFFER) {
#if BUILDFLAG(USE_LINUX_VIDEO_ACCELERATION)
layout_ = GetPlatformVideoFrameLayout(
video_->PixelFormat(), aligned_coded_size,
gfx::BufferUsage::VEA_READ_CAMERA_AND_CPU_READ_WRITE);
#endif
} else {
layout_ = CreateVideoFrameLayout(video_->PixelFormat(), aligned_coded_size,
kPlatformBufferAlignment);
}
LOG_ASSERT(layout_) << "Failed creating VideoFrameLayout";
if (create_frame_mode_ == CreateFrameMode::kOnDemand) {
return;
}
video_frame_data_.resize(num_frames_);
for (size_t i = 0; i < num_frames_; i++) {
video_frame_data_[i] =
CreateVideoFrameData(storage_type_, video->GetFrame(i),
video_->FrameLayout(), *layout_, test_sii_.get());
}
LOG_ASSERT(video_frame_data_.size() == num_frames_)
<< "Failed to initialize VideoFrames";
}
AlignedDataHelper::~AlignedDataHelper() {}
void AlignedDataHelper::Rewind() {
frame_index_ = 0;
}
bool AlignedDataHelper::AtHeadOfStream() const {
return frame_index_ == 0;
}
bool AlignedDataHelper::AtEndOfStream() const {
return frame_index_ == num_read_frames_;
}
void AlignedDataHelper::UpdateFrameRate(uint32_t frame_rate) {
if (frame_rate == 0) {
time_stamp_interval_ = base::Seconds(0u);
} else {
time_stamp_interval_ = base::Seconds(1u) / frame_rate;
}
}
scoped_refptr<VideoFrame> AlignedDataHelper::GetNextFrame() {
LOG_ASSERT(!AtEndOfStream());
base::TimeDelta frame_timestamp;
if (time_stamp_interval_.is_zero())
frame_timestamp = base::TimeTicks::Now().since_origin();
else
frame_timestamp = elapsed_frame_time_;
elapsed_frame_time_ += time_stamp_interval_;
const uint32_t read_frame_index =
GetReadFrameIndex(frame_index_++, reverse_, num_frames_);
if (create_frame_mode_ == CreateFrameMode::kOnDemand) {
auto frame_data = video_->GetFrame(read_frame_index);
VideoFrameData video_frame_data =
CreateVideoFrameData(storage_type_, frame_data, video_->FrameLayout(),
*layout_, test_sii_.get());
return CreateVideoFrameFromVideoFrameData(video_frame_data,
frame_timestamp);
} else {
return CreateVideoFrameFromVideoFrameData(
video_frame_data_[read_frame_index], frame_timestamp);
}
}
scoped_refptr<VideoFrame> AlignedDataHelper::CreateVideoFrameFromVideoFrameData(
const VideoFrameData& video_frame_data,
base::TimeDelta frame_timestamp) const {
if (storage_type_ == VideoFrame::STORAGE_GPU_MEMORY_BUFFER) {
const auto& gmb_handle = video_frame_data.gmb_handle;
auto dup_handle = gmb_handle.Clone();
if (dup_handle.is_null()) {
LOG(ERROR) << "Failed duplicating GpuMemoryBufferHandle";
return nullptr;
}
std::optional<viz::SharedImageFormat> si_format =
media::VideoPixelFormatToSharedImageFormat(layout_->format());
if (!si_format) {
LOG(ERROR) << "Unexpected format: " << layout_->format();
return nullptr;
}
const auto si_usage = gpu::SHARED_IMAGE_USAGE_CPU_WRITE_ONLY |
gpu::SHARED_IMAGE_USAGE_DISPLAY_READ;
auto shared_image = test_sii_->CreateSharedImage(
{*si_format, layout_->coded_size(), gfx::ColorSpace(),
gpu::SharedImageUsageSet(si_usage), "AlignedDataHelper"},
gpu::kNullSurfaceHandle,
gfx::BufferUsage::VEA_READ_CAMERA_AND_CPU_READ_WRITE,
std::move(dup_handle));
if (!shared_image) {
LOG(ERROR) << "Failed to create a mappable shared image.";
return nullptr;
}
return media::VideoFrame::WrapMappableSharedImage(
std::move(shared_image), test_sii_->GenVerifiedSyncToken(),
base::NullCallback(), visible_rect_, natural_size_, frame_timestamp);
} else {
const auto& shmem_region = video_frame_data.shmem_region;
auto dup_region = shmem_region.Duplicate();
if (!dup_region.IsValid()) {
LOG(ERROR) << "Failed duplicating shmem region";
return nullptr;
}
base::ReadOnlySharedMemoryMapping mapping = shmem_region.Map();
uint8_t* buf = const_cast<uint8_t*>(mapping.GetMemoryAs<uint8_t>());
std::array<base::span<uint8_t>, VideoFrame::kMaxPlanes> data = {};
for (size_t i = 0; i < layout_->planes().size(); i++) {
data[i] = UNSAFE_TODO(base::span(buf + layout_->planes()[i].offset,
layout_->planes()[i].size));
}
auto frame = media::VideoFrame::WrapExternalYuvDataWithLayout(
*layout_, visible_rect_, natural_size_, data[0], data[1], data[2],
frame_timestamp);
DCHECK(frame);
frame->BackWithOwnedSharedMemory(std::move(dup_region), std::move(mapping));
return frame;
}
}
AlignedDataHelper::VideoFrameData AlignedDataHelper::CreateVideoFrameData(
VideoFrame::StorageType storage_type,
const RawVideo::FrameData& src_frame,
const VideoFrameLayout& src_layout,
const VideoFrameLayout& dst_layout,
gpu::TestSharedImageInterface* test_sii) {
LOG_ASSERT(gfx::Rect(dst_layout.coded_size())
.Contains(gfx::Rect(src_layout.coded_size())))
<< "The destination buffer resolution must not be smaller than the "
"source buffer resolution";
const VideoPixelFormat pixel_format = src_layout.format();
const gfx::Size& resolution = src_layout.coded_size();
if (storage_type == VideoFrame::STORAGE_GPU_MEMORY_BUFFER) {
#if BUILDFLAG(USE_LINUX_VIDEO_ACCELERATION)
auto memory_frame =
VideoFrame::CreateFrame(pixel_format, resolution, gfx::Rect(resolution),
resolution, base::TimeDelta());
LOG_ASSERT(!!memory_frame) << "Failed creating VideoFrame";
for (size_t i = 0; i < src_layout.planes().size(); i++) {
libyuv::CopyPlane(
src_frame.plane_addrs[i], src_frame.strides[i],
memory_frame->writable_data(i), memory_frame->stride(i),
VideoFrame::RowBytes(i, pixel_format, resolution.width()),
VideoFrame::Rows(i, pixel_format, resolution.height()));
}
auto frame =
CloneVideoFrame(memory_frame.get(), dst_layout, test_sii,
VideoFrame::STORAGE_GPU_MEMORY_BUFFER,
gfx::BufferUsage::VEA_READ_CAMERA_AND_CPU_READ_WRITE);
LOG_ASSERT(!!frame) << "Failed creating GpuMemoryBuffer VideoFrame";
auto gmb_handle = CreateGpuMemoryBufferHandle(frame.get());
LOG_ASSERT(!gmb_handle.is_null())
<< "Failed creating GpuMemoryBufferHandle";
return VideoFrameData(std::move(gmb_handle));
#else
NOTREACHED();
#endif
} else {
const size_t dst_video_frame_size =
dst_layout.planes().back().offset + dst_layout.planes().back().size;
auto mapped_region =
base::ReadOnlySharedMemoryRegion::Create(dst_video_frame_size);
LOG_ASSERT(mapped_region.IsValid()) << "Failed allocating a region";
base::WritableSharedMemoryMapping& mapping = mapped_region.mapping;
LOG_ASSERT(mapping.IsValid());
uint8_t* buffer = mapping.GetMemoryAs<uint8_t>();
for (size_t i = 0; i < src_layout.planes().size(); i++) {
auto dst_plane_layout = dst_layout.planes()[i];
uint8_t* dst_ptr = &buffer[dst_plane_layout.offset];
libyuv::CopyPlane(
src_frame.plane_addrs[i], src_frame.strides[i], dst_ptr,
dst_plane_layout.stride,
VideoFrame::RowBytes(i, pixel_format, resolution.width()),
VideoFrame::Rows(i, pixel_format, resolution.height()));
}
return VideoFrameData(std::move(mapped_region.region));
}
}
RawDataHelper::RawDataHelper(const RawVideo* video, bool reverse)
: video_(video), reverse_(reverse) {}
RawDataHelper::~RawDataHelper() = default;
scoped_refptr<const VideoFrame> RawDataHelper::GetFrame(size_t index) const {
uint32_t read_frame_index =
GetReadFrameIndex(index, reverse_, video_->NumFrames());
std::array<base::span<uint8_t>, VideoFrame::kMaxPlanes> frame_data = {};
const size_t num_planes = VideoFrame::NumPlanes(video_->PixelFormat());
RawVideo::FrameData src_frame = video_->GetFrame(read_frame_index);
for (size_t i = 0; i < num_planes; ++i) {
frame_data[i] =
UNSAFE_TODO(base::span(const_cast<uint8_t*>(src_frame.plane_addrs[i]),
video_->FrameLayout().planes()[i].size));
}
scoped_refptr<VideoFrame> video_frame =
VideoFrame::WrapExternalYuvDataWithLayout(
video_->FrameLayout(), video_->VisibleRect(),
video_->VisibleRect().size(), frame_data[0], frame_data[1],
frame_data[2], base::TimeTicks::Now().since_origin());
video_frame->AddDestructionObserver(
base::DoNothingWithBoundArgs(std::move(src_frame)));
return video_frame;
}
}