1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/blink/renderer/modules/mediastream/video_track_adapter.h"
6
7 #include <limits>
8
9 #include "base/bind_helpers.h"
10 #include "base/run_loop.h"
11 #include "base/synchronization/waitable_event.h"
12 #include "base/test/bind_test_util.h"
13 #include "base/threading/thread.h"
14 #include "media/base/limits.h"
15 #include "testing/gtest/include/gtest/gtest.h"
16 #include "third_party/blink/public/web/web_heap.h"
17 #include "third_party/blink/renderer/modules/mediastream/mock_encoded_video_frame.h"
18 #include "third_party/blink/renderer/modules/mediastream/mock_media_stream_video_source.h"
19 #include "third_party/blink/renderer/modules/mediastream/video_track_adapter_settings.h"
20 #include "third_party/blink/renderer/platform/testing/io_task_runner_testing_platform_support.h"
21 #include "third_party/blink/renderer/platform/testing/video_frame_utils.h"
22
23 namespace blink {
24
25 // Most VideoTrackAdapter functionality is tested in MediaStreamVideoSourceTest.
26 // These tests focus on the computation of cropped frame sizes in edge cases
27 // that cannot be easily reproduced by a mocked video source, such as tests
28 // involving frames of zero size.
29 // Such frames can be produced by sources in the wild (e.g., element capture).
30
31 // Test that cropped sizes with zero-area input frames are correctly computed.
32 // Aspect ratio limits should be ignored.
TEST(VideoTrackAdapterTest,ZeroInputArea)33 TEST(VideoTrackAdapterTest, ZeroInputArea) {
34 const int kMaxWidth = 640;
35 const int kMaxHeight = 480;
36 const int kSmallDimension = 300;
37 const int kLargeDimension = 1000;
38 static_assert(kSmallDimension < kMaxWidth && kSmallDimension < kMaxHeight,
39 "kSmallDimension must be less than kMaxWidth and kMaxHeight");
40 static_assert(
41 kLargeDimension > kMaxWidth && kLargeDimension > kMaxHeight,
42 "kLargeDimension must be greater than kMaxWidth and kMaxHeight");
43 const VideoTrackAdapterSettings kVideoTrackAdapterSettings(
44 gfx::Size(kMaxWidth, kMaxHeight), 0.1 /* min_aspect_ratio */,
45 2.0 /* max_aspect_ratio */, 0.0 /* max_frame_rate */);
46 const bool kIsRotatedValues[] = {true, false};
47
48 for (bool is_rotated : kIsRotatedValues) {
49 gfx::Size desired_size;
50
51 VideoTrackAdapter::CalculateDesiredSize(
52 is_rotated, gfx::Size(0, 0), kVideoTrackAdapterSettings, &desired_size);
53 EXPECT_EQ(desired_size.width(), 0);
54 EXPECT_EQ(desired_size.height(), 0);
55
56 // Zero width.
57 VideoTrackAdapter::CalculateDesiredSize(
58 is_rotated, gfx::Size(0, kSmallDimension), kVideoTrackAdapterSettings,
59 &desired_size);
60 EXPECT_EQ(desired_size.width(), 0);
61 EXPECT_EQ(desired_size.height(), kSmallDimension);
62
63 // Zero height.
64 VideoTrackAdapter::CalculateDesiredSize(
65 is_rotated, gfx::Size(kSmallDimension, 0), kVideoTrackAdapterSettings,
66 &desired_size);
67 EXPECT_EQ(desired_size.width(), kSmallDimension);
68 EXPECT_EQ(desired_size.height(), 0);
69
70 // Requires "cropping" of height.
71 VideoTrackAdapter::CalculateDesiredSize(
72 is_rotated, gfx::Size(0, kLargeDimension), kVideoTrackAdapterSettings,
73 &desired_size);
74 EXPECT_EQ(desired_size.width(), 0);
75 EXPECT_EQ(desired_size.height(), is_rotated ? kMaxWidth : kMaxHeight);
76
77 // Requires "cropping" of width.
78 VideoTrackAdapter::CalculateDesiredSize(
79 is_rotated, gfx::Size(kLargeDimension, 0), kVideoTrackAdapterSettings,
80 &desired_size);
81 EXPECT_EQ(desired_size.width(), is_rotated ? kMaxHeight : kMaxWidth);
82 EXPECT_EQ(desired_size.height(), 0);
83 }
84 }
85
86 // Test that zero-size cropped areas are correctly computed. Aspect ratio
87 // limits should be ignored.
TEST(VideoTrackAdapterTest,ZeroOutputArea)88 TEST(VideoTrackAdapterTest, ZeroOutputArea) {
89 const double kMinAspectRatio = 0.1;
90 const double kMaxAspectRatio = 2.0;
91 const int kInputWidth = 640;
92 const int kInputHeight = 480;
93 const int kSmallMaxDimension = 300;
94 const int kLargeMaxDimension = 1000;
95 static_assert(
96 kSmallMaxDimension < kInputWidth && kSmallMaxDimension < kInputHeight,
97 "kSmallMaxDimension must be less than kInputWidth and kInputHeight");
98 static_assert(
99 kLargeMaxDimension > kInputWidth && kLargeMaxDimension > kInputHeight,
100 "kLargeMaxDimension must be greater than kInputWidth and kInputHeight");
101
102 gfx::Size desired_size;
103
104 VideoTrackAdapter::CalculateDesiredSize(
105 false /* is_rotated */, gfx::Size(kInputWidth, kInputHeight),
106 VideoTrackAdapterSettings(gfx::Size(0, 0), kMinAspectRatio,
107 kMaxAspectRatio, 0.0 /* max_frame_rate */),
108 &desired_size);
109 EXPECT_EQ(desired_size.width(), 0);
110 EXPECT_EQ(desired_size.height(), 0);
111
112 // Width is cropped to zero.
113 VideoTrackAdapter::CalculateDesiredSize(
114 false /* is_rotated */, gfx::Size(kInputWidth, kInputHeight),
115 VideoTrackAdapterSettings(gfx::Size(0, kLargeMaxDimension), 0.0,
116 HUGE_VAL, // kMinAspectRatio, kMaxAspectRatio,
117 0.0 /* max_frame_rate */),
118 &desired_size);
119 EXPECT_EQ(desired_size.width(), 0);
120 EXPECT_EQ(desired_size.height(), kInputHeight);
121
122 // Requires "cropping" of width and height.
123 VideoTrackAdapter::CalculateDesiredSize(
124 false /* is_rotated */, gfx::Size(kInputWidth, kInputHeight),
125 VideoTrackAdapterSettings(gfx::Size(0, kSmallMaxDimension),
126 kMinAspectRatio, kMaxAspectRatio,
127 0.0 /* max_frame_rate */),
128 &desired_size);
129 EXPECT_EQ(desired_size.width(), 0);
130 EXPECT_EQ(desired_size.height(), kSmallMaxDimension);
131
132 // Height is cropped to zero.
133 VideoTrackAdapter::CalculateDesiredSize(
134 false /* is_rotated */, gfx::Size(kInputWidth, kInputHeight),
135 VideoTrackAdapterSettings(gfx::Size(kLargeMaxDimension, 0),
136 kMinAspectRatio, kMaxAspectRatio,
137 0.0 /* max_frame_rate */),
138 &desired_size);
139 EXPECT_EQ(desired_size.width(), kInputWidth);
140 EXPECT_EQ(desired_size.height(), 0);
141
142 // Requires "cropping" of width and height.
143 VideoTrackAdapter::CalculateDesiredSize(
144 false /* is_rotated */, gfx::Size(kInputWidth, kInputHeight),
145 VideoTrackAdapterSettings(
146 gfx::Size(kSmallMaxDimension /* max_width */, 0 /* max_height */),
147 kMinAspectRatio, kMaxAspectRatio, 0.0 /* max_frame_rate */),
148 &desired_size);
149 EXPECT_EQ(desired_size.width(), kSmallMaxDimension);
150 EXPECT_EQ(desired_size.height(), 0);
151 }
152
153 // Test that large frames are handled correctly.
TEST(VideoTrackAdapterTest,LargeFrames)154 TEST(VideoTrackAdapterTest, LargeFrames) {
155 const int kInputWidth = std::numeric_limits<int>::max();
156 const int kInputHeight = std::numeric_limits<int>::max();
157 const int kMaxWidth = std::numeric_limits<int>::max();
158 const int kMaxHeight = std::numeric_limits<int>::max();
159
160 gfx::Size desired_size;
161
162 // If a target size is provided in VideoTrackAdapterSettings, rescaling is
163 // allowed and frames must be clamped to the maximum allowed size, even if the
164 // target exceeds the system maximum.
165 bool success = VideoTrackAdapter::CalculateDesiredSize(
166 false /* is_rotated */, gfx::Size(kInputWidth, kInputHeight),
167 VideoTrackAdapterSettings(gfx::Size(kMaxWidth, kMaxHeight),
168 0.0 /* max_frame_rate */),
169 &desired_size);
170 EXPECT_TRUE(success);
171 EXPECT_EQ(desired_size.width(), media::limits::kMaxDimension);
172 EXPECT_EQ(desired_size.height(), media::limits::kMaxDimension);
173
174 // If no target size is provided in VideoTrackAdapterSettings, rescaling is
175 // disabled and |desired_size| is left unmodified.
176 desired_size.set_width(0);
177 desired_size.set_height(0);
178 success = VideoTrackAdapter::CalculateDesiredSize(
179 false /* is_rotated */, gfx::Size(kInputWidth, kInputHeight),
180 VideoTrackAdapterSettings(), &desired_size);
181 EXPECT_FALSE(success);
182 EXPECT_EQ(desired_size.width(), 0);
183 EXPECT_EQ(desired_size.height(), 0);
184 }
185
186 // Test that regular frames are not rescaled if settings do not specify a target
187 // resolution.
TEST(VideoTrackAdapterTest,NoRescaling)188 TEST(VideoTrackAdapterTest, NoRescaling) {
189 const int kInputWidth = 640;
190 const int kInputHeight = 480;
191
192 // No target size,
193 gfx::Size desired_size;
194 bool success = VideoTrackAdapter::CalculateDesiredSize(
195 false /* is_rotated */, gfx::Size(kInputWidth, kInputHeight),
196 VideoTrackAdapterSettings(), &desired_size);
197 EXPECT_TRUE(success);
198 EXPECT_EQ(desired_size.width(), kInputWidth);
199 EXPECT_EQ(desired_size.height(), kInputHeight);
200 }
201
202 class VideoTrackAdapterFixtureTest : public ::testing::Test {
203 public:
VideoTrackAdapterFixtureTest()204 VideoTrackAdapterFixtureTest()
205 : testing_render_thread_("TestingRenderThread"),
206 frame_received_(base::WaitableEvent::ResetPolicy::MANUAL,
207 base::WaitableEvent::InitialState::NOT_SIGNALED) {}
208 ~VideoTrackAdapterFixtureTest() override = default;
209
210 protected:
SetUp()211 void SetUp() override { testing_render_thread_.Start(); }
212
TearDown()213 void TearDown() override {
214 if (track_added_) {
215 testing_render_thread_.task_runner()->PostTask(
216 FROM_HERE, base::BindOnce(&VideoTrackAdapter::RemoveTrack, adapter_,
217 null_track_.get()));
218 }
219 testing_render_thread_.Stop();
220 }
221
CreateAdapter(media::VideoCaptureFormat capture_format)222 void CreateAdapter(media::VideoCaptureFormat capture_format) {
223 mock_source_ =
224 std::make_unique<MockMediaStreamVideoSource>(capture_format, false);
225 // Create the VideoTrackAdapter instance on |testing_render_thread_|.
226 base::WaitableEvent adapter_created(
227 base::WaitableEvent::ResetPolicy::MANUAL,
228 base::WaitableEvent::InitialState::NOT_SIGNALED);
229 testing_render_thread_.task_runner()->PostTask(
230 FROM_HERE, base::BindLambdaForTesting([&]() {
231 adapter_ = base::MakeRefCounted<VideoTrackAdapter>(
232 platform_support_->GetIOTaskRunner(), mock_source_->GetWeakPtr());
233 adapter_created.Signal();
234 }));
235 adapter_created.Wait();
236 }
237
238 // Create or re-configure the dummy |null_track_| with the given
239 // |adapter_settings|.
ConfigureTrack(const VideoTrackAdapterSettings & adapter_settings)240 void ConfigureTrack(const VideoTrackAdapterSettings& adapter_settings) {
241 if (!track_added_) {
242 AddTrackInternal(null_track_.get(), adapter_settings);
243 track_added_ = true;
244 } else {
245 testing_render_thread_.task_runner()->PostTask(
246 FROM_HERE,
247 base::BindOnce(&VideoTrackAdapter::ReconfigureTrack, adapter_,
248 null_track_.get(), adapter_settings));
249 }
250 }
251
AddTrackInternal(MediaStreamVideoTrack * track,const VideoTrackAdapterSettings & adapter_settings)252 void AddTrackInternal(MediaStreamVideoTrack* track,
253 const VideoTrackAdapterSettings& adapter_settings) {
254 testing_render_thread_.task_runner()->PostTask(
255 FROM_HERE,
256 base::BindOnce(
257 &VideoTrackAdapter::AddTrack, adapter_, track,
258 base::BindRepeating(&VideoTrackAdapterFixtureTest::OnFrameDelivered,
259 base::Unretained(this)),
260 base::BindRepeating(
261 &VideoTrackAdapterFixtureTest::OnEncodedVideoFrameDelivered,
262 base::Unretained(this)),
263 base::DoNothing(), base::DoNothing(), adapter_settings));
264 }
265
SetFrameValidationCallback(VideoCaptureDeliverFrameCB callback)266 void SetFrameValidationCallback(VideoCaptureDeliverFrameCB callback) {
267 frame_validation_callback_ = std::move(callback);
268 }
269
270 // Deliver |frame| to |adapter_| and wait until OnFrameDelivered signals that
271 // it receives the processed frame.
DeliverAndValidateFrame(scoped_refptr<media::VideoFrame> frame,base::TimeTicks estimated_capture_time)272 void DeliverAndValidateFrame(scoped_refptr<media::VideoFrame> frame,
273 base::TimeTicks estimated_capture_time) {
274 auto deliver_frame = [&]() {
275 platform_support_->GetIOTaskRunner()->PostTask(
276 FROM_HERE, base::BindOnce(&VideoTrackAdapter::DeliverFrameOnIO,
277 adapter_, frame, estimated_capture_time));
278 };
279
280 frame_received_.Reset();
281 // Bounce the call to DeliverFrameOnIO off |testing_render_thread_| to
282 // synchronize with the AddTrackOnIO / ReconfigureTrackOnIO that would be
283 // invoked through ConfigureTrack.
284 testing_render_thread_.task_runner()->PostTask(
285 FROM_HERE, base::BindLambdaForTesting(deliver_frame));
286 frame_received_.Wait();
287 }
288
OnFrameDelivered(scoped_refptr<media::VideoFrame> frame,base::TimeTicks estimated_capture_time)289 void OnFrameDelivered(scoped_refptr<media::VideoFrame> frame,
290 base::TimeTicks estimated_capture_time) {
291 if (frame_validation_callback_) {
292 frame_validation_callback_.Run(frame, estimated_capture_time);
293 }
294 frame_received_.Signal();
295 }
296
297 MOCK_METHOD2(OnEncodedVideoFrameDelivered,
298 void(scoped_refptr<EncodedVideoFrame>,
299 base::TimeTicks estimated_capture_time));
300
301 ScopedTestingPlatformSupport<IOTaskRunnerTestingPlatformSupport>
302 platform_support_;
303 base::Thread testing_render_thread_;
304 std::unique_ptr<MockMediaStreamVideoSource> mock_source_;
305 scoped_refptr<VideoTrackAdapter> adapter_;
306
307 base::WaitableEvent frame_received_;
308 VideoCaptureDeliverFrameCB frame_validation_callback_;
309
310 // For testing we use a nullptr for MediaStreamVideoTrack.
311 std::unique_ptr<MediaStreamVideoTrack> null_track_ = nullptr;
312 bool track_added_ = false;
313 };
314
TEST_F(VideoTrackAdapterFixtureTest,DeliverFrame_GpuMemoryBuffer)315 TEST_F(VideoTrackAdapterFixtureTest, DeliverFrame_GpuMemoryBuffer) {
316 // Attributes for the original input frame.
317 const gfx::Size kCodedSize(1280, 960);
318 const gfx::Rect kVisibleRect(0, 120, 1280, 720);
319 const gfx::Size kNaturalSize(1280, 720);
320 const double kFrameRate = 30.0;
321 auto gmb_frame =
322 CreateTestFrame(kCodedSize, kVisibleRect, kNaturalSize,
323 media::VideoFrame::STORAGE_GPU_MEMORY_BUFFER);
324
325 // Initialize the VideoTrackAdapter to handle GpuMemoryBuffer. NV12 is the
326 // only pixel format supported at the moment.
327 const media::VideoCaptureFormat stream_format(kCodedSize, kFrameRate,
328 media::PIXEL_FORMAT_NV12);
329 CreateAdapter(stream_format);
330
331 // Keep the desired size the same as the natural size of the original frame.
332 VideoTrackAdapterSettings settings_nonscaled(kNaturalSize, kFrameRate);
333 ConfigureTrack(settings_nonscaled);
334 auto check_nonscaled = [&](scoped_refptr<media::VideoFrame> frame,
335 base::TimeTicks estimated_capture_time) {
336 // We should get the original frame as-is here.
337 EXPECT_EQ(frame->storage_type(),
338 media::VideoFrame::STORAGE_GPU_MEMORY_BUFFER);
339 EXPECT_EQ(frame->GetGpuMemoryBuffer(), gmb_frame->GetGpuMemoryBuffer());
340 EXPECT_EQ(frame->coded_size(), kCodedSize);
341 EXPECT_EQ(frame->visible_rect(), kVisibleRect);
342 EXPECT_EQ(frame->natural_size(), kNaturalSize);
343 };
344 SetFrameValidationCallback(base::BindLambdaForTesting(check_nonscaled));
345 DeliverAndValidateFrame(gmb_frame, base::TimeTicks());
346
347 // Scale the original frame by a factor of 0.5x.
348 const gfx::Size kDesiredSize(640, 360);
349 VideoTrackAdapterSettings settings_scaled(kDesiredSize, kFrameRate);
350 ConfigureTrack(settings_scaled);
351 auto check_scaled = [&](scoped_refptr<media::VideoFrame> frame,
352 base::TimeTicks estimated_capture_time) {
353 // The original frame should be wrapped in a new frame, with |kDesiredSize|
354 // exposed as natural size of the wrapped frame.
355 EXPECT_EQ(frame->storage_type(),
356 media::VideoFrame::STORAGE_GPU_MEMORY_BUFFER);
357 EXPECT_EQ(frame->GetGpuMemoryBuffer(), gmb_frame->GetGpuMemoryBuffer());
358 EXPECT_EQ(frame->coded_size(), kCodedSize);
359 EXPECT_EQ(frame->visible_rect(), kVisibleRect);
360 EXPECT_EQ(frame->natural_size(), kDesiredSize);
361 };
362 SetFrameValidationCallback(base::BindLambdaForTesting(check_scaled));
363 DeliverAndValidateFrame(gmb_frame, base::TimeTicks());
364 }
365
366 class VideoTrackAdapterEncodedTest : public ::testing::Test {
367 public:
VideoTrackAdapterEncodedTest()368 VideoTrackAdapterEncodedTest()
369 : render_thread_("VideoTrackAdapterEncodedTest_RenderThread") {
370 render_thread_.Start();
371 auto source = std::make_unique<MockMediaStreamVideoSource>(
372 media::VideoCaptureFormat(), false);
373 mock_source_ = source.get();
374 web_source_.Initialize(
375 blink::WebString::FromASCII("source_id"),
376 blink::WebMediaStreamSource::kTypeVideo,
377 blink::WebString::FromASCII("DeliverEncodedVideoFrameSource"),
378 false /* remote */);
379 web_source_.SetPlatformSource(std::move(source));
380 RunSyncOnRenderThread([&] {
381 adapter_ = base::MakeRefCounted<VideoTrackAdapter>(
382 platform_support_->GetIOTaskRunner(), mock_source_->GetWeakPtr());
383 });
384 }
385
~VideoTrackAdapterEncodedTest()386 ~VideoTrackAdapterEncodedTest() {
387 web_source_.Reset();
388 WebHeap::CollectAllGarbageForTesting();
389 }
390
AddTrack()391 std::unique_ptr<MediaStreamVideoTrack> AddTrack() {
392 auto track = std::make_unique<MediaStreamVideoTrack>(
393 mock_source_, WebPlatformMediaStreamSource::ConstraintsOnceCallback(),
394 true);
395 RunSyncOnRenderThread([&] {
396 adapter_->AddTrack(
397 track.get(),
398 base::BindRepeating(&VideoTrackAdapterEncodedTest::OnFrameDelivered,
399 base::Unretained(this)),
400 base::BindRepeating(
401 &VideoTrackAdapterEncodedTest::OnEncodedVideoFrameDelivered,
402 base::Unretained(this)),
403 base::DoNothing(), base::DoNothing(), VideoTrackAdapterSettings());
404 });
405 return track;
406 }
407
408 template <class Function>
RunSyncOnRenderThread(Function function)409 void RunSyncOnRenderThread(Function function) {
410 base::RunLoop run_loop;
411 base::OnceClosure quit_closure = run_loop.QuitClosure();
412 render_thread_.task_runner()->PostTask(FROM_HERE,
413 base::BindLambdaForTesting([&] {
414 std::move(function)();
415 std::move(quit_closure).Run();
416 }));
417 run_loop.Run();
418 }
419
420 MOCK_METHOD2(OnFrameDelivered,
421 void(scoped_refptr<media::VideoFrame> frame,
422 base::TimeTicks estimated_capture_time));
423 MOCK_METHOD2(OnEncodedVideoFrameDelivered,
424 void(scoped_refptr<EncodedVideoFrame>,
425 base::TimeTicks estimated_capture_time));
426
427 protected:
428 ScopedTestingPlatformSupport<IOTaskRunnerTestingPlatformSupport>
429 platform_support_;
430 base::Thread render_thread_;
431 WebMediaStreamSource web_source_;
432 MockMediaStreamVideoSource* mock_source_;
433 scoped_refptr<VideoTrackAdapter> adapter_;
434 };
435
TEST_F(VideoTrackAdapterEncodedTest,DeliverEncodedVideoFrame)436 TEST_F(VideoTrackAdapterEncodedTest, DeliverEncodedVideoFrame) {
437 auto track1 = AddTrack();
438 auto track2 = AddTrack();
439 EXPECT_CALL(*this, OnEncodedVideoFrameDelivered)
440 .Times(2)
441 .WillRepeatedly(
442 testing::Invoke([&](const scoped_refptr<EncodedVideoFrame>& frame,
443 base::TimeTicks estimated_capture_time) {}));
444 base::RunLoop run_loop;
445 base::OnceClosure quit_closure = run_loop.QuitClosure();
446 platform_support_->GetIOTaskRunner()->PostTask(
447 FROM_HERE, base::BindLambdaForTesting([&]() {
448 adapter_->DeliverEncodedVideoFrameOnIO(
449 base::MakeRefCounted<MockEncodedVideoFrame>(), base::TimeTicks());
450 std::move(quit_closure).Run();
451 }));
452 run_loop.Run();
453 RunSyncOnRenderThread([&] {
454 adapter_->RemoveTrack(track1.get());
455 adapter_->RemoveTrack(track2.get());
456 });
457 }
458
459 } // namespace blink
460