| /* | |
| * 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 <cstdint> | |
| #include <memory> | |
| #include <optional> | |
| #include <utility> | |
| #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 "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/frame_base.h" | |
| #include "video/timing/simulator/receiver.h" | |
| #include "video/timing/simulator/rtc_event_log_driver.h" | |
| #include "video/timing/simulator/rtp_packet_simulator.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<int>(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); | |
| } | |
| SortByArrivalOrder(stream.frames); | |
| return stream; | |
| } | |
| private: | |
| SequenceChecker sequence_checker_; | |
| const Environment env_; | |
| const uint32_t ssrc_; | |
| std::optional<Timestamp> creation_timestamp_ | |
| RTC_GUARDED_BY(sequence_checker_); | |
| SeqNumUnwrapper<uint32_t> rtp_timestamp_unwrapper_ | |
| RTC_GUARDED_BY(sequence_checker_); | |
| absl::flat_hash_map</*frame_id*/ int64_t, DecodabilitySimulator::Frame> | |
| 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, | |
| uint32_t rtx_ssrc, | |
| DecodabilitySimulator::Results* absl_nonnull | |
| results) | |
| : collector_(env, ssrc), | |
| tracker_(env, DecodabilityTracker::Config{.ssrc = ssrc}, &collector_), | |
| assembler_(env, ssrc, &collector_, &tracker_), | |
| receiver_(env, ssrc, rtx_ssrc, &assembler_), | |
| results_(*results) { | |
| RTC_DCHECK_RUN_ON(&sequence_checker_); | |
| tracker_.SetDecodedFrameIdCallback(&assembler_); | |
| } | |
| ~DecodabilitySimulatorStream() override = default; | |
| // Implements `RtcEventLogDriver::StreamInterface`. | |
| void InsertSimulatedPacket( | |
| const RtpPacketSimulator::SimulatedPacket& simulated_packet) override { | |
| RTC_DCHECK_RUN_ON(&sequence_checker_); | |
| receiver_.InsertSimulatedPacket(simulated_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_); | |
| Receiver receiver_ RTC_GUARDED_BY(sequence_checker_); | |
| DecodabilitySimulator::Results& results_; | |
| }; | |
| } // namespace | |
| DecodabilitySimulator::DecodabilitySimulator(Config config) : config_(config) {} | |
| DecodabilitySimulator::~DecodabilitySimulator() = default; | |
| DecodabilitySimulator::Results DecodabilitySimulator::Simulate( | |
| const ParsedRtcEventLog& parsed_log) const { | |
| // Outputs. | |
| Results results; | |
| // Simulation. | |
| auto stream_factory = [&results](const Environment& env, uint32_t ssrc, | |
| uint32_t rtx_ssrc) { | |
| return std::make_unique<DecodabilitySimulatorStream>(env, ssrc, rtx_ssrc, | |
| &results); | |
| }; | |
| // Decodability should not be a function of any field trials, so we pass the | |
| // empty string here. | |
| RtcEventLogDriver rtc_event_log_simulator( | |
| {.reuse_streams = config_.reuse_streams, | |
| .ssrc_filter = config_.ssrc_filter}, | |
| &parsed_log, | |
| /*field_trials_string=*/"", std::move(stream_factory)); | |
| rtc_event_log_simulator.Simulate(); | |
| // Return. | |
| SortByStreamOrder(results.streams); | |
| return results; | |
| } | |
| } // namespace webrtc::video_timing_simulator |