blob: 7e72d90321412982c14acc62ee75f5afd80bb35b [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 "third_party/blink/renderer/modules/webcodecs/video_frame_layout.h"
#include <stdint.h>
#include <array>
#include <vector>
#include "base/numerics/checked_math.h"
#include "media/base/limits.h"
#include "media/base/video_frame.h"
#include "media/base/video_types.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_plane_layout.h"
#include "third_party/blink/renderer/modules/webcodecs/video_frame_rect_util.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/wtf/wtf_size_t.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
namespace blink {
VideoFrameLayout::VideoFrameLayout() : format_(media::PIXEL_FORMAT_UNKNOWN) {}
VideoFrameLayout::VideoFrameLayout(media::VideoPixelFormat format,
const gfx::Size& coded_size,
ExceptionState& exception_state)
: format_(format), coded_size_(coded_size) {
DCHECK_LE(coded_size_.width(), media::limits::kMaxDimension);
DCHECK_LE(coded_size_.height(), media::limits::kMaxDimension);
const wtf_size_t num_planes =
static_cast<wtf_size_t>(media::VideoFrame::NumPlanes(format_));
uint32_t offset = 0;
for (wtf_size_t i = 0; i < num_planes; i++) {
const gfx::Size sample_size = media::VideoFrame::SampleSize(format_, i);
const uint32_t sample_bytes =
media::VideoFrame::BytesPerElement(format_, i);
const uint32_t columns =
PlaneSize(coded_size_.width(), sample_size.width());
const uint32_t rows = PlaneSize(coded_size_.height(), sample_size.height());
const uint32_t stride = columns * sample_bytes;
planes_.push_back(Plane{offset, stride});
offset += stride * rows;
}
}
VideoFrameLayout::VideoFrameLayout(
media::VideoPixelFormat format,
const gfx::Size& coded_size,
const HeapVector<Member<PlaneLayout>>& layout,
ExceptionState& exception_state)
: format_(format), coded_size_(coded_size) {
DCHECK_LE(coded_size_.width(), media::limits::kMaxDimension);
DCHECK_LE(coded_size_.height(), media::limits::kMaxDimension);
const wtf_size_t num_planes =
static_cast<wtf_size_t>(media::VideoFrame::NumPlanes(format_));
if (layout.size() != num_planes) {
exception_state.ThrowTypeError(
String::Format("Invalid layout. Expected %u planes, found %u.",
num_planes, layout.size()));
return;
}
std::array<uint32_t, media::VideoFrame::kMaxPlanes> end = {};
for (wtf_size_t i = 0; i < num_planes; i++) {
const gfx::Size sample_size = media::VideoFrame::SampleSize(format_, i);
const uint32_t sample_bytes =
media::VideoFrame::BytesPerElement(format_, i);
const uint32_t columns =
PlaneSize(coded_size_.width(), sample_size.width());
const uint32_t rows = PlaneSize(coded_size_.height(), sample_size.height());
const uint32_t offset = layout[i]->offset();
const uint32_t stride = layout[i]->stride();
// Each row must fit inside the stride.
const uint32_t min_stride = columns * sample_bytes;
if (stride < min_stride) {
exception_state.ThrowTypeError(
String::Format("Invalid layout. Expected plane %u to have stride at "
"least %u, found %u.",
i, min_stride, stride));
return;
}
const auto checked_bytes = base::CheckedNumeric<uint32_t>(stride) * rows;
const auto checked_end = checked_bytes + offset;
// Each plane size must not overflow int for compatibility with libyuv.
// There are probably tighter bounds we could enforce.
if (!checked_bytes.Cast<int>().IsValid()) {
exception_state.ThrowTypeError(String::Format(
"Invalid layout. Plane %u with stride %u and height %u exceeds "
"implementation limit.",
i, stride, rows));
return;
}
// The size of the buffer must not overflow uint32_t for compatibility with
// ArrayBuffer.
if (!checked_end.IsValid()) {
exception_state.ThrowTypeError(
String::Format("Invalid layout. Plane %u with offset %u and stride "
"%u exceeds implementation limit.",
i, offset, stride));
return;
}
// Planes must not overlap.
end[i] = checked_end.ValueOrDie();
for (wtf_size_t j = 0; j < i; j++) {
if (offset < end[j] && planes_[j].offset < end[i]) {
exception_state.ThrowTypeError(String::Format(
"Invalid layout. Plane %u overlaps with plane %u.", i, j));
return;
}
}
planes_.push_back(Plane{offset, stride});
}
}
media::VideoFrameLayout VideoFrameLayout::ToMediaLayout() {
std::vector<media::ColorPlaneLayout> planes;
planes.reserve(planes_.size());
for (wtf_size_t i = 0; i < planes_.size(); i++) {
auto& plane = planes_[i];
const size_t height =
media::VideoFrame::PlaneSizeInSamples(format_, i, coded_size_).height();
const size_t plane_size = plane.stride * height;
planes.emplace_back(plane.stride, plane.offset, plane_size);
}
return media::VideoFrameLayout::CreateWithPlanes(format_, coded_size_,
std::move(planes))
.value();
}
uint32_t VideoFrameLayout::Size() const {
uint32_t size = 0;
for (wtf_size_t i = 0; i < planes_.size(); i++) {
const gfx::Size sample_size = media::VideoFrame::SampleSize(format_, i);
const uint32_t rows = PlaneSize(coded_size_.height(), sample_size.height());
const uint32_t end = planes_[i].offset + planes_[i].stride * rows;
size = std::max(size, end);
}
return size;
}
media::VideoPixelFormat VideoFrameLayout::Format() const {
return format_;
}
wtf_size_t VideoFrameLayout::NumPlanes() const {
return planes_.size();
}
uint32_t VideoFrameLayout::Offset(wtf_size_t i) const {
return planes_[i].offset;
}
uint32_t VideoFrameLayout::Stride(wtf_size_t i) const {
return planes_[i].stride;
}
} // namespace blink