| /* | |
| * Copyright (c) 2025 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 "video/timing/simulator/rtc_event_log_driver.h" | |
| #include <cstdint> | |
| #include <memory> | |
| #include <optional> | |
| #include <utility> | |
| #include "absl/base/nullability.h" | |
| #include "absl/functional/any_invocable.h" | |
| #include "absl/strings/string_view.h" | |
| #include "api/environment/environment_factory.h" | |
| #include "api/field_trials.h" | |
| #include "api/sequence_checker.h" | |
| #include "api/task_queue/task_queue_factory.h" | |
| #include "api/units/time_delta.h" | |
| #include "api/units/timestamp.h" | |
| #include "logging/rtc_event_log/events/logged_rtp_rtcp.h" | |
| #include "logging/rtc_event_log/events/rtc_event_video_receive_stream_config.h" | |
| #include "logging/rtc_event_log/rtc_event_log_parser.h" | |
| #include "modules/rtp_rtcp/source/rtp_packet_received.h" | |
| #include "rtc_base/checks.h" | |
| #include "rtc_base/logging.h" | |
| #include "test/time_controller/simulated_time_task_queue_controller.h" | |
| #include "video/timing/simulator/rtp_packet_simulator.h" | |
| namespace webrtc::video_timing_simulator { | |
| RtcEventLogDriver::RtcEventLogDriver( | |
| const Config& config, | |
| const ParsedRtcEventLog* absl_nonnull parsed_log, | |
| absl::string_view field_trials_string, | |
| RtcEventLogDriver::StreamInterfaceFactory stream_factory) | |
| : config_(config), | |
| time_controller_(Timestamp::Zero()), | |
| env_(CreateEnvironment( | |
| std::make_unique<webrtc::FieldTrials>(field_trials_string), | |
| time_controller_.GetClock(), | |
| time_controller_.GetTaskQueueFactory())), | |
| parsed_log_(*parsed_log), | |
| stream_factory_(std::move(stream_factory)), | |
| prev_log_timestamp_(std::nullopt), | |
| simulator_queue_(time_controller_.GetTaskQueueFactory()->CreateTaskQueue( | |
| "simulator_queue", | |
| TaskQueueFactory::Priority::kNormal)), | |
| packet_simulator_(env_) { | |
| RTC_DCHECK(stream_factory_) << "stream_factory must be provided"; | |
| // Config events. | |
| processor_.AddEvents( | |
| parsed_log_.video_recv_configs(), | |
| [&](const auto& config) { OnLoggedVideoRecvConfig(config); }); | |
| // Video packet events (media + RTX). | |
| for (const auto& stream : parsed_log_.incoming_rtp_packets_by_ssrc()) { | |
| bool is_video = parsed_log_.GetMediaType( | |
| stream.ssrc, PacketDirection::kIncomingPacket) == | |
| ParsedRtcEventLog::MediaType::VIDEO; | |
| if (!is_video) { | |
| continue; | |
| } | |
| processor_.AddEvents(stream.incoming_packets, [&](const auto& packet) { | |
| OnLoggedRtpPacketIncoming(packet); | |
| }); | |
| } | |
| } | |
| RtcEventLogDriver::~RtcEventLogDriver() = default; | |
| void RtcEventLogDriver::Simulate() { | |
| // Walk through events in timestamp order and call the registered handlers. | |
| processor_.ProcessEventsInOrder(); | |
| // Attempt to get straggling frames out by advancing time a little bit after | |
| // the last logged event. | |
| time_controller_.AdvanceTime(kShutdownAdvanceTimeSlack); | |
| // Tear down on the queue. | |
| bool done = false; | |
| simulator_queue_->PostTask([this, &done]() { | |
| RTC_DCHECK_RUN_ON(simulator_queue_.get()); | |
| for (auto& stream : streams_) { | |
| stream.second->Close(); | |
| } | |
| receiving_streams_.clear(); | |
| streams_.clear(); | |
| done = true; | |
| }); | |
| time_controller_.AdvanceTime(TimeDelta::Zero()); | |
| RTC_DCHECK(done); | |
| } | |
| void RtcEventLogDriver::AdvanceTime(Timestamp log_timestamp) { | |
| if (!prev_log_timestamp_) { | |
| // For the first event, set the clock in absolute terms. | |
| prev_log_timestamp_ = log_timestamp; | |
| time_controller_.AdvanceTime(log_timestamp - env_.clock().CurrentTime()); | |
| RTC_DCHECK_EQ(env_.clock().CurrentTime(), log_timestamp); | |
| return; | |
| } | |
| TimeDelta duration = log_timestamp - *prev_log_timestamp_; | |
| prev_log_timestamp_ = log_timestamp; | |
| if (duration < TimeDelta::Zero()) { | |
| RTC_LOG(LS_ERROR) | |
| << "Non-monotonic sequence of timestamps. Will not advance time." | |
| << " (simulated_ts=" << env_.clock().CurrentTime() << ")"; | |
| return; | |
| } | |
| time_controller_.AdvanceTime(duration); | |
| } | |
| void RtcEventLogDriver::HandleEvent(Timestamp log_timestamp, | |
| absl::AnyInvocable<void() &&> handler) { | |
| // Execute all tasks scheduled before the new logged event. | |
| AdvanceTime(log_timestamp); | |
| bool done = false; | |
| simulator_queue_->PostTask([handler = std::move(handler), &done]() mutable { | |
| std::move(handler)(); | |
| done = true; | |
| }); | |
| // Execute the logged event itself. | |
| AdvanceTime(log_timestamp); | |
| RTC_DCHECK(done) << "Handler was not executed"; | |
| } | |
| void RtcEventLogDriver::OnLoggedVideoRecvConfig( | |
| const webrtc::LoggedVideoRecvConfig& config) { | |
| uint32_t ssrc = config.config.remote_ssrc; | |
| uint32_t rtx_ssrc = config.config.rtx_ssrc; | |
| HandleEvent(config.log_time(), [this, ssrc, rtx_ssrc]() { | |
| RTC_DCHECK_RUN_ON(simulator_queue_.get()); | |
| all_known_ssrcs_.insert(ssrc); | |
| // Skip setting up stream if not included in the filter. | |
| if (!config_.ssrc_filter.empty() && !config_.ssrc_filter.contains(ssrc)) { | |
| RTC_LOG(LS_INFO) << "OnLoggedVideoRecvConfig being skipped for " | |
| << "ssrc=" << ssrc << ", rtx_ssrc=" << rtx_ssrc | |
| << " (simulated_ts=" << env_.clock().CurrentTime() | |
| << ")"; | |
| return; | |
| } | |
| RTC_LOG(LS_INFO) << "OnLoggedVideoRecvConfig for " | |
| << "ssrc=" << ssrc << ", rtx_ssrc=" << rtx_ssrc | |
| << " (simulated_ts=" << env_.clock().CurrentTime() << ")"; | |
| if (auto it = streams_.find(ssrc); it != streams_.end()) { | |
| if (config_.reuse_streams) { | |
| // TODO(b/384950328): Support changing RTX SSRCs on the fly? | |
| RTC_LOG(LS_WARNING) | |
| << "Stream for ssrc=" << ssrc << " already existed. Reusing it." | |
| << " (simulated_ts=" << env_.clock().CurrentTime() << ")"; | |
| return; | |
| } else { | |
| RTC_LOG(LS_WARNING) | |
| << "Stream for ssrc=" << ssrc << " already existed. Overwriting it." | |
| << " (simulated_ts=" << env_.clock().CurrentTime() << ")"; | |
| it->second->Close(); | |
| } | |
| } | |
| std::unique_ptr<StreamInterface> stream = | |
| stream_factory_(env_, ssrc, rtx_ssrc); | |
| RTC_DCHECK(stream); | |
| // Keep track of the stream object for ownership purposes. | |
| streams_[ssrc] = std::move(stream); | |
| // Also map it by the two SSRCs, for packet demuxing purposes. | |
| receiving_streams_[ssrc] = streams_[ssrc].get(); | |
| if (rtx_ssrc != 0) { | |
| receiving_streams_[rtx_ssrc] = streams_[ssrc].get(); | |
| } | |
| }); | |
| } | |
| void RtcEventLogDriver::OnLoggedRtpPacketIncoming( | |
| const webrtc::LoggedRtpPacketIncoming& packet) { | |
| HandleEvent(packet.log_time(), [this, packet]() { | |
| RTC_DCHECK_RUN_ON(simulator_queue_.get()); | |
| uint32_t ssrc = packet.rtp.header.ssrc; | |
| if (auto it = receiving_streams_.find(ssrc); | |
| it != receiving_streams_.end()) { | |
| RtpPacketSimulator::SimulatedPacket simulated_packet = | |
| packet_simulator_.SimulateRtpPacketReceived(packet.rtp); | |
| RTC_DCHECK_EQ(simulated_packet.rtp_packet.arrival_time(), | |
| packet.log_time()); | |
| RTC_DCHECK_EQ(env_.clock().CurrentTime(), packet.log_time()); | |
| it->second->InsertSimulatedPacket(simulated_packet); | |
| } else if (!all_known_ssrcs_.contains(ssrc)) { | |
| RTC_LOG(LS_WARNING) << "Received packet for unknown ssrc=" << ssrc | |
| << " (simulated_ts=" << env_.clock().CurrentTime() | |
| << ")"; | |
| } | |
| }); | |
| } | |
| } // namespace webrtc::video_timing_simulator |