Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2231)

Unified Diff: cc/resources/media/skcanvas_video_renderer.cc

Issue 445013002: media: Optimize HW Video to 2D Canvas copy. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: cc/resources/media/skcanvas_video_renderer.cc
diff --git a/cc/resources/media/skcanvas_video_renderer.cc b/cc/resources/media/skcanvas_video_renderer.cc
new file mode 100644
index 0000000000000000000000000000000000000000..3a89a98576344ffb9d8657af78fc7e321283a84b
--- /dev/null
+++ b/cc/resources/media/skcanvas_video_renderer.cc
@@ -0,0 +1,380 @@
+// 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 "cc/resources/media/skcanvas_video_renderer.h"
+
+#include "base/logging.h"
+#include "gpu/GLES2/gl2extchromium.h"
+#include "gpu/command_buffer/client/gles2_interface.h"
+#include "gpu/command_buffer/common/mailbox_holder.h"
+#include "media/base/video_frame.h"
+#include "media/base/yuv_convert.h"
+#include "skia/ext/refptr.h"
+#include "third_party/libyuv/include/libyuv.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/gpu/GrContext.h"
+#include "third_party/skia/include/gpu/SkGrPixelRef.h"
+
+// Skia internal format depends on a platform. On Android it is ABGR, on others
+// it is ARGB.
+#if SK_B32_SHIFT == 0 && SK_G32_SHIFT == 8 && SK_R32_SHIFT == 16 && \
+ SK_A32_SHIFT == 24
+#define LIBYUV_I420_TO_ARGB libyuv::I420ToARGB
+#define LIBYUV_I422_TO_ARGB libyuv::I422ToARGB
+#elif SK_R32_SHIFT == 0 && SK_G32_SHIFT == 8 && SK_B32_SHIFT == 16 && \
+ SK_A32_SHIFT == 24
+#define LIBYUV_I420_TO_ARGB libyuv::I420ToABGR
+#define LIBYUV_I422_TO_ARGB libyuv::I422ToABGR
dshwang 2014/08/06 14:10:59 Now cc depends on thirdparty/libyuv directly. Howe
+#else
+#error Unexpected Skia ARGB_8888 layout!
+#endif
+
+namespace cc {
+
+namespace {
+
+bool IsYUV(media::VideoFrame::Format format) {
+ return format == media::VideoFrame::YV12 ||
+ format == media::VideoFrame::YV16 ||
+ format == media::VideoFrame::I420 ||
+ format == media::VideoFrame::YV12A ||
+ format == media::VideoFrame::YV12J ||
+ format == media::VideoFrame::YV24;
+}
+
+bool IsYUVOrNative(media::VideoFrame::Format format) {
+ return IsYUV(format) || format == media::VideoFrame::NATIVE_TEXTURE;
+}
+
+// Converts a VideoFrame containing YUV data to a SkBitmap containing RGB data.
+//
+// |bitmap| will be (re)allocated to match the dimensions of |video_frame|.
+void ConvertVideoFrameToBitmap(media::VideoFrame* video_frame,
+ SkBitmap* bitmap) {
+ DCHECK(IsYUVOrNative(video_frame->format())) << video_frame->format();
+ if (IsYUV(video_frame->format())) {
+ DCHECK_EQ(video_frame->stride(media::VideoFrame::kUPlane),
+ video_frame->stride(media::VideoFrame::kVPlane));
+ }
+
+ // Check if |bitmap| needs to be (re)allocated.
+ if (bitmap->isNull() ||
+ bitmap->width() != video_frame->visible_rect().width() ||
+ bitmap->height() != video_frame->visible_rect().height()) {
+ bitmap->allocN32Pixels(video_frame->visible_rect().width(),
+ video_frame->visible_rect().height());
+ bitmap->setIsVolatile(true);
+ }
+
+ bitmap->lockPixels();
+
+ size_t y_offset = 0;
+ size_t uv_offset = 0;
+ if (IsYUV(video_frame->format())) {
+ int y_shift = (video_frame->format() == media::VideoFrame::YV16) ? 0 : 1;
+ // Use the "left" and "top" of the destination rect to locate the offset
+ // in Y, U and V planes.
+ y_offset = (video_frame->stride(media::VideoFrame::kYPlane) *
+ video_frame->visible_rect().y()) +
+ video_frame->visible_rect().x();
+ // For format YV12, there is one U, V value per 2x2 block.
+ // For format YV16, there is one U, V value per 2x1 block.
+ uv_offset = (video_frame->stride(media::VideoFrame::kUPlane) *
+ (video_frame->visible_rect().y() >> y_shift)) +
+ (video_frame->visible_rect().x() >> 1);
+ }
+
+ switch (video_frame->format()) {
+ case media::VideoFrame::YV12:
+ case media::VideoFrame::I420:
+ LIBYUV_I420_TO_ARGB(
+ video_frame->data(media::VideoFrame::kYPlane) + y_offset,
+ video_frame->stride(media::VideoFrame::kYPlane),
+ video_frame->data(media::VideoFrame::kUPlane) + uv_offset,
+ video_frame->stride(media::VideoFrame::kUPlane),
+ video_frame->data(media::VideoFrame::kVPlane) + uv_offset,
+ video_frame->stride(media::VideoFrame::kVPlane),
+ static_cast<uint8*>(bitmap->getPixels()),
+ bitmap->rowBytes(),
+ video_frame->visible_rect().width(),
+ video_frame->visible_rect().height());
+ break;
+
+ case media::VideoFrame::YV12J:
+ media::ConvertYUVToRGB32(
+ video_frame->data(media::VideoFrame::kYPlane) + y_offset,
+ video_frame->data(media::VideoFrame::kUPlane) + uv_offset,
+ video_frame->data(media::VideoFrame::kVPlane) + uv_offset,
+ static_cast<uint8*>(bitmap->getPixels()),
+ video_frame->visible_rect().width(),
+ video_frame->visible_rect().height(),
+ video_frame->stride(media::VideoFrame::kYPlane),
+ video_frame->stride(media::VideoFrame::kUPlane),
+ bitmap->rowBytes(),
+ media::YV12J);
+ break;
+
+ case media::VideoFrame::YV16:
+ LIBYUV_I422_TO_ARGB(
+ video_frame->data(media::VideoFrame::kYPlane) + y_offset,
+ video_frame->stride(media::VideoFrame::kYPlane),
+ video_frame->data(media::VideoFrame::kUPlane) + uv_offset,
+ video_frame->stride(media::VideoFrame::kUPlane),
+ video_frame->data(media::VideoFrame::kVPlane) + uv_offset,
+ video_frame->stride(media::VideoFrame::kVPlane),
+ static_cast<uint8*>(bitmap->getPixels()),
+ bitmap->rowBytes(),
+ video_frame->visible_rect().width(),
+ video_frame->visible_rect().height());
+ break;
+
+ case media::VideoFrame::YV12A:
+ // Since libyuv doesn't support YUVA, fallback to media, which is not ARM
+ // optimized.
+ // TODO(fbarchard, mtomasz): Use libyuv, then copy the alpha channel.
+ media::ConvertYUVAToARGB(
+ video_frame->data(media::VideoFrame::kYPlane) + y_offset,
+ video_frame->data(media::VideoFrame::kUPlane) + uv_offset,
+ video_frame->data(media::VideoFrame::kVPlane) + uv_offset,
+ video_frame->data(media::VideoFrame::kAPlane),
+ static_cast<uint8*>(bitmap->getPixels()),
+ video_frame->visible_rect().width(),
+ video_frame->visible_rect().height(),
+ video_frame->stride(media::VideoFrame::kYPlane),
+ video_frame->stride(media::VideoFrame::kUPlane),
+ video_frame->stride(media::VideoFrame::kAPlane),
+ bitmap->rowBytes(),
+ media::YV12);
+ break;
+
+ case media::VideoFrame::YV24:
+ libyuv::I444ToARGB(
+ video_frame->data(media::VideoFrame::kYPlane) + y_offset,
+ video_frame->stride(media::VideoFrame::kYPlane),
+ video_frame->data(media::VideoFrame::kUPlane) + uv_offset,
+ video_frame->stride(media::VideoFrame::kUPlane),
+ video_frame->data(media::VideoFrame::kVPlane) + uv_offset,
+ video_frame->stride(media::VideoFrame::kVPlane),
+ static_cast<uint8*>(bitmap->getPixels()),
+ bitmap->rowBytes(),
+ video_frame->visible_rect().width(),
+ video_frame->visible_rect().height());
+#if SK_R32_SHIFT == 0 && SK_G32_SHIFT == 8 && SK_B32_SHIFT == 16 && \
+ SK_A32_SHIFT == 24
+ libyuv::ARGBToABGR(static_cast<uint8*>(bitmap->getPixels()),
+ bitmap->rowBytes(),
+ static_cast<uint8*>(bitmap->getPixels()),
+ bitmap->rowBytes(),
+ video_frame->visible_rect().width(),
+ video_frame->visible_rect().height());
+#endif
+ break;
+
+ case media::VideoFrame::NATIVE_TEXTURE:
+ DCHECK_EQ(video_frame->format(), media::VideoFrame::NATIVE_TEXTURE);
+ video_frame->ReadPixelsFromNativeTexture(*bitmap);
+ break;
+
+ default:
+ NOTREACHED();
+ break;
+ }
+ bitmap->notifyPixelsChanged();
+ bitmap->unlockPixels();
+}
+
+bool EnsureTextureBackedSkBitmap(GrContext* gr,
+ SkBitmap* bitmap,
+ const gfx::Size& size,
+ GrSurfaceOrigin origin,
+ GrPixelConfig config) {
+ if (!bitmap->getTexture() || bitmap->width() != size.width() ||
+ bitmap->height() != size.height()) {
+ if (!gr)
+ return false;
+ GrTextureDesc desc;
+ desc.fConfig = config;
+ desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
+ desc.fSampleCnt = 0;
+ desc.fOrigin = origin;
+ desc.fWidth = size.width();
+ desc.fHeight = size.height();
+ skia::RefPtr<GrTexture> texture;
+ texture = skia::AdoptRef(gr->createUncachedTexture(desc, 0, 0));
+ if (!texture.get())
+ return false;
+
+ SkImageInfo info = SkImageInfo::MakeN32Premul(desc.fWidth, desc.fHeight);
+ SkGrPixelRef* pixelRef = SkNEW_ARGS(SkGrPixelRef, (info, texture.get()));
+ if (!pixelRef)
+ return false;
+ bitmap->setInfo(info);
+ bitmap->setPixelRef(pixelRef)->unref();
+ }
+
+ return true;
+}
+
+bool ConvertVideoFrameToTexture(media::VideoFrame* video_frame,
+ SkBitmap* bitmap,
+ ContextProvider* context_provider) {
+ DCHECK(context_provider &&
+ video_frame->format() == media::VideoFrame::NATIVE_TEXTURE);
+ gpu::gles2::GLES2Interface* gl = context_provider->ContextGL();
+ DCHECK(gl);
+
+ // Check if we could reuse existing texture based bitmap.
+ // Otherwise, release existing texture based bitmap and allocate
+ // a new one based on video size.
+ if (!EnsureTextureBackedSkBitmap(context_provider->GrContext(),
+ bitmap,
+ video_frame->visible_rect().size(),
+ kTopLeft_GrSurfaceOrigin,
+ kSkia8888_GrPixelConfig)) {
+ return false;
+ }
+
+ unsigned textureId =
+ static_cast<unsigned>((bitmap->getTexture())->getTextureHandle());
+ SkCanvasVideoRenderer::CopyVideoFrameToTexture(
+ gl, video_frame, textureId, 0, GL_RGBA, GL_UNSIGNED_BYTE, true, false);
+ return true;
+}
+
+class SyncPointClientImpl : public media::VideoFrame::SyncPointClient {
+ public:
+ explicit SyncPointClientImpl(gpu::gles2::GLES2Interface* gl) : gl_(gl) {}
+ virtual ~SyncPointClientImpl() {}
+ virtual uint32 InsertSyncPoint() OVERRIDE {
+ return gl_->InsertSyncPointCHROMIUM();
+ }
+ virtual void WaitSyncPoint(uint32 sync_point) OVERRIDE {
+ gl_->WaitSyncPointCHROMIUM(sync_point);
+ }
+
+ private:
+ gpu::gles2::GLES2Interface* gl_;
+};
+
+} // anonymous namespace
+
+SkCanvasVideoRenderer::SkCanvasVideoRenderer()
+ : last_frame_timestamp_(media::kNoTimestamp()),
+ accelerated_last_frame_timestamp_(media::kNoTimestamp()) {
+}
+
+SkCanvasVideoRenderer::~SkCanvasVideoRenderer() {
+}
+
+void SkCanvasVideoRenderer::Paint(media::VideoFrame* video_frame,
+ SkCanvas* canvas,
+ const gfx::RectF& dest_rect,
+ uint8 alpha,
+ ContextProvider* context_provider) {
+ if (alpha == 0) {
+ return;
+ }
+
+ SkRect dest;
+ dest.set(dest_rect.x(), dest_rect.y(), dest_rect.right(), dest_rect.bottom());
+
+ SkPaint paint;
+ paint.setAlpha(alpha);
+
+ // Paint black rectangle if there isn't a frame available or the
+ // frame has an unexpected format.
+ if (!video_frame || video_frame->natural_size().IsEmpty() ||
+ !IsYUVOrNative(video_frame->format())) {
+ canvas->drawRect(dest, paint);
+ return;
+ }
+
+ bool accelerated = false;
+ if (context_provider &&
+ video_frame->format() == media::VideoFrame::NATIVE_TEXTURE &&
+ canvas->getGrContext()) {
+ // TODO(dshwang): Android video decoder doesn't update the timestamp on a
+ // VideoFrame. To reduce redundant copy, Android should update the
+ // timestamp.
+ if (video_frame->timestamp() != accelerated_last_frame_timestamp_ ||
+ video_frame->timestamp() == base::TimeDelta()) {
+ accelerated = ConvertVideoFrameToTexture(
+ video_frame, &accelerated_last_frame_, context_provider);
+ if (accelerated) {
+ accelerated_last_frame_timestamp_ = video_frame->timestamp();
+ }
+ } else {
+ DCHECK(accelerated_last_frame_.getTexture());
+ accelerated = true;
+ }
+ }
+
+ // Check if we should convert and update |last_frame_|.
+ if (!accelerated && video_frame->timestamp() != last_frame_timestamp_) {
+ ConvertVideoFrameToBitmap(video_frame, &last_frame_);
+ last_frame_timestamp_ = video_frame->timestamp();
+ }
+
+ canvas->drawBitmapRect(
+ accelerated ? accelerated_last_frame_ : last_frame_, NULL, dest, &paint);
+
+ CleanUpTemporaryBuffers();
+}
+
+// If a buffer is not used by 3 sec, remove it.
+void SkCanvasVideoRenderer::CleanUpTemporaryBuffers() {
+ static const base::TimeDelta buffer_time = base::TimeDelta::FromSeconds(3);
+ base::TimeDelta last_timestamp =
+ accelerated_last_frame_timestamp_ > last_frame_timestamp_
+ ? accelerated_last_frame_timestamp_
+ : last_frame_timestamp_;
+ if (last_timestamp > last_frame_timestamp_ + buffer_time &&
+ !last_frame_.isNull())
+ last_frame_.reset();
+ if (last_timestamp > accelerated_last_frame_timestamp_ + buffer_time &&
+ !accelerated_last_frame_.isNull())
+ accelerated_last_frame_.reset();
+}
+
+// static
+void SkCanvasVideoRenderer::CopyVideoFrameToTexture(
+ gpu::gles2::GLES2Interface* gl,
+ media::VideoFrame* video_frame,
+ unsigned int texture,
+ unsigned int level,
+ unsigned int internal_format,
+ unsigned int type,
+ bool premultiply_alpha,
+ bool flip_y) {
+ DCHECK(video_frame &&
+ video_frame->format() == media::VideoFrame::NATIVE_TEXTURE);
+ const gpu::MailboxHolder* mailbox_holder = video_frame->mailbox_holder();
+ DCHECK(mailbox_holder->texture_target == GL_TEXTURE_2D ||
+ mailbox_holder->texture_target == GL_TEXTURE_EXTERNAL_OES);
+
+ gl->WaitSyncPointCHROMIUM(mailbox_holder->sync_point);
+ uint32 source_texture = gl->CreateAndConsumeTextureCHROMIUM(
+ mailbox_holder->texture_target, mailbox_holder->mailbox.name);
+
+ // The video is stored in a unmultiplied format, so premultiply
+ // if necessary.
+ gl->PixelStorei(GL_UNPACK_PREMULTIPLY_ALPHA_CHROMIUM, premultiply_alpha);
+ // Application itself needs to take care of setting the right flip_y
+ // value down to get the expected result.
+ // flip_y==true means to reverse the video orientation while
+ // flip_y==false means to keep the intrinsic orientation.
+ gl->PixelStorei(GL_UNPACK_FLIP_Y_CHROMIUM, flip_y);
+ gl->CopyTextureCHROMIUM(
+ GL_TEXTURE_2D, source_texture, texture, level, internal_format, type);
+ gl->PixelStorei(GL_UNPACK_FLIP_Y_CHROMIUM, false);
+ gl->PixelStorei(GL_UNPACK_PREMULTIPLY_ALPHA_CHROMIUM, false);
+
+ gl->DeleteTextures(1, &source_texture);
+ gl->Flush();
+
+ SyncPointClientImpl client(gl);
+ video_frame->UpdateReleaseSyncPoint(&client);
+}
+
+} // namespace cc

Powered by Google App Engine
This is Rietveld 408576698