[go: nahoru, domu]

blob: 184565b89057b05d915d813666ddeb021e213697 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// 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/logging.h"
#include "base/process/memory.h"
#include "base/strings/string_piece.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#include "media/base/color_plane_layout.h"
#include "media/base/format_utils.h"
#include "media/base/limits.h"
#include "media/base/timestamp_constants.h"
#include "media/base/video_util.h"
#include "ui/gfx/buffer_format_util.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/gpu_memory_buffer.h"
#if BUILDFLAG(IS_MAC)
#include "ui/gfx/mac/io_surface.h"
#endif
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;
}
void ReleaseMailboxAndDropGpuMemoryBuffer(
VideoFrame::ReleaseMailboxCB cb,
const gpu::SyncToken& sync_token,
std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer) {
std::move(cb).Run(sync_token);
}
VideoFrame::ReleaseMailboxAndGpuMemoryBufferCB WrapReleaseMailboxCB(
VideoFrame::ReleaseMailboxCB cb) {
if (cb.is_null())
return VideoFrame::ReleaseMailboxAndGpuMemoryBufferCB();
return base::BindOnce(&ReleaseMailboxAndDropGpuMemoryBuffer, std::move(cb));
}
} // namespace
// Static constexpr class for generating unique identifiers for each VideoFrame.
static base::AtomicSequenceNumber g_unique_id_generator;
// static
std::string VideoFrame::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 BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
case VideoFrame::STORAGE_DMABUFS:
return "DMABUFS";
#endif
case VideoFrame::STORAGE_GPU_MEMORY_BUFFER:
return "GPU_MEMORY_BUFFER";
}
NOTREACHED() << "Invalid StorageType provided: " << storage_type;
return "INVALID";
}
// static
bool VideoFrame::IsStorageTypeMappable(VideoFrame::StorageType storage_type) {
return
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
// This is not strictly needed but makes explicit that, at VideoFrame
// level, DmaBufs are not mappable from userspace.
storage_type != VideoFrame::STORAGE_DMABUFS &&
#endif
// GpuMemoryBuffer is not mappable at VideoFrame level. In most places
// GpuMemoryBuffer is opaque to the CPU, and for places that really need
// to access the data on CPU they can get the buffer with
// GetGpuMemoryBuffer() and call gfx::GpuMemoryBuffer::Map().
(storage_type == VideoFrame::STORAGE_UNOWNED_MEMORY ||
storage_type == VideoFrame::STORAGE_OWNED_MEMORY ||
storage_type == VideoFrame::STORAGE_SHMEM);
}
// static
bool VideoFrame::IsValidPlane(VideoPixelFormat format, size_t plane) {
DCHECK_LE(NumPlanes(format), static_cast<size_t>(kMaxPlanes));
return plane < NumPlanes(format);
}
// static
gfx::Size VideoFrame::SampleSize(VideoPixelFormat format, size_t plane) {
DCHECK(IsValidPlane(format, plane));
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:
case PIXEL_FORMAT_I444A:
case PIXEL_FORMAT_YUV444AP10:
return gfx::Size(1, 1);
case PIXEL_FORMAT_I422:
case PIXEL_FORMAT_YUV422P9:
case PIXEL_FORMAT_YUV422P10:
case PIXEL_FORMAT_YUV422P12:
case PIXEL_FORMAT_I422A:
case PIXEL_FORMAT_YUV422AP10:
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_YUV420P9:
case PIXEL_FORMAT_YUV420P10:
case PIXEL_FORMAT_YUV420P12:
case PIXEL_FORMAT_P016LE:
case PIXEL_FORMAT_YUV420AP10:
return gfx::Size(2, 2);
case PIXEL_FORMAT_UYVY:
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:
case PIXEL_FORMAT_RGBAF16:
break;
}
}
NOTREACHED();
return gfx::Size();
}
// Checks if |source_format| can be wrapped into a |target_format| frame.
static bool AreValidPixelFormatsForWrap(VideoPixelFormat source_format,
VideoPixelFormat target_format) {
return source_format == target_format ||
(source_format == PIXEL_FORMAT_I420A &&
target_format == PIXEL_FORMAT_I420) ||
(source_format == PIXEL_FORMAT_ARGB &&
target_format == PIXEL_FORMAT_XRGB) ||
(source_format == PIXEL_FORMAT_ABGR &&
target_format == PIXEL_FORMAT_XBGR);
}
// 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:
case PIXEL_FORMAT_RGBAF16:
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_UYVY:
case PIXEL_FORMAT_P016LE:
case PIXEL_FORMAT_I422A:
case PIXEL_FORMAT_I444A:
case PIXEL_FORMAT_YUV420AP10:
case PIXEL_FORMAT_YUV422AP10:
case PIXEL_FORMAT_YUV444AP10:
return true;
case PIXEL_FORMAT_UNKNOWN:
break;
}
NOTREACHED() << "Unsupported video frame format: " << format;
return false;
}
// Creates VideoFrameLayout for tightly packed frame.
static absl::optional<VideoFrameLayout> GetDefaultLayout(
VideoPixelFormat format,
const gfx::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:
case PIXEL_FORMAT_XRGB:
case PIXEL_FORMAT_ABGR:
case PIXEL_FORMAT_XBGR:
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:
DLOG(ERROR) << "Unsupported pixel format"
<< VideoPixelFormatToString(format);
return absl::nullopt;
}
return VideoFrameLayout::CreateWithPlanes(format, coded_size, planes);
}
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
// This class allows us to embed a vector<ScopedFD> into a scoped_refptr, and
// thus to have several VideoFrames share the same set of DMABUF FDs.
class VideoFrame::DmabufHolder
: public base::RefCountedThreadSafe<DmabufHolder> {
public:
DmabufHolder() = default;
DmabufHolder(std::vector<base::ScopedFD>&& fds) : fds_(std::move(fds)) {}
const std::vector<base::ScopedFD>& fds() const { return fds_; }
size_t size() const { return fds_.size(); }
private:
std::vector<base::ScopedFD> fds_;
friend class base::RefCountedThreadSafe<DmabufHolder>;
~DmabufHolder() = default;
};
#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
// static
bool VideoFrame::IsValidConfig(VideoPixelFormat format,
StorageType storage_type,
const gfx::Size& coded_size,
const gfx::Rect& visible_rect,
const gfx::Size& natural_size) {
return IsValidConfigInternal(format, FrameControlType::kNone, coded_size,
visible_rect, natural_size);
}
// 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::CreateVideoHoleFrame(
const base::UnguessableToken& overlay_plane_id,
const gfx::Size& natural_size,
base::TimeDelta timestamp) {
auto layout = VideoFrameLayout::Create(PIXEL_FORMAT_UNKNOWN, natural_size);
if (!layout) {
DLOG(ERROR) << "Invalid layout.";
return nullptr;
}
scoped_refptr<VideoFrame> frame = new VideoFrame(
*layout, StorageType::STORAGE_OPAQUE, gfx::Rect(natural_size),
natural_size, timestamp, FrameControlType::kVideoHole);
frame->metadata().overlay_plane_id = overlay_plane_id;
return frame;
}
// 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_NV12 && format != PIXEL_FORMAT_I420 &&
format != PIXEL_FORMAT_ABGR && format != PIXEL_FORMAT_XBGR &&
format != PIXEL_FORMAT_XR30 && format != PIXEL_FORMAT_XB30 &&
format != PIXEL_FORMAT_P016LE && format != PIXEL_FORMAT_RGBAF16) {
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_and_gmb_release_cb_ =
WrapReleaseMailboxCB(std::move(mailbox_holder_release_cb));
// Wrapping native textures should... have textures. https://crbug.com/864145.
DCHECK(frame->HasTextures());
DCHECK_GT(frame->NumTextures(), 0u);
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) {
StorageType storage_type = STORAGE_UNOWNED_MEMORY;
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;
}
const auto& last_plane = layout.planes()[layout.planes().size() - 1];
const size_t required_size = last_plane.offset + last_plane.size;
if (data_size < required_size) {
DLOG(ERROR) << __func__ << " Provided data size is too small. Provided "
<< data_size << " bytes, but " << required_size
<< " bytes are required."
<< 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;
}
return frame;
}
// 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) {
auto layout = VideoFrameLayout::CreateWithStrides(
format, coded_size, {y_stride, u_stride, v_stride});
if (!layout) {
DLOG(ERROR) << "Invalid layout.";
return nullptr;
}
return WrapExternalYuvDataWithLayout(*layout, visible_rect, natural_size,
y_data, u_data, v_data, timestamp);
}
// static
scoped_refptr<VideoFrame> VideoFrame::WrapExternalYuvDataWithLayout(
const VideoFrameLayout& layout,
const gfx::Rect& visible_rect,
const gfx::Size& natural_size,
uint8_t* y_data,
uint8_t* u_data,
uint8_t* v_data,
base::TimeDelta timestamp) {
const StorageType storage = STORAGE_UNOWNED_MEMORY;
const VideoPixelFormat format = layout.format();
if (!IsValidConfig(format, storage, layout.coded_size(), visible_rect,
natural_size)) {
DLOG(ERROR) << __func__ << " Invalid config."
<< ConfigToString(format, storage, layout.coded_size(),
visible_rect, natural_size);
return nullptr;
}
if (!IsYuvPlanar(format)) {
DLOG(ERROR) << __func__ << " Format is not YUV. " << format;
return nullptr;
}
DCHECK_LE(NumPlanes(format), 3u);
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;
}
auto layout = VideoFrameLayout::CreateWithStrides(
format, coded_size, {y_stride, u_stride, v_stride, a_stride});
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;
}
// 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 uv_stride,
uint8_t* y_data,
uint8_t* uv_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) != 2) {
DLOG(ERROR) << "Expecting Y, UV planes to be present for the video format.";
return nullptr;
}
auto layout = VideoFrameLayout::CreateWithStrides(format, coded_size,
{y_stride, uv_stride});
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_[kUVPlane] = uv_data;
return frame;
}
// static
scoped_refptr<VideoFrame> VideoFrame::WrapExternalGpuMemoryBuffer(
const gfx::Rect& visible_rect,
const gfx::Size& natural_size,
std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer,
const gpu::MailboxHolder (&mailbox_holders)[kMaxPlanes],
ReleaseMailboxAndGpuMemoryBufferCB mailbox_holder_and_gmb_release_cb,
base::TimeDelta timestamp) {
const absl::optional<VideoPixelFormat> format =
GfxBufferFormatToVideoPixelFormat(gpu_memory_buffer->GetFormat());
if (!format)
return nullptr;
constexpr StorageType storage = STORAGE_GPU_MEMORY_BUFFER;
const gfx::Size& coded_size = gpu_memory_buffer->GetSize();
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 num_planes =
NumberOfPlanesForLinearBufferFormat(gpu_memory_buffer->GetFormat());
std::vector<ColorPlaneLayout> planes(num_planes);
for (size_t i = 0; i < num_planes; ++i)
planes[i].stride = gpu_memory_buffer->stride(i);
uint64_t modifier = gfx::NativePixmapHandle::kNoModifier;
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
if (gpu_memory_buffer->GetType() == gfx::NATIVE_PIXMAP) {
const auto gmb_handle = gpu_memory_buffer->CloneHandle();
if (gmb_handle.is_null() ||
gmb_handle.native_pixmap_handle.planes.empty()) {
DLOG(ERROR) << "Failed to clone the GpuMemoryBufferHandle";
return nullptr;
}
if (gmb_handle.native_pixmap_handle.planes.size() != num_planes) {
DLOG(ERROR) << "Invalid number of planes="
<< gmb_handle.native_pixmap_handle.planes.size()
<< ", expected num_planes=" << num_planes;
return nullptr;
}
for (size_t i = 0; i < num_planes; ++i) {
const auto& plane = gmb_handle.native_pixmap_handle.planes[i];
planes[i].stride = plane.stride;
planes[i].offset = plane.offset;
planes[i].size = plane.size;
}
modifier = gmb_handle.native_pixmap_handle.modifier;
}
#endif
const auto layout = VideoFrameLayout::CreateWithPlanes(
*format, coded_size, std::move(planes),
VideoFrameLayout::kBufferAddressAlignment, modifier);
if (!layout) {
DLOG(ERROR) << __func__ << " Invalid layout";
return nullptr;
}
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;
}
frame->gpu_memory_buffer_ = std::move(gpu_memory_buffer);
memcpy(&frame->mailbox_holders_, mailbox_holders,
sizeof(frame->mailbox_holders_));
frame->mailbox_holders_and_gmb_release_cb_ =
std::move(mailbox_holder_and_gmb_release_cb);
return frame;
}
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
// 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_and_gmb_release_cb_ =
ReleaseMailboxAndGpuMemoryBufferCB();
frame->dmabuf_fds_ =
base::MakeRefCounted<DmabufHolder>(std::move(dmabuf_fds));
DCHECK(frame->HasDmaBufs());
return frame;
}
#endif
#if BUILDFLAG(IS_MAC)
// static
scoped_refptr<VideoFrame> VideoFrame::WrapUnacceleratedIOSurface(
gfx::GpuMemoryBufferHandle handle,
const gfx::Rect& visible_rect,
base::TimeDelta timestamp) {
if (handle.type != gfx::GpuMemoryBufferType::IO_SURFACE_BUFFER) {
DLOG(ERROR) << "Non-IOSurface handle.";
return nullptr;
}
gfx::ScopedIOSurface io_surface = handle.io_surface;
if (!io_surface) {
return nullptr;
}
// Only support NV12 IOSurfaces.
const OSType cv_pixel_format = IOSurfaceGetPixelFormat(io_surface);
if (cv_pixel_format != kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) {
DLOG(ERROR) << "Invalid (non-NV12) pixel format.";
return nullptr;
}
const VideoPixelFormat pixel_format = PIXEL_FORMAT_NV12;
// Retrieve the layout parameters for |io_surface_|.
const size_t num_planes = IOSurfaceGetPlaneCount(io_surface);
const gfx::Size size(IOSurfaceGetWidth(io_surface),
IOSurfaceGetHeight(io_surface));
std::vector<int32_t> strides;
for (size_t i = 0; i < num_planes; ++i)
strides.push_back(IOSurfaceGetBytesPerRowOfPlane(io_surface, i));
absl::optional<VideoFrameLayout> layout =
media::VideoFrameLayout::CreateWithStrides(pixel_format, size, strides);
if (!layout) {
DLOG(ERROR) << "Invalid layout.";
return nullptr;
}
const StorageType storage_type = STORAGE_UNOWNED_MEMORY;
if (!IsValidConfig(pixel_format, storage_type, size, visible_rect, size)) {
DLOG(ERROR) << "Invalid config.";
return nullptr;
}
// Lock the IOSurface for CPU read access. After the VideoFrame is created,
// add a destruction callback to unlock the IOSurface.
kern_return_t lock_result =
IOSurfaceLock(io_surface, kIOSurfaceLockReadOnly, nullptr);
if (lock_result != kIOReturnSuccess) {
DLOG(ERROR) << "Failed to lock IOSurface.";
return nullptr;
}
auto unlock_lambda = [](base::ScopedCFTypeRef<IOSurfaceRef> io_surface) {
IOSurfaceUnlock(io_surface, kIOSurfaceLockReadOnly, nullptr);
};
scoped_refptr<VideoFrame> frame =
new VideoFrame(*layout, storage_type, visible_rect, size, timestamp);
for (size_t i = 0; i < num_planes; ++i) {
frame->data_[i] = reinterpret_cast<uint8_t*>(
IOSurfaceGetBaseAddressOfPlane(io_surface, i));
}
frame->AddDestructionObserver(
base::BindOnce(unlock_lambda, std::move(io_surface)));
return frame;
}
// 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(
scoped_refptr<VideoFrame> frame,
VideoPixelFormat format,
const gfx::Rect& visible_rect,
const gfx::Size& natural_size) {
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;
}
size_t new_plane_count = NumPlanes(format);
absl::optional<VideoFrameLayout> new_layout;
if (format == frame->format()) {
new_layout = frame->layout();
} else {
std::vector<ColorPlaneLayout> new_planes = frame->layout().planes();
if (new_plane_count > new_planes.size()) {
DLOG(ERROR) << " Wrapping frame has more planes than old one."
<< " old plane count: " << new_planes.size()
<< " new plane count: " << new_plane_count;
return nullptr;
}
new_planes.resize(new_plane_count);
new_layout = VideoFrameLayout::CreateWithPlanes(format, frame->coded_size(),
new_planes);
}
if (!new_layout.has_value()) {
DLOG(ERROR) << " Can't create layout for the wrapping frame";
return nullptr;
}
scoped_refptr<VideoFrame> wrapping_frame(
new VideoFrame(new_layout.value(), 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 < new_plane_count; ++i) {
wrapping_frame->data_[i] = frame->data_[i];
}
}
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
DCHECK(frame->dmabuf_fds_);
// If there are any |dmabuf_fds_| plugged in, we should refer them too.
wrapping_frame->dmabuf_fds_ = frame->dmabuf_fds_;
#endif
if (frame->storage_type() == STORAGE_SHMEM) {
DCHECK(frame->shm_region_ && frame->shm_region_->IsValid());
wrapping_frame->BackWithSharedMemory(frame->shm_region_);
}
// Don't let a Matryoshka doll of frames occur. Do this here instead of above
// since |frame| may have different metadata than |frame->wrapped_frame_|.
//
// We must still keep |frame| alive though since it may have destruction
// observers which signal that the underlying resource is okay to reuse. E.g.,
// VideoFramePool.
if (frame->wrapped_frame_) {
wrapping_frame->AddDestructionObserver(
base::BindOnce([](scoped_refptr<VideoFrame>) {}, frame));
frame = frame->wrapped_frame_;
}
wrapping_frame->wrapped_frame_ = std::move(frame);
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, FrameControlType::kEos);
frame->metadata().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);
if (frame)
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);
if (frame)
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) {
gfx::Size size = PlaneSizeInSamples(format, plane, coded_size);
size.set_width(size.width() * BytesPerElement(format, plane));
return size;
}
// static
gfx::Size VideoFrame::PlaneSizeInSamples(VideoPixelFormat format,
size_t plane,
const gfx::Size& coded_size) {
DCHECK(IsValidPlane(format, plane));
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::AlignUp(width, 2);
height = base::bits::AlignUp(height, 2);
}
const gfx::Size subsample = SampleSize(format, plane);
DCHECK(width % subsample.width() == 0);
DCHECK(height % subsample.height() == 0);
return gfx::Size(width / subsample.width(), height / subsample.height());
}
// static
int VideoFrame::PlaneHorizontalBitsPerPixel(VideoPixelFormat format,
size_t plane) {
DCHECK(IsValidPlane(format, plane));
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(format, plane));
return PlaneHorizontalBitsPerPixel(format, plane) /
SampleSize(format, plane).height();
}
// static
size_t VideoFrame::RowBytes(size_t plane, VideoPixelFormat format, int width) {
DCHECK(IsValidPlane(format, plane));
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_RGBAF16:
return 8;
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_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:
case PIXEL_FORMAT_YUV420AP10:
case PIXEL_FORMAT_YUV422AP10:
case PIXEL_FORMAT_YUV444AP10:
return 2;
case PIXEL_FORMAT_NV12:
case PIXEL_FORMAT_NV21: {
static const int bytes_per_element[] = {1, 2};
DCHECK_LT(plane, std::size(bytes_per_element));
return bytes_per_element[plane];
}
case PIXEL_FORMAT_P016LE: {
static const int bytes_per_element[] = {1, 2};
DCHECK_LT(plane, std::size(bytes_per_element));
return bytes_per_element[plane] * 2;
}
case PIXEL_FORMAT_YV12:
case PIXEL_FORMAT_I420:
case PIXEL_FORMAT_I422:
case PIXEL_FORMAT_I420A:
case PIXEL_FORMAT_I444:
case PIXEL_FORMAT_I422A:
case PIXEL_FORMAT_I444A:
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::AlignUp(
RowBytes(plane, format, coded_size.width()), kFrameAddressAlignment));
}
}
return strides;
}
// static
size_t VideoFrame::Rows(size_t plane, VideoPixelFormat format, int height) {
DCHECK(IsValidPlane(format, plane));
const int sample_height = SampleSize(format, plane).height();
return base::bits::AlignUp(height, sample_height) / sample_height;
}
// static
size_t VideoFrame::Columns(size_t plane, VideoPixelFormat format, int width) {
DCHECK(IsValidPlane(format, plane));
const int sample_width = SampleSize(format, plane).width();
return base::bits::AlignUp(width, sample_width) / sample_width;
}
// static
void VideoFrame::HashFrameForTesting(base::MD5Context* context,
const 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<const char*>(
frame.data(plane) +
frame.stride(plane) * row),
frame.row_bytes(plane)));
}
}
}
void VideoFrame::BackWithSharedMemory(
const base::ReadOnlySharedMemoryRegion* region) {
DCHECK(!shm_region_);
DCHECK(!owned_shm_region_.IsValid());
// Either we should be backing a frame created with WrapExternal*, or we are
// wrapping an existing STORAGE_SHMEM, in which case the storage type has
// already been set to STORAGE_SHMEM.
DCHECK(storage_type_ == STORAGE_UNOWNED_MEMORY ||
storage_type_ == STORAGE_SHMEM);
DCHECK(region && region->IsValid());
storage_type_ = STORAGE_SHMEM;
shm_region_ = region;
}
void VideoFrame::BackWithOwnedSharedMemory(
base::ReadOnlySharedMemoryRegion region,
base::ReadOnlySharedMemoryMapping mapping) {
DCHECK(!shm_region_);
DCHECK(!owned_shm_region_.IsValid());
// We should be backing a frame created with WrapExternal*. We cannot be
// wrapping an existing STORAGE_SHMEM, as the region is unowned in that case.
DCHECK(storage_type_ == STORAGE_UNOWNED_MEMORY);
storage_type_ = STORAGE_SHMEM;
owned_shm_region_ = std::move(region);
shm_region_ = &owned_shm_region_;
owned_shm_mapping_ = std::move(mapping);
}
bool VideoFrame::IsMappable() const {
return IsStorageTypeMappable(storage_type_);
}
bool VideoFrame::HasTextures() const {
return wrapped_frame_ ? wrapped_frame_->HasTextures()
: !mailbox_holders_[0].mailbox.IsZero();
}
size_t VideoFrame::NumTextures() const {
if (wrapped_frame_)
return wrapped_frame_->NumTextures();
if (!HasTextures())
return 0;
size_t i = 0;
for (; i < NumPlanes(format()); ++i) {
const auto& mailbox = mailbox_holders_[i].mailbox;
if (mailbox.IsZero())
return i;
}
return i;
}
bool VideoFrame::HasGpuMemoryBuffer() const {
return wrapped_frame_ ? wrapped_frame_->HasGpuMemoryBuffer()
: !!gpu_memory_buffer_;
}
gfx::GpuMemoryBuffer* VideoFrame::GetGpuMemoryBuffer() const {
return wrapped_frame_ ? wrapped_frame_->GetGpuMemoryBuffer()
: gpu_memory_buffer_.get();
}
bool VideoFrame::IsSameAllocation(VideoPixelFormat format,
const gfx::Size& coded_size,
const gfx::Rect& visible_rect,
const gfx::Size& natural_size) const {
// CreateFrameInternal() changes coded_size to new_coded_size. Match that
// behavior here.
const gfx::Size new_coded_size = DetermineAlignedSize(format, coded_size);
return this->format() == format && this->coded_size() == new_coded_size &&
visible_rect_ == visible_rect && natural_size_ == natural_size;
}
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());
}
int VideoFrame::columns(size_t plane) const {
return Columns(plane, format(), coded_size().width());
}
template <typename T>
T VideoFrame::GetVisibleDataInternal(T data, size_t plane) const {
DCHECK(IsValidPlane(format(), plane));
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 +
stride(plane) * (offset.y() / subsample.height()) + // Row offset.
BytesPerElement(format(), plane) * // Column offset.
(offset.x() / subsample.width());
}
const uint8_t* VideoFrame::visible_data(size_t plane) const {
return GetVisibleDataInternal<const uint8_t*>(data(plane), plane);
}
uint8_t* VideoFrame::GetWritableVisibleData(size_t plane) {
return GetVisibleDataInternal<uint8_t*>(writable_data(plane), plane);
}
const gpu::MailboxHolder& VideoFrame::mailbox_holder(
size_t texture_index) const {
DCHECK(HasTextures());
DCHECK(IsValidPlane(format(), texture_index));
return wrapped_frame_ ? wrapped_frame_->mailbox_holder(texture_index)
: mailbox_holders_[texture_index];
}
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
const std::vector<base::ScopedFD>& VideoFrame::DmabufFds() const {
DCHECK_EQ(storage_type_, STORAGE_DMABUFS);
return dmabuf_fds_->fds();
}
bool VideoFrame::HasDmaBufs() const {
return dmabuf_fds_->size() > 0;
}
bool VideoFrame::IsSameDmaBufsAs(const VideoFrame& frame) const {
return storage_type_ == STORAGE_DMABUFS &&
frame.storage_type_ == STORAGE_DMABUFS &&
&DmabufFds() == &frame.DmabufFds();
}
#endif
#if BUILDFLAG(IS_MAC)
CVPixelBufferRef VideoFrame::CvPixelBuffer() const {
return cv_pixel_buffer_.get();
}
#endif
void VideoFrame::SetReleaseMailboxCB(ReleaseMailboxCB release_mailbox_cb) {
DCHECK(release_mailbox_cb);
DCHECK(!mailbox_holders_and_gmb_release_cb_);
// We don't relay SetReleaseMailboxCB to |wrapped_frame_| because the method
// is not thread safe. This method should only be called by the owner of
// |wrapped_frame_| directly.
DCHECK(!wrapped_frame_);
mailbox_holders_and_gmb_release_cb_ =
WrapReleaseMailboxCB(std::move(release_mailbox_cb));
}
void VideoFrame::SetReleaseMailboxAndGpuMemoryBufferCB(
ReleaseMailboxAndGpuMemoryBufferCB release_mailbox_cb) {
// See remarks in SetReleaseMailboxCB.
DCHECK(release_mailbox_cb);
DCHECK(!mailbox_holders_and_gmb_release_cb_);
DCHECK(!wrapped_frame_);
mailbox_holders_and_gmb_release_cb_ = std::move(release_mailbox_cb);
}
bool VideoFrame::HasReleaseMailboxCB() const {
return wrapped_frame_ ? wrapped_frame_->HasReleaseMailboxCB()
: !!mailbox_holders_and_gmb_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());
if (wrapped_frame_) {
return wrapped_frame_->UpdateReleaseSyncToken(client);
}
base::AutoLock locker(release_sync_token_lock_);
// Must wait on the previous sync point before inserting a new sync point so
// that |mailbox_holders_and_gmb_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_;
}
gpu::SyncToken VideoFrame::UpdateMailboxHolderSyncToken(
size_t plane,
SyncTokenClient* client) {
DCHECK(HasOneRef());
DCHECK(HasTextures());
DCHECK(!wrapped_frame_);
DCHECK_LT(plane, kMaxPlanes);
// No lock is required due to the HasOneRef() check.
auto& token = mailbox_holders_[plane].sync_token;
if (token.HasData())
client->WaitSyncToken(token);
client->GenerateSyncToken(&token);
return token;
}
std::string VideoFrame::AsHumanReadableString() const {
if (metadata().end_of_stream)
return "end of stream";
std::ostringstream s;
s << ConfigToString(format(), storage_type_, coded_size(), visible_rect_,
natural_size_)
<< " timestamp:" << timestamp_.InMicroseconds();
if (HasTextures())
s << " textures: " << NumTextures();
return s.str();
}
size_t VideoFrame::BitDepth() const {
return media::BitDepth(format());
}
VideoFrame::VideoFrame(const VideoFrameLayout& layout,
StorageType storage_type,
const gfx::Rect& visible_rect,
const gfx::Size& natural_size,
base::TimeDelta timestamp,
FrameControlType frame_control_type)
: layout_(layout),
storage_type_(storage_type),
visible_rect_(Intersection(visible_rect, gfx::Rect(layout.coded_size()))),
natural_size_(natural_size),
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
dmabuf_fds_(base::MakeRefCounted<DmabufHolder>()),
#endif
timestamp_(timestamp),
unique_id_(g_unique_id_generator.GetNext()) {
DCHECK(IsValidConfigInternal(format(), frame_control_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_and_gmb_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_and_gmb_release_cb_)
.Run(release_sync_token, std::move(gpu_memory_buffer_));
}
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
gfx::Size VideoFrame::DetermineAlignedSize(VideoPixelFormat format,
const gfx::Size& dimensions) {
const gfx::Size alignment = CommonAlignment(format);
const gfx::Size adjusted =
gfx::Size(base::bits::AlignUp(dimensions.width(), alignment.width()),
base::bits::AlignUp(dimensions.height(), alignment.height()));
DCHECK((adjusted.width() % alignment.width() == 0) &&
(adjusted.height() % alignment.height() == 0));
return adjusted;
}
// static
bool VideoFrame::IsValidSize(const gfx::Size& coded_size,
const gfx::Rect& visible_rect,
const gfx::Size& natural_size) {
return IsValidCodedSize(coded_size) && IsValidCodedSize(natural_size) &&
!(visible_rect.x() < 0 || visible_rect.y() < 0 ||
visible_rect.right() > coded_size.width() ||
visible_rect.bottom() > coded_size.height());
}
// static
bool VideoFrame::IsValidCodedSize(const gfx::Size& size) {
const int size_area = size.GetCheckedArea().ValueOrDefault(INT_MAX);
static_assert(limits::kMaxCanvas < INT_MAX, "");
return size_area <= limits::kMaxCanvas &&
size.width() <= limits::kMaxDimension &&
size.height() <= limits::kMaxDimension;
}
// static
bool VideoFrame::IsValidConfigInternal(VideoPixelFormat format,
FrameControlType frame_control_type,
const gfx::Size& coded_size,
const gfx::Rect& visible_rect,
const gfx::Size& natural_size) {
// Check maximum limits for all formats.
if (!IsValidSize(coded_size, visible_rect, natural_size)) {
return false;
}
switch (frame_control_type) {
case FrameControlType::kNone:
// Check that software-allocated buffer formats are not empty.
return !coded_size.IsEmpty() && !visible_rect.IsEmpty() &&
!natural_size.IsEmpty();
case FrameControlType::kEos:
DCHECK_EQ(format, PIXEL_FORMAT_UNKNOWN);
return coded_size.IsEmpty() && visible_rect.IsEmpty() &&
natural_size.IsEmpty();
case FrameControlType::kVideoHole:
DCHECK_EQ(format, PIXEL_FORMAT_UNKNOWN);
return !coded_size.IsEmpty() && !visible_rect.IsEmpty() &&
!natural_size.IsEmpty();
}
}
// 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, new_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));
return frame->AllocateMemory(zero_initialize_memory) ? frame : nullptr;
}
// 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);
}
bool 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 buffer_size =
std::accumulate(plane_size.begin(), plane_size.end(), 0u);
const size_t allocation_size =
buffer_size + (layout_.buffer_addr_align() - 1);
uint8_t* data = nullptr;
if (zero_initialize_memory) {
if (!base::UncheckedCalloc(1, allocation_size,
reinterpret_cast<void**>(&data)) ||
!data) {
return false;
}
} else {
if (!base::UncheckedMalloc(allocation_size,
reinterpret_cast<void**>(&data)) ||
!data) {
return false;
}
}
private_data_.reset(data);
data = base::bits::AlignUp(data, layout_.buffer_addr_align());
DCHECK_LE(data + buffer_size, private_data_.get() + allocation_size);
// 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];
}
return true;
}
bool VideoFrame::IsValidSharedMemoryFrame() const {
if (storage_type_ == STORAGE_SHMEM)
return shm_region_ && shm_region_->IsValid();
return false;
}
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::AlignUp(static_cast<size_t>(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