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 |