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

Unified Diff: media/filters/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: Rebase to ToT. Address nits Created 6 years, 2 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: media/filters/skcanvas_video_renderer.cc
diff --git a/media/filters/skcanvas_video_renderer.cc b/media/filters/skcanvas_video_renderer.cc
index 9dcf67a7ada815ea478a3ef9c00777c3ad04b918..41485d90b5237c9bca97cde3e0ce2e4f84dec3d9 100644
--- a/media/filters/skcanvas_video_renderer.cc
+++ b/media/filters/skcanvas_video_renderer.cc
@@ -5,11 +5,17 @@
#include "media/filters/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/core/SkImageGenerator.h"
+#include "third_party/skia/include/gpu/GrContext.h"
+#include "third_party/skia/include/gpu/SkGrPixelRef.h"
#include "ui/gfx/skbitmap_operations.h"
// Skia internal format depends on a platform. On Android it is ABGR, on others
@@ -28,7 +34,15 @@
namespace media {
-static bool IsYUV(media::VideoFrame::Format format) {
+namespace {
+
+// This class keeps two temporary resources; software bitmap, hardware bitmap.
+// If both bitmap are created and then only software bitmap is updated every
+// frame, hardware bitmap outlives until the media player dies. So we delete
+// a temporary resource if it is not used for 3 sec.
+const int kTemporaryResourceDeletionDelay = 3; // Seconds;
scherkus (not reviewing) 2014/10/15 17:10:23 note this is in media time, which has little relat
+
+bool IsYUV(media::VideoFrame::Format format) {
switch (format) {
case VideoFrame::YV12:
case VideoFrame::YV16:
@@ -49,7 +63,7 @@ static bool IsYUV(media::VideoFrame::Format format) {
return false;
}
-static bool IsJPEGColorSpace(media::VideoFrame::Format format) {
+bool IsJPEGColorSpace(media::VideoFrame::Format format) {
switch (format) {
case VideoFrame::YV12J:
return true;
@@ -70,12 +84,12 @@ static bool IsJPEGColorSpace(media::VideoFrame::Format format) {
return false;
}
-static bool IsYUVOrNative(media::VideoFrame::Format format) {
+bool IsYUVOrNative(media::VideoFrame::Format format) {
return IsYUV(format) || format == media::VideoFrame::NATIVE_TEXTURE;
}
// Converts a |video_frame| to raw |rgb_pixels|.
-static void ConvertVideoFrameToRGBPixels(
+void ConvertVideoFrameToRGBPixels(
const scoped_refptr<media::VideoFrame>& video_frame,
void* rgb_pixels,
size_t row_bytes) {
@@ -205,6 +219,94 @@ static void ConvertVideoFrameToRGBPixels(
}
}
+bool EnsureTextureBackedSkBitmap(GrContext* gr,
scherkus (not reviewing) 2014/10/15 17:10:23 I know you lifted this code from WebMediaPlayerAnd
+ SkBitmap* bitmap,
+ const gfx::Size& size) {
+ if (!bitmap->getTexture() || bitmap->width() != size.width() ||
+ bitmap->height() != size.height()) {
+ if (!gr)
+ return false;
+ GrTextureDesc desc;
+ // Use kRGBA_8888_GrPixelConfig, not kSkia8888_GrPixelConfig, because
+ // glCopyTextureChromium doesn't support GL_BGRA_EXT as internal format.
+ desc.fConfig = kRGBA_8888_GrPixelConfig;
+ desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
+ desc.fSampleCnt = 0;
+ desc.fOrigin = kTopLeft_GrSurfaceOrigin;
+ desc.fWidth = size.width();
+ desc.fHeight = size.height();
+ GrAutoScratchTexture scratch_texture(
+ gr, desc, GrContext::kExact_ScratchTexMatch);
+ skia::RefPtr<GrTexture> texture = skia::AdoptRef(scratch_texture.detach());
+ 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(
scherkus (not reviewing) 2014/10/15 17:10:23 considering all the various combinations of sw vs.
+ VideoFrame* video_frame,
+ SkBitmap* bitmap,
+ SkCanvasVideoRenderer::Context3DProvider* context_provider) {
+ DCHECK(context_provider && context_provider->gl &&
+ context_provider->gr_context &&
+ video_frame->format() == VideoFrame::NATIVE_TEXTURE);
+
+ // 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->gr_context,
+ bitmap,
+ video_frame->visible_rect().size())) {
+ return false;
+ }
+
+ unsigned textureId =
scherkus (not reviewing) 2014/10/15 17:10:24 textureId -> texture_id
+ static_cast<unsigned>((bitmap->getTexture())->getTextureHandle());
+ // If CopyVideoFrameToTexture() changes the state of the 'textureId', it's
+ // needed to invalidate the state cached in skia, but currently the state
+ // isn't changed.
+ SkCanvasVideoRenderer::CopyVideoFrameToTexture(context_provider->gl,
+ video_frame,
+ textureId,
+ 0,
+ GL_RGBA,
+ GL_UNSIGNED_BYTE,
+ true,
+ false);
+ return true;
+}
+
+class SyncPointClientImpl : public 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_;
+};
+
+bool IsNull(SkCanvasVideoRenderer::Context3DProvider* context_provider) {
+ return !context_provider ||
+ !(context_provider->gl && context_provider->gr_context);
+}
+
+} // anonymous namespace
+
// Generates an RGB image from a VideoFrame.
class VideoImageGenerator : public SkImageGenerator {
public:
@@ -289,18 +391,22 @@ class VideoImageGenerator : public SkImageGenerator {
};
SkCanvasVideoRenderer::SkCanvasVideoRenderer()
- : generator_(NULL), last_frame_timestamp_(media::kNoTimestamp()) {
+ : generator_(NULL),
+ last_frame_timestamp_(media::kNoTimestamp()),
+ accelerated_last_frame_timestamp_(media::kNoTimestamp()) {
last_frame_.setIsVolatile(true);
}
SkCanvasVideoRenderer::~SkCanvasVideoRenderer() {}
-void SkCanvasVideoRenderer::Paint(const scoped_refptr<VideoFrame>& video_frame,
- SkCanvas* canvas,
- const gfx::RectF& dest_rect,
- uint8 alpha,
- SkXfermode::Mode mode,
- VideoRotation video_rotation) {
+void SkCanvasVideoRenderer::Paint(
+ const scoped_refptr<VideoFrame>& video_frame,
+ SkCanvas* canvas,
+ const gfx::RectF& dest_rect,
+ uint8 alpha,
+ SkXfermode::Mode mode,
+ VideoRotation video_rotation,
+ SkCanvasVideoRenderer::Context3DProvider* context_provider) {
scherkus (not reviewing) 2014/10/15 17:10:23 we can drop SkCanvasVideoRenderer::, right?
if (alpha == 0) {
return;
}
@@ -313,32 +419,56 @@ void SkCanvasVideoRenderer::Paint(const scoped_refptr<VideoFrame>& video_frame,
// Paint black rectangle if there isn't a frame available or the
// frame has an unexpected format.
- if (!video_frame.get() || !IsYUVOrNative(video_frame->format())) {
+ if (!video_frame.get() || video_frame->natural_size().IsEmpty() ||
+ !IsYUVOrNative(video_frame->format())) {
canvas->drawRect(dest, paint);
canvas->flush();
return;
}
+ bool accelerated = false;
+ if (!IsNull(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 (accelerated_last_frame_.isNull() ||
+ video_frame->timestamp() != accelerated_last_frame_timestamp_ ||
+ video_frame->timestamp() == base::TimeDelta()) {
+ accelerated = ConvertVideoFrameToTexture(
+ video_frame.get(), &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 (last_frame_.isNull() ||
- video_frame->timestamp() != last_frame_timestamp_) {
- generator_ = new VideoImageGenerator(video_frame);
+ if (!accelerated) {
+ if (last_frame_.isNull() ||
+ video_frame->timestamp() != last_frame_timestamp_) {
+ generator_ = new VideoImageGenerator(video_frame);
+
+ // Note: This takes ownership of |generator_|.
+ if (!SkInstallDiscardablePixelRef(generator_, &last_frame_)) {
+ NOTREACHED();
+ }
+ DCHECK(video_frame->visible_rect().width() == last_frame_.width() &&
+ video_frame->visible_rect().height() == last_frame_.height());
- // Note: This takes ownership of |generator_|.
- if (!SkInstallDiscardablePixelRef(generator_, &last_frame_)) {
- NOTREACHED();
+ last_frame_timestamp_ = video_frame->timestamp();
+ } else if (generator_) {
+ generator_->set_frame(video_frame);
}
- DCHECK(video_frame->visible_rect().width() == last_frame_.width() &&
- video_frame->visible_rect().height() == last_frame_.height());
-
- last_frame_timestamp_ = video_frame->timestamp();
- } else if (generator_) {
- generator_->set_frame(video_frame);
}
paint.setXfermodeMode(mode);
paint.setFilterLevel(SkPaint::kLow_FilterLevel);
+ SkBitmap& target_frame = accelerated ? accelerated_last_frame_ : last_frame_;
bool need_transform =
video_rotation != VIDEO_ROTATION_0 ||
dest_rect.size() != video_frame->visible_rect().size() ||
@@ -371,12 +501,12 @@ void SkCanvasVideoRenderer::Paint(const scoped_refptr<VideoFrame>& video_frame,
gfx::SizeF(rotated_dest_size.height(), rotated_dest_size.width());
}
canvas->scale(
- SkFloatToScalar(rotated_dest_size.width() / last_frame_.width()),
- SkFloatToScalar(rotated_dest_size.height() / last_frame_.height()));
- canvas->translate(-SkFloatToScalar(last_frame_.width() * 0.5f),
- -SkFloatToScalar(last_frame_.height() * 0.5f));
+ SkFloatToScalar(rotated_dest_size.width() / target_frame.width()),
+ SkFloatToScalar(rotated_dest_size.height() / target_frame.height()));
+ canvas->translate(-SkFloatToScalar(target_frame.width() * 0.5f),
+ -SkFloatToScalar(target_frame.height() * 0.5f));
}
- canvas->drawBitmap(last_frame_, 0, 0, &paint);
+ canvas->drawBitmap(target_frame, 0, 0, &paint);
if (need_transform)
canvas->restore();
canvas->flush();
@@ -384,6 +514,7 @@ void SkCanvasVideoRenderer::Paint(const scoped_refptr<VideoFrame>& video_frame,
// |video_frame| not to be outlived.
if (generator_)
generator_->set_frame(NULL);
+ CleanUpTemporaryBuffers();
}
void SkCanvasVideoRenderer::Copy(const scoped_refptr<VideoFrame>& video_frame,
@@ -393,7 +524,66 @@ void SkCanvasVideoRenderer::Copy(const scoped_refptr<VideoFrame>& video_frame,
video_frame->visible_rect(),
0xff,
SkXfermode::kSrc_Mode,
- media::VIDEO_ROTATION_0);
+ media::VIDEO_ROTATION_0,
+ NULL);
+}
+
+void SkCanvasVideoRenderer::CleanUpTemporaryBuffers() {
+ static const base::TimeDelta buffer_time =
+ base::TimeDelta::FromSeconds(kTemporaryResourceDeletionDelay);
+ // Find the latest time stamp.
+ base::TimeDelta last_timestamp =
+ accelerated_last_frame_timestamp_ > last_frame_timestamp_
+ ? accelerated_last_frame_timestamp_
+ : last_frame_timestamp_;
+ // Delete a too old resource.
+ 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(
scherkus (not reviewing) 2014/10/15 17:10:23 ditto for precise naming this is much more like C
+ gpu::gles2::GLES2Interface* gl,
+ 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() == 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 media

Powered by Google App Engine
This is Rietveld 408576698