/* * 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/decodability_simulator.h" #include #include #include #include #include "absl/algorithm/container.h" #include "absl/base/nullability.h" #include "absl/container/flat_hash_map.h" #include "api/environment/environment.h" #include "api/sequence_checker.h" #include "api/units/data_size.h" #include "api/units/timestamp.h" #include "api/video/encoded_frame.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 "rtc_base/numerics/sequence_number_unwrapper.h" #include "rtc_base/thread_annotations.h" #include "video/timing/simulator/assembler.h" #include "video/timing/simulator/decodability_tracker.h" #include "video/timing/simulator/rtc_event_log_driver.h" namespace webrtc::video_timing_simulator { namespace { // Observes the `Assembler` and `DecodabilityTracker` in order to collect // frame metadata for decodable frames. class DecodableFrameCollector : public AssemblerEvents, public DecodabilityTrackerEvents { public: DecodableFrameCollector(const Environment& env, uint32_t ssrc) : env_(env), ssrc_(ssrc) { RTC_DCHECK_NE(ssrc_, 0); } ~DecodableFrameCollector() override = default; DecodableFrameCollector(const DecodableFrameCollector&) = delete; DecodableFrameCollector& operator=(const DecodableFrameCollector&) = delete; // Implements `AssemblerEvents`. void OnAssembledFrame(const EncodedFrame& assembled_frame) override { RTC_DCHECK_RUN_ON(&sequence_checker_); Timestamp now = env_.clock().CurrentTime(); if (!creation_timestamp_) { creation_timestamp_ = now; } int64_t frame_id = assembled_frame.Id(); if (frames_.contains(frame_id)) { RTC_LOG(LS_WARNING) << "Assembled frame_id=" << frame_id << " on ssrc=" << ssrc_ << " had already been collected. Dropping it." << " (simulated_ts=" << env_.clock().CurrentTime() << ")"; return; } auto& frame = frames_[frame_id]; RTC_DCHECK(!assembled_frame.PacketInfos().empty()); frame.num_packets = static_cast(assembled_frame.PacketInfos().size()); frame.size = DataSize::Bytes(assembled_frame.size()); frame.unwrapped_rtp_timestamp = rtp_timestamp_unwrapper_.Unwrap(assembled_frame.RtpTimestamp()); frame.assembled_timestamp = now; } // Implements `DecodabilityTrackerEvents`. void OnDecodableFrame(const EncodedFrame& decodable_frame) override { RTC_DCHECK_RUN_ON(&sequence_checker_); int64_t frame_id = decodable_frame.Id(); auto it = frames_.find(frame_id); if (it == frames_.end()) { RTC_LOG(LS_WARNING) << "Decodable frame_id=" << frame_id << " on ssrc=" << ssrc_ << " had no assembly information collected. Dropping it." << " (simulated_ts=" << env_.clock().CurrentTime() << ")"; return; } auto& frame = it->second; frame.decodable_timestamp = env_.clock().CurrentTime(); } DecodabilitySimulator::Stream GetStream() const { RTC_DCHECK_RUN_ON(&sequence_checker_); DecodabilitySimulator::Stream stream; stream.ssrc = ssrc_; if (creation_timestamp_) { stream.creation_timestamp = *creation_timestamp_; } stream.frames.reserve(frames_.size()); for (const auto& [key, value] : frames_) { stream.frames.push_back(value); } absl::c_sort(stream.frames); return stream; } private: SequenceChecker sequence_checker_; const Environment env_; const uint32_t ssrc_; std::optional creation_timestamp_ RTC_GUARDED_BY(sequence_checker_); SeqNumUnwrapper rtp_timestamp_unwrapper_ RTC_GUARDED_BY(sequence_checker_); absl::flat_hash_map frames_ RTC_GUARDED_BY(sequence_checker_); }; // Combines all objects needed to perform decodability simulation of a single // stream. Inserts the streams results to the `results` pointer when `Close()` // is called (at the end of simulation). class DecodabilitySimulatorStream : public RtcEventLogDriver::StreamInterface { public: DecodabilitySimulatorStream(const Environment& env, uint32_t ssrc, DecodabilitySimulator::Results* absl_nonnull results) : collector_(env, ssrc), tracker_(env, DecodabilityTracker::Config{.ssrc = ssrc}, &collector_), assembler_(env, ssrc, &collector_, &tracker_), results_(*results) { RTC_DCHECK_RUN_ON(&sequence_checker_); tracker_.SetDecodedFrameIdCallback(&assembler_); } ~DecodabilitySimulatorStream() override = default; // Implements `RtcEventLogDriver::StreamInterface`. void InsertPacket(const RtpPacketReceived& rtp_packet) override { RTC_DCHECK_RUN_ON(&sequence_checker_); assembler_.InsertPacket(rtp_packet); } void Close() override { RTC_DCHECK_RUN_ON(&sequence_checker_); DecodabilitySimulator::Stream stream = collector_.GetStream(); if (!stream.IsEmpty()) { RTC_DCHECK_NE(stream.ssrc, 0u); results_.streams.push_back(stream); } } private: SequenceChecker sequence_checker_; DecodableFrameCollector collector_ RTC_GUARDED_BY(sequence_checker_); DecodabilityTracker tracker_ RTC_GUARDED_BY(sequence_checker_); Assembler assembler_ RTC_GUARDED_BY(sequence_checker_); DecodabilitySimulator::Results& results_; }; } // namespace DecodabilitySimulator::Results DecodabilitySimulator::Simulate( const ParsedRtcEventLog& parsed_log) const { // Outputs. Results results; // Simulation. auto stream_factory = [&results](const Environment& env, uint32_t ssrc) { return std::make_unique(env, ssrc, &results); }; // In order to keep the decodability data clean, we do not reuse streams. // Decodability should not be a function of any field trials, so we pass the // empty string here. RtcEventLogDriver rtc_event_log_simulator( {.reuse_streams = false}, &parsed_log, /*field_trials_string=*/"", std::move(stream_factory)); rtc_event_log_simulator.Simulate(); // Return. absl::c_sort(results.streams); return results; } } // namespace webrtc::video_timing_simulator