| Index: media/video/gpu_memory_buffer_video_frame_copier.cc
|
| diff --git a/media/video/gpu_memory_buffer_video_frame_copier.cc b/media/video/gpu_memory_buffer_video_frame_copier.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..0557d20750b1d4d5bb792db1cb057f4653a2b312
|
| --- /dev/null
|
| +++ b/media/video/gpu_memory_buffer_video_frame_copier.cc
|
| @@ -0,0 +1,393 @@
|
| +// Copyright 2015 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/video/gpu_memory_buffer_video_frame_copier.h"
|
| +
|
| +#include "base/barrier_closure.h"
|
| +#include "base/bind.h"
|
| +#include "media/renderers/gpu_video_accelerator_factories.h"
|
| +#include "media/video/gpu_memory_buffer_video_frame_pool.h"
|
| +#include "third_party/libyuv/include/libyuv.h"
|
| +
|
| +namespace media {
|
| +
|
| +class GpuMemoryBufferVideoFrameCopier::CopierImpl
|
| + : public base::RefCountedThreadSafe<
|
| + GpuMemoryBufferVideoFrameCopier::CopierImpl> {
|
| + public:
|
| + // |media_task_runner| is the media task runner associated with the
|
| + // GL context provided by |gpu_factories|
|
| + // |worker_task_runner| is a task runner used to asynchronously copy
|
| + // video frame's planes.
|
| + // |gpu_factories| is an interface to GPU related operation and can be
|
| + // null if a GL context is not available.
|
| + CopierImpl(
|
| + const scoped_refptr<base::SingleThreadTaskRunner>& media_task_runner,
|
| + const scoped_refptr<base::TaskRunner>& worker_task_runner,
|
| + GpuVideoAcceleratorFactories* gpu_factories)
|
| + : media_task_runner_(media_task_runner),
|
| + worker_task_runner_(worker_task_runner),
|
| + gpu_factories_(gpu_factories),
|
| + video_frame_pool_(new GpuMemoryBufferVideoFramePool(media_task_runner_,
|
| + gpu_factories)),
|
| + output_format_(PIXEL_FORMAT_UNKNOWN) {
|
| + DCHECK(media_task_runner_);
|
| + DCHECK(worker_task_runner_);
|
| + }
|
| +
|
| + // Takes a software VideoFrame and calls |frame_ready_cb| with a VideoFrame
|
| + // backed by native textures if possible.
|
| + // The data contained in video_frame is copied into the returned frame
|
| + // asynchronously posting tasks to |worker_task_runner_|, while
|
| + // |frame_ready_cb| will be called on |media_task_runner_| once all the data
|
| + // has been copied.
|
| + void CreateHardwareFrame(const scoped_refptr<VideoFrame>& video_frame,
|
| + const FrameReadyCB& cb);
|
| +
|
| + private:
|
| + friend class base::RefCountedThreadSafe<
|
| + GpuMemoryBufferVideoFrameCopier::CopierImpl>;
|
| + ~CopierImpl();
|
| +
|
| + // Copy |video_frame| data into |video_frame_future|
|
| + // and calls |frame_ready_cb| when done.
|
| + void CopyVideoFrameToGpuMemoryBuffers(
|
| + const scoped_refptr<VideoFrame>& video_frame,
|
| + std::unique_ptr<VideoFrameFuture> video_frame_future,
|
| + const FrameReadyCB& frame_ready_cb);
|
| +
|
| + // Called when all the data has been copied.
|
| + void OnCopiesDone(const scoped_refptr<VideoFrame>& video_frame,
|
| + std::unique_ptr<VideoFrameFuture> video_frame_future,
|
| + const FrameReadyCB& frame_ready_cb);
|
| +
|
| + // Create new VideoFrame by |video_frame_future| and calls |frame_ready_cb|.
|
| + // This has to be run on |media_task_runner_| where |frame_ready_cb| will also
|
| + // be run.
|
| + void CreateGpuMemoryBufferVideoFrame(
|
| + const scoped_refptr<VideoFrame>& video_frame,
|
| + std::unique_ptr<VideoFrameFuture> video_frame_future,
|
| + const FrameReadyCB& frame_ready_cb);
|
| +
|
| + // Task runner associated to the GL context provided by |gpu_factories_|.
|
| + scoped_refptr<base::SingleThreadTaskRunner> media_task_runner_;
|
| + // Task runner used to asynchronously copy planes.
|
| + scoped_refptr<base::TaskRunner> worker_task_runner_;
|
| +
|
| + // Interface to GPU related operations.
|
| + GpuVideoAcceleratorFactories* gpu_factories_;
|
| +
|
| + std::unique_ptr<GpuMemoryBufferVideoFramePool> video_frame_pool_;
|
| +
|
| + // TODO(dcastagna): change the following type from VideoPixelFormat to
|
| + // BufferFormat.
|
| + VideoPixelFormat output_format_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(CopierImpl);
|
| +};
|
| +
|
| +namespace {
|
| +
|
| +// VideoFrame copies to GpuMemoryBuffers will be split in copies where the
|
| +// output size is |kBytesPerCopyTarget| bytes and run in parallel.
|
| +const size_t kBytesPerCopyTarget = 1024 * 1024; // 1MB
|
| +
|
| +// The number of output rows to be copied in each iteration.
|
| +int RowsPerCopy(size_t plane, VideoPixelFormat format, int width) {
|
| + int bytes_per_row = VideoFrame::RowBytes(plane, format, width);
|
| + if (format == PIXEL_FORMAT_NV12) {
|
| + DCHECK_EQ(0u, plane);
|
| + bytes_per_row += VideoFrame::RowBytes(1, format, width);
|
| + }
|
| + // Copy an even number of lines, and at least one.
|
| + return std::max<size_t>((kBytesPerCopyTarget / bytes_per_row) & ~1, 1);
|
| +}
|
| +
|
| +void CopyRowsToI420Buffer(int first_row,
|
| + int rows,
|
| + int bytes_per_row,
|
| + const uint8_t* source,
|
| + int source_stride,
|
| + uint8_t* output,
|
| + int dest_stride,
|
| + const base::Closure& done) {
|
| + TRACE_EVENT2("media", "CopyRowsToI420Buffer", "bytes_per_row", bytes_per_row,
|
| + "rows", rows);
|
| + if (output) {
|
| + DCHECK_NE(dest_stride, 0);
|
| + DCHECK_LE(bytes_per_row, std::abs(dest_stride));
|
| + DCHECK_LE(bytes_per_row, source_stride);
|
| +
|
| + libyuv::CopyPlane(source + source_stride * first_row, source_stride,
|
| + output + dest_stride * first_row, dest_stride,
|
| + bytes_per_row, rows);
|
| + }
|
| + done.Run();
|
| +}
|
| +
|
| +void CopyRowsToNV12Buffer(int first_row,
|
| + int rows,
|
| + int bytes_per_row,
|
| + const scoped_refptr<VideoFrame>& source_frame,
|
| + uint8_t* dest_y,
|
| + int dest_stride_y,
|
| + uint8_t* dest_uv,
|
| + int dest_stride_uv,
|
| + const base::Closure& done) {
|
| + TRACE_EVENT2("media", "CopyRowsToNV12Buffer", "bytes_per_row", bytes_per_row,
|
| + "rows", rows);
|
| + if (dest_y && dest_uv) {
|
| + DCHECK_NE(dest_stride_y, 0);
|
| + DCHECK_NE(dest_stride_uv, 0);
|
| + DCHECK_LE(bytes_per_row, std::abs(dest_stride_y));
|
| + DCHECK_LE(bytes_per_row, std::abs(dest_stride_uv));
|
| + DCHECK_EQ(0, first_row % 2);
|
| +
|
| + libyuv::I420ToNV12(
|
| + source_frame->visible_data(VideoFrame::kYPlane) +
|
| + first_row * source_frame->stride(VideoFrame::kYPlane),
|
| + source_frame->stride(VideoFrame::kYPlane),
|
| + source_frame->visible_data(VideoFrame::kUPlane) +
|
| + first_row / 2 * source_frame->stride(VideoFrame::kUPlane),
|
| + source_frame->stride(VideoFrame::kUPlane),
|
| + source_frame->visible_data(VideoFrame::kVPlane) +
|
| + first_row / 2 * source_frame->stride(VideoFrame::kVPlane),
|
| + source_frame->stride(VideoFrame::kVPlane),
|
| + dest_y + first_row * dest_stride_y, dest_stride_y,
|
| + dest_uv + first_row / 2 * dest_stride_uv, dest_stride_uv, bytes_per_row,
|
| + rows);
|
| + }
|
| + done.Run();
|
| +}
|
| +
|
| +void CopyRowsToUYVYBuffer(int first_row,
|
| + int rows,
|
| + int width,
|
| + const scoped_refptr<VideoFrame>& source_frame,
|
| + uint8_t* output,
|
| + int dest_stride,
|
| + const base::Closure& done) {
|
| + TRACE_EVENT2("media", "CopyRowsToUYVYBuffer", "bytes_per_row", width * 2,
|
| + "rows", rows);
|
| + if (output) {
|
| + DCHECK_NE(dest_stride, 0);
|
| + DCHECK_LE(width, std::abs(dest_stride / 2));
|
| + DCHECK_EQ(0, first_row % 2);
|
| + libyuv::I420ToUYVY(
|
| + source_frame->visible_data(VideoFrame::kYPlane) +
|
| + first_row * source_frame->stride(VideoFrame::kYPlane),
|
| + source_frame->stride(VideoFrame::kYPlane),
|
| + source_frame->visible_data(VideoFrame::kUPlane) +
|
| + first_row / 2 * source_frame->stride(VideoFrame::kUPlane),
|
| + source_frame->stride(VideoFrame::kUPlane),
|
| + source_frame->visible_data(VideoFrame::kVPlane) +
|
| + first_row / 2 * source_frame->stride(VideoFrame::kVPlane),
|
| + source_frame->stride(VideoFrame::kVPlane),
|
| + output + first_row * dest_stride, dest_stride, width, rows);
|
| + }
|
| + done.Run();
|
| +}
|
| +
|
| +} // unnamed namespace
|
| +
|
| +// Creates a VideoFrame backed by native textures starting from a software
|
| +// VideoFrame.
|
| +// The data contained in |video_frame| is copied into the VideoFrame passed to
|
| +// |frame_ready_cb|.
|
| +// This has to be called on the thread where |media_task_runner_| is current.
|
| +void GpuMemoryBufferVideoFrameCopier::CopierImpl::CreateHardwareFrame(
|
| + const scoped_refptr<VideoFrame>& video_frame,
|
| + const FrameReadyCB& frame_ready_cb) {
|
| + DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| + // Lazily initialize output_format_ since VideoFrameOutputFormat() has to be
|
| + // called on the media_thread while this object might be instantiated on any.
|
| + if (output_format_ == PIXEL_FORMAT_UNKNOWN)
|
| + output_format_ = gpu_factories_->VideoFrameOutputFormat();
|
| +
|
| + if (output_format_ == PIXEL_FORMAT_UNKNOWN) {
|
| + frame_ready_cb.Run(video_frame);
|
| + return;
|
| + }
|
| +
|
| + if (video_frame->HasTextures()) {
|
| + frame_ready_cb.Run(video_frame);
|
| + return;
|
| + }
|
| +
|
| + switch (video_frame->format()) {
|
| + // Supported cases.
|
| + case PIXEL_FORMAT_YV12:
|
| + case PIXEL_FORMAT_I420:
|
| + break;
|
| + // Unsupported cases.
|
| + case PIXEL_FORMAT_YV12A:
|
| + case PIXEL_FORMAT_YV16:
|
| + case PIXEL_FORMAT_YV24:
|
| + case PIXEL_FORMAT_NV12:
|
| + case PIXEL_FORMAT_NV21:
|
| + 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_MT21:
|
| + 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_UNKNOWN:
|
| + frame_ready_cb.Run(video_frame);
|
| + return;
|
| + }
|
| +
|
| + std::unique_ptr<VideoFrameFuture> video_frame_future =
|
| + video_frame_pool_->CreateFrame(
|
| + output_format_, video_frame->visible_rect().size(),
|
| + video_frame->visible_rect(), video_frame->natural_size(),
|
| + video_frame->timestamp());
|
| + if (!video_frame_future) {
|
| + frame_ready_cb.Run(video_frame);
|
| + return;
|
| + }
|
| +
|
| + worker_task_runner_->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&CopierImpl::CopyVideoFrameToGpuMemoryBuffers, this,
|
| + video_frame, base::Passed(std::move(video_frame_future)),
|
| + frame_ready_cb));
|
| +}
|
| +
|
| +void GpuMemoryBufferVideoFrameCopier::CopierImpl::OnCopiesDone(
|
| + const scoped_refptr<VideoFrame>& video_frame,
|
| + std::unique_ptr<VideoFrameFuture> video_frame_future,
|
| + const FrameReadyCB& frame_ready_cb) {
|
| + media_task_runner_->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&CopierImpl::CreateGpuMemoryBufferVideoFrame, this,
|
| + video_frame, base::Passed(std::move(video_frame_future)),
|
| + frame_ready_cb));
|
| +}
|
| +
|
| +// Copies |video_frame| into |video_frame_future| asynchronously, posting n
|
| +// tasks that will be synchronized by a barrier.
|
| +// After the barrier is passed OnCopiesDone will be called.
|
| +void GpuMemoryBufferVideoFrameCopier::CopierImpl::
|
| + CopyVideoFrameToGpuMemoryBuffers(
|
| + const scoped_refptr<VideoFrame>& video_frame,
|
| + std::unique_ptr<VideoFrameFuture> video_frame_future,
|
| + const FrameReadyCB& frame_ready_cb) {
|
| + // Compute the number of tasks to post and create the barrier.
|
| + const size_t num_planes = VideoFrame::NumPlanes(output_format_);
|
| + const gfx::Size coded_size = video_frame_future->coded_size();
|
| + size_t copies = 0;
|
| + for (size_t i = 0; i < num_planes;
|
| + i += GpuMemoryBufferVideoFramePool::PlanesPerCopy(output_format_, i)) {
|
| + const int rows = VideoFrame::Rows(i, output_format_, coded_size.height());
|
| + const int rows_per_copy =
|
| + RowsPerCopy(i, output_format_, coded_size.width());
|
| + copies += rows / rows_per_copy;
|
| + if (rows % rows_per_copy)
|
| + ++copies;
|
| + }
|
| +
|
| + VideoFrameFuture* future_ptr = video_frame_future.get();
|
| + const base::Closure copies_done =
|
| + base::Bind(&CopierImpl::OnCopiesDone, this, video_frame,
|
| + base::Passed(std::move(video_frame_future)), frame_ready_cb);
|
| + const base::Closure barrier = base::BarrierClosure(copies, copies_done);
|
| +
|
| + // Post all the async tasks.
|
| + for (size_t i = 0; i < num_planes;
|
| + i += GpuMemoryBufferVideoFramePool::PlanesPerCopy(output_format_, i)) {
|
| + const int rows = VideoFrame::Rows(i, output_format_, coded_size.height());
|
| + const int rows_per_copy =
|
| + RowsPerCopy(i, output_format_, coded_size.width());
|
| +
|
| + for (int row = 0; row < rows; row += rows_per_copy) {
|
| + const int rows_to_copy = std::min(rows_per_copy, rows - row);
|
| + switch (output_format_) {
|
| + case PIXEL_FORMAT_I420: {
|
| + const int bytes_per_row =
|
| + VideoFrame::RowBytes(i, output_format_, coded_size.width());
|
| + worker_task_runner_->PostTask(
|
| + FROM_HERE, base::Bind(&CopyRowsToI420Buffer, row, rows_to_copy,
|
| + bytes_per_row, video_frame->visible_data(i),
|
| + video_frame->stride(i), future_ptr->data(i),
|
| + future_ptr->stride(i), barrier));
|
| + break;
|
| + }
|
| + case PIXEL_FORMAT_NV12:
|
| + DCHECK(i == 0);
|
| + worker_task_runner_->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&CopyRowsToNV12Buffer, row, rows_to_copy,
|
| + coded_size.width(), video_frame, future_ptr->data(0),
|
| + future_ptr->stride(0), future_ptr->data(1),
|
| + future_ptr->stride(1), barrier));
|
| + break;
|
| + case PIXEL_FORMAT_UYVY:
|
| + worker_task_runner_->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&CopyRowsToUYVYBuffer, row, rows_to_copy,
|
| + coded_size.width(), video_frame, future_ptr->data(i),
|
| + future_ptr->stride(i), barrier));
|
| + break;
|
| + default:
|
| + NOTREACHED();
|
| + }
|
| + }
|
| + }
|
| +}
|
| +
|
| +void GpuMemoryBufferVideoFrameCopier::CopierImpl::
|
| + CreateGpuMemoryBufferVideoFrame(
|
| + const scoped_refptr<VideoFrame>& video_frame,
|
| + std::unique_ptr<VideoFrameFuture> video_frame_future,
|
| + const FrameReadyCB& frame_ready_cb) {
|
| + scoped_refptr<VideoFrame> new_frame = video_frame_future->Release();
|
| + if (!new_frame) {
|
| + frame_ready_cb.Run(video_frame);
|
| + return;
|
| + }
|
| +
|
| + if (new_frame && new_frame->format() == PIXEL_FORMAT_I420 &&
|
| + video_frame->metadata()->IsTrue(VideoFrameMetadata::ALLOW_OVERLAY))
|
| + new_frame->metadata()->SetBoolean(VideoFrameMetadata::ALLOW_OVERLAY, true);
|
| +
|
| + base::TimeTicks render_time;
|
| + if (video_frame->metadata()->GetTimeTicks(VideoFrameMetadata::REFERENCE_TIME,
|
| + &render_time)) {
|
| + new_frame->metadata()->SetTimeTicks(VideoFrameMetadata::REFERENCE_TIME,
|
| + render_time);
|
| + }
|
| +
|
| + frame_ready_cb.Run(new_frame);
|
| +}
|
| +
|
| +GpuMemoryBufferVideoFrameCopier::CopierImpl::~CopierImpl() {}
|
| +
|
| +GpuMemoryBufferVideoFrameCopier::GpuMemoryBufferVideoFrameCopier() {}
|
| +
|
| +GpuMemoryBufferVideoFrameCopier::GpuMemoryBufferVideoFrameCopier(
|
| + const scoped_refptr<base::SingleThreadTaskRunner>& media_task_runner,
|
| + const scoped_refptr<base::TaskRunner>& worker_task_runner,
|
| + GpuVideoAcceleratorFactories* gpu_factories)
|
| + : copier_impl_(new CopierImpl(media_task_runner,
|
| + worker_task_runner,
|
| + gpu_factories)) {}
|
| +
|
| +GpuMemoryBufferVideoFrameCopier::~GpuMemoryBufferVideoFrameCopier() {}
|
| +
|
| +void GpuMemoryBufferVideoFrameCopier::MaybeCreateHardwareFrame(
|
| + const scoped_refptr<VideoFrame>& video_frame,
|
| + const FrameReadyCB& frame_ready_cb) {
|
| + DCHECK(video_frame);
|
| + copier_impl_->CreateHardwareFrame(video_frame, frame_ready_cb);
|
| +}
|
| +
|
| +} // namespace media
|
|
|