Sebastian Jansson | 5fbebd5 | 2019-02-20 11:16:19 +0100 | [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 | #include <atomic> |
| 11 | |
Andrey Logvin | f9ee0e0 | 2021-01-14 09:50:32 +0000 | [diff] [blame] | 12 | #include "api/test/network_emulation/create_cross_traffic.h" |
| 13 | #include "api/test/network_emulation/cross_traffic.h" |
Erik Språng | 1d50cb6 | 2020-07-02 17:41:32 +0200 | [diff] [blame] | 14 | #include "test/field_trial.h" |
Sebastian Jansson | 5fbebd5 | 2019-02-20 11:16:19 +0100 | [diff] [blame] | 15 | #include "test/gtest.h" |
| 16 | #include "test/scenario/scenario.h" |
| 17 | |
| 18 | namespace webrtc { |
| 19 | namespace test { |
| 20 | namespace { |
Sebastian Jansson | d37307c | 2019-02-22 17:07:49 +0100 | [diff] [blame] | 21 | using Capture = VideoStreamConfig::Source::Capture; |
| 22 | using ContentType = VideoStreamConfig::Encoder::ContentType; |
Sebastian Jansson | 5fbebd5 | 2019-02-20 11:16:19 +0100 | [diff] [blame] | 23 | using Codec = VideoStreamConfig::Encoder::Codec; |
| 24 | using CodecImpl = VideoStreamConfig::Encoder::Implementation; |
| 25 | } // namespace |
| 26 | |
Sebastian Jansson | 342f98b | 2019-06-18 16:08:23 +0200 | [diff] [blame] | 27 | TEST(VideoStreamTest, ReceivesFramesFromFileBasedStreams) { |
Danil Chapovalov | 0c626af | 2020-02-10 11:16:00 +0100 | [diff] [blame] | 28 | TimeDelta kRunTime = TimeDelta::Millis(500); |
Sebastian Jansson | d37307c | 2019-02-22 17:07:49 +0100 | [diff] [blame] | 29 | std::vector<int> kFrameRates = {15, 30}; |
| 30 | std::deque<std::atomic<int>> frame_counts(2); |
| 31 | frame_counts[0] = 0; |
| 32 | frame_counts[1] = 0; |
| 33 | { |
| 34 | Scenario s; |
Sebastian Jansson | ef86d14 | 2019-04-15 14:42:42 +0200 | [diff] [blame] | 35 | auto route = |
| 36 | s.CreateRoutes(s.CreateClient("caller", CallClientConfig()), |
| 37 | {s.CreateSimulationNode(NetworkSimulationConfig())}, |
| 38 | s.CreateClient("callee", CallClientConfig()), |
| 39 | {s.CreateSimulationNode(NetworkSimulationConfig())}); |
Sebastian Jansson | d37307c | 2019-02-22 17:07:49 +0100 | [diff] [blame] | 40 | |
| 41 | s.CreateVideoStream(route->forward(), [&](VideoStreamConfig* c) { |
Sebastian Jansson | cf2df2f | 2019-04-02 11:51:28 +0200 | [diff] [blame] | 42 | c->hooks.frame_pair_handlers = { |
| 43 | [&](const VideoFramePair&) { frame_counts[0]++; }}; |
Sebastian Jansson | d37307c | 2019-02-22 17:07:49 +0100 | [diff] [blame] | 44 | c->source.capture = Capture::kVideoFile; |
| 45 | c->source.video_file.name = "foreman_cif"; |
| 46 | c->source.video_file.width = 352; |
| 47 | c->source.video_file.height = 288; |
| 48 | c->source.framerate = kFrameRates[0]; |
| 49 | c->encoder.implementation = CodecImpl::kSoftware; |
| 50 | c->encoder.codec = Codec::kVideoCodecVP8; |
| 51 | }); |
| 52 | s.CreateVideoStream(route->forward(), [&](VideoStreamConfig* c) { |
Sebastian Jansson | cf2df2f | 2019-04-02 11:51:28 +0200 | [diff] [blame] | 53 | c->hooks.frame_pair_handlers = { |
| 54 | [&](const VideoFramePair&) { frame_counts[1]++; }}; |
Sebastian Jansson | d37307c | 2019-02-22 17:07:49 +0100 | [diff] [blame] | 55 | c->source.capture = Capture::kImageSlides; |
| 56 | c->source.slides.images.crop.width = 320; |
| 57 | c->source.slides.images.crop.height = 240; |
| 58 | c->source.framerate = kFrameRates[1]; |
| 59 | c->encoder.implementation = CodecImpl::kSoftware; |
| 60 | c->encoder.codec = Codec::kVideoCodecVP9; |
| 61 | }); |
| 62 | s.RunFor(kRunTime); |
| 63 | } |
| 64 | std::vector<int> expected_counts; |
| 65 | for (int fps : kFrameRates) |
| 66 | expected_counts.push_back( |
| 67 | static_cast<int>(kRunTime.seconds<double>() * fps * 0.8)); |
| 68 | |
| 69 | EXPECT_GE(frame_counts[0], expected_counts[0]); |
| 70 | EXPECT_GE(frame_counts[1], expected_counts[1]); |
| 71 | } |
| 72 | |
Niels Möller | be74b80 | 2022-03-18 14:10:15 +0100 | [diff] [blame] | 73 | TEST(VideoStreamTest, ReceivesVp8SimulcastFrames) { |
Danil Chapovalov | 0c626af | 2020-02-10 11:16:00 +0100 | [diff] [blame] | 74 | TimeDelta kRunTime = TimeDelta::Millis(500); |
Sebastian Jansson | 5fbebd5 | 2019-02-20 11:16:19 +0100 | [diff] [blame] | 75 | int kFrameRate = 30; |
| 76 | |
Sebastian Jansson | cf2df2f | 2019-04-02 11:51:28 +0200 | [diff] [blame] | 77 | std::deque<std::atomic<int>> frame_counts(3); |
| 78 | frame_counts[0] = 0; |
| 79 | frame_counts[1] = 0; |
| 80 | frame_counts[2] = 0; |
Sebastian Jansson | 5fbebd5 | 2019-02-20 11:16:19 +0100 | [diff] [blame] | 81 | { |
| 82 | Scenario s; |
Sebastian Jansson | ef86d14 | 2019-04-15 14:42:42 +0200 | [diff] [blame] | 83 | auto route = |
| 84 | s.CreateRoutes(s.CreateClient("caller", CallClientConfig()), |
| 85 | {s.CreateSimulationNode(NetworkSimulationConfig())}, |
| 86 | s.CreateClient("callee", CallClientConfig()), |
| 87 | {s.CreateSimulationNode(NetworkSimulationConfig())}); |
Sebastian Jansson | 5fbebd5 | 2019-02-20 11:16:19 +0100 | [diff] [blame] | 88 | s.CreateVideoStream(route->forward(), [&](VideoStreamConfig* c) { |
| 89 | // TODO(srte): Replace with code checking for all simulcast streams when |
| 90 | // there's a hook available for that. |
Sebastian Jansson | cf2df2f | 2019-04-02 11:51:28 +0200 | [diff] [blame] | 91 | c->hooks.frame_pair_handlers = {[&](const VideoFramePair& info) { |
| 92 | frame_counts[info.layer_id]++; |
| 93 | RTC_DCHECK(info.decoded); |
| 94 | printf("%i: [%3i->%3i, %i], %i->%i, \n", info.layer_id, info.capture_id, |
| 95 | info.decode_id, info.repeated, info.captured->width(), |
| 96 | info.decoded->width()); |
| 97 | }}; |
Sebastian Jansson | 5fbebd5 | 2019-02-20 11:16:19 +0100 | [diff] [blame] | 98 | c->source.framerate = kFrameRate; |
| 99 | // The resolution must be high enough to allow smaller layers to be |
| 100 | // created. |
| 101 | c->source.generator.width = 1024; |
| 102 | c->source.generator.height = 768; |
Sebastian Jansson | 5fbebd5 | 2019-02-20 11:16:19 +0100 | [diff] [blame] | 103 | c->encoder.implementation = CodecImpl::kSoftware; |
| 104 | c->encoder.codec = Codec::kVideoCodecVP8; |
| 105 | // By enabling multiple spatial layers, simulcast will be enabled for VP8. |
| 106 | c->encoder.layers.spatial = 3; |
| 107 | }); |
| 108 | s.RunFor(kRunTime); |
| 109 | } |
| 110 | |
Sebastian Jansson | cf2df2f | 2019-04-02 11:51:28 +0200 | [diff] [blame] | 111 | // Using high error margin to avoid flakyness. |
Sebastian Jansson | 5fbebd5 | 2019-02-20 11:16:19 +0100 | [diff] [blame] | 112 | const int kExpectedCount = |
Sebastian Jansson | cf2df2f | 2019-04-02 11:51:28 +0200 | [diff] [blame] | 113 | static_cast<int>(kRunTime.seconds<double>() * kFrameRate * 0.5); |
Sebastian Jansson | 5fbebd5 | 2019-02-20 11:16:19 +0100 | [diff] [blame] | 114 | |
Sebastian Jansson | cf2df2f | 2019-04-02 11:51:28 +0200 | [diff] [blame] | 115 | EXPECT_GE(frame_counts[0], kExpectedCount); |
| 116 | EXPECT_GE(frame_counts[1], kExpectedCount); |
| 117 | EXPECT_GE(frame_counts[2], kExpectedCount); |
Sebastian Jansson | 5fbebd5 | 2019-02-20 11:16:19 +0100 | [diff] [blame] | 118 | } |
Sebastian Jansson | 342f98b | 2019-06-18 16:08:23 +0200 | [diff] [blame] | 119 | |
Sebastian Jansson | c57b0ee | 2019-06-26 16:22:39 +0200 | [diff] [blame] | 120 | TEST(VideoStreamTest, SendsNacksOnLoss) { |
| 121 | Scenario s; |
| 122 | auto route = |
| 123 | s.CreateRoutes(s.CreateClient("caller", CallClientConfig()), |
| 124 | {s.CreateSimulationNode([](NetworkSimulationConfig* c) { |
| 125 | c->loss_rate = 0.2; |
| 126 | })}, |
| 127 | s.CreateClient("callee", CallClientConfig()), |
| 128 | {s.CreateSimulationNode(NetworkSimulationConfig())}); |
| 129 | // NACK retransmissions are enabled by default. |
| 130 | auto video = s.CreateVideoStream(route->forward(), VideoStreamConfig()); |
Danil Chapovalov | 0c626af | 2020-02-10 11:16:00 +0100 | [diff] [blame] | 131 | s.RunFor(TimeDelta::Seconds(1)); |
Sebastian Jansson | 3e66a49 | 2020-01-14 12:30:13 +0100 | [diff] [blame] | 132 | int retransmit_packets = 0; |
Tomas Gunnarsson | 788d805 | 2021-05-03 16:23:08 +0200 | [diff] [blame] | 133 | VideoSendStream::Stats stats; |
| 134 | route->first()->SendTask([&]() { stats = video->send()->GetStats(); }); |
| 135 | for (const auto& substream : stats.substreams) { |
Sebastian Jansson | 3e66a49 | 2020-01-14 12:30:13 +0100 | [diff] [blame] | 136 | retransmit_packets += substream.second.rtp_stats.retransmitted.packets; |
| 137 | } |
| 138 | EXPECT_GT(retransmit_packets, 0); |
Sebastian Jansson | c57b0ee | 2019-06-26 16:22:39 +0200 | [diff] [blame] | 139 | } |
| 140 | |
Sebastian Jansson | 342f98b | 2019-06-18 16:08:23 +0200 | [diff] [blame] | 141 | TEST(VideoStreamTest, SendsFecWithUlpFec) { |
| 142 | Scenario s; |
| 143 | auto route = |
| 144 | s.CreateRoutes(s.CreateClient("caller", CallClientConfig()), |
| 145 | {s.CreateSimulationNode([](NetworkSimulationConfig* c) { |
| 146 | c->loss_rate = 0.1; |
Danil Chapovalov | 0c626af | 2020-02-10 11:16:00 +0100 | [diff] [blame] | 147 | c->delay = TimeDelta::Millis(100); |
Sebastian Jansson | 342f98b | 2019-06-18 16:08:23 +0200 | [diff] [blame] | 148 | })}, |
| 149 | s.CreateClient("callee", CallClientConfig()), |
| 150 | {s.CreateSimulationNode(NetworkSimulationConfig())}); |
| 151 | auto video = s.CreateVideoStream(route->forward(), [&](VideoStreamConfig* c) { |
Sebastian Jansson | c57b0ee | 2019-06-26 16:22:39 +0200 | [diff] [blame] | 152 | // We do not allow NACK+ULPFEC for generic codec, using VP8. |
| 153 | c->encoder.codec = VideoStreamConfig::Encoder::Codec::kVideoCodecVP8; |
Sebastian Jansson | 342f98b | 2019-06-18 16:08:23 +0200 | [diff] [blame] | 154 | c->stream.use_ulpfec = true; |
| 155 | }); |
Danil Chapovalov | 0c626af | 2020-02-10 11:16:00 +0100 | [diff] [blame] | 156 | s.RunFor(TimeDelta::Seconds(5)); |
Tomas Gunnarsson | 788d805 | 2021-05-03 16:23:08 +0200 | [diff] [blame] | 157 | VideoSendStream::Stats video_stats; |
| 158 | route->first()->SendTask([&]() { video_stats = video->send()->GetStats(); }); |
Sebastian Jansson | 342f98b | 2019-06-18 16:08:23 +0200 | [diff] [blame] | 159 | EXPECT_GT(video_stats.substreams.begin()->second.rtp_stats.fec.packets, 0u); |
| 160 | } |
| 161 | TEST(VideoStreamTest, SendsFecWithFlexFec) { |
| 162 | Scenario s; |
| 163 | auto route = |
| 164 | s.CreateRoutes(s.CreateClient("caller", CallClientConfig()), |
| 165 | {s.CreateSimulationNode([](NetworkSimulationConfig* c) { |
| 166 | c->loss_rate = 0.1; |
Danil Chapovalov | 0c626af | 2020-02-10 11:16:00 +0100 | [diff] [blame] | 167 | c->delay = TimeDelta::Millis(100); |
Sebastian Jansson | 342f98b | 2019-06-18 16:08:23 +0200 | [diff] [blame] | 168 | })}, |
| 169 | s.CreateClient("callee", CallClientConfig()), |
| 170 | {s.CreateSimulationNode(NetworkSimulationConfig())}); |
| 171 | auto video = s.CreateVideoStream(route->forward(), [&](VideoStreamConfig* c) { |
| 172 | c->stream.use_flexfec = true; |
| 173 | }); |
Danil Chapovalov | 0c626af | 2020-02-10 11:16:00 +0100 | [diff] [blame] | 174 | s.RunFor(TimeDelta::Seconds(5)); |
Tomas Gunnarsson | 788d805 | 2021-05-03 16:23:08 +0200 | [diff] [blame] | 175 | VideoSendStream::Stats video_stats; |
| 176 | route->first()->SendTask([&]() { video_stats = video->send()->GetStats(); }); |
Sebastian Jansson | 342f98b | 2019-06-18 16:08:23 +0200 | [diff] [blame] | 177 | EXPECT_GT(video_stats.substreams.begin()->second.rtp_stats.fec.packets, 0u); |
| 178 | } |
Erik Språng | 576db1b | 2020-06-08 13:32:20 +0200 | [diff] [blame] | 179 | |
| 180 | TEST(VideoStreamTest, ResolutionAdaptsToAvailableBandwidth) { |
| 181 | // Declared before scenario to avoid use after free. |
| 182 | std::atomic<size_t> num_qvga_frames_(0); |
| 183 | std::atomic<size_t> num_vga_frames_(0); |
| 184 | |
| 185 | Scenario s; |
| 186 | // Link has enough capacity for VGA. |
| 187 | NetworkSimulationConfig net_conf; |
| 188 | net_conf.bandwidth = DataRate::KilobitsPerSec(800); |
| 189 | net_conf.delay = TimeDelta::Millis(50); |
| 190 | auto* client = s.CreateClient("send", [&](CallClientConfig* c) { |
| 191 | c->transport.rates.start_rate = DataRate::KilobitsPerSec(800); |
| 192 | }); |
| 193 | auto send_net = {s.CreateSimulationNode(net_conf)}; |
| 194 | auto ret_net = {s.CreateSimulationNode(net_conf)}; |
| 195 | auto* route = s.CreateRoutes( |
| 196 | client, send_net, s.CreateClient("return", CallClientConfig()), ret_net); |
| 197 | |
| 198 | s.CreateVideoStream(route->forward(), [&](VideoStreamConfig* c) { |
| 199 | c->hooks.frame_pair_handlers = {[&](const VideoFramePair& info) { |
| 200 | if (info.decoded->width() == 640) { |
| 201 | ++num_vga_frames_; |
| 202 | } else if (info.decoded->width() == 320) { |
| 203 | ++num_qvga_frames_; |
| 204 | } else { |
| 205 | ADD_FAILURE() << "Unexpected resolution: " << info.decoded->width(); |
| 206 | } |
| 207 | }}; |
| 208 | c->source.framerate = 30; |
| 209 | // The resolution must be high enough to allow smaller layers to be |
| 210 | // created. |
| 211 | c->source.generator.width = 640; |
| 212 | c->source.generator.height = 480; |
| 213 | c->encoder.implementation = CodecImpl::kSoftware; |
| 214 | c->encoder.codec = Codec::kVideoCodecVP9; |
| 215 | // Enable SVC. |
| 216 | c->encoder.layers.spatial = 2; |
| 217 | }); |
| 218 | |
| 219 | // Run for a few seconds, until streams have stabilized, |
| 220 | // check that we are sending VGA. |
| 221 | s.RunFor(TimeDelta::Seconds(5)); |
| 222 | EXPECT_GT(num_vga_frames_, 0u); |
| 223 | |
| 224 | // Trigger cross traffic, run until we have seen 3 consecutive |
| 225 | // seconds with no VGA frames due to reduced available bandwidth. |
Andrey Logvin | f9ee0e0 | 2021-01-14 09:50:32 +0000 | [diff] [blame] | 226 | auto cross_traffic = s.net()->StartCrossTraffic(CreateFakeTcpCrossTraffic( |
| 227 | s.net()->CreateRoute(send_net), s.net()->CreateRoute(ret_net), |
| 228 | FakeTcpConfig())); |
Erik Språng | 576db1b | 2020-06-08 13:32:20 +0200 | [diff] [blame] | 229 | |
| 230 | int num_seconds_without_vga = 0; |
| 231 | int num_iterations = 0; |
| 232 | do { |
| 233 | ASSERT_LE(++num_iterations, 100); |
| 234 | num_qvga_frames_ = 0; |
| 235 | num_vga_frames_ = 0; |
| 236 | s.RunFor(TimeDelta::Seconds(1)); |
| 237 | if (num_qvga_frames_ > 0 && num_vga_frames_ == 0) { |
| 238 | ++num_seconds_without_vga; |
| 239 | } else { |
| 240 | num_seconds_without_vga = 0; |
| 241 | } |
| 242 | } while (num_seconds_without_vga < 3); |
| 243 | |
| 244 | // Stop cross traffic, make sure we recover and get VGA frames agian. |
| 245 | s.net()->StopCrossTraffic(cross_traffic); |
| 246 | num_qvga_frames_ = 0; |
| 247 | num_vga_frames_ = 0; |
| 248 | |
| 249 | s.RunFor(TimeDelta::Seconds(40)); |
| 250 | EXPECT_GT(num_qvga_frames_, 0u); |
| 251 | EXPECT_GT(num_vga_frames_, 0u); |
| 252 | } |
| 253 | |
Erik Språng | 279f370 | 2020-10-13 21:55:07 +0200 | [diff] [blame] | 254 | TEST(VideoStreamTest, SuspendsBelowMinBitrate) { |
| 255 | const DataRate kMinVideoBitrate = DataRate::KilobitsPerSec(30); |
| 256 | |
| 257 | // Declared before scenario to avoid use after free. |
| 258 | std::atomic<Timestamp> last_frame_timestamp(Timestamp::MinusInfinity()); |
| 259 | |
| 260 | Scenario s; |
| 261 | NetworkSimulationConfig net_config; |
| 262 | net_config.bandwidth = kMinVideoBitrate * 4; |
| 263 | net_config.delay = TimeDelta::Millis(10); |
| 264 | auto* client = s.CreateClient("send", [&](CallClientConfig* c) { |
| 265 | // Min transmit rate needs to be lower than kMinVideoBitrate for this test |
| 266 | // to make sense. |
| 267 | c->transport.rates.min_rate = kMinVideoBitrate / 2; |
| 268 | c->transport.rates.start_rate = kMinVideoBitrate; |
| 269 | c->transport.rates.max_rate = kMinVideoBitrate * 2; |
| 270 | }); |
| 271 | auto send_net = s.CreateMutableSimulationNode( |
| 272 | [&](NetworkSimulationConfig* c) { *c = net_config; }); |
| 273 | auto ret_net = {s.CreateSimulationNode(net_config)}; |
| 274 | auto* route = |
| 275 | s.CreateRoutes(client, {send_net->node()}, |
| 276 | s.CreateClient("return", CallClientConfig()), ret_net); |
| 277 | |
| 278 | s.CreateVideoStream(route->forward(), [&](VideoStreamConfig* c) { |
| 279 | c->hooks.frame_pair_handlers = {[&](const VideoFramePair& pair) { |
| 280 | if (pair.repeated == 0) { |
| 281 | last_frame_timestamp = pair.capture_time; |
| 282 | } |
| 283 | }}; |
| 284 | c->source.framerate = 30; |
| 285 | c->source.generator.width = 320; |
| 286 | c->source.generator.height = 180; |
| 287 | c->encoder.implementation = CodecImpl::kFake; |
| 288 | c->encoder.codec = Codec::kVideoCodecVP8; |
| 289 | c->encoder.min_data_rate = kMinVideoBitrate; |
| 290 | c->encoder.suspend_below_min_bitrate = true; |
| 291 | c->stream.pad_to_rate = kMinVideoBitrate; |
| 292 | }); |
| 293 | |
| 294 | // Run for a few seconds, check we have received at least one frame. |
| 295 | s.RunFor(TimeDelta::Seconds(2)); |
| 296 | EXPECT_TRUE(last_frame_timestamp.load().IsFinite()); |
| 297 | |
| 298 | // Degrade network to below min bitrate. |
| 299 | send_net->UpdateConfig([&](NetworkSimulationConfig* c) { |
| 300 | c->bandwidth = kMinVideoBitrate * 0.9; |
| 301 | }); |
| 302 | |
| 303 | // Run for 20s, verify that no frames arrive that were captured after the |
| 304 | // first five seconds, allowing some margin for BWE backoff to trigger and |
| 305 | // packets already in the pipeline to potentially arrive. |
| 306 | s.RunFor(TimeDelta::Seconds(20)); |
| 307 | EXPECT_GT(s.Now() - last_frame_timestamp, TimeDelta::Seconds(15)); |
| 308 | |
| 309 | // Relax the network constraints and run for a while more, verify that we |
| 310 | // start receiving frames again. |
| 311 | send_net->UpdateConfig( |
| 312 | [&](NetworkSimulationConfig* c) { c->bandwidth = kMinVideoBitrate * 4; }); |
| 313 | last_frame_timestamp = Timestamp::MinusInfinity(); |
| 314 | s.RunFor(TimeDelta::Seconds(15)); |
| 315 | EXPECT_TRUE(last_frame_timestamp.load().IsFinite()); |
| 316 | } |
| 317 | |
Sebastian Jansson | 5fbebd5 | 2019-02-20 11:16:19 +0100 | [diff] [blame] | 318 | } // namespace test |
| 319 | } // namespace webrtc |