|  | /* | 
|  | *  Copyright 2020 The WebRTC Project Authors. All rights reserved. | 
|  | * | 
|  | *  Use of this source code is governed by a BSD-style license | 
|  | *  that can be found in the LICENSE file in the root of the source | 
|  | *  tree. An additional intellectual property rights grant can be found | 
|  | *  in the file PATENTS.  All contributing project authors may | 
|  | *  be found in the AUTHORS file in the root of the source tree. | 
|  | */ | 
|  |  | 
|  | #include "video/video_source_sink_controller.h" | 
|  |  | 
|  | #include <limits> | 
|  |  | 
|  | #include "api/video/video_frame.h" | 
|  | #include "api/video/video_source_interface.h" | 
|  | #include "call/adaptation/video_source_restrictions.h" | 
|  | #include "test/gmock.h" | 
|  | #include "test/gtest.h" | 
|  |  | 
|  | using testing::_; | 
|  |  | 
|  | namespace webrtc { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | using FrameSize = rtc::VideoSinkWants::FrameSize; | 
|  | constexpr int kIntUnconstrained = std::numeric_limits<int>::max(); | 
|  |  | 
|  | class MockVideoSinkWithVideoFrame : public rtc::VideoSinkInterface<VideoFrame> { | 
|  | public: | 
|  | ~MockVideoSinkWithVideoFrame() override {} | 
|  |  | 
|  | MOCK_METHOD(void, OnFrame, (const VideoFrame& frame), (override)); | 
|  | MOCK_METHOD(void, OnDiscardedFrame, (), (override)); | 
|  | }; | 
|  |  | 
|  | class MockVideoSourceWithVideoFrame | 
|  | : public rtc::VideoSourceInterface<VideoFrame> { | 
|  | public: | 
|  | ~MockVideoSourceWithVideoFrame() override {} | 
|  |  | 
|  | MOCK_METHOD(void, | 
|  | AddOrUpdateSink, | 
|  | (rtc::VideoSinkInterface<VideoFrame>*, | 
|  | const rtc::VideoSinkWants&), | 
|  | (override)); | 
|  | MOCK_METHOD(void, | 
|  | RemoveSink, | 
|  | (rtc::VideoSinkInterface<VideoFrame>*), | 
|  | (override)); | 
|  | MOCK_METHOD(void, RequestRefreshFrame, (), (override)); | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | TEST(VideoSourceSinkControllerTest, UnconstrainedByDefault) { | 
|  | MockVideoSinkWithVideoFrame sink; | 
|  | MockVideoSourceWithVideoFrame source; | 
|  | VideoSourceSinkController controller(&sink, &source); | 
|  | EXPECT_EQ(controller.restrictions(), VideoSourceRestrictions()); | 
|  | EXPECT_FALSE(controller.pixels_per_frame_upper_limit().has_value()); | 
|  | EXPECT_FALSE(controller.frame_rate_upper_limit().has_value()); | 
|  | EXPECT_FALSE(controller.rotation_applied()); | 
|  | EXPECT_FALSE(controller.scale_resolution_down_to().has_value()); | 
|  | EXPECT_EQ(controller.resolution_alignment(), 1); | 
|  |  | 
|  | EXPECT_CALL(source, AddOrUpdateSink(_, _)) | 
|  | .WillOnce([](rtc::VideoSinkInterface<VideoFrame>* sink, | 
|  | const rtc::VideoSinkWants& wants) { | 
|  | EXPECT_FALSE(wants.rotation_applied); | 
|  | EXPECT_EQ(wants.max_pixel_count, kIntUnconstrained); | 
|  | EXPECT_EQ(wants.target_pixel_count, std::nullopt); | 
|  | EXPECT_EQ(wants.max_framerate_fps, kIntUnconstrained); | 
|  | EXPECT_EQ(wants.resolution_alignment, 1); | 
|  | EXPECT_FALSE(wants.requested_resolution.has_value()); | 
|  | }); | 
|  | controller.PushSourceSinkSettings(); | 
|  | } | 
|  |  | 
|  | TEST(VideoSourceSinkControllerTest, VideoRestrictionsToSinkWants) { | 
|  | MockVideoSinkWithVideoFrame sink; | 
|  | MockVideoSourceWithVideoFrame source; | 
|  | VideoSourceSinkController controller(&sink, &source); | 
|  |  | 
|  | VideoSourceRestrictions restrictions = controller.restrictions(); | 
|  | // max_pixels_per_frame() maps to `max_pixel_count`. | 
|  | restrictions.set_max_pixels_per_frame(42u); | 
|  | // target_pixels_per_frame() maps to `target_pixel_count`. | 
|  | restrictions.set_target_pixels_per_frame(200u); | 
|  | // max_frame_rate() maps to `max_framerate_fps`. | 
|  | restrictions.set_max_frame_rate(30.0); | 
|  | controller.SetRestrictions(restrictions); | 
|  | EXPECT_CALL(source, AddOrUpdateSink(_, _)) | 
|  | .WillOnce([](rtc::VideoSinkInterface<VideoFrame>* sink, | 
|  | const rtc::VideoSinkWants& wants) { | 
|  | EXPECT_EQ(wants.max_pixel_count, 42); | 
|  | EXPECT_EQ(wants.target_pixel_count, 200); | 
|  | EXPECT_EQ(wants.max_framerate_fps, 30); | 
|  | }); | 
|  | controller.PushSourceSinkSettings(); | 
|  |  | 
|  | // pixels_per_frame_upper_limit() caps `max_pixel_count`. | 
|  | controller.SetPixelsPerFrameUpperLimit(24); | 
|  | // frame_rate_upper_limit() caps `max_framerate_fps`. | 
|  | controller.SetFrameRateUpperLimit(10.0); | 
|  |  | 
|  | EXPECT_CALL(source, AddOrUpdateSink(_, _)) | 
|  | .WillOnce([](rtc::VideoSinkInterface<VideoFrame>* sink, | 
|  | const rtc::VideoSinkWants& wants) { | 
|  | EXPECT_EQ(wants.max_pixel_count, 24); | 
|  | EXPECT_EQ(wants.max_framerate_fps, 10); | 
|  | }); | 
|  | controller.PushSourceSinkSettings(); | 
|  | } | 
|  |  | 
|  | TEST(VideoSourceSinkControllerTest, RotationApplied) { | 
|  | MockVideoSinkWithVideoFrame sink; | 
|  | MockVideoSourceWithVideoFrame source; | 
|  | VideoSourceSinkController controller(&sink, &source); | 
|  | controller.SetRotationApplied(true); | 
|  | EXPECT_TRUE(controller.rotation_applied()); | 
|  |  | 
|  | EXPECT_CALL(source, AddOrUpdateSink(_, _)) | 
|  | .WillOnce([](rtc::VideoSinkInterface<VideoFrame>* sink, | 
|  | const rtc::VideoSinkWants& wants) { | 
|  | EXPECT_TRUE(wants.rotation_applied); | 
|  | }); | 
|  | controller.PushSourceSinkSettings(); | 
|  | } | 
|  |  | 
|  | TEST(VideoSourceSinkControllerTest, ResolutionAlignment) { | 
|  | MockVideoSinkWithVideoFrame sink; | 
|  | MockVideoSourceWithVideoFrame source; | 
|  | VideoSourceSinkController controller(&sink, &source); | 
|  | controller.SetResolutionAlignment(13); | 
|  | EXPECT_EQ(controller.resolution_alignment(), 13); | 
|  |  | 
|  | EXPECT_CALL(source, AddOrUpdateSink(_, _)) | 
|  | .WillOnce([](rtc::VideoSinkInterface<VideoFrame>* sink, | 
|  | const rtc::VideoSinkWants& wants) { | 
|  | EXPECT_EQ(wants.resolution_alignment, 13); | 
|  | }); | 
|  | controller.PushSourceSinkSettings(); | 
|  | } | 
|  |  | 
|  | TEST(VideoSourceSinkControllerTest, | 
|  | PushSourceSinkSettingsWithoutSourceDoesNotCrash) { | 
|  | MockVideoSinkWithVideoFrame sink; | 
|  | VideoSourceSinkController controller(&sink, nullptr); | 
|  | controller.PushSourceSinkSettings(); | 
|  | } | 
|  |  | 
|  | TEST(VideoSourceSinkControllerTest, RequestsRefreshFrameWithSource) { | 
|  | MockVideoSinkWithVideoFrame sink; | 
|  | MockVideoSourceWithVideoFrame source; | 
|  | VideoSourceSinkController controller(&sink, &source); | 
|  | EXPECT_CALL(source, RequestRefreshFrame); | 
|  | controller.RequestRefreshFrame(); | 
|  | } | 
|  |  | 
|  | TEST(VideoSourceSinkControllerTest, | 
|  | RequestsRefreshFrameWithoutSourceDoesNotCrash) { | 
|  | MockVideoSinkWithVideoFrame sink; | 
|  | VideoSourceSinkController controller(&sink, nullptr); | 
|  | controller.RequestRefreshFrame(); | 
|  | } | 
|  |  | 
|  | TEST(VideoSourceSinkControllerTest, ScaleResolutionDownToPropagatesToWants) { | 
|  | MockVideoSinkWithVideoFrame sink; | 
|  | MockVideoSourceWithVideoFrame source; | 
|  | VideoSourceSinkController controller(&sink, &source); | 
|  | controller.SetScaleResolutionDownTo(FrameSize(640, 360)); | 
|  | EXPECT_TRUE(controller.scale_resolution_down_to().has_value()); | 
|  |  | 
|  | EXPECT_CALL(source, AddOrUpdateSink(_, _)) | 
|  | .WillOnce([](rtc::VideoSinkInterface<VideoFrame>* sink, | 
|  | const rtc::VideoSinkWants& wants) { | 
|  | EXPECT_EQ(*wants.requested_resolution, FrameSize(640, 360)); | 
|  | }); | 
|  | controller.PushSourceSinkSettings(); | 
|  | } | 
|  |  | 
|  | TEST(VideoSourceSinkControllerTest, ActivePropagatesToWants) { | 
|  | MockVideoSinkWithVideoFrame sink; | 
|  | MockVideoSourceWithVideoFrame source; | 
|  | VideoSourceSinkController controller(&sink, &source); | 
|  | controller.SetActive(true); | 
|  | EXPECT_TRUE(controller.active()); | 
|  |  | 
|  | EXPECT_CALL(source, AddOrUpdateSink(_, _)) | 
|  | .WillOnce([](rtc::VideoSinkInterface<VideoFrame>* sink, | 
|  | const rtc::VideoSinkWants& wants) { | 
|  | EXPECT_TRUE(wants.is_active); | 
|  | }); | 
|  | controller.PushSourceSinkSettings(); | 
|  | } | 
|  |  | 
|  | }  // namespace webrtc |