|  | /* | 
|  | *  Copyright (c) 2016 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 "logging/rtc_event_log/rtc_event_log_parser.h" | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <cstdint> | 
|  | #include <cstring> | 
|  | #include <limits> | 
|  | #include <map> | 
|  | #include <optional> | 
|  | #include <set> | 
|  | #include <string> | 
|  | #include <tuple> | 
|  | #include <utility> | 
|  | #include <vector> | 
|  |  | 
|  | #include "absl/strings/string_view.h" | 
|  | #include "api/candidate.h" | 
|  | #include "api/dtls_transport_interface.h" | 
|  | #include "api/rtc_event_log/rtc_event.h" | 
|  | #include "api/rtp_headers.h" | 
|  | #include "api/rtp_parameters.h" | 
|  | #include "api/transport/bandwidth_usage.h" | 
|  | #include "api/units/data_rate.h" | 
|  | #include "api/units/time_delta.h" | 
|  | #include "api/units/timestamp.h" | 
|  | #include "api/video/video_codec_type.h" | 
|  | #include "logging/rtc_event_log/dependency_descriptor_encoder_decoder.h" | 
|  | #include "logging/rtc_event_log/encoder/blob_encoding.h" | 
|  | #include "logging/rtc_event_log/encoder/delta_encoding.h" | 
|  | #include "logging/rtc_event_log/encoder/rtc_event_log_encoder_common.h" | 
|  | #include "logging/rtc_event_log/encoder/var_int.h" | 
|  | #include "logging/rtc_event_log/events/logged_rtp_rtcp.h" | 
|  | #include "logging/rtc_event_log/events/rtc_event_alr_state.h" | 
|  | #include "logging/rtc_event_log/events/rtc_event_audio_network_adaptation.h" | 
|  | #include "logging/rtc_event_log/events/rtc_event_audio_playout.h" | 
|  | #include "logging/rtc_event_log/events/rtc_event_audio_receive_stream_config.h" | 
|  | #include "logging/rtc_event_log/events/rtc_event_audio_send_stream_config.h" | 
|  | #include "logging/rtc_event_log/events/rtc_event_begin_log.h" | 
|  | #include "logging/rtc_event_log/events/rtc_event_bwe_update_delay_based.h" | 
|  | #include "logging/rtc_event_log/events/rtc_event_bwe_update_loss_based.h" | 
|  | #include "logging/rtc_event_log/events/rtc_event_dtls_transport_state.h" | 
|  | #include "logging/rtc_event_log/events/rtc_event_dtls_writable_state.h" | 
|  | #include "logging/rtc_event_log/events/rtc_event_end_log.h" | 
|  | #include "logging/rtc_event_log/events/rtc_event_frame_decoded.h" | 
|  | #include "logging/rtc_event_log/events/rtc_event_generic_ack_received.h" | 
|  | #include "logging/rtc_event_log/events/rtc_event_generic_packet_received.h" | 
|  | #include "logging/rtc_event_log/events/rtc_event_generic_packet_sent.h" | 
|  | #include "logging/rtc_event_log/events/rtc_event_ice_candidate_pair.h" | 
|  | #include "logging/rtc_event_log/events/rtc_event_ice_candidate_pair_config.h" | 
|  | #include "logging/rtc_event_log/events/rtc_event_log_parse_status.h" | 
|  | #include "logging/rtc_event_log/events/rtc_event_probe_cluster_created.h" | 
|  | #include "logging/rtc_event_log/events/rtc_event_probe_result_failure.h" | 
|  | #include "logging/rtc_event_log/events/rtc_event_probe_result_success.h" | 
|  | #include "logging/rtc_event_log/events/rtc_event_remote_estimate.h" | 
|  | #include "logging/rtc_event_log/events/rtc_event_route_change.h" | 
|  | #include "logging/rtc_event_log/events/rtc_event_rtcp_packet_incoming.h" | 
|  | #include "logging/rtc_event_log/events/rtc_event_rtcp_packet_outgoing.h" | 
|  | #include "logging/rtc_event_log/events/rtc_event_rtp_packet_incoming.h" | 
|  | #include "logging/rtc_event_log/events/rtc_event_rtp_packet_outgoing.h" | 
|  | #include "logging/rtc_event_log/events/rtc_event_video_receive_stream_config.h" | 
|  | #include "logging/rtc_event_log/events/rtc_event_video_send_stream_config.h" | 
|  | #include "logging/rtc_event_log/rtc_event_log.pb.h" | 
|  | #include "logging/rtc_event_log/rtc_event_log2.pb.h" | 
|  | #include "logging/rtc_event_log/rtc_event_processor.h" | 
|  | #include "logging/rtc_event_log/rtc_stream_config.h" | 
|  | #include "modules/audio_coding/audio_network_adaptor/include/audio_network_adaptor_config.h" | 
|  | #include "modules/rtp_rtcp/include/rtp_cvo.h" | 
|  | #include "modules/rtp_rtcp/include/rtp_header_extension_map.h" | 
|  | #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" | 
|  | #include "modules/rtp_rtcp/source/ntp_time_util.h" | 
|  | #include "modules/rtp_rtcp/source/rtcp_packet/bye.h" | 
|  | #include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" | 
|  | #include "modules/rtp_rtcp/source/rtcp_packet/congestion_control_feedback.h" | 
|  | #include "modules/rtp_rtcp/source/rtcp_packet/extended_reports.h" | 
|  | #include "modules/rtp_rtcp/source/rtcp_packet/fir.h" | 
|  | #include "modules/rtp_rtcp/source/rtcp_packet/nack.h" | 
|  | #include "modules/rtp_rtcp/source/rtcp_packet/pli.h" | 
|  | #include "modules/rtp_rtcp/source/rtcp_packet/psfb.h" | 
|  | #include "modules/rtp_rtcp/source/rtcp_packet/receiver_report.h" | 
|  | #include "modules/rtp_rtcp/source/rtcp_packet/rtpfb.h" | 
|  | #include "modules/rtp_rtcp/source/rtcp_packet/sender_report.h" | 
|  | #include "modules/rtp_rtcp/source/rtp_dependency_descriptor_extension.h" | 
|  | #include "modules/rtp_rtcp/source/rtp_header_extensions.h" | 
|  | #include "modules/rtp_rtcp/source/rtp_packet_received.h" | 
|  | #include "rtc_base/checks.h" | 
|  | #include "rtc_base/copy_on_write_buffer.h" | 
|  | #include "rtc_base/logging.h" | 
|  | #include "rtc_base/numerics/safe_conversions.h" | 
|  | #include "rtc_base/numerics/sequence_number_unwrapper.h" | 
|  | #include "rtc_base/protobuf_utils.h" | 
|  | #include "rtc_base/system/file_wrapper.h" | 
|  |  | 
|  | using webrtc_event_logging::ToSigned; | 
|  | using webrtc_event_logging::ToUnsigned; | 
|  |  | 
|  | namespace webrtc { | 
|  |  | 
|  | namespace { | 
|  | constexpr size_t kMaxLogSize = 250000000; | 
|  |  | 
|  | constexpr size_t kIpv4Overhead = 20; | 
|  | constexpr size_t kIpv6Overhead = 40; | 
|  | constexpr size_t kUdpOverhead = 8; | 
|  | constexpr size_t kSrtpOverhead = 10; | 
|  | constexpr size_t kStunOverhead = 4; | 
|  | constexpr uint16_t kDefaultOverhead = | 
|  | kUdpOverhead + kSrtpOverhead + kIpv4Overhead; | 
|  |  | 
|  | constexpr char kIncompleteLogError[] = | 
|  | "Could not parse the entire log. Only the beginning will be used."; | 
|  |  | 
|  | struct MediaStreamInfo { | 
|  | MediaStreamInfo() = default; | 
|  | MediaStreamInfo(LoggedMediaType media_type, bool rtx) | 
|  | : media_type(media_type), rtx(rtx) {} | 
|  | LoggedMediaType media_type = LoggedMediaType::kUnknown; | 
|  | bool rtx = false; | 
|  | SeqNumUnwrapper<uint32_t> unwrap_capture_ticks; | 
|  | }; | 
|  |  | 
|  | template <typename Iterable> | 
|  | void AddRecvStreamInfos(std::map<uint32_t, MediaStreamInfo>* streams, | 
|  | const Iterable configs, | 
|  | LoggedMediaType media_type) { | 
|  | for (auto& conf : configs) { | 
|  | streams->insert({conf.config.remote_ssrc, {media_type, false}}); | 
|  | if (conf.config.rtx_ssrc != 0) | 
|  | streams->insert({conf.config.rtx_ssrc, {media_type, true}}); | 
|  | } | 
|  | } | 
|  | template <typename Iterable> | 
|  | void AddSendStreamInfos(std::map<uint32_t, MediaStreamInfo>* streams, | 
|  | const Iterable configs, | 
|  | LoggedMediaType media_type) { | 
|  | for (auto& conf : configs) { | 
|  | streams->insert({conf.config.local_ssrc, {media_type, false}}); | 
|  | if (conf.config.rtx_ssrc != 0) | 
|  | streams->insert({conf.config.rtx_ssrc, {media_type, true}}); | 
|  | } | 
|  | } | 
|  | struct OverheadChangeEvent { | 
|  | Timestamp timestamp; | 
|  | uint16_t overhead; | 
|  | }; | 
|  | std::vector<OverheadChangeEvent> GetOverheadChangingEvents( | 
|  | const std::vector<InferredRouteChangeEvent>& route_changes, | 
|  | PacketDirection direction) { | 
|  | std::vector<OverheadChangeEvent> overheads; | 
|  | for (auto& event : route_changes) { | 
|  | uint16_t new_overhead = direction == PacketDirection::kIncomingPacket | 
|  | ? event.return_overhead | 
|  | : event.send_overhead; | 
|  | if (overheads.empty() || new_overhead != overheads.back().overhead) { | 
|  | overheads.push_back({event.log_time, new_overhead}); | 
|  | } | 
|  | } | 
|  | return overheads; | 
|  | } | 
|  |  | 
|  | bool IdenticalRtcpContents(const std::vector<uint8_t>& last_rtcp, | 
|  | absl::string_view new_rtcp) { | 
|  | if (last_rtcp.size() != new_rtcp.size()) | 
|  | return false; | 
|  | return memcmp(last_rtcp.data(), new_rtcp.data(), new_rtcp.size()) == 0; | 
|  | } | 
|  |  | 
|  | // Conversion functions for legacy wire format. | 
|  | RtcpMode GetRuntimeRtcpMode(rtclog::VideoReceiveConfig::RtcpMode rtcp_mode) { | 
|  | switch (rtcp_mode) { | 
|  | case rtclog::VideoReceiveConfig::RTCP_COMPOUND: | 
|  | return RtcpMode::kCompound; | 
|  | case rtclog::VideoReceiveConfig::RTCP_REDUCEDSIZE: | 
|  | return RtcpMode::kReducedSize; | 
|  | } | 
|  | RTC_DCHECK_NOTREACHED(); | 
|  | return RtcpMode::kOff; | 
|  | } | 
|  |  | 
|  | BandwidthUsage GetRuntimeDetectorState( | 
|  | rtclog::DelayBasedBweUpdate::DetectorState detector_state) { | 
|  | switch (detector_state) { | 
|  | case rtclog::DelayBasedBweUpdate::BWE_NORMAL: | 
|  | return BandwidthUsage::kBwNormal; | 
|  | case rtclog::DelayBasedBweUpdate::BWE_UNDERUSING: | 
|  | return BandwidthUsage::kBwUnderusing; | 
|  | case rtclog::DelayBasedBweUpdate::BWE_OVERUSING: | 
|  | return BandwidthUsage::kBwOverusing; | 
|  | } | 
|  | RTC_DCHECK_NOTREACHED(); | 
|  | return BandwidthUsage::kBwNormal; | 
|  | } | 
|  |  | 
|  | IceCandidatePairConfigType GetRuntimeIceCandidatePairConfigType( | 
|  | rtclog::IceCandidatePairConfig::IceCandidatePairConfigType type) { | 
|  | switch (type) { | 
|  | case rtclog::IceCandidatePairConfig::ADDED: | 
|  | return IceCandidatePairConfigType::kAdded; | 
|  | case rtclog::IceCandidatePairConfig::UPDATED: | 
|  | return IceCandidatePairConfigType::kUpdated; | 
|  | case rtclog::IceCandidatePairConfig::DESTROYED: | 
|  | return IceCandidatePairConfigType::kDestroyed; | 
|  | case rtclog::IceCandidatePairConfig::SELECTED: | 
|  | return IceCandidatePairConfigType::kSelected; | 
|  | } | 
|  | RTC_DCHECK_NOTREACHED(); | 
|  | return IceCandidatePairConfigType::kAdded; | 
|  | } | 
|  |  | 
|  | // Converts a log type (proto based) to a matching `IceCandidateType` value | 
|  | // and checks for validity of the log type (since the enums aren't a perfect | 
|  | // match). | 
|  | bool GetRuntimeIceCandidateType( | 
|  | rtclog::IceCandidatePairConfig::IceCandidateType log_type, | 
|  | IceCandidateType& parsed_type) { | 
|  | switch (log_type) { | 
|  | case rtclog::IceCandidatePairConfig::LOCAL: | 
|  | parsed_type = IceCandidateType::kHost; | 
|  | break; | 
|  | case rtclog::IceCandidatePairConfig::STUN: | 
|  | parsed_type = IceCandidateType::kSrflx; | 
|  | break; | 
|  | case rtclog::IceCandidatePairConfig::PRFLX: | 
|  | parsed_type = IceCandidateType::kPrflx; | 
|  | break; | 
|  | case rtclog::IceCandidatePairConfig::RELAY: | 
|  | parsed_type = IceCandidateType::kRelay; | 
|  | break; | 
|  | default: | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | IceCandidatePairProtocol GetRuntimeIceCandidatePairProtocol( | 
|  | rtclog::IceCandidatePairConfig::Protocol protocol) { | 
|  | switch (protocol) { | 
|  | case rtclog::IceCandidatePairConfig::UDP: | 
|  | return IceCandidatePairProtocol::kUdp; | 
|  | case rtclog::IceCandidatePairConfig::TCP: | 
|  | return IceCandidatePairProtocol::kTcp; | 
|  | case rtclog::IceCandidatePairConfig::SSLTCP: | 
|  | return IceCandidatePairProtocol::kSsltcp; | 
|  | case rtclog::IceCandidatePairConfig::TLS: | 
|  | return IceCandidatePairProtocol::kTls; | 
|  | case rtclog::IceCandidatePairConfig::UNKNOWN_PROTOCOL: | 
|  | return IceCandidatePairProtocol::kUnknown; | 
|  | } | 
|  | RTC_DCHECK_NOTREACHED(); | 
|  | return IceCandidatePairProtocol::kUnknown; | 
|  | } | 
|  |  | 
|  | IceCandidatePairAddressFamily GetRuntimeIceCandidatePairAddressFamily( | 
|  | rtclog::IceCandidatePairConfig::AddressFamily address_family) { | 
|  | switch (address_family) { | 
|  | case rtclog::IceCandidatePairConfig::IPV4: | 
|  | return IceCandidatePairAddressFamily::kIpv4; | 
|  | case rtclog::IceCandidatePairConfig::IPV6: | 
|  | return IceCandidatePairAddressFamily::kIpv6; | 
|  | case rtclog::IceCandidatePairConfig::UNKNOWN_ADDRESS_FAMILY: | 
|  | return IceCandidatePairAddressFamily::kUnknown; | 
|  | } | 
|  | RTC_DCHECK_NOTREACHED(); | 
|  | return IceCandidatePairAddressFamily::kUnknown; | 
|  | } | 
|  |  | 
|  | IceCandidateNetworkType GetRuntimeIceCandidateNetworkType( | 
|  | rtclog::IceCandidatePairConfig::NetworkType network_type) { | 
|  | switch (network_type) { | 
|  | case rtclog::IceCandidatePairConfig::ETHERNET: | 
|  | return IceCandidateNetworkType::kEthernet; | 
|  | case rtclog::IceCandidatePairConfig::LOOPBACK: | 
|  | return IceCandidateNetworkType::kLoopback; | 
|  | case rtclog::IceCandidatePairConfig::WIFI: | 
|  | return IceCandidateNetworkType::kWifi; | 
|  | case rtclog::IceCandidatePairConfig::VPN: | 
|  | return IceCandidateNetworkType::kVpn; | 
|  | case rtclog::IceCandidatePairConfig::CELLULAR: | 
|  | return IceCandidateNetworkType::kCellular; | 
|  | case rtclog::IceCandidatePairConfig::UNKNOWN_NETWORK_TYPE: | 
|  | return IceCandidateNetworkType::kUnknown; | 
|  | } | 
|  | RTC_DCHECK_NOTREACHED(); | 
|  | return IceCandidateNetworkType::kUnknown; | 
|  | } | 
|  |  | 
|  | IceCandidatePairEventType GetRuntimeIceCandidatePairEventType( | 
|  | rtclog::IceCandidatePairEvent::IceCandidatePairEventType type) { | 
|  | switch (type) { | 
|  | case rtclog::IceCandidatePairEvent::CHECK_SENT: | 
|  | return IceCandidatePairEventType::kCheckSent; | 
|  | case rtclog::IceCandidatePairEvent::CHECK_RECEIVED: | 
|  | return IceCandidatePairEventType::kCheckReceived; | 
|  | case rtclog::IceCandidatePairEvent::CHECK_RESPONSE_SENT: | 
|  | return IceCandidatePairEventType::kCheckResponseSent; | 
|  | case rtclog::IceCandidatePairEvent::CHECK_RESPONSE_RECEIVED: | 
|  | return IceCandidatePairEventType::kCheckResponseReceived; | 
|  | } | 
|  | RTC_DCHECK_NOTREACHED(); | 
|  | return IceCandidatePairEventType::kCheckSent; | 
|  | } | 
|  |  | 
|  | VideoCodecType GetRuntimeCodecType(rtclog2::FrameDecodedEvents::Codec codec) { | 
|  | switch (codec) { | 
|  | case rtclog2::FrameDecodedEvents::CODEC_GENERIC: | 
|  | return VideoCodecType::kVideoCodecGeneric; | 
|  | case rtclog2::FrameDecodedEvents::CODEC_VP8: | 
|  | return VideoCodecType::kVideoCodecVP8; | 
|  | case rtclog2::FrameDecodedEvents::CODEC_VP9: | 
|  | return VideoCodecType::kVideoCodecVP9; | 
|  | case rtclog2::FrameDecodedEvents::CODEC_AV1: | 
|  | return VideoCodecType::kVideoCodecAV1; | 
|  | case rtclog2::FrameDecodedEvents::CODEC_H264: | 
|  | return VideoCodecType::kVideoCodecH264; | 
|  | case rtclog2::FrameDecodedEvents::CODEC_H265: | 
|  | return VideoCodecType::kVideoCodecH265; | 
|  | case rtclog2::FrameDecodedEvents::CODEC_UNKNOWN: | 
|  | RTC_LOG(LS_ERROR) << "Unknown codec type. Returning generic."; | 
|  | return VideoCodecType::kVideoCodecGeneric; | 
|  | } | 
|  | RTC_DCHECK_NOTREACHED(); | 
|  | return VideoCodecType::kVideoCodecGeneric; | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus GetHeaderExtensions( | 
|  | std::vector<RtpExtension>* header_extensions, | 
|  | const RepeatedPtrField<rtclog::RtpHeaderExtension>& | 
|  | proto_header_extensions) { | 
|  | header_extensions->clear(); | 
|  | for (auto& p : proto_header_extensions) { | 
|  | RTC_PARSE_CHECK_OR_RETURN(p.has_name()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(p.has_id()); | 
|  | const std::string& name = p.name(); | 
|  | int id = p.id(); | 
|  | header_extensions->push_back(RtpExtension(name, id)); | 
|  | } | 
|  | return ParsedRtcEventLog::ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | template <typename ProtoType, typename LoggedType> | 
|  | ParsedRtcEventLog::ParseStatus StoreRtpPackets( | 
|  | const ProtoType& proto, | 
|  | std::map<uint32_t, std::vector<LoggedType>>* rtp_packets_map) { | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_timestamp_ms()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_marker()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_payload_type()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_sequence_number()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_rtp_timestamp()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_ssrc()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_payload_size()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_header_size()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_padding_size()); | 
|  |  | 
|  | const size_t number_of_deltas = | 
|  | proto.has_number_of_deltas() ? proto.number_of_deltas() : 0u; | 
|  | const size_t total_packets = number_of_deltas + 1; | 
|  |  | 
|  | std::vector<std::vector<uint8_t>> dependency_descriptor_wire_format( | 
|  | total_packets); | 
|  | if (proto.has_dependency_descriptor()) { | 
|  | auto status_or_decoded = | 
|  | RtcEventLogDependencyDescriptorEncoderDecoder::Decode( | 
|  | proto.dependency_descriptor(), total_packets); | 
|  | if (!status_or_decoded.ok()) { | 
|  | return status_or_decoded.status(); | 
|  | } | 
|  | dependency_descriptor_wire_format = status_or_decoded.value(); | 
|  | } | 
|  |  | 
|  | // Base event | 
|  | { | 
|  | RTPHeader header; | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | IsValueInRangeForNumericType<bool>(proto.marker())); | 
|  | header.markerBit = static_cast<bool>(proto.marker()); | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | IsValueInRangeForNumericType<uint8_t>(proto.payload_type())); | 
|  | header.payloadType = static_cast<uint8_t>(proto.payload_type()); | 
|  |  | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | IsValueInRangeForNumericType<uint16_t>(proto.sequence_number())); | 
|  | header.sequenceNumber = static_cast<uint16_t>(proto.sequence_number()); | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | IsValueInRangeForNumericType<uint32_t>(proto.rtp_timestamp())); | 
|  | header.timestamp = static_cast<uint32_t>(proto.rtp_timestamp()); | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | IsValueInRangeForNumericType<uint32_t>(proto.ssrc())); | 
|  | header.ssrc = static_cast<uint32_t>(proto.ssrc()); | 
|  | header.numCSRCs = 0;  // TODO(terelius): Implement CSRC. | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | IsValueInRangeForNumericType<size_t>(proto.padding_size())); | 
|  | header.paddingLength = static_cast<size_t>(proto.padding_size()); | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | IsValueInRangeForNumericType<size_t>(proto.header_size())); | 
|  | header.headerLength = static_cast<size_t>(proto.header_size()); | 
|  | // TODO(terelius): Should we implement payload_type_frequency? | 
|  | if (proto.has_transport_sequence_number()) { | 
|  | header.extension.hasTransportSequenceNumber = true; | 
|  | RTC_PARSE_CHECK_OR_RETURN(IsValueInRangeForNumericType<uint16_t>( | 
|  | proto.transport_sequence_number())); | 
|  | header.extension.transportSequenceNumber = | 
|  | static_cast<uint16_t>(proto.transport_sequence_number()); | 
|  | } | 
|  | if (proto.has_transmission_time_offset()) { | 
|  | header.extension.hasTransmissionTimeOffset = true; | 
|  | RTC_PARSE_CHECK_OR_RETURN(IsValueInRangeForNumericType<int32_t>( | 
|  | proto.transmission_time_offset())); | 
|  | header.extension.transmissionTimeOffset = | 
|  | static_cast<int32_t>(proto.transmission_time_offset()); | 
|  | } | 
|  | if (proto.has_absolute_send_time()) { | 
|  | header.extension.hasAbsoluteSendTime = true; | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | IsValueInRangeForNumericType<uint32_t>(proto.absolute_send_time())); | 
|  | header.extension.absoluteSendTime = | 
|  | static_cast<uint32_t>(proto.absolute_send_time()); | 
|  | } | 
|  | if (proto.has_video_rotation()) { | 
|  | header.extension.hasVideoRotation = true; | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | IsValueInRangeForNumericType<uint8_t>(proto.video_rotation())); | 
|  | header.extension.videoRotation = ConvertCVOByteToVideoRotation( | 
|  | static_cast<uint8_t>(proto.video_rotation())); | 
|  | } | 
|  | if (proto.has_audio_level()) { | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_voice_activity()); | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | IsValueInRangeForNumericType<bool>(proto.voice_activity())); | 
|  | bool voice_activity = static_cast<bool>(proto.voice_activity()); | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | IsValueInRangeForNumericType<int>(proto.audio_level())); | 
|  | int audio_level = static_cast<int>(proto.audio_level()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_LE(audio_level, 0x7F); | 
|  | header.extension.set_audio_level(AudioLevel(voice_activity, audio_level)); | 
|  | } else { | 
|  | RTC_PARSE_CHECK_OR_RETURN(!proto.has_voice_activity()); | 
|  | } | 
|  | LoggedType logged_packet( | 
|  | Timestamp::Millis(proto.timestamp_ms()), header, proto.header_size(), | 
|  | proto.payload_size() + header.headerLength + header.paddingLength); | 
|  | if (!dependency_descriptor_wire_format[0].empty()) { | 
|  | logged_packet.rtp.dependency_descriptor_wire_format = | 
|  | dependency_descriptor_wire_format[0]; | 
|  | } | 
|  | (*rtp_packets_map)[header.ssrc].push_back(std::move(logged_packet)); | 
|  | } | 
|  |  | 
|  | if (number_of_deltas == 0) { | 
|  | return ParsedRtcEventLog::ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | // timestamp_ms (event) | 
|  | std::vector<std::optional<uint64_t>> timestamp_ms_values = | 
|  | DecodeDeltas(proto.timestamp_ms_deltas(), | 
|  | ToUnsigned(proto.timestamp_ms()), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(timestamp_ms_values.size(), number_of_deltas); | 
|  |  | 
|  | // marker (RTP base) | 
|  | std::vector<std::optional<uint64_t>> marker_values = | 
|  | DecodeDeltas(proto.marker_deltas(), proto.marker(), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(marker_values.size(), number_of_deltas); | 
|  |  | 
|  | // payload_type (RTP base) | 
|  | std::vector<std::optional<uint64_t>> payload_type_values = DecodeDeltas( | 
|  | proto.payload_type_deltas(), proto.payload_type(), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(payload_type_values.size(), number_of_deltas); | 
|  |  | 
|  | // sequence_number (RTP base) | 
|  | std::vector<std::optional<uint64_t>> sequence_number_values = | 
|  | DecodeDeltas(proto.sequence_number_deltas(), proto.sequence_number(), | 
|  | number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(sequence_number_values.size(), number_of_deltas); | 
|  |  | 
|  | // rtp_timestamp (RTP base) | 
|  | std::vector<std::optional<uint64_t>> rtp_timestamp_values = DecodeDeltas( | 
|  | proto.rtp_timestamp_deltas(), proto.rtp_timestamp(), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(rtp_timestamp_values.size(), number_of_deltas); | 
|  |  | 
|  | // ssrc (RTP base) | 
|  | std::vector<std::optional<uint64_t>> ssrc_values = | 
|  | DecodeDeltas(proto.ssrc_deltas(), proto.ssrc(), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(ssrc_values.size(), number_of_deltas); | 
|  |  | 
|  | // payload_size (RTP base) | 
|  | std::vector<std::optional<uint64_t>> payload_size_values = DecodeDeltas( | 
|  | proto.payload_size_deltas(), proto.payload_size(), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(payload_size_values.size(), number_of_deltas); | 
|  |  | 
|  | // header_size (RTP base) | 
|  | std::vector<std::optional<uint64_t>> header_size_values = DecodeDeltas( | 
|  | proto.header_size_deltas(), proto.header_size(), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(header_size_values.size(), number_of_deltas); | 
|  |  | 
|  | // padding_size (RTP base) | 
|  | std::vector<std::optional<uint64_t>> padding_size_values = DecodeDeltas( | 
|  | proto.padding_size_deltas(), proto.padding_size(), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(padding_size_values.size(), number_of_deltas); | 
|  |  | 
|  | // transport_sequence_number (RTP extension) | 
|  | std::vector<std::optional<uint64_t>> transport_sequence_number_values; | 
|  | { | 
|  | const std::optional<uint64_t> base_transport_sequence_number = | 
|  | proto.has_transport_sequence_number() | 
|  | ? proto.transport_sequence_number() | 
|  | : std::optional<uint64_t>(); | 
|  | transport_sequence_number_values = | 
|  | DecodeDeltas(proto.transport_sequence_number_deltas(), | 
|  | base_transport_sequence_number, number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(transport_sequence_number_values.size(), | 
|  | number_of_deltas); | 
|  | } | 
|  |  | 
|  | // transmission_time_offset (RTP extension) | 
|  | std::vector<std::optional<uint64_t>> transmission_time_offset_values; | 
|  | { | 
|  | const std::optional<uint64_t> unsigned_base_transmission_time_offset = | 
|  | proto.has_transmission_time_offset() | 
|  | ? ToUnsigned(proto.transmission_time_offset()) | 
|  | : std::optional<uint64_t>(); | 
|  | transmission_time_offset_values = | 
|  | DecodeDeltas(proto.transmission_time_offset_deltas(), | 
|  | unsigned_base_transmission_time_offset, number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(transmission_time_offset_values.size(), | 
|  | number_of_deltas); | 
|  | } | 
|  |  | 
|  | // absolute_send_time (RTP extension) | 
|  | std::vector<std::optional<uint64_t>> absolute_send_time_values; | 
|  | { | 
|  | const std::optional<uint64_t> base_absolute_send_time = | 
|  | proto.has_absolute_send_time() ? proto.absolute_send_time() | 
|  | : std::optional<uint64_t>(); | 
|  | absolute_send_time_values = | 
|  | DecodeDeltas(proto.absolute_send_time_deltas(), base_absolute_send_time, | 
|  | number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(absolute_send_time_values.size(), | 
|  | number_of_deltas); | 
|  | } | 
|  |  | 
|  | // video_rotation (RTP extension) | 
|  | std::vector<std::optional<uint64_t>> video_rotation_values; | 
|  | { | 
|  | const std::optional<uint64_t> base_video_rotation = | 
|  | proto.has_video_rotation() ? proto.video_rotation() | 
|  | : std::optional<uint64_t>(); | 
|  | video_rotation_values = DecodeDeltas(proto.video_rotation_deltas(), | 
|  | base_video_rotation, number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(video_rotation_values.size(), | 
|  | number_of_deltas); | 
|  | } | 
|  |  | 
|  | // audio_level (RTP extension) | 
|  | std::vector<std::optional<uint64_t>> audio_level_values; | 
|  | { | 
|  | const std::optional<uint64_t> base_audio_level = | 
|  | proto.has_audio_level() ? proto.audio_level() | 
|  | : std::optional<uint64_t>(); | 
|  | audio_level_values = DecodeDeltas(proto.audio_level_deltas(), | 
|  | base_audio_level, number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(audio_level_values.size(), number_of_deltas); | 
|  | } | 
|  |  | 
|  | // voice_activity (RTP extension) | 
|  | std::vector<std::optional<uint64_t>> voice_activity_values; | 
|  | { | 
|  | const std::optional<uint64_t> base_voice_activity = | 
|  | proto.has_voice_activity() ? proto.voice_activity() | 
|  | : std::optional<uint64_t>(); | 
|  | voice_activity_values = DecodeDeltas(proto.voice_activity_deltas(), | 
|  | base_voice_activity, number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(voice_activity_values.size(), | 
|  | number_of_deltas); | 
|  | } | 
|  |  | 
|  | // Populate events from decoded deltas | 
|  | for (size_t i = 0; i < number_of_deltas; ++i) { | 
|  | RTC_PARSE_CHECK_OR_RETURN(timestamp_ms_values[i].has_value()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(marker_values[i].has_value()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(payload_type_values[i].has_value()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(sequence_number_values[i].has_value()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(rtp_timestamp_values[i].has_value()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(ssrc_values[i].has_value()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(payload_size_values[i].has_value()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(header_size_values[i].has_value()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(padding_size_values[i].has_value()); | 
|  |  | 
|  | int64_t timestamp_ms; | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | ToSigned(timestamp_ms_values[i].value(), ×tamp_ms)); | 
|  |  | 
|  | RTPHeader header; | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | IsValueInRangeForNumericType<bool>(*marker_values[i])); | 
|  | header.markerBit = static_cast<bool>(*marker_values[i]); | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | IsValueInRangeForNumericType<uint8_t>(*payload_type_values[i])); | 
|  | header.payloadType = static_cast<uint8_t>(*payload_type_values[i]); | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | IsValueInRangeForNumericType<uint16_t>(*sequence_number_values[i])); | 
|  | header.sequenceNumber = static_cast<uint16_t>(*sequence_number_values[i]); | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | IsValueInRangeForNumericType<uint32_t>(*rtp_timestamp_values[i])); | 
|  | header.timestamp = static_cast<uint32_t>(*rtp_timestamp_values[i]); | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | IsValueInRangeForNumericType<uint32_t>(*ssrc_values[i])); | 
|  | header.ssrc = static_cast<uint32_t>(*ssrc_values[i]); | 
|  | header.numCSRCs = 0;  // TODO(terelius): Implement CSRC. | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | IsValueInRangeForNumericType<size_t>(*padding_size_values[i])); | 
|  | header.paddingLength = static_cast<size_t>(*padding_size_values[i]); | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | IsValueInRangeForNumericType<size_t>(*header_size_values[i])); | 
|  | header.headerLength = static_cast<size_t>(*header_size_values[i]); | 
|  | // TODO(terelius): Should we implement payload_type_frequency? | 
|  | if (transport_sequence_number_values.size() > i && | 
|  | transport_sequence_number_values[i].has_value()) { | 
|  | header.extension.hasTransportSequenceNumber = true; | 
|  | RTC_PARSE_CHECK_OR_RETURN(IsValueInRangeForNumericType<uint16_t>( | 
|  | transport_sequence_number_values[i].value())); | 
|  | header.extension.transportSequenceNumber = | 
|  | static_cast<uint16_t>(transport_sequence_number_values[i].value()); | 
|  | } | 
|  | if (transmission_time_offset_values.size() > i && | 
|  | transmission_time_offset_values[i].has_value()) { | 
|  | header.extension.hasTransmissionTimeOffset = true; | 
|  | int32_t transmission_time_offset; | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | ToSigned(transmission_time_offset_values[i].value(), | 
|  | &transmission_time_offset)); | 
|  | header.extension.transmissionTimeOffset = transmission_time_offset; | 
|  | } | 
|  | if (absolute_send_time_values.size() > i && | 
|  | absolute_send_time_values[i].has_value()) { | 
|  | header.extension.hasAbsoluteSendTime = true; | 
|  | RTC_PARSE_CHECK_OR_RETURN(IsValueInRangeForNumericType<uint32_t>( | 
|  | absolute_send_time_values[i].value())); | 
|  | header.extension.absoluteSendTime = | 
|  | static_cast<uint32_t>(absolute_send_time_values[i].value()); | 
|  | } | 
|  | if (video_rotation_values.size() > i && | 
|  | video_rotation_values[i].has_value()) { | 
|  | header.extension.hasVideoRotation = true; | 
|  | RTC_PARSE_CHECK_OR_RETURN(IsValueInRangeForNumericType<uint8_t>( | 
|  | video_rotation_values[i].value())); | 
|  | header.extension.videoRotation = ConvertCVOByteToVideoRotation( | 
|  | static_cast<uint8_t>(video_rotation_values[i].value())); | 
|  | } | 
|  | if (audio_level_values.size() > i && audio_level_values[i].has_value()) { | 
|  | RTC_PARSE_CHECK_OR_RETURN(voice_activity_values.size() > i && | 
|  | voice_activity_values[i].has_value()); | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | IsValueInRangeForNumericType<bool>(voice_activity_values[i].value())); | 
|  | bool voice_activity = static_cast<bool>(voice_activity_values[i].value()); | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | IsValueInRangeForNumericType<int>(audio_level_values[i].value())); | 
|  | int audio_level = static_cast<int>(audio_level_values[i].value()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_LE(audio_level, 0x7F); | 
|  | header.extension.set_audio_level(AudioLevel(voice_activity, audio_level)); | 
|  | } else { | 
|  | RTC_PARSE_CHECK_OR_RETURN(voice_activity_values.size() <= i || | 
|  | !voice_activity_values[i].has_value()); | 
|  | } | 
|  | LoggedType logged_packet(Timestamp::Millis(timestamp_ms), header, | 
|  | header.headerLength, | 
|  | payload_size_values[i].value() + | 
|  | header.headerLength + header.paddingLength); | 
|  | if (!dependency_descriptor_wire_format[i + 1].empty()) { | 
|  | logged_packet.rtp.dependency_descriptor_wire_format = | 
|  | dependency_descriptor_wire_format[i + 1]; | 
|  | } | 
|  | (*rtp_packets_map)[header.ssrc].push_back(std::move(logged_packet)); | 
|  | } | 
|  | return ParsedRtcEventLog::ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | template <typename ProtoType, typename LoggedType> | 
|  | ParsedRtcEventLog::ParseStatus StoreRtcpPackets( | 
|  | const ProtoType& proto, | 
|  | std::vector<LoggedType>* rtcp_packets, | 
|  | bool remove_duplicates) { | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_timestamp_ms()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_raw_packet()); | 
|  |  | 
|  | // TODO(terelius): Incoming RTCP may be delivered once for audio and once | 
|  | // for video. As a work around, we remove the duplicated packets since they | 
|  | // cause problems when analyzing the log or feeding it into the transport | 
|  | // feedback adapter. | 
|  | if (!remove_duplicates || rtcp_packets->empty() || | 
|  | !IdenticalRtcpContents(rtcp_packets->back().rtcp.raw_data, | 
|  | proto.raw_packet())) { | 
|  | // Base event | 
|  | rtcp_packets->emplace_back(Timestamp::Millis(proto.timestamp_ms()), | 
|  | proto.raw_packet()); | 
|  | } | 
|  |  | 
|  | const size_t number_of_deltas = | 
|  | proto.has_number_of_deltas() ? proto.number_of_deltas() : 0u; | 
|  | if (number_of_deltas == 0) { | 
|  | return ParsedRtcEventLog::ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | // timestamp_ms | 
|  | std::vector<std::optional<uint64_t>> timestamp_ms_values = | 
|  | DecodeDeltas(proto.timestamp_ms_deltas(), | 
|  | ToUnsigned(proto.timestamp_ms()), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(timestamp_ms_values.size(), number_of_deltas); | 
|  |  | 
|  | // raw_packet | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_raw_packet_blobs()); | 
|  | std::vector<absl::string_view> raw_packet_values = | 
|  | DecodeBlobs(proto.raw_packet_blobs(), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(raw_packet_values.size(), number_of_deltas); | 
|  |  | 
|  | // Populate events from decoded deltas | 
|  | for (size_t i = 0; i < number_of_deltas; ++i) { | 
|  | RTC_PARSE_CHECK_OR_RETURN(timestamp_ms_values[i].has_value()); | 
|  | int64_t timestamp_ms; | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | ToSigned(timestamp_ms_values[i].value(), ×tamp_ms)); | 
|  |  | 
|  | // TODO(terelius): Incoming RTCP may be delivered once for audio and once | 
|  | // for video. As a work around, we remove the duplicated packets since they | 
|  | // cause problems when analyzing the log or feeding it into the transport | 
|  | // feedback adapter. | 
|  | if (remove_duplicates && !rtcp_packets->empty() && | 
|  | IdenticalRtcpContents(rtcp_packets->back().rtcp.raw_data, | 
|  | raw_packet_values[i])) { | 
|  | continue; | 
|  | } | 
|  | std::string data(raw_packet_values[i]); | 
|  | rtcp_packets->emplace_back(Timestamp::Millis(timestamp_ms), data); | 
|  | } | 
|  | return ParsedRtcEventLog::ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus StoreRtcpBlocks( | 
|  | int64_t timestamp_us, | 
|  | const uint8_t* packet_begin, | 
|  | const uint8_t* packet_end, | 
|  | std::vector<LoggedRtcpPacketSenderReport>* sr_list, | 
|  | std::vector<LoggedRtcpPacketReceiverReport>* rr_list, | 
|  | std::vector<LoggedRtcpPacketExtendedReports>* xr_list, | 
|  | std::vector<LoggedRtcpPacketRemb>* remb_list, | 
|  | std::vector<LoggedRtcpPacketNack>* nack_list, | 
|  | std::vector<LoggedRtcpPacketFir>* fir_list, | 
|  | std::vector<LoggedRtcpPacketPli>* pli_list, | 
|  | std::vector<LoggedRtcpPacketBye>* bye_list, | 
|  | std::vector<LoggedRtcpPacketTransportFeedback>* transport_feedback_list, | 
|  | std::vector<LoggedRtcpCongestionControlFeedback>* congestion_feedback_list, | 
|  | std::vector<LoggedRtcpPacketLossNotification>* loss_notification_list) { | 
|  | Timestamp timestamp = Timestamp::Micros(timestamp_us); | 
|  | rtcp::CommonHeader header; | 
|  | for (const uint8_t* block = packet_begin; block < packet_end; | 
|  | block = header.NextPacket()) { | 
|  | RTC_PARSE_CHECK_OR_RETURN(header.Parse(block, packet_end - block)); | 
|  | if (header.type() == rtcp::TransportFeedback::kPacketType && | 
|  | header.fmt() == rtcp::TransportFeedback::kFeedbackMessageType) { | 
|  | LoggedRtcpPacketTransportFeedback parsed_block; | 
|  | parsed_block.timestamp = timestamp; | 
|  | RTC_PARSE_CHECK_OR_RETURN(parsed_block.transport_feedback.Parse(header)); | 
|  | transport_feedback_list->push_back(std::move(parsed_block)); | 
|  | } else if (header.type() == rtcp::Rtpfb::kPacketType && | 
|  | header.fmt() == | 
|  | rtcp::CongestionControlFeedback::kFeedbackMessageType) { | 
|  | rtcp::CongestionControlFeedback feedback; | 
|  | RTC_PARSE_CHECK_OR_RETURN(feedback.Parse(header)); | 
|  | congestion_feedback_list->emplace_back(timestamp, std::move(feedback)); | 
|  | } else if (header.type() == rtcp::SenderReport::kPacketType) { | 
|  | LoggedRtcpPacketSenderReport parsed_block; | 
|  | parsed_block.timestamp = timestamp; | 
|  | RTC_PARSE_CHECK_OR_RETURN(parsed_block.sr.Parse(header)); | 
|  | sr_list->push_back(std::move(parsed_block)); | 
|  | } else if (header.type() == rtcp::ReceiverReport::kPacketType) { | 
|  | LoggedRtcpPacketReceiverReport parsed_block; | 
|  | parsed_block.timestamp = timestamp; | 
|  | RTC_PARSE_CHECK_OR_RETURN(parsed_block.rr.Parse(header)); | 
|  | rr_list->push_back(std::move(parsed_block)); | 
|  | } else if (header.type() == rtcp::ExtendedReports::kPacketType) { | 
|  | LoggedRtcpPacketExtendedReports parsed_block; | 
|  | parsed_block.timestamp = timestamp; | 
|  | RTC_PARSE_CHECK_OR_RETURN(parsed_block.xr.Parse(header)); | 
|  | xr_list->push_back(std::move(parsed_block)); | 
|  | } else if (header.type() == rtcp::Fir::kPacketType && | 
|  | header.fmt() == rtcp::Fir::kFeedbackMessageType) { | 
|  | LoggedRtcpPacketFir parsed_block; | 
|  | parsed_block.timestamp = timestamp; | 
|  | RTC_PARSE_CHECK_OR_RETURN(parsed_block.fir.Parse(header)); | 
|  | fir_list->push_back(std::move(parsed_block)); | 
|  | } else if (header.type() == rtcp::Pli::kPacketType && | 
|  | header.fmt() == rtcp::Pli::kFeedbackMessageType) { | 
|  | LoggedRtcpPacketPli parsed_block; | 
|  | parsed_block.timestamp = timestamp; | 
|  | RTC_PARSE_CHECK_OR_RETURN(parsed_block.pli.Parse(header)); | 
|  | pli_list->push_back(std::move(parsed_block)); | 
|  | } else if (header.type() == rtcp::Bye::kPacketType) { | 
|  | LoggedRtcpPacketBye parsed_block; | 
|  | parsed_block.timestamp = timestamp; | 
|  | RTC_PARSE_CHECK_OR_RETURN(parsed_block.bye.Parse(header)); | 
|  | bye_list->push_back(std::move(parsed_block)); | 
|  | } else if (header.type() == rtcp::Psfb::kPacketType && | 
|  | header.fmt() == rtcp::Psfb::kAfbMessageType) { | 
|  | bool type_found = false; | 
|  | if (!type_found) { | 
|  | LoggedRtcpPacketRemb parsed_block; | 
|  | parsed_block.timestamp = timestamp; | 
|  | if (parsed_block.remb.Parse(header)) { | 
|  | remb_list->push_back(std::move(parsed_block)); | 
|  | type_found = true; | 
|  | } | 
|  | } | 
|  | if (!type_found) { | 
|  | LoggedRtcpPacketLossNotification parsed_block; | 
|  | parsed_block.timestamp = timestamp; | 
|  | if (parsed_block.loss_notification.Parse(header)) { | 
|  | loss_notification_list->push_back(std::move(parsed_block)); | 
|  | type_found = true; | 
|  | } | 
|  | } | 
|  | // We ignore other application-layer feedback types. | 
|  | } else if (header.type() == rtcp::Nack::kPacketType && | 
|  | header.fmt() == rtcp::Nack::kFeedbackMessageType) { | 
|  | LoggedRtcpPacketNack parsed_block; | 
|  | parsed_block.timestamp = timestamp; | 
|  | RTC_PARSE_CHECK_OR_RETURN(parsed_block.nack.Parse(header)); | 
|  | nack_list->push_back(std::move(parsed_block)); | 
|  | } | 
|  | } | 
|  | return ParsedRtcEventLog::ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | // Conversion functions for version 2 of the wire format. | 
|  | BandwidthUsage GetRuntimeDetectorState( | 
|  | rtclog2::DelayBasedBweUpdates::DetectorState detector_state) { | 
|  | switch (detector_state) { | 
|  | case rtclog2::DelayBasedBweUpdates::BWE_NORMAL: | 
|  | return BandwidthUsage::kBwNormal; | 
|  | case rtclog2::DelayBasedBweUpdates::BWE_UNDERUSING: | 
|  | return BandwidthUsage::kBwUnderusing; | 
|  | case rtclog2::DelayBasedBweUpdates::BWE_OVERUSING: | 
|  | return BandwidthUsage::kBwOverusing; | 
|  | case rtclog2::DelayBasedBweUpdates::BWE_UNKNOWN_STATE: | 
|  | break; | 
|  | } | 
|  | RTC_DCHECK_NOTREACHED(); | 
|  | return BandwidthUsage::kBwNormal; | 
|  | } | 
|  |  | 
|  | ProbeFailureReason GetRuntimeProbeFailureReason( | 
|  | rtclog2::BweProbeResultFailure::FailureReason failure) { | 
|  | switch (failure) { | 
|  | case rtclog2::BweProbeResultFailure::INVALID_SEND_RECEIVE_INTERVAL: | 
|  | return ProbeFailureReason::kInvalidSendReceiveInterval; | 
|  | case rtclog2::BweProbeResultFailure::INVALID_SEND_RECEIVE_RATIO: | 
|  | return ProbeFailureReason::kInvalidSendReceiveRatio; | 
|  | case rtclog2::BweProbeResultFailure::TIMEOUT: | 
|  | return ProbeFailureReason::kTimeout; | 
|  | case rtclog2::BweProbeResultFailure::UNKNOWN: | 
|  | break; | 
|  | } | 
|  | RTC_DCHECK_NOTREACHED(); | 
|  | return ProbeFailureReason::kTimeout; | 
|  | } | 
|  |  | 
|  | DtlsTransportState GetRuntimeDtlsTransportState( | 
|  | rtclog2::DtlsTransportStateEvent::DtlsTransportState state) { | 
|  | switch (state) { | 
|  | case rtclog2::DtlsTransportStateEvent::DTLS_TRANSPORT_NEW: | 
|  | return DtlsTransportState::kNew; | 
|  | case rtclog2::DtlsTransportStateEvent::DTLS_TRANSPORT_CONNECTING: | 
|  | return DtlsTransportState::kConnecting; | 
|  | case rtclog2::DtlsTransportStateEvent::DTLS_TRANSPORT_CONNECTED: | 
|  | return DtlsTransportState::kConnected; | 
|  | case rtclog2::DtlsTransportStateEvent::DTLS_TRANSPORT_CLOSED: | 
|  | return DtlsTransportState::kClosed; | 
|  | case rtclog2::DtlsTransportStateEvent::DTLS_TRANSPORT_FAILED: | 
|  | return DtlsTransportState::kFailed; | 
|  | case rtclog2::DtlsTransportStateEvent::UNKNOWN_DTLS_TRANSPORT_STATE: | 
|  | RTC_DCHECK_NOTREACHED(); | 
|  | return DtlsTransportState::kNumValues; | 
|  | } | 
|  | RTC_DCHECK_NOTREACHED(); | 
|  | return DtlsTransportState::kNumValues; | 
|  | } | 
|  |  | 
|  | IceCandidatePairConfigType GetRuntimeIceCandidatePairConfigType( | 
|  | rtclog2::IceCandidatePairConfig::IceCandidatePairConfigType type) { | 
|  | switch (type) { | 
|  | case rtclog2::IceCandidatePairConfig::ADDED: | 
|  | return IceCandidatePairConfigType::kAdded; | 
|  | case rtclog2::IceCandidatePairConfig::UPDATED: | 
|  | return IceCandidatePairConfigType::kUpdated; | 
|  | case rtclog2::IceCandidatePairConfig::DESTROYED: | 
|  | return IceCandidatePairConfigType::kDestroyed; | 
|  | case rtclog2::IceCandidatePairConfig::SELECTED: | 
|  | return IceCandidatePairConfigType::kSelected; | 
|  | case rtclog2::IceCandidatePairConfig::UNKNOWN_CONFIG_TYPE: | 
|  | break; | 
|  | } | 
|  | RTC_DCHECK_NOTREACHED(); | 
|  | return IceCandidatePairConfigType::kAdded; | 
|  | } | 
|  |  | 
|  | IceCandidateType GetRuntimeIceCandidateType( | 
|  | rtclog2::IceCandidatePairConfig::IceCandidateType type) { | 
|  | switch (type) { | 
|  | case rtclog2::IceCandidatePairConfig::LOCAL: | 
|  | return IceCandidateType::kHost; | 
|  | case rtclog2::IceCandidatePairConfig::STUN: | 
|  | return IceCandidateType::kSrflx; | 
|  | case rtclog2::IceCandidatePairConfig::PRFLX: | 
|  | return IceCandidateType::kPrflx; | 
|  | case rtclog2::IceCandidatePairConfig::RELAY: | 
|  | return IceCandidateType::kRelay; | 
|  | default: | 
|  | RTC_DCHECK_NOTREACHED(); | 
|  | return IceCandidateType::kHost; | 
|  | } | 
|  | } | 
|  |  | 
|  | bool GetRuntimeIceCandidateType( | 
|  | rtclog2::IceCandidatePairConfig::IceCandidateType log_type, | 
|  | IceCandidateType& parsed_type) { | 
|  | switch (log_type) { | 
|  | case rtclog2::IceCandidatePairConfig::LOCAL: | 
|  | parsed_type = IceCandidateType::kHost; | 
|  | break; | 
|  | case rtclog2::IceCandidatePairConfig::STUN: | 
|  | parsed_type = IceCandidateType::kSrflx; | 
|  | break; | 
|  | case rtclog2::IceCandidatePairConfig::PRFLX: | 
|  | parsed_type = IceCandidateType::kPrflx; | 
|  | break; | 
|  | case rtclog2::IceCandidatePairConfig::RELAY: | 
|  | parsed_type = IceCandidateType::kRelay; | 
|  | break; | 
|  | default: | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | IceCandidatePairProtocol GetRuntimeIceCandidatePairProtocol( | 
|  | rtclog2::IceCandidatePairConfig::Protocol protocol) { | 
|  | switch (protocol) { | 
|  | case rtclog2::IceCandidatePairConfig::UDP: | 
|  | return IceCandidatePairProtocol::kUdp; | 
|  | case rtclog2::IceCandidatePairConfig::TCP: | 
|  | return IceCandidatePairProtocol::kTcp; | 
|  | case rtclog2::IceCandidatePairConfig::SSLTCP: | 
|  | return IceCandidatePairProtocol::kSsltcp; | 
|  | case rtclog2::IceCandidatePairConfig::TLS: | 
|  | return IceCandidatePairProtocol::kTls; | 
|  | case rtclog2::IceCandidatePairConfig::UNKNOWN_PROTOCOL: | 
|  | return IceCandidatePairProtocol::kUnknown; | 
|  | } | 
|  | RTC_DCHECK_NOTREACHED(); | 
|  | return IceCandidatePairProtocol::kUnknown; | 
|  | } | 
|  |  | 
|  | IceCandidatePairAddressFamily GetRuntimeIceCandidatePairAddressFamily( | 
|  | rtclog2::IceCandidatePairConfig::AddressFamily address_family) { | 
|  | switch (address_family) { | 
|  | case rtclog2::IceCandidatePairConfig::IPV4: | 
|  | return IceCandidatePairAddressFamily::kIpv4; | 
|  | case rtclog2::IceCandidatePairConfig::IPV6: | 
|  | return IceCandidatePairAddressFamily::kIpv6; | 
|  | case rtclog2::IceCandidatePairConfig::UNKNOWN_ADDRESS_FAMILY: | 
|  | return IceCandidatePairAddressFamily::kUnknown; | 
|  | } | 
|  | RTC_DCHECK_NOTREACHED(); | 
|  | return IceCandidatePairAddressFamily::kUnknown; | 
|  | } | 
|  |  | 
|  | IceCandidateNetworkType GetRuntimeIceCandidateNetworkType( | 
|  | rtclog2::IceCandidatePairConfig::NetworkType network_type) { | 
|  | switch (network_type) { | 
|  | case rtclog2::IceCandidatePairConfig::ETHERNET: | 
|  | return IceCandidateNetworkType::kEthernet; | 
|  | case rtclog2::IceCandidatePairConfig::LOOPBACK: | 
|  | return IceCandidateNetworkType::kLoopback; | 
|  | case rtclog2::IceCandidatePairConfig::WIFI: | 
|  | return IceCandidateNetworkType::kWifi; | 
|  | case rtclog2::IceCandidatePairConfig::VPN: | 
|  | return IceCandidateNetworkType::kVpn; | 
|  | case rtclog2::IceCandidatePairConfig::CELLULAR: | 
|  | return IceCandidateNetworkType::kCellular; | 
|  | case rtclog2::IceCandidatePairConfig::UNKNOWN_NETWORK_TYPE: | 
|  | return IceCandidateNetworkType::kUnknown; | 
|  | } | 
|  | RTC_DCHECK_NOTREACHED(); | 
|  | return IceCandidateNetworkType::kUnknown; | 
|  | } | 
|  |  | 
|  | IceCandidatePairEventType GetRuntimeIceCandidatePairEventType( | 
|  | rtclog2::IceCandidatePairEvent::IceCandidatePairEventType type) { | 
|  | switch (type) { | 
|  | case rtclog2::IceCandidatePairEvent::CHECK_SENT: | 
|  | return IceCandidatePairEventType::kCheckSent; | 
|  | case rtclog2::IceCandidatePairEvent::CHECK_RECEIVED: | 
|  | return IceCandidatePairEventType::kCheckReceived; | 
|  | case rtclog2::IceCandidatePairEvent::CHECK_RESPONSE_SENT: | 
|  | return IceCandidatePairEventType::kCheckResponseSent; | 
|  | case rtclog2::IceCandidatePairEvent::CHECK_RESPONSE_RECEIVED: | 
|  | return IceCandidatePairEventType::kCheckResponseReceived; | 
|  | case rtclog2::IceCandidatePairEvent::UNKNOWN_CHECK_TYPE: | 
|  | break; | 
|  | } | 
|  | RTC_DCHECK_NOTREACHED(); | 
|  | return IceCandidatePairEventType::kCheckSent; | 
|  | } | 
|  |  | 
|  | std::vector<RtpExtension> GetRuntimeRtpHeaderExtensionConfig( | 
|  | const rtclog2::RtpHeaderExtensionConfig& proto_header_extensions) { | 
|  | std::vector<RtpExtension> rtp_extensions; | 
|  | if (proto_header_extensions.has_transmission_time_offset_id()) { | 
|  | rtp_extensions.emplace_back( | 
|  | RtpExtension::kTimestampOffsetUri, | 
|  | proto_header_extensions.transmission_time_offset_id()); | 
|  | } | 
|  | if (proto_header_extensions.has_absolute_send_time_id()) { | 
|  | rtp_extensions.emplace_back( | 
|  | RtpExtension::kAbsSendTimeUri, | 
|  | proto_header_extensions.absolute_send_time_id()); | 
|  | } | 
|  | if (proto_header_extensions.has_transport_sequence_number_id()) { | 
|  | rtp_extensions.emplace_back( | 
|  | RtpExtension::kTransportSequenceNumberUri, | 
|  | proto_header_extensions.transport_sequence_number_id()); | 
|  | } | 
|  | if (proto_header_extensions.has_audio_level_id()) { | 
|  | rtp_extensions.emplace_back(RtpExtension::kAudioLevelUri, | 
|  | proto_header_extensions.audio_level_id()); | 
|  | } | 
|  | if (proto_header_extensions.has_video_rotation_id()) { | 
|  | rtp_extensions.emplace_back(RtpExtension::kVideoRotationUri, | 
|  | proto_header_extensions.video_rotation_id()); | 
|  | } | 
|  | if (proto_header_extensions.has_dependency_descriptor_id()) { | 
|  | rtp_extensions.emplace_back( | 
|  | RtpExtension::kDependencyDescriptorUri, | 
|  | proto_header_extensions.dependency_descriptor_id()); | 
|  | } | 
|  | return rtp_extensions; | 
|  | } | 
|  | // End of conversion functions. | 
|  |  | 
|  | LoggedPacketInfo::LoggedPacketInfo(const LoggedRtpPacket& rtp, | 
|  | LoggedMediaType media_type, | 
|  | bool rtx, | 
|  | Timestamp capture_time) | 
|  | : ssrc(rtp.header.ssrc), | 
|  | stream_seq_no(rtp.header.sequenceNumber), | 
|  | size(static_cast<uint16_t>(rtp.total_length)), | 
|  | payload_size(static_cast<uint16_t>(rtp.total_length - | 
|  | rtp.header.paddingLength - | 
|  | rtp.header.headerLength)), | 
|  | padding_size(static_cast<uint16_t>(rtp.header.paddingLength)), | 
|  | payload_type(rtp.header.payloadType), | 
|  | media_type(media_type), | 
|  | rtx(rtx), | 
|  | marker_bit(rtp.header.markerBit), | 
|  | has_transport_seq_no(rtp.header.extension.hasTransportSequenceNumber), | 
|  | transport_seq_no(static_cast<uint16_t>( | 
|  | has_transport_seq_no ? rtp.header.extension.transportSequenceNumber | 
|  | : 0)), | 
|  | capture_time(capture_time), | 
|  | log_packet_time(Timestamp::Micros(rtp.log_time_us())), | 
|  | reported_send_time(rtp.header.extension.hasAbsoluteSendTime | 
|  | ? rtp.header.extension.GetAbsoluteSendTimestamp() | 
|  | : Timestamp::MinusInfinity()) {} | 
|  |  | 
|  | LoggedPacketInfo::LoggedPacketInfo(const LoggedPacketInfo&) = default; | 
|  |  | 
|  | LoggedPacketInfo::~LoggedPacketInfo() {} | 
|  |  | 
|  | ParsedRtcEventLog::~ParsedRtcEventLog() = default; | 
|  |  | 
|  | ParsedRtcEventLog::LoggedRtpStreamIncoming::LoggedRtpStreamIncoming() = default; | 
|  | ParsedRtcEventLog::LoggedRtpStreamIncoming::LoggedRtpStreamIncoming( | 
|  | const LoggedRtpStreamIncoming& rhs) = default; | 
|  | ParsedRtcEventLog::LoggedRtpStreamIncoming::~LoggedRtpStreamIncoming() = | 
|  | default; | 
|  |  | 
|  | ParsedRtcEventLog::LoggedRtpStreamOutgoing::LoggedRtpStreamOutgoing() = default; | 
|  | ParsedRtcEventLog::LoggedRtpStreamOutgoing::LoggedRtpStreamOutgoing( | 
|  | const LoggedRtpStreamOutgoing& rhs) = default; | 
|  | ParsedRtcEventLog::LoggedRtpStreamOutgoing::~LoggedRtpStreamOutgoing() = | 
|  | default; | 
|  |  | 
|  | ParsedRtcEventLog::LoggedRtpStreamView::LoggedRtpStreamView( | 
|  | uint32_t ssrc, | 
|  | const std::vector<LoggedRtpPacketIncoming>& packets) | 
|  | : ssrc(ssrc), packet_view() { | 
|  | for (const LoggedRtpPacketIncoming& packet : packets) { | 
|  | packet_view.push_back(&(packet.rtp)); | 
|  | } | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::LoggedRtpStreamView::LoggedRtpStreamView( | 
|  | uint32_t ssrc, | 
|  | const std::vector<LoggedRtpPacketOutgoing>& packets) | 
|  | : ssrc(ssrc), packet_view() { | 
|  | for (const LoggedRtpPacketOutgoing& packet : packets) { | 
|  | packet_view.push_back(&(packet.rtp)); | 
|  | } | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::LoggedRtpStreamView::LoggedRtpStreamView( | 
|  | const LoggedRtpStreamView&) = default; | 
|  |  | 
|  | // Return default values for header extensions, to use on streams without stored | 
|  | // mapping data. Currently this only applies to audio streams, since the mapping | 
|  | // is not stored in the event log. | 
|  | // TODO(ivoc): Remove this once this mapping is stored in the event log for | 
|  | //             audio streams. Tracking bug: webrtc:6399 | 
|  | RtpHeaderExtensionMap ParsedRtcEventLog::GetDefaultHeaderExtensionMap() { | 
|  | // Values from before the default RTP header extension IDs were removed. | 
|  | constexpr int kAudioLevelDefaultId = 1; | 
|  | constexpr int kTimestampOffsetDefaultId = 2; | 
|  | constexpr int kAbsSendTimeDefaultId = 3; | 
|  | constexpr int kVideoRotationDefaultId = 4; | 
|  | constexpr int kTransportSequenceNumberDefaultId = 5; | 
|  | constexpr int kPlayoutDelayDefaultId = 6; | 
|  | constexpr int kVideoContentTypeDefaultId = 7; | 
|  | constexpr int kVideoTimingDefaultId = 8; | 
|  | constexpr int kDependencyDescriptorDefaultId = 9; | 
|  |  | 
|  | RtpHeaderExtensionMap default_map(/*extmap_allow_mixed=*/true); | 
|  | default_map.Register<AudioLevelExtension>(kAudioLevelDefaultId); | 
|  | default_map.Register<TransmissionOffset>(kTimestampOffsetDefaultId); | 
|  | default_map.Register<AbsoluteSendTime>(kAbsSendTimeDefaultId); | 
|  | default_map.Register<VideoOrientation>(kVideoRotationDefaultId); | 
|  | default_map.Register<TransportSequenceNumber>( | 
|  | kTransportSequenceNumberDefaultId); | 
|  | default_map.Register<PlayoutDelayLimits>(kPlayoutDelayDefaultId); | 
|  | default_map.Register<VideoContentTypeExtension>(kVideoContentTypeDefaultId); | 
|  | default_map.Register<VideoTimingExtension>(kVideoTimingDefaultId); | 
|  | default_map.Register<RtpDependencyDescriptorExtension>( | 
|  | kDependencyDescriptorDefaultId); | 
|  | return default_map; | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParsedRtcEventLog( | 
|  | UnconfiguredHeaderExtensions parse_unconfigured_header_extensions, | 
|  | bool allow_incomplete_logs) | 
|  | : parse_unconfigured_header_extensions_( | 
|  | parse_unconfigured_header_extensions), | 
|  | allow_incomplete_logs_(allow_incomplete_logs) { | 
|  | Clear(); | 
|  | } | 
|  |  | 
|  | void ParsedRtcEventLog::Clear() { | 
|  | default_extension_map_ = GetDefaultHeaderExtensionMap(); | 
|  |  | 
|  | incoming_rtx_ssrcs_.clear(); | 
|  | incoming_video_ssrcs_.clear(); | 
|  | incoming_audio_ssrcs_.clear(); | 
|  | outgoing_rtx_ssrcs_.clear(); | 
|  | outgoing_video_ssrcs_.clear(); | 
|  | outgoing_audio_ssrcs_.clear(); | 
|  |  | 
|  | incoming_rtp_packets_map_.clear(); | 
|  | outgoing_rtp_packets_map_.clear(); | 
|  | incoming_rtp_packets_by_ssrc_.clear(); | 
|  | outgoing_rtp_packets_by_ssrc_.clear(); | 
|  | incoming_rtp_packet_views_by_ssrc_.clear(); | 
|  | outgoing_rtp_packet_views_by_ssrc_.clear(); | 
|  |  | 
|  | incoming_rtcp_packets_.clear(); | 
|  | outgoing_rtcp_packets_.clear(); | 
|  |  | 
|  | incoming_rr_.clear(); | 
|  | outgoing_rr_.clear(); | 
|  | incoming_sr_.clear(); | 
|  | outgoing_sr_.clear(); | 
|  | incoming_nack_.clear(); | 
|  | outgoing_nack_.clear(); | 
|  | incoming_remb_.clear(); | 
|  | outgoing_remb_.clear(); | 
|  | incoming_transport_feedback_.clear(); | 
|  | outgoing_transport_feedback_.clear(); | 
|  | incoming_loss_notification_.clear(); | 
|  | outgoing_loss_notification_.clear(); | 
|  |  | 
|  | start_log_events_.clear(); | 
|  | stop_log_events_.clear(); | 
|  | audio_playout_events_.clear(); | 
|  | neteq_set_minimum_delay_events_.clear(); | 
|  | audio_network_adaptation_events_.clear(); | 
|  | bwe_probe_cluster_created_events_.clear(); | 
|  | bwe_probe_failure_events_.clear(); | 
|  | bwe_probe_success_events_.clear(); | 
|  | bwe_delay_updates_.clear(); | 
|  | bwe_loss_updates_.clear(); | 
|  | dtls_transport_states_.clear(); | 
|  | dtls_writable_states_.clear(); | 
|  | decoded_frames_.clear(); | 
|  | alr_state_events_.clear(); | 
|  | ice_candidate_pair_configs_.clear(); | 
|  | ice_candidate_pair_events_.clear(); | 
|  | audio_recv_configs_.clear(); | 
|  | audio_send_configs_.clear(); | 
|  | video_recv_configs_.clear(); | 
|  | video_send_configs_.clear(); | 
|  |  | 
|  | last_incoming_rtcp_packet_.clear(); | 
|  |  | 
|  | first_timestamp_ = Timestamp::PlusInfinity(); | 
|  | last_timestamp_ = Timestamp::MinusInfinity(); | 
|  | first_log_segment_ = LogSegment(0, std::numeric_limits<int64_t>::max()); | 
|  |  | 
|  | incoming_rtp_extensions_maps_.clear(); | 
|  | outgoing_rtp_extensions_maps_.clear(); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::ParseFile( | 
|  | absl::string_view filename) { | 
|  | FileWrapper file = FileWrapper::OpenReadOnly(filename); | 
|  | if (!file.is_open()) { | 
|  | RTC_LOG(LS_WARNING) << "Could not open file " << filename | 
|  | << " for reading."; | 
|  | RTC_PARSE_CHECK_OR_RETURN(file.is_open()); | 
|  | } | 
|  |  | 
|  | // Compute file size. | 
|  | std::optional<size_t> file_size = file.FileSize(); | 
|  | RTC_PARSE_CHECK_OR_RETURN(file_size.has_value()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_GE(*file_size, 0u); | 
|  | RTC_PARSE_CHECK_OR_RETURN_LE(*file_size, kMaxLogSize); | 
|  |  | 
|  | // Read file into memory. | 
|  | std::string buffer(*file_size, '\0'); | 
|  | size_t bytes_read = file.Read(&buffer[0], buffer.size()); | 
|  | if (bytes_read != *file_size) { | 
|  | RTC_LOG(LS_WARNING) << "Failed to read file " << filename; | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(bytes_read, *file_size); | 
|  | } | 
|  |  | 
|  | return ParseStream(buffer); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::ParseString( | 
|  | absl::string_view s) { | 
|  | return ParseStream(s); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::ParseStream( | 
|  | absl::string_view s) { | 
|  | Clear(); | 
|  | ParseStatus status = ParseStreamInternal(s); | 
|  |  | 
|  | // Cache the configured SSRCs. | 
|  | for (const auto& video_recv_config : video_recv_configs()) { | 
|  | incoming_video_ssrcs_.insert(video_recv_config.config.remote_ssrc); | 
|  | incoming_video_ssrcs_.insert(video_recv_config.config.rtx_ssrc); | 
|  | incoming_rtx_ssrcs_.insert(video_recv_config.config.rtx_ssrc); | 
|  | } | 
|  | for (const auto& video_send_config : video_send_configs()) { | 
|  | outgoing_video_ssrcs_.insert(video_send_config.config.local_ssrc); | 
|  | outgoing_video_ssrcs_.insert(video_send_config.config.rtx_ssrc); | 
|  | outgoing_rtx_ssrcs_.insert(video_send_config.config.rtx_ssrc); | 
|  | } | 
|  | for (const auto& audio_recv_config : audio_recv_configs()) { | 
|  | incoming_audio_ssrcs_.insert(audio_recv_config.config.remote_ssrc); | 
|  | } | 
|  | for (const auto& audio_send_config : audio_send_configs()) { | 
|  | outgoing_audio_ssrcs_.insert(audio_send_config.config.local_ssrc); | 
|  | } | 
|  |  | 
|  | // ParseStreamInternal stores the RTP packets in a map indexed by SSRC. | 
|  | // Since we dont need rapid lookup based on SSRC after parsing, we move the | 
|  | // packets_streams from map to vector. | 
|  | incoming_rtp_packets_by_ssrc_.reserve(incoming_rtp_packets_map_.size()); | 
|  | for (auto& kv : incoming_rtp_packets_map_) { | 
|  | incoming_rtp_packets_by_ssrc_.emplace_back(LoggedRtpStreamIncoming()); | 
|  | incoming_rtp_packets_by_ssrc_.back().ssrc = kv.first; | 
|  | incoming_rtp_packets_by_ssrc_.back().incoming_packets = | 
|  | std::move(kv.second); | 
|  | } | 
|  | incoming_rtp_packets_map_.clear(); | 
|  | outgoing_rtp_packets_by_ssrc_.reserve(outgoing_rtp_packets_map_.size()); | 
|  | for (auto& kv : outgoing_rtp_packets_map_) { | 
|  | outgoing_rtp_packets_by_ssrc_.emplace_back(LoggedRtpStreamOutgoing()); | 
|  | outgoing_rtp_packets_by_ssrc_.back().ssrc = kv.first; | 
|  | outgoing_rtp_packets_by_ssrc_.back().outgoing_packets = | 
|  | std::move(kv.second); | 
|  | } | 
|  | outgoing_rtp_packets_map_.clear(); | 
|  |  | 
|  | // Build PacketViews for easier iteration over RTP packets. | 
|  | for (const auto& stream : incoming_rtp_packets_by_ssrc_) { | 
|  | incoming_rtp_packet_views_by_ssrc_.emplace_back( | 
|  | LoggedRtpStreamView(stream.ssrc, stream.incoming_packets)); | 
|  | } | 
|  | for (const auto& stream : outgoing_rtp_packets_by_ssrc_) { | 
|  | outgoing_rtp_packet_views_by_ssrc_.emplace_back( | 
|  | LoggedRtpStreamView(stream.ssrc, stream.outgoing_packets)); | 
|  | } | 
|  |  | 
|  | // Set up convenience wrappers around the most commonly used RTCP types. | 
|  | for (const auto& incoming : incoming_rtcp_packets_) { | 
|  | const int64_t timestamp_us = incoming.rtcp.timestamp.us(); | 
|  | const uint8_t* packet_begin = incoming.rtcp.raw_data.data(); | 
|  | const uint8_t* packet_end = packet_begin + incoming.rtcp.raw_data.size(); | 
|  | auto store_rtcp_status = StoreRtcpBlocks( | 
|  | timestamp_us, packet_begin, packet_end, &incoming_sr_, &incoming_rr_, | 
|  | &incoming_xr_, &incoming_remb_, &incoming_nack_, &incoming_fir_, | 
|  | &incoming_pli_, &incoming_bye_, &incoming_transport_feedback_, | 
|  | &incoming_congestion_feedback_, &incoming_loss_notification_); | 
|  | RTC_RETURN_IF_ERROR(store_rtcp_status); | 
|  | } | 
|  |  | 
|  | for (const auto& outgoing : outgoing_rtcp_packets_) { | 
|  | const int64_t timestamp_us = outgoing.rtcp.timestamp.us(); | 
|  | const uint8_t* packet_begin = outgoing.rtcp.raw_data.data(); | 
|  | const uint8_t* packet_end = packet_begin + outgoing.rtcp.raw_data.size(); | 
|  | auto store_rtcp_status = StoreRtcpBlocks( | 
|  | timestamp_us, packet_begin, packet_end, &outgoing_sr_, &outgoing_rr_, | 
|  | &outgoing_xr_, &outgoing_remb_, &outgoing_nack_, &outgoing_fir_, | 
|  | &outgoing_pli_, &outgoing_bye_, &outgoing_transport_feedback_, | 
|  | &outgoing_congestion_feedback_, &outgoing_loss_notification_); | 
|  | RTC_RETURN_IF_ERROR(store_rtcp_status); | 
|  | } | 
|  |  | 
|  | // Store first and last timestamp events that might happen before the call is | 
|  | // connected or after the call is disconnected. Typical examples are | 
|  | // stream configurations and starting/stopping the log. | 
|  | // TODO(terelius): Figure out if we actually need to find the first and last | 
|  | // timestamp in the parser. It seems like this could be done by the caller. | 
|  | first_timestamp_ = Timestamp::PlusInfinity(); | 
|  | last_timestamp_ = Timestamp::MinusInfinity(); | 
|  | StoreFirstAndLastTimestamp(alr_state_events()); | 
|  | StoreFirstAndLastTimestamp(route_change_events()); | 
|  | for (const auto& audio_stream : audio_playout_events()) { | 
|  | // Audio playout events are grouped by SSRC. | 
|  | StoreFirstAndLastTimestamp(audio_stream.second); | 
|  | } | 
|  | for (const auto& set_minimum_delay : neteq_set_minimum_delay_events()) { | 
|  | // NetEq SetMinimumDelay grouped by SSRC. | 
|  | StoreFirstAndLastTimestamp(set_minimum_delay.second); | 
|  | } | 
|  | StoreFirstAndLastTimestamp(audio_network_adaptation_events()); | 
|  | StoreFirstAndLastTimestamp(bwe_probe_cluster_created_events()); | 
|  | StoreFirstAndLastTimestamp(bwe_probe_failure_events()); | 
|  | StoreFirstAndLastTimestamp(bwe_probe_success_events()); | 
|  | StoreFirstAndLastTimestamp(bwe_delay_updates()); | 
|  | StoreFirstAndLastTimestamp(bwe_loss_updates()); | 
|  | for (const auto& frame_stream : decoded_frames()) { | 
|  | StoreFirstAndLastTimestamp(frame_stream.second); | 
|  | } | 
|  | StoreFirstAndLastTimestamp(dtls_transport_states()); | 
|  | StoreFirstAndLastTimestamp(dtls_writable_states()); | 
|  | StoreFirstAndLastTimestamp(ice_candidate_pair_configs()); | 
|  | StoreFirstAndLastTimestamp(ice_candidate_pair_events()); | 
|  | for (const auto& rtp_stream : incoming_rtp_packets_by_ssrc()) { | 
|  | StoreFirstAndLastTimestamp(rtp_stream.incoming_packets); | 
|  | } | 
|  | for (const auto& rtp_stream : outgoing_rtp_packets_by_ssrc()) { | 
|  | StoreFirstAndLastTimestamp(rtp_stream.outgoing_packets); | 
|  | } | 
|  | StoreFirstAndLastTimestamp(incoming_rtcp_packets()); | 
|  | StoreFirstAndLastTimestamp(outgoing_rtcp_packets()); | 
|  | StoreFirstAndLastTimestamp(generic_packets_sent_); | 
|  | StoreFirstAndLastTimestamp(generic_packets_received_); | 
|  | StoreFirstAndLastTimestamp(generic_acks_received_); | 
|  | StoreFirstAndLastTimestamp(remote_estimate_events_); | 
|  |  | 
|  | // Stop events could be missing due to file size limits. If so, use the | 
|  | // last event, or the next start timestamp if available. | 
|  | // TODO(terelius): This could be improved. Instead of using the next start | 
|  | // event, we could use the timestamp of the the last previous regular event. | 
|  | auto start_iter = start_log_events().begin(); | 
|  | auto stop_iter = stop_log_events().begin(); | 
|  | int64_t start_us = | 
|  | first_timestamp().us_or(std::numeric_limits<int64_t>::max()); | 
|  | int64_t next_start_us = std::numeric_limits<int64_t>::max(); | 
|  | int64_t stop_us = std::numeric_limits<int64_t>::max(); | 
|  | if (start_iter != start_log_events().end()) { | 
|  | start_us = std::min(start_us, start_iter->log_time_us()); | 
|  | ++start_iter; | 
|  | if (start_iter != start_log_events().end()) | 
|  | next_start_us = start_iter->log_time_us(); | 
|  | } | 
|  | if (stop_iter != stop_log_events().end()) { | 
|  | stop_us = stop_iter->log_time_us(); | 
|  | } | 
|  | stop_us = std::min(stop_us, next_start_us); | 
|  | if (stop_us == std::numeric_limits<int64_t>::max() && | 
|  | !last_timestamp().IsMinusInfinity()) { | 
|  | stop_us = last_timestamp().us(); | 
|  | } | 
|  | RTC_PARSE_CHECK_OR_RETURN_LE(start_us, stop_us); | 
|  | first_log_segment_ = LogSegment(start_us, stop_us); | 
|  |  | 
|  | if (first_timestamp_ > last_timestamp_) { | 
|  | first_timestamp_ = last_timestamp_ = Timestamp::Zero(); | 
|  | } | 
|  |  | 
|  | return status; | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::ParseStreamInternal( | 
|  | absl::string_view s) { | 
|  | constexpr uint64_t kMaxEventSize = 10000000;  // Sanity check. | 
|  | // Protobuf defines the message tag as | 
|  | // (field_number << 3) | wire_type. In the legacy encoding, the field number | 
|  | // is supposed to be 1 and the wire type for a length-delimited field is 2. | 
|  | // In the new encoding we still expect the wire type to be 2, but the field | 
|  | // number will be greater than 1. | 
|  | constexpr uint64_t kExpectedV1Tag = (1 << 3) | 2; | 
|  | bool success = false; | 
|  |  | 
|  | // "Peek" at the first varint. | 
|  | absl::string_view event_start = s; | 
|  | uint64_t tag = 0; | 
|  | std::tie(success, std::ignore) = DecodeVarInt(s, &tag); | 
|  | if (!success) { | 
|  | RTC_LOG(LS_WARNING) << "Failed to read varint from beginning of event log."; | 
|  | RTC_PARSE_WARN_AND_RETURN_SUCCESS_IF(allow_incomplete_logs_, | 
|  | kIncompleteLogError); | 
|  | return ParseStatus::Error("Failed to read field tag varint", __FILE__, | 
|  | __LINE__); | 
|  | } | 
|  | s = event_start; | 
|  |  | 
|  | if (tag >> 1 == static_cast<uint64_t>(RtcEvent::Type::BeginV3Log)) { | 
|  | return ParseStreamInternalV3(s); | 
|  | } | 
|  |  | 
|  | while (!s.empty()) { | 
|  | // If not, "reset" event_start and read the field tag for the next event. | 
|  | event_start = s; | 
|  | std::tie(success, s) = DecodeVarInt(s, &tag); | 
|  | if (!success) { | 
|  | RTC_LOG(LS_WARNING) | 
|  | << "Failed to read field tag from beginning of protobuf event."; | 
|  | RTC_PARSE_WARN_AND_RETURN_SUCCESS_IF(allow_incomplete_logs_, | 
|  | kIncompleteLogError); | 
|  | return ParseStatus::Error("Failed to read field tag varint", __FILE__, | 
|  | __LINE__); | 
|  | } | 
|  |  | 
|  | constexpr uint64_t kWireTypeMask = 0x07; | 
|  | const uint64_t wire_type = tag & kWireTypeMask; | 
|  | if (wire_type != 2) { | 
|  | RTC_LOG(LS_WARNING) << "Expected field tag with wire type 2 (length " | 
|  | "delimited message). Found wire type " | 
|  | << wire_type; | 
|  | RTC_PARSE_WARN_AND_RETURN_SUCCESS_IF(allow_incomplete_logs_, | 
|  | kIncompleteLogError); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(wire_type, 2); | 
|  | } | 
|  |  | 
|  | // Read the length field. | 
|  | uint64_t message_length = 0; | 
|  | std::tie(success, s) = DecodeVarInt(s, &message_length); | 
|  | if (!success) { | 
|  | RTC_LOG(LS_WARNING) << "Missing message length after protobuf field tag."; | 
|  | RTC_PARSE_WARN_AND_RETURN_SUCCESS_IF(allow_incomplete_logs_, | 
|  | kIncompleteLogError); | 
|  | return ParseStatus::Error("Failed to read message length varint", | 
|  | __FILE__, __LINE__); | 
|  | } | 
|  |  | 
|  | if (message_length > s.size()) { | 
|  | RTC_LOG(LS_WARNING) << "Protobuf message length is larger than the " | 
|  | "remaining bytes in the proto."; | 
|  | RTC_PARSE_WARN_AND_RETURN_SUCCESS_IF(allow_incomplete_logs_, | 
|  | kIncompleteLogError); | 
|  | return ParseStatus::Error( | 
|  | "Incomplete message: the length of the next message is larger than " | 
|  | "the remaining bytes in the proto", | 
|  | __FILE__, __LINE__); | 
|  | } | 
|  |  | 
|  | RTC_PARSE_CHECK_OR_RETURN_LE(message_length, kMaxEventSize); | 
|  | // Skip forward to the start of the next event. | 
|  | s = s.substr(message_length); | 
|  | size_t total_event_size = event_start.size() - s.size(); | 
|  | RTC_CHECK_LE(total_event_size, event_start.size()); | 
|  |  | 
|  | if (tag == kExpectedV1Tag) { | 
|  | // Parse the protobuf event from the buffer. | 
|  | rtclog::EventStream event_stream; | 
|  | if (!event_stream.ParseFromArray(event_start.data(), total_event_size)) { | 
|  | RTC_LOG(LS_WARNING) | 
|  | << "Failed to parse legacy-format protobuf message."; | 
|  | RTC_PARSE_WARN_AND_RETURN_SUCCESS_IF(allow_incomplete_logs_, | 
|  | kIncompleteLogError); | 
|  | RTC_PARSE_CHECK_OR_RETURN(false); | 
|  | } | 
|  |  | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(event_stream.stream_size(), 1); | 
|  | auto status = StoreParsedLegacyEvent(event_stream.stream(0)); | 
|  | RTC_RETURN_IF_ERROR(status); | 
|  | } else { | 
|  | // Parse the protobuf event from the buffer. | 
|  | rtclog2::EventStream event_stream; | 
|  | if (!event_stream.ParseFromArray(event_start.data(), total_event_size)) { | 
|  | RTC_LOG(LS_WARNING) << "Failed to parse new-format protobuf message."; | 
|  | RTC_PARSE_WARN_AND_RETURN_SUCCESS_IF(allow_incomplete_logs_, | 
|  | kIncompleteLogError); | 
|  | RTC_PARSE_CHECK_OR_RETURN(false); | 
|  | } | 
|  | auto status = StoreParsedNewFormatEvent(event_stream); | 
|  | RTC_RETURN_IF_ERROR(status); | 
|  | } | 
|  | } | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::ParseStreamInternalV3( | 
|  | absl::string_view s) { | 
|  | constexpr uint64_t kMaxEventSize = 10000000;  // Sanity check. | 
|  | bool expect_begin_log_event = true; | 
|  | bool success = false; | 
|  |  | 
|  | while (!s.empty()) { | 
|  | // Read event type. | 
|  | uint64_t event_tag = 0; | 
|  | std::tie(success, s) = DecodeVarInt(s, &event_tag); | 
|  | RTC_PARSE_CHECK_OR_RETURN_MESSAGE(success, "Failed to read event type."); | 
|  | bool batched = event_tag & 1; | 
|  | uint64_t event_type = event_tag >> 1; | 
|  |  | 
|  | // Read event size | 
|  | uint64_t event_size_bytes = 0; | 
|  | std::tie(success, s) = DecodeVarInt(s, &event_size_bytes); | 
|  | RTC_PARSE_CHECK_OR_RETURN_MESSAGE(success, "Failed to read event size."); | 
|  | if (event_size_bytes > kMaxEventSize || event_size_bytes > s.size()) { | 
|  | RTC_LOG(LS_WARNING) << "Event size is too large."; | 
|  | RTC_PARSE_CHECK_OR_RETURN_LE(event_size_bytes, kMaxEventSize); | 
|  | RTC_PARSE_CHECK_OR_RETURN_LE(event_size_bytes, s.size()); | 
|  | } | 
|  |  | 
|  | // Read remaining event fields into a buffer. | 
|  | absl::string_view event_fields = s.substr(0, event_size_bytes); | 
|  | s = s.substr(event_size_bytes); | 
|  |  | 
|  | if (expect_begin_log_event) { | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ( | 
|  | event_type, static_cast<uint32_t>(RtcEvent::Type::BeginV3Log)); | 
|  | expect_begin_log_event = false; | 
|  | } | 
|  |  | 
|  | switch (event_type) { | 
|  | case static_cast<uint32_t>(RtcEvent::Type::BeginV3Log): | 
|  | RtcEventBeginLog::Parse(event_fields, batched, start_log_events_); | 
|  | break; | 
|  | case static_cast<uint32_t>(RtcEvent::Type::EndV3Log): | 
|  | RtcEventEndLog::Parse(event_fields, batched, stop_log_events_); | 
|  | expect_begin_log_event = true; | 
|  | break; | 
|  | case static_cast<uint32_t>(RtcEvent::Type::AlrStateEvent): | 
|  | RtcEventAlrState::Parse(event_fields, batched, alr_state_events_); | 
|  | break; | 
|  | case static_cast<uint32_t>(RtcEvent::Type::AudioPlayout): | 
|  | RtcEventAudioPlayout::Parse(event_fields, batched, | 
|  | audio_playout_events_); | 
|  | break; | 
|  | case static_cast<uint32_t>(RtcEvent::Type::BweUpdateDelayBased): | 
|  | RtcEventBweUpdateDelayBased::Parse(event_fields, batched, | 
|  | bwe_delay_updates_); | 
|  | break; | 
|  | case static_cast<uint32_t>(RtcEvent::Type::AudioNetworkAdaptation): | 
|  | RtcEventAudioNetworkAdaptation::Parse(event_fields, batched, | 
|  | audio_network_adaptation_events_); | 
|  | break; | 
|  | case static_cast<uint32_t>(RtcEvent::Type::AudioReceiveStreamConfig): | 
|  | RtcEventAudioReceiveStreamConfig::Parse(event_fields, batched, | 
|  | audio_recv_configs_); | 
|  | break; | 
|  | case static_cast<uint32_t>(RtcEvent::Type::AudioSendStreamConfig): | 
|  | RtcEventAudioSendStreamConfig::Parse(event_fields, batched, | 
|  | audio_send_configs_); | 
|  | break; | 
|  | case static_cast<uint32_t>(RtcEvent::Type::BweUpdateLossBased): | 
|  | RtcEventBweUpdateLossBased::Parse(event_fields, batched, | 
|  | bwe_loss_updates_); | 
|  | break; | 
|  | case static_cast<uint32_t>(RtcEvent::Type::DtlsTransportState): | 
|  | RtcEventDtlsTransportState::Parse(event_fields, batched, | 
|  | dtls_transport_states_); | 
|  | break; | 
|  | case static_cast<uint32_t>(RtcEvent::Type::DtlsWritableState): | 
|  | RtcEventDtlsWritableState::Parse(event_fields, batched, | 
|  | dtls_writable_states_); | 
|  | break; | 
|  | case static_cast<uint32_t>(RtcEvent::Type::FrameDecoded): | 
|  | RtcEventFrameDecoded::Parse(event_fields, batched, decoded_frames_); | 
|  | break; | 
|  | case static_cast<uint32_t>(RtcEvent::Type::GenericAckReceived): | 
|  | RtcEventGenericAckReceived::Parse(event_fields, batched, | 
|  | generic_acks_received_); | 
|  | break; | 
|  | case static_cast<uint32_t>(RtcEvent::Type::GenericPacketReceived): | 
|  | RtcEventGenericPacketReceived::Parse(event_fields, batched, | 
|  | generic_packets_received_); | 
|  | break; | 
|  | case static_cast<uint32_t>(RtcEvent::Type::GenericPacketSent): | 
|  | RtcEventGenericPacketSent::Parse(event_fields, batched, | 
|  | generic_packets_sent_); | 
|  | break; | 
|  | case static_cast<uint32_t>(RtcEvent::Type::IceCandidatePairConfig): | 
|  | RtcEventIceCandidatePairConfig::Parse(event_fields, batched, | 
|  | ice_candidate_pair_configs_); | 
|  | break; | 
|  | case static_cast<uint32_t>(RtcEvent::Type::IceCandidatePairEvent): | 
|  | RtcEventIceCandidatePair::Parse(event_fields, batched, | 
|  | ice_candidate_pair_events_); | 
|  | break; | 
|  | case static_cast<uint32_t>(RtcEvent::Type::ProbeClusterCreated): | 
|  | RtcEventProbeClusterCreated::Parse(event_fields, batched, | 
|  | bwe_probe_cluster_created_events_); | 
|  | break; | 
|  | case static_cast<uint32_t>(RtcEvent::Type::ProbeResultFailure): | 
|  | RtcEventProbeResultFailure::Parse(event_fields, batched, | 
|  | bwe_probe_failure_events_); | 
|  | break; | 
|  | case static_cast<uint32_t>(RtcEvent::Type::ProbeResultSuccess): | 
|  | RtcEventProbeResultSuccess::Parse(event_fields, batched, | 
|  | bwe_probe_success_events_); | 
|  | break; | 
|  | case static_cast<uint32_t>(RtcEvent::Type::RemoteEstimateEvent): | 
|  | RtcEventRemoteEstimate::Parse(event_fields, batched, | 
|  | remote_estimate_events_); | 
|  | break; | 
|  | case static_cast<uint32_t>(RtcEvent::Type::RouteChangeEvent): | 
|  | RtcEventRouteChange::Parse(event_fields, batched, route_change_events_); | 
|  | break; | 
|  | case static_cast<uint32_t>(RtcEvent::Type::RtcpPacketIncoming): | 
|  | RtcEventRtcpPacketIncoming::Parse(event_fields, batched, | 
|  | incoming_rtcp_packets_); | 
|  | break; | 
|  | case static_cast<uint32_t>(RtcEvent::Type::RtcpPacketOutgoing): | 
|  | RtcEventRtcpPacketOutgoing::Parse(event_fields, batched, | 
|  | outgoing_rtcp_packets_); | 
|  | break; | 
|  | case static_cast<uint32_t>(RtcEvent::Type::RtpPacketIncoming): | 
|  | RtcEventRtpPacketIncoming::Parse(event_fields, batched, | 
|  | incoming_rtp_packets_map_); | 
|  | break; | 
|  | case static_cast<uint32_t>(RtcEvent::Type::RtpPacketOutgoing): | 
|  | RtcEventRtpPacketOutgoing::Parse(event_fields, batched, | 
|  | outgoing_rtp_packets_map_); | 
|  | break; | 
|  | case static_cast<uint32_t>(RtcEvent::Type::VideoReceiveStreamConfig): | 
|  | RtcEventVideoReceiveStreamConfig::Parse(event_fields, batched, | 
|  | video_recv_configs_); | 
|  | break; | 
|  | case static_cast<uint32_t>(RtcEvent::Type::VideoSendStreamConfig): | 
|  | RtcEventVideoSendStreamConfig::Parse(event_fields, batched, | 
|  | video_send_configs_); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | void ParsedRtcEventLog::StoreFirstAndLastTimestamp(const std::vector<T>& v) { | 
|  | if (v.empty()) | 
|  | return; | 
|  | first_timestamp_ = std::min(first_timestamp_, v.front().log_time()); | 
|  | last_timestamp_ = std::max(last_timestamp_, v.back().log_time()); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::StoreParsedLegacyEvent( | 
|  | const rtclog::Event& event) { | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_type()); | 
|  | switch (event.type()) { | 
|  | case rtclog::Event::VIDEO_RECEIVER_CONFIG_EVENT: { | 
|  | auto config = GetVideoReceiveConfig(event); | 
|  | if (!config.ok()) | 
|  | return config.status(); | 
|  |  | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_timestamp_us()); | 
|  | int64_t timestamp_us = event.timestamp_us(); | 
|  | video_recv_configs_.emplace_back(Timestamp::Micros(timestamp_us), | 
|  | config.value()); | 
|  | incoming_rtp_extensions_maps_[config.value().remote_ssrc] = | 
|  | RtpHeaderExtensionMap(config.value().rtp_extensions); | 
|  | incoming_rtp_extensions_maps_[config.value().rtx_ssrc] = | 
|  | RtpHeaderExtensionMap(config.value().rtp_extensions); | 
|  | break; | 
|  | } | 
|  | case rtclog::Event::VIDEO_SENDER_CONFIG_EVENT: { | 
|  | auto config = GetVideoSendConfig(event); | 
|  | if (!config.ok()) | 
|  | return config.status(); | 
|  |  | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_timestamp_us()); | 
|  | int64_t timestamp_us = event.timestamp_us(); | 
|  | video_send_configs_.emplace_back(Timestamp::Micros(timestamp_us), | 
|  | config.value()); | 
|  | outgoing_rtp_extensions_maps_[config.value().local_ssrc] = | 
|  | RtpHeaderExtensionMap(config.value().rtp_extensions); | 
|  | outgoing_rtp_extensions_maps_[config.value().rtx_ssrc] = | 
|  | RtpHeaderExtensionMap(config.value().rtp_extensions); | 
|  | break; | 
|  | } | 
|  | case rtclog::Event::AUDIO_RECEIVER_CONFIG_EVENT: { | 
|  | auto config = GetAudioReceiveConfig(event); | 
|  | if (!config.ok()) | 
|  | return config.status(); | 
|  |  | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_timestamp_us()); | 
|  | int64_t timestamp_us = event.timestamp_us(); | 
|  | audio_recv_configs_.emplace_back(Timestamp::Micros(timestamp_us), | 
|  | config.value()); | 
|  | incoming_rtp_extensions_maps_[config.value().remote_ssrc] = | 
|  | RtpHeaderExtensionMap(config.value().rtp_extensions); | 
|  | break; | 
|  | } | 
|  | case rtclog::Event::AUDIO_SENDER_CONFIG_EVENT: { | 
|  | auto config = GetAudioSendConfig(event); | 
|  | if (!config.ok()) | 
|  | return config.status(); | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_timestamp_us()); | 
|  | int64_t timestamp_us = event.timestamp_us(); | 
|  | audio_send_configs_.emplace_back(Timestamp::Micros(timestamp_us), | 
|  | config.value()); | 
|  | outgoing_rtp_extensions_maps_[config.value().local_ssrc] = | 
|  | RtpHeaderExtensionMap(config.value().rtp_extensions); | 
|  | break; | 
|  | } | 
|  | case rtclog::Event::RTP_EVENT: { | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_rtp_packet()); | 
|  | const rtclog::RtpPacket& rtp_packet = event.rtp_packet(); | 
|  | RTC_PARSE_CHECK_OR_RETURN(rtp_packet.has_header()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(rtp_packet.has_incoming()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(rtp_packet.has_packet_length()); | 
|  | size_t total_length = rtp_packet.packet_length(); | 
|  |  | 
|  | // Use RtpPacketReceived instead of more generic RtpPacket because former | 
|  | // has a buildin convertion to RTPHeader. | 
|  | RtpPacketReceived rtp_header; | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | rtp_header.Parse(CopyOnWriteBuffer(rtp_packet.header()))); | 
|  |  | 
|  | if (const RtpHeaderExtensionMap* extension_map = GetRtpHeaderExtensionMap( | 
|  | rtp_packet.incoming(), rtp_header.Ssrc())) { | 
|  | rtp_header.IdentifyExtensions(*extension_map); | 
|  | } | 
|  |  | 
|  | RTPHeader parsed_header; | 
|  | rtp_header.GetHeader(&parsed_header); | 
|  |  | 
|  | // Since we give the parser only a header, there is no way for it to know | 
|  | // the padding length. The best solution would be to log the padding | 
|  | // length in RTC event log. In absence of it, we assume the RTP packet to | 
|  | // contain only padding, if the padding bit is set. | 
|  | // TODO(webrtc:9730): Use a generic way to obtain padding length. | 
|  | if (rtp_header.has_padding()) | 
|  | parsed_header.paddingLength = total_length - rtp_header.size(); | 
|  |  | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_timestamp_us()); | 
|  | int64_t timestamp_us = event.timestamp_us(); | 
|  | if (rtp_packet.incoming()) { | 
|  | incoming_rtp_packets_map_[parsed_header.ssrc].push_back( | 
|  | LoggedRtpPacketIncoming(Timestamp::Micros(timestamp_us), | 
|  | parsed_header, rtp_header.size(), | 
|  | total_length)); | 
|  | } else { | 
|  | outgoing_rtp_packets_map_[parsed_header.ssrc].push_back( | 
|  | LoggedRtpPacketOutgoing(Timestamp::Micros(timestamp_us), | 
|  | parsed_header, rtp_header.size(), | 
|  | total_length)); | 
|  | } | 
|  | break; | 
|  | } | 
|  | case rtclog::Event::RTCP_EVENT: { | 
|  | PacketDirection direction; | 
|  | std::vector<uint8_t> packet; | 
|  | auto status = GetRtcpPacket(event, &direction, &packet); | 
|  | RTC_RETURN_IF_ERROR(status); | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_timestamp_us()); | 
|  | int64_t timestamp_us = event.timestamp_us(); | 
|  | if (direction == kIncomingPacket) { | 
|  | // Currently incoming RTCP packets are logged twice, both for audio and | 
|  | // video. Only act on one of them. Compare against the previous parsed | 
|  | // incoming RTCP packet. | 
|  | if (packet == last_incoming_rtcp_packet_) | 
|  | break; | 
|  | incoming_rtcp_packets_.push_back( | 
|  | LoggedRtcpPacketIncoming(Timestamp::Micros(timestamp_us), packet)); | 
|  | last_incoming_rtcp_packet_ = packet; | 
|  | } else { | 
|  | outgoing_rtcp_packets_.push_back( | 
|  | LoggedRtcpPacketOutgoing(Timestamp::Micros(timestamp_us), packet)); | 
|  | } | 
|  | break; | 
|  | } | 
|  | case rtclog::Event::LOG_START: { | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_timestamp_us()); | 
|  | int64_t timestamp_us = event.timestamp_us(); | 
|  | start_log_events_.push_back( | 
|  | LoggedStartEvent(Timestamp::Micros(timestamp_us))); | 
|  | break; | 
|  | } | 
|  | case rtclog::Event::LOG_END: { | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_timestamp_us()); | 
|  | int64_t timestamp_us = event.timestamp_us(); | 
|  | stop_log_events_.push_back( | 
|  | LoggedStopEvent(Timestamp::Micros(timestamp_us))); | 
|  | break; | 
|  | } | 
|  | case rtclog::Event::AUDIO_PLAYOUT_EVENT: { | 
|  | auto status_or_value = GetAudioPlayout(event); | 
|  | RTC_RETURN_IF_ERROR(status_or_value.status()); | 
|  | LoggedAudioPlayoutEvent playout_event = status_or_value.value(); | 
|  | audio_playout_events_[playout_event.ssrc].push_back(playout_event); | 
|  | break; | 
|  | } | 
|  | case rtclog::Event::LOSS_BASED_BWE_UPDATE: { | 
|  | auto status_or_value = GetLossBasedBweUpdate(event); | 
|  | RTC_RETURN_IF_ERROR(status_or_value.status()); | 
|  | bwe_loss_updates_.push_back(status_or_value.value()); | 
|  | break; | 
|  | } | 
|  | case rtclog::Event::DELAY_BASED_BWE_UPDATE: { | 
|  | auto status_or_value = GetDelayBasedBweUpdate(event); | 
|  | RTC_RETURN_IF_ERROR(status_or_value.status()); | 
|  | bwe_delay_updates_.push_back(status_or_value.value()); | 
|  | break; | 
|  | } | 
|  | case rtclog::Event::AUDIO_NETWORK_ADAPTATION_EVENT: { | 
|  | auto status_or_value = GetAudioNetworkAdaptation(event); | 
|  | RTC_RETURN_IF_ERROR(status_or_value.status()); | 
|  | audio_network_adaptation_events_.push_back(status_or_value.value()); | 
|  | break; | 
|  | } | 
|  | case rtclog::Event::BWE_PROBE_CLUSTER_CREATED_EVENT: { | 
|  | auto status_or_value = GetBweProbeClusterCreated(event); | 
|  | RTC_RETURN_IF_ERROR(status_or_value.status()); | 
|  | bwe_probe_cluster_created_events_.push_back(status_or_value.value()); | 
|  | break; | 
|  | } | 
|  | case rtclog::Event::BWE_PROBE_RESULT_EVENT: { | 
|  | // Probe successes and failures are currently stored in the same proto | 
|  | // message, we are moving towards separate messages. Probe results | 
|  | // therefore need special treatment in the parser. | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_probe_result()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.probe_result().has_result()); | 
|  | if (event.probe_result().result() == rtclog::BweProbeResult::SUCCESS) { | 
|  | auto status_or_value = GetBweProbeSuccess(event); | 
|  | RTC_RETURN_IF_ERROR(status_or_value.status()); | 
|  | bwe_probe_success_events_.push_back(status_or_value.value()); | 
|  | } else { | 
|  | auto status_or_value = GetBweProbeFailure(event); | 
|  | RTC_RETURN_IF_ERROR(status_or_value.status()); | 
|  | bwe_probe_failure_events_.push_back(status_or_value.value()); | 
|  | } | 
|  | break; | 
|  | } | 
|  | case rtclog::Event::ALR_STATE_EVENT: { | 
|  | auto status_or_value = GetAlrState(event); | 
|  | RTC_RETURN_IF_ERROR(status_or_value.status()); | 
|  | alr_state_events_.push_back(status_or_value.value()); | 
|  | break; | 
|  | } | 
|  | case rtclog::Event::ICE_CANDIDATE_PAIR_CONFIG: { | 
|  | auto status_or_value = GetIceCandidatePairConfig(event); | 
|  | RTC_RETURN_IF_ERROR(status_or_value.status()); | 
|  | ice_candidate_pair_configs_.push_back(status_or_value.value()); | 
|  | break; | 
|  | } | 
|  | case rtclog::Event::ICE_CANDIDATE_PAIR_EVENT: { | 
|  | auto status_or_value = GetIceCandidatePairEvent(event); | 
|  | RTC_RETURN_IF_ERROR(status_or_value.status()); | 
|  | ice_candidate_pair_events_.push_back(status_or_value.value()); | 
|  | break; | 
|  | } | 
|  | case rtclog::Event::REMOTE_ESTIMATE: { | 
|  | auto status_or_value = GetRemoteEstimateEvent(event); | 
|  | RTC_RETURN_IF_ERROR(status_or_value.status()); | 
|  | remote_estimate_events_.push_back(status_or_value.value()); | 
|  | break; | 
|  | } | 
|  | case rtclog::Event::UNKNOWN_EVENT: { | 
|  | break; | 
|  | } | 
|  | } | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | const RtpHeaderExtensionMap* ParsedRtcEventLog::GetRtpHeaderExtensionMap( | 
|  | bool incoming, | 
|  | uint32_t ssrc) { | 
|  | auto& extensions_maps = | 
|  | incoming ? incoming_rtp_extensions_maps_ : outgoing_rtp_extensions_maps_; | 
|  | auto it = extensions_maps.find(ssrc); | 
|  | if (it != extensions_maps.end()) { | 
|  | return &(it->second); | 
|  | } | 
|  | if (parse_unconfigured_header_extensions_ == | 
|  | UnconfiguredHeaderExtensions::kAttemptWebrtcDefaultConfig) { | 
|  | RTC_DLOG(LS_WARNING) << "Using default header extension map for SSRC " | 
|  | << ssrc; | 
|  | extensions_maps.insert(std::make_pair(ssrc, default_extension_map_)); | 
|  | return &default_extension_map_; | 
|  | } | 
|  | RTC_DLOG(LS_WARNING) << "Not parsing header extensions for SSRC " << ssrc | 
|  | << ". No header extension map found."; | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::GetRtcpPacket( | 
|  | const rtclog::Event& event, | 
|  | PacketDirection* incoming, | 
|  | std::vector<uint8_t>* packet) const { | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_type()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(event.type(), rtclog::Event::RTCP_EVENT); | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_rtcp_packet()); | 
|  | const rtclog::RtcpPacket& rtcp_packet = event.rtcp_packet(); | 
|  | // Get direction of packet. | 
|  | RTC_PARSE_CHECK_OR_RETURN(rtcp_packet.has_incoming()); | 
|  | if (incoming != nullptr) { | 
|  | *incoming = rtcp_packet.incoming() ? kIncomingPacket : kOutgoingPacket; | 
|  | } | 
|  | // Get packet contents. | 
|  | RTC_PARSE_CHECK_OR_RETURN(rtcp_packet.has_packet_data()); | 
|  | if (packet != nullptr) { | 
|  | packet->resize(rtcp_packet.packet_data().size()); | 
|  | memcpy(packet->data(), rtcp_packet.packet_data().data(), | 
|  | rtcp_packet.packet_data().size()); | 
|  | } | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatusOr<rtclog::StreamConfig> | 
|  | ParsedRtcEventLog::GetVideoReceiveConfig(const rtclog::Event& event) const { | 
|  | rtclog::StreamConfig config; | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_type()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(event.type(), | 
|  | rtclog::Event::VIDEO_RECEIVER_CONFIG_EVENT); | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_video_receiver_config()); | 
|  | const rtclog::VideoReceiveConfig& receiver_config = | 
|  | event.video_receiver_config(); | 
|  | // Get SSRCs. | 
|  | RTC_PARSE_CHECK_OR_RETURN(receiver_config.has_remote_ssrc()); | 
|  | config.remote_ssrc = receiver_config.remote_ssrc(); | 
|  | RTC_PARSE_CHECK_OR_RETURN(receiver_config.has_local_ssrc()); | 
|  | config.local_ssrc = receiver_config.local_ssrc(); | 
|  | config.rtx_ssrc = 0; | 
|  | // Get RTCP settings. | 
|  | RTC_PARSE_CHECK_OR_RETURN(receiver_config.has_rtcp_mode()); | 
|  | config.rtcp_mode = GetRuntimeRtcpMode(receiver_config.rtcp_mode()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(receiver_config.has_remb()); | 
|  | config.remb = receiver_config.remb(); | 
|  |  | 
|  | // Get RTX map. | 
|  | std::map<uint32_t, const rtclog::RtxConfig> rtx_map; | 
|  | for (int i = 0; i < receiver_config.rtx_map_size(); i++) { | 
|  | const rtclog::RtxMap& map = receiver_config.rtx_map(i); | 
|  | RTC_PARSE_CHECK_OR_RETURN(map.has_payload_type()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(map.has_config()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(map.config().has_rtx_ssrc()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(map.config().has_rtx_payload_type()); | 
|  | rtx_map.insert(std::make_pair(map.payload_type(), map.config())); | 
|  | } | 
|  |  | 
|  | // Get header extensions. | 
|  | auto status = GetHeaderExtensions(&config.rtp_extensions, | 
|  | receiver_config.header_extensions()); | 
|  | RTC_RETURN_IF_ERROR(status); | 
|  |  | 
|  | // Get decoders. | 
|  | config.codecs.clear(); | 
|  | for (int i = 0; i < receiver_config.decoders_size(); i++) { | 
|  | RTC_PARSE_CHECK_OR_RETURN(receiver_config.decoders(i).has_name()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(receiver_config.decoders(i).has_payload_type()); | 
|  | int rtx_payload_type = 0; | 
|  | auto rtx_it = rtx_map.find(receiver_config.decoders(i).payload_type()); | 
|  | if (rtx_it != rtx_map.end()) { | 
|  | rtx_payload_type = rtx_it->second.rtx_payload_type(); | 
|  | if (config.rtx_ssrc != 0 && | 
|  | config.rtx_ssrc != rtx_it->second.rtx_ssrc()) { | 
|  | RTC_LOG(LS_WARNING) | 
|  | << "RtcEventLog protobuf contained different SSRCs for " | 
|  | "different received RTX payload types. Will only use " | 
|  | "rtx_ssrc = " | 
|  | << config.rtx_ssrc << "."; | 
|  | } else { | 
|  | config.rtx_ssrc = rtx_it->second.rtx_ssrc(); | 
|  | } | 
|  | } | 
|  | config.codecs.emplace_back(receiver_config.decoders(i).name(), | 
|  | receiver_config.decoders(i).payload_type(), | 
|  | rtx_payload_type); | 
|  | } | 
|  | return config; | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatusOr<rtclog::StreamConfig> | 
|  | ParsedRtcEventLog::GetVideoSendConfig(const rtclog::Event& event) const { | 
|  | rtclog::StreamConfig config; | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_type()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(event.type(), | 
|  | rtclog::Event::VIDEO_SENDER_CONFIG_EVENT); | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_video_sender_config()); | 
|  | const rtclog::VideoSendConfig& sender_config = event.video_sender_config(); | 
|  |  | 
|  | // Get SSRCs. | 
|  | // VideoSendStreamConfig no longer stores multiple SSRCs. If you are | 
|  | // analyzing a very old log, try building the parser from the same | 
|  | // WebRTC version. | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(sender_config.ssrcs_size(), 1); | 
|  | config.local_ssrc = sender_config.ssrcs(0); | 
|  | RTC_PARSE_CHECK_OR_RETURN_LE(sender_config.rtx_ssrcs_size(), 1); | 
|  | if (sender_config.rtx_ssrcs_size() == 1) { | 
|  | config.rtx_ssrc = sender_config.rtx_ssrcs(0); | 
|  | } | 
|  |  | 
|  | // Get header extensions. | 
|  | auto status = GetHeaderExtensions(&config.rtp_extensions, | 
|  | sender_config.header_extensions()); | 
|  | RTC_RETURN_IF_ERROR(status); | 
|  |  | 
|  | // Get the codec. | 
|  | RTC_PARSE_CHECK_OR_RETURN(sender_config.has_encoder()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(sender_config.encoder().has_name()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(sender_config.encoder().has_payload_type()); | 
|  | config.codecs.emplace_back( | 
|  | sender_config.encoder().name(), sender_config.encoder().payload_type(), | 
|  | sender_config.has_rtx_payload_type() ? sender_config.rtx_payload_type() | 
|  | : 0); | 
|  | return config; | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatusOr<rtclog::StreamConfig> | 
|  | ParsedRtcEventLog::GetAudioReceiveConfig(const rtclog::Event& event) const { | 
|  | rtclog::StreamConfig config; | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_type()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(event.type(), | 
|  | rtclog::Event::AUDIO_RECEIVER_CONFIG_EVENT); | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_audio_receiver_config()); | 
|  | const rtclog::AudioReceiveConfig& receiver_config = | 
|  | event.audio_receiver_config(); | 
|  | // Get SSRCs. | 
|  | RTC_PARSE_CHECK_OR_RETURN(receiver_config.has_remote_ssrc()); | 
|  | config.remote_ssrc = receiver_config.remote_ssrc(); | 
|  | RTC_PARSE_CHECK_OR_RETURN(receiver_config.has_local_ssrc()); | 
|  | config.local_ssrc = receiver_config.local_ssrc(); | 
|  | // Get header extensions. | 
|  | auto status = GetHeaderExtensions(&config.rtp_extensions, | 
|  | receiver_config.header_extensions()); | 
|  | RTC_RETURN_IF_ERROR(status); | 
|  |  | 
|  | return config; | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatusOr<rtclog::StreamConfig> | 
|  | ParsedRtcEventLog::GetAudioSendConfig(const rtclog::Event& event) const { | 
|  | rtclog::StreamConfig config; | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_type()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(event.type(), | 
|  | rtclog::Event::AUDIO_SENDER_CONFIG_EVENT); | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_audio_sender_config()); | 
|  | const rtclog::AudioSendConfig& sender_config = event.audio_sender_config(); | 
|  | // Get SSRCs. | 
|  | RTC_PARSE_CHECK_OR_RETURN(sender_config.has_ssrc()); | 
|  | config.local_ssrc = sender_config.ssrc(); | 
|  | // Get header extensions. | 
|  | auto status = GetHeaderExtensions(&config.rtp_extensions, | 
|  | sender_config.header_extensions()); | 
|  | RTC_RETURN_IF_ERROR(status); | 
|  |  | 
|  | return config; | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatusOr<LoggedAudioPlayoutEvent> | 
|  | ParsedRtcEventLog::GetAudioPlayout(const rtclog::Event& event) const { | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_type()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(event.type(), | 
|  | rtclog::Event::AUDIO_PLAYOUT_EVENT); | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_audio_playout_event()); | 
|  | const rtclog::AudioPlayoutEvent& playout_event = event.audio_playout_event(); | 
|  | LoggedAudioPlayoutEvent res; | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_timestamp_us()); | 
|  | res.timestamp = Timestamp::Micros(event.timestamp_us()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(playout_event.has_local_ssrc()); | 
|  | res.ssrc = playout_event.local_ssrc(); | 
|  | return res; | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatusOr<LoggedBweLossBasedUpdate> | 
|  | ParsedRtcEventLog::GetLossBasedBweUpdate(const rtclog::Event& event) const { | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_type()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(event.type(), | 
|  | rtclog::Event::LOSS_BASED_BWE_UPDATE); | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_loss_based_bwe_update()); | 
|  | const rtclog::LossBasedBweUpdate& loss_event = event.loss_based_bwe_update(); | 
|  |  | 
|  | LoggedBweLossBasedUpdate bwe_update; | 
|  | RTC_CHECK(event.has_timestamp_us()); | 
|  | bwe_update.timestamp = Timestamp::Micros(event.timestamp_us()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(loss_event.has_bitrate_bps()); | 
|  | bwe_update.bitrate_bps = loss_event.bitrate_bps(); | 
|  | RTC_PARSE_CHECK_OR_RETURN(loss_event.has_fraction_loss()); | 
|  | bwe_update.fraction_lost = loss_event.fraction_loss(); | 
|  | RTC_PARSE_CHECK_OR_RETURN(loss_event.has_total_packets()); | 
|  | bwe_update.expected_packets = loss_event.total_packets(); | 
|  | return bwe_update; | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatusOr<LoggedBweDelayBasedUpdate> | 
|  | ParsedRtcEventLog::GetDelayBasedBweUpdate(const rtclog::Event& event) const { | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_type()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(event.type(), | 
|  | rtclog::Event::DELAY_BASED_BWE_UPDATE); | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_delay_based_bwe_update()); | 
|  | const rtclog::DelayBasedBweUpdate& delay_event = | 
|  | event.delay_based_bwe_update(); | 
|  |  | 
|  | LoggedBweDelayBasedUpdate res; | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_timestamp_us()); | 
|  | res.timestamp = Timestamp::Micros(event.timestamp_us()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(delay_event.has_bitrate_bps()); | 
|  | res.bitrate_bps = delay_event.bitrate_bps(); | 
|  | RTC_PARSE_CHECK_OR_RETURN(delay_event.has_detector_state()); | 
|  | res.detector_state = GetRuntimeDetectorState(delay_event.detector_state()); | 
|  | return res; | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatusOr<LoggedAudioNetworkAdaptationEvent> | 
|  | ParsedRtcEventLog::GetAudioNetworkAdaptation(const rtclog::Event& event) const { | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_type()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(event.type(), | 
|  | rtclog::Event::AUDIO_NETWORK_ADAPTATION_EVENT); | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_audio_network_adaptation()); | 
|  | const rtclog::AudioNetworkAdaptation& ana_event = | 
|  | event.audio_network_adaptation(); | 
|  |  | 
|  | LoggedAudioNetworkAdaptationEvent res; | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_timestamp_us()); | 
|  | res.timestamp = Timestamp::Micros(event.timestamp_us()); | 
|  | if (ana_event.has_bitrate_bps()) | 
|  | res.config.bitrate_bps = ana_event.bitrate_bps(); | 
|  | if (ana_event.has_enable_fec()) | 
|  | res.config.enable_fec = ana_event.enable_fec(); | 
|  | if (ana_event.has_enable_dtx()) | 
|  | res.config.enable_dtx = ana_event.enable_dtx(); | 
|  | if (ana_event.has_frame_length_ms()) | 
|  | res.config.frame_length_ms = ana_event.frame_length_ms(); | 
|  | if (ana_event.has_num_channels()) | 
|  | res.config.num_channels = ana_event.num_channels(); | 
|  | if (ana_event.has_uplink_packet_loss_fraction()) | 
|  | res.config.uplink_packet_loss_fraction = | 
|  | ana_event.uplink_packet_loss_fraction(); | 
|  | return res; | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatusOr<LoggedBweProbeClusterCreatedEvent> | 
|  | ParsedRtcEventLog::GetBweProbeClusterCreated(const rtclog::Event& event) const { | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_type()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(event.type(), | 
|  | rtclog::Event::BWE_PROBE_CLUSTER_CREATED_EVENT); | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_probe_cluster()); | 
|  | const rtclog::BweProbeCluster& pcc_event = event.probe_cluster(); | 
|  | LoggedBweProbeClusterCreatedEvent res; | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_timestamp_us()); | 
|  | res.timestamp = Timestamp::Micros(event.timestamp_us()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(pcc_event.has_id()); | 
|  | res.id = pcc_event.id(); | 
|  | RTC_PARSE_CHECK_OR_RETURN(pcc_event.has_bitrate_bps()); | 
|  | res.bitrate_bps = pcc_event.bitrate_bps(); | 
|  | RTC_PARSE_CHECK_OR_RETURN(pcc_event.has_min_packets()); | 
|  | res.min_packets = pcc_event.min_packets(); | 
|  | RTC_PARSE_CHECK_OR_RETURN(pcc_event.has_min_bytes()); | 
|  | res.min_bytes = pcc_event.min_bytes(); | 
|  | return res; | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatusOr<LoggedBweProbeFailureEvent> | 
|  | ParsedRtcEventLog::GetBweProbeFailure(const rtclog::Event& event) const { | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_type()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(event.type(), | 
|  | rtclog::Event::BWE_PROBE_RESULT_EVENT); | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_probe_result()); | 
|  | const rtclog::BweProbeResult& pr_event = event.probe_result(); | 
|  | RTC_PARSE_CHECK_OR_RETURN(pr_event.has_result()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_NE(pr_event.result(), | 
|  | rtclog::BweProbeResult::SUCCESS); | 
|  |  | 
|  | LoggedBweProbeFailureEvent res; | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_timestamp_us()); | 
|  | res.timestamp = Timestamp::Micros(event.timestamp_us()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(pr_event.has_id()); | 
|  | res.id = pr_event.id(); | 
|  | RTC_PARSE_CHECK_OR_RETURN(pr_event.has_result()); | 
|  | if (pr_event.result() == | 
|  | rtclog::BweProbeResult::INVALID_SEND_RECEIVE_INTERVAL) { | 
|  | res.failure_reason = ProbeFailureReason::kInvalidSendReceiveInterval; | 
|  | } else if (pr_event.result() == | 
|  | rtclog::BweProbeResult::INVALID_SEND_RECEIVE_RATIO) { | 
|  | res.failure_reason = ProbeFailureReason::kInvalidSendReceiveRatio; | 
|  | } else if (pr_event.result() == rtclog::BweProbeResult::TIMEOUT) { | 
|  | res.failure_reason = ProbeFailureReason::kTimeout; | 
|  | } else { | 
|  | RTC_DCHECK_NOTREACHED(); | 
|  | } | 
|  | RTC_PARSE_CHECK_OR_RETURN(!pr_event.has_bitrate_bps()); | 
|  |  | 
|  | return res; | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatusOr<LoggedBweProbeSuccessEvent> | 
|  | ParsedRtcEventLog::GetBweProbeSuccess(const rtclog::Event& event) const { | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_type()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(event.type(), | 
|  | rtclog::Event::BWE_PROBE_RESULT_EVENT); | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_probe_result()); | 
|  | const rtclog::BweProbeResult& pr_event = event.probe_result(); | 
|  | RTC_PARSE_CHECK_OR_RETURN(pr_event.has_result()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(pr_event.result(), | 
|  | rtclog::BweProbeResult::SUCCESS); | 
|  |  | 
|  | LoggedBweProbeSuccessEvent res; | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_timestamp_us()); | 
|  | res.timestamp = Timestamp::Micros(event.timestamp_us()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(pr_event.has_id()); | 
|  | res.id = pr_event.id(); | 
|  | RTC_PARSE_CHECK_OR_RETURN(pr_event.has_bitrate_bps()); | 
|  | res.bitrate_bps = pr_event.bitrate_bps(); | 
|  |  | 
|  | return res; | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatusOr<LoggedAlrStateEvent> | 
|  | ParsedRtcEventLog::GetAlrState(const rtclog::Event& event) const { | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_type()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(event.type(), rtclog::Event::ALR_STATE_EVENT); | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_alr_state()); | 
|  | const rtclog::AlrState& alr_event = event.alr_state(); | 
|  | LoggedAlrStateEvent res; | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_timestamp_us()); | 
|  | res.timestamp = Timestamp::Micros(event.timestamp_us()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(alr_event.has_in_alr()); | 
|  | res.in_alr = alr_event.in_alr(); | 
|  |  | 
|  | return res; | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatusOr<LoggedIceCandidatePairConfig> | 
|  | ParsedRtcEventLog::GetIceCandidatePairConfig( | 
|  | const rtclog::Event& rtc_event) const { | 
|  | RTC_PARSE_CHECK_OR_RETURN(rtc_event.has_type()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(rtc_event.type(), | 
|  | rtclog::Event::ICE_CANDIDATE_PAIR_CONFIG); | 
|  | LoggedIceCandidatePairConfig res; | 
|  | const rtclog::IceCandidatePairConfig& config = | 
|  | rtc_event.ice_candidate_pair_config(); | 
|  | RTC_PARSE_CHECK_OR_RETURN(rtc_event.has_timestamp_us()); | 
|  | res.timestamp = Timestamp::Micros(rtc_event.timestamp_us()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(config.has_config_type()); | 
|  | res.type = GetRuntimeIceCandidatePairConfigType(config.config_type()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(config.has_candidate_pair_id()); | 
|  | res.candidate_pair_id = config.candidate_pair_id(); | 
|  | RTC_PARSE_CHECK_OR_RETURN(config.has_local_candidate_type()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(GetRuntimeIceCandidateType( | 
|  | config.local_candidate_type(), res.local_candidate_type)); | 
|  | RTC_PARSE_CHECK_OR_RETURN(config.has_local_relay_protocol()); | 
|  | res.local_relay_protocol = | 
|  | GetRuntimeIceCandidatePairProtocol(config.local_relay_protocol()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(config.has_local_network_type()); | 
|  | res.local_network_type = | 
|  | GetRuntimeIceCandidateNetworkType(config.local_network_type()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(config.has_local_address_family()); | 
|  | res.local_address_family = | 
|  | GetRuntimeIceCandidatePairAddressFamily(config.local_address_family()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(config.has_remote_candidate_type()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(GetRuntimeIceCandidateType( | 
|  | config.remote_candidate_type(), res.remote_candidate_type)); | 
|  | RTC_PARSE_CHECK_OR_RETURN(config.has_remote_address_family()); | 
|  | res.remote_address_family = | 
|  | GetRuntimeIceCandidatePairAddressFamily(config.remote_address_family()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(config.has_candidate_pair_protocol()); | 
|  | res.candidate_pair_protocol = | 
|  | GetRuntimeIceCandidatePairProtocol(config.candidate_pair_protocol()); | 
|  | return res; | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatusOr<LoggedIceCandidatePairEvent> | 
|  | ParsedRtcEventLog::GetIceCandidatePairEvent( | 
|  | const rtclog::Event& rtc_event) const { | 
|  | RTC_PARSE_CHECK_OR_RETURN(rtc_event.has_type()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(rtc_event.type(), | 
|  | rtclog::Event::ICE_CANDIDATE_PAIR_EVENT); | 
|  | LoggedIceCandidatePairEvent res; | 
|  | const rtclog::IceCandidatePairEvent& event = | 
|  | rtc_event.ice_candidate_pair_event(); | 
|  | RTC_PARSE_CHECK_OR_RETURN(rtc_event.has_timestamp_us()); | 
|  | res.timestamp = Timestamp::Micros(rtc_event.timestamp_us()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_event_type()); | 
|  | res.type = GetRuntimeIceCandidatePairEventType(event.event_type()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_candidate_pair_id()); | 
|  | res.candidate_pair_id = event.candidate_pair_id(); | 
|  | // transaction_id is not supported by rtclog::Event | 
|  | res.transaction_id = 0; | 
|  | return res; | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatusOr<LoggedRemoteEstimateEvent> | 
|  | ParsedRtcEventLog::GetRemoteEstimateEvent(const rtclog::Event& event) const { | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_type()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(event.type(), rtclog::Event::REMOTE_ESTIMATE); | 
|  | LoggedRemoteEstimateEvent res; | 
|  | const rtclog::RemoteEstimate& remote_estimate_event = event.remote_estimate(); | 
|  | RTC_PARSE_CHECK_OR_RETURN(event.has_timestamp_us()); | 
|  | res.timestamp = Timestamp::Micros(event.timestamp_us()); | 
|  | if (remote_estimate_event.has_link_capacity_lower_kbps()) | 
|  | res.link_capacity_lower = DataRate::KilobitsPerSec( | 
|  | remote_estimate_event.link_capacity_lower_kbps()); | 
|  | if (remote_estimate_event.has_link_capacity_upper_kbps()) | 
|  | res.link_capacity_upper = DataRate::KilobitsPerSec( | 
|  | remote_estimate_event.link_capacity_upper_kbps()); | 
|  | return res; | 
|  | } | 
|  |  | 
|  | // Returns the MediaType for registered SSRCs. Search from the end to use last | 
|  | // registered types first. | 
|  | ParsedRtcEventLog::MediaType ParsedRtcEventLog::GetMediaType( | 
|  | uint32_t ssrc, | 
|  | PacketDirection direction) const { | 
|  | if (direction == kIncomingPacket) { | 
|  | if (std::find(incoming_video_ssrcs_.begin(), incoming_video_ssrcs_.end(), | 
|  | ssrc) != incoming_video_ssrcs_.end()) { | 
|  | return MediaType::VIDEO; | 
|  | } | 
|  | if (std::find(incoming_audio_ssrcs_.begin(), incoming_audio_ssrcs_.end(), | 
|  | ssrc) != incoming_audio_ssrcs_.end()) { | 
|  | return MediaType::AUDIO; | 
|  | } | 
|  | } else { | 
|  | if (std::find(outgoing_video_ssrcs_.begin(), outgoing_video_ssrcs_.end(), | 
|  | ssrc) != outgoing_video_ssrcs_.end()) { | 
|  | return MediaType::VIDEO; | 
|  | } | 
|  | if (std::find(outgoing_audio_ssrcs_.begin(), outgoing_audio_ssrcs_.end(), | 
|  | ssrc) != outgoing_audio_ssrcs_.end()) { | 
|  | return MediaType::AUDIO; | 
|  | } | 
|  | } | 
|  | return MediaType::ANY; | 
|  | } | 
|  |  | 
|  | std::vector<InferredRouteChangeEvent> ParsedRtcEventLog::GetRouteChanges() | 
|  | const { | 
|  | std::vector<InferredRouteChangeEvent> route_changes; | 
|  | for (auto& candidate : ice_candidate_pair_configs()) { | 
|  | if (candidate.type == IceCandidatePairConfigType::kSelected) { | 
|  | InferredRouteChangeEvent route; | 
|  | route.route_id = candidate.candidate_pair_id; | 
|  | route.log_time = Timestamp::Millis(candidate.log_time_ms()); | 
|  |  | 
|  | route.send_overhead = kUdpOverhead + kSrtpOverhead + kIpv4Overhead; | 
|  | if (candidate.remote_address_family == | 
|  | IceCandidatePairAddressFamily::kIpv6) | 
|  | route.send_overhead += kIpv6Overhead - kIpv4Overhead; | 
|  | if (candidate.remote_candidate_type != IceCandidateType::kHost) | 
|  | route.send_overhead += kStunOverhead; | 
|  | route.return_overhead = kUdpOverhead + kSrtpOverhead + kIpv4Overhead; | 
|  | if (candidate.remote_address_family == | 
|  | IceCandidatePairAddressFamily::kIpv6) | 
|  | route.return_overhead += kIpv6Overhead - kIpv4Overhead; | 
|  | if (candidate.remote_candidate_type != IceCandidateType::kHost) | 
|  | route.return_overhead += kStunOverhead; | 
|  | route_changes.push_back(route); | 
|  | } | 
|  | } | 
|  | return route_changes; | 
|  | } | 
|  |  | 
|  | std::vector<LoggedPacketInfo> ParsedRtcEventLog::GetPacketInfos( | 
|  | PacketDirection direction) const { | 
|  | std::map<uint32_t, MediaStreamInfo> streams; | 
|  | if (direction == PacketDirection::kIncomingPacket) { | 
|  | AddRecvStreamInfos(&streams, audio_recv_configs(), LoggedMediaType::kAudio); | 
|  | AddRecvStreamInfos(&streams, video_recv_configs(), LoggedMediaType::kVideo); | 
|  | } else if (direction == PacketDirection::kOutgoingPacket) { | 
|  | AddSendStreamInfos(&streams, audio_send_configs(), LoggedMediaType::kAudio); | 
|  | AddSendStreamInfos(&streams, video_send_configs(), LoggedMediaType::kVideo); | 
|  | } | 
|  |  | 
|  | std::vector<OverheadChangeEvent> overheads = | 
|  | GetOverheadChangingEvents(GetRouteChanges(), direction); | 
|  | auto overhead_iter = overheads.begin(); | 
|  | std::vector<LoggedPacketInfo> packets; | 
|  | std::map</*unwrapped transport sequence number*/ int64_t, | 
|  | /*index into packets*/ size_t> | 
|  | twcc_indices; | 
|  | RtpSequenceNumberUnwrapper transport_seq_num_unwrapper; | 
|  |  | 
|  | class PerSSRCInfo { | 
|  | public: | 
|  | void AddPacketIndex(uint16_t rtp_sequence_number, size_t index) { | 
|  | packet_indices_[rtp_seq_num_unwrapper_.Unwrap(rtp_sequence_number)] = | 
|  | index; | 
|  | } | 
|  | std::optional<size_t> FindPacketIndex(uint16_t rtp_sequence_number) { | 
|  | auto it = packet_indices_.find( | 
|  | rtp_seq_num_unwrapper_.Unwrap(rtp_sequence_number)); | 
|  | if (it == packet_indices_.end()) { | 
|  | return std::nullopt; | 
|  | } | 
|  | return it->second; | 
|  | } | 
|  |  | 
|  | private: | 
|  | RtpSequenceNumberUnwrapper rtp_seq_num_unwrapper_; | 
|  | std::map</*unwrapped rtp sequence number*/ int64_t, | 
|  | /*index into packets*/ size_t> | 
|  | packet_indices_; | 
|  | }; | 
|  | std::map</*ssrc*/ uint32_t, PerSSRCInfo> ccfb_indices; | 
|  |  | 
|  | uint16_t current_overhead = kDefaultOverhead; | 
|  | Timestamp last_log_time = Timestamp::Zero(); | 
|  |  | 
|  | auto advance_time = [&](Timestamp new_log_time) { | 
|  | if (overhead_iter != overheads.end() && | 
|  | new_log_time >= overhead_iter->timestamp) { | 
|  | current_overhead = overhead_iter->overhead; | 
|  | ++overhead_iter; | 
|  | } | 
|  | // If we have a large time delta, it can be caused by a gap in logging, | 
|  | // therefore we don't want to match up sequence numbers as we might have had | 
|  | // a wraparound. | 
|  | if (new_log_time - last_log_time > TimeDelta::Seconds(30)) { | 
|  | transport_seq_num_unwrapper.Reset(); | 
|  | twcc_indices.clear(); | 
|  | ccfb_indices.clear(); | 
|  | } | 
|  | RTC_DCHECK_GE(new_log_time, last_log_time); | 
|  | last_log_time = new_log_time; | 
|  | }; | 
|  |  | 
|  | auto rtp_handler = [&](const LoggedRtpPacket& rtp) { | 
|  | advance_time(rtp.log_time()); | 
|  | MediaStreamInfo* stream = &streams[rtp.header.ssrc]; | 
|  | Timestamp capture_time = Timestamp::MinusInfinity(); | 
|  | if (!stream->rtx) { | 
|  | // RTX copy the timestamp of the retransmitted packets. This means that | 
|  | // RTX streams don't have a unique clock offset and frequency, so | 
|  | // the RTP timstamps can't be unwrapped. | 
|  |  | 
|  | // Add an offset to avoid `capture_ticks` to become negative in the case | 
|  | // of reordering. | 
|  | constexpr int64_t kStartingCaptureTimeTicks = 90 * 48 * 10000; | 
|  | int64_t capture_ticks = | 
|  | kStartingCaptureTimeTicks + | 
|  | stream->unwrap_capture_ticks.Unwrap(rtp.header.timestamp); | 
|  | // TODO(srte): Use logged sample rate when it is added to the format. | 
|  | capture_time = Timestamp::Seconds( | 
|  | capture_ticks / | 
|  | (stream->media_type == LoggedMediaType::kAudio ? 48000.0 : 90000.0)); | 
|  | } | 
|  | LoggedPacketInfo logged(rtp, stream->media_type, stream->rtx, capture_time); | 
|  | logged.overhead = current_overhead; | 
|  | if (logged.has_transport_seq_no) { | 
|  | logged.log_feedback_time = Timestamp::PlusInfinity(); | 
|  | int64_t unwrapped_seq_num = | 
|  | transport_seq_num_unwrapper.Unwrap(logged.transport_seq_no); | 
|  | if (twcc_indices.find(unwrapped_seq_num) != twcc_indices.end()) { | 
|  | Timestamp prev_log_packet_time = | 
|  | packets[twcc_indices[unwrapped_seq_num]].log_packet_time; | 
|  | RTC_LOG(LS_WARNING) | 
|  | << "Repeated sent packet sequence number: " << unwrapped_seq_num | 
|  | << " Packet time:" << prev_log_packet_time.seconds() << "s vs " | 
|  | << logged.log_packet_time.seconds() | 
|  | << "s at:" << rtp.log_time_ms() / 1000; | 
|  | } | 
|  | twcc_indices[unwrapped_seq_num] = packets.size(); | 
|  | } else { | 
|  | ccfb_indices[rtp.header.ssrc].AddPacketIndex(rtp.header.sequenceNumber, | 
|  | packets.size()); | 
|  | } | 
|  | packets.push_back(logged); | 
|  | }; | 
|  |  | 
|  | Timestamp twcc_feedback_base_time = Timestamp::MinusInfinity(); | 
|  | Timestamp twcc_last_feedback_base_time = Timestamp::MinusInfinity(); | 
|  |  | 
|  | auto twcc_feedback_handler = | 
|  | [&](const LoggedRtcpPacketTransportFeedback& logged_rtcp) { | 
|  | auto log_feedback_time = logged_rtcp.log_time(); | 
|  | advance_time(log_feedback_time); | 
|  | const auto& feedback = logged_rtcp.transport_feedback; | 
|  | // Add timestamp deltas to a local time base selected on first packet | 
|  | // arrival. This won't be the true time base, but makes it easier to | 
|  | // manually inspect time stamps. | 
|  | if (!twcc_last_feedback_base_time.IsFinite()) { | 
|  | twcc_feedback_base_time = log_feedback_time; | 
|  | } else { | 
|  | twcc_feedback_base_time += | 
|  | feedback.GetBaseDelta(twcc_last_feedback_base_time); | 
|  | } | 
|  | twcc_last_feedback_base_time = feedback.BaseTime(); | 
|  |  | 
|  | std::vector<LoggedPacketInfo*> packet_feedbacks; | 
|  | packet_feedbacks.reserve(feedback.GetPacketStatusCount()); | 
|  | std::vector<int64_t> unknown_seq_nums; | 
|  | feedback.ForAllPackets([&](uint16_t sequence_number, | 
|  | TimeDelta delta_since_base) { | 
|  | int64_t unwrapped_seq_num = | 
|  | transport_seq_num_unwrapper.Unwrap(sequence_number); | 
|  | auto it = twcc_indices.find(unwrapped_seq_num); | 
|  | if (it == twcc_indices.end()) { | 
|  | unknown_seq_nums.push_back(unwrapped_seq_num); | 
|  | return; | 
|  | } | 
|  | LoggedPacketInfo* sent = &packets[it->second]; | 
|  | if (log_feedback_time - sent->log_packet_time > | 
|  | TimeDelta::Seconds(60)) { | 
|  | RTC_LOG(LS_WARNING) | 
|  | << "Received very late feedback, possibly due to wraparound."; | 
|  | return; | 
|  | } | 
|  | if (delta_since_base.IsFinite()) { | 
|  | if (sent->reported_recv_time.IsInfinite()) { | 
|  | sent->reported_recv_time = | 
|  | twcc_feedback_base_time + delta_since_base; | 
|  | sent->log_feedback_time = log_feedback_time; | 
|  | } | 
|  | } else { | 
|  | if (sent->reported_recv_time.IsInfinite() && | 
|  | sent->log_feedback_time.IsInfinite()) { | 
|  | sent->reported_recv_time = Timestamp::PlusInfinity(); | 
|  | sent->log_feedback_time = log_feedback_time; | 
|  | } | 
|  | } | 
|  | packet_feedbacks.push_back(sent); | 
|  | }); | 
|  | if (!unknown_seq_nums.empty()) { | 
|  | RTC_LOG(LS_WARNING) | 
|  | << "Received feedback for unknown packets: " | 
|  | << unknown_seq_nums.front() << " - " << unknown_seq_nums.back(); | 
|  | } | 
|  | if (packet_feedbacks.empty()) | 
|  | return; | 
|  | LoggedPacketInfo* last = packet_feedbacks.back(); | 
|  | last->last_in_feedback = true; | 
|  | for (LoggedPacketInfo* fb : packet_feedbacks) { | 
|  | if (direction == PacketDirection::kOutgoingPacket) { | 
|  | if (last->reported_recv_time.IsFinite() && | 
|  | fb->reported_recv_time.IsFinite()) { | 
|  | fb->feedback_hold_duration = | 
|  | last->reported_recv_time - fb->reported_recv_time; | 
|  | } | 
|  | } else { | 
|  | fb->feedback_hold_duration = | 
|  | log_feedback_time - fb->log_packet_time; | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | Timestamp ccfb_feedback_offset = Timestamp::MinusInfinity(); | 
|  | std::optional<uint32_t> last_feedback_compact_ntp_time_; | 
|  | auto ccfb_feedback_handler = | 
|  | [&](const LoggedRtcpCongestionControlFeedback& logged_rtcp) { | 
|  | Timestamp log_feedback_time = logged_rtcp.log_time(); | 
|  | advance_time(log_feedback_time); | 
|  | const rtcp::CongestionControlFeedback& feedback = | 
|  | logged_rtcp.congestion_feedback; | 
|  |  | 
|  | if (ccfb_feedback_offset.IsInfinite()) { | 
|  | ccfb_feedback_offset = log_feedback_time; | 
|  | } | 
|  | TimeDelta feedback_delta = | 
|  | last_feedback_compact_ntp_time_.has_value() | 
|  | ? CompactNtpIntervalToTimeDelta( | 
|  | feedback.report_timestamp_compact_ntp() - | 
|  | *last_feedback_compact_ntp_time_) | 
|  | : TimeDelta::Zero(); | 
|  | last_feedback_compact_ntp_time_ = | 
|  | feedback.report_timestamp_compact_ntp(); | 
|  | if (feedback_delta < TimeDelta::Zero()) { | 
|  | RTC_LOG(LS_WARNING) | 
|  | << "Unexpected feedback ntp time delta " << feedback_delta << "."; | 
|  | ccfb_feedback_offset = log_feedback_time; | 
|  | } else { | 
|  | ccfb_feedback_offset += feedback_delta; | 
|  | } | 
|  | for (const rtcp::CongestionControlFeedback::PacketInfo& packet : | 
|  | feedback.packets()) { | 
|  | std::optional<size_t> packets_index = | 
|  | ccfb_indices[packet.ssrc].FindPacketIndex(packet.sequence_number); | 
|  | if (!packets_index.has_value()) { | 
|  | RTC_LOG(LS_WARNING) | 
|  | << " Got feedback for unknown packet, ssrc: " << packet.ssrc | 
|  | << " rtp seqno:" << packet.sequence_number; | 
|  | continue; | 
|  | } | 
|  | LoggedPacketInfo* sent = &packets[*packets_index]; | 
|  | RTC_DCHECK_EQ(packet.ssrc, sent->ssrc); | 
|  | RTC_DCHECK_EQ(packet.sequence_number, sent->stream_seq_no); | 
|  | if (log_feedback_time - sent->log_packet_time > | 
|  | TimeDelta::Seconds(60)) { | 
|  | RTC_LOG(LS_WARNING) | 
|  | << "Received very late feedback, possibly due to wraparound."; | 
|  | return; | 
|  | } | 
|  | if (packet.arrival_time_offset.IsFinite()) { | 
|  | if (sent->reported_recv_time.IsInfinite()) { | 
|  | sent->log_feedback_time = log_feedback_time; | 
|  | sent->feedback_hold_duration = packet.arrival_time_offset; | 
|  | sent->reported_recv_time = | 
|  | ccfb_feedback_offset - packet.arrival_time_offset; | 
|  | } | 
|  | } else { | 
|  | if (sent->log_feedback_time.IsInfinite()) { | 
|  | sent->log_feedback_time = log_feedback_time; | 
|  | sent->reported_recv_time = Timestamp::PlusInfinity(); | 
|  | } | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | RtcEventProcessor process; | 
|  | for (const auto& rtp_packets : rtp_packets_by_ssrc(direction)) { | 
|  | process.AddEvents(rtp_packets.packet_view, rtp_handler, direction); | 
|  | } | 
|  | if (direction == PacketDirection::kOutgoingPacket) { | 
|  | process.AddEvents(incoming_transport_feedback_, twcc_feedback_handler, | 
|  | PacketDirection::kIncomingPacket); | 
|  | process.AddEvents(incoming_congestion_feedback_, ccfb_feedback_handler, | 
|  | PacketDirection::kIncomingPacket); | 
|  | } else { | 
|  | process.AddEvents(outgoing_transport_feedback_, twcc_feedback_handler, | 
|  | PacketDirection::kOutgoingPacket); | 
|  | process.AddEvents(outgoing_congestion_feedback_, ccfb_feedback_handler, | 
|  | PacketDirection::kOutgoingPacket); | 
|  | } | 
|  | process.ProcessEventsInOrder(); | 
|  | return packets; | 
|  | } | 
|  |  | 
|  | std::vector<LoggedIceCandidatePairConfig> ParsedRtcEventLog::GetIceCandidates() | 
|  | const { | 
|  | std::vector<LoggedIceCandidatePairConfig> candidates; | 
|  | std::set<uint32_t> added; | 
|  | for (auto& candidate : ice_candidate_pair_configs()) { | 
|  | if (added.find(candidate.candidate_pair_id) == added.end()) { | 
|  | candidates.push_back(candidate); | 
|  | added.insert(candidate.candidate_pair_id); | 
|  | } | 
|  | } | 
|  | return candidates; | 
|  | } | 
|  |  | 
|  | std::vector<LoggedIceEvent> ParsedRtcEventLog::GetIceEvents() const { | 
|  | using CheckType = IceCandidatePairEventType; | 
|  | using ConfigType = IceCandidatePairConfigType; | 
|  | using Combined = LoggedIceEventType; | 
|  | std::map<CheckType, Combined> check_map( | 
|  | {{CheckType::kCheckSent, Combined::kCheckSent}, | 
|  | {CheckType::kCheckReceived, Combined::kCheckReceived}, | 
|  | {CheckType::kCheckResponseSent, Combined::kCheckResponseSent}, | 
|  | {CheckType::kCheckResponseReceived, Combined::kCheckResponseReceived}}); | 
|  | std::map<ConfigType, Combined> config_map( | 
|  | {{ConfigType::kAdded, Combined::kAdded}, | 
|  | {ConfigType::kUpdated, Combined::kUpdated}, | 
|  | {ConfigType::kDestroyed, Combined::kDestroyed}, | 
|  | {ConfigType::kSelected, Combined::kSelected}}); | 
|  | std::vector<LoggedIceEvent> log_events; | 
|  | auto handle_check = [&](const LoggedIceCandidatePairEvent& check) { | 
|  | log_events.push_back( | 
|  | LoggedIceEvent{.candidate_pair_id = check.candidate_pair_id, | 
|  | .log_time = Timestamp::Millis(check.log_time_ms()), | 
|  | .event_type = check_map[check.type]}); | 
|  | }; | 
|  | auto handle_config = [&](const LoggedIceCandidatePairConfig& conf) { | 
|  | log_events.push_back( | 
|  | LoggedIceEvent{.candidate_pair_id = conf.candidate_pair_id, | 
|  | .log_time = Timestamp::Millis(conf.log_time_ms()), | 
|  | .event_type = config_map[conf.type]}); | 
|  | }; | 
|  | RtcEventProcessor process; | 
|  | process.AddEvents(ice_candidate_pair_events(), handle_check); | 
|  | process.AddEvents(ice_candidate_pair_configs(), handle_config); | 
|  | process.ProcessEventsInOrder(); | 
|  | return log_events; | 
|  | } | 
|  |  | 
|  | std::vector<MatchedSendArrivalTimes> GetNetworkTrace( | 
|  | const ParsedRtcEventLog& parsed_log) { | 
|  | std::vector<MatchedSendArrivalTimes> rtp_rtcp_matched; | 
|  | for (auto& packet : | 
|  | parsed_log.GetPacketInfos(PacketDirection::kOutgoingPacket)) { | 
|  | if (packet.log_feedback_time.IsFinite()) { | 
|  | rtp_rtcp_matched.emplace_back(packet.log_feedback_time.ms(), | 
|  | packet.log_packet_time.ms(), | 
|  | packet.reported_recv_time.ms_or( | 
|  | MatchedSendArrivalTimes::kNotReceived), | 
|  | packet.size); | 
|  | } | 
|  | } | 
|  | return rtp_rtcp_matched; | 
|  | } | 
|  |  | 
|  | // Helper functions for new format start here | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::StoreParsedNewFormatEvent( | 
|  | const rtclog2::EventStream& stream) { | 
|  | RTC_DCHECK_EQ(stream.stream_size(), 0);  // No legacy format event. | 
|  |  | 
|  | RTC_DCHECK_EQ( | 
|  | stream.incoming_rtp_packets_size() + stream.outgoing_rtp_packets_size() + | 
|  | stream.incoming_rtcp_packets_size() + | 
|  | stream.outgoing_rtcp_packets_size() + | 
|  | stream.audio_playout_events_size() + stream.begin_log_events_size() + | 
|  | stream.end_log_events_size() + stream.loss_based_bwe_updates_size() + | 
|  | stream.delay_based_bwe_updates_size() + | 
|  | stream.dtls_transport_state_events_size() + | 
|  | stream.dtls_writable_states_size() + | 
|  | stream.audio_network_adaptations_size() + | 
|  | stream.probe_clusters_size() + stream.probe_success_size() + | 
|  | stream.probe_failure_size() + stream.alr_states_size() + | 
|  | stream.route_changes_size() + stream.remote_estimates_size() + | 
|  | stream.ice_candidate_configs_size() + | 
|  | stream.ice_candidate_events_size() + | 
|  | stream.audio_recv_stream_configs_size() + | 
|  | stream.audio_send_stream_configs_size() + | 
|  | stream.video_recv_stream_configs_size() + | 
|  | stream.video_send_stream_configs_size() + | 
|  | stream.generic_packets_sent_size() + | 
|  | stream.generic_packets_received_size() + | 
|  | stream.generic_acks_received_size() + | 
|  | stream.frame_decoded_events_size() + | 
|  | stream.neteq_set_minimum_delay_size(), | 
|  | 1u); | 
|  |  | 
|  | if (stream.incoming_rtp_packets_size() == 1) { | 
|  | return StoreIncomingRtpPackets(stream.incoming_rtp_packets(0)); | 
|  | } else if (stream.outgoing_rtp_packets_size() == 1) { | 
|  | return StoreOutgoingRtpPackets(stream.outgoing_rtp_packets(0)); | 
|  | } else if (stream.incoming_rtcp_packets_size() == 1) { | 
|  | return StoreIncomingRtcpPackets(stream.incoming_rtcp_packets(0)); | 
|  | } else if (stream.outgoing_rtcp_packets_size() == 1) { | 
|  | return StoreOutgoingRtcpPackets(stream.outgoing_rtcp_packets(0)); | 
|  | } else if (stream.audio_playout_events_size() == 1) { | 
|  | return StoreAudioPlayoutEvent(stream.audio_playout_events(0)); | 
|  | } else if (stream.begin_log_events_size() == 1) { | 
|  | return StoreStartEvent(stream.begin_log_events(0)); | 
|  | } else if (stream.end_log_events_size() == 1) { | 
|  | return StoreStopEvent(stream.end_log_events(0)); | 
|  | } else if (stream.loss_based_bwe_updates_size() == 1) { | 
|  | return StoreBweLossBasedUpdate(stream.loss_based_bwe_updates(0)); | 
|  | } else if (stream.delay_based_bwe_updates_size() == 1) { | 
|  | return StoreBweDelayBasedUpdate(stream.delay_based_bwe_updates(0)); | 
|  | } else if (stream.dtls_transport_state_events_size() == 1) { | 
|  | return StoreDtlsTransportState(stream.dtls_transport_state_events(0)); | 
|  | } else if (stream.dtls_writable_states_size() == 1) { | 
|  | return StoreDtlsWritableState(stream.dtls_writable_states(0)); | 
|  | } else if (stream.audio_network_adaptations_size() == 1) { | 
|  | return StoreAudioNetworkAdaptationEvent( | 
|  | stream.audio_network_adaptations(0)); | 
|  | } else if (stream.probe_clusters_size() == 1) { | 
|  | return StoreBweProbeClusterCreated(stream.probe_clusters(0)); | 
|  | } else if (stream.probe_success_size() == 1) { | 
|  | return StoreBweProbeSuccessEvent(stream.probe_success(0)); | 
|  | } else if (stream.probe_failure_size() == 1) { | 
|  | return StoreBweProbeFailureEvent(stream.probe_failure(0)); | 
|  | } else if (stream.alr_states_size() == 1) { | 
|  | return StoreAlrStateEvent(stream.alr_states(0)); | 
|  | } else if (stream.route_changes_size() == 1) { | 
|  | return StoreRouteChangeEvent(stream.route_changes(0)); | 
|  | } else if (stream.remote_estimates_size() == 1) { | 
|  | return StoreRemoteEstimateEvent(stream.remote_estimates(0)); | 
|  | } else if (stream.ice_candidate_configs_size() == 1) { | 
|  | return StoreIceCandidatePairConfig(stream.ice_candidate_configs(0)); | 
|  | } else if (stream.ice_candidate_events_size() == 1) { | 
|  | return StoreIceCandidateEvent(stream.ice_candidate_events(0)); | 
|  | } else if (stream.audio_recv_stream_configs_size() == 1) { | 
|  | return StoreAudioRecvConfig(stream.audio_recv_stream_configs(0)); | 
|  | } else if (stream.audio_send_stream_configs_size() == 1) { | 
|  | return StoreAudioSendConfig(stream.audio_send_stream_configs(0)); | 
|  | } else if (stream.video_recv_stream_configs_size() == 1) { | 
|  | return StoreVideoRecvConfig(stream.video_recv_stream_configs(0)); | 
|  | } else if (stream.video_send_stream_configs_size() == 1) { | 
|  | return StoreVideoSendConfig(stream.video_send_stream_configs(0)); | 
|  | } else if (stream.generic_packets_received_size() == 1) { | 
|  | return StoreGenericPacketReceivedEvent(stream.generic_packets_received(0)); | 
|  | } else if (stream.generic_packets_sent_size() == 1) { | 
|  | return StoreGenericPacketSentEvent(stream.generic_packets_sent(0)); | 
|  | } else if (stream.generic_acks_received_size() == 1) { | 
|  | return StoreGenericAckReceivedEvent(stream.generic_acks_received(0)); | 
|  | } else if (stream.frame_decoded_events_size() == 1) { | 
|  | return StoreFrameDecodedEvents(stream.frame_decoded_events(0)); | 
|  | } else if (stream.neteq_set_minimum_delay_size() == 1) { | 
|  | return StoreNetEqSetMinimumDelay(stream.neteq_set_minimum_delay(0)); | 
|  | } else { | 
|  | RTC_DCHECK_NOTREACHED(); | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::StoreAlrStateEvent( | 
|  | const rtclog2::AlrState& proto) { | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_timestamp_ms()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_in_alr()); | 
|  | LoggedAlrStateEvent alr_event; | 
|  | alr_event.timestamp = Timestamp::Millis(proto.timestamp_ms()); | 
|  | alr_event.in_alr = proto.in_alr(); | 
|  |  | 
|  | alr_state_events_.push_back(alr_event); | 
|  | // TODO(terelius): Should we delta encode this event type? | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::StoreRouteChangeEvent( | 
|  | const rtclog2::RouteChange& proto) { | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_timestamp_ms()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_connected()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_overhead()); | 
|  | LoggedRouteChangeEvent route_event; | 
|  | route_event.timestamp = Timestamp::Millis(proto.timestamp_ms()); | 
|  | route_event.connected = proto.connected(); | 
|  | route_event.overhead = proto.overhead(); | 
|  |  | 
|  | route_change_events_.push_back(route_event); | 
|  | // TODO(terelius): Should we delta encode this event type? | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::StoreRemoteEstimateEvent( | 
|  | const rtclog2::RemoteEstimates& proto) { | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_timestamp_ms()); | 
|  | // Base event | 
|  | LoggedRemoteEstimateEvent base_event; | 
|  | base_event.timestamp = Timestamp::Millis(proto.timestamp_ms()); | 
|  |  | 
|  | std::optional<uint64_t> base_link_capacity_lower_kbps; | 
|  | if (proto.has_link_capacity_lower_kbps()) { | 
|  | base_link_capacity_lower_kbps = proto.link_capacity_lower_kbps(); | 
|  | base_event.link_capacity_lower = | 
|  | DataRate::KilobitsPerSec(proto.link_capacity_lower_kbps()); | 
|  | } | 
|  |  | 
|  | std::optional<uint64_t> base_link_capacity_upper_kbps; | 
|  | if (proto.has_link_capacity_upper_kbps()) { | 
|  | base_link_capacity_upper_kbps = proto.link_capacity_upper_kbps(); | 
|  | base_event.link_capacity_upper = | 
|  | DataRate::KilobitsPerSec(proto.link_capacity_upper_kbps()); | 
|  | } | 
|  |  | 
|  | remote_estimate_events_.push_back(base_event); | 
|  |  | 
|  | const size_t number_of_deltas = | 
|  | proto.has_number_of_deltas() ? proto.number_of_deltas() : 0u; | 
|  | if (number_of_deltas == 0) { | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | // timestamp_ms | 
|  | auto timestamp_ms_values = | 
|  | DecodeDeltas(proto.timestamp_ms_deltas(), | 
|  | ToUnsigned(proto.timestamp_ms()), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(timestamp_ms_values.size(), number_of_deltas); | 
|  |  | 
|  | // link_capacity_lower_kbps | 
|  | auto link_capacity_lower_kbps_values = | 
|  | DecodeDeltas(proto.link_capacity_lower_kbps_deltas(), | 
|  | base_link_capacity_lower_kbps, number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(link_capacity_lower_kbps_values.size(), | 
|  | number_of_deltas); | 
|  |  | 
|  | // link_capacity_upper_kbps | 
|  | auto link_capacity_upper_kbps_values = | 
|  | DecodeDeltas(proto.link_capacity_upper_kbps_deltas(), | 
|  | base_link_capacity_upper_kbps, number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(link_capacity_upper_kbps_values.size(), | 
|  | number_of_deltas); | 
|  |  | 
|  | // Populate events from decoded deltas | 
|  | for (size_t i = 0; i < number_of_deltas; ++i) { | 
|  | LoggedRemoteEstimateEvent event; | 
|  | RTC_PARSE_CHECK_OR_RETURN(timestamp_ms_values[i].has_value()); | 
|  | event.timestamp = Timestamp::Millis(*timestamp_ms_values[i]); | 
|  | if (link_capacity_lower_kbps_values[i]) | 
|  | event.link_capacity_lower = | 
|  | DataRate::KilobitsPerSec(*link_capacity_lower_kbps_values[i]); | 
|  | if (link_capacity_upper_kbps_values[i]) | 
|  | event.link_capacity_upper = | 
|  | DataRate::KilobitsPerSec(*link_capacity_upper_kbps_values[i]); | 
|  | remote_estimate_events_.push_back(event); | 
|  | } | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::StoreAudioPlayoutEvent( | 
|  | const rtclog2::AudioPlayoutEvents& proto) { | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_timestamp_ms()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_local_ssrc()); | 
|  |  | 
|  | // Base event | 
|  | audio_playout_events_[proto.local_ssrc()].emplace_back( | 
|  | Timestamp::Millis(proto.timestamp_ms()), proto.local_ssrc()); | 
|  |  | 
|  | const size_t number_of_deltas = | 
|  | proto.has_number_of_deltas() ? proto.number_of_deltas() : 0u; | 
|  | if (number_of_deltas == 0) { | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | // timestamp_ms | 
|  | std::vector<std::optional<uint64_t>> timestamp_ms_values = | 
|  | DecodeDeltas(proto.timestamp_ms_deltas(), | 
|  | ToUnsigned(proto.timestamp_ms()), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(timestamp_ms_values.size(), number_of_deltas); | 
|  |  | 
|  | // local_ssrc | 
|  | std::vector<std::optional<uint64_t>> local_ssrc_values = DecodeDeltas( | 
|  | proto.local_ssrc_deltas(), proto.local_ssrc(), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(local_ssrc_values.size(), number_of_deltas); | 
|  |  | 
|  | // Populate events from decoded deltas | 
|  | for (size_t i = 0; i < number_of_deltas; ++i) { | 
|  | RTC_PARSE_CHECK_OR_RETURN(timestamp_ms_values[i].has_value()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(local_ssrc_values[i].has_value()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_LE(local_ssrc_values[i].value(), | 
|  | std::numeric_limits<uint32_t>::max()); | 
|  |  | 
|  | int64_t timestamp_ms; | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | ToSigned(timestamp_ms_values[i].value(), ×tamp_ms)); | 
|  |  | 
|  | const uint32_t local_ssrc = | 
|  | static_cast<uint32_t>(local_ssrc_values[i].value()); | 
|  | audio_playout_events_[local_ssrc].emplace_back( | 
|  | Timestamp::Millis(timestamp_ms), local_ssrc); | 
|  | } | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::StoreNetEqSetMinimumDelay( | 
|  | const rtclog2::NetEqSetMinimumDelay& proto) { | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_timestamp_ms()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_remote_ssrc()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_minimum_delay_ms()); | 
|  |  | 
|  | // Base event | 
|  | neteq_set_minimum_delay_events_[proto.remote_ssrc()].emplace_back( | 
|  | Timestamp::Millis(proto.timestamp_ms()), proto.remote_ssrc(), | 
|  | static_cast<int>(proto.minimum_delay_ms())); | 
|  |  | 
|  | const size_t number_of_deltas = | 
|  | proto.has_number_of_deltas() ? proto.number_of_deltas() : 0u; | 
|  | if (number_of_deltas == 0) { | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | // timestamp_ms | 
|  | std::vector<std::optional<uint64_t>> timestamp_ms_values = | 
|  | DecodeDeltas(proto.timestamp_ms_deltas(), | 
|  | ToUnsigned(proto.timestamp_ms()), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(timestamp_ms_values.size(), number_of_deltas); | 
|  |  | 
|  | // remote_ssrc | 
|  | std::vector<std::optional<uint64_t>> remote_ssrc_values = DecodeDeltas( | 
|  | proto.remote_ssrc_deltas(), proto.remote_ssrc(), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(remote_ssrc_values.size(), number_of_deltas); | 
|  |  | 
|  | // minimum_delay_ms | 
|  | std::vector<std::optional<uint64_t>> minimum_delay_ms_values = | 
|  | DecodeDeltas(proto.minimum_delay_ms_deltas(), | 
|  | ToUnsigned(proto.minimum_delay_ms()), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(minimum_delay_ms_values.size(), | 
|  | number_of_deltas); | 
|  |  | 
|  | // Populate events from decoded deltas | 
|  | for (size_t i = 0; i < number_of_deltas; ++i) { | 
|  | RTC_PARSE_CHECK_OR_RETURN(timestamp_ms_values[i].has_value()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(remote_ssrc_values[i].has_value()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_LE(remote_ssrc_values[i].value(), | 
|  | std::numeric_limits<uint32_t>::max()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(minimum_delay_ms_values[i].has_value()); | 
|  |  | 
|  | int64_t timestamp_ms; | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | ToSigned(timestamp_ms_values[i].value(), ×tamp_ms)); | 
|  |  | 
|  | const uint32_t remote_ssrc = | 
|  | static_cast<uint32_t>(remote_ssrc_values[i].value()); | 
|  | int minimum_delay_ms; | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | ToSigned(minimum_delay_ms_values[i].value(), &minimum_delay_ms)); | 
|  | neteq_set_minimum_delay_events_[remote_ssrc].emplace_back( | 
|  | Timestamp::Millis(timestamp_ms), remote_ssrc, minimum_delay_ms); | 
|  | } | 
|  |  | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::StoreIncomingRtpPackets( | 
|  | const rtclog2::IncomingRtpPackets& proto) { | 
|  | return StoreRtpPackets(proto, &incoming_rtp_packets_map_); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::StoreOutgoingRtpPackets( | 
|  | const rtclog2::OutgoingRtpPackets& proto) { | 
|  | return StoreRtpPackets(proto, &outgoing_rtp_packets_map_); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::StoreIncomingRtcpPackets( | 
|  | const rtclog2::IncomingRtcpPackets& proto) { | 
|  | return StoreRtcpPackets(proto, &incoming_rtcp_packets_, | 
|  | /*remove_duplicates=*/true); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::StoreOutgoingRtcpPackets( | 
|  | const rtclog2::OutgoingRtcpPackets& proto) { | 
|  | return StoreRtcpPackets(proto, &outgoing_rtcp_packets_, | 
|  | /*remove_duplicates=*/false); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::StoreStartEvent( | 
|  | const rtclog2::BeginLogEvent& proto) { | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_timestamp_ms()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_version()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_utc_time_ms()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(proto.version(), 2); | 
|  | LoggedStartEvent start_event(Timestamp::Millis(proto.timestamp_ms()), | 
|  | Timestamp::Millis(proto.utc_time_ms())); | 
|  |  | 
|  | start_log_events_.push_back(start_event); | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::StoreStopEvent( | 
|  | const rtclog2::EndLogEvent& proto) { | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_timestamp_ms()); | 
|  | LoggedStopEvent stop_event(Timestamp::Millis(proto.timestamp_ms())); | 
|  |  | 
|  | stop_log_events_.push_back(stop_event); | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::StoreBweLossBasedUpdate( | 
|  | const rtclog2::LossBasedBweUpdates& proto) { | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_timestamp_ms()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_bitrate_bps()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_fraction_loss()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_total_packets()); | 
|  |  | 
|  | // Base event | 
|  | bwe_loss_updates_.emplace_back(Timestamp::Millis(proto.timestamp_ms()), | 
|  | proto.bitrate_bps(), proto.fraction_loss(), | 
|  | proto.total_packets()); | 
|  |  | 
|  | const size_t number_of_deltas = | 
|  | proto.has_number_of_deltas() ? proto.number_of_deltas() : 0u; | 
|  | if (number_of_deltas == 0) { | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | // timestamp_ms | 
|  | std::vector<std::optional<uint64_t>> timestamp_ms_values = | 
|  | DecodeDeltas(proto.timestamp_ms_deltas(), | 
|  | ToUnsigned(proto.timestamp_ms()), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(timestamp_ms_values.size(), number_of_deltas); | 
|  |  | 
|  | // bitrate_bps | 
|  | std::vector<std::optional<uint64_t>> bitrate_bps_values = DecodeDeltas( | 
|  | proto.bitrate_bps_deltas(), proto.bitrate_bps(), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(bitrate_bps_values.size(), number_of_deltas); | 
|  |  | 
|  | // fraction_loss | 
|  | std::vector<std::optional<uint64_t>> fraction_loss_values = DecodeDeltas( | 
|  | proto.fraction_loss_deltas(), proto.fraction_loss(), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(fraction_loss_values.size(), number_of_deltas); | 
|  |  | 
|  | // total_packets | 
|  | std::vector<std::optional<uint64_t>> total_packets_values = DecodeDeltas( | 
|  | proto.total_packets_deltas(), proto.total_packets(), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(total_packets_values.size(), number_of_deltas); | 
|  |  | 
|  | // Populate events from decoded deltas | 
|  | for (size_t i = 0; i < number_of_deltas; ++i) { | 
|  | RTC_PARSE_CHECK_OR_RETURN(timestamp_ms_values[i].has_value()); | 
|  | int64_t timestamp_ms; | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | ToSigned(timestamp_ms_values[i].value(), ×tamp_ms)); | 
|  |  | 
|  | RTC_PARSE_CHECK_OR_RETURN(bitrate_bps_values[i].has_value()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_LE(bitrate_bps_values[i].value(), | 
|  | std::numeric_limits<uint32_t>::max()); | 
|  | const uint32_t bitrate_bps = | 
|  | static_cast<uint32_t>(bitrate_bps_values[i].value()); | 
|  |  | 
|  | RTC_PARSE_CHECK_OR_RETURN(fraction_loss_values[i].has_value()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_LE(fraction_loss_values[i].value(), | 
|  | std::numeric_limits<uint32_t>::max()); | 
|  | const uint32_t fraction_loss = | 
|  | static_cast<uint32_t>(fraction_loss_values[i].value()); | 
|  |  | 
|  | RTC_PARSE_CHECK_OR_RETURN(total_packets_values[i].has_value()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_LE(total_packets_values[i].value(), | 
|  | std::numeric_limits<uint32_t>::max()); | 
|  | const uint32_t total_packets = | 
|  | static_cast<uint32_t>(total_packets_values[i].value()); | 
|  |  | 
|  | bwe_loss_updates_.emplace_back(Timestamp::Millis(timestamp_ms), bitrate_bps, | 
|  | fraction_loss, total_packets); | 
|  | } | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::StoreBweDelayBasedUpdate( | 
|  | const rtclog2::DelayBasedBweUpdates& proto) { | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_timestamp_ms()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_bitrate_bps()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_detector_state()); | 
|  |  | 
|  | // Base event | 
|  | const BandwidthUsage base_detector_state = | 
|  | GetRuntimeDetectorState(proto.detector_state()); | 
|  | bwe_delay_updates_.emplace_back(Timestamp::Millis(proto.timestamp_ms()), | 
|  | proto.bitrate_bps(), base_detector_state); | 
|  |  | 
|  | const size_t number_of_deltas = | 
|  | proto.has_number_of_deltas() ? proto.number_of_deltas() : 0u; | 
|  | if (number_of_deltas == 0) { | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | // timestamp_ms | 
|  | std::vector<std::optional<uint64_t>> timestamp_ms_values = | 
|  | DecodeDeltas(proto.timestamp_ms_deltas(), | 
|  | ToUnsigned(proto.timestamp_ms()), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(timestamp_ms_values.size(), number_of_deltas); | 
|  |  | 
|  | // bitrate_bps | 
|  | std::vector<std::optional<uint64_t>> bitrate_bps_values = DecodeDeltas( | 
|  | proto.bitrate_bps_deltas(), proto.bitrate_bps(), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(bitrate_bps_values.size(), number_of_deltas); | 
|  |  | 
|  | // detector_state | 
|  | std::vector<std::optional<uint64_t>> detector_state_values = DecodeDeltas( | 
|  | proto.detector_state_deltas(), | 
|  | static_cast<uint64_t>(proto.detector_state()), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(detector_state_values.size(), number_of_deltas); | 
|  |  | 
|  | // Populate events from decoded deltas | 
|  | for (size_t i = 0; i < number_of_deltas; ++i) { | 
|  | RTC_PARSE_CHECK_OR_RETURN(timestamp_ms_values[i].has_value()); | 
|  | int64_t timestamp_ms; | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | ToSigned(timestamp_ms_values[i].value(), ×tamp_ms)); | 
|  |  | 
|  | RTC_PARSE_CHECK_OR_RETURN(bitrate_bps_values[i].has_value()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_LE(bitrate_bps_values[i].value(), | 
|  | std::numeric_limits<uint32_t>::max()); | 
|  | const uint32_t bitrate_bps = | 
|  | static_cast<uint32_t>(bitrate_bps_values[i].value()); | 
|  |  | 
|  | RTC_PARSE_CHECK_OR_RETURN(detector_state_values[i].has_value()); | 
|  | const auto detector_state = | 
|  | static_cast<rtclog2::DelayBasedBweUpdates::DetectorState>( | 
|  | detector_state_values[i].value()); | 
|  |  | 
|  | bwe_delay_updates_.emplace_back(Timestamp::Millis(timestamp_ms), | 
|  | bitrate_bps, | 
|  | GetRuntimeDetectorState(detector_state)); | 
|  | } | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::StoreBweProbeClusterCreated( | 
|  | const rtclog2::BweProbeCluster& proto) { | 
|  | LoggedBweProbeClusterCreatedEvent probe_cluster; | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_timestamp_ms()); | 
|  | probe_cluster.timestamp = Timestamp::Millis(proto.timestamp_ms()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_id()); | 
|  | probe_cluster.id = proto.id(); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_bitrate_bps()); | 
|  | probe_cluster.bitrate_bps = proto.bitrate_bps(); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_min_packets()); | 
|  | probe_cluster.min_packets = proto.min_packets(); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_min_bytes()); | 
|  | probe_cluster.min_bytes = proto.min_bytes(); | 
|  |  | 
|  | bwe_probe_cluster_created_events_.push_back(probe_cluster); | 
|  |  | 
|  | // TODO(terelius): Should we delta encode this event type? | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::StoreBweProbeSuccessEvent( | 
|  | const rtclog2::BweProbeResultSuccess& proto) { | 
|  | LoggedBweProbeSuccessEvent probe_result; | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_timestamp_ms()); | 
|  | probe_result.timestamp = Timestamp::Millis(proto.timestamp_ms()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_id()); | 
|  | probe_result.id = proto.id(); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_bitrate_bps()); | 
|  | probe_result.bitrate_bps = proto.bitrate_bps(); | 
|  |  | 
|  | bwe_probe_success_events_.push_back(probe_result); | 
|  |  | 
|  | // TODO(terelius): Should we delta encode this event type? | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::StoreBweProbeFailureEvent( | 
|  | const rtclog2::BweProbeResultFailure& proto) { | 
|  | LoggedBweProbeFailureEvent probe_result; | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_timestamp_ms()); | 
|  | probe_result.timestamp = Timestamp::Millis(proto.timestamp_ms()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_id()); | 
|  | probe_result.id = proto.id(); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_failure()); | 
|  | probe_result.failure_reason = GetRuntimeProbeFailureReason(proto.failure()); | 
|  |  | 
|  | bwe_probe_failure_events_.push_back(probe_result); | 
|  |  | 
|  | // TODO(terelius): Should we delta encode this event type? | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::StoreFrameDecodedEvents( | 
|  | const rtclog2::FrameDecodedEvents& proto) { | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_timestamp_ms()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_ssrc()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_render_time_ms()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_width()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_height()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_codec()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_qp()); | 
|  |  | 
|  | LoggedFrameDecoded base_frame; | 
|  | base_frame.timestamp = Timestamp::Millis(proto.timestamp_ms()); | 
|  | base_frame.ssrc = proto.ssrc(); | 
|  | base_frame.render_time_ms = proto.render_time_ms(); | 
|  | base_frame.width = proto.width(); | 
|  | base_frame.height = proto.height(); | 
|  | base_frame.codec = GetRuntimeCodecType(proto.codec()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_GE(proto.qp(), 0); | 
|  | RTC_PARSE_CHECK_OR_RETURN_LE(proto.qp(), 255); | 
|  | base_frame.qp = static_cast<uint8_t>(proto.qp()); | 
|  |  | 
|  | decoded_frames_[base_frame.ssrc].push_back(base_frame); | 
|  |  | 
|  | const size_t number_of_deltas = | 
|  | proto.has_number_of_deltas() ? proto.number_of_deltas() : 0u; | 
|  | if (number_of_deltas == 0) { | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | // timestamp_ms | 
|  | std::vector<std::optional<uint64_t>> timestamp_ms_values = | 
|  | DecodeDeltas(proto.timestamp_ms_deltas(), | 
|  | ToUnsigned(proto.timestamp_ms()), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(timestamp_ms_values.size(), number_of_deltas); | 
|  |  | 
|  | // SSRC | 
|  | std::vector<std::optional<uint64_t>> ssrc_values = | 
|  | DecodeDeltas(proto.ssrc_deltas(), proto.ssrc(), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(ssrc_values.size(), number_of_deltas); | 
|  |  | 
|  | // render_time_ms | 
|  | std::vector<std::optional<uint64_t>> render_time_ms_values = | 
|  | DecodeDeltas(proto.render_time_ms_deltas(), | 
|  | ToUnsigned(proto.render_time_ms()), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(render_time_ms_values.size(), number_of_deltas); | 
|  |  | 
|  | // width | 
|  | std::vector<std::optional<uint64_t>> width_values = DecodeDeltas( | 
|  | proto.width_deltas(), ToUnsigned(proto.width()), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(width_values.size(), number_of_deltas); | 
|  |  | 
|  | // height | 
|  | std::vector<std::optional<uint64_t>> height_values = DecodeDeltas( | 
|  | proto.height_deltas(), ToUnsigned(proto.height()), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(height_values.size(), number_of_deltas); | 
|  |  | 
|  | // codec | 
|  | std::vector<std::optional<uint64_t>> codec_values = | 
|  | DecodeDeltas(proto.codec_deltas(), static_cast<uint64_t>(proto.codec()), | 
|  | number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(codec_values.size(), number_of_deltas); | 
|  |  | 
|  | // qp | 
|  | std::vector<std::optional<uint64_t>> qp_values = | 
|  | DecodeDeltas(proto.qp_deltas(), proto.qp(), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(qp_values.size(), number_of_deltas); | 
|  |  | 
|  | // Populate events from decoded deltas | 
|  | for (size_t i = 0; i < number_of_deltas; ++i) { | 
|  | LoggedFrameDecoded frame; | 
|  | int64_t timestamp_ms; | 
|  | RTC_PARSE_CHECK_OR_RETURN(timestamp_ms_values[i].has_value()); | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | ToSigned(timestamp_ms_values[i].value(), ×tamp_ms)); | 
|  | frame.timestamp = Timestamp::Millis(timestamp_ms); | 
|  |  | 
|  | RTC_PARSE_CHECK_OR_RETURN(ssrc_values[i].has_value()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_LE(ssrc_values[i].value(), | 
|  | std::numeric_limits<uint32_t>::max()); | 
|  | frame.ssrc = static_cast<uint32_t>(ssrc_values[i].value()); | 
|  |  | 
|  | RTC_PARSE_CHECK_OR_RETURN(render_time_ms_values[i].has_value()); | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | ToSigned(render_time_ms_values[i].value(), &frame.render_time_ms)); | 
|  |  | 
|  | RTC_PARSE_CHECK_OR_RETURN(width_values[i].has_value()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(ToSigned(width_values[i].value(), &frame.width)); | 
|  |  | 
|  | RTC_PARSE_CHECK_OR_RETURN(height_values[i].has_value()); | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | ToSigned(height_values[i].value(), &frame.height)); | 
|  |  | 
|  | RTC_PARSE_CHECK_OR_RETURN(codec_values[i].has_value()); | 
|  | frame.codec = | 
|  | GetRuntimeCodecType(static_cast<rtclog2::FrameDecodedEvents::Codec>( | 
|  | codec_values[i].value())); | 
|  |  | 
|  | RTC_PARSE_CHECK_OR_RETURN(qp_values[i].has_value()); | 
|  | RTC_PARSE_CHECK_OR_RETURN_LE(qp_values[i].value(), | 
|  | std::numeric_limits<uint8_t>::max()); | 
|  | frame.qp = static_cast<uint8_t>(qp_values[i].value()); | 
|  |  | 
|  | decoded_frames_[frame.ssrc].push_back(frame); | 
|  | } | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::StoreGenericAckReceivedEvent( | 
|  | const rtclog2::GenericAckReceived& proto) { | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_timestamp_ms()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_packet_number()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_acked_packet_number()); | 
|  | // receive_acked_packet_time_ms is optional. | 
|  |  | 
|  | std::optional<int64_t> base_receive_acked_packet_time_ms; | 
|  | if (proto.has_receive_acked_packet_time_ms()) { | 
|  | base_receive_acked_packet_time_ms = proto.receive_acked_packet_time_ms(); | 
|  | } | 
|  | generic_acks_received_.push_back( | 
|  | {Timestamp::Millis(proto.timestamp_ms()), proto.packet_number(), | 
|  | proto.acked_packet_number(), base_receive_acked_packet_time_ms}); | 
|  |  | 
|  | const size_t number_of_deltas = | 
|  | proto.has_number_of_deltas() ? proto.number_of_deltas() : 0u; | 
|  | if (number_of_deltas == 0) { | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | // timestamp_ms | 
|  | std::vector<std::optional<uint64_t>> timestamp_ms_values = | 
|  | DecodeDeltas(proto.timestamp_ms_deltas(), | 
|  | ToUnsigned(proto.timestamp_ms()), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(timestamp_ms_values.size(), number_of_deltas); | 
|  |  | 
|  | // packet_number | 
|  | std::vector<std::optional<uint64_t>> packet_number_values = | 
|  | DecodeDeltas(proto.packet_number_deltas(), | 
|  | ToUnsigned(proto.packet_number()), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(packet_number_values.size(), number_of_deltas); | 
|  |  | 
|  | // acked_packet_number | 
|  | std::vector<std::optional<uint64_t>> acked_packet_number_values = | 
|  | DecodeDeltas(proto.acked_packet_number_deltas(), | 
|  | ToUnsigned(proto.acked_packet_number()), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(acked_packet_number_values.size(), | 
|  | number_of_deltas); | 
|  |  | 
|  | // optional receive_acked_packet_time_ms | 
|  | const std::optional<uint64_t> unsigned_receive_acked_packet_time_ms_base = | 
|  | proto.has_receive_acked_packet_time_ms() | 
|  | ? std::optional<uint64_t>( | 
|  | ToUnsigned(proto.receive_acked_packet_time_ms())) | 
|  | : std::optional<uint64_t>(); | 
|  | std::vector<std::optional<uint64_t>> receive_acked_packet_time_ms_values = | 
|  | DecodeDeltas(proto.receive_acked_packet_time_ms_deltas(), | 
|  | unsigned_receive_acked_packet_time_ms_base, | 
|  | number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(receive_acked_packet_time_ms_values.size(), | 
|  | number_of_deltas); | 
|  |  | 
|  | for (size_t i = 0; i < number_of_deltas; i++) { | 
|  | int64_t timestamp_ms; | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | ToSigned(timestamp_ms_values[i].value(), ×tamp_ms)); | 
|  | int64_t packet_number; | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | ToSigned(packet_number_values[i].value(), &packet_number)); | 
|  | int64_t acked_packet_number; | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | ToSigned(acked_packet_number_values[i].value(), &acked_packet_number)); | 
|  | std::optional<int64_t> receive_acked_packet_time_ms; | 
|  |  | 
|  | if (receive_acked_packet_time_ms_values[i].has_value()) { | 
|  | int64_t value; | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | ToSigned(receive_acked_packet_time_ms_values[i].value(), &value)); | 
|  | receive_acked_packet_time_ms = value; | 
|  | } | 
|  | generic_acks_received_.push_back({Timestamp::Millis(timestamp_ms), | 
|  | packet_number, acked_packet_number, | 
|  | receive_acked_packet_time_ms}); | 
|  | } | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::StoreGenericPacketSentEvent( | 
|  | const rtclog2::GenericPacketSent& proto) { | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_timestamp_ms()); | 
|  |  | 
|  | // Base event | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_packet_number()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_overhead_length()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_payload_length()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_padding_length()); | 
|  |  | 
|  | generic_packets_sent_.push_back( | 
|  | {Timestamp::Millis(proto.timestamp_ms()), proto.packet_number(), | 
|  | static_cast<size_t>(proto.overhead_length()), | 
|  | static_cast<size_t>(proto.payload_length()), | 
|  | static_cast<size_t>(proto.padding_length())}); | 
|  |  | 
|  | const size_t number_of_deltas = | 
|  | proto.has_number_of_deltas() ? proto.number_of_deltas() : 0u; | 
|  | if (number_of_deltas == 0) { | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | // timestamp_ms | 
|  | std::vector<std::optional<uint64_t>> timestamp_ms_values = | 
|  | DecodeDeltas(proto.timestamp_ms_deltas(), | 
|  | ToUnsigned(proto.timestamp_ms()), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(timestamp_ms_values.size(), number_of_deltas); | 
|  |  | 
|  | // packet_number | 
|  | std::vector<std::optional<uint64_t>> packet_number_values = | 
|  | DecodeDeltas(proto.packet_number_deltas(), | 
|  | ToUnsigned(proto.packet_number()), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(packet_number_values.size(), number_of_deltas); | 
|  |  | 
|  | std::vector<std::optional<uint64_t>> overhead_length_values = | 
|  | DecodeDeltas(proto.overhead_length_deltas(), proto.overhead_length(), | 
|  | number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(overhead_length_values.size(), number_of_deltas); | 
|  |  | 
|  | std::vector<std::optional<uint64_t>> payload_length_values = DecodeDeltas( | 
|  | proto.payload_length_deltas(), proto.payload_length(), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(payload_length_values.size(), number_of_deltas); | 
|  |  | 
|  | std::vector<std::optional<uint64_t>> padding_length_values = DecodeDeltas( | 
|  | proto.padding_length_deltas(), proto.padding_length(), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(padding_length_values.size(), number_of_deltas); | 
|  |  | 
|  | for (size_t i = 0; i < number_of_deltas; i++) { | 
|  | int64_t timestamp_ms; | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | ToSigned(timestamp_ms_values[i].value(), ×tamp_ms)); | 
|  | int64_t packet_number; | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | ToSigned(packet_number_values[i].value(), &packet_number)); | 
|  | RTC_PARSE_CHECK_OR_RETURN(overhead_length_values[i].has_value()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(payload_length_values[i].has_value()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(padding_length_values[i].has_value()); | 
|  | generic_packets_sent_.push_back( | 
|  | {Timestamp::Millis(timestamp_ms), packet_number, | 
|  | static_cast<size_t>(overhead_length_values[i].value()), | 
|  | static_cast<size_t>(payload_length_values[i].value()), | 
|  | static_cast<size_t>(padding_length_values[i].value())}); | 
|  | } | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus | 
|  | ParsedRtcEventLog::StoreGenericPacketReceivedEvent( | 
|  | const rtclog2::GenericPacketReceived& proto) { | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_timestamp_ms()); | 
|  |  | 
|  | // Base event | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_packet_number()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_packet_length()); | 
|  |  | 
|  | generic_packets_received_.push_back({Timestamp::Millis(proto.timestamp_ms()), | 
|  | proto.packet_number(), | 
|  | proto.packet_length()}); | 
|  |  | 
|  | const size_t number_of_deltas = | 
|  | proto.has_number_of_deltas() ? proto.number_of_deltas() : 0u; | 
|  | if (number_of_deltas == 0) { | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | // timestamp_ms | 
|  | std::vector<std::optional<uint64_t>> timestamp_ms_values = | 
|  | DecodeDeltas(proto.timestamp_ms_deltas(), | 
|  | ToUnsigned(proto.timestamp_ms()), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(timestamp_ms_values.size(), number_of_deltas); | 
|  |  | 
|  | // packet_number | 
|  | std::vector<std::optional<uint64_t>> packet_number_values = | 
|  | DecodeDeltas(proto.packet_number_deltas(), | 
|  | ToUnsigned(proto.packet_number()), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(packet_number_values.size(), number_of_deltas); | 
|  |  | 
|  | std::vector<std::optional<uint64_t>> packet_length_values = DecodeDeltas( | 
|  | proto.packet_length_deltas(), proto.packet_length(), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(packet_length_values.size(), number_of_deltas); | 
|  |  | 
|  | for (size_t i = 0; i < number_of_deltas; i++) { | 
|  | int64_t timestamp_ms; | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | ToSigned(timestamp_ms_values[i].value(), ×tamp_ms)); | 
|  | int64_t packet_number; | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | ToSigned(packet_number_values[i].value(), &packet_number)); | 
|  | RTC_PARSE_CHECK_OR_RETURN_LE(packet_length_values[i].value(), | 
|  | std::numeric_limits<int32_t>::max()); | 
|  | int32_t packet_length = | 
|  | static_cast<int32_t>(packet_length_values[i].value()); | 
|  | generic_packets_received_.push_back( | 
|  | {Timestamp::Millis(timestamp_ms), packet_number, packet_length}); | 
|  | } | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus | 
|  | ParsedRtcEventLog::StoreAudioNetworkAdaptationEvent( | 
|  | const rtclog2::AudioNetworkAdaptations& proto) { | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_timestamp_ms()); | 
|  |  | 
|  | // Base event | 
|  | { | 
|  | AudioEncoderRuntimeConfig runtime_config; | 
|  | if (proto.has_bitrate_bps()) { | 
|  | runtime_config.bitrate_bps = proto.bitrate_bps(); | 
|  | } | 
|  | if (proto.has_frame_length_ms()) { | 
|  | runtime_config.frame_length_ms = proto.frame_length_ms(); | 
|  | } | 
|  | if (proto.has_uplink_packet_loss_fraction()) { | 
|  | float uplink_packet_loss_fraction; | 
|  | RTC_PARSE_CHECK_OR_RETURN(ParsePacketLossFractionFromProtoFormat( | 
|  | proto.uplink_packet_loss_fraction(), &uplink_packet_loss_fraction)); | 
|  | runtime_config.uplink_packet_loss_fraction = uplink_packet_loss_fraction; | 
|  | } | 
|  | if (proto.has_enable_fec()) { | 
|  | runtime_config.enable_fec = proto.enable_fec(); | 
|  | } | 
|  | if (proto.has_enable_dtx()) { | 
|  | runtime_config.enable_dtx = proto.enable_dtx(); | 
|  | } | 
|  | if (proto.has_num_channels()) { | 
|  | // Note: Encoding N as N-1 only done for `num_channels_deltas`. | 
|  | runtime_config.num_channels = proto.num_channels(); | 
|  | } | 
|  | audio_network_adaptation_events_.emplace_back( | 
|  | Timestamp::Millis(proto.timestamp_ms()), runtime_config); | 
|  | } | 
|  |  | 
|  | const size_t number_of_deltas = | 
|  | proto.has_number_of_deltas() ? proto.number_of_deltas() : 0u; | 
|  | if (number_of_deltas == 0) { | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | // timestamp_ms | 
|  | std::vector<std::optional<uint64_t>> timestamp_ms_values = | 
|  | DecodeDeltas(proto.timestamp_ms_deltas(), | 
|  | ToUnsigned(proto.timestamp_ms()), number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(timestamp_ms_values.size(), number_of_deltas); | 
|  |  | 
|  | // bitrate_bps | 
|  | const std::optional<uint64_t> unsigned_base_bitrate_bps = | 
|  | proto.has_bitrate_bps() | 
|  | ? std::optional<uint64_t>(ToUnsigned(proto.bitrate_bps())) | 
|  | : std::optional<uint64_t>(); | 
|  | std::vector<std::optional<uint64_t>> bitrate_bps_values = DecodeDeltas( | 
|  | proto.bitrate_bps_deltas(), unsigned_base_bitrate_bps, number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(bitrate_bps_values.size(), number_of_deltas); | 
|  |  | 
|  | // frame_length_ms | 
|  | const std::optional<uint64_t> unsigned_base_frame_length_ms = | 
|  | proto.has_frame_length_ms() | 
|  | ? std::optional<uint64_t>(ToUnsigned(proto.frame_length_ms())) | 
|  | : std::optional<uint64_t>(); | 
|  | std::vector<std::optional<uint64_t>> frame_length_ms_values = | 
|  | DecodeDeltas(proto.frame_length_ms_deltas(), | 
|  | unsigned_base_frame_length_ms, number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(frame_length_ms_values.size(), number_of_deltas); | 
|  |  | 
|  | // uplink_packet_loss_fraction | 
|  | const std::optional<uint64_t> uplink_packet_loss_fraction = | 
|  | proto.has_uplink_packet_loss_fraction() | 
|  | ? std::optional<uint64_t>(proto.uplink_packet_loss_fraction()) | 
|  | : std::optional<uint64_t>(); | 
|  | std::vector<std::optional<uint64_t>> uplink_packet_loss_fraction_values = | 
|  | DecodeDeltas(proto.uplink_packet_loss_fraction_deltas(), | 
|  | uplink_packet_loss_fraction, number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(uplink_packet_loss_fraction_values.size(), | 
|  | number_of_deltas); | 
|  |  | 
|  | // enable_fec | 
|  | const std::optional<uint64_t> enable_fec = | 
|  | proto.has_enable_fec() ? std::optional<uint64_t>(proto.enable_fec()) | 
|  | : std::optional<uint64_t>(); | 
|  | std::vector<std::optional<uint64_t>> enable_fec_values = | 
|  | DecodeDeltas(proto.enable_fec_deltas(), enable_fec, number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(enable_fec_values.size(), number_of_deltas); | 
|  |  | 
|  | // enable_dtx | 
|  | const std::optional<uint64_t> enable_dtx = | 
|  | proto.has_enable_dtx() ? std::optional<uint64_t>(proto.enable_dtx()) | 
|  | : std::optional<uint64_t>(); | 
|  | std::vector<std::optional<uint64_t>> enable_dtx_values = | 
|  | DecodeDeltas(proto.enable_dtx_deltas(), enable_dtx, number_of_deltas); | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(enable_dtx_values.size(), number_of_deltas); | 
|  |  | 
|  | // num_channels | 
|  | // Note: For delta encoding, all num_channel values, including the base, | 
|  | // were shifted down by one, but in the base event, they were not. | 
|  | // We likewise shift the base event down by one, to get the same base as | 
|  | // encoding had, but then shift all of the values (except the base) back up | 
|  | // to their original value. | 
|  | std::optional<uint64_t> shifted_base_num_channels; | 
|  | if (proto.has_num_channels()) { | 
|  | shifted_base_num_channels = | 
|  | std::optional<uint64_t>(proto.num_channels() - 1); | 
|  | } | 
|  | std::vector<std::optional<uint64_t>> num_channels_values = DecodeDeltas( | 
|  | proto.num_channels_deltas(), shifted_base_num_channels, number_of_deltas); | 
|  | for (size_t i = 0; i < num_channels_values.size(); ++i) { | 
|  | if (num_channels_values[i].has_value()) { | 
|  | num_channels_values[i] = num_channels_values[i].value() + 1; | 
|  | } | 
|  | } | 
|  | RTC_PARSE_CHECK_OR_RETURN_EQ(num_channels_values.size(), number_of_deltas); | 
|  |  | 
|  | // Populate events from decoded deltas | 
|  | for (size_t i = 0; i < number_of_deltas; ++i) { | 
|  | RTC_PARSE_CHECK_OR_RETURN(timestamp_ms_values[i].has_value()); | 
|  | int64_t timestamp_ms; | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | ToSigned(timestamp_ms_values[i].value(), ×tamp_ms)); | 
|  |  | 
|  | AudioEncoderRuntimeConfig runtime_config; | 
|  | if (bitrate_bps_values[i].has_value()) { | 
|  | int signed_bitrate_bps; | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | ToSigned(bitrate_bps_values[i].value(), &signed_bitrate_bps)); | 
|  | runtime_config.bitrate_bps = signed_bitrate_bps; | 
|  | } | 
|  | if (frame_length_ms_values[i].has_value()) { | 
|  | int signed_frame_length_ms; | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | ToSigned(frame_length_ms_values[i].value(), &signed_frame_length_ms)); | 
|  | runtime_config.frame_length_ms = signed_frame_length_ms; | 
|  | } | 
|  | if (uplink_packet_loss_fraction_values[i].has_value()) { | 
|  | float uplink_packet_loss_fraction2; | 
|  | RTC_PARSE_CHECK_OR_RETURN(IsValueInRangeForNumericType<uint32_t>( | 
|  | uplink_packet_loss_fraction_values[i].value())); | 
|  | RTC_PARSE_CHECK_OR_RETURN(ParsePacketLossFractionFromProtoFormat( | 
|  | static_cast<uint32_t>(uplink_packet_loss_fraction_values[i].value()), | 
|  | &uplink_packet_loss_fraction2)); | 
|  | runtime_config.uplink_packet_loss_fraction = uplink_packet_loss_fraction2; | 
|  | } | 
|  | if (enable_fec_values[i].has_value()) { | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | IsValueInRangeForNumericType<bool>(enable_fec_values[i].value())); | 
|  | runtime_config.enable_fec = | 
|  | static_cast<bool>(enable_fec_values[i].value()); | 
|  | } | 
|  | if (enable_dtx_values[i].has_value()) { | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | IsValueInRangeForNumericType<bool>(enable_dtx_values[i].value())); | 
|  | runtime_config.enable_dtx = | 
|  | static_cast<bool>(enable_dtx_values[i].value()); | 
|  | } | 
|  | if (num_channels_values[i].has_value()) { | 
|  | RTC_PARSE_CHECK_OR_RETURN( | 
|  | IsValueInRangeForNumericType<size_t>(num_channels_values[i].value())); | 
|  | runtime_config.num_channels = | 
|  | static_cast<size_t>(num_channels_values[i].value()); | 
|  | } | 
|  | audio_network_adaptation_events_.emplace_back( | 
|  | Timestamp::Millis(timestamp_ms), runtime_config); | 
|  | } | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::StoreDtlsTransportState( | 
|  | const rtclog2::DtlsTransportStateEvent& proto) { | 
|  | LoggedDtlsTransportState dtls_state; | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_timestamp_ms()); | 
|  | dtls_state.timestamp = Timestamp::Millis(proto.timestamp_ms()); | 
|  |  | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_dtls_transport_state()); | 
|  | dtls_state.dtls_transport_state = | 
|  | GetRuntimeDtlsTransportState(proto.dtls_transport_state()); | 
|  |  | 
|  | dtls_transport_states_.push_back(dtls_state); | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::StoreDtlsWritableState( | 
|  | const rtclog2::DtlsWritableState& proto) { | 
|  | LoggedDtlsWritableState dtls_writable_state; | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_timestamp_ms()); | 
|  | dtls_writable_state.timestamp = Timestamp::Millis(proto.timestamp_ms()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_writable()); | 
|  | dtls_writable_state.writable = proto.writable(); | 
|  |  | 
|  | dtls_writable_states_.push_back(dtls_writable_state); | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::StoreIceCandidatePairConfig( | 
|  | const rtclog2::IceCandidatePairConfig& proto) { | 
|  | LoggedIceCandidatePairConfig ice_config; | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_timestamp_ms()); | 
|  | ice_config.timestamp = Timestamp::Millis(proto.timestamp_ms()); | 
|  |  | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_config_type()); | 
|  | ice_config.type = GetRuntimeIceCandidatePairConfigType(proto.config_type()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_candidate_pair_id()); | 
|  | ice_config.candidate_pair_id = proto.candidate_pair_id(); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_local_candidate_type()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(GetRuntimeIceCandidateType( | 
|  | proto.local_candidate_type(), ice_config.local_candidate_type)); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_local_relay_protocol()); | 
|  | ice_config.local_relay_protocol = | 
|  | GetRuntimeIceCandidatePairProtocol(proto.local_relay_protocol()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_local_network_type()); | 
|  | ice_config.local_network_type = | 
|  | GetRuntimeIceCandidateNetworkType(proto.local_network_type()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_local_address_family()); | 
|  | ice_config.local_address_family = | 
|  | GetRuntimeIceCandidatePairAddressFamily(proto.local_address_family()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_remote_candidate_type()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(GetRuntimeIceCandidateType( | 
|  | proto.remote_candidate_type(), ice_config.remote_candidate_type)); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_remote_address_family()); | 
|  | ice_config.remote_address_family = | 
|  | GetRuntimeIceCandidatePairAddressFamily(proto.remote_address_family()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_candidate_pair_protocol()); | 
|  | ice_config.candidate_pair_protocol = | 
|  | GetRuntimeIceCandidatePairProtocol(proto.candidate_pair_protocol()); | 
|  |  | 
|  | ice_candidate_pair_configs_.push_back(ice_config); | 
|  |  | 
|  | // TODO(terelius): Should we delta encode this event type? | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::StoreIceCandidateEvent( | 
|  | const rtclog2::IceCandidatePairEvent& proto) { | 
|  | LoggedIceCandidatePairEvent ice_event; | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_timestamp_ms()); | 
|  | ice_event.timestamp = Timestamp::Millis(proto.timestamp_ms()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_event_type()); | 
|  | ice_event.type = GetRuntimeIceCandidatePairEventType(proto.event_type()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_candidate_pair_id()); | 
|  | ice_event.candidate_pair_id = proto.candidate_pair_id(); | 
|  | // TODO(zstein): Make the transaction_id field required once all old versions | 
|  | // of the log (which don't have the field) are obsolete. | 
|  | ice_event.transaction_id = | 
|  | proto.has_transaction_id() ? proto.transaction_id() : 0; | 
|  |  | 
|  | ice_candidate_pair_events_.push_back(ice_event); | 
|  |  | 
|  | // TODO(terelius): Should we delta encode this event type? | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::StoreVideoRecvConfig( | 
|  | const rtclog2::VideoRecvStreamConfig& proto) { | 
|  | LoggedVideoRecvConfig stream; | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_timestamp_ms()); | 
|  | stream.timestamp = Timestamp::Millis(proto.timestamp_ms()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_remote_ssrc()); | 
|  | stream.config.remote_ssrc = proto.remote_ssrc(); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_local_ssrc()); | 
|  | stream.config.local_ssrc = proto.local_ssrc(); | 
|  | if (proto.has_rtx_ssrc()) { | 
|  | stream.config.rtx_ssrc = proto.rtx_ssrc(); | 
|  | } | 
|  | if (proto.has_header_extensions()) { | 
|  | stream.config.rtp_extensions = | 
|  | GetRuntimeRtpHeaderExtensionConfig(proto.header_extensions()); | 
|  | } | 
|  | video_recv_configs_.push_back(stream); | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::StoreVideoSendConfig( | 
|  | const rtclog2::VideoSendStreamConfig& proto) { | 
|  | LoggedVideoSendConfig stream; | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_timestamp_ms()); | 
|  | stream.timestamp = Timestamp::Millis(proto.timestamp_ms()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_ssrc()); | 
|  | stream.config.local_ssrc = proto.ssrc(); | 
|  | if (proto.has_rtx_ssrc()) { | 
|  | stream.config.rtx_ssrc = proto.rtx_ssrc(); | 
|  | } | 
|  | if (proto.has_header_extensions()) { | 
|  | stream.config.rtp_extensions = | 
|  | GetRuntimeRtpHeaderExtensionConfig(proto.header_extensions()); | 
|  | } | 
|  | video_send_configs_.push_back(stream); | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::StoreAudioRecvConfig( | 
|  | const rtclog2::AudioRecvStreamConfig& proto) { | 
|  | LoggedAudioRecvConfig stream; | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_timestamp_ms()); | 
|  | stream.timestamp = Timestamp::Millis(proto.timestamp_ms()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_remote_ssrc()); | 
|  | stream.config.remote_ssrc = proto.remote_ssrc(); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_local_ssrc()); | 
|  | stream.config.local_ssrc = proto.local_ssrc(); | 
|  | if (proto.has_header_extensions()) { | 
|  | stream.config.rtp_extensions = | 
|  | GetRuntimeRtpHeaderExtensionConfig(proto.header_extensions()); | 
|  | } | 
|  | audio_recv_configs_.push_back(stream); | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | ParsedRtcEventLog::ParseStatus ParsedRtcEventLog::StoreAudioSendConfig( | 
|  | const rtclog2::AudioSendStreamConfig& proto) { | 
|  | LoggedAudioSendConfig stream; | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_timestamp_ms()); | 
|  | stream.timestamp = Timestamp::Millis(proto.timestamp_ms()); | 
|  | RTC_PARSE_CHECK_OR_RETURN(proto.has_ssrc()); | 
|  | stream.config.local_ssrc = proto.ssrc(); | 
|  | if (proto.has_header_extensions()) { | 
|  | stream.config.rtp_extensions = | 
|  | GetRuntimeRtpHeaderExtensionConfig(proto.header_extensions()); | 
|  | } | 
|  | audio_send_configs_.push_back(stream); | 
|  | return ParseStatus::Success(); | 
|  | } | 
|  |  | 
|  | }  // namespace webrtc |