summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/video/picture_id_tests.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /third_party/libwebrtc/video/picture_id_tests.cc
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/video/picture_id_tests.cc')
-rw-r--r--third_party/libwebrtc/video/picture_id_tests.cc428
1 files changed, 428 insertions, 0 deletions
diff --git a/third_party/libwebrtc/video/picture_id_tests.cc b/third_party/libwebrtc/video/picture_id_tests.cc
new file mode 100644
index 0000000000..06491b924a
--- /dev/null
+++ b/third_party/libwebrtc/video/picture_id_tests.cc
@@ -0,0 +1,428 @@
+/*
+ * Copyright (c) 2013 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 <memory>
+
+#include "api/test/simulated_network.h"
+#include "api/test/video/function_video_encoder_factory.h"
+#include "call/fake_network_pipe.h"
+#include "call/simulated_network.h"
+#include "media/engine/internal_encoder_factory.h"
+#include "media/engine/simulcast_encoder_adapter.h"
+#include "modules/rtp_rtcp/source/create_video_rtp_depacketizer.h"
+#include "modules/rtp_rtcp/source/rtp_packet.h"
+#include "modules/video_coding/codecs/vp8/include/vp8.h"
+#include "modules/video_coding/codecs/vp9/include/vp9.h"
+#include "rtc_base/numerics/safe_conversions.h"
+#include "rtc_base/numerics/sequence_number_util.h"
+#include "rtc_base/synchronization/mutex.h"
+#include "rtc_base/task_queue_for_test.h"
+#include "test/call_test.h"
+
+namespace webrtc {
+namespace {
+const int kFrameMaxWidth = 1280;
+const int kFrameMaxHeight = 720;
+const int kFrameRate = 30;
+const int kMaxSecondsLost = 5;
+const int kMaxFramesLost = kFrameRate * kMaxSecondsLost;
+const int kMinPacketsToObserve = 10;
+const int kEncoderBitrateBps = 300000;
+const uint32_t kPictureIdWraparound = (1 << 15);
+const size_t kNumTemporalLayers[] = {1, 2, 3};
+
+} // namespace
+
+class PictureIdObserver : public test::RtpRtcpObserver {
+ public:
+ explicit PictureIdObserver(VideoCodecType codec_type)
+ : test::RtpRtcpObserver(test::CallTest::kDefaultTimeout),
+ depacketizer_(CreateVideoRtpDepacketizer(codec_type)),
+ max_expected_picture_id_gap_(0),
+ max_expected_tl0_idx_gap_(0),
+ num_ssrcs_to_observe_(1) {}
+
+ void SetExpectedSsrcs(size_t num_expected_ssrcs) {
+ MutexLock lock(&mutex_);
+ num_ssrcs_to_observe_ = num_expected_ssrcs;
+ }
+
+ void ResetObservedSsrcs() {
+ MutexLock lock(&mutex_);
+ // Do not clear the timestamp and picture_id, to ensure that we check
+ // consistency between reinits and recreations.
+ num_packets_sent_.clear();
+ observed_ssrcs_.clear();
+ }
+
+ void SetMaxExpectedPictureIdGap(int max_expected_picture_id_gap) {
+ MutexLock lock(&mutex_);
+ max_expected_picture_id_gap_ = max_expected_picture_id_gap;
+ // Expect smaller gap for `tl0_pic_idx` (running index for temporal_idx 0).
+ max_expected_tl0_idx_gap_ = max_expected_picture_id_gap_ / 2;
+ }
+
+ private:
+ struct ParsedPacket {
+ uint32_t timestamp;
+ uint32_t ssrc;
+ int16_t picture_id;
+ int16_t tl0_pic_idx;
+ uint8_t temporal_idx;
+ VideoFrameType frame_type;
+ };
+
+ bool ParsePayload(const uint8_t* packet,
+ size_t length,
+ ParsedPacket* parsed) const {
+ RtpPacket rtp_packet;
+ EXPECT_TRUE(rtp_packet.Parse(packet, length));
+ EXPECT_TRUE(rtp_packet.Ssrc() == test::CallTest::kVideoSendSsrcs[0] ||
+ rtp_packet.Ssrc() == test::CallTest::kVideoSendSsrcs[1] ||
+ rtp_packet.Ssrc() == test::CallTest::kVideoSendSsrcs[2])
+ << "Unknown SSRC sent.";
+
+ if (rtp_packet.payload_size() == 0) {
+ return false; // Padding packet.
+ }
+
+ parsed->timestamp = rtp_packet.Timestamp();
+ parsed->ssrc = rtp_packet.Ssrc();
+
+ absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed_payload =
+ depacketizer_->Parse(rtp_packet.PayloadBuffer());
+ EXPECT_TRUE(parsed_payload);
+
+ if (const auto* vp8_header = absl::get_if<RTPVideoHeaderVP8>(
+ &parsed_payload->video_header.video_type_header)) {
+ parsed->picture_id = vp8_header->pictureId;
+ parsed->tl0_pic_idx = vp8_header->tl0PicIdx;
+ parsed->temporal_idx = vp8_header->temporalIdx;
+ } else if (const auto* vp9_header = absl::get_if<RTPVideoHeaderVP9>(
+ &parsed_payload->video_header.video_type_header)) {
+ parsed->picture_id = vp9_header->picture_id;
+ parsed->tl0_pic_idx = vp9_header->tl0_pic_idx;
+ parsed->temporal_idx = vp9_header->temporal_idx;
+ } else {
+ RTC_DCHECK_NOTREACHED();
+ }
+
+ parsed->frame_type = parsed_payload->video_header.frame_type;
+ return true;
+ }
+
+ // Verify continuity and monotonicity of picture_id sequence.
+ void VerifyPictureId(const ParsedPacket& current,
+ const ParsedPacket& last) const
+ RTC_EXCLUSIVE_LOCKS_REQUIRED(&mutex_) {
+ if (current.timestamp == last.timestamp) {
+ EXPECT_EQ(last.picture_id, current.picture_id);
+ return; // Same frame.
+ }
+
+ // Packet belongs to a new frame.
+ // Picture id should be increasing.
+ EXPECT_TRUE((AheadOf<uint16_t, kPictureIdWraparound>(current.picture_id,
+ last.picture_id)));
+
+ // Expect continuously increasing picture id.
+ int diff = ForwardDiff<uint16_t, kPictureIdWraparound>(last.picture_id,
+ current.picture_id);
+ EXPECT_LE(diff - 1, max_expected_picture_id_gap_);
+ if (diff > 2) {
+ // If the VideoSendStream is destroyed, any frames still in queue is lost.
+ // This can result in a two-frame gap, which will result in logs like
+ // "packet transmission failed, no matching RTP module found, or
+ // transmission error".
+ // A larger gap is only possible for first frame after a recreation, i.e.
+ // key frames.
+ EXPECT_EQ(VideoFrameType::kVideoFrameKey, current.frame_type);
+ }
+ }
+
+ void VerifyTl0Idx(const ParsedPacket& current, const ParsedPacket& last) const
+ RTC_EXCLUSIVE_LOCKS_REQUIRED(&mutex_) {
+ if (current.tl0_pic_idx == kNoTl0PicIdx ||
+ current.temporal_idx == kNoTemporalIdx) {
+ return; // No temporal layers.
+ }
+
+ if (current.timestamp == last.timestamp || current.temporal_idx != 0) {
+ EXPECT_EQ(last.tl0_pic_idx, current.tl0_pic_idx);
+ return;
+ }
+
+ // New frame with `temporal_idx` 0.
+ // `tl0_pic_idx` should be increasing.
+ EXPECT_TRUE(AheadOf<uint8_t>(current.tl0_pic_idx, last.tl0_pic_idx));
+
+ // Expect continuously increasing idx.
+ int diff = ForwardDiff<uint8_t>(last.tl0_pic_idx, current.tl0_pic_idx);
+ if (diff > 1) {
+ // If the VideoSendStream is destroyed, any frames still in queue is lost.
+ // Gaps only possible for first frame after a recreation, i.e. key frames.
+ EXPECT_EQ(VideoFrameType::kVideoFrameKey, current.frame_type);
+ EXPECT_LE(diff - 1, max_expected_tl0_idx_gap_);
+ }
+ }
+
+ Action OnSendRtp(const uint8_t* packet, size_t length) override {
+ MutexLock lock(&mutex_);
+
+ ParsedPacket parsed;
+ if (!ParsePayload(packet, length, &parsed))
+ return SEND_PACKET;
+
+ uint32_t ssrc = parsed.ssrc;
+ if (last_observed_packet_.find(ssrc) != last_observed_packet_.end()) {
+ // Compare to last packet.
+ VerifyPictureId(parsed, last_observed_packet_[ssrc]);
+ VerifyTl0Idx(parsed, last_observed_packet_[ssrc]);
+ }
+
+ last_observed_packet_[ssrc] = parsed;
+
+ // Pass the test when enough media packets have been received on all
+ // streams.
+ if (++num_packets_sent_[ssrc] >= kMinPacketsToObserve &&
+ observed_ssrcs_.find(ssrc) == observed_ssrcs_.end()) {
+ observed_ssrcs_.insert(ssrc);
+ if (observed_ssrcs_.size() == num_ssrcs_to_observe_) {
+ observation_complete_.Set();
+ }
+ }
+ return SEND_PACKET;
+ }
+
+ Mutex mutex_;
+ const std::unique_ptr<VideoRtpDepacketizer> depacketizer_;
+ std::map<uint32_t, ParsedPacket> last_observed_packet_ RTC_GUARDED_BY(mutex_);
+ std::map<uint32_t, size_t> num_packets_sent_ RTC_GUARDED_BY(mutex_);
+ int max_expected_picture_id_gap_ RTC_GUARDED_BY(mutex_);
+ int max_expected_tl0_idx_gap_ RTC_GUARDED_BY(mutex_);
+ size_t num_ssrcs_to_observe_ RTC_GUARDED_BY(mutex_);
+ std::set<uint32_t> observed_ssrcs_ RTC_GUARDED_BY(mutex_);
+};
+
+class PictureIdTest : public test::CallTest,
+ public ::testing::WithParamInterface<size_t> {
+ public:
+ PictureIdTest() : num_temporal_layers_(GetParam()) {}
+
+ virtual ~PictureIdTest() {
+ SendTask(task_queue(), [this]() {
+ send_transport_.reset();
+ receive_transport_.reset();
+ DestroyCalls();
+ });
+ }
+
+ void SetupEncoder(VideoEncoderFactory* encoder_factory,
+ const std::string& payload_name);
+ void SetVideoEncoderConfig(int num_streams);
+ void TestPictureIdContinuousAfterReconfigure(
+ const std::vector<int>& ssrc_counts);
+ void TestPictureIdIncreaseAfterRecreateStreams(
+ const std::vector<int>& ssrc_counts);
+
+ private:
+ const size_t num_temporal_layers_;
+ std::unique_ptr<PictureIdObserver> observer_;
+};
+
+// TODO(bugs.webrtc.org/13725): Enable on android when flakiness fixed.
+#if defined(WEBRTC_ANDROID)
+#define MAYBE_TemporalLayers DISABLED_TemporalLayers
+#else
+#define MAYBE_TemporalLayers TemporalLayers
+#endif
+
+INSTANTIATE_TEST_SUITE_P(MAYBE_TemporalLayers,
+ PictureIdTest,
+ ::testing::ValuesIn(kNumTemporalLayers));
+
+void PictureIdTest::SetupEncoder(VideoEncoderFactory* encoder_factory,
+ const std::string& payload_name) {
+ observer_.reset(
+ new PictureIdObserver(PayloadStringToCodecType(payload_name)));
+
+ SendTask(
+ task_queue(), [this, encoder_factory, payload_name]() {
+ CreateCalls();
+ CreateSendTransport(BuiltInNetworkBehaviorConfig(), observer_.get());
+ CreateSendConfig(kNumSimulcastStreams, 0, 0, send_transport_.get());
+ GetVideoSendConfig()->encoder_settings.encoder_factory =
+ encoder_factory;
+ GetVideoSendConfig()->rtp.payload_name = payload_name;
+ GetVideoEncoderConfig()->codec_type =
+ PayloadStringToCodecType(payload_name);
+ SetVideoEncoderConfig(/* number_of_streams */ 1);
+ });
+}
+
+void PictureIdTest::SetVideoEncoderConfig(int num_streams) {
+ GetVideoEncoderConfig()->number_of_streams = num_streams;
+ GetVideoEncoderConfig()->max_bitrate_bps = kEncoderBitrateBps;
+
+ // Always divide the same total bitrate across all streams so that sending a
+ // single stream avoids lowering the bitrate estimate and requiring a
+ // subsequent rampup.
+ const int encoder_stream_bps = kEncoderBitrateBps / num_streams;
+ double scale_factor = 1.0;
+ for (int i = num_streams - 1; i >= 0; --i) {
+ VideoStream& stream = GetVideoEncoderConfig()->simulcast_layers[i];
+ // Reduce the min bitrate by 10% to account for overhead that might
+ // otherwise cause streams to not be enabled.
+ stream.min_bitrate_bps = static_cast<int>(encoder_stream_bps * 0.9);
+ stream.target_bitrate_bps = encoder_stream_bps;
+ stream.max_bitrate_bps = encoder_stream_bps;
+ stream.num_temporal_layers = num_temporal_layers_;
+ stream.scale_resolution_down_by = scale_factor;
+ scale_factor *= 2.0;
+ }
+}
+
+void PictureIdTest::TestPictureIdContinuousAfterReconfigure(
+ const std::vector<int>& ssrc_counts) {
+ SendTask(task_queue(), [this]() {
+ CreateVideoStreams();
+ CreateFrameGeneratorCapturer(kFrameRate, kFrameMaxWidth, kFrameMaxHeight);
+
+ // Initial test with a single stream.
+ Start();
+ });
+
+ EXPECT_TRUE(observer_->Wait()) << "Timed out waiting for packets.";
+
+ // Reconfigure VideoEncoder and test picture id increase.
+ // Expect continuously increasing picture id, equivalent to no gaps.
+ observer_->SetMaxExpectedPictureIdGap(0);
+ for (int ssrc_count : ssrc_counts) {
+ SetVideoEncoderConfig(ssrc_count);
+ observer_->SetExpectedSsrcs(ssrc_count);
+ observer_->ResetObservedSsrcs();
+ // Make sure the picture_id sequence is continuous on reinit and recreate.
+ SendTask(task_queue(), [this]() {
+ GetVideoSendStream()->ReconfigureVideoEncoder(
+ GetVideoEncoderConfig()->Copy());
+ });
+ EXPECT_TRUE(observer_->Wait()) << "Timed out waiting for packets.";
+ }
+
+ SendTask(task_queue(), [this]() {
+ Stop();
+ DestroyStreams();
+ });
+}
+
+void PictureIdTest::TestPictureIdIncreaseAfterRecreateStreams(
+ const std::vector<int>& ssrc_counts) {
+ SendTask(task_queue(), [this]() {
+ CreateVideoStreams();
+ CreateFrameGeneratorCapturer(kFrameRate, kFrameMaxWidth, kFrameMaxHeight);
+
+ // Initial test with a single stream.
+ Start();
+ });
+
+ EXPECT_TRUE(observer_->Wait()) << "Timed out waiting for packets.";
+
+ // Recreate VideoSendStream and test picture id increase.
+ // When the VideoSendStream is destroyed, any frames still in queue is lost
+ // with it, therefore it is expected that some frames might be lost.
+ observer_->SetMaxExpectedPictureIdGap(kMaxFramesLost);
+ for (int ssrc_count : ssrc_counts) {
+ SendTask(task_queue(), [this, &ssrc_count]() {
+ DestroyVideoSendStreams();
+
+ SetVideoEncoderConfig(ssrc_count);
+ observer_->SetExpectedSsrcs(ssrc_count);
+ observer_->ResetObservedSsrcs();
+
+ CreateVideoSendStreams();
+ GetVideoSendStream()->Start();
+ CreateFrameGeneratorCapturer(kFrameRate, kFrameMaxWidth, kFrameMaxHeight);
+ });
+
+ EXPECT_TRUE(observer_->Wait()) << "Timed out waiting for packets.";
+ }
+
+ SendTask(task_queue(), [this]() {
+ Stop();
+ DestroyStreams();
+ });
+}
+
+TEST_P(PictureIdTest, ContinuousAfterReconfigureVp8) {
+ test::FunctionVideoEncoderFactory encoder_factory(
+ []() { return VP8Encoder::Create(); });
+ SetupEncoder(&encoder_factory, "VP8");
+ TestPictureIdContinuousAfterReconfigure({1, 3, 3, 1, 1});
+}
+
+TEST_P(PictureIdTest, IncreasingAfterRecreateStreamVp8) {
+ test::FunctionVideoEncoderFactory encoder_factory(
+ []() { return VP8Encoder::Create(); });
+ SetupEncoder(&encoder_factory, "VP8");
+ TestPictureIdIncreaseAfterRecreateStreams({1, 3, 3, 1, 1});
+}
+
+TEST_P(PictureIdTest, ContinuousAfterStreamCountChangeVp8) {
+ test::FunctionVideoEncoderFactory encoder_factory(
+ []() { return VP8Encoder::Create(); });
+ // Make sure that the picture id is not reset if the stream count goes
+ // down and then up.
+ SetupEncoder(&encoder_factory, "VP8");
+ TestPictureIdContinuousAfterReconfigure({3, 1, 3});
+}
+
+TEST_P(PictureIdTest, ContinuousAfterReconfigureSimulcastEncoderAdapter) {
+ InternalEncoderFactory internal_encoder_factory;
+ test::FunctionVideoEncoderFactory encoder_factory(
+ [&internal_encoder_factory]() {
+ return std::make_unique<SimulcastEncoderAdapter>(
+ &internal_encoder_factory, SdpVideoFormat("VP8"));
+ });
+ SetupEncoder(&encoder_factory, "VP8");
+ TestPictureIdContinuousAfterReconfigure({1, 3, 3, 1, 1});
+}
+
+TEST_P(PictureIdTest, IncreasingAfterRecreateStreamSimulcastEncoderAdapter) {
+ InternalEncoderFactory internal_encoder_factory;
+ test::FunctionVideoEncoderFactory encoder_factory(
+ [&internal_encoder_factory]() {
+ return std::make_unique<SimulcastEncoderAdapter>(
+ &internal_encoder_factory, SdpVideoFormat("VP8"));
+ });
+ SetupEncoder(&encoder_factory, "VP8");
+ TestPictureIdIncreaseAfterRecreateStreams({1, 3, 3, 1, 1});
+}
+
+TEST_P(PictureIdTest, ContinuousAfterStreamCountChangeSimulcastEncoderAdapter) {
+ InternalEncoderFactory internal_encoder_factory;
+ test::FunctionVideoEncoderFactory encoder_factory(
+ [&internal_encoder_factory]() {
+ return std::make_unique<SimulcastEncoderAdapter>(
+ &internal_encoder_factory, SdpVideoFormat("VP8"));
+ });
+ // Make sure that the picture id is not reset if the stream count goes
+ // down and then up.
+ SetupEncoder(&encoder_factory, "VP8");
+ TestPictureIdContinuousAfterReconfigure({3, 1, 3});
+}
+
+TEST_P(PictureIdTest, IncreasingAfterRecreateStreamVp9) {
+ test::FunctionVideoEncoderFactory encoder_factory(
+ []() { return VP9Encoder::Create(); });
+ SetupEncoder(&encoder_factory, "VP9");
+ TestPictureIdIncreaseAfterRecreateStreams({1, 1});
+}
+
+} // namespace webrtc