Markus Handell | 9c27ed2 | 2019-12-04 11:57:58 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2019 The WebRTC project authors. All Rights Reserved. |
| 3 | * |
| 4 | * Use of this source code is governed by a BSD-style license |
| 5 | * that can be found in the LICENSE file in the root of the source |
| 6 | * tree. An additional intellectual property rights grant can be found |
| 7 | * in the file PATENTS. All contributing project authors may |
| 8 | * be found in the AUTHORS file in the root of the source tree. |
| 9 | */ |
| 10 | |
| 11 | #include "pc/video_rtp_receiver.h" |
| 12 | |
Harald Alvestrand | c24a218 | 2022-02-23 13:44:59 | [diff] [blame] | 13 | #include <functional> |
Markus Handell | 9c27ed2 | 2019-12-04 11:57:58 | [diff] [blame] | 14 | #include <memory> |
| 15 | |
Harald Alvestrand | c24a218 | 2022-02-23 13:44:59 | [diff] [blame] | 16 | #include "api/task_queue/task_queue_base.h" |
| 17 | #include "api/video/recordable_encoded_frame.h" |
Markus Handell | 9c27ed2 | 2019-12-04 11:57:58 | [diff] [blame] | 18 | #include "api/video/test/mock_recordable_encoded_frame.h" |
| 19 | #include "media/base/fake_media_engine.h" |
Florent Castelli | 1f31c20 | 2023-06-26 00:26:07 | [diff] [blame] | 20 | #include "media/base/media_channel.h" |
Danil Chapovalov | 2aaef45 | 2022-08-12 13:55:11 | [diff] [blame] | 21 | #include "rtc_base/task_queue_for_test.h" |
Markus Handell | 9c27ed2 | 2019-12-04 11:57:58 | [diff] [blame] | 22 | #include "test/gmock.h" |
Harald Alvestrand | c24a218 | 2022-02-23 13:44:59 | [diff] [blame] | 23 | #include "test/gtest.h" |
Markus Handell | 9c27ed2 | 2019-12-04 11:57:58 | [diff] [blame] | 24 | |
| 25 | using ::testing::_; |
Tommi | 4ccdf932 | 2021-05-17 12:50:10 | [diff] [blame] | 26 | using ::testing::AnyNumber; |
Markus Handell | 9c27ed2 | 2019-12-04 11:57:58 | [diff] [blame] | 27 | using ::testing::InSequence; |
| 28 | using ::testing::Mock; |
Tommi | 4ccdf932 | 2021-05-17 12:50:10 | [diff] [blame] | 29 | using ::testing::NiceMock; |
Markus Handell | 9c27ed2 | 2019-12-04 11:57:58 | [diff] [blame] | 30 | using ::testing::SaveArg; |
| 31 | using ::testing::StrictMock; |
| 32 | |
| 33 | namespace webrtc { |
| 34 | namespace { |
| 35 | |
| 36 | class VideoRtpReceiverTest : public testing::Test { |
| 37 | protected: |
Florent Castelli | 1f31c20 | 2023-06-26 00:26:07 | [diff] [blame] | 38 | class MockVideoMediaSendChannel : public cricket::FakeVideoMediaSendChannel { |
Markus Handell | 9c27ed2 | 2019-12-04 11:57:58 | [diff] [blame] | 39 | public: |
Florent Castelli | 1f31c20 | 2023-06-26 00:26:07 | [diff] [blame] | 40 | MockVideoMediaSendChannel( |
Tommi | c9625f0 | 2021-05-06 20:03:19 | [diff] [blame] | 41 | const cricket::VideoOptions& options, |
| 42 | TaskQueueBase* network_thread = rtc::Thread::Current()) |
Florent Castelli | 1f31c20 | 2023-06-26 00:26:07 | [diff] [blame] | 43 | : FakeVideoMediaSendChannel(options, network_thread) {} |
| 44 | MOCK_METHOD(void, |
| 45 | GenerateSendKeyFrame, |
| 46 | (uint32_t, const std::vector<std::string>&), |
| 47 | (override)); |
| 48 | }; |
| 49 | |
| 50 | class MockVideoMediaReceiveChannel |
| 51 | : public cricket::FakeVideoMediaReceiveChannel { |
| 52 | public: |
| 53 | MockVideoMediaReceiveChannel( |
| 54 | const cricket::VideoOptions& options, |
| 55 | TaskQueueBase* network_thread = rtc::Thread::Current()) |
| 56 | : FakeVideoMediaReceiveChannel(options, network_thread) {} |
Danil Chapovalov | 3a35312 | 2020-05-15 09:16:53 | [diff] [blame] | 57 | MOCK_METHOD(void, |
| 58 | SetRecordableEncodedFrameCallback, |
| 59 | (uint32_t, std::function<void(const RecordableEncodedFrame&)>), |
| 60 | (override)); |
| 61 | MOCK_METHOD(void, |
| 62 | ClearRecordableEncodedFrameCallback, |
| 63 | (uint32_t), |
| 64 | (override)); |
Philipp Hancke | d237c2b | 2022-10-25 07:54:28 | [diff] [blame] | 65 | MOCK_METHOD(void, RequestRecvKeyFrame, (uint32_t), (override)); |
Markus Handell | 9c27ed2 | 2019-12-04 11:57:58 | [diff] [blame] | 66 | }; |
| 67 | |
| 68 | class MockVideoSink : public rtc::VideoSinkInterface<RecordableEncodedFrame> { |
| 69 | public: |
Danil Chapovalov | 3a35312 | 2020-05-15 09:16:53 | [diff] [blame] | 70 | MOCK_METHOD(void, OnFrame, (const RecordableEncodedFrame&), (override)); |
Markus Handell | 9c27ed2 | 2019-12-04 11:57:58 | [diff] [blame] | 71 | }; |
| 72 | |
| 73 | VideoRtpReceiverTest() |
| 74 | : worker_thread_(rtc::Thread::Create()), |
Florent Castelli | 1f31c20 | 2023-06-26 00:26:07 | [diff] [blame] | 75 | channel_(cricket::VideoOptions()), |
Tommi | 4ccdf932 | 2021-05-17 12:50:10 | [diff] [blame] | 76 | receiver_(rtc::make_ref_counted<VideoRtpReceiver>( |
| 77 | worker_thread_.get(), |
| 78 | std::string("receiver"), |
| 79 | std::vector<std::string>({"stream"}))) { |
Markus Handell | 9c27ed2 | 2019-12-04 11:57:58 | [diff] [blame] | 80 | worker_thread_->Start(); |
Tommi | 6589def | 2022-02-17 22:36:47 | [diff] [blame] | 81 | SetMediaChannel(&channel_); |
Markus Handell | 9c27ed2 | 2019-12-04 11:57:58 | [diff] [blame] | 82 | } |
| 83 | |
Tommi | 4ccdf932 | 2021-05-17 12:50:10 | [diff] [blame] | 84 | ~VideoRtpReceiverTest() override { |
Tommi | 6589def | 2022-02-17 22:36:47 | [diff] [blame] | 85 | // Clear expectations that tests may have set up before calling |
| 86 | // SetMediaChannel(nullptr). |
Tommi | 4ccdf932 | 2021-05-17 12:50:10 | [diff] [blame] | 87 | Mock::VerifyAndClearExpectations(&channel_); |
| 88 | receiver_->Stop(); |
Tommi | 6589def | 2022-02-17 22:36:47 | [diff] [blame] | 89 | SetMediaChannel(nullptr); |
| 90 | } |
| 91 | |
Florent Castelli | 1f31c20 | 2023-06-26 00:26:07 | [diff] [blame] | 92 | void SetMediaChannel(cricket::MediaReceiveChannelInterface* media_channel) { |
Danil Chapovalov | 2aaef45 | 2022-08-12 13:55:11 | [diff] [blame] | 93 | SendTask(worker_thread_.get(), |
| 94 | [&]() { receiver_->SetMediaChannel(media_channel); }); |
Tommi | 4ccdf932 | 2021-05-17 12:50:10 | [diff] [blame] | 95 | } |
| 96 | |
Harald Alvestrand | a654437 | 2023-11-13 09:33:56 | [diff] [blame] | 97 | VideoTrackSourceInterface* Source() { |
Markus Handell | 9c27ed2 | 2019-12-04 11:57:58 | [diff] [blame] | 98 | return receiver_->streams()[0]->FindVideoTrack("receiver")->GetSource(); |
| 99 | } |
| 100 | |
Niels Möller | 83830f3 | 2022-05-20 07:12:57 | [diff] [blame] | 101 | rtc::AutoThread main_thread_; |
Markus Handell | 9c27ed2 | 2019-12-04 11:57:58 | [diff] [blame] | 102 | std::unique_ptr<rtc::Thread> worker_thread_; |
Florent Castelli | 1f31c20 | 2023-06-26 00:26:07 | [diff] [blame] | 103 | NiceMock<MockVideoMediaReceiveChannel> channel_; |
Markus Handell | 9c27ed2 | 2019-12-04 11:57:58 | [diff] [blame] | 104 | rtc::scoped_refptr<VideoRtpReceiver> receiver_; |
| 105 | }; |
| 106 | |
| 107 | TEST_F(VideoRtpReceiverTest, SupportsEncodedOutput) { |
| 108 | EXPECT_TRUE(Source()->SupportsEncodedOutput()); |
| 109 | } |
| 110 | |
| 111 | TEST_F(VideoRtpReceiverTest, GeneratesKeyFrame) { |
Philipp Hancke | d237c2b | 2022-10-25 07:54:28 | [diff] [blame] | 112 | EXPECT_CALL(channel_, RequestRecvKeyFrame(0)); |
Markus Handell | 9c27ed2 | 2019-12-04 11:57:58 | [diff] [blame] | 113 | Source()->GenerateKeyFrame(); |
| 114 | } |
| 115 | |
| 116 | TEST_F(VideoRtpReceiverTest, |
| 117 | GenerateKeyFrameOnChannelSwitchUnlessGenerateKeyframeCalled) { |
| 118 | // A channel switch without previous call to GenerateKeyFrame shouldn't |
| 119 | // cause a call to happen on the new channel. |
Florent Castelli | 1f31c20 | 2023-06-26 00:26:07 | [diff] [blame] | 120 | MockVideoMediaReceiveChannel channel2{cricket::VideoOptions()}; |
Philipp Hancke | d237c2b | 2022-10-25 07:54:28 | [diff] [blame] | 121 | EXPECT_CALL(channel_, RequestRecvKeyFrame).Times(0); |
| 122 | EXPECT_CALL(channel2, RequestRecvKeyFrame).Times(0); |
Tommi | 6589def | 2022-02-17 22:36:47 | [diff] [blame] | 123 | SetMediaChannel(&channel2); |
Markus Handell | 9c27ed2 | 2019-12-04 11:57:58 | [diff] [blame] | 124 | Mock::VerifyAndClearExpectations(&channel2); |
| 125 | |
| 126 | // Generate a key frame. When we switch channel next time, we will have to |
| 127 | // re-generate it as we don't know if it was eventually received |
Philipp Hancke | d237c2b | 2022-10-25 07:54:28 | [diff] [blame] | 128 | EXPECT_CALL(channel2, RequestRecvKeyFrame).Times(1); |
Markus Handell | 9c27ed2 | 2019-12-04 11:57:58 | [diff] [blame] | 129 | Source()->GenerateKeyFrame(); |
Florent Castelli | 1f31c20 | 2023-06-26 00:26:07 | [diff] [blame] | 130 | MockVideoMediaReceiveChannel channel3{cricket::VideoOptions()}; |
Philipp Hancke | d237c2b | 2022-10-25 07:54:28 | [diff] [blame] | 131 | EXPECT_CALL(channel3, RequestRecvKeyFrame); |
Tommi | 6589def | 2022-02-17 22:36:47 | [diff] [blame] | 132 | SetMediaChannel(&channel3); |
Markus Handell | 9c27ed2 | 2019-12-04 11:57:58 | [diff] [blame] | 133 | |
| 134 | // Switching to a new channel should now not cause calls to GenerateKeyFrame. |
Florent Castelli | 1f31c20 | 2023-06-26 00:26:07 | [diff] [blame] | 135 | StrictMock<MockVideoMediaReceiveChannel> channel4{cricket::VideoOptions()}; |
Tommi | 6589def | 2022-02-17 22:36:47 | [diff] [blame] | 136 | SetMediaChannel(&channel4); |
Tommi | 4ccdf932 | 2021-05-17 12:50:10 | [diff] [blame] | 137 | |
Tommi | 6589def | 2022-02-17 22:36:47 | [diff] [blame] | 138 | // We must call SetMediaChannel(nullptr) here since the mock media channels |
| 139 | // live on the stack and `receiver_` still has a pointer to those objects. |
| 140 | SetMediaChannel(nullptr); |
Markus Handell | 9c27ed2 | 2019-12-04 11:57:58 | [diff] [blame] | 141 | } |
| 142 | |
| 143 | TEST_F(VideoRtpReceiverTest, EnablesEncodedOutput) { |
| 144 | EXPECT_CALL(channel_, SetRecordableEncodedFrameCallback(/*ssrc=*/0, _)); |
| 145 | EXPECT_CALL(channel_, ClearRecordableEncodedFrameCallback).Times(0); |
| 146 | MockVideoSink sink; |
| 147 | Source()->AddEncodedSink(&sink); |
| 148 | } |
| 149 | |
| 150 | TEST_F(VideoRtpReceiverTest, DisablesEncodedOutput) { |
| 151 | EXPECT_CALL(channel_, ClearRecordableEncodedFrameCallback(/*ssrc=*/0)); |
| 152 | MockVideoSink sink; |
| 153 | Source()->AddEncodedSink(&sink); |
| 154 | Source()->RemoveEncodedSink(&sink); |
| 155 | } |
| 156 | |
| 157 | TEST_F(VideoRtpReceiverTest, DisablesEnablesEncodedOutputOnChannelSwitch) { |
| 158 | InSequence s; |
| 159 | EXPECT_CALL(channel_, SetRecordableEncodedFrameCallback); |
| 160 | EXPECT_CALL(channel_, ClearRecordableEncodedFrameCallback); |
| 161 | MockVideoSink sink; |
| 162 | Source()->AddEncodedSink(&sink); |
Florent Castelli | 1f31c20 | 2023-06-26 00:26:07 | [diff] [blame] | 163 | MockVideoMediaReceiveChannel channel2{cricket::VideoOptions()}; |
Markus Handell | 9c27ed2 | 2019-12-04 11:57:58 | [diff] [blame] | 164 | EXPECT_CALL(channel2, SetRecordableEncodedFrameCallback); |
Tommi | 6589def | 2022-02-17 22:36:47 | [diff] [blame] | 165 | SetMediaChannel(&channel2); |
Markus Handell | 9c27ed2 | 2019-12-04 11:57:58 | [diff] [blame] | 166 | Mock::VerifyAndClearExpectations(&channel2); |
| 167 | |
| 168 | // When clearing encoded frame buffer function, we need channel switches |
| 169 | // to NOT set the callback again. |
| 170 | EXPECT_CALL(channel2, ClearRecordableEncodedFrameCallback); |
| 171 | Source()->RemoveEncodedSink(&sink); |
Florent Castelli | 1f31c20 | 2023-06-26 00:26:07 | [diff] [blame] | 172 | StrictMock<MockVideoMediaReceiveChannel> channel3{cricket::VideoOptions()}; |
Tommi | 6589def | 2022-02-17 22:36:47 | [diff] [blame] | 173 | SetMediaChannel(&channel3); |
Tommi | 4ccdf932 | 2021-05-17 12:50:10 | [diff] [blame] | 174 | |
Tommi | 6589def | 2022-02-17 22:36:47 | [diff] [blame] | 175 | // We must call SetMediaChannel(nullptr) here since the mock media channels |
| 176 | // live on the stack and `receiver_` still has a pointer to those objects. |
| 177 | SetMediaChannel(nullptr); |
Markus Handell | 9c27ed2 | 2019-12-04 11:57:58 | [diff] [blame] | 178 | } |
| 179 | |
| 180 | TEST_F(VideoRtpReceiverTest, BroadcastsEncodedFramesWhenEnabled) { |
| 181 | std::function<void(const RecordableEncodedFrame&)> broadcast; |
| 182 | EXPECT_CALL(channel_, SetRecordableEncodedFrameCallback(_, _)) |
| 183 | .WillRepeatedly(SaveArg<1>(&broadcast)); |
| 184 | MockVideoSink sink; |
| 185 | Source()->AddEncodedSink(&sink); |
| 186 | |
| 187 | // Make sure SetEncodedFrameBufferFunction completes. |
| 188 | Mock::VerifyAndClearExpectations(&channel_); |
| 189 | |
| 190 | // Pass two frames on different contexts. |
| 191 | EXPECT_CALL(sink, OnFrame).Times(2); |
| 192 | MockRecordableEncodedFrame frame; |
| 193 | broadcast(frame); |
Danil Chapovalov | 2aaef45 | 2022-08-12 13:55:11 | [diff] [blame] | 194 | SendTask(worker_thread_.get(), [&] { broadcast(frame); }); |
Markus Handell | 9c27ed2 | 2019-12-04 11:57:58 | [diff] [blame] | 195 | } |
| 196 | |
| 197 | TEST_F(VideoRtpReceiverTest, EnablesEncodedOutputOnChannelRestart) { |
| 198 | InSequence s; |
Markus Handell | 9c27ed2 | 2019-12-04 11:57:58 | [diff] [blame] | 199 | MockVideoSink sink; |
| 200 | Source()->AddEncodedSink(&sink); |
| 201 | EXPECT_CALL(channel_, SetRecordableEncodedFrameCallback(4711, _)); |
| 202 | receiver_->SetupMediaChannel(4711); |
| 203 | EXPECT_CALL(channel_, ClearRecordableEncodedFrameCallback(4711)); |
| 204 | EXPECT_CALL(channel_, SetRecordableEncodedFrameCallback(0, _)); |
| 205 | receiver_->SetupUnsignaledMediaChannel(); |
| 206 | } |
| 207 | |
| 208 | } // namespace |
| 209 | } // namespace webrtc |