blob: af31fe2c13fe7c682d41ab655810e7aa5d6e5575 [file] [log] [blame]
Sergey Silkind4311562023-11-13 14:48:251/*
2 * Copyright (c) 2022 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 "test/video_codec_tester.h"
12
13#include <map>
14#include <memory>
15#include <string>
16#include <tuple>
17#include <utility>
18#include <vector>
19
20#include "api/test/mock_video_decoder.h"
21#include "api/test/mock_video_decoder_factory.h"
22#include "api/test/mock_video_encoder.h"
23#include "api/test/mock_video_encoder_factory.h"
24#include "api/units/data_rate.h"
25#include "api/units/time_delta.h"
26#include "api/video/i420_buffer.h"
27#include "api/video/video_frame.h"
28#include "modules/video_coding/include/video_codec_interface.h"
29#include "modules/video_coding/svc/scalability_mode_util.h"
30#include "test/gmock.h"
31#include "test/gtest.h"
32#include "test/testsupport/file_utils.h"
33#include "third_party/libyuv/include/libyuv/planar_functions.h"
34
35namespace webrtc {
36namespace test {
37
38namespace {
39using ::testing::_;
40using ::testing::ElementsAre;
41using ::testing::Field;
42using ::testing::Invoke;
43using ::testing::InvokeWithoutArgs;
44using ::testing::NiceMock;
45using ::testing::Return;
46using ::testing::SizeIs;
47
48using VideoCodecStats = VideoCodecTester::VideoCodecStats;
49using VideoSourceSettings = VideoCodecTester::VideoSourceSettings;
50using CodedVideoSource = VideoCodecTester::CodedVideoSource;
51using EncodingSettings = VideoCodecTester::EncodingSettings;
52using LayerSettings = EncodingSettings::LayerSettings;
53using LayerId = VideoCodecTester::LayerId;
54using DecoderSettings = VideoCodecTester::DecoderSettings;
55using EncoderSettings = VideoCodecTester::EncoderSettings;
56using PacingSettings = VideoCodecTester::PacingSettings;
57using PacingMode = PacingSettings::PacingMode;
58using Filter = VideoCodecStats::Filter;
59using Frame = VideoCodecTester::VideoCodecStats::Frame;
60using Stream = VideoCodecTester::VideoCodecStats::Stream;
61
62constexpr int kWidth = 2;
63constexpr int kHeight = 2;
64const DataRate kTargetLayerBitrate = DataRate::BytesPerSec(100);
65const Frequency kTargetFramerate = Frequency::Hertz(30);
66constexpr Frequency k90kHz = Frequency::Hertz(90000);
67
68rtc::scoped_refptr<I420Buffer> CreateYuvBuffer(uint8_t y = 0,
69 uint8_t u = 0,
70 uint8_t v = 0) {
71 rtc::scoped_refptr<I420Buffer> buffer(I420Buffer::Create(2, 2));
72
73 libyuv::I420Rect(buffer->MutableDataY(), buffer->StrideY(),
74 buffer->MutableDataU(), buffer->StrideU(),
75 buffer->MutableDataV(), buffer->StrideV(), 0, 0,
76 buffer->width(), buffer->height(), y, u, v);
77 return buffer;
78}
79
80std::string CreateYuvFile(int width, int height, int num_frames) {
81 std::string path = webrtc::test::TempFilename(webrtc::test::OutputPath(),
82 "video_codec_tester_unittest");
83 FILE* file = fopen(path.c_str(), "wb");
84 for (int frame_num = 0; frame_num < num_frames; ++frame_num) {
85 uint8_t y = (frame_num + 0) & 255;
86 uint8_t u = (frame_num + 1) & 255;
87 uint8_t v = (frame_num + 2) & 255;
88 rtc::scoped_refptr<I420Buffer> buffer = CreateYuvBuffer(y, u, v);
89 fwrite(buffer->DataY(), 1, width * height, file);
90 int chroma_size_bytes = (width + 1) / 2 * (height + 1) / 2;
91 fwrite(buffer->DataU(), 1, chroma_size_bytes, file);
92 fwrite(buffer->DataV(), 1, chroma_size_bytes, file);
93 }
94 fclose(file);
95 return path;
96}
97
98std::unique_ptr<VideoCodecStats> RunTest(std::vector<std::vector<Frame>> frames,
99 ScalabilityMode scalability_mode) {
100 int num_frames = static_cast<int>(frames.size());
101 std::string source_yuv_path = CreateYuvFile(kWidth, kHeight, num_frames);
102 VideoSourceSettings source_settings{
103 .file_path = source_yuv_path,
104 .resolution = {.width = kWidth, .height = kHeight},
105 .framerate = kTargetFramerate};
106
107 int num_encoded_frames = 0;
108 EncodedImageCallback* encoded_frame_callback;
109 NiceMock<MockVideoEncoderFactory> encoder_factory;
110 ON_CALL(encoder_factory, CreateVideoEncoder)
111 .WillByDefault([&](const SdpVideoFormat&) {
112 auto encoder = std::make_unique<NiceMock<MockVideoEncoder>>();
113 ON_CALL(*encoder, RegisterEncodeCompleteCallback)
114 .WillByDefault([&](EncodedImageCallback* callback) {
115 encoded_frame_callback = callback;
116 return WEBRTC_VIDEO_CODEC_OK;
117 });
118 ON_CALL(*encoder, Encode)
119 .WillByDefault([&](const VideoFrame& input_frame,
120 const std::vector<VideoFrameType>*) {
121 for (const Frame& frame : frames[num_encoded_frames]) {
122 EncodedImage encoded_frame;
123 encoded_frame._encodedWidth = frame.width;
124 encoded_frame._encodedHeight = frame.height;
125 encoded_frame.SetFrameType(
126 frame.keyframe ? VideoFrameType::kVideoFrameKey
127 : VideoFrameType::kVideoFrameDelta);
128 encoded_frame.SetRtpTimestamp(input_frame.timestamp());
129 encoded_frame.SetSpatialIndex(frame.layer_id.spatial_idx);
130 encoded_frame.SetTemporalIndex(frame.layer_id.temporal_idx);
131 encoded_frame.SetEncodedData(
132 EncodedImageBuffer::Create(frame.frame_size.bytes()));
133 encoded_frame_callback->OnEncodedImage(
134 encoded_frame,
135 /*codec_specific_info=*/nullptr);
136 }
137 ++num_encoded_frames;
138 return WEBRTC_VIDEO_CODEC_OK;
139 });
140 return encoder;
141 });
142
143 int num_decoded_frames = 0;
144 DecodedImageCallback* decode_callback;
145 NiceMock<MockVideoDecoderFactory> decoder_factory;
146 ON_CALL(decoder_factory, CreateVideoDecoder)
147 .WillByDefault([&](const SdpVideoFormat&) {
148 auto decoder = std::make_unique<NiceMock<MockVideoDecoder>>();
149 ON_CALL(*decoder, RegisterDecodeCompleteCallback)
150 .WillByDefault([&](DecodedImageCallback* callback) {
151 decode_callback = callback;
152 return WEBRTC_VIDEO_CODEC_OK;
153 });
154 ON_CALL(*decoder, Decode(_, _))
155 .WillByDefault([&](const EncodedImage& encoded_frame, int64_t) {
156 // Make values to be different from source YUV generated in
157 // `CreateYuvFile`.
158 uint8_t y = ((num_decoded_frames + 1) * 2) & 255;
159 uint8_t u = ((num_decoded_frames + 2) * 2) & 255;
160 uint8_t v = ((num_decoded_frames + 3) * 2) & 255;
161 rtc::scoped_refptr<I420Buffer> frame_buffer =
162 CreateYuvBuffer(y, u, v);
163 VideoFrame decoded_frame =
164 VideoFrame::Builder()
165 .set_video_frame_buffer(frame_buffer)
166 .set_timestamp_rtp(encoded_frame.RtpTimestamp())
167 .build();
168 decode_callback->Decoded(decoded_frame);
169 ++num_decoded_frames;
170 return WEBRTC_VIDEO_CODEC_OK;
171 });
172 return decoder;
173 });
174
175 int num_spatial_layers = ScalabilityModeToNumSpatialLayers(scalability_mode);
176 int num_temporal_layers =
177 ScalabilityModeToNumTemporalLayers(scalability_mode);
178
179 std::map<uint32_t, EncodingSettings> encoding_settings;
180 for (int frame_num = 0; frame_num < num_frames; ++frame_num) {
181 std::map<LayerId, LayerSettings> layers_settings;
182 for (int sidx = 0; sidx < num_spatial_layers; ++sidx) {
183 for (int tidx = 0; tidx < num_temporal_layers; ++tidx) {
184 layers_settings.emplace(
185 LayerId{.spatial_idx = sidx, .temporal_idx = tidx},
186 LayerSettings{.resolution = {.width = kWidth, .height = kHeight},
187 .framerate = kTargetFramerate /
188 (1 << (num_temporal_layers - 1 - tidx)),
189 .bitrate = kTargetLayerBitrate});
190 }
191 }
192 encoding_settings.emplace(
193 frames[frame_num][0].timestamp_rtp,
194 EncodingSettings{.scalability_mode = scalability_mode,
195 .layers_settings = layers_settings});
196 }
197
198 EncoderSettings encoder_settings;
199 DecoderSettings decoder_settings;
200 std::unique_ptr<VideoCodecStats> stats =
201 VideoCodecTester::RunEncodeDecodeTest(
202 source_settings, &encoder_factory, &decoder_factory, encoder_settings,
203 decoder_settings, encoding_settings);
204 remove(source_yuv_path.c_str());
205 return stats;
206}
207
208EncodedImage CreateEncodedImage(uint32_t timestamp_rtp) {
209 EncodedImage encoded_image;
210 encoded_image.SetRtpTimestamp(timestamp_rtp);
211 return encoded_image;
212}
213
214class MockCodedVideoSource : public CodedVideoSource {
215 public:
216 MockCodedVideoSource(int num_frames, Frequency framerate)
217 : num_frames_(num_frames), frame_num_(0), framerate_(framerate) {}
218
219 absl::optional<EncodedImage> PullFrame() override {
220 if (frame_num_ >= num_frames_) {
221 return absl::nullopt;
222 }
223 uint32_t timestamp_rtp = frame_num_ * k90kHz / framerate_;
224 ++frame_num_;
225 return CreateEncodedImage(timestamp_rtp);
226 }
227
228 private:
229 int num_frames_;
230 int frame_num_;
231 Frequency framerate_;
232};
233
234} // namespace
235
236TEST(VideoCodecTester, Slice) {
237 std::unique_ptr<VideoCodecStats> stats = RunTest(
238 {{{.timestamp_rtp = 0, .layer_id = {.spatial_idx = 0, .temporal_idx = 0}},
239 {.timestamp_rtp = 0,
240 .layer_id = {.spatial_idx = 1, .temporal_idx = 0}}},
241 {{.timestamp_rtp = 1,
242 .layer_id = {.spatial_idx = 0, .temporal_idx = 1}}}},
243 ScalabilityMode::kL2T2);
244 std::vector<Frame> slice = stats->Slice(Filter{}, /*merge=*/false);
245 EXPECT_THAT(slice, ElementsAre(Field(&Frame::timestamp_rtp, 0),
246 Field(&Frame::timestamp_rtp, 0),
247 Field(&Frame::timestamp_rtp, 1)));
248
249 slice = stats->Slice({.min_timestamp_rtp = 1}, /*merge=*/false);
250 EXPECT_THAT(slice, ElementsAre(Field(&Frame::timestamp_rtp, 1)));
251
252 slice = stats->Slice({.max_timestamp_rtp = 0}, /*merge=*/false);
253 EXPECT_THAT(slice, ElementsAre(Field(&Frame::timestamp_rtp, 0),
254 Field(&Frame::timestamp_rtp, 0)));
255
256 slice = stats->Slice({.layer_id = {{.spatial_idx = 0, .temporal_idx = 0}}},
257 /*merge=*/false);
258 EXPECT_THAT(slice, ElementsAre(Field(&Frame::timestamp_rtp, 0)));
259
260 slice = stats->Slice({.layer_id = {{.spatial_idx = 0, .temporal_idx = 1}}},
261 /*merge=*/false);
262 EXPECT_THAT(slice, ElementsAre(Field(&Frame::timestamp_rtp, 0),
263 Field(&Frame::timestamp_rtp, 1)));
264}
265
266TEST(VideoCodecTester, Merge) {
267 std::unique_ptr<VideoCodecStats> stats =
268 RunTest({{{.timestamp_rtp = 0,
269 .layer_id = {.spatial_idx = 0, .temporal_idx = 0},
270 .frame_size = DataSize::Bytes(1),
271 .keyframe = true},
272 {.timestamp_rtp = 0,
273 .layer_id = {.spatial_idx = 1, .temporal_idx = 0},
274 .frame_size = DataSize::Bytes(2)}},
275 {{.timestamp_rtp = 1,
276 .layer_id = {.spatial_idx = 0, .temporal_idx = 1},
277 .frame_size = DataSize::Bytes(4)},
278 {.timestamp_rtp = 1,
279 .layer_id = {.spatial_idx = 1, .temporal_idx = 1},
280 .frame_size = DataSize::Bytes(8)}}},
281 ScalabilityMode::kL2T2_KEY);
282
283 std::vector<Frame> slice = stats->Slice(Filter{}, /*merge=*/true);
284 EXPECT_THAT(
285 slice,
286 ElementsAre(
287 AllOf(Field(&Frame::timestamp_rtp, 0), Field(&Frame::keyframe, true),
288 Field(&Frame::frame_size, DataSize::Bytes(3))),
289 AllOf(Field(&Frame::timestamp_rtp, 1), Field(&Frame::keyframe, false),
290 Field(&Frame::frame_size, DataSize::Bytes(12)))));
291}
292
293struct AggregationTestParameters {
294 Filter filter;
295 double expected_keyframe_sum;
296 double expected_encoded_bitrate_kbps;
297 double expected_encoded_framerate_fps;
298 double expected_bitrate_mismatch_pct;
299 double expected_framerate_mismatch_pct;
300};
301
302class VideoCodecTesterTestAggregation
303 : public ::testing::TestWithParam<AggregationTestParameters> {};
304
305TEST_P(VideoCodecTesterTestAggregation, Aggregate) {
306 AggregationTestParameters test_params = GetParam();
307 std::unique_ptr<VideoCodecStats> stats =
308 RunTest({{// L0T0
309 {.timestamp_rtp = 0,
310 .layer_id = {.spatial_idx = 0, .temporal_idx = 0},
311 .frame_size = DataSize::Bytes(1),
312 .keyframe = true},
313 // L1T0
314 {.timestamp_rtp = 0,
315 .layer_id = {.spatial_idx = 1, .temporal_idx = 0},
316 .frame_size = DataSize::Bytes(2)}},
317 // Emulate frame drop (frame_size = 0).
318 {{.timestamp_rtp = 3000,
319 .layer_id = {.spatial_idx = 0, .temporal_idx = 0},
320 .frame_size = DataSize::Zero()}},
321 {// L0T1
322 {.timestamp_rtp = 87000,
323 .layer_id = {.spatial_idx = 0, .temporal_idx = 1},
324 .frame_size = DataSize::Bytes(4)},
325 // L1T1
326 {.timestamp_rtp = 87000,
327 .layer_id = {.spatial_idx = 1, .temporal_idx = 1},
328 .frame_size = DataSize::Bytes(8)}}},
329 ScalabilityMode::kL2T2_KEY);
330
331 Stream stream = stats->Aggregate(test_params.filter);
332 EXPECT_EQ(stream.keyframe.GetSum(), test_params.expected_keyframe_sum);
333 EXPECT_EQ(stream.encoded_bitrate_kbps.GetAverage(),
334 test_params.expected_encoded_bitrate_kbps);
335 EXPECT_EQ(stream.encoded_framerate_fps.GetAverage(),
336 test_params.expected_encoded_framerate_fps);
337 EXPECT_EQ(stream.bitrate_mismatch_pct.GetAverage(),
338 test_params.expected_bitrate_mismatch_pct);
339 EXPECT_EQ(stream.framerate_mismatch_pct.GetAverage(),
340 test_params.expected_framerate_mismatch_pct);
341}
342
343INSTANTIATE_TEST_SUITE_P(
344 All,
345 VideoCodecTesterTestAggregation,
346 ::testing::Values(
347 // No filtering.
348 AggregationTestParameters{
349 .filter = {},
350 .expected_keyframe_sum = 1,
351 .expected_encoded_bitrate_kbps =
352 DataRate::BytesPerSec(15).kbps<double>(),
353 .expected_encoded_framerate_fps = 2,
354 .expected_bitrate_mismatch_pct =
355 100 * (15.0 / (kTargetLayerBitrate.bytes_per_sec() * 4) - 1),
356 .expected_framerate_mismatch_pct =
357 100 * (2.0 / kTargetFramerate.hertz() - 1)},
358 // L0T0
359 AggregationTestParameters{
360 .filter = {.layer_id = {{.spatial_idx = 0, .temporal_idx = 0}}},
361 .expected_keyframe_sum = 1,
362 .expected_encoded_bitrate_kbps =
363 DataRate::BytesPerSec(1).kbps<double>(),
364 .expected_encoded_framerate_fps = 1,
365 .expected_bitrate_mismatch_pct =
366 100 * (1.0 / kTargetLayerBitrate.bytes_per_sec() - 1),
367 .expected_framerate_mismatch_pct =
368 100 * (1.0 / (kTargetFramerate.hertz() / 2) - 1)},
369 // L0T1
370 AggregationTestParameters{
371 .filter = {.layer_id = {{.spatial_idx = 0, .temporal_idx = 1}}},
372 .expected_keyframe_sum = 1,
373 .expected_encoded_bitrate_kbps =
374 DataRate::BytesPerSec(5).kbps<double>(),
375 .expected_encoded_framerate_fps = 2,
376 .expected_bitrate_mismatch_pct =
377 100 * (5.0 / (kTargetLayerBitrate.bytes_per_sec() * 2) - 1),
378 .expected_framerate_mismatch_pct =
379 100 * (2.0 / kTargetFramerate.hertz() - 1)},
380 // L1T0
381 AggregationTestParameters{
382 .filter = {.layer_id = {{.spatial_idx = 1, .temporal_idx = 0}}},
383 .expected_keyframe_sum = 1,
384 .expected_encoded_bitrate_kbps =
385 DataRate::BytesPerSec(3).kbps<double>(),
386 .expected_encoded_framerate_fps = 1,
387 .expected_bitrate_mismatch_pct =
388 100 * (3.0 / kTargetLayerBitrate.bytes_per_sec() - 1),
389 .expected_framerate_mismatch_pct =
390 100 * (1.0 / (kTargetFramerate.hertz() / 2) - 1)},
391 // L1T1
392 AggregationTestParameters{
393 .filter = {.layer_id = {{.spatial_idx = 1, .temporal_idx = 1}}},
394 .expected_keyframe_sum = 1,
395 .expected_encoded_bitrate_kbps =
396 DataRate::BytesPerSec(11).kbps<double>(),
397 .expected_encoded_framerate_fps = 2,
398 .expected_bitrate_mismatch_pct =
399 100 * (11.0 / (kTargetLayerBitrate.bytes_per_sec() * 2) - 1),
400 .expected_framerate_mismatch_pct =
401 100 * (2.0 / kTargetFramerate.hertz() - 1)}));
402
403TEST(VideoCodecTester, Psnr) {
404 std::unique_ptr<VideoCodecStats> stats =
405 RunTest({{{.timestamp_rtp = 0, .frame_size = DataSize::Bytes(1)}},
406 {{.timestamp_rtp = 3000, .frame_size = DataSize::Bytes(1)}}},
407 ScalabilityMode::kL1T1);
408
409 std::vector<Frame> slice = stats->Slice(Filter{}, /*merge=*/false);
410 ASSERT_THAT(slice, SizeIs(2));
411 ASSERT_TRUE(slice[0].psnr.has_value());
412 ASSERT_TRUE(slice[1].psnr.has_value());
413 EXPECT_NEAR(slice[0].psnr->y, 42, 1);
414 EXPECT_NEAR(slice[0].psnr->u, 38, 1);
415 EXPECT_NEAR(slice[0].psnr->v, 36, 1);
416 EXPECT_NEAR(slice[1].psnr->y, 38, 1);
417 EXPECT_NEAR(slice[1].psnr->u, 36, 1);
418 EXPECT_NEAR(slice[1].psnr->v, 34, 1);
419}
420
421class VideoCodecTesterTestPacing
422 : public ::testing::TestWithParam<std::tuple<PacingSettings, int>> {
423 public:
424 const int kSourceWidth = 2;
425 const int kSourceHeight = 2;
426 const int kNumFrames = 3;
427 const int kTargetLayerBitrateKbps = 128;
428 const Frequency kTargetFramerate = Frequency::Hertz(10);
429
430 void SetUp() override {
431 source_yuv_file_path_ = webrtc::test::TempFilename(
432 webrtc::test::OutputPath(), "video_codec_tester_impl_unittest");
433 FILE* file = fopen(source_yuv_file_path_.c_str(), "wb");
434 for (int i = 0; i < 3 * kSourceWidth * kSourceHeight / 2; ++i) {
435 fwrite("x", 1, 1, file);
436 }
437 fclose(file);
438 }
439
440 protected:
441 std::string source_yuv_file_path_;
442};
443
444TEST_P(VideoCodecTesterTestPacing, PaceEncode) {
445 auto [pacing_settings, expected_delta_ms] = GetParam();
446 VideoSourceSettings video_source{
447 .file_path = source_yuv_file_path_,
448 .resolution = {.width = kSourceWidth, .height = kSourceHeight},
449 .framerate = kTargetFramerate};
450
451 NiceMock<MockVideoEncoderFactory> encoder_factory;
452 ON_CALL(encoder_factory, CreateVideoEncoder(_))
453 .WillByDefault([](const SdpVideoFormat&) {
454 return std::make_unique<NiceMock<MockVideoEncoder>>();
455 });
456
457 std::map<uint32_t, EncodingSettings> encoding_settings =
458 VideoCodecTester::CreateEncodingSettings(
459 "VP8", "L1T1", kSourceWidth, kSourceHeight, {kTargetLayerBitrateKbps},
460 kTargetFramerate.hertz(), kNumFrames);
461
462 EncoderSettings encoder_settings;
463 encoder_settings.pacing_settings = pacing_settings;
464 std::vector<Frame> frames =
465 VideoCodecTester::RunEncodeTest(video_source, &encoder_factory,
466 encoder_settings, encoding_settings)
467 ->Slice(/*filter=*/{}, /*merge=*/false);
468 ASSERT_THAT(frames, SizeIs(kNumFrames));
469 EXPECT_NEAR((frames[1].encode_start - frames[0].encode_start).ms(),
470 expected_delta_ms, 10);
471 EXPECT_NEAR((frames[2].encode_start - frames[1].encode_start).ms(),
472 expected_delta_ms, 10);
473}
474
475TEST_P(VideoCodecTesterTestPacing, PaceDecode) {
476 auto [pacing_settings, expected_delta_ms] = GetParam();
477 MockCodedVideoSource video_source(kNumFrames, kTargetFramerate);
478
479 NiceMock<MockVideoDecoderFactory> decoder_factory;
480 ON_CALL(decoder_factory, CreateVideoDecoder(_))
481 .WillByDefault([](const SdpVideoFormat&) {
482 return std::make_unique<NiceMock<MockVideoDecoder>>();
483 });
484
485 DecoderSettings decoder_settings;
486 decoder_settings.pacing_settings = pacing_settings;
487 std::vector<Frame> frames =
488 VideoCodecTester::RunDecodeTest(&video_source, &decoder_factory,
489 decoder_settings, SdpVideoFormat("VP8"))
490 ->Slice(/*filter=*/{}, /*merge=*/false);
491 ASSERT_THAT(frames, SizeIs(kNumFrames));
492 EXPECT_NEAR((frames[1].decode_start - frames[0].decode_start).ms(),
493 expected_delta_ms, 10);
494 EXPECT_NEAR((frames[2].decode_start - frames[1].decode_start).ms(),
495 expected_delta_ms, 10);
496}
497
498INSTANTIATE_TEST_SUITE_P(
499 DISABLED_All,
500 VideoCodecTesterTestPacing,
501 ::testing::Values(
502 // No pacing.
503 std::make_tuple(PacingSettings{.mode = PacingMode::kNoPacing},
504 /*expected_delta_ms=*/0),
505 // Real-time pacing.
506 std::make_tuple(PacingSettings{.mode = PacingMode::kRealTime},
507 /*expected_delta_ms=*/100),
508 // Pace with specified constant rate.
509 std::make_tuple(PacingSettings{.mode = PacingMode::kConstantRate,
510 .constant_rate = Frequency::Hertz(20)},
511 /*expected_delta_ms=*/50)));
512} // namespace test
513} // namespace webrtc