| // 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. |
| |
| #include "media/base/video_frame.h" |
| |
| #include <algorithm> |
| #include <climits> |
| #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 "build/build_config.h" |
| #include "media/base/limits.h" |
| #include "media/base/scopedfd_helper.h" |
| #include "media/base/timestamp_constants.h" |
| #include "media/base/video_util.h" |
| #include "ui/gfx/geometry/point.h" |
| |
| namespace media { |
| |
| namespace { |
| |
| // Helper to privide gfx::Rect::Intersect() as an expression. |
| gfx::Rect Intersection(gfx::Rect a, const gfx::Rect& b) { |
| a.Intersect(b); |
| return a; |
| } |
| |
| } // 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"; |
| #if defined(OS_LINUX) |
| case VideoFrame::STORAGE_DMABUFS: |
| return "DMABUFS"; |
| #endif |
| 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 |
| #if defined(OS_LINUX) |
| // This is not strictly needed but makes explicit that, at VideoFrame |
| // level, DmaBufs are not mappable from userspace. |
| storage_type != VideoFrame::STORAGE_DMABUFS && |
| #endif |
| (storage_type == VideoFrame::STORAGE_UNOWNED_MEMORY || |
| storage_type == VideoFrame::STORAGE_OWNED_MEMORY || |
| storage_type == VideoFrame::STORAGE_SHMEM || |
| storage_type == VideoFrame::STORAGE_MOJO_SHARED_BUFFER); |
| } |
| |
| // Checks if |source_format| can be wrapped into a |target_format| frame. |
| static bool AreValidPixelFormatsForWrap(VideoPixelFormat source_format, |
| VideoPixelFormat target_format) { |
| if (source_format == target_format) |
| return true; |
| |
| // It is possible to add other planar to planar format conversions here if the |
| // use case is there. |
| return source_format == PIXEL_FORMAT_I420A && |
| target_format == PIXEL_FORMAT_I420; |
| } |
| |
| // 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_RGB32: |
| case PIXEL_FORMAT_Y16: |
| case PIXEL_FORMAT_ABGR: |
| case PIXEL_FORMAT_XBGR: |
| return false; |
| case PIXEL_FORMAT_NV12: |
| case PIXEL_FORMAT_NV21: |
| case PIXEL_FORMAT_MT21: |
| 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_UYVY: |
| 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 gfx::Size& coded_size) { |
| std::vector<VideoFrameLayout::Plane> 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_width * uv_height; |
| planes = std::vector<VideoFrameLayout::Plane>{ |
| VideoFrameLayout::Plane(coded_size.width(), 0), |
| VideoFrameLayout::Plane(uv_stride, coded_size.GetArea()), |
| VideoFrameLayout::Plane(uv_stride, coded_size.GetArea() + uv_size), |
| }; |
| break; |
| } |
| |
| case PIXEL_FORMAT_Y16: |
| planes = std::vector<VideoFrameLayout::Plane>{ |
| VideoFrameLayout::Plane(coded_size.width() * 2, 0)}; |
| break; |
| |
| case PIXEL_FORMAT_ARGB: |
| planes = std::vector<VideoFrameLayout::Plane>{ |
| VideoFrameLayout::Plane(coded_size.width() * 4, 0)}; |
| break; |
| |
| case PIXEL_FORMAT_NV12: { |
| int uv_width = (coded_size.width() + 1) / 2; |
| int uv_stride = uv_width * 2; |
| planes = std::vector<VideoFrameLayout::Plane>{ |
| VideoFrameLayout::Plane(coded_size.width(), 0), |
| VideoFrameLayout::Plane(uv_stride, coded_size.GetArea()), |
| }; |
| 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 gfx::Size& coded_size, |
| const gfx::Rect& visible_rect, |
| const gfx::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 == 28, |
| "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 gfx::Size& coded_size, |
| const gfx::Rect& visible_rect, |
| const gfx::Size& natural_size, |
| base::TimeDelta timestamp) { |
| return CreateFrameInternal(format, coded_size, visible_rect, natural_size, |
| timestamp, false); |
| } |
| |
| // static |
| scoped_refptr<VideoFrame> VideoFrame::CreateZeroInitializedFrame( |
| VideoPixelFormat format, |
| const gfx::Size& coded_size, |
| const gfx::Rect& visible_rect, |
| const gfx::Size& natural_size, |
| base::TimeDelta timestamp) { |
| return CreateFrameInternal(format, coded_size, visible_rect, natural_size, |
| timestamp, true); |
| } |
| |
| // static |
| scoped_refptr<VideoFrame> VideoFrame::WrapNativeTextures( |
| VideoPixelFormat format, |
| const gpu::MailboxHolder (&mailbox_holders)[kMaxPlanes], |
| ReleaseMailboxCB mailbox_holder_release_cb, |
| const gfx::Size& coded_size, |
| const gfx::Rect& visible_rect, |
| const gfx::Size& natural_size, |
| base::TimeDelta timestamp) { |
| if (format != PIXEL_FORMAT_ARGB && format != PIXEL_FORMAT_XRGB && |
| format != PIXEL_FORMAT_RGB32 && format != PIXEL_FORMAT_UYVY && |
| format != PIXEL_FORMAT_NV12 && format != PIXEL_FORMAT_I420) { |
| DLOG(ERROR) << "Unsupported pixel format: " |
| << VideoPixelFormatToString(format); |
| return nullptr; |
| } |
| const StorageType storage = STORAGE_OPAQUE; |
| if (!IsValidConfig(format, storage, coded_size, visible_rect, natural_size)) { |
| DLOG(ERROR) << __func__ << " Invalid config." |
| << ConfigToString(format, storage, coded_size, visible_rect, |
| natural_size); |
| return nullptr; |
| } |
| |
| auto layout = VideoFrameLayout::Create(format, coded_size); |
| if (!layout) { |
| DLOG(ERROR) << "Invalid layout."; |
| return nullptr; |
| } |
| |
| scoped_refptr<VideoFrame> frame = |
| new VideoFrame(*layout, storage, visible_rect, natural_size, timestamp); |
| memcpy(&frame->mailbox_holders_, mailbox_holders, |
| sizeof(frame->mailbox_holders_)); |
| frame->mailbox_holders_release_cb_ = std::move(mailbox_holder_release_cb); |
| |
| // Wrapping native textures should... have textures. https://crbug.com/864145. |
| DCHECK(frame->HasTextures()); |
| |
| return frame; |
| } |
| |
| // static |
| scoped_refptr<VideoFrame> VideoFrame::WrapExternalData( |
| VideoPixelFormat format, |
| const gfx::Size& coded_size, |
| const gfx::Rect& visible_rect, |
| const gfx::Size& natural_size, |
| uint8_t* data, |
| size_t data_size, |
| base::TimeDelta timestamp) { |
| auto layout = GetDefaultLayout(format, coded_size); |
| if (!layout) |
| return nullptr; |
| return WrapExternalDataWithLayout(*layout, visible_rect, natural_size, data, |
| data_size, timestamp); |
| } |
| |
| // static |
| scoped_refptr<VideoFrame> VideoFrame::WrapExternalDataWithLayout( |
| const VideoFrameLayout& layout, |
| const gfx::Rect& visible_rect, |
| const gfx::Size& natural_size, |
| uint8_t* data, |
| size_t data_size, |
| base::TimeDelta timestamp) { |
| return WrapExternalStorage(STORAGE_UNOWNED_MEMORY, layout, visible_rect, |
| natural_size, data, data_size, timestamp, nullptr, |
| nullptr, base::SharedMemoryHandle(), 0); |
| } |
| |
| // static |
| scoped_refptr<VideoFrame> VideoFrame::WrapExternalReadOnlySharedMemory( |
| VideoPixelFormat format, |
| const gfx::Size& coded_size, |
| const gfx::Rect& visible_rect, |
| const gfx::Size& natural_size, |
| uint8_t* data, |
| size_t data_size, |
| base::ReadOnlySharedMemoryRegion* region, |
| 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, region, nullptr, |
| base::SharedMemoryHandle(), data_offset); |
| } |
| |
| // static |
| scoped_refptr<VideoFrame> VideoFrame::WrapExternalUnsafeSharedMemory( |
| VideoPixelFormat format, |
| const gfx::Size& coded_size, |
| const gfx::Rect& visible_rect, |
| const gfx::Size& natural_size, |
| uint8_t* data, |
| size_t data_size, |
| base::UnsafeSharedMemoryRegion* region, |
| 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, region, |
| base::SharedMemoryHandle(), data_offset); |
| } |
| |
| // static |
| scoped_refptr<VideoFrame> VideoFrame::WrapExternalSharedMemory( |
| VideoPixelFormat format, |
| const gfx::Size& coded_size, |
| const gfx::Rect& visible_rect, |
| const gfx::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::WrapExternalYuvData( |
| VideoPixelFormat format, |
| const gfx::Size& coded_size, |
| const gfx::Rect& visible_rect, |
| const gfx::Size& natural_size, |
| int32_t y_stride, |
| int32_t u_stride, |
| int32_t v_stride, |
| uint8_t* y_data, |
| uint8_t* u_data, |
| uint8_t* v_data, |
| base::TimeDelta timestamp) { |
| const StorageType storage = STORAGE_UNOWNED_MEMORY; |
| if (!IsValidConfig(format, storage, coded_size, visible_rect, natural_size)) { |
| DLOG(ERROR) << __func__ << " Invalid config." |
| << ConfigToString(format, storage, coded_size, visible_rect, |
| natural_size); |
| return nullptr; |
| } |
| |
| const size_t height = coded_size.height(); |
| auto layout = VideoFrameLayout::CreateWithStrides( |
| format, coded_size, {y_stride, u_stride, v_stride}, |
| {std::abs(y_stride) * height, std::abs(u_stride) * height, |
| std::abs(v_stride) * height}); |
| if (!layout) { |
| DLOG(ERROR) << "Invalid layout."; |
| return nullptr; |
| } |
| |
| scoped_refptr<VideoFrame> frame( |
| new VideoFrame(*layout, storage, visible_rect, natural_size, timestamp)); |
| frame->data_[kYPlane] = y_data; |
| frame->data_[kUPlane] = u_data; |
| frame->data_[kVPlane] = v_data; |
| return frame; |
| } |
| |
| // static |
| scoped_refptr<VideoFrame> VideoFrame::WrapExternalYuvaData( |
| VideoPixelFormat format, |
| const gfx::Size& coded_size, |
| const gfx::Rect& visible_rect, |
| const gfx::Size& natural_size, |
| int32_t y_stride, |
| int32_t u_stride, |
| int32_t v_stride, |
| int32_t a_stride, |
| uint8_t* y_data, |
| uint8_t* u_data, |
| uint8_t* v_data, |
| uint8_t* a_data, |
| base::TimeDelta timestamp) { |
| const StorageType storage = STORAGE_UNOWNED_MEMORY; |
| if (!IsValidConfig(format, storage, coded_size, visible_rect, natural_size)) { |
| DLOG(ERROR) << __func__ << " Invalid config." |
| << ConfigToString(format, storage, coded_size, visible_rect, |
| natural_size); |
| return nullptr; |
| } |
| |
| if (NumPlanes(format) != 4) { |
| DLOG(ERROR) << "Expecting Y, U, V and A planes to be present for the video" |
| << " format."; |
| return nullptr; |
| } |
| |
| const size_t height = coded_size.height(); |
| auto layout = VideoFrameLayout::CreateWithStrides( |
| format, coded_size, {y_stride, u_stride, v_stride, a_stride}, |
| {abs(y_stride) * height, abs(u_stride) * height, abs(v_stride) * height, |
| abs(a_stride) * height}); |
| if (!layout) { |
| DLOG(ERROR) << "Invalid layout"; |
| return nullptr; |
| } |
| |
| scoped_refptr<VideoFrame> frame( |
| new VideoFrame(*layout, storage, visible_rect, natural_size, timestamp)); |
| frame->data_[kYPlane] = y_data; |
| frame->data_[kUPlane] = u_data; |
| frame->data_[kVPlane] = v_data; |
| frame->data_[kAPlane] = a_data; |
| return frame; |
| } |
| |
| #if defined(OS_LINUX) |
| // static |
| scoped_refptr<VideoFrame> VideoFrame::WrapExternalDmabufs( |
| const VideoFrameLayout& layout, |
| const gfx::Rect& visible_rect, |
| const gfx::Size& natural_size, |
| std::vector<base::ScopedFD> dmabuf_fds, |
| base::TimeDelta timestamp) { |
| const StorageType storage = STORAGE_DMABUFS; |
| const VideoPixelFormat format = layout.format(); |
| const gfx::Size& coded_size = layout.coded_size(); |
| if (!IsValidConfig(format, storage, coded_size, visible_rect, natural_size)) { |
| DLOG(ERROR) << __func__ << " Invalid config." |
| << ConfigToString(format, storage, coded_size, visible_rect, |
| natural_size); |
| return nullptr; |
| } |
| |
| if (dmabuf_fds.empty() || dmabuf_fds.size() > NumPlanes(format)) { |
| DLOG(ERROR) << __func__ << " Incorrect number of dmabuf fds provided, got: " |
| << dmabuf_fds.size() << ", expected 1 to " << NumPlanes(format); |
| return nullptr; |
| } |
| |
| gpu::MailboxHolder mailbox_holders[kMaxPlanes]; |
| scoped_refptr<VideoFrame> frame = |
| new VideoFrame(layout, storage, visible_rect, natural_size, timestamp); |
| if (!frame) { |
| DLOG(ERROR) << __func__ << " Couldn't create VideoFrame instance."; |
| return nullptr; |
| } |
| memcpy(&frame->mailbox_holders_, mailbox_holders, |
| sizeof(frame->mailbox_holders_)); |
| frame->mailbox_holders_release_cb_ = ReleaseMailboxCB(); |
| frame->dmabuf_fds_ = std::move(dmabuf_fds); |
| DCHECK(frame->HasDmaBufs()); |
| |
| return frame; |
| } |
| #endif |
| |
| #if defined(OS_MACOSX) |
| // static |
| scoped_refptr<VideoFrame> VideoFrame::WrapCVPixelBuffer( |
| CVPixelBufferRef cv_pixel_buffer, |
| base::TimeDelta timestamp) { |
| DCHECK(cv_pixel_buffer); |
| DCHECK(CFGetTypeID(cv_pixel_buffer) == CVPixelBufferGetTypeID()); |
| |
| const OSType cv_format = CVPixelBufferGetPixelFormatType(cv_pixel_buffer); |
| VideoPixelFormat format; |
| // There are very few compatible CV pixel formats, so just check each. |
| if (cv_format == kCVPixelFormatType_420YpCbCr8Planar) { |
| format = PIXEL_FORMAT_I420; |
| } else if (cv_format == kCVPixelFormatType_444YpCbCr8) { |
| format = PIXEL_FORMAT_I444; |
| } else if (cv_format == '420v') { |
| // TODO(jfroy): Use kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange when the |
| // minimum OS X and iOS SDKs permits it. |
| format = PIXEL_FORMAT_NV12; |
| } else { |
| DLOG(ERROR) << "CVPixelBuffer format not supported: " << cv_format; |
| return nullptr; |
| } |
| |
| const gfx::Size coded_size(CVImageBufferGetEncodedSize(cv_pixel_buffer)); |
| const gfx::Rect visible_rect(CVImageBufferGetCleanRect(cv_pixel_buffer)); |
| const gfx::Size natural_size(CVImageBufferGetDisplaySize(cv_pixel_buffer)); |
| const StorageType storage = STORAGE_UNOWNED_MEMORY; |
| |
| if (!IsValidConfig(format, storage, coded_size, visible_rect, natural_size)) { |
| DLOG(ERROR) << __func__ << " Invalid config." |
| << ConfigToString(format, storage, coded_size, visible_rect, |
| natural_size); |
| return nullptr; |
| } |
| |
| auto layout = VideoFrameLayout::Create(format, coded_size); |
| if (!layout) { |
| DLOG(ERROR) << "Invalid layout."; |
| return nullptr; |
| } |
| |
| scoped_refptr<VideoFrame> frame( |
| new VideoFrame(*layout, storage, visible_rect, natural_size, timestamp)); |
| |
| frame->cv_pixel_buffer_.reset(cv_pixel_buffer, base::scoped_policy::RETAIN); |
| return frame; |
| } |
| #endif |
| |
| // static |
| scoped_refptr<VideoFrame> VideoFrame::WrapVideoFrame( |
| const scoped_refptr<VideoFrame>& frame, |
| VideoPixelFormat format, |
| const gfx::Rect& visible_rect, |
| const gfx::Size& natural_size) { |
| // Frames with textures need mailbox info propagated, and there's no support |
| // for that here yet, see http://crbug/362521. |
| CHECK(!frame->HasTextures()); |
| DCHECK(frame->visible_rect().Contains(visible_rect)); |
| |
| if (!AreValidPixelFormatsForWrap(frame->format(), format)) { |
| DLOG(ERROR) << __func__ << " Invalid format conversion." |
| << VideoPixelFormatToString(frame->format()) << " to " |
| << VideoPixelFormatToString(format); |
| return nullptr; |
| } |
| |
| if (!IsValidConfig(format, frame->storage_type(), frame->coded_size(), |
| visible_rect, natural_size)) { |
| DLOG(ERROR) << __func__ << " Invalid config." |
| << ConfigToString(format, frame->storage_type(), |
| frame->coded_size(), visible_rect, |
| natural_size); |
| return nullptr; |
| } |
| |
| scoped_refptr<VideoFrame> wrapping_frame( |
| new VideoFrame(frame->layout(), frame->storage_type(), visible_rect, |
| natural_size, frame->timestamp())); |
| |
| // Copy all metadata to the wrapped frame. |
| wrapping_frame->metadata()->MergeMetadataFrom(frame->metadata()); |
| |
| if (frame->IsMappable()) { |
| for (size_t i = 0; i < NumPlanes(format); ++i) { |
| wrapping_frame->data_[i] = frame->data(i); |
| } |
| } |
| |
| #if defined(OS_LINUX) |
| // If there are any |dmabuf_fds_| plugged in, we should duplicate them. |
| if (frame->storage_type() == STORAGE_DMABUFS) { |
| wrapping_frame->dmabuf_fds_ = DuplicateFDs(frame->dmabuf_fds_); |
| if (wrapping_frame->dmabuf_fds_.empty()) { |
| DLOG(ERROR) << __func__ << " Couldn't duplicate fds."; |
| return nullptr; |
| } |
| } |
| #endif |
| |
| if (frame->storage_type() == STORAGE_SHMEM) { |
| if (frame->read_only_shared_memory_region_) { |
| DCHECK(frame->read_only_shared_memory_region_->IsValid()); |
| wrapping_frame->AddReadOnlySharedMemoryRegion( |
| frame->read_only_shared_memory_region_); |
| } else if (frame->unsafe_shared_memory_region_) { |
| DCHECK(frame->unsafe_shared_memory_region_->IsValid()); |
| wrapping_frame->AddUnsafeSharedMemoryRegion( |
| frame->unsafe_shared_memory_region_); |
| } else { |
| DCHECK(frame->shared_memory_handle_.IsValid()); |
| wrapping_frame->AddSharedMemoryHandle(frame->shared_memory_handle_); |
| } |
| } |
| |
| return wrapping_frame; |
| } |
| |
| // static |
| scoped_refptr<VideoFrame> VideoFrame::CreateEOSFrame() { |
| auto layout = VideoFrameLayout::Create(PIXEL_FORMAT_UNKNOWN, gfx::Size()); |
| if (!layout) { |
| DLOG(ERROR) << "Invalid layout."; |
| return nullptr; |
| } |
| scoped_refptr<VideoFrame> frame = new VideoFrame( |
| *layout, STORAGE_UNKNOWN, gfx::Rect(), gfx::Size(), kNoTimestamp); |
| frame->metadata()->SetBoolean(VideoFrameMetadata::END_OF_STREAM, true); |
| return frame; |
| } |
| |
| // static |
| scoped_refptr<VideoFrame> VideoFrame::CreateColorFrame( |
| const gfx::Size& size, |
| uint8_t y, |
| uint8_t u, |
| uint8_t v, |
| base::TimeDelta timestamp) { |
| scoped_refptr<VideoFrame> frame = |
| CreateFrame(PIXEL_FORMAT_I420, size, gfx::Rect(size), size, timestamp); |
| FillYUV(frame.get(), y, u, v); |
| return frame; |
| } |
| |
| // static |
| scoped_refptr<VideoFrame> VideoFrame::CreateBlackFrame(const gfx::Size& size) { |
| const uint8_t kBlackY = 0x00; |
| const uint8_t kBlackUV = 0x80; |
| const base::TimeDelta kZero; |
| return CreateColorFrame(size, kBlackY, kBlackUV, kBlackUV, kZero); |
| } |
| |
| // static |
| scoped_refptr<VideoFrame> VideoFrame::CreateTransparentFrame( |
| const gfx::Size& size) { |
| const uint8_t kBlackY = 0x00; |
| const uint8_t kBlackUV = 0x00; |
| const uint8_t kTransparentA = 0x00; |
| const base::TimeDelta kZero; |
| scoped_refptr<VideoFrame> frame = |
| CreateFrame(PIXEL_FORMAT_I420A, size, gfx::Rect(size), size, kZero); |
| FillYUVA(frame.get(), kBlackY, kBlackUV, kBlackUV, kTransparentA); |
| return frame; |
| } |
| |
| // static |
| size_t VideoFrame::NumPlanes(VideoPixelFormat format) { |
| return VideoFrameLayout::NumPlanes(format); |
| } |
| |
| // static |
| size_t VideoFrame::AllocationSize(VideoPixelFormat format, |
| const gfx::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 |
| gfx::Size VideoFrame::PlaneSize(VideoPixelFormat format, |
| size_t plane, |
| const gfx::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 gfx::Size subsample = SampleSize(format, plane); |
| DCHECK(width % subsample.width() == 0); |
| DCHECK(height % subsample.height() == 0); |
| return gfx::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(plane, format)); |
| switch (format) { |
| case PIXEL_FORMAT_ARGB: |
| case PIXEL_FORMAT_XRGB: |
| case PIXEL_FORMAT_RGB32: |
| case PIXEL_FORMAT_ABGR: |
| case PIXEL_FORMAT_XBGR: |
| return 4; |
| case PIXEL_FORMAT_RGB24: |
| return 3; |
| case PIXEL_FORMAT_Y16: |
| case PIXEL_FORMAT_UYVY: |
| 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: |
| return 2; |
| case PIXEL_FORMAT_NV12: |
| case PIXEL_FORMAT_NV21: |
| case PIXEL_FORMAT_MT21: { |
| 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 gfx::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; |
| } |
| |
| // static |
| void VideoFrame::HashFrameForTesting(base::MD5Context* context, |
| const scoped_refptr<VideoFrame>& frame) { |
| DCHECK(context); |
| for (size_t plane = 0; plane < NumPlanes(frame->format()); ++plane) { |
| for (int row = 0; row < frame->rows(plane); ++row) { |
| base::MD5Update( |
| context, |
| base::StringPiece(reinterpret_cast<char*>(frame->data(plane) + |
| frame->stride(plane) * row), |
| frame->row_bytes(plane))); |
| } |
| } |
| } |
| |
| bool VideoFrame::IsMappable() const { |
| return IsStorageTypeMappable(storage_type_); |
| } |
| |
| bool VideoFrame::HasTextures() const { |
| return !mailbox_holders_[0].mailbox.IsZero(); |
| } |
| |
| size_t VideoFrame::NumTextures() const { |
| if (!HasTextures()) |
| return 0; |
| |
| size_t i = 0; |
| for (; i < NumPlanes(format()); ++i) { |
| if (mailbox_holders_[i].mailbox.IsZero()) { |
| return i; |
| } |
| } |
| return i; |
| } |
| |
| gfx::ColorSpace VideoFrame::ColorSpace() const { |
| return color_space_; |
| } |
| |
| 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 gfx::Size alignment = CommonAlignment(format()); |
| const gfx::Point offset( |
| base::bits::AlignDown(visible_rect_.x(), alignment.width()), |
| base::bits::AlignDown(visible_rect_.y(), alignment.height())); |
| |
| const gfx::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)); |
| } |
| |
| const gpu::MailboxHolder& |
| VideoFrame::mailbox_holder(size_t texture_index) const { |
| DCHECK(HasTextures()); |
| DCHECK(IsValidPlane(texture_index, format())); |
| return mailbox_holders_[texture_index]; |
| } |
| |
| 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_; |
| } |
| |
| #if defined(OS_LINUX) |
| 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(); |
| } |
| #endif |
| |
| 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; |
| } |
| |
| #if defined(OS_MACOSX) |
| CVPixelBufferRef VideoFrame::CvPixelBuffer() const { |
| return cv_pixel_buffer_.get(); |
| } |
| #endif |
| |
| void VideoFrame::SetReleaseMailboxCB(ReleaseMailboxCB release_mailbox_cb) { |
| DCHECK(release_mailbox_cb); |
| DCHECK(!mailbox_holders_release_cb_); |
| mailbox_holders_release_cb_ = std::move(release_mailbox_cb); |
| } |
| |
| bool VideoFrame::HasReleaseMailboxCB() const { |
| return !!mailbox_holders_release_cb_; |
| } |
| |
| void VideoFrame::AddDestructionObserver(base::OnceClosure callback) { |
| DCHECK(!callback.is_null()); |
| done_callbacks_.push_back(std::move(callback)); |
| } |
| |
| gpu::SyncToken VideoFrame::UpdateReleaseSyncToken(SyncTokenClient* client) { |
| DCHECK(HasTextures()); |
| base::AutoLock locker(release_sync_token_lock_); |
| // Must wait on the previous sync point before inserting a new sync point so |
| // that |mailbox_holders_release_cb_| guarantees the previous sync point |
| // occurred when it waits on |release_sync_token_|. |
| if (release_sync_token_.HasData()) |
| client->WaitSyncToken(release_sync_token_); |
| client->GenerateSyncToken(&release_sync_token_); |
| return release_sync_token_; |
| } |
| |
| std::string VideoFrame::AsHumanReadableString() { |
| if (metadata()->IsTrue(media::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 gfx::Rect& visible_rect, |
| const gfx::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 gfx::Rect& visible_rect, |
| const gfx::Size& natural_size, |
| base::TimeDelta timestamp) |
| : layout_(layout), |
| storage_type_(storage_type), |
| visible_rect_(Intersection(visible_rect, gfx::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(&mailbox_holders_, 0, sizeof(mailbox_holders_)); |
| memset(&data_, 0, sizeof(data_)); |
| } |
| |
| VideoFrame::~VideoFrame() { |
| if (mailbox_holders_release_cb_) { |
| gpu::SyncToken release_sync_token; |
| { |
| // To ensure that changes to |release_sync_token_| are visible on this |
| // thread (imply a memory barrier). |
| base::AutoLock locker(release_sync_token_lock_); |
| release_sync_token = release_sync_token_; |
| } |
| std::move(mailbox_holders_release_cb_).Run(release_sync_token); |
| } |
| |
| for (auto& callback : done_callbacks_) |
| std::move(callback).Run(); |
| } |
| |
| // static |
| std::string VideoFrame::ConfigToString(const VideoPixelFormat format, |
| const StorageType storage_type, |
| const gfx::Size& coded_size, |
| const gfx::Rect& visible_rect, |
| const gfx::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 |
| gfx::Size VideoFrame::DetermineAlignedSize(VideoPixelFormat format, |
| const gfx::Size& dimensions) { |
| const gfx::Size alignment = CommonAlignment(format); |
| const gfx::Size adjusted = |
| gfx::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 gfx::Size& coded_size, |
| const gfx::Rect& visible_rect, |
| const gfx::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 gfx::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 gfx::Rect& visible_rect, |
| const gfx::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 |
| gfx::Size VideoFrame::SampleSize(VideoPixelFormat format, size_t plane) { |
| DCHECK(IsValidPlane(plane, format)); |
| |
| switch (plane) { |
| case kYPlane: // and kARGBPlane: |
| case kAPlane: |
| return gfx::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 gfx::Size(1, 1); |
| |
| case PIXEL_FORMAT_I422: |
| case PIXEL_FORMAT_YUV422P9: |
| case PIXEL_FORMAT_YUV422P10: |
| case PIXEL_FORMAT_YUV422P12: |
| return gfx::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_MT21: |
| case PIXEL_FORMAT_YUV420P9: |
| case PIXEL_FORMAT_YUV420P10: |
| case PIXEL_FORMAT_YUV420P12: |
| return gfx::Size(2, 2); |
| |
| case PIXEL_FORMAT_UNKNOWN: |
| case PIXEL_FORMAT_UYVY: |
| case PIXEL_FORMAT_YUY2: |
| case PIXEL_FORMAT_ARGB: |
| case PIXEL_FORMAT_XRGB: |
| case PIXEL_FORMAT_RGB24: |
| case PIXEL_FORMAT_RGB32: |
| case PIXEL_FORMAT_MJPEG: |
| case PIXEL_FORMAT_ABGR: |
| case PIXEL_FORMAT_XBGR: |
| break; |
| } |
| } |
| NOTREACHED(); |
| return gfx::Size(); |
| } |
| |
| // static |
| gfx::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 gfx::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 gfx::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(); |
| size_t total_buffer_size = layout_.GetTotalBufferSize(); |
| // If caller does not provide buffer layout, it uses sum of calculated color |
| // planes' size as buffer size VideoFrame needs to allocate. |
| if (total_buffer_size == 0) { |
| 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 { |
| const size_t num_planes = NumPlanes(format()); |
| const size_t num_buffers = layout_.num_buffers(); |
| const bool buffer_equals_plane = num_buffers == num_planes; |
| const bool buffer_assigned = layout_.GetTotalBufferSize() > 0; |
| |
| // We have three cases for plane size mapping: |
| // 1) buffer size assigned, and #buffers == #planes: use buffers' size as |
| // color planes' size. |
| // 2) buffer size unassigned: use legacy calculation formula. |
| // 3) buffer size assigned, and #buffers < #planes: map first B-1 buffers' |
| // size to first B-1 color planes. And for the rest color planes' size, |
| // fallback to use legacy calculation formula. |
| // The reason to use buffer size (if available) as color plane size is that |
| // color plane size is used to calculate each plane's starting address. |
| // For caller who already specify a buffer for each plane, use buffer size |
| // to calculate buffer/plane head address is the trivial choice. |
| if (buffer_equals_plane && buffer_assigned) { |
| return layout_.buffer_sizes(); |
| } |
| |
| size_t mappable_buffers = 0; |
| if (buffer_assigned) |
| mappable_buffers = num_buffers - (buffer_equals_plane ? 0 : 1); |
| |
| std::vector<size_t> plane_size; |
| for (size_t plane = 0; plane < num_planes; ++plane) { |
| if (plane < mappable_buffers) { |
| DCHECK_LT(plane, num_buffers); |
| plane_size.push_back(layout_.buffer_sizes()[plane]); |
| } else { |
| // 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.push_back(width * height); |
| } |
| } |
| if (num_planes > 1 && mappable_buffers < num_planes) { |
| // 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(kUPlane, format())); |
| plane_size.back() += std::abs(stride(kUPlane)) + kFrameSizePadding; |
| } |
| return plane_size; |
| } |
| |
| } // namespace media |