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