// 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 #include #include #include #include #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::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 GetDefaultLayout( VideoPixelFormat format, const Size& coded_size) { std::vector 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(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( coded_size.width() * 2, 0, coded_size.GetArea() * 2)}; break; case PIXEL_FORMAT_ARGB: planes = std::vector{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(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::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::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::CreateEOSFrame() { auto layout = VideoFrameLayout::Create(PIXEL_FORMAT_UNKNOWN, Size()); if (!layout) { DLOG(ERROR) << "Invalid layout."; return nullptr; } scoped_refptr 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 VideoFrame::ComputeStrides(VideoPixelFormat format, const Size& coded_size) { std::vector 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( static_cast(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& 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::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 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(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::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::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 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(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 plane_size = CalculatePlaneSize(); const size_t total_buffer_size = std::accumulate(plane_size.begin(), plane_size.end(), 0u); uint8_t* data = reinterpret_cast( 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 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 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