summaryrefslogtreecommitdiffstats
path: root/webkit/media
diff options
context:
space:
mode:
authorscherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-12-21 19:57:12 +0000
committerscherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-12-21 19:57:12 +0000
commitaf502b7e8ca3ba9e4cdd85348ee7d26ea29e5576 (patch)
treeb7d35fc89eaec39a6597b16a0321d7f803b22549 /webkit/media
parent977eab20cbb51e8866784c204ef7ca9ea7648cb8 (diff)
downloadchromium_src-af502b7e8ca3ba9e4cdd85348ee7d26ea29e5576.zip
chromium_src-af502b7e8ca3ba9e4cdd85348ee7d26ea29e5576.tar.gz
chromium_src-af502b7e8ca3ba9e4cdd85348ee7d26ea29e5576.tar.bz2
Clean up painting and conversion functions inside webkit_media::VideoRendererImpl.
The SkBitmap for a slow paint is now lazily allocated until it's needed. Furthermore the SkBitmap will be reallocated to match the dimensions of incoming VideoFrames. BUG=95640 Review URL: http://codereview.chromium.org/8742034 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@115375 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit/media')
-rw-r--r--webkit/media/video_renderer_impl.cc355
-rw-r--r--webkit/media/video_renderer_impl.h52
-rw-r--r--webkit/media/video_renderer_impl_unittest.cc253
-rw-r--r--webkit/media/webmediaplayer_impl.cc2
4 files changed, 454 insertions, 208 deletions
diff --git a/webkit/media/video_renderer_impl.cc b/webkit/media/video_renderer_impl.cc
index 01bc048..d1ae85b 100644
--- a/webkit/media/video_renderer_impl.cc
+++ b/webkit/media/video_renderer_impl.cc
@@ -9,71 +9,19 @@
#include "media/base/yuv_convert.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkDevice.h"
-#include "webkit/media/webmediaplayer_proxy.h"
namespace webkit_media {
-VideoRendererImpl::VideoRendererImpl(
- const scoped_refptr<WebMediaPlayerProxy>& proxy)
- : proxy_(proxy),
- last_converted_frame_(NULL) {
-}
-
-VideoRendererImpl::~VideoRendererImpl() {}
-
-bool VideoRendererImpl::OnInitialize(media::VideoDecoder* decoder) {
- natural_size_ = decoder->natural_size();
- bitmap_.setConfig(SkBitmap::kARGB_8888_Config,
- natural_size_.width(), natural_size_.height());
- bitmap_.allocPixels();
- bitmap_.eraseRGB(0x00, 0x00, 0x00);
- bitmap_.setIsVolatile(true);
- return true;
-}
-
-void VideoRendererImpl::OnStop(const base::Closure& callback) {
- if (!callback.is_null())
- callback.Run();
-}
-
-void VideoRendererImpl::OnFrameAvailable() {
- proxy_->Repaint();
-}
-
-// This method is always called on the renderer's thread.
-void VideoRendererImpl::Paint(SkCanvas* canvas, const gfx::Rect& dest_rect) {
- scoped_refptr<media::VideoFrame> video_frame;
- GetCurrentFrame(&video_frame);
- if (!video_frame) {
- SkPaint paint;
- paint.setColor(SK_ColorBLACK);
- canvas->drawRectCoords(
- static_cast<float>(dest_rect.x()),
- static_cast<float>(dest_rect.y()),
- static_cast<float>(dest_rect.right()),
- static_cast<float>(dest_rect.bottom()),
- paint);
- } else {
- if (CanFastPaint(canvas, dest_rect)) {
- FastPaint(video_frame, canvas, dest_rect);
- } else {
- SlowPaint(video_frame, canvas, dest_rect);
- }
- }
-
- PutCurrentFrame(video_frame);
-}
-
// CanFastPaint is a helper method to determine the conditions for fast
// painting. The conditions are:
// 1. No skew in canvas matrix.
// 2. No flipping nor mirroring.
// 3. Canvas has pixel format ARGB8888.
// 4. Canvas is opaque.
+//
// TODO(hclam): The fast paint method should support flipping and mirroring.
// Disable the flipping and mirroring checks once we have it.
-bool VideoRendererImpl::CanFastPaint(SkCanvas* canvas,
- const gfx::Rect& dest_rect) {
+static bool CanFastPaint(SkCanvas* canvas, const gfx::Rect& dest_rect) {
// Fast paint does not handle opacity value other than 1.0. Hence use slow
// paint if opacity is not 1.0. Since alpha = opacity * 0xFF, we check that
// alpha != 0xFF.
@@ -112,62 +60,39 @@ bool VideoRendererImpl::CanFastPaint(SkCanvas* canvas,
return false;
}
-void VideoRendererImpl::SlowPaint(media::VideoFrame* video_frame,
- SkCanvas* canvas,
- const gfx::Rect& dest_rect) {
- // 1. Convert YUV frame to RGB.
- base::TimeDelta timestamp = video_frame->GetTimestamp();
- if (video_frame != last_converted_frame_ ||
- timestamp != last_converted_timestamp_) {
- last_converted_frame_ = video_frame;
- last_converted_timestamp_ = timestamp;
- DCHECK(video_frame->format() == media::VideoFrame::YV12 ||
- video_frame->format() == media::VideoFrame::YV16);
- DCHECK(video_frame->stride(media::VideoFrame::kUPlane) ==
- video_frame->stride(media::VideoFrame::kVPlane));
- DCHECK(video_frame->planes() == media::VideoFrame::kNumYUVPlanes);
- bitmap_.lockPixels();
- media::YUVType yuv_type =
- (video_frame->format() == media::VideoFrame::YV12) ?
- media::YV12 : media::YV16;
- media::ConvertYUVToRGB32(video_frame->data(media::VideoFrame::kYPlane),
- video_frame->data(media::VideoFrame::kUPlane),
- video_frame->data(media::VideoFrame::kVPlane),
- static_cast<uint8*>(bitmap_.getPixels()),
- video_frame->width(),
- video_frame->height(),
- video_frame->stride(media::VideoFrame::kYPlane),
- video_frame->stride(media::VideoFrame::kUPlane),
- bitmap_.rowBytes(),
- yuv_type);
- bitmap_.notifyPixelsChanged();
- bitmap_.unlockPixels();
- }
-
- // 2. Paint the bitmap to canvas.
+// Slow paint does a scaled blit from an RGB source.
+static void SlowPaint(
+ const SkBitmap& bitmap,
+ SkCanvas* canvas,
+ const gfx::Rect& dest_rect) {
SkMatrix matrix;
matrix.setTranslate(static_cast<SkScalar>(dest_rect.x()),
static_cast<SkScalar>(dest_rect.y()));
- if (dest_rect.width() != natural_size_.width() ||
- dest_rect.height() != natural_size_.height()) {
+ if (dest_rect.width() != bitmap.width() ||
+ dest_rect.height() != bitmap.height()) {
matrix.preScale(SkIntToScalar(dest_rect.width()) /
- SkIntToScalar(natural_size_.width()),
+ SkIntToScalar(bitmap.width()),
SkIntToScalar(dest_rect.height()) /
- SkIntToScalar(natural_size_.height()));
+ SkIntToScalar(bitmap.height()));
}
SkPaint paint;
paint.setFlags(SkPaint::kFilterBitmap_Flag);
- canvas->drawBitmapMatrix(bitmap_, matrix, &paint);
+ canvas->drawBitmapMatrix(bitmap, matrix, &paint);
}
-void VideoRendererImpl::FastPaint(media::VideoFrame* video_frame,
- SkCanvas* canvas,
- const gfx::Rect& dest_rect) {
+// Fast paint does YUV => RGB, scaling, blitting all in one step into the
+// canvas. It's not always safe and appropriate to perform fast paint.
+// CanFastPaint() is used to determine the conditions.
+static void FastPaint(
+ const scoped_refptr<media::VideoFrame>& video_frame,
+ SkCanvas* canvas,
+ const gfx::Rect& dest_rect) {
DCHECK(video_frame->format() == media::VideoFrame::YV12 ||
video_frame->format() == media::VideoFrame::YV16);
- DCHECK(video_frame->stride(media::VideoFrame::kUPlane) ==
- video_frame->stride(media::VideoFrame::kVPlane));
+ DCHECK_EQ(video_frame->stride(media::VideoFrame::kUPlane),
+ video_frame->stride(media::VideoFrame::kVPlane));
DCHECK(video_frame->planes() == media::VideoFrame::kNumYUVPlanes);
+
const SkBitmap& bitmap = canvas->getDevice()->accessBitmap(true);
media::YUVType yuv_type = (video_frame->format() == media::VideoFrame::YV12) ?
media::YV12 : media::YV16;
@@ -190,77 +115,177 @@ void VideoRendererImpl::FastPaint(media::VideoFrame* video_frame,
local_dest_rect.round(&local_dest_irect);
local_dest_rect.round(&local_dest_irect_saved);
- // Only does the paint if the destination rect intersects with the clip
- // rect.
- if (local_dest_irect.intersect(canvas->getTotalClip().getBounds())) {
- // At this point |local_dest_irect| contains the rect that we should draw
- // to within the clipping rect.
-
- // Calculate the address for the top left corner of destination rect in
- // the canvas that we will draw to. The address is obtained by the base
- // address of the canvas shifted by "left" and "top" of the rect.
- uint8* dest_rect_pointer = static_cast<uint8*>(bitmap.getPixels()) +
- local_dest_irect.fTop * bitmap.rowBytes() +
- local_dest_irect.fLeft * 4;
-
- // Project the clip rect to the original video frame, obtains the
- // dimensions of the projected clip rect, "left" and "top" of the rect.
- // The math here are all integer math so we won't have rounding error and
- // write outside of the canvas.
- // We have the assumptions of dest_rect.width() and dest_rect.height()
- // being non-zero, these are valid assumptions since finding intersection
- // above rejects empty rectangle so we just do a DCHECK here.
- DCHECK_NE(0, dest_rect.width());
- DCHECK_NE(0, dest_rect.height());
- size_t frame_clip_width = local_dest_irect.width() *
- video_frame->width() / local_dest_irect_saved.width();
- size_t frame_clip_height = local_dest_irect.height() *
- video_frame->height() / local_dest_irect_saved.height();
-
- // Project the "left" and "top" of the final destination rect to local
- // coordinates of the video frame, use these values to find the offsets
- // in the video frame to start reading.
- size_t frame_clip_left =
- (local_dest_irect.fLeft - local_dest_irect_saved.fLeft) *
- video_frame->width() / local_dest_irect_saved.width();
- size_t frame_clip_top =
- (local_dest_irect.fTop - local_dest_irect_saved.fTop) *
- video_frame->height() / local_dest_irect_saved.height();
-
- // Use the "left" and "top" of the destination rect to locate the offset
- // in Y, U and V planes.
- size_t y_offset = video_frame->stride(media::VideoFrame::kYPlane) *
- frame_clip_top + frame_clip_left;
- // For format YV12, there is one U, V value per 2x2 block.
- // For format YV16, there is one u, V value per 2x1 block.
- size_t uv_offset = (video_frame->stride(media::VideoFrame::kUPlane) *
- (frame_clip_top >> y_shift)) + (frame_clip_left >> 1);
- uint8* frame_clip_y =
- video_frame->data(media::VideoFrame::kYPlane) + y_offset;
- uint8* frame_clip_u =
- video_frame->data(media::VideoFrame::kUPlane) + uv_offset;
- uint8* frame_clip_v =
- video_frame->data(media::VideoFrame::kVPlane) + uv_offset;
- bitmap.lockPixels();
-
- // TODO(hclam): do rotation and mirroring here.
- // TODO(fbarchard): switch filtering based on performance.
- media::ScaleYUVToRGB32(frame_clip_y,
- frame_clip_u,
- frame_clip_v,
- dest_rect_pointer,
- frame_clip_width,
- frame_clip_height,
- local_dest_irect.width(),
- local_dest_irect.height(),
+ // No point painting if the destination rect doesn't intersect with the
+ // clip rect.
+ if (!local_dest_irect.intersect(canvas->getTotalClip().getBounds()))
+ return;
+
+ // At this point |local_dest_irect| contains the rect that we should draw
+ // to within the clipping rect.
+
+ // Calculate the address for the top left corner of destination rect in
+ // the canvas that we will draw to. The address is obtained by the base
+ // address of the canvas shifted by "left" and "top" of the rect.
+ uint8* dest_rect_pointer = static_cast<uint8*>(bitmap.getPixels()) +
+ local_dest_irect.fTop * bitmap.rowBytes() +
+ local_dest_irect.fLeft * 4;
+
+ // Project the clip rect to the original video frame, obtains the
+ // dimensions of the projected clip rect, "left" and "top" of the rect.
+ // The math here are all integer math so we won't have rounding error and
+ // write outside of the canvas.
+ // We have the assumptions of dest_rect.width() and dest_rect.height()
+ // being non-zero, these are valid assumptions since finding intersection
+ // above rejects empty rectangle so we just do a DCHECK here.
+ DCHECK_NE(0, dest_rect.width());
+ DCHECK_NE(0, dest_rect.height());
+ size_t frame_clip_width = local_dest_irect.width() *
+ video_frame->width() / local_dest_irect_saved.width();
+ size_t frame_clip_height = local_dest_irect.height() *
+ video_frame->height() / local_dest_irect_saved.height();
+
+ // Project the "left" and "top" of the final destination rect to local
+ // coordinates of the video frame, use these values to find the offsets
+ // in the video frame to start reading.
+ size_t frame_clip_left =
+ (local_dest_irect.fLeft - local_dest_irect_saved.fLeft) *
+ video_frame->width() / local_dest_irect_saved.width();
+ size_t frame_clip_top =
+ (local_dest_irect.fTop - local_dest_irect_saved.fTop) *
+ video_frame->height() / local_dest_irect_saved.height();
+
+ // Use the "left" and "top" of the destination rect to locate the offset
+ // in Y, U and V planes.
+ size_t y_offset = video_frame->stride(media::VideoFrame::kYPlane) *
+ frame_clip_top + frame_clip_left;
+
+ // For format YV12, there is one U, V value per 2x2 block.
+ // For format YV16, there is one u, V value per 2x1 block.
+ size_t uv_offset = (video_frame->stride(media::VideoFrame::kUPlane) *
+ (frame_clip_top >> y_shift)) + (frame_clip_left >> 1);
+ uint8* frame_clip_y =
+ video_frame->data(media::VideoFrame::kYPlane) + y_offset;
+ uint8* frame_clip_u =
+ video_frame->data(media::VideoFrame::kUPlane) + uv_offset;
+ uint8* frame_clip_v =
+ video_frame->data(media::VideoFrame::kVPlane) + uv_offset;
+
+ // TODO(hclam): do rotation and mirroring here.
+ // TODO(fbarchard): switch filtering based on performance.
+ bitmap.lockPixels();
+ media::ScaleYUVToRGB32(frame_clip_y,
+ frame_clip_u,
+ frame_clip_v,
+ dest_rect_pointer,
+ frame_clip_width,
+ frame_clip_height,
+ local_dest_irect.width(),
+ local_dest_irect.height(),
+ video_frame->stride(media::VideoFrame::kYPlane),
+ video_frame->stride(media::VideoFrame::kUPlane),
+ bitmap.rowBytes(),
+ yuv_type,
+ media::ROTATE_0,
+ media::FILTER_BILINEAR);
+ bitmap.unlockPixels();
+}
+
+// Converts a VideoFrame containing YUV data to a SkBitmap containing RGB data.
+//
+// |bitmap| will be (re)allocated to match the dimensions of |video_frame|.
+static void ConvertVideoFrameToBitmap(
+ const scoped_refptr<media::VideoFrame>& video_frame,
+ SkBitmap* bitmap) {
+ DCHECK(video_frame->format() == media::VideoFrame::YV12 ||
+ video_frame->format() == media::VideoFrame::YV16);
+ DCHECK(video_frame->stride(media::VideoFrame::kUPlane) ==
+ video_frame->stride(media::VideoFrame::kVPlane));
+ DCHECK(video_frame->planes() == media::VideoFrame::kNumYUVPlanes);
+
+ // Check if |bitmap| needs to be (re)allocated.
+ if (bitmap->isNull() ||
+ bitmap->width() != static_cast<int>(video_frame->width()) ||
+ bitmap->height() != static_cast<int>(video_frame->height())) {
+ bitmap->setConfig(SkBitmap::kARGB_8888_Config,
+ video_frame->width(),
+ video_frame->height());
+ bitmap->allocPixels();
+ bitmap->setIsVolatile(true);
+ }
+
+ bitmap->lockPixels();
+ media::YUVType yuv_type =
+ (video_frame->format() == media::VideoFrame::YV12) ?
+ media::YV12 : media::YV16;
+ media::ConvertYUVToRGB32(video_frame->data(media::VideoFrame::kYPlane),
+ video_frame->data(media::VideoFrame::kUPlane),
+ video_frame->data(media::VideoFrame::kVPlane),
+ static_cast<uint8*>(bitmap->getPixels()),
+ video_frame->width(),
+ video_frame->height(),
video_frame->stride(media::VideoFrame::kYPlane),
video_frame->stride(media::VideoFrame::kUPlane),
- bitmap.rowBytes(),
- yuv_type,
- media::ROTATE_0,
- media::FILTER_BILINEAR);
- bitmap.unlockPixels();
+ bitmap->rowBytes(),
+ yuv_type);
+ bitmap->notifyPixelsChanged();
+ bitmap->unlockPixels();
+}
+
+VideoRendererImpl::VideoRendererImpl(const base::Closure& paint_cb)
+ : paint_cb_(paint_cb),
+ bitmap_timestamp_(media::kNoTimestamp) {
+}
+
+VideoRendererImpl::~VideoRendererImpl() {}
+
+// This method is always called on the renderer's thread.
+void VideoRendererImpl::Paint(SkCanvas* canvas, const gfx::Rect& dest_rect) {
+ scoped_refptr<media::VideoFrame> video_frame;
+ GetCurrentFrame(&video_frame);
+
+ // Paint black rectangle if there isn't a frame available.
+ if (!video_frame) {
+ SkPaint paint;
+ paint.setColor(SK_ColorBLACK);
+ canvas->drawRectCoords(
+ static_cast<float>(dest_rect.x()),
+ static_cast<float>(dest_rect.y()),
+ static_cast<float>(dest_rect.right()),
+ static_cast<float>(dest_rect.bottom()),
+ paint);
+ PutCurrentFrame(video_frame);
+ return;
+ }
+
+ // Scale and convert to RGB in one step if we can.
+ if (CanFastPaint(canvas, dest_rect)) {
+ FastPaint(video_frame, canvas, dest_rect);
+ PutCurrentFrame(video_frame);
+ return;
+ }
+
+ // Check if we should convert and update |bitmap_|.
+ if (video_frame->GetTimestamp() != bitmap_timestamp_) {
+ ConvertVideoFrameToBitmap(video_frame, &bitmap_);
+ bitmap_timestamp_ = video_frame->GetTimestamp();
}
+
+ // Do a slower paint using |bitmap_|.
+ SlowPaint(bitmap_, canvas, dest_rect);
+ PutCurrentFrame(video_frame);
+}
+
+bool VideoRendererImpl::OnInitialize(media::VideoDecoder* decoder) {
+ return true;
+}
+
+void VideoRendererImpl::OnStop(const base::Closure& callback) {
+ if (!callback.is_null())
+ callback.Run();
+}
+
+void VideoRendererImpl::OnFrameAvailable() {
+ paint_cb_.Run();
}
} // namespace webkit_media
diff --git a/webkit/media/video_renderer_impl.h b/webkit/media/video_renderer_impl.h
index 20cacad..c9e4fb9 100644
--- a/webkit/media/video_renderer_impl.h
+++ b/webkit/media/video_renderer_impl.h
@@ -5,27 +5,23 @@
#ifndef WEBKIT_MEDIA_VIDEO_RENDERER_IMPL_H_
#define WEBKIT_MEDIA_VIDEO_RENDERER_IMPL_H_
-#include "media/base/buffers.h"
+#include "base/callback.h"
#include "media/base/filters.h"
#include "media/filters/video_renderer_base.h"
-#include "third_party/WebKit/Source/WebKit/chromium/public/WebMediaPlayer.h"
#include "ui/gfx/rect.h"
-#include "ui/gfx/size.h"
#include "third_party/skia/include/core/SkBitmap.h"
class SkCanvas;
namespace webkit_media {
-class WebMediaPlayerProxy;
-
-// The video renderer implementation to be use by the media pipeline. It lives
-// inside video renderer thread and also WebKit's main thread. We need to be
-// extra careful about members shared by two different threads, especially
-// video frame buffers.
+// The video renderer implementation used by the media pipeline. It runs
+// primarily on the video renderer thread with the render thread used for
+// painting.
class VideoRendererImpl : public media::VideoRendererBase {
public:
- explicit VideoRendererImpl(const scoped_refptr<WebMediaPlayerProxy>& proxy);
+ // |paint_cb| is executed whenever a new frame is available for painting.
+ explicit VideoRendererImpl(const base::Closure& paint_cb);
virtual ~VideoRendererImpl();
// Paint the current front frame on the |canvas| stretching it to fit the
@@ -41,40 +37,12 @@ class VideoRendererImpl : public media::VideoRendererBase {
virtual void OnFrameAvailable() OVERRIDE;
private:
- // Determine the conditions to perform fast paint. Returns true if we can do
- // fast paint otherwise false.
- bool CanFastPaint(SkCanvas* canvas, const gfx::Rect& dest_rect);
-
- // Slow paint does a YUV => RGB, and scaled blit in two separate operations.
- void SlowPaint(media::VideoFrame* video_frame,
- SkCanvas* canvas,
- const gfx::Rect& dest_rect);
-
- // Fast paint does YUV => RGB, scaling, blitting all in one step into the
- // canvas. It's not always safe and appropriate to perform fast paint.
- // CanFastPaint() is used to determine the conditions.
- void FastPaint(media::VideoFrame* video_frame,
- SkCanvas* canvas,
- const gfx::Rect& dest_rect);
+ // Callback to execute when a new frame is available for painting.
+ base::Closure paint_cb_;
- // Pointer to our parent object that is called to request repaints.
- scoped_refptr<WebMediaPlayerProxy> proxy_;
-
- // An RGB bitmap used to convert the video frames.
+ // An RGB bitmap and corresponding timestamp of converted video frame data.
SkBitmap bitmap_;
-
- // These two members are used to determine if the |bitmap_| contains
- // an already converted image of the current frame. IMPORTANT NOTE: The
- // value of |last_converted_frame_| must only be used for comparison purposes,
- // and it should be assumed that the value of the pointer is INVALID unless
- // it matches the pointer returned from GetCurrentFrame(). Even then, just
- // to make sure, we compare the timestamp to be sure the bits in the
- // |current_frame_bitmap_| are valid.
- media::VideoFrame* last_converted_frame_;
- base::TimeDelta last_converted_timestamp_;
-
- // The natural size of the video.
- gfx::Size natural_size_;
+ base::TimeDelta bitmap_timestamp_;
DISALLOW_COPY_AND_ASSIGN(VideoRendererImpl);
};
diff --git a/webkit/media/video_renderer_impl_unittest.cc b/webkit/media/video_renderer_impl_unittest.cc
new file mode 100644
index 0000000..ba9573f
--- /dev/null
+++ b/webkit/media/video_renderer_impl_unittest.cc
@@ -0,0 +1,253 @@
+// Copyright (c) 2011 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 "base/bind.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/test_timeouts.h"
+#include "media/base/limits.h"
+#include "media/base/mock_filters.h"
+#include "media/base/mock_filter_host.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkDevice.h"
+#include "webkit/media/video_renderer_impl.h"
+
+using media::PipelineStatistics;
+using media::PipelineStatus;
+using media::VideoDecoder;
+using media::VideoFrame;
+
+using testing::_;
+using testing::NiceMock;
+using testing::Return;
+using testing::ReturnRef;
+
+namespace webkit_media {
+
+static const int64 kFrameDuration = 10;
+static const int kWidth = 320;
+static const int kHeight = 240;
+static const gfx::Size kNaturalSize(kWidth, kHeight);
+
+class VideoRendererImplTest : public testing::Test {
+ public:
+ VideoRendererImplTest()
+ : timeout_(base::TimeDelta::FromMilliseconds(
+ TestTimeouts::action_timeout_ms())),
+ cv_(&lock_),
+ prerolled_(false) {
+ // Bind callbacks with various values for TestPainting() tests.
+ natural_frame_cb_ = base::Bind(
+ &VideoRendererImplTest::DeliverFrame, base::Unretained(this),
+ kWidth, kHeight);
+ larger_frame_cb_ = base::Bind(
+ &VideoRendererImplTest::DeliverFrame, base::Unretained(this),
+ kWidth * 2, kHeight * 2);
+ smaller_frame_cb_ = base::Bind(
+ &VideoRendererImplTest::DeliverFrame, base::Unretained(this),
+ kWidth / 2, kHeight / 2);
+ fast_paint_cb_ = base::Bind(
+ &VideoRendererImplTest::Paint, base::Unretained(this), true);
+ slow_paint_cb_ = base::Bind(
+ &VideoRendererImplTest::Paint, base::Unretained(this), false);
+
+ // Forward all time requests to lock-protected getter.
+ ON_CALL(host_, GetTime())
+ .WillByDefault(Invoke(this, &VideoRendererImplTest::GetTime));
+
+ decoder_ = new media::MockVideoDecoder();
+ EXPECT_CALL(*decoder_, natural_size())
+ .WillOnce(ReturnRef(kNaturalSize));
+
+ // Initialize the renderer.
+ base::WaitableEvent event(false, false);
+ renderer_ = new VideoRendererImpl(base::Bind(
+ &VideoRendererImplTest::OnPaint, base::Unretained(this)));
+ renderer_->set_host(&host_);
+ renderer_->SetPlaybackRate(1.0f);
+ renderer_->Initialize(
+ decoder_,
+ base::Bind(&base::WaitableEvent::Signal, base::Unretained(&event)),
+ base::Bind(&VideoRendererImplTest::OnStatistics,
+ base::Unretained(this)));
+ CHECK(event.TimedWait(timeout_)) << "Timed out waiting to initialize.";
+ }
+
+ virtual ~VideoRendererImplTest() {
+ // Stop the renderer.
+ base::WaitableEvent event(false, false);
+ renderer_->Stop(
+ base::Bind(&base::WaitableEvent::Signal, base::Unretained(&event)));
+ CHECK(event.TimedWait(timeout_)) << "Timed out waiting to stop.";
+ }
+
+ typedef base::Callback<void(int64)> DeliverCB;
+ void TestPainting(const DeliverCB& deliver_first_frame,
+ const DeliverCB& deliver_remaining_frames,
+ const base::Closure& paint) {
+ StartPrerolling();
+ for (int i = 0; i < media::limits::kMaxVideoFrames; ++i) {
+ WaitForRead();
+ if (i == 0)
+ deliver_first_frame.Run(i * kFrameDuration);
+ else
+ deliver_remaining_frames.Run(i * kFrameDuration);
+ }
+
+ // Wait for prerolling to complete and paint first frame.
+ WaitForPrerolled();
+ paint.Run();
+
+ // Advance to remaining frames and paint again.
+ Play();
+ SetTime(kFrameDuration);
+ WaitForRead();
+ paint.Run();
+ }
+
+ const DeliverCB& natural_frame_cb() { return natural_frame_cb_; }
+ const DeliverCB& larger_frame_cb() { return larger_frame_cb_; }
+ const DeliverCB& smaller_frame_cb() { return smaller_frame_cb_; }
+ const base::Closure& fast_paint_cb() { return fast_paint_cb_; }
+ const base::Closure& slow_paint_cb() { return slow_paint_cb_; }
+
+ private:
+ void OnPaint() {}
+ void OnStatistics(const PipelineStatistics&) {}
+
+ void StartPrerolling() {
+ EXPECT_CALL(*decoder_, Read(_))
+ .WillRepeatedly(Invoke(this, &VideoRendererImplTest::FrameRequested));
+
+ renderer_->Seek(base::TimeDelta::FromMicroseconds(0),
+ base::Bind(&VideoRendererImplTest::PrerollDone,
+ base::Unretained(this),
+ media::PIPELINE_OK));
+ }
+
+ void Play() {
+ base::WaitableEvent event(false, false);
+ renderer_->Play(
+ base::Bind(&base::WaitableEvent::Signal, base::Unretained(&event)));
+ CHECK(event.TimedWait(timeout_)) << "Timed out waiting to play.";
+ }
+
+ void SetTime(int64 timestamp) {
+ base::AutoLock l(lock_);
+ time_ = base::TimeDelta::FromMicroseconds(timestamp);
+ }
+
+ base::TimeDelta GetTime() {
+ base::AutoLock l(lock_);
+ return time_;
+ }
+
+ void WaitForRead() {
+ base::AutoLock l(lock_);
+ if (!read_cb_.is_null())
+ return;
+ cv_.TimedWait(timeout_);
+ CHECK(!read_cb_.is_null()) << "Timed out waiting for read.";
+ }
+
+ void WaitForPrerolled() {
+ base::AutoLock l(lock_);
+ if (prerolled_)
+ return;
+ cv_.TimedWait(timeout_);
+ CHECK(prerolled_) << "Timed out waiting for prerolled.";
+ }
+
+ void FrameRequested(const VideoDecoder::ReadCB& callback) {
+ base::AutoLock l(lock_);
+ CHECK(read_cb_.is_null());
+ read_cb_ = callback;
+ cv_.Signal();
+ }
+
+ void PrerollDone(media::PipelineStatus expected_status,
+ PipelineStatus status) {
+ base::AutoLock l(lock_);
+ EXPECT_EQ(status, expected_status);
+ prerolled_ = true;
+ cv_.Signal();
+ }
+
+ // Allocates a frame of |width| and |height| dimensions with |timestamp|
+ // and delivers it to the renderer by running |read_cb_|.
+ void DeliverFrame(int width, int height, int64 timestamp) {
+ scoped_refptr<VideoFrame> video_frame =
+ VideoFrame::CreateBlackFrame(width, height);
+ video_frame->SetTimestamp(base::TimeDelta::FromMicroseconds(timestamp));
+ video_frame->SetDuration(base::TimeDelta::FromMicroseconds(kFrameDuration));
+
+ VideoDecoder::ReadCB read_cb;
+ {
+ base::AutoLock l(lock_);
+ CHECK(!read_cb_.is_null());
+ std::swap(read_cb_, read_cb);
+ }
+
+ read_cb.Run(video_frame);
+ }
+
+ // Triggers the fast painting path when |opaque| is true and the slow
+ // painting path when |opaque| is false.
+ void Paint(bool opaque) {
+ SkDevice device(SkBitmap::kARGB_8888_Config, kWidth, kHeight, opaque);
+ SkCanvas canvas(&device);
+ gfx::Rect rect(0, 0, kWidth, kHeight);
+ renderer_->Paint(&canvas, rect);
+ }
+
+ NiceMock<media::MockFilterHost> host_;
+ scoped_refptr<VideoRendererImpl> renderer_;
+ scoped_refptr<media::MockVideoDecoder> decoder_;
+
+ base::TimeDelta timeout_;
+
+ base::Lock lock_;
+ base::ConditionVariable cv_;
+
+ // Protected by |lock_|.
+ base::TimeDelta time_;
+ VideoDecoder::ReadCB read_cb_;
+ bool prerolled_;
+
+ // Prebound closures for use with TestPainting().
+ DeliverCB natural_frame_cb_;
+ DeliverCB larger_frame_cb_;
+ DeliverCB smaller_frame_cb_;
+ base::Closure slow_paint_cb_;
+ base::Closure fast_paint_cb_;
+
+ DISALLOW_COPY_AND_ASSIGN(VideoRendererImplTest);
+};
+
+TEST_F(VideoRendererImplTest, FastPaint_Natural) {
+ TestPainting(natural_frame_cb(), natural_frame_cb(), fast_paint_cb());
+}
+
+TEST_F(VideoRendererImplTest, SlowPaint_Natural) {
+ TestPainting(natural_frame_cb(), natural_frame_cb(), slow_paint_cb());
+}
+
+TEST_F(VideoRendererImplTest, FastPaint_Larger) {
+ TestPainting(natural_frame_cb(), larger_frame_cb(), fast_paint_cb());
+}
+
+TEST_F(VideoRendererImplTest, SlowPaint_Larger) {
+ TestPainting(natural_frame_cb(), larger_frame_cb(), slow_paint_cb());
+}
+
+TEST_F(VideoRendererImplTest, FastPaint_Smaller) {
+ TestPainting(natural_frame_cb(), smaller_frame_cb(), fast_paint_cb());
+}
+
+TEST_F(VideoRendererImplTest, SlowPaint_Smaller) {
+ TestPainting(natural_frame_cb(), smaller_frame_cb(), slow_paint_cb());
+}
+
+} // namespace webkit_media
diff --git a/webkit/media/webmediaplayer_impl.cc b/webkit/media/webmediaplayer_impl.cc
index 37eed33..e1d0e6e 100644
--- a/webkit/media/webmediaplayer_impl.cc
+++ b/webkit/media/webmediaplayer_impl.cc
@@ -164,7 +164,7 @@ bool WebMediaPlayerImpl::Initialize(
// Create proxy and default video renderer.
proxy_ = new WebMediaPlayerProxy(main_loop_, this);
scoped_refptr<VideoRendererImpl> video_renderer =
- new VideoRendererImpl(proxy_);
+ new VideoRendererImpl(base::Bind(&WebMediaPlayerProxy::Repaint, proxy_));
filter_collection_->AddVideoRenderer(video_renderer);
proxy_->SetVideoRenderer(video_renderer);