OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "content/browser/renderer_host/media/web_contents_video_capture_device.
h" | 5 #include "content/browser/renderer_host/media/web_contents_video_capture_device.
h" |
6 | 6 |
7 #include "base/bind_helpers.h" | 7 #include "base/bind_helpers.h" |
8 #include "base/synchronization/condition_variable.h" | 8 #include "base/debug/debugger.h" |
9 #include "base/synchronization/waitable_event.h" | 9 #include "base/run_loop.h" |
10 #include "base/time.h" | 10 #include "base/time.h" |
| 11 #include "base/timer.h" |
11 #include "content/browser/browser_thread_impl.h" | 12 #include "content/browser/browser_thread_impl.h" |
12 #include "content/browser/renderer_host/render_widget_host_delegate.h" | 13 #include "content/browser/renderer_host/media/web_contents_capture_util.h" |
| 14 #include "content/browser/renderer_host/render_view_host_factory.h" |
13 #include "content/browser/renderer_host/render_widget_host_impl.h" | 15 #include "content/browser/renderer_host/render_widget_host_impl.h" |
| 16 #include "content/browser/renderer_host/test_render_view_host.h" |
14 #include "content/public/test/mock_render_process_host.h" | 17 #include "content/public/test/mock_render_process_host.h" |
15 #include "content/public/test/test_browser_context.h" | 18 #include "content/public/test/test_browser_context.h" |
| 19 #include "content/public/test/test_browser_thread.h" |
| 20 #include "content/public/test/test_utils.h" |
| 21 #include "content/test/test_web_contents.h" |
16 #include "media/base/video_util.h" | 22 #include "media/base/video_util.h" |
17 #include "media/video/capture/video_capture_types.h" | 23 #include "media/video/capture/video_capture_types.h" |
18 #include "skia/ext/platform_canvas.h" | 24 #include "skia/ext/platform_canvas.h" |
19 #include "testing/gtest/include/gtest/gtest.h" | 25 #include "testing/gtest/include/gtest/gtest.h" |
20 #include "third_party/skia/include/core/SkColor.h" | 26 #include "third_party/skia/include/core/SkColor.h" |
21 | 27 |
22 namespace content { | 28 namespace content { |
23 namespace { | 29 namespace { |
24 const int kTestWidth = 1280; | 30 const int kTestWidth = 1280; |
25 const int kTestHeight = 720; | 31 const int kTestHeight = 720; |
26 const int kBytesPerPixel = 4; | 32 const int kBytesPerPixel = 4; |
27 const int kTestFramesPerSecond = 8; | 33 const int kTestFramesPerSecond = 20; |
28 const base::TimeDelta kWaitTimeout = | 34 const base::TimeDelta kWaitTimeout = base::TimeDelta::FromMilliseconds(2000); |
29 base::TimeDelta::FromMilliseconds(2000); | |
30 const SkColor kNothingYet = 0xdeadbeef; | 35 const SkColor kNothingYet = 0xdeadbeef; |
31 const SkColor kNotInterested = ~kNothingYet; | 36 const SkColor kNotInterested = ~kNothingYet; |
| 37 |
| 38 void DeadlineExceeded(base::Closure quit_closure) { |
| 39 if (!base::debug::BeingDebugged()) { |
| 40 FAIL() << "Deadline exceeded while waiting, quitting"; |
| 41 quit_closure.Run(); |
| 42 } else { |
| 43 LOG(WARNING) << "Deadline exceeded; test would fail if debugger weren't " |
| 44 << "attached."; |
| 45 } |
32 } | 46 } |
33 | 47 |
| 48 void RunCurrentLoopWithDeadline() { |
| 49 base::Timer deadline(false, false); |
| 50 deadline.Start(FROM_HERE, kWaitTimeout, base::Bind( |
| 51 &DeadlineExceeded, MessageLoop::current()->QuitClosure())); |
| 52 MessageLoop::current()->Run(); |
| 53 deadline.Stop(); |
| 54 } |
| 55 |
| 56 // Thread-safe class that controls the source pattern to be captured by the |
| 57 // system under test. The lifetime of this class is greater than the lifetime |
| 58 // of all objects that reference it, so it does not need to be reference |
| 59 // counted. |
| 60 class CaptureTestSourceController { |
| 61 public: |
| 62 CaptureTestSourceController() |
| 63 : color_(SK_ColorMAGENTA), |
| 64 copy_result_size_(kTestWidth, kTestHeight), |
| 65 can_copy_to_video_frame_(false) {} |
| 66 |
| 67 void SetSolidColor(SkColor color) { |
| 68 base::AutoLock guard(lock_); |
| 69 color_ = color; |
| 70 } |
| 71 |
| 72 SkColor GetSolidColor() { |
| 73 base::AutoLock guard(lock_); |
| 74 return color_; |
| 75 } |
| 76 |
| 77 void SetCopyResultSize(int width, int height) { |
| 78 base::AutoLock guard(lock_); |
| 79 copy_result_size_ = gfx::Size(width, height); |
| 80 } |
| 81 |
| 82 gfx::Size GetCopyResultSize() { |
| 83 base::AutoLock guard(lock_); |
| 84 return copy_result_size_; |
| 85 } |
| 86 |
| 87 void SignalBackingStoreCopy() { |
| 88 // TODO(nick): This actually should always be happening on the UI thread. |
| 89 base::AutoLock guard(lock_); |
| 90 if (!copy_done_.is_null()) { |
| 91 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, copy_done_); |
| 92 copy_done_.Reset(); |
| 93 } |
| 94 } |
| 95 |
| 96 void SetCanCopyToVideoFrame(bool value) { |
| 97 base::AutoLock guard(lock_); |
| 98 can_copy_to_video_frame_ = value; |
| 99 } |
| 100 |
| 101 bool CanCopyToVideoFrame() { |
| 102 base::AutoLock guard(lock_); |
| 103 return can_copy_to_video_frame_; |
| 104 } |
| 105 |
| 106 void WaitForNextBackingStoreCopy() { |
| 107 { |
| 108 base::AutoLock guard(lock_); |
| 109 copy_done_ = MessageLoop::current()->QuitClosure(); |
| 110 } |
| 111 RunCurrentLoopWithDeadline(); |
| 112 } |
| 113 |
| 114 void OnShutdown() { |
| 115 base::AutoLock guard(lock_); |
| 116 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, shutdown_hook_); |
| 117 } |
| 118 |
| 119 void SetShutdownHook(base::Closure shutdown_hook) { |
| 120 base::AutoLock guard(lock_); |
| 121 shutdown_hook_ = shutdown_hook; |
| 122 } |
| 123 |
| 124 private: |
| 125 base::Lock lock_; // Guards changes to all members. |
| 126 SkColor color_; |
| 127 gfx::Size copy_result_size_; |
| 128 bool can_copy_to_video_frame_; |
| 129 base::Closure copy_done_; |
| 130 base::Closure shutdown_hook_; |
| 131 |
| 132 DISALLOW_COPY_AND_ASSIGN(CaptureTestSourceController); |
| 133 }; |
| 134 |
34 // A stub implementation which returns solid-color bitmaps in calls to | 135 // A stub implementation which returns solid-color bitmaps in calls to |
35 // CopyFromBackingStore(). The unit tests can change the color for successive | 136 // CopyFromCompositingSurfaceToVideoFrame(), and which allows the video-frame |
36 // captures. | 137 // readback path to be switched on and off. The behavior is controlled by a |
37 class StubRenderWidgetHost : public RenderWidgetHostImpl { | 138 // CaptureTestSourceController. |
38 public: | 139 class CaptureTestView : public TestRenderWidgetHostView { |
39 StubRenderWidgetHost(RenderProcessHost* process, int routing_id) | 140 public: |
40 : RenderWidgetHostImpl(&delegate_, process, routing_id), | 141 explicit CaptureTestView(RenderWidgetHostImpl* rwh, |
41 color_(kNothingYet), | 142 CaptureTestSourceController* controller) |
42 copy_result_size_(kTestWidth, kTestHeight), | 143 : TestRenderWidgetHostView(rwh), |
43 copy_event_(false, false) {} | 144 controller_(controller) {} |
44 | 145 virtual ~CaptureTestView() {} |
45 void SetSolidColor(SkColor color) { | 146 |
46 base::AutoLock guard(lock_); | 147 // TestRenderWidgetHostView overrides. |
47 color_ = color; | 148 virtual gfx::Rect GetViewBounds() const OVERRIDE { |
48 } | 149 return gfx::Rect(100, 100, 100 + kTestWidth, 100 + kTestHeight); |
49 | 150 } |
50 void SetCopyResultSize(int width, int height) { | 151 |
51 base::AutoLock guard(lock_); | 152 virtual bool CanCopyToVideoFrame() const OVERRIDE { |
52 copy_result_size_ = gfx::Size(width, height); | 153 return controller_->CanCopyToVideoFrame(); |
53 } | 154 } |
54 | 155 |
55 bool WaitForNextBackingStoreCopy() { | 156 virtual void CopyFromCompositingSurfaceToVideoFrame( |
56 if (!copy_event_.TimedWait(kWaitTimeout)) { | 157 const gfx::Rect& src_subrect, |
57 ADD_FAILURE() << "WaitForNextBackingStoreCopy: wait deadline exceeded"; | 158 const scoped_refptr<media::VideoFrame>& target, |
58 return false; | 159 const base::Callback<void(bool)>& callback) OVERRIDE { |
59 } | 160 SkColor c = controller_->GetSolidColor(); |
60 return true; | 161 media::FillYUV(target, SkColorGetR(c), SkColorGetG(c), SkColorGetB(c)); |
61 } | 162 callback.Run(true); |
62 | 163 controller_->SignalBackingStoreCopy(); |
63 // RenderWidgetHostImpl overrides. | 164 } |
| 165 |
| 166 private: |
| 167 CaptureTestSourceController* const controller_; |
| 168 |
| 169 DISALLOW_IMPLICIT_CONSTRUCTORS(CaptureTestView); |
| 170 }; |
| 171 |
| 172 #if defined(COMPILER_MSVC) |
| 173 // MSVC warns on diamond inheritance. See comment for same warning on |
| 174 // RenderViewHostImpl. |
| 175 #pragma warning(push) |
| 176 #pragma warning(disable: 4250) |
| 177 #endif |
| 178 |
| 179 // A stub implementation which returns solid-color bitmaps in calls to |
| 180 // CopyFromBackingStore(). The behavior is controlled by a |
| 181 // CaptureTestSourceController. |
| 182 class CaptureTestRenderViewHost : public TestRenderViewHost { |
| 183 public: |
| 184 CaptureTestRenderViewHost(SiteInstance* instance, |
| 185 RenderViewHostDelegate* delegate, |
| 186 RenderWidgetHostDelegate* widget_delegate, |
| 187 int routing_id, |
| 188 bool swapped_out, |
| 189 CaptureTestSourceController* controller) |
| 190 : TestRenderViewHost(instance, delegate, widget_delegate, routing_id, |
| 191 swapped_out), |
| 192 controller_(controller) { |
| 193 // Override the default view installed by TestRenderViewHost; we need |
| 194 // our special subclass which has mocked-out tab capture support. |
| 195 RenderWidgetHostView* old_view = GetView(); |
| 196 SetView(new CaptureTestView(this, controller)); |
| 197 delete old_view; |
| 198 } |
| 199 |
| 200 // TestRenderViewHost overrides. |
64 virtual void CopyFromBackingStore( | 201 virtual void CopyFromBackingStore( |
65 const gfx::Rect& src_rect, | 202 const gfx::Rect& src_rect, |
66 const gfx::Size& accelerated_dst_size, | 203 const gfx::Size& accelerated_dst_size, |
67 const base::Callback<void(bool, const SkBitmap&)>& callback) OVERRIDE { | 204 const base::Callback<void(bool, const SkBitmap&)>& callback) OVERRIDE { |
| 205 gfx::Size size = controller_->GetCopyResultSize(); |
| 206 SkColor color = controller_->GetSolidColor(); |
| 207 |
68 // Although it's not necessary, use a PlatformBitmap here (instead of a | 208 // Although it's not necessary, use a PlatformBitmap here (instead of a |
69 // regular SkBitmap) to exercise possible threading issues. | 209 // regular SkBitmap) to exercise possible threading issues. |
70 scoped_ptr<skia::PlatformBitmap> platform_bitmap(new skia::PlatformBitmap); | 210 skia::PlatformBitmap output; |
71 EXPECT_TRUE(platform_bitmap->Allocate( | 211 EXPECT_TRUE(output.Allocate(size.width(), size.height(), false)); |
72 copy_result_size_.width(), copy_result_size_.height(), false)); | 212 { |
73 { | 213 SkAutoLockPixels locker(output.GetBitmap()); |
74 SkAutoLockPixels locker(platform_bitmap->GetBitmap()); | 214 output.GetBitmap().eraseColor(color); |
75 base::AutoLock guard(lock_); | 215 } |
76 platform_bitmap->GetBitmap().eraseColor(color_); | 216 callback.Run(true, output.GetBitmap()); |
77 } | 217 controller_->SignalBackingStoreCopy(); |
78 | 218 } |
79 callback.Run(true, platform_bitmap->GetBitmap()); | 219 |
80 copy_event_.Signal(); | 220 private: |
81 } | 221 CaptureTestSourceController* controller_; |
82 | 222 |
83 private: | 223 DISALLOW_IMPLICIT_CONSTRUCTORS(CaptureTestRenderViewHost); |
84 class StubRenderWidgetHostDelegate : public RenderWidgetHostDelegate { | 224 }; |
85 public: | 225 |
86 StubRenderWidgetHostDelegate() {} | 226 #if defined(COMPILER_MSVC) |
87 virtual ~StubRenderWidgetHostDelegate() {} | 227 // Re-enable warning 4250 |
88 | 228 #pragma warning(pop) |
89 private: | 229 #endif |
90 DISALLOW_COPY_AND_ASSIGN(StubRenderWidgetHostDelegate); | 230 |
91 }; | 231 class CaptureTestRenderViewHostFactory : public RenderViewHostFactory { |
92 | 232 public: |
93 StubRenderWidgetHostDelegate delegate_; | 233 explicit CaptureTestRenderViewHostFactory( |
94 base::Lock lock_; // Guards changes to color_. | 234 CaptureTestSourceController* controller) : controller_(controller) { |
95 SkColor color_; | 235 RegisterFactory(this); |
96 gfx::Size copy_result_size_; | 236 } |
97 base::WaitableEvent copy_event_; | 237 |
98 | 238 virtual ~CaptureTestRenderViewHostFactory() { |
99 DISALLOW_IMPLICIT_CONSTRUCTORS(StubRenderWidgetHost); | 239 UnregisterFactory(); |
| 240 } |
| 241 |
| 242 // RenderViewHostFactory implementation. |
| 243 virtual RenderViewHost* CreateRenderViewHost( |
| 244 SiteInstance* instance, |
| 245 RenderViewHostDelegate* delegate, |
| 246 RenderWidgetHostDelegate* widget_delegate, |
| 247 int routing_id, |
| 248 bool swapped_out, |
| 249 SessionStorageNamespace* session_storage_namespace) { |
| 250 return new CaptureTestRenderViewHost(instance, delegate, widget_delegate, |
| 251 routing_id, swapped_out, controller_); |
| 252 } |
| 253 private: |
| 254 CaptureTestSourceController* controller_; |
| 255 |
| 256 DISALLOW_IMPLICIT_CONSTRUCTORS(CaptureTestRenderViewHostFactory); |
100 }; | 257 }; |
101 | 258 |
102 // A stub consumer of captured video frames, which checks the output of | 259 // A stub consumer of captured video frames, which checks the output of |
103 // WebContentsVideoCaptureDevice. | 260 // WebContentsVideoCaptureDevice. |
104 class StubConsumer : public media::VideoCaptureDevice::EventHandler { | 261 class StubConsumer : public media::VideoCaptureDevice::EventHandler { |
105 public: | 262 public: |
106 StubConsumer() : output_changed_(&lock_), | 263 StubConsumer() : error_encountered_(false), wait_color_(0xcafe1950) {} |
107 picture_color_(kNothingYet), | |
108 error_encountered_(false) {} | |
109 virtual ~StubConsumer() {} | 264 virtual ~StubConsumer() {} |
110 | 265 |
111 // Returns false if an error was encountered. | 266 void QuitIfConditionMet(SkColor color) { |
112 bool WaitForNextColorOrError(SkColor expected_color) { | 267 base::AutoLock guard(lock_); |
113 base::TimeTicks deadline = base::TimeTicks::Now() + kWaitTimeout; | 268 |
114 base::AutoLock guard(lock_); | 269 if (wait_color_ == color || error_encountered_) |
115 while (picture_color_ != expected_color && !error_encountered_) { | 270 MessageLoop::current()->Quit(); |
116 output_changed_.TimedWait(kWaitTimeout); | 271 } |
117 if (base::TimeTicks::Now() >= deadline) { | 272 |
118 ADD_FAILURE() << "WaitForNextColorOrError: wait deadline exceeded"; | 273 void WaitForNextColor(SkColor expected_color) { |
119 return false; | 274 { |
120 } | 275 base::AutoLock guard(lock_); |
121 } | 276 wait_color_ = expected_color; |
122 if (!error_encountered_) { | 277 error_encountered_ = false; |
123 EXPECT_EQ(expected_color, picture_color_); | 278 } |
124 return true; | 279 RunCurrentLoopWithDeadline(); |
125 } else { | 280 { |
126 return false; | 281 base::AutoLock guard(lock_); |
127 } | 282 ASSERT_FALSE(error_encountered_); |
128 } | 283 } |
129 | 284 } |
| 285 |
| 286 void WaitForError() { |
| 287 { |
| 288 base::AutoLock guard(lock_); |
| 289 wait_color_ = kNotInterested; |
| 290 error_encountered_ = false; |
| 291 } |
| 292 RunCurrentLoopWithDeadline(); |
| 293 { |
| 294 base::AutoLock guard(lock_); |
| 295 ASSERT_TRUE(error_encountered_); |
| 296 } |
| 297 } |
| 298 |
130 virtual void OnIncomingCapturedFrame(const uint8* data, int length, | 299 virtual void OnIncomingCapturedFrame(const uint8* data, int length, |
131 base::Time timestamp) OVERRIDE { | 300 base::Time timestamp) OVERRIDE { |
132 DCHECK(data); | 301 DCHECK(data); |
133 static const int kNumPixels = kTestWidth * kTestHeight; | 302 static const int kNumPixels = kTestWidth * kTestHeight; |
134 EXPECT_EQ(kNumPixels * kBytesPerPixel, length); | 303 EXPECT_EQ(kNumPixels * kBytesPerPixel, length); |
135 const uint32* p = reinterpret_cast<const uint32*>(data); | 304 const uint32* p = reinterpret_cast<const uint32*>(data); |
136 const uint32* const p_end = p + kNumPixels; | 305 const uint32* const p_end = p + kNumPixels; |
137 const SkColor color = *p; | 306 const SkColor color = *p; |
138 bool all_pixels_are_the_same_color = true; | 307 bool all_pixels_are_the_same_color = true; |
139 for (++p; p < p_end; ++p) { | 308 for (++p; p < p_end; ++p) { |
140 if (*p != color) { | 309 if (*p != color) { |
141 all_pixels_are_the_same_color = false; | 310 all_pixels_are_the_same_color = false; |
142 break; | 311 break; |
143 } | 312 } |
144 } | 313 } |
145 EXPECT_TRUE(all_pixels_are_the_same_color); | 314 EXPECT_TRUE(all_pixels_are_the_same_color); |
146 | 315 PostColorOrError(color); |
147 { | |
148 base::AutoLock guard(lock_); | |
149 if (color != picture_color_) { | |
150 picture_color_ = color; | |
151 output_changed_.Signal(); | |
152 } | |
153 } | |
154 } | 316 } |
155 | 317 |
156 virtual void OnIncomingCapturedVideoFrame(media::VideoFrame* frame, | 318 virtual void OnIncomingCapturedVideoFrame(media::VideoFrame* frame, |
157 base::Time timestamp) OVERRIDE { | 319 base::Time timestamp) OVERRIDE { |
158 EXPECT_EQ(gfx::Size(kTestWidth, kTestHeight), frame->coded_size()); | 320 EXPECT_EQ(gfx::Size(kTestWidth, kTestHeight), frame->coded_size()); |
159 EXPECT_EQ(media::VideoFrame::YV12, frame->format()); | 321 EXPECT_EQ(media::VideoFrame::YV12, frame->format()); |
160 bool all_pixels_are_the_same_color = true; | |
161 uint8 yuv[3] = {0}; | 322 uint8 yuv[3] = {0}; |
162 for (int plane = 0; plane < 3; ++plane) { | 323 for (int plane = 0; plane < 3; ++plane) { |
163 yuv[plane] = frame->data(plane)[0]; | 324 yuv[plane] = frame->data(plane)[0]; |
164 for (int y = 0; y < frame->rows(plane); ++y) { | |
165 for (int x = 0; x < frame->row_bytes(plane); ++x) { | |
166 if (yuv[plane] != frame->data(plane)[x + y * frame->stride(plane)]) { | |
167 all_pixels_are_the_same_color = false; | |
168 break; | |
169 } | |
170 } | |
171 } | |
172 } | 325 } |
173 EXPECT_TRUE(all_pixels_are_the_same_color); | 326 // TODO(nick): We just look at the first pixel presently, because if |
174 const SkColor color = SkColorSetRGB(yuv[0], yuv[1], yuv[2]); | 327 // the analysis is too slow, the backlog of frames will grow without bound |
| 328 // and trouble erupts. http://crbug.com/174519 |
| 329 PostColorOrError(SkColorSetRGB(yuv[0], yuv[1], yuv[2])); |
| 330 } |
175 | 331 |
176 { | 332 void PostColorOrError(SkColor new_color) { |
177 base::AutoLock guard(lock_); | 333 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( |
178 if (color != picture_color_) { | 334 &StubConsumer::QuitIfConditionMet, base::Unretained(this), new_color)); |
179 picture_color_ = color; | |
180 output_changed_.Signal(); | |
181 } | |
182 } | |
183 } | 335 } |
184 | 336 |
185 virtual void OnError() OVERRIDE { | 337 virtual void OnError() OVERRIDE { |
186 base::AutoLock guard(lock_); | 338 { |
187 error_encountered_ = true; | 339 base::AutoLock guard(lock_); |
188 output_changed_.Signal(); | 340 error_encountered_ = true; |
| 341 } |
| 342 PostColorOrError(kNothingYet); |
189 } | 343 } |
190 | 344 |
191 virtual void OnFrameInfo(const media::VideoCaptureCapability& info) OVERRIDE { | 345 virtual void OnFrameInfo(const media::VideoCaptureCapability& info) OVERRIDE { |
192 EXPECT_EQ(kTestWidth, info.width); | 346 EXPECT_EQ(kTestWidth, info.width); |
193 EXPECT_EQ(kTestHeight, info.height); | 347 EXPECT_EQ(kTestHeight, info.height); |
194 EXPECT_EQ(kTestFramesPerSecond, info.frame_rate); | 348 EXPECT_EQ(kTestFramesPerSecond, info.frame_rate); |
195 EXPECT_EQ(media::VideoCaptureCapability::kARGB, info.color); | 349 EXPECT_EQ(media::VideoCaptureCapability::kARGB, info.color); |
196 } | 350 } |
197 | 351 |
198 private: | 352 private: |
199 base::Lock lock_; | 353 base::Lock lock_; |
200 base::ConditionVariable output_changed_; | |
201 SkColor picture_color_; | |
202 bool error_encountered_; | 354 bool error_encountered_; |
| 355 SkColor wait_color_; |
203 | 356 |
204 DISALLOW_COPY_AND_ASSIGN(StubConsumer); | 357 DISALLOW_COPY_AND_ASSIGN(StubConsumer); |
205 }; | 358 }; |
206 | 359 |
| 360 } // namespace |
| 361 |
207 // Test harness that sets up a minimal environment with necessary stubs. | 362 // Test harness that sets up a minimal environment with necessary stubs. |
208 class WebContentsVideoCaptureDeviceTest : public testing::Test { | 363 class WebContentsVideoCaptureDeviceTest : public testing::Test { |
209 public: | 364 public: |
210 WebContentsVideoCaptureDeviceTest() {} | 365 WebContentsVideoCaptureDeviceTest() {} |
211 | 366 |
212 protected: | 367 protected: |
213 virtual void SetUp() { | 368 virtual void SetUp() { |
214 // This is a MessageLoop for the current thread. The MockRenderProcessHost | 369 // TODO(nick): Sadness and woe! Much "mock-the-world" boilerplate could be |
215 // will schedule its destruction in this MessageLoop during TearDown(). | 370 // eliminated here, if only we could use RenderViewHostTestHarness. The |
216 message_loop_.reset(new MessageLoop(MessageLoop::TYPE_IO)); | 371 // catch is that we need our TestRenderViewHost to support a |
| 372 // CopyFromBackingStore operation that we control. To accomplish that, |
| 373 // either RenderViewHostTestHarness would have to support installing a |
| 374 // custom RenderViewHostFactory, or else we implant some kind of delegated |
| 375 // CopyFromBackingStore functionality into TestRenderViewHost itself. |
217 | 376 |
218 // The CopyFromBackingStore and WebContents tracking occur on the UI thread. | 377 // The main thread will serve as the UI thread as well as the test thread. |
219 ui_thread_.reset(new BrowserThreadImpl(BrowserThread::UI)); | 378 // We'll manually pump the run loop at appropriate times in the test. |
220 ui_thread_->Start(); | 379 ui_thread_.reset(new TestBrowserThread(BrowserThread::UI, &message_loop_)); |
221 | 380 |
222 // And the rest... | 381 render_process_host_factory_.reset(new MockRenderProcessHostFactory()); |
| 382 // Create our (self-registering) RVH factory, so that when we create a |
| 383 // WebContents, it in turn creates CaptureTestRenderViewHosts. |
| 384 render_view_host_factory_.reset( |
| 385 new CaptureTestRenderViewHostFactory(&controller_)); |
| 386 |
223 browser_context_.reset(new TestBrowserContext()); | 387 browser_context_.reset(new TestBrowserContext()); |
224 source_.reset(new StubRenderWidgetHost( | 388 |
225 new MockRenderProcessHost(browser_context_.get()), MSG_ROUTING_NONE)); | 389 scoped_refptr<SiteInstance> site_instance = |
226 destroyed_.reset(new base::WaitableEvent(true, false)); | 390 SiteInstance::Create(browser_context_.get()); |
227 device_.reset(WebContentsVideoCaptureDevice::CreateForTesting( | 391 static_cast<SiteInstanceImpl*>(site_instance.get())-> |
228 source_.get(), | 392 set_render_process_host_factory(render_process_host_factory_.get()); |
229 base::Bind(&base::WaitableEvent::Signal, | 393 web_contents_.reset( |
230 base::Unretained(destroyed_.get())))); | 394 TestWebContents::Create(browser_context_.get(), site_instance)); |
231 consumer_.reset(new StubConsumer); | 395 |
| 396 // This is actually a CaptureTestRenderViewHost. |
| 397 RenderWidgetHostImpl* rwh = |
| 398 RenderWidgetHostImpl::From(web_contents_->GetRenderViewHost()); |
| 399 |
| 400 std::string device_id = |
| 401 WebContentsCaptureUtil::AppendWebContentsDeviceScheme( |
| 402 base::StringPrintf("%d:%d", rwh->GetProcess()->GetID(), |
| 403 rwh->GetRoutingID())); |
| 404 |
| 405 base::Closure destroy_cb = base::Bind( |
| 406 &CaptureTestSourceController::OnShutdown, |
| 407 base::Unretained(&controller_)); |
| 408 |
| 409 device_.reset(WebContentsVideoCaptureDevice::Create(device_id, destroy_cb)); |
232 } | 410 } |
233 | 411 |
234 virtual void TearDown() { | 412 virtual void TearDown() { |
235 // Tear down in opposite order of set-up. | 413 // Tear down in opposite order of set-up. |
236 device_->DeAllocate(); // Guarantees no more use of consumer_. | 414 |
237 consumer_.reset(); | 415 // The device is destroyed asynchronously, and will notify the |
238 device_.reset(); // Release reference to internal CaptureMachine. | 416 // CaptureTestSourceController when it finishes destruction. |
239 message_loop_->RunUntilIdle(); // Just in case. | 417 // Trigger this, and wait. |
240 destroyed_->Wait(); // Wait until CaptureMachine is fully destroyed. | 418 base::RunLoop shutdown_loop; |
241 destroyed_.reset(); | 419 controller_.SetShutdownHook(shutdown_loop.QuitClosure()); |
242 source_.reset(); | 420 device_->DeAllocate(); |
| 421 device_.reset(); |
| 422 shutdown_loop.Run(); |
| 423 |
| 424 // Destroy the browser objects. |
| 425 web_contents_.reset(); |
243 browser_context_.reset(); | 426 browser_context_.reset(); |
244 ui_thread_->Stop(); | 427 |
245 ui_thread_.reset(); | 428 content::RunAllPendingInMessageLoop(); |
246 message_loop_->RunUntilIdle(); // Deletes MockRenderProcessHost. | 429 |
247 message_loop_.reset(); | 430 render_view_host_factory_.reset(); |
| 431 render_process_host_factory_.reset(); |
248 } | 432 } |
249 | 433 |
250 // Accessors. | 434 // Accessors. |
251 StubRenderWidgetHost* source() const { return source_.get(); } | 435 CaptureTestSourceController* source() { return &controller_; } |
252 media::VideoCaptureDevice* device() const { return device_.get(); } | 436 media::VideoCaptureDevice* device() { return device_.get(); } |
253 StubConsumer* consumer() const { return consumer_.get(); } | 437 StubConsumer* consumer() { return &consumer_; } |
254 | 438 |
255 private: | 439 private: |
256 scoped_ptr<MessageLoop> message_loop_; | 440 // The consumer is the ultimate recipient of captured pixel data. |
257 scoped_ptr<BrowserThreadImpl> ui_thread_; | 441 StubConsumer consumer_; |
| 442 |
| 443 // The controller controls which pixel patterns to produce. |
| 444 CaptureTestSourceController controller_; |
| 445 |
| 446 // We run the UI message loop on the main thread. The capture device |
| 447 // will also spin up its own threads. |
| 448 MessageLoopForUI message_loop_; |
| 449 scoped_ptr<TestBrowserThread> ui_thread_; |
| 450 |
| 451 // Self-registering RenderProcessHostFactory. |
| 452 scoped_ptr<MockRenderProcessHostFactory> render_process_host_factory_; |
| 453 |
| 454 // Creates capture-capable RenderViewHosts whose pixel content production is |
| 455 // under the control of |controller_|. |
| 456 scoped_ptr<CaptureTestRenderViewHostFactory> render_view_host_factory_; |
| 457 |
| 458 // A mocked-out browser and tab. |
258 scoped_ptr<TestBrowserContext> browser_context_; | 459 scoped_ptr<TestBrowserContext> browser_context_; |
259 scoped_ptr<StubRenderWidgetHost> source_; | 460 scoped_ptr<WebContents> web_contents_; |
260 scoped_ptr<base::WaitableEvent> destroyed_; | 461 |
| 462 // Finally, the WebContentsVideoCaptureDevice under test. |
261 scoped_ptr<media::VideoCaptureDevice> device_; | 463 scoped_ptr<media::VideoCaptureDevice> device_; |
262 scoped_ptr<StubConsumer> consumer_; | |
263 | 464 |
264 DISALLOW_COPY_AND_ASSIGN(WebContentsVideoCaptureDeviceTest); | 465 DISALLOW_COPY_AND_ASSIGN(WebContentsVideoCaptureDeviceTest); |
265 }; | 466 }; |
266 | 467 |
267 // The "happy case" test. No scaling is needed, so we should be able to change | 468 // The "happy case" test. No scaling is needed, so we should be able to change |
268 // the picture emitted from the source and expect to see each delivered to the | 469 // the picture emitted from the source and expect to see each delivered to the |
269 // consumer. The test will alternate between the SkBitmap and the VideoFrame | 470 // consumer. The test will alternate between the SkBitmap and the VideoFrame |
270 // paths, just as RenderWidgetHost might if the content falls in and out of | 471 // paths, just as RenderWidgetHost might if the content falls in and out of |
271 // accelerated compositing. | 472 // accelerated compositing. |
272 TEST_F(WebContentsVideoCaptureDeviceTest, GoesThroughAllTheMotions) { | 473 TEST_F(WebContentsVideoCaptureDeviceTest, GoesThroughAllTheMotions) { |
273 device()->Allocate(kTestWidth, kTestHeight, kTestFramesPerSecond, | 474 device()->Allocate(kTestWidth, kTestHeight, kTestFramesPerSecond, consumer()); |
274 consumer()); | |
275 | 475 |
276 device()->Start(); | 476 device()->Start(); |
277 | 477 |
278 bool use_video_frames = false; | 478 bool use_video_frames = false; |
279 for (int i = 0; i < 4; i++, use_video_frames = !use_video_frames) { | 479 for (int i = 0; i < 4; i++, use_video_frames = !use_video_frames) { |
280 SCOPED_TRACE( | 480 SCOPED_TRACE(StringPrintf("Using %s path, iteration #%d", |
281 testing::Message() << "Using " | 481 use_video_frames ? "VideoFrame" : "SkBitmap", i)); |
282 << (use_video_frames ? "VideoFrame" : "SkBitmap") | 482 source()->SetCanCopyToVideoFrame(use_video_frames); |
283 << " path, iteration #" << i); | |
284 // TODO(nick): Implement this. | |
285 // source()->SetUseVideoFrames(use_video_frames); | |
286 source()->SetSolidColor(SK_ColorRED); | 483 source()->SetSolidColor(SK_ColorRED); |
287 source()->WaitForNextBackingStoreCopy(); | 484 ASSERT_NO_FATAL_FAILURE(source()->WaitForNextBackingStoreCopy()); |
288 ASSERT_TRUE(consumer()->WaitForNextColorOrError(SK_ColorRED)); | 485 ASSERT_NO_FATAL_FAILURE(consumer()->WaitForNextColor(SK_ColorRED)); |
289 source()->SetSolidColor(SK_ColorGREEN); | 486 source()->SetSolidColor(SK_ColorGREEN); |
290 ASSERT_TRUE(consumer()->WaitForNextColorOrError(SK_ColorGREEN)); | 487 ASSERT_NO_FATAL_FAILURE(consumer()->WaitForNextColor(SK_ColorGREEN)); |
291 source()->SetSolidColor(SK_ColorBLUE); | 488 source()->SetSolidColor(SK_ColorBLUE); |
292 ASSERT_TRUE(consumer()->WaitForNextColorOrError(SK_ColorBLUE)); | 489 ASSERT_NO_FATAL_FAILURE(consumer()->WaitForNextColor(SK_ColorBLUE)); |
293 source()->SetSolidColor(SK_ColorBLACK); | 490 source()->SetSolidColor(SK_ColorBLACK); |
294 ASSERT_TRUE(consumer()->WaitForNextColorOrError(SK_ColorBLACK)); | 491 ASSERT_NO_FATAL_FAILURE(consumer()->WaitForNextColor(SK_ColorBLACK)); |
295 } | 492 } |
296 | 493 |
297 device()->DeAllocate(); | 494 device()->DeAllocate(); |
298 } | 495 } |
299 | 496 |
300 TEST_F(WebContentsVideoCaptureDeviceTest, RejectsInvalidAllocateParams) { | 497 TEST_F(WebContentsVideoCaptureDeviceTest, RejectsInvalidAllocateParams) { |
301 device()->Allocate(1280, 720, -2, consumer()); | 498 device()->Allocate(1280, 720, -2, consumer()); |
302 EXPECT_FALSE(consumer()->WaitForNextColorOrError(kNotInterested)); | 499 ASSERT_NO_FATAL_FAILURE(consumer()->WaitForError()); |
303 } | 500 } |
304 | 501 |
305 TEST_F(WebContentsVideoCaptureDeviceTest, BadFramesGoodFrames) { | 502 TEST_F(WebContentsVideoCaptureDeviceTest, BadFramesGoodFrames) { |
306 device()->Allocate(kTestWidth, kTestHeight, kTestFramesPerSecond, | 503 device()->Allocate(kTestWidth, kTestHeight, kTestFramesPerSecond, consumer()); |
307 consumer()); | |
308 | |
309 | 504 |
310 // 1x1 is too small to process; we intend for this to result in an error. | 505 // 1x1 is too small to process; we intend for this to result in an error. |
311 source()->SetCopyResultSize(1, 1); | 506 source()->SetCopyResultSize(1, 1); |
312 source()->SetSolidColor(SK_ColorRED); | 507 source()->SetSolidColor(SK_ColorRED); |
313 device()->Start(); | 508 device()->Start(); |
314 | 509 |
315 // These frames ought to be dropped during the Render stage. Let | 510 // These frames ought to be dropped during the Render stage. Let |
316 // several captures to happen. | 511 // several captures to happen. |
317 ASSERT_TRUE(source()->WaitForNextBackingStoreCopy()); | 512 ASSERT_NO_FATAL_FAILURE(source()->WaitForNextBackingStoreCopy()); |
318 ASSERT_TRUE(source()->WaitForNextBackingStoreCopy()); | 513 ASSERT_NO_FATAL_FAILURE(source()->WaitForNextBackingStoreCopy()); |
319 ASSERT_TRUE(source()->WaitForNextBackingStoreCopy()); | 514 ASSERT_NO_FATAL_FAILURE(source()->WaitForNextBackingStoreCopy()); |
320 ASSERT_TRUE(source()->WaitForNextBackingStoreCopy()); | 515 ASSERT_NO_FATAL_FAILURE(source()->WaitForNextBackingStoreCopy()); |
321 ASSERT_TRUE(source()->WaitForNextBackingStoreCopy()); | 516 ASSERT_NO_FATAL_FAILURE(source()->WaitForNextBackingStoreCopy()); |
322 | 517 |
323 // Now push some good frames through; they should be processed normally. | 518 // Now push some good frames through; they should be processed normally. |
324 source()->SetCopyResultSize(kTestWidth, kTestHeight); | 519 source()->SetCopyResultSize(kTestWidth, kTestHeight); |
325 source()->SetSolidColor(SK_ColorGREEN); | 520 source()->SetSolidColor(SK_ColorGREEN); |
326 EXPECT_TRUE(consumer()->WaitForNextColorOrError(SK_ColorGREEN)); | 521 ASSERT_NO_FATAL_FAILURE(consumer()->WaitForNextColor(SK_ColorGREEN)); |
327 source()->SetSolidColor(SK_ColorRED); | 522 source()->SetSolidColor(SK_ColorRED); |
328 EXPECT_TRUE(consumer()->WaitForNextColorOrError(SK_ColorRED)); | 523 ASSERT_NO_FATAL_FAILURE(consumer()->WaitForNextColor(SK_ColorRED)); |
| 524 |
| 525 device()->Stop(); |
329 device()->DeAllocate(); | 526 device()->DeAllocate(); |
330 } | 527 } |
331 | 528 |
332 } // namespace content | 529 } // namespace content |
OLD | NEW |