diff options
author | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-12-21 19:57:12 +0000 |
---|---|---|
committer | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-12-21 19:57:12 +0000 |
commit | af502b7e8ca3ba9e4cdd85348ee7d26ea29e5576 (patch) | |
tree | b7d35fc89eaec39a6597b16a0321d7f803b22549 /webkit/media | |
parent | 977eab20cbb51e8866784c204ef7ca9ea7648cb8 (diff) | |
download | chromium_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.cc | 355 | ||||
-rw-r--r-- | webkit/media/video_renderer_impl.h | 52 | ||||
-rw-r--r-- | webkit/media/video_renderer_impl_unittest.cc | 253 | ||||
-rw-r--r-- | webkit/media/webmediaplayer_impl.cc | 2 |
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); |