| /* | |
| * 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 <optional> | |
| #include "api/video/video_frame.h" | |
| #include "api/video/video_sink_interface.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 = VideoSinkWants::FrameSize; | |
| constexpr int kIntUnconstrained = std::numeric_limits<int>::max(); | |
| class MockVideoSinkWithVideoFrame : public VideoSinkInterface<VideoFrame> { | |
| public: | |
| ~MockVideoSinkWithVideoFrame() override {} | |
| MOCK_METHOD(void, OnFrame, (const VideoFrame& frame), (override)); | |
| MOCK_METHOD(void, OnDiscardedFrame, (), (override)); | |
| }; | |
| class MockVideoSourceWithVideoFrame : public VideoSourceInterface<VideoFrame> { | |
| public: | |
| ~MockVideoSourceWithVideoFrame() override {} | |
| MOCK_METHOD(void, | |
| AddOrUpdateSink, | |
| (VideoSinkInterface<VideoFrame>*, const VideoSinkWants&), | |
| (override)); | |
| MOCK_METHOD(void, RemoveSink, (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([](VideoSinkInterface<VideoFrame>* sink, | |
| const 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([](VideoSinkInterface<VideoFrame>* sink, | |
| const 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([](VideoSinkInterface<VideoFrame>* sink, | |
| const 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([](VideoSinkInterface<VideoFrame>* sink, | |
| const 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([](VideoSinkInterface<VideoFrame>* sink, | |
| const 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([](VideoSinkInterface<VideoFrame>* sink, | |
| const 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( | |
| [](VideoSinkInterface<VideoFrame>* sink, | |
| const VideoSinkWants& wants) { EXPECT_TRUE(wants.is_active); }); | |
| controller.PushSourceSinkSettings(); | |
| } | |
| } // namespace webrtc |