| /* |
| * Copyright (c) 2019 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 "modules/rtp_rtcp/source/source_tracker.h" |
| |
| #include <algorithm> |
| #include <list> |
| #include <random> |
| #include <set> |
| #include <tuple> |
| #include <utility> |
| #include <vector> |
| |
| #include "api/rtp_headers.h" |
| #include "api/rtp_packet_info.h" |
| #include "api/rtp_packet_infos.h" |
| #include "test/gmock.h" |
| #include "test/gtest.h" |
| |
| namespace webrtc { |
| namespace { |
| |
| using ::testing::Combine; |
| using ::testing::ElementsAre; |
| using ::testing::ElementsAreArray; |
| using ::testing::IsEmpty; |
| using ::testing::SizeIs; |
| using ::testing::TestWithParam; |
| using ::testing::Values; |
| |
| constexpr size_t kPacketInfosCountMax = 5; |
| |
| // Simple "guaranteed to be correct" re-implementation of |SourceTracker| for |
| // dual-implementation testing purposes. |
| class ExpectedSourceTracker { |
| public: |
| explicit ExpectedSourceTracker(Clock* clock) : clock_(clock) {} |
| |
| void OnFrameDelivered(const RtpPacketInfos& packet_infos) { |
| const int64_t now_ms = clock_->TimeInMilliseconds(); |
| |
| for (const auto& packet_info : packet_infos) { |
| for (const auto& csrc : packet_info.csrcs()) { |
| entries_.emplace_front(now_ms, csrc, RtpSourceType::CSRC, |
| packet_info.audio_level(), |
| packet_info.rtp_timestamp()); |
| } |
| |
| entries_.emplace_front(now_ms, packet_info.ssrc(), RtpSourceType::SSRC, |
| packet_info.audio_level(), |
| packet_info.rtp_timestamp()); |
| } |
| |
| PruneEntries(now_ms); |
| } |
| |
| std::vector<RtpSource> GetSources() const { |
| PruneEntries(clock_->TimeInMilliseconds()); |
| |
| return std::vector<RtpSource>(entries_.begin(), entries_.end()); |
| } |
| |
| private: |
| void PruneEntries(int64_t now_ms) const { |
| const int64_t prune_ms = now_ms - 10000; // 10 seconds |
| |
| std::set<std::pair<RtpSourceType, uint32_t>> seen; |
| |
| auto it = entries_.begin(); |
| auto end = entries_.end(); |
| while (it != end) { |
| auto next = it; |
| ++next; |
| |
| auto key = std::make_pair(it->source_type(), it->source_id()); |
| if (!seen.insert(key).second || it->timestamp_ms() < prune_ms) { |
| entries_.erase(it); |
| } |
| |
| it = next; |
| } |
| } |
| |
| Clock* const clock_; |
| |
| mutable std::list<RtpSource> entries_; |
| }; |
| |
| class SourceTrackerRandomTest |
| : public TestWithParam<std::tuple<uint32_t, uint32_t>> { |
| protected: |
| SourceTrackerRandomTest() |
| : ssrcs_count_(std::get<0>(GetParam())), |
| csrcs_count_(std::get<1>(GetParam())), |
| generator_(42) {} |
| |
| RtpPacketInfos GeneratePacketInfos() { |
| size_t count = std::uniform_int_distribution<size_t>( |
| 1, kPacketInfosCountMax)(generator_); |
| |
| RtpPacketInfos::vector_type packet_infos; |
| for (size_t i = 0; i < count; ++i) { |
| packet_infos.emplace_back(GenerateSsrc(), GenerateCsrcs(), |
| GenerateRtpTimestamp(), GenerateAudioLevel(), |
| GenerateReceiveTimeMs()); |
| } |
| |
| return RtpPacketInfos(std::move(packet_infos)); |
| } |
| |
| int64_t GenerateClockAdvanceTimeMilliseconds() { |
| double roll = std::uniform_real_distribution<double>(0.0, 1.0)(generator_); |
| |
| if (roll < 0.05) { |
| return 0; |
| } |
| |
| if (roll < 0.08) { |
| return SourceTracker::kTimeoutMs - 1; |
| } |
| |
| if (roll < 0.11) { |
| return SourceTracker::kTimeoutMs; |
| } |
| |
| if (roll < 0.19) { |
| return std::uniform_int_distribution<int64_t>( |
| SourceTracker::kTimeoutMs, |
| SourceTracker::kTimeoutMs * 1000)(generator_); |
| } |
| |
| return std::uniform_int_distribution<int64_t>( |
| 1, SourceTracker::kTimeoutMs - 1)(generator_); |
| } |
| |
| private: |
| uint32_t GenerateSsrc() { |
| return std::uniform_int_distribution<uint32_t>(1, ssrcs_count_)(generator_); |
| } |
| |
| std::vector<uint32_t> GenerateCsrcs() { |
| std::vector<uint32_t> csrcs; |
| for (size_t i = 1; i <= csrcs_count_ && csrcs.size() < kRtpCsrcSize; ++i) { |
| if (std::bernoulli_distribution(0.5)(generator_)) { |
| csrcs.push_back(i); |
| } |
| } |
| |
| return csrcs; |
| } |
| |
| uint32_t GenerateRtpTimestamp() { |
| return std::uniform_int_distribution<uint32_t>()(generator_); |
| } |
| |
| absl::optional<uint8_t> GenerateAudioLevel() { |
| if (std::bernoulli_distribution(0.25)(generator_)) { |
| return absl::nullopt; |
| } |
| |
| // Workaround for std::uniform_int_distribution<uint8_t> not being allowed. |
| return static_cast<uint8_t>( |
| std::uniform_int_distribution<uint16_t>()(generator_)); |
| } |
| |
| int64_t GenerateReceiveTimeMs() { |
| return std::uniform_int_distribution<int64_t>()(generator_); |
| } |
| |
| const uint32_t ssrcs_count_; |
| const uint32_t csrcs_count_; |
| |
| std::mt19937 generator_; |
| }; |
| |
| } // namespace |
| |
| TEST_P(SourceTrackerRandomTest, RandomOperations) { |
| constexpr size_t kIterationsCount = 200; |
| |
| SimulatedClock clock(1000000000000ULL); |
| SourceTracker actual_tracker(&clock); |
| ExpectedSourceTracker expected_tracker(&clock); |
| |
| ASSERT_THAT(actual_tracker.GetSources(), IsEmpty()); |
| ASSERT_THAT(expected_tracker.GetSources(), IsEmpty()); |
| |
| for (size_t i = 0; i < kIterationsCount; ++i) { |
| RtpPacketInfos packet_infos = GeneratePacketInfos(); |
| |
| actual_tracker.OnFrameDelivered(packet_infos); |
| expected_tracker.OnFrameDelivered(packet_infos); |
| |
| clock.AdvanceTimeMilliseconds(GenerateClockAdvanceTimeMilliseconds()); |
| |
| ASSERT_THAT(actual_tracker.GetSources(), |
| ElementsAreArray(expected_tracker.GetSources())); |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(, |
| SourceTrackerRandomTest, |
| Combine(/*ssrcs_count_=*/Values(1, 2, 4), |
| /*csrcs_count_=*/Values(0, 1, 3, 7))); |
| |
| TEST(SourceTrackerTest, StartEmpty) { |
| SimulatedClock clock(1000000000000ULL); |
| SourceTracker tracker(&clock); |
| |
| EXPECT_THAT(tracker.GetSources(), IsEmpty()); |
| } |
| |
| TEST(SourceTrackerTest, OnFrameDeliveredRecordsSources) { |
| constexpr uint32_t kSsrc = 10; |
| constexpr uint32_t kCsrcs0 = 20; |
| constexpr uint32_t kCsrcs1 = 21; |
| constexpr uint32_t kRtpTimestamp = 40; |
| constexpr absl::optional<uint8_t> kAudioLevel = 50; |
| constexpr int64_t kReceiveTimeMs = 60; |
| |
| SimulatedClock clock(1000000000000ULL); |
| SourceTracker tracker(&clock); |
| |
| tracker.OnFrameDelivered(RtpPacketInfos({RtpPacketInfo( |
| kSsrc, {kCsrcs0, kCsrcs1}, kRtpTimestamp, kAudioLevel, kReceiveTimeMs)})); |
| |
| int64_t timestamp_ms = clock.TimeInMilliseconds(); |
| |
| EXPECT_THAT(tracker.GetSources(), |
| ElementsAre(RtpSource(timestamp_ms, kSsrc, RtpSourceType::SSRC, |
| kAudioLevel, kRtpTimestamp), |
| RtpSource(timestamp_ms, kCsrcs1, RtpSourceType::CSRC, |
| kAudioLevel, kRtpTimestamp), |
| RtpSource(timestamp_ms, kCsrcs0, RtpSourceType::CSRC, |
| kAudioLevel, kRtpTimestamp))); |
| } |
| |
| TEST(SourceTrackerTest, OnFrameDeliveredUpdatesSources) { |
| constexpr uint32_t kSsrc = 10; |
| constexpr uint32_t kCsrcs0 = 20; |
| constexpr uint32_t kCsrcs1 = 21; |
| constexpr uint32_t kCsrcs2 = 22; |
| constexpr uint32_t kRtpTimestamp0 = 40; |
| constexpr uint32_t kRtpTimestamp1 = 41; |
| constexpr absl::optional<uint8_t> kAudioLevel0 = 50; |
| constexpr absl::optional<uint8_t> kAudioLevel1 = absl::nullopt; |
| constexpr int64_t kReceiveTimeMs0 = 60; |
| constexpr int64_t kReceiveTimeMs1 = 61; |
| |
| SimulatedClock clock(1000000000000ULL); |
| SourceTracker tracker(&clock); |
| |
| tracker.OnFrameDelivered( |
| RtpPacketInfos({RtpPacketInfo(kSsrc, {kCsrcs0, kCsrcs1}, kRtpTimestamp0, |
| kAudioLevel0, kReceiveTimeMs0)})); |
| |
| int64_t timestamp_ms_0 = clock.TimeInMilliseconds(); |
| |
| clock.AdvanceTimeMilliseconds(17); |
| |
| tracker.OnFrameDelivered( |
| RtpPacketInfos({RtpPacketInfo(kSsrc, {kCsrcs0, kCsrcs2}, kRtpTimestamp1, |
| kAudioLevel1, kReceiveTimeMs1)})); |
| |
| int64_t timestamp_ms_1 = clock.TimeInMilliseconds(); |
| |
| EXPECT_THAT( |
| tracker.GetSources(), |
| ElementsAre(RtpSource(timestamp_ms_1, kSsrc, RtpSourceType::SSRC, |
| kAudioLevel1, kRtpTimestamp1), |
| RtpSource(timestamp_ms_1, kCsrcs2, RtpSourceType::CSRC, |
| kAudioLevel1, kRtpTimestamp1), |
| RtpSource(timestamp_ms_1, kCsrcs0, RtpSourceType::CSRC, |
| kAudioLevel1, kRtpTimestamp1), |
| RtpSource(timestamp_ms_0, kCsrcs1, RtpSourceType::CSRC, |
| kAudioLevel0, kRtpTimestamp0))); |
| } |
| |
| TEST(SourceTrackerTest, TimedOutSourcesAreRemoved) { |
| constexpr uint32_t kSsrc = 10; |
| constexpr uint32_t kCsrcs0 = 20; |
| constexpr uint32_t kCsrcs1 = 21; |
| constexpr uint32_t kCsrcs2 = 22; |
| constexpr uint32_t kRtpTimestamp0 = 40; |
| constexpr uint32_t kRtpTimestamp1 = 41; |
| constexpr absl::optional<uint8_t> kAudioLevel0 = 50; |
| constexpr absl::optional<uint8_t> kAudioLevel1 = absl::nullopt; |
| constexpr int64_t kReceiveTimeMs0 = 60; |
| constexpr int64_t kReceiveTimeMs1 = 61; |
| |
| SimulatedClock clock(1000000000000ULL); |
| SourceTracker tracker(&clock); |
| |
| tracker.OnFrameDelivered( |
| RtpPacketInfos({RtpPacketInfo(kSsrc, {kCsrcs0, kCsrcs1}, kRtpTimestamp0, |
| kAudioLevel0, kReceiveTimeMs0)})); |
| |
| clock.AdvanceTimeMilliseconds(17); |
| |
| tracker.OnFrameDelivered( |
| RtpPacketInfos({RtpPacketInfo(kSsrc, {kCsrcs0, kCsrcs2}, kRtpTimestamp1, |
| kAudioLevel1, kReceiveTimeMs1)})); |
| |
| int64_t timestamp_ms_1 = clock.TimeInMilliseconds(); |
| |
| clock.AdvanceTimeMilliseconds(SourceTracker::kTimeoutMs); |
| |
| EXPECT_THAT( |
| tracker.GetSources(), |
| ElementsAre(RtpSource(timestamp_ms_1, kSsrc, RtpSourceType::SSRC, |
| kAudioLevel1, kRtpTimestamp1), |
| RtpSource(timestamp_ms_1, kCsrcs2, RtpSourceType::CSRC, |
| kAudioLevel1, kRtpTimestamp1), |
| RtpSource(timestamp_ms_1, kCsrcs0, RtpSourceType::CSRC, |
| kAudioLevel1, kRtpTimestamp1))); |
| } |
| |
| } // namespace webrtc |