blob: 0c6950df7821533c71e1f6936987a1b1f5abcfe9 [file] [log] [blame]
// Copyright 2016 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/mojo/common/mojo_shared_buffer_video_frame.h"
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "base/numerics/safe_math.h"
#include "mojo/public/cpp/system/platform_handle.h"
namespace media {
// static
scoped_refptr<MojoSharedBufferVideoFrame>
MojoSharedBufferVideoFrame::CreateDefaultI420ForTesting(
const gfx::Size& dimensions,
base::TimeDelta timestamp) {
const VideoPixelFormat format = PIXEL_FORMAT_I420;
const gfx::Rect visible_rect(dimensions);
// Since we're allocating memory for the new frame, pad the requested
// size if necessary so that the requested size does line up on sample
// boundaries. See related discussion in VideoFrame::CreateFrameInternal().
const gfx::Size coded_size = DetermineAlignedSize(format, dimensions);
if (!IsValidConfig(format, STORAGE_MOJO_SHARED_BUFFER, coded_size,
visible_rect, dimensions)) {
LOG(DFATAL) << __func__ << " Invalid config. "
<< ConfigToString(format, STORAGE_MOJO_SHARED_BUFFER,
dimensions, visible_rect, dimensions);
return nullptr;
}
// Allocate a shared memory buffer big enough to hold the desired frame.
const size_t allocation_size = VideoFrame::AllocationSize(format, coded_size);
mojo::ScopedSharedBufferHandle handle =
mojo::SharedBufferHandle::Create(allocation_size);
if (!handle.is_valid()) {
DLOG(ERROR) << __func__ << " Unable to allocate memory.";
return nullptr;
}
// Create and initialize the frame. As this is I420 format, the U and V
// planes have samples for each 2x2 block. The memory is laid out as follows:
// - Yplane, full size (each element represents a 1x1 block)
// - Uplane, quarter size (each element represents a 2x2 block)
// - Vplane, quarter size (each element represents a 2x2 block)
DCHECK((coded_size.width() % 2 == 0) && (coded_size.height() % 2 == 0));
return Create(format, coded_size, visible_rect, dimensions, std::move(handle),
allocation_size, 0 /* y_offset */, coded_size.GetArea(),
coded_size.GetArea() * 5 / 4, coded_size.width(),
coded_size.width() / 2, coded_size.width() / 2, timestamp);
}
scoped_refptr<MojoSharedBufferVideoFrame>
MojoSharedBufferVideoFrame::CreateFromYUVFrame(const VideoFrame& frame) {
DCHECK_EQ(VideoFrame::NumPlanes(frame.format()), 3u);
const size_t y_stride = frame.stride(VideoFrame::kYPlane);
const size_t u_stride = frame.stride(VideoFrame::kUPlane);
const size_t v_stride = frame.stride(VideoFrame::kVPlane);
const size_t y_size =
VideoFrame::Rows(kYPlane, frame.format(), frame.coded_size().height()) *
y_stride;
const size_t u_size =
VideoFrame::Rows(kUPlane, frame.format(), frame.coded_size().height()) *
u_stride;
const size_t v_size =
VideoFrame::Rows(kVPlane, frame.format(), frame.coded_size().height()) *
v_stride;
size_t allocation_size = y_size + u_size + v_size;
mojo::ScopedSharedBufferHandle handle =
mojo::SharedBufferHandle::Create(allocation_size);
// Computes the offset of planes in shared memory buffer.
const size_t y_offset = 0u;
const size_t u_offset = y_offset + y_size;
const size_t v_offset = y_offset + y_size + u_size;
// The data from |frame| may not be consecutive between planes. Copy data into
// a shared memory buffer which is tightly packed. Padding inside each planes
// are preserved.
scoped_refptr<MojoSharedBufferVideoFrame> mojo_frame =
MojoSharedBufferVideoFrame::Create(
frame.format(), frame.coded_size(), frame.visible_rect(),
frame.natural_size(), std::move(handle), allocation_size, y_offset,
u_offset, v_offset, y_stride, u_stride, v_stride, frame.timestamp());
// Copy Y plane.
memcpy(mojo_frame->shared_buffer_data(),
static_cast<const void*>(frame.data(VideoFrame::kYPlane)), y_size);
// Copy U plane.
memcpy(mojo_frame->shared_buffer_data() + u_offset,
static_cast<const void*>(frame.data(VideoFrame::kUPlane)), u_size);
// Copy V plane.
memcpy(mojo_frame->shared_buffer_data() + v_offset,
static_cast<const void*>(frame.data(VideoFrame::kVPlane)), v_size);
// TODO(xingliu): Maybe also copy the alpha plane in
// |MojoSharedBufferVideoFrame|. The alpha plane is ignored here, but
// the |shared_memory| should contain the space for alpha plane.
return mojo_frame;
}
// static
scoped_refptr<MojoSharedBufferVideoFrame> MojoSharedBufferVideoFrame::Create(
VideoPixelFormat format,
const gfx::Size& coded_size,
const gfx::Rect& visible_rect,
const gfx::Size& natural_size,
mojo::ScopedSharedBufferHandle handle,
size_t data_size,
size_t y_offset,
size_t u_offset,
size_t v_offset,
int32_t y_stride,
int32_t u_stride,
int32_t v_stride,
base::TimeDelta timestamp) {
if (!IsValidConfig(format, STORAGE_MOJO_SHARED_BUFFER, coded_size,
visible_rect, natural_size)) {
LOG(DFATAL) << __func__ << " Invalid config. "
<< ConfigToString(format, STORAGE_MOJO_SHARED_BUFFER,
coded_size, visible_rect, natural_size);
return nullptr;
}
// Validate that the offsets and strides fit in the buffer.
//
// We can rely on coded_size.GetArea() being relatively small (compared to the
// range of an int) due to the IsValidConfig() check above.
//
// TODO(sandersd): Allow non-sequential formats.
if (NumPlanes(format) != 3) {
DLOG(ERROR) << __func__ << " " << VideoPixelFormatToString(format)
<< " is not supported; only YUV formats are allowed";
return nullptr;
}
if (y_stride < 0 || u_stride < 0 || v_stride < 0) {
DLOG(ERROR) << __func__ << " Invalid stride";
return nullptr;
}
// Compute the number of bytes needed on each row.
const size_t y_row_bytes = RowBytes(kYPlane, format, coded_size.width());
const size_t u_row_bytes = RowBytes(kUPlane, format, coded_size.width());
const size_t v_row_bytes = RowBytes(kVPlane, format, coded_size.width());
// Safe given sizeof(size_t) >= sizeof(int32_t).
size_t y_stride_size_t = y_stride;
size_t u_stride_size_t = u_stride;
size_t v_stride_size_t = v_stride;
if (y_stride_size_t < y_row_bytes || u_stride_size_t < u_row_bytes ||
v_stride_size_t < v_row_bytes) {
DLOG(ERROR) << __func__ << " Invalid stride";
return nullptr;
}
const size_t y_rows = Rows(kYPlane, format, coded_size.height());
const size_t u_rows = Rows(kUPlane, format, coded_size.height());
const size_t v_rows = Rows(kVPlane, format, coded_size.height());
// The last row only needs RowBytes() and not a full stride. This is to avoid
// problems if the U and V data is interleaved (where |stride| is double the
// number of bytes actually needed).
base::CheckedNumeric<size_t> y_bound = base::CheckAdd(
y_offset, base::CheckMul(base::CheckSub(y_rows, 1), y_stride_size_t),
y_row_bytes);
base::CheckedNumeric<size_t> u_bound = base::CheckAdd(
u_offset, base::CheckMul(base::CheckSub(u_rows, 1), u_stride_size_t),
u_row_bytes);
base::CheckedNumeric<size_t> v_bound = base::CheckAdd(
v_offset, base::CheckMul(base::CheckSub(v_rows, 1), v_stride_size_t),
v_row_bytes);
if (!y_bound.IsValid() || !u_bound.IsValid() || !v_bound.IsValid() ||
y_bound.ValueOrDie() > data_size || u_bound.ValueOrDie() > data_size ||
v_bound.ValueOrDie() > data_size) {
DLOG(ERROR) << __func__ << " Invalid offset";
return nullptr;
}
auto layout = VideoFrameLayout::CreateWithStrides(
format, coded_size, {y_stride, u_stride, v_stride});
if (!layout) {
return nullptr;
}
// Now allocate the frame and initialize it.
scoped_refptr<MojoSharedBufferVideoFrame> frame(
new MojoSharedBufferVideoFrame(*layout, visible_rect, natural_size,
std::move(handle), data_size, timestamp));
if (!frame->Init(y_offset, u_offset, v_offset)) {
DLOG(ERROR) << __func__ << " MojoSharedBufferVideoFrame::Init failed.";
return nullptr;
}
return frame;
}
MojoSharedBufferVideoFrame::MojoSharedBufferVideoFrame(
const VideoFrameLayout& layout,
const gfx::Rect& visible_rect,
const gfx::Size& natural_size,
mojo::ScopedSharedBufferHandle handle,
size_t mapped_size,
base::TimeDelta timestamp)
: VideoFrame(layout,
STORAGE_MOJO_SHARED_BUFFER,
visible_rect,
natural_size,
timestamp),
shared_buffer_handle_(std::move(handle)),
shared_buffer_size_(mapped_size) {
DCHECK(shared_buffer_handle_.is_valid());
}
bool MojoSharedBufferVideoFrame::Init(size_t y_offset,
size_t u_offset,
size_t v_offset) {
DCHECK(!shared_buffer_mapping_);
shared_buffer_mapping_ = shared_buffer_handle_->Map(shared_buffer_size_);
if (!shared_buffer_mapping_)
return false;
offsets_[kYPlane] = y_offset;
offsets_[kUPlane] = u_offset;
offsets_[kVPlane] = v_offset;
set_data(kYPlane, shared_buffer_data() + y_offset);
set_data(kUPlane, shared_buffer_data() + u_offset);
set_data(kVPlane, shared_buffer_data() + v_offset);
return true;
}
MojoSharedBufferVideoFrame::~MojoSharedBufferVideoFrame() {
// Call |mojo_shared_buffer_done_cb_| to take ownership of
// |shared_buffer_handle_|.
if (mojo_shared_buffer_done_cb_)
mojo_shared_buffer_done_cb_.Run(std::move(shared_buffer_handle_),
shared_buffer_size_);
}
size_t MojoSharedBufferVideoFrame::PlaneOffset(size_t plane) const {
DCHECK(IsValidPlane(plane, format()));
return offsets_[plane];
}
void MojoSharedBufferVideoFrame::SetMojoSharedBufferDoneCB(
const MojoSharedBufferDoneCB& mojo_shared_buffer_done_cb) {
mojo_shared_buffer_done_cb_ = mojo_shared_buffer_done_cb;
}
const mojo::SharedBufferHandle& MojoSharedBufferVideoFrame::Handle() const {
return shared_buffer_handle_.get();
}
size_t MojoSharedBufferVideoFrame::MappedSize() const {
return shared_buffer_size_;
}
} // namespace media