| /* |
| * Copyright (c) 2022 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 "test/pc/e2e/analyzer/video/default_video_quality_analyzer_frame_in_flight.h" |
| |
| #include <unordered_map> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/types/optional.h" |
| #include "api/units/data_size.h" |
| #include "api/units/time_delta.h" |
| #include "api/units/timestamp.h" |
| #include "api/video/video_frame.h" |
| #include "api/video/video_frame_type.h" |
| #include "test/pc/e2e/analyzer/video/default_video_quality_analyzer_internal_shared_objects.h" |
| |
| namespace webrtc { |
| namespace { |
| |
| template <typename T> |
| absl::optional<T> MaybeGetValue(const std::unordered_map<size_t, T>& map, |
| size_t key) { |
| auto it = map.find(key); |
| if (it == map.end()) { |
| return absl::nullopt; |
| } |
| return it->second; |
| } |
| |
| } // namespace |
| |
| FrameInFlight::FrameInFlight( |
| size_t stream, |
| uint16_t frame_id, |
| Timestamp captured_time, |
| absl::optional<TimeDelta> time_between_captured_frames, |
| std::set<size_t> expected_receivers) |
| : stream_(stream), |
| expected_receivers_(std::move(expected_receivers)), |
| frame_id_(frame_id), |
| captured_time_(captured_time), |
| time_between_captured_frames_(time_between_captured_frames) {} |
| |
| std::vector<size_t> FrameInFlight::GetPeersWhichDidntReceive() const { |
| std::vector<size_t> out; |
| for (size_t peer : expected_receivers_) { |
| auto it = receiver_stats_.find(peer); |
| if (it == receiver_stats_.end() || |
| (!it->second.dropped && !it->second.superfluous && |
| it->second.rendered_time.IsInfinite())) { |
| out.push_back(peer); |
| } |
| } |
| return out; |
| } |
| |
| bool FrameInFlight::HaveAllPeersReceived() const { |
| for (size_t peer : expected_receivers_) { |
| auto it = receiver_stats_.find(peer); |
| if (it == receiver_stats_.end()) { |
| return false; |
| } |
| |
| if (!it->second.dropped && !it->second.superfluous && |
| it->second.rendered_time.IsInfinite()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| void FrameInFlight::OnFrameEncoded( |
| webrtc::Timestamp time, |
| absl::optional<TimeDelta> time_between_encoded_frames, |
| VideoFrameType frame_type, |
| DataSize encoded_image_size, |
| uint32_t target_encode_bitrate, |
| int stream_index, |
| int qp, |
| StreamCodecInfo used_encoder) { |
| encoded_time_ = time; |
| if (time_between_encoded_frames.has_value()) { |
| time_between_encoded_frames_ = |
| time_between_encoded_frames_.value_or(TimeDelta::Zero()) + |
| *time_between_encoded_frames; |
| } |
| frame_type_ = frame_type; |
| encoded_image_size_ = encoded_image_size; |
| target_encode_bitrate_ += target_encode_bitrate; |
| stream_layers_qp_[stream_index].AddSample(SamplesStatsCounter::StatsSample{ |
| .value = static_cast<double>(qp), .time = time}); |
| // Update used encoder info. If simulcast/SVC is used, this method can |
| // be called multiple times, in such case we should preserve the value |
| // of `used_encoder_.switched_on_at` from the first invocation as the |
| // smallest one. |
| Timestamp encoder_switched_on_at = used_encoder_.has_value() |
| ? used_encoder_->switched_on_at |
| : Timestamp::PlusInfinity(); |
| RTC_DCHECK(used_encoder.switched_on_at.IsFinite()); |
| RTC_DCHECK(used_encoder.switched_from_at.IsFinite()); |
| used_encoder_ = used_encoder; |
| if (encoder_switched_on_at < used_encoder_->switched_on_at) { |
| used_encoder_->switched_on_at = encoder_switched_on_at; |
| } |
| } |
| |
| void FrameInFlight::OnFramePreDecode(size_t peer, |
| webrtc::Timestamp received_time, |
| webrtc::Timestamp decode_start_time, |
| VideoFrameType frame_type, |
| DataSize encoded_image_size) { |
| receiver_stats_[peer].received_time = received_time; |
| receiver_stats_[peer].decode_start_time = decode_start_time; |
| receiver_stats_[peer].frame_type = frame_type; |
| receiver_stats_[peer].encoded_image_size = encoded_image_size; |
| } |
| |
| bool FrameInFlight::HasReceivedTime(size_t peer) const { |
| auto it = receiver_stats_.find(peer); |
| if (it == receiver_stats_.end()) { |
| return false; |
| } |
| return it->second.received_time.IsFinite(); |
| } |
| |
| void FrameInFlight::OnFrameDecoded(size_t peer, |
| webrtc::Timestamp time, |
| int width, |
| int height, |
| const StreamCodecInfo& used_decoder) { |
| receiver_stats_[peer].decode_end_time = time; |
| receiver_stats_[peer].used_decoder = used_decoder; |
| receiver_stats_[peer].decoded_frame_width = width; |
| receiver_stats_[peer].decoded_frame_height = height; |
| } |
| |
| void FrameInFlight::OnDecoderError(size_t peer, |
| const StreamCodecInfo& used_decoder) { |
| receiver_stats_[peer].decoder_failed = true; |
| receiver_stats_[peer].used_decoder = used_decoder; |
| } |
| |
| bool FrameInFlight::HasDecodeEndTime(size_t peer) const { |
| auto it = receiver_stats_.find(peer); |
| if (it == receiver_stats_.end()) { |
| return false; |
| } |
| return it->second.decode_end_time.IsFinite(); |
| } |
| |
| void FrameInFlight::OnFrameRendered(size_t peer, webrtc::Timestamp time) { |
| receiver_stats_[peer].rendered_time = time; |
| } |
| |
| bool FrameInFlight::HasRenderedTime(size_t peer) const { |
| auto it = receiver_stats_.find(peer); |
| if (it == receiver_stats_.end()) { |
| return false; |
| } |
| return it->second.rendered_time.IsFinite(); |
| } |
| |
| bool FrameInFlight::IsDropped(size_t peer) const { |
| auto it = receiver_stats_.find(peer); |
| if (it == receiver_stats_.end()) { |
| return false; |
| } |
| return it->second.dropped; |
| } |
| |
| FrameStats FrameInFlight::GetStatsForPeer(size_t peer) const { |
| RTC_DCHECK_NE(frame_id_, VideoFrame::kNotSetId) |
| << "Frame id isn't initialized"; |
| RTC_DCHECK(!IsSuperfluous(peer)) |
| << "This frame is superfluous for peer " << peer; |
| FrameStats stats(frame_id_, captured_time_); |
| stats.time_between_captured_frames = time_between_captured_frames_; |
| stats.time_between_encoded_frames = time_between_encoded_frames_; |
| stats.pre_encode_time = pre_encode_time_; |
| stats.encoded_time = encoded_time_; |
| stats.target_encode_bitrate = target_encode_bitrate_; |
| stats.encoded_frame_type = frame_type_; |
| stats.encoded_image_size = encoded_image_size_; |
| stats.used_encoder = used_encoder_; |
| stats.spatial_layers_qp = stream_layers_qp_; |
| |
| absl::optional<ReceiverFrameStats> receiver_stats = |
| MaybeGetValue<ReceiverFrameStats>(receiver_stats_, peer); |
| if (receiver_stats.has_value()) { |
| stats.received_time = receiver_stats->received_time; |
| stats.decode_start_time = receiver_stats->decode_start_time; |
| stats.decode_end_time = receiver_stats->decode_end_time; |
| stats.rendered_time = receiver_stats->rendered_time; |
| stats.prev_frame_rendered_time = receiver_stats->prev_frame_rendered_time; |
| stats.time_between_rendered_frames = |
| receiver_stats->time_between_rendered_frames; |
| stats.decoded_frame_width = receiver_stats->decoded_frame_width; |
| stats.decoded_frame_height = receiver_stats->decoded_frame_height; |
| stats.used_decoder = receiver_stats->used_decoder; |
| stats.pre_decoded_frame_type = receiver_stats->frame_type; |
| stats.pre_decoded_image_size = receiver_stats->encoded_image_size; |
| stats.decoder_failed = receiver_stats->decoder_failed; |
| } |
| return stats; |
| } |
| |
| bool FrameInFlight::IsSuperfluous(size_t peer) const { |
| auto it = receiver_stats_.find(peer); |
| if (it == receiver_stats_.end()) { |
| return false; |
| } |
| return it->second.superfluous; |
| } |
| |
| } // namespace webrtc |