822 lines
28 KiB
C++
822 lines
28 KiB
C++
// Copyright (c) 2012 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.
|
|
// Note: ported from Chromium commit head: 602bc8fa60fa
|
|
// Note: only necessary functions are ported.
|
|
// Note: some shared memory-related functionality here is no longer present in
|
|
// Chromium.
|
|
|
|
#include "video_frame.h"
|
|
|
|
#include <algorithm>
|
|
#include <climits>
|
|
#include <limits>
|
|
#include <numeric>
|
|
#include <utility>
|
|
|
|
#include "base/atomic_sequence_num.h"
|
|
#include "base/bind.h"
|
|
#include "base/bits.h"
|
|
#include "base/callback_helpers.h"
|
|
#include "base/logging.h"
|
|
#include "base/memory/aligned_memory.h"
|
|
#include "base/stl_util.h"
|
|
#include "base/strings/string_piece.h"
|
|
#include "base/strings/stringprintf.h"
|
|
#include "base/time/time.h"
|
|
#include "media_limits.h"
|
|
|
|
namespace media {
|
|
|
|
// Note: moved from Chromium media/base/timestamp_constants.h
|
|
// Indicates an invalid or missing timestamp.
|
|
constexpr base::TimeDelta kNoTimestamp =
|
|
base::TimeDelta::FromMicroseconds(std::numeric_limits<int64_t>::min());
|
|
|
|
namespace {
|
|
|
|
// Helper to provide Rect::Intersect() as an expression.
|
|
Rect Intersection(Rect a, const Rect& b) {
|
|
a.Intersect(b);
|
|
return a;
|
|
}
|
|
|
|
// Note: moved from Chromium base/bits.h which is not included in libchrome.
|
|
// Round down |size| to a multiple of alignment, which must be a power of two.
|
|
size_t AlignDown(size_t size, size_t alignment) {
|
|
DCHECK(base::bits::IsPowerOfTwo(alignment));
|
|
return size & ~(alignment - 1);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// Static constexpr class for generating unique identifiers for each VideoFrame.
|
|
static base::AtomicSequenceNumber g_unique_id_generator;
|
|
|
|
static std::string StorageTypeToString(
|
|
const VideoFrame::StorageType storage_type) {
|
|
switch (storage_type) {
|
|
case VideoFrame::STORAGE_UNKNOWN:
|
|
return "UNKNOWN";
|
|
case VideoFrame::STORAGE_OPAQUE:
|
|
return "OPAQUE";
|
|
case VideoFrame::STORAGE_UNOWNED_MEMORY:
|
|
return "UNOWNED_MEMORY";
|
|
case VideoFrame::STORAGE_OWNED_MEMORY:
|
|
return "OWNED_MEMORY";
|
|
case VideoFrame::STORAGE_SHMEM:
|
|
return "SHMEM";
|
|
case VideoFrame::STORAGE_DMABUFS:
|
|
return "DMABUFS";
|
|
case VideoFrame::STORAGE_MOJO_SHARED_BUFFER:
|
|
return "MOJO_SHARED_BUFFER";
|
|
}
|
|
|
|
NOTREACHED() << "Invalid StorageType provided: " << storage_type;
|
|
return "INVALID";
|
|
}
|
|
|
|
// static
|
|
bool VideoFrame::IsStorageTypeMappable(VideoFrame::StorageType storage_type) {
|
|
return
|
|
// This is not strictly needed but makes explicit that, at VideoFrame
|
|
// level, DmaBufs are not mappable from userspace.
|
|
storage_type != VideoFrame::STORAGE_DMABUFS &&
|
|
(storage_type == VideoFrame::STORAGE_UNOWNED_MEMORY ||
|
|
storage_type == VideoFrame::STORAGE_OWNED_MEMORY ||
|
|
storage_type == VideoFrame::STORAGE_SHMEM ||
|
|
storage_type == VideoFrame::STORAGE_MOJO_SHARED_BUFFER);
|
|
}
|
|
|
|
// If it is required to allocate aligned to multiple-of-two size overall for the
|
|
// frame of pixel |format|.
|
|
static bool RequiresEvenSizeAllocation(VideoPixelFormat format) {
|
|
switch (format) {
|
|
case PIXEL_FORMAT_ARGB:
|
|
case PIXEL_FORMAT_XRGB:
|
|
case PIXEL_FORMAT_RGB24:
|
|
case PIXEL_FORMAT_Y16:
|
|
case PIXEL_FORMAT_ABGR:
|
|
case PIXEL_FORMAT_XBGR:
|
|
case PIXEL_FORMAT_XR30:
|
|
case PIXEL_FORMAT_XB30:
|
|
case PIXEL_FORMAT_BGRA:
|
|
return false;
|
|
case PIXEL_FORMAT_NV12:
|
|
case PIXEL_FORMAT_NV21:
|
|
case PIXEL_FORMAT_I420:
|
|
case PIXEL_FORMAT_MJPEG:
|
|
case PIXEL_FORMAT_YUY2:
|
|
case PIXEL_FORMAT_YV12:
|
|
case PIXEL_FORMAT_I422:
|
|
case PIXEL_FORMAT_I444:
|
|
case PIXEL_FORMAT_YUV420P9:
|
|
case PIXEL_FORMAT_YUV422P9:
|
|
case PIXEL_FORMAT_YUV444P9:
|
|
case PIXEL_FORMAT_YUV420P10:
|
|
case PIXEL_FORMAT_YUV422P10:
|
|
case PIXEL_FORMAT_YUV444P10:
|
|
case PIXEL_FORMAT_YUV420P12:
|
|
case PIXEL_FORMAT_YUV422P12:
|
|
case PIXEL_FORMAT_YUV444P12:
|
|
case PIXEL_FORMAT_I420A:
|
|
case PIXEL_FORMAT_P016LE:
|
|
return true;
|
|
case PIXEL_FORMAT_UNKNOWN:
|
|
break;
|
|
}
|
|
NOTREACHED() << "Unsupported video frame format: " << format;
|
|
return false;
|
|
}
|
|
|
|
// Creates VideoFrameLayout for tightly packed frame.
|
|
static base::Optional<VideoFrameLayout> GetDefaultLayout(
|
|
VideoPixelFormat format,
|
|
const Size& coded_size) {
|
|
std::vector<ColorPlaneLayout> planes;
|
|
|
|
switch (format) {
|
|
case PIXEL_FORMAT_I420: {
|
|
int uv_width = (coded_size.width() + 1) / 2;
|
|
int uv_height = (coded_size.height() + 1) / 2;
|
|
int uv_stride = uv_width;
|
|
int uv_size = uv_stride * uv_height;
|
|
planes = std::vector<ColorPlaneLayout>{
|
|
ColorPlaneLayout(coded_size.width(), 0, coded_size.GetArea()),
|
|
ColorPlaneLayout(uv_stride, coded_size.GetArea(), uv_size),
|
|
ColorPlaneLayout(uv_stride, coded_size.GetArea() + uv_size, uv_size),
|
|
};
|
|
break;
|
|
}
|
|
|
|
case PIXEL_FORMAT_Y16:
|
|
planes = std::vector<ColorPlaneLayout>{ColorPlaneLayout(
|
|
coded_size.width() * 2, 0, coded_size.GetArea() * 2)};
|
|
break;
|
|
|
|
case PIXEL_FORMAT_ARGB:
|
|
planes = std::vector<ColorPlaneLayout>{ColorPlaneLayout(
|
|
coded_size.width() * 4, 0, coded_size.GetArea() * 4)};
|
|
break;
|
|
|
|
case PIXEL_FORMAT_NV12: {
|
|
int uv_width = (coded_size.width() + 1) / 2;
|
|
int uv_height = (coded_size.height() + 1) / 2;
|
|
int uv_stride = uv_width * 2;
|
|
int uv_size = uv_stride * uv_height;
|
|
planes = std::vector<ColorPlaneLayout>{
|
|
ColorPlaneLayout(coded_size.width(), 0, coded_size.GetArea()),
|
|
ColorPlaneLayout(uv_stride, coded_size.GetArea(), uv_size),
|
|
};
|
|
break;
|
|
}
|
|
|
|
default:
|
|
// TODO(miu): This function should support any pixel format.
|
|
// http://crbug.com/555909 .
|
|
DLOG(ERROR)
|
|
<< "Only PIXEL_FORMAT_I420, PIXEL_FORMAT_Y16, PIXEL_FORMAT_NV12, "
|
|
"and PIXEL_FORMAT_ARGB formats are supported: "
|
|
<< VideoPixelFormatToString(format);
|
|
return base::nullopt;
|
|
}
|
|
|
|
return VideoFrameLayout::CreateWithPlanes(format, coded_size, planes);
|
|
}
|
|
|
|
// static
|
|
bool VideoFrame::IsValidConfig(VideoPixelFormat format,
|
|
StorageType storage_type,
|
|
const Size& coded_size,
|
|
const Rect& visible_rect,
|
|
const Size& natural_size) {
|
|
// Check maximum limits for all formats.
|
|
int coded_size_area = coded_size.GetCheckedArea().ValueOrDefault(INT_MAX);
|
|
int natural_size_area = natural_size.GetCheckedArea().ValueOrDefault(INT_MAX);
|
|
static_assert(limits::kMaxCanvas < INT_MAX, "");
|
|
if (coded_size_area > limits::kMaxCanvas ||
|
|
coded_size.width() > limits::kMaxDimension ||
|
|
coded_size.height() > limits::kMaxDimension || visible_rect.x() < 0 ||
|
|
visible_rect.y() < 0 || visible_rect.right() > coded_size.width() ||
|
|
visible_rect.bottom() > coded_size.height() ||
|
|
natural_size_area > limits::kMaxCanvas ||
|
|
natural_size.width() > limits::kMaxDimension ||
|
|
natural_size.height() > limits::kMaxDimension) {
|
|
return false;
|
|
}
|
|
|
|
// TODO(mcasas): Remove parameter |storage_type| when the opaque storage types
|
|
// comply with the checks below. Right now we skip them.
|
|
if (!IsStorageTypeMappable(storage_type))
|
|
return true;
|
|
|
|
// Make sure new formats are properly accounted for in the method.
|
|
static_assert(PIXEL_FORMAT_MAX == 32,
|
|
"Added pixel format, please review IsValidConfig()");
|
|
|
|
if (format == PIXEL_FORMAT_UNKNOWN) {
|
|
return coded_size.IsEmpty() && visible_rect.IsEmpty() &&
|
|
natural_size.IsEmpty();
|
|
}
|
|
|
|
// Check that software-allocated buffer formats are not empty.
|
|
return !coded_size.IsEmpty() && !visible_rect.IsEmpty() &&
|
|
!natural_size.IsEmpty();
|
|
}
|
|
|
|
// static
|
|
scoped_refptr<VideoFrame> VideoFrame::CreateFrame(VideoPixelFormat format,
|
|
const Size& coded_size,
|
|
const Rect& visible_rect,
|
|
const Size& natural_size,
|
|
base::TimeDelta timestamp) {
|
|
return CreateFrameInternal(format, coded_size, visible_rect, natural_size,
|
|
timestamp, false);
|
|
}
|
|
|
|
// static
|
|
scoped_refptr<VideoFrame> VideoFrame::WrapExternalSharedMemory(
|
|
VideoPixelFormat format,
|
|
const Size& coded_size,
|
|
const Rect& visible_rect,
|
|
const Size& natural_size,
|
|
uint8_t* data,
|
|
size_t data_size,
|
|
base::SharedMemoryHandle handle,
|
|
size_t data_offset,
|
|
base::TimeDelta timestamp) {
|
|
auto layout = GetDefaultLayout(format, coded_size);
|
|
if (!layout)
|
|
return nullptr;
|
|
return WrapExternalStorage(STORAGE_SHMEM, *layout, visible_rect, natural_size,
|
|
data, data_size, timestamp, nullptr, nullptr,
|
|
handle, data_offset);
|
|
}
|
|
|
|
// static
|
|
scoped_refptr<VideoFrame> VideoFrame::CreateEOSFrame() {
|
|
auto layout = VideoFrameLayout::Create(PIXEL_FORMAT_UNKNOWN, Size());
|
|
if (!layout) {
|
|
DLOG(ERROR) << "Invalid layout.";
|
|
return nullptr;
|
|
}
|
|
scoped_refptr<VideoFrame> frame =
|
|
new VideoFrame(*layout, STORAGE_UNKNOWN, Rect(), Size(), kNoTimestamp);
|
|
frame->metadata()->SetBoolean(VideoFrameMetadata::END_OF_STREAM, true);
|
|
return frame;
|
|
}
|
|
|
|
// static
|
|
size_t VideoFrame::NumPlanes(VideoPixelFormat format) {
|
|
return VideoFrameLayout::NumPlanes(format);
|
|
}
|
|
|
|
// static
|
|
size_t VideoFrame::AllocationSize(VideoPixelFormat format,
|
|
const Size& coded_size) {
|
|
size_t total = 0;
|
|
for (size_t i = 0; i < NumPlanes(format); ++i)
|
|
total += PlaneSize(format, i, coded_size).GetArea();
|
|
return total;
|
|
}
|
|
|
|
// static
|
|
Size VideoFrame::PlaneSize(VideoPixelFormat format,
|
|
size_t plane,
|
|
const Size& coded_size) {
|
|
DCHECK(IsValidPlane(plane, format));
|
|
|
|
int width = coded_size.width();
|
|
int height = coded_size.height();
|
|
if (RequiresEvenSizeAllocation(format)) {
|
|
// Align to multiple-of-two size overall. This ensures that non-subsampled
|
|
// planes can be addressed by pixel with the same scaling as the subsampled
|
|
// planes.
|
|
width = base::bits::Align(width, 2);
|
|
height = base::bits::Align(height, 2);
|
|
}
|
|
|
|
const Size subsample = SampleSize(format, plane);
|
|
DCHECK(width % subsample.width() == 0);
|
|
DCHECK(height % subsample.height() == 0);
|
|
return Size(BytesPerElement(format, plane) * width / subsample.width(),
|
|
height / subsample.height());
|
|
}
|
|
|
|
// static
|
|
int VideoFrame::PlaneHorizontalBitsPerPixel(VideoPixelFormat format,
|
|
size_t plane) {
|
|
DCHECK(IsValidPlane(plane, format));
|
|
const int bits_per_element = 8 * BytesPerElement(format, plane);
|
|
const int horiz_pixels_per_element = SampleSize(format, plane).width();
|
|
DCHECK_EQ(bits_per_element % horiz_pixels_per_element, 0);
|
|
return bits_per_element / horiz_pixels_per_element;
|
|
}
|
|
|
|
// static
|
|
int VideoFrame::PlaneBitsPerPixel(VideoPixelFormat format, size_t plane) {
|
|
DCHECK(IsValidPlane(plane, format));
|
|
return PlaneHorizontalBitsPerPixel(format, plane) /
|
|
SampleSize(format, plane).height();
|
|
}
|
|
|
|
// static
|
|
size_t VideoFrame::RowBytes(size_t plane, VideoPixelFormat format, int width) {
|
|
DCHECK(IsValidPlane(plane, format));
|
|
return BytesPerElement(format, plane) * Columns(plane, format, width);
|
|
}
|
|
|
|
// static
|
|
int VideoFrame::BytesPerElement(VideoPixelFormat format, size_t plane) {
|
|
DCHECK(IsValidPlane(format, plane));
|
|
switch (format) {
|
|
case PIXEL_FORMAT_ARGB:
|
|
case PIXEL_FORMAT_BGRA:
|
|
case PIXEL_FORMAT_XRGB:
|
|
case PIXEL_FORMAT_ABGR:
|
|
case PIXEL_FORMAT_XBGR:
|
|
case PIXEL_FORMAT_XR30:
|
|
case PIXEL_FORMAT_XB30:
|
|
return 4;
|
|
case PIXEL_FORMAT_RGB24:
|
|
return 3;
|
|
case PIXEL_FORMAT_Y16:
|
|
case PIXEL_FORMAT_YUY2:
|
|
case PIXEL_FORMAT_YUV420P9:
|
|
case PIXEL_FORMAT_YUV422P9:
|
|
case PIXEL_FORMAT_YUV444P9:
|
|
case PIXEL_FORMAT_YUV420P10:
|
|
case PIXEL_FORMAT_YUV422P10:
|
|
case PIXEL_FORMAT_YUV444P10:
|
|
case PIXEL_FORMAT_YUV420P12:
|
|
case PIXEL_FORMAT_YUV422P12:
|
|
case PIXEL_FORMAT_YUV444P12:
|
|
case PIXEL_FORMAT_P016LE:
|
|
return 2;
|
|
case PIXEL_FORMAT_NV12:
|
|
case PIXEL_FORMAT_NV21: {
|
|
static const int bytes_per_element[] = {1, 2};
|
|
DCHECK_LT(plane, base::size(bytes_per_element));
|
|
return bytes_per_element[plane];
|
|
}
|
|
case PIXEL_FORMAT_YV12:
|
|
case PIXEL_FORMAT_I420:
|
|
case PIXEL_FORMAT_I422:
|
|
case PIXEL_FORMAT_I420A:
|
|
case PIXEL_FORMAT_I444:
|
|
return 1;
|
|
case PIXEL_FORMAT_MJPEG:
|
|
return 0;
|
|
case PIXEL_FORMAT_UNKNOWN:
|
|
break;
|
|
}
|
|
NOTREACHED();
|
|
return 0;
|
|
}
|
|
|
|
// static
|
|
std::vector<int32_t> VideoFrame::ComputeStrides(VideoPixelFormat format,
|
|
const Size& coded_size) {
|
|
std::vector<int32_t> strides;
|
|
const size_t num_planes = NumPlanes(format);
|
|
if (num_planes == 1) {
|
|
strides.push_back(RowBytes(0, format, coded_size.width()));
|
|
} else {
|
|
for (size_t plane = 0; plane < num_planes; ++plane) {
|
|
strides.push_back(base::bits::Align(
|
|
RowBytes(plane, format, coded_size.width()), kFrameAddressAlignment));
|
|
}
|
|
}
|
|
return strides;
|
|
}
|
|
|
|
// static
|
|
size_t VideoFrame::Rows(size_t plane, VideoPixelFormat format, int height) {
|
|
DCHECK(IsValidPlane(plane, format));
|
|
const int sample_height = SampleSize(format, plane).height();
|
|
return base::bits::Align(height, sample_height) / sample_height;
|
|
}
|
|
|
|
// static
|
|
size_t VideoFrame::Columns(size_t plane, VideoPixelFormat format, int width) {
|
|
DCHECK(IsValidPlane(plane, format));
|
|
const int sample_width = SampleSize(format, plane).width();
|
|
return base::bits::Align(width, sample_width) / sample_width;
|
|
}
|
|
|
|
bool VideoFrame::IsMappable() const {
|
|
return IsStorageTypeMappable(storage_type_);
|
|
}
|
|
|
|
int VideoFrame::row_bytes(size_t plane) const {
|
|
return RowBytes(plane, format(), coded_size().width());
|
|
}
|
|
|
|
int VideoFrame::rows(size_t plane) const {
|
|
return Rows(plane, format(), coded_size().height());
|
|
}
|
|
|
|
const uint8_t* VideoFrame::visible_data(size_t plane) const {
|
|
DCHECK(IsValidPlane(plane, format()));
|
|
DCHECK(IsMappable());
|
|
|
|
// Calculate an offset that is properly aligned for all planes.
|
|
const Size alignment = CommonAlignment(format());
|
|
const int offset_x = AlignDown(visible_rect_.x(), alignment.width());
|
|
const int offset_y = AlignDown(visible_rect_.y(), alignment.height());
|
|
|
|
const Size subsample = SampleSize(format(), plane);
|
|
DCHECK(offset_x % subsample.width() == 0);
|
|
DCHECK(offset_y % subsample.height() == 0);
|
|
return data(plane) +
|
|
stride(plane) * (offset_y / subsample.height()) + // Row offset.
|
|
BytesPerElement(format(), plane) * // Column offset.
|
|
(offset_x / subsample.width());
|
|
}
|
|
|
|
uint8_t* VideoFrame::visible_data(size_t plane) {
|
|
return const_cast<uint8_t*>(
|
|
static_cast<const VideoFrame*>(this)->visible_data(plane));
|
|
}
|
|
|
|
base::ReadOnlySharedMemoryRegion* VideoFrame::read_only_shared_memory_region()
|
|
const {
|
|
DCHECK_EQ(storage_type_, STORAGE_SHMEM);
|
|
DCHECK(read_only_shared_memory_region_ &&
|
|
read_only_shared_memory_region_->IsValid());
|
|
return read_only_shared_memory_region_;
|
|
}
|
|
|
|
base::UnsafeSharedMemoryRegion* VideoFrame::unsafe_shared_memory_region()
|
|
const {
|
|
DCHECK_EQ(storage_type_, STORAGE_SHMEM);
|
|
DCHECK(unsafe_shared_memory_region_ &&
|
|
unsafe_shared_memory_region_->IsValid());
|
|
return unsafe_shared_memory_region_;
|
|
}
|
|
|
|
base::SharedMemoryHandle VideoFrame::shared_memory_handle() const {
|
|
DCHECK_EQ(storage_type_, STORAGE_SHMEM);
|
|
DCHECK(shared_memory_handle_.IsValid());
|
|
return shared_memory_handle_;
|
|
}
|
|
|
|
size_t VideoFrame::shared_memory_offset() const {
|
|
DCHECK_EQ(storage_type_, STORAGE_SHMEM);
|
|
DCHECK((read_only_shared_memory_region_ &&
|
|
read_only_shared_memory_region_->IsValid()) ||
|
|
(unsafe_shared_memory_region_ &&
|
|
unsafe_shared_memory_region_->IsValid()) ||
|
|
shared_memory_handle_.IsValid());
|
|
return shared_memory_offset_;
|
|
}
|
|
|
|
const std::vector<base::ScopedFD>& VideoFrame::DmabufFds() const {
|
|
DCHECK_EQ(storage_type_, STORAGE_DMABUFS);
|
|
|
|
return dmabuf_fds_;
|
|
}
|
|
|
|
bool VideoFrame::HasDmaBufs() const {
|
|
return !dmabuf_fds_.empty();
|
|
}
|
|
|
|
void VideoFrame::AddReadOnlySharedMemoryRegion(
|
|
base::ReadOnlySharedMemoryRegion* region) {
|
|
storage_type_ = STORAGE_SHMEM;
|
|
DCHECK(SharedMemoryUninitialized());
|
|
DCHECK(region && region->IsValid());
|
|
read_only_shared_memory_region_ = region;
|
|
}
|
|
|
|
void VideoFrame::AddUnsafeSharedMemoryRegion(
|
|
base::UnsafeSharedMemoryRegion* region) {
|
|
storage_type_ = STORAGE_SHMEM;
|
|
DCHECK(SharedMemoryUninitialized());
|
|
DCHECK(region && region->IsValid());
|
|
unsafe_shared_memory_region_ = region;
|
|
}
|
|
|
|
void VideoFrame::AddSharedMemoryHandle(base::SharedMemoryHandle handle) {
|
|
storage_type_ = STORAGE_SHMEM;
|
|
DCHECK(SharedMemoryUninitialized());
|
|
shared_memory_handle_ = handle;
|
|
}
|
|
|
|
void VideoFrame::AddDestructionObserver(base::OnceClosure callback) {
|
|
DCHECK(!callback.is_null());
|
|
done_callbacks_.push_back(std::move(callback));
|
|
}
|
|
|
|
std::string VideoFrame::AsHumanReadableString() {
|
|
if (metadata()->IsTrue(VideoFrameMetadata::END_OF_STREAM))
|
|
return "end of stream";
|
|
|
|
std::ostringstream s;
|
|
s << ConfigToString(format(), storage_type_, coded_size(), visible_rect_,
|
|
natural_size_)
|
|
<< " timestamp:" << timestamp_.InMicroseconds();
|
|
return s.str();
|
|
}
|
|
|
|
size_t VideoFrame::BitDepth() const {
|
|
return media::BitDepth(format());
|
|
}
|
|
|
|
// static
|
|
scoped_refptr<VideoFrame> VideoFrame::WrapExternalStorage(
|
|
StorageType storage_type,
|
|
const VideoFrameLayout& layout,
|
|
const Rect& visible_rect,
|
|
const Size& natural_size,
|
|
uint8_t* data,
|
|
size_t data_size,
|
|
base::TimeDelta timestamp,
|
|
base::ReadOnlySharedMemoryRegion* read_only_region,
|
|
base::UnsafeSharedMemoryRegion* unsafe_region,
|
|
base::SharedMemoryHandle handle,
|
|
size_t data_offset) {
|
|
DCHECK(IsStorageTypeMappable(storage_type));
|
|
|
|
if (!IsValidConfig(layout.format(), storage_type, layout.coded_size(),
|
|
visible_rect, natural_size)) {
|
|
DLOG(ERROR) << __func__ << " Invalid config."
|
|
<< ConfigToString(layout.format(), storage_type,
|
|
layout.coded_size(), visible_rect,
|
|
natural_size);
|
|
return nullptr;
|
|
}
|
|
|
|
scoped_refptr<VideoFrame> frame = new VideoFrame(
|
|
layout, storage_type, visible_rect, natural_size, timestamp);
|
|
|
|
for (size_t i = 0; i < layout.planes().size(); ++i) {
|
|
frame->data_[i] = data + layout.planes()[i].offset;
|
|
}
|
|
|
|
if (storage_type == STORAGE_SHMEM) {
|
|
if (read_only_region || unsafe_region) {
|
|
DCHECK(!handle.IsValid());
|
|
DCHECK_NE(!!read_only_region, !!unsafe_region)
|
|
<< "Expected exactly one read-only or unsafe region for "
|
|
<< "STORAGE_SHMEM VideoFrame";
|
|
if (read_only_region) {
|
|
frame->read_only_shared_memory_region_ = read_only_region;
|
|
DCHECK(frame->read_only_shared_memory_region_->IsValid());
|
|
} else if (unsafe_region) {
|
|
frame->unsafe_shared_memory_region_ = unsafe_region;
|
|
DCHECK(frame->unsafe_shared_memory_region_->IsValid());
|
|
}
|
|
frame->shared_memory_offset_ = data_offset;
|
|
} else {
|
|
frame->AddSharedMemoryHandle(handle);
|
|
frame->shared_memory_offset_ = data_offset;
|
|
}
|
|
}
|
|
|
|
return frame;
|
|
}
|
|
|
|
VideoFrame::VideoFrame(const VideoFrameLayout& layout,
|
|
StorageType storage_type,
|
|
const Rect& visible_rect,
|
|
const Size& natural_size,
|
|
base::TimeDelta timestamp)
|
|
: layout_(layout),
|
|
storage_type_(storage_type),
|
|
visible_rect_(Intersection(visible_rect, Rect(layout.coded_size()))),
|
|
natural_size_(natural_size),
|
|
shared_memory_offset_(0),
|
|
timestamp_(timestamp),
|
|
unique_id_(g_unique_id_generator.GetNext()) {
|
|
DCHECK(IsValidConfig(format(), storage_type, coded_size(), visible_rect_,
|
|
natural_size_));
|
|
DCHECK(visible_rect_ == visible_rect)
|
|
<< "visible_rect " << visible_rect.ToString() << " exceeds coded_size "
|
|
<< coded_size().ToString();
|
|
memset(&data_, 0, sizeof(data_));
|
|
}
|
|
|
|
VideoFrame::~VideoFrame() {
|
|
for (auto& callback : done_callbacks_)
|
|
std::move(callback).Run();
|
|
}
|
|
|
|
// static
|
|
std::string VideoFrame::ConfigToString(const VideoPixelFormat format,
|
|
const StorageType storage_type,
|
|
const Size& coded_size,
|
|
const Rect& visible_rect,
|
|
const Size& natural_size) {
|
|
return base::StringPrintf(
|
|
"format:%s storage_type:%s coded_size:%s visible_rect:%s natural_size:%s",
|
|
VideoPixelFormatToString(format).c_str(),
|
|
StorageTypeToString(storage_type).c_str(), coded_size.ToString().c_str(),
|
|
visible_rect.ToString().c_str(), natural_size.ToString().c_str());
|
|
}
|
|
|
|
// static
|
|
bool VideoFrame::IsValidPlane(size_t plane, VideoPixelFormat format) {
|
|
DCHECK_LE(NumPlanes(format), static_cast<size_t>(kMaxPlanes));
|
|
return (plane < NumPlanes(format));
|
|
}
|
|
|
|
// static
|
|
Size VideoFrame::DetermineAlignedSize(VideoPixelFormat format,
|
|
const Size& dimensions) {
|
|
const Size alignment = CommonAlignment(format);
|
|
const Size adjusted =
|
|
Size(base::bits::Align(dimensions.width(), alignment.width()),
|
|
base::bits::Align(dimensions.height(), alignment.height()));
|
|
DCHECK((adjusted.width() % alignment.width() == 0) &&
|
|
(adjusted.height() % alignment.height() == 0));
|
|
return adjusted;
|
|
}
|
|
|
|
// static
|
|
scoped_refptr<VideoFrame> VideoFrame::CreateFrameInternal(
|
|
VideoPixelFormat format,
|
|
const Size& coded_size,
|
|
const Rect& visible_rect,
|
|
const Size& natural_size,
|
|
base::TimeDelta timestamp,
|
|
bool zero_initialize_memory) {
|
|
// Since we're creating a new frame (and allocating memory for it ourselves),
|
|
// we can pad the requested |coded_size| if necessary if the request does not
|
|
// line up on sample boundaries. See discussion at http://crrev.com/1240833003
|
|
const Size new_coded_size = DetermineAlignedSize(format, coded_size);
|
|
auto layout = VideoFrameLayout::CreateWithStrides(
|
|
format, new_coded_size, ComputeStrides(format, coded_size));
|
|
if (!layout) {
|
|
DLOG(ERROR) << "Invalid layout.";
|
|
return nullptr;
|
|
}
|
|
|
|
return CreateFrameWithLayout(*layout, visible_rect, natural_size, timestamp,
|
|
zero_initialize_memory);
|
|
}
|
|
|
|
scoped_refptr<VideoFrame> VideoFrame::CreateFrameWithLayout(
|
|
const VideoFrameLayout& layout,
|
|
const Rect& visible_rect,
|
|
const Size& natural_size,
|
|
base::TimeDelta timestamp,
|
|
bool zero_initialize_memory) {
|
|
const StorageType storage = STORAGE_OWNED_MEMORY;
|
|
if (!IsValidConfig(layout.format(), storage, layout.coded_size(),
|
|
visible_rect, natural_size)) {
|
|
DLOG(ERROR) << __func__ << " Invalid config."
|
|
<< ConfigToString(layout.format(), storage, layout.coded_size(),
|
|
visible_rect, natural_size);
|
|
return nullptr;
|
|
}
|
|
|
|
scoped_refptr<VideoFrame> frame(new VideoFrame(
|
|
std::move(layout), storage, visible_rect, natural_size, timestamp));
|
|
frame->AllocateMemory(zero_initialize_memory);
|
|
return frame;
|
|
}
|
|
|
|
bool VideoFrame::SharedMemoryUninitialized() {
|
|
return !read_only_shared_memory_region_ && !unsafe_shared_memory_region_ &&
|
|
!shared_memory_handle_.IsValid();
|
|
}
|
|
|
|
// static
|
|
bool VideoFrame::IsValidPlane(VideoPixelFormat format, size_t plane) {
|
|
DCHECK_LE(NumPlanes(format), static_cast<size_t>(kMaxPlanes));
|
|
return plane < NumPlanes(format);
|
|
}
|
|
|
|
// static
|
|
Size VideoFrame::SampleSize(VideoPixelFormat format, size_t plane) {
|
|
DCHECK(IsValidPlane(format, plane));
|
|
|
|
switch (plane) {
|
|
case kYPlane: // and kARGBPlane:
|
|
case kAPlane:
|
|
return Size(1, 1);
|
|
|
|
case kUPlane: // and kUVPlane:
|
|
case kVPlane:
|
|
switch (format) {
|
|
case PIXEL_FORMAT_I444:
|
|
case PIXEL_FORMAT_YUV444P9:
|
|
case PIXEL_FORMAT_YUV444P10:
|
|
case PIXEL_FORMAT_YUV444P12:
|
|
case PIXEL_FORMAT_Y16:
|
|
return Size(1, 1);
|
|
|
|
case PIXEL_FORMAT_I422:
|
|
case PIXEL_FORMAT_YUV422P9:
|
|
case PIXEL_FORMAT_YUV422P10:
|
|
case PIXEL_FORMAT_YUV422P12:
|
|
return Size(2, 1);
|
|
|
|
case PIXEL_FORMAT_YV12:
|
|
case PIXEL_FORMAT_I420:
|
|
case PIXEL_FORMAT_I420A:
|
|
case PIXEL_FORMAT_NV12:
|
|
case PIXEL_FORMAT_NV21:
|
|
case PIXEL_FORMAT_YUV420P9:
|
|
case PIXEL_FORMAT_YUV420P10:
|
|
case PIXEL_FORMAT_YUV420P12:
|
|
case PIXEL_FORMAT_P016LE:
|
|
return Size(2, 2);
|
|
|
|
case PIXEL_FORMAT_UNKNOWN:
|
|
case PIXEL_FORMAT_YUY2:
|
|
case PIXEL_FORMAT_ARGB:
|
|
case PIXEL_FORMAT_XRGB:
|
|
case PIXEL_FORMAT_RGB24:
|
|
case PIXEL_FORMAT_MJPEG:
|
|
case PIXEL_FORMAT_ABGR:
|
|
case PIXEL_FORMAT_XBGR:
|
|
case PIXEL_FORMAT_XR30:
|
|
case PIXEL_FORMAT_XB30:
|
|
case PIXEL_FORMAT_BGRA:
|
|
break;
|
|
}
|
|
}
|
|
NOTREACHED();
|
|
return Size();
|
|
}
|
|
|
|
// static
|
|
Size VideoFrame::CommonAlignment(VideoPixelFormat format) {
|
|
int max_sample_width = 0;
|
|
int max_sample_height = 0;
|
|
for (size_t plane = 0; plane < NumPlanes(format); ++plane) {
|
|
const Size sample_size = SampleSize(format, plane);
|
|
max_sample_width = std::max(max_sample_width, sample_size.width());
|
|
max_sample_height = std::max(max_sample_height, sample_size.height());
|
|
}
|
|
return Size(max_sample_width, max_sample_height);
|
|
}
|
|
|
|
void VideoFrame::AllocateMemory(bool zero_initialize_memory) {
|
|
DCHECK_EQ(storage_type_, STORAGE_OWNED_MEMORY);
|
|
static_assert(0 == kYPlane, "y plane data must be index 0");
|
|
|
|
std::vector<size_t> plane_size = CalculatePlaneSize();
|
|
const size_t total_buffer_size =
|
|
std::accumulate(plane_size.begin(), plane_size.end(), 0u);
|
|
|
|
uint8_t* data = reinterpret_cast<uint8_t*>(
|
|
base::AlignedAlloc(total_buffer_size, layout_.buffer_addr_align()));
|
|
if (zero_initialize_memory) {
|
|
memset(data, 0, total_buffer_size);
|
|
}
|
|
AddDestructionObserver(base::BindOnce(&base::AlignedFree, data));
|
|
|
|
// Note that if layout.buffer_sizes is specified, color planes' layout is the
|
|
// same as buffers'. See CalculatePlaneSize() for detail.
|
|
for (size_t plane = 0, offset = 0; plane < NumPlanes(format()); ++plane) {
|
|
data_[plane] = data + offset;
|
|
offset += plane_size[plane];
|
|
}
|
|
}
|
|
|
|
std::vector<size_t> VideoFrame::CalculatePlaneSize() const {
|
|
// We have two cases for plane size mapping:
|
|
// 1) If plane size is specified: use planes' size.
|
|
// 2) VideoFrameLayout::size is unassigned: use legacy calculation formula.
|
|
|
|
const size_t num_planes = NumPlanes(format());
|
|
const auto& planes = layout_.planes();
|
|
std::vector<size_t> plane_size(num_planes);
|
|
bool plane_size_assigned = true;
|
|
DCHECK_EQ(planes.size(), num_planes);
|
|
for (size_t i = 0; i < num_planes; ++i) {
|
|
plane_size[i] = planes[i].size;
|
|
plane_size_assigned &= plane_size[i] != 0;
|
|
}
|
|
|
|
if (plane_size_assigned)
|
|
return plane_size;
|
|
|
|
// Reset plane size.
|
|
std::fill(plane_size.begin(), plane_size.end(), 0u);
|
|
for (size_t plane = 0; plane < num_planes; ++plane) {
|
|
// These values were chosen to mirror ffmpeg's get_video_buffer().
|
|
// TODO(dalecurtis): This should be configurable; eventually ffmpeg wants
|
|
// us to use av_cpu_max_align(), but... for now, they just hard-code 32.
|
|
const size_t height =
|
|
base::bits::Align(rows(plane), kFrameAddressAlignment);
|
|
const size_t width = std::abs(stride(plane));
|
|
plane_size[plane] = width * height;
|
|
}
|
|
|
|
if (num_planes > 1) {
|
|
// The extra line of UV being allocated is because h264 chroma MC
|
|
// overreads by one line in some cases, see libavcodec/utils.c:
|
|
// avcodec_align_dimensions2() and libavcodec/x86/h264_chromamc.asm:
|
|
// put_h264_chroma_mc4_ssse3().
|
|
DCHECK(IsValidPlane(format(), kUPlane));
|
|
plane_size.back() += std::abs(stride(kUPlane)) + kFrameSizePadding;
|
|
}
|
|
return plane_size;
|
|
}
|
|
|
|
} // namespace media
|