Index: content/browser/media/capture/web_contents_video_capture_device_unittest.cc |
diff --git a/content/browser/media/capture/web_contents_video_capture_device_unittest.cc b/content/browser/media/capture/web_contents_video_capture_device_unittest.cc |
index ab430a7625f31ecb52a1b94d90e1d34c17e63164..02b297f35e07f3eb491911698c07f49a46162fde 100644 |
--- a/content/browser/media/capture/web_contents_video_capture_device_unittest.cc |
+++ b/content/browser/media/capture/web_contents_video_capture_device_unittest.cc |
@@ -11,11 +11,13 @@ |
#include "base/time/time.h" |
#include "base/timer/timer.h" |
#include "content/browser/browser_thread_impl.h" |
+#include "content/browser/frame_host/render_frame_host_impl.h" |
#include "content/browser/media/capture/video_capture_oracle.h" |
#include "content/browser/media/capture/web_contents_capture_util.h" |
#include "content/browser/renderer_host/media/video_capture_buffer_pool.h" |
#include "content/browser/renderer_host/render_view_host_factory.h" |
#include "content/browser/renderer_host/render_widget_host_impl.h" |
+#include "content/browser/web_contents/web_contents_impl.h" |
#include "content/public/browser/notification_service.h" |
#include "content/public/browser/notification_types.h" |
#include "content/public/browser/render_widget_host_view_frame_subscriber.h" |
@@ -77,7 +79,6 @@ SkColor ConvertRgbToYuv(SkColor rgb) { |
// counted. |
class CaptureTestSourceController { |
public: |
- |
CaptureTestSourceController() |
: color_(SK_ColorMAGENTA), |
copy_result_size_(kTestWidth, kTestHeight), |
@@ -162,13 +163,22 @@ class CaptureTestView : public TestRenderWidgetHostView { |
explicit CaptureTestView(RenderWidgetHostImpl* rwh, |
CaptureTestSourceController* controller) |
: TestRenderWidgetHostView(rwh), |
- controller_(controller) {} |
+ controller_(controller), |
+ fake_bounds_(100, 100, 100 + kTestWidth, 100 + kTestHeight) {} |
~CaptureTestView() override {} |
// TestRenderWidgetHostView overrides. |
gfx::Rect GetViewBounds() const override { |
- return gfx::Rect(100, 100, 100 + kTestWidth, 100 + kTestHeight); |
+ return fake_bounds_; |
+ } |
+ |
+ void SetSize(const gfx::Size& size) override { |
+ SetBounds(gfx::Rect(fake_bounds_.origin(), size)); |
+ } |
+ |
+ void SetBounds(const gfx::Rect& rect) override { |
+ fake_bounds_ = rect; |
} |
bool CanCopyToVideoFrame() const override { |
@@ -213,6 +223,7 @@ class CaptureTestView : public TestRenderWidgetHostView { |
private: |
scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber_; |
CaptureTestSourceController* const controller_; |
+ gfx::Rect fake_bounds_; |
DISALLOW_IMPLICIT_CONSTRUCTORS(CaptureTestView); |
}; |
@@ -308,9 +319,10 @@ class CaptureTestRenderViewHostFactory : public RenderViewHostFactory { |
// WebContentsVideoCaptureDevice. |
class StubClient : public media::VideoCaptureDevice::Client { |
public: |
- StubClient(const base::Callback<void(SkColor)>& color_callback, |
- const base::Closure& error_callback) |
- : color_callback_(color_callback), |
+ StubClient( |
+ const base::Callback<void(SkColor, const gfx::Size&)>& report_callback, |
+ const base::Closure& error_callback) |
+ : report_callback_(report_callback), |
error_callback_(error_callback) { |
buffer_pool_ = new VideoCaptureBufferPool(2); |
} |
@@ -360,20 +372,29 @@ class StubClient : public media::VideoCaptureDevice::Client { |
scoped_ptr<Buffer> buffer, |
const scoped_refptr<media::VideoFrame>& frame, |
const base::TimeTicks& timestamp) override { |
- EXPECT_EQ(gfx::Size(kTestWidth, kTestHeight), frame->visible_rect().size()); |
+ EXPECT_FALSE(frame->visible_rect().IsEmpty()); |
EXPECT_EQ(media::VideoFrame::I420, frame->format()); |
double frame_rate = 0; |
EXPECT_TRUE( |
frame->metadata()->GetDouble(media::VideoFrameMetadata::FRAME_RATE, |
&frame_rate)); |
EXPECT_EQ(kTestFramesPerSecond, frame_rate); |
- uint8 yuv[3]; |
- for (int plane = 0; plane < 3; ++plane) |
- yuv[plane] = frame->visible_data(plane)[0]; |
- // TODO(nick): We just look at the first pixel presently, because if |
- // the analysis is too slow, the backlog of frames will grow without bound |
- // and trouble erupts. http://crbug.com/174519 |
- color_callback_.Run((SkColorSetRGB(yuv[0], yuv[1], yuv[2]))); |
+ |
+ // TODO(miu): We just look at the center pixel presently, because if the |
+ // analysis is too slow, the backlog of frames will grow without bound and |
+ // trouble erupts. http://crbug.com/174519 |
+ using media::VideoFrame; |
+ const gfx::Point center = frame->visible_rect().CenterPoint(); |
+ const int center_offset_y = |
+ (frame->stride(VideoFrame::kYPlane) * center.y()) + center.x(); |
+ const int center_offset_uv = |
+ (frame->stride(VideoFrame::kUPlane) * (center.y() / 2)) + |
+ (center.x() / 2); |
+ report_callback_.Run( |
+ SkColorSetRGB(frame->data(VideoFrame::kYPlane)[center_offset_y], |
+ frame->data(VideoFrame::kUPlane)[center_offset_uv], |
+ frame->data(VideoFrame::kVPlane)[center_offset_uv]), |
+ frame->visible_rect().size()); |
} |
void OnError(const std::string& reason) override { error_callback_.Run(); } |
@@ -407,7 +428,7 @@ class StubClient : public media::VideoCaptureDevice::Client { |
}; |
scoped_refptr<VideoCaptureBufferPool> buffer_pool_; |
- base::Callback<void(SkColor)> color_callback_; |
+ base::Callback<void(SkColor, const gfx::Size&)> report_callback_; |
base::Closure error_callback_; |
DISALLOW_COPY_AND_ASSIGN(StubClient); |
@@ -417,9 +438,11 @@ class StubClientObserver { |
public: |
StubClientObserver() |
: error_encountered_(false), |
- wait_color_yuv_(0xcafe1950) { |
+ wait_color_yuv_(0xcafe1950), |
+ wait_size_(kTestWidth, kTestHeight) { |
client_.reset(new StubClient( |
- base::Bind(&StubClientObserver::OnColor, base::Unretained(this)), |
+ base::Bind(&StubClientObserver::DidDeliverFrame, |
+ base::Unretained(this)), |
base::Bind(&StubClientObserver::OnError, base::Unretained(this)))); |
} |
@@ -429,16 +452,30 @@ class StubClientObserver { |
return client_.Pass(); |
} |
- void QuitIfConditionMet(SkColor color) { |
+ void QuitIfConditionsMet(SkColor color, const gfx::Size& size) { |
base::AutoLock guard(lock_); |
- if (wait_color_yuv_ == color || error_encountered_) |
+ if (error_encountered_) |
+ base::MessageLoop::current()->Quit(); |
+ else if (wait_color_yuv_ == color && wait_size_.IsEmpty()) |
+ base::MessageLoop::current()->Quit(); |
+ else if (wait_color_yuv_ == color && wait_size_ == size) |
base::MessageLoop::current()->Quit(); |
} |
+ // Run the current loop until a frame is delivered with the |expected_color| |
+ // and any non-empty frame size. |
void WaitForNextColor(SkColor expected_color) { |
+ WaitForNextColorAndFrameSize(expected_color, gfx::Size()); |
+ } |
+ |
+ // Run the current loop until a frame is delivered with the |expected_color| |
+ // and is of the |expected_size|. |
+ void WaitForNextColorAndFrameSize(SkColor expected_color, |
+ const gfx::Size& expected_size) { |
{ |
base::AutoLock guard(lock_); |
wait_color_yuv_ = ConvertRgbToYuv(expected_color); |
+ wait_size_ = expected_size; |
error_encountered_ = false; |
} |
RunCurrentLoopWithDeadline(); |
@@ -452,6 +489,7 @@ class StubClientObserver { |
{ |
base::AutoLock guard(lock_); |
wait_color_yuv_ = kNotInterested; |
+ wait_size_ = gfx::Size(); |
error_encountered_ = false; |
} |
RunCurrentLoopWithDeadline(); |
@@ -472,22 +510,25 @@ class StubClientObserver { |
error_encountered_ = true; |
} |
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( |
- &StubClientObserver::QuitIfConditionMet, |
+ &StubClientObserver::QuitIfConditionsMet, |
base::Unretained(this), |
- kNothingYet)); |
+ kNothingYet, |
+ gfx::Size())); |
} |
- void OnColor(SkColor color) { |
+ void DidDeliverFrame(SkColor color, const gfx::Size& size) { |
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( |
- &StubClientObserver::QuitIfConditionMet, |
+ &StubClientObserver::QuitIfConditionsMet, |
base::Unretained(this), |
- color)); |
+ color, |
+ size)); |
} |
private: |
base::Lock lock_; |
bool error_encountered_; |
SkColor wait_color_yuv_; |
+ gfx::Size wait_size_; |
scoped_ptr<StubClient> client_; |
DISALLOW_COPY_AND_ASSIGN(StubClientObserver); |
@@ -622,6 +663,22 @@ class WebContentsVideoCaptureDeviceTest : public testing::Test { |
} |
} |
+ void SimulateSourceSizeChange(const gfx::Size& size) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ CaptureTestView* test_view = static_cast<CaptureTestView*>( |
+ web_contents_->GetRenderViewHost()->GetView()); |
+ test_view->SetSize(size); |
+ // Normally, RenderWidgetHostImpl would notify WebContentsImpl that the size |
+ // has changed. However, in this test setup where there is no render |
+ // process, we must notify WebContentsImpl directly. |
+ WebContentsImpl* const as_web_contents_impl = |
+ static_cast<WebContentsImpl*>(web_contents_.get()); |
+ RenderWidgetHostDelegate* const as_rwh_delegate = |
+ static_cast<RenderWidgetHostDelegate*>(as_web_contents_impl); |
+ as_rwh_delegate->RenderWidgetWasResized( |
+ as_web_contents_impl->GetMainFrame()->GetRenderWidgetHost(), true); |
+ } |
+ |
void DestroyVideoCaptureDevice() { device_.reset(); } |
StubClientObserver* client_observer() { |
@@ -879,5 +936,158 @@ TEST_F(WebContentsVideoCaptureDeviceTest, BadFramesGoodFrames) { |
device()->StopAndDeAllocate(); |
} |
+// Tests that, when configured with the FIXED_ASPECT_RATIO resolution change |
+// policy, the source size changes result in video frames of possibly varying |
+// resolutions, but all with the same aspect ratio. |
+TEST_F(WebContentsVideoCaptureDeviceTest, VariableResolution_FixedAspectRatio) { |
+ media::VideoCaptureParams capture_params; |
+ capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight); |
+ capture_params.requested_format.frame_rate = kTestFramesPerSecond; |
+ capture_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420; |
+ capture_params.resolution_change_policy = |
+ media::RESOLUTION_POLICY_FIXED_ASPECT_RATIO; |
+ |
+ device()->AllocateAndStart(capture_params, client_observer()->PassClient()); |
+ |
+ for (int i = 0; i < 6; i++) { |
+ const char* name = NULL; |
+ switch (i % 3) { |
+ case 0: |
+ source()->SetCanCopyToVideoFrame(true); |
+ source()->SetUseFrameSubscriber(false); |
+ name = "VideoFrame"; |
+ break; |
+ case 1: |
+ source()->SetCanCopyToVideoFrame(false); |
+ source()->SetUseFrameSubscriber(true); |
+ name = "Subscriber"; |
+ break; |
+ case 2: |
+ source()->SetCanCopyToVideoFrame(false); |
+ source()->SetUseFrameSubscriber(false); |
+ name = "SkBitmap"; |
+ break; |
+ default: |
+ FAIL(); |
+ } |
+ |
+ SCOPED_TRACE(base::StringPrintf("Using %s path, iteration #%d", name, i)); |
+ |
+ // Source size equals maximum size. Expect delivered frames to be |
+ // kTestWidth by kTestHeight. |
+ source()->SetSolidColor(SK_ColorRED); |
+ SimulateSourceSizeChange(gfx::Size(kTestWidth, kTestHeight)); |
+ SimulateDrawEvent(); |
+ ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize( |
+ SK_ColorRED, gfx::Size(kTestWidth, kTestHeight))); |
+ |
+ // Source size is half in both dimensions. Expect delivered frames to be of |
+ // the same aspect ratio as kTestWidth by kTestHeight, but larger than the |
+ // half size because the minimum height is 180 lines. |
+ source()->SetSolidColor(SK_ColorGREEN); |
+ SimulateSourceSizeChange(gfx::Size(kTestWidth / 2, kTestHeight / 2)); |
+ SimulateDrawEvent(); |
+ ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize( |
+ SK_ColorGREEN, gfx::Size(180 * kTestWidth / kTestHeight, 180))); |
+ |
+ // Source size changes aspect ratio. Expect delivered frames to be padded |
+ // in the horizontal dimension to preserve aspect ratio. |
+ source()->SetSolidColor(SK_ColorBLUE); |
+ SimulateSourceSizeChange(gfx::Size(kTestWidth / 2, kTestHeight)); |
+ SimulateDrawEvent(); |
+ ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize( |
+ SK_ColorBLUE, gfx::Size(kTestWidth, kTestHeight))); |
+ |
+ // Source size changes aspect ratio again. Expect delivered frames to be |
+ // padded in the vertical dimension to preserve aspect ratio. |
+ source()->SetSolidColor(SK_ColorBLACK); |
+ SimulateSourceSizeChange(gfx::Size(kTestWidth, kTestHeight / 2)); |
+ SimulateDrawEvent(); |
+ ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize( |
+ SK_ColorBLACK, gfx::Size(kTestWidth, kTestHeight))); |
+ } |
+ |
+ device()->StopAndDeAllocate(); |
+} |
+ |
+// Tests that, when configured with the ANY_WITHIN_LIMIT resolution change |
+// policy, the source size changes result in video frames of possibly varying |
+// resolutions. |
+TEST_F(WebContentsVideoCaptureDeviceTest, VariableResolution_AnyWithinLimits) { |
+ media::VideoCaptureParams capture_params; |
+ capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight); |
+ capture_params.requested_format.frame_rate = kTestFramesPerSecond; |
+ capture_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420; |
+ capture_params.resolution_change_policy = |
+ media::RESOLUTION_POLICY_ANY_WITHIN_LIMIT; |
+ |
+ device()->AllocateAndStart(capture_params, client_observer()->PassClient()); |
+ |
+ for (int i = 0; i < 6; i++) { |
+ const char* name = NULL; |
+ switch (i % 3) { |
+ case 0: |
+ source()->SetCanCopyToVideoFrame(true); |
+ source()->SetUseFrameSubscriber(false); |
+ name = "VideoFrame"; |
+ break; |
+ case 1: |
+ source()->SetCanCopyToVideoFrame(false); |
+ source()->SetUseFrameSubscriber(true); |
+ name = "Subscriber"; |
+ break; |
+ case 2: |
+ source()->SetCanCopyToVideoFrame(false); |
+ source()->SetUseFrameSubscriber(false); |
+ name = "SkBitmap"; |
+ break; |
+ default: |
+ FAIL(); |
+ } |
+ |
+ SCOPED_TRACE(base::StringPrintf("Using %s path, iteration #%d", name, i)); |
+ |
+ // Source size equals maximum size. Expect delivered frames to be |
+ // kTestWidth by kTestHeight. |
+ source()->SetSolidColor(SK_ColorRED); |
+ SimulateSourceSizeChange(gfx::Size(kTestWidth, kTestHeight)); |
+ SimulateDrawEvent(); |
+ ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize( |
+ SK_ColorRED, gfx::Size(kTestWidth, kTestHeight))); |
+ |
+ // Source size is half in both dimensions. Expect delivered frames to also |
+ // be half in both dimensions. |
+ source()->SetSolidColor(SK_ColorGREEN); |
+ SimulateSourceSizeChange(gfx::Size(kTestWidth / 2, kTestHeight / 2)); |
+ SimulateDrawEvent(); |
+ ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize( |
+ SK_ColorGREEN, gfx::Size(kTestWidth / 2, kTestHeight / 2))); |
+ |
+ // Source size changes to something arbitrary. Since the source size is |
+ // less than the maximum size, expect delivered frames to be the same size |
+ // as the source size. |
+ source()->SetSolidColor(SK_ColorBLUE); |
+ gfx::Size arbitrary_source_size(kTestWidth / 2 + 42, kTestHeight - 10); |
+ SimulateSourceSizeChange(arbitrary_source_size); |
+ SimulateDrawEvent(); |
+ ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize( |
+ SK_ColorBLUE, arbitrary_source_size)); |
+ |
+ // Source size changes to something arbitrary that exceeds the maximum frame |
+ // size. Since the source size exceeds the maximum size, expect delivered |
+ // frames to be downscaled. |
+ source()->SetSolidColor(SK_ColorBLACK); |
+ arbitrary_source_size = gfx::Size(kTestWidth * 2 + 99, kTestHeight / 2); |
+ SimulateSourceSizeChange(arbitrary_source_size); |
+ SimulateDrawEvent(); |
+ ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize( |
+ SK_ColorBLACK, gfx::Size(kTestWidth, |
+ kTestWidth * arbitrary_source_size.height() / |
+ arbitrary_source_size.width()))); |
+ } |
+ |
+ device()->StopAndDeAllocate(); |
+} |
+ |
} // namespace |
} // namespace content |