| /* |
| * 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 <stdint.h> |
| #include <string.h> |
| |
| #include <algorithm> |
| #include <limits> |
| #include <map> |
| #include <utility> |
| |
| #include "absl/memory/memory.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/types/optional.h" |
| #include "api/network_state_predictor.h" |
| #include "api/rtc_event_log/rtc_event_log.h" |
| #include "api/rtp_headers.h" |
| #include "api/rtp_parameters.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/rtc_event_processor.h" |
| #include "modules/audio_coding/audio_network_adaptor/include/audio_network_adaptor.h" |
| #include "modules/rtp_rtcp/include/rtp_cvo.h" |
| #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" |
| #include "modules/rtp_rtcp/source/byte_io.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 int64_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; |
| header.markerBit = rtc::checked_cast<bool>(proto.marker()); |
| header.payloadType = rtc::checked_cast<uint8_t>(proto.payload_type()); |
| header.sequenceNumber = |
| rtc::checked_cast<uint16_t>(proto.sequence_number()); |
| header.timestamp = rtc::checked_cast<uint32_t>(proto.rtp_timestamp()); |
| header.ssrc = rtc::checked_cast<uint32_t>(proto.ssrc()); |
| header.numCSRCs = 0; // TODO(terelius): Implement CSRC. |
| header.paddingLength = rtc::checked_cast<size_t>(proto.padding_size()); |
| header.headerLength = rtc::checked_cast<size_t>(proto.header_size()); |
| // TODO(terelius): Should we implement payload_type_frequency? |
| if (proto.has_transport_sequence_number()) { |
| header.extension.hasTransportSequenceNumber = true; |
| header.extension.transportSequenceNumber = |
| rtc::checked_cast<uint16_t>(proto.transport_sequence_number()); |
| } |
| if (proto.has_transmission_time_offset()) { |
| header.extension.hasTransmissionTimeOffset = true; |
| header.extension.transmissionTimeOffset = |
| rtc::checked_cast<int32_t>(proto.transmission_time_offset()); |
| } |
| if (proto.has_absolute_send_time()) { |
| header.extension.hasAbsoluteSendTime = true; |
| header.extension.absoluteSendTime = |
| rtc::checked_cast<uint32_t>(proto.absolute_send_time()); |
| } |
| if (proto.has_video_rotation()) { |
| header.extension.hasVideoRotation = true; |
| header.extension.videoRotation = ConvertCVOByteToVideoRotation( |
| rtc::checked_cast<uint8_t>(proto.video_rotation())); |
| } |
| if (proto.has_audio_level()) { |
| RTC_PARSE_CHECK_OR_RETURN(proto.has_voice_activity()); |
| header.extension.hasAudioLevel = true; |
| header.extension.voiceActivity = |
| rtc::checked_cast<bool>(proto.voice_activity()); |
| const uint8_t audio_level = |
| rtc::checked_cast<uint8_t>(proto.audio_level()); |
| RTC_PARSE_CHECK_OR_RETURN_LE(audio_level, 0x7Fu); |
| header.extension.audioLevel = 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<absl::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<absl::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<absl::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<absl::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<absl::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<absl::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<absl::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<absl::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<absl::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<absl::optional<uint64_t>> transport_sequence_number_values; |
| { |
| const absl::optional<uint64_t> base_transport_sequence_number = |
| proto.has_transport_sequence_number() |
| ? proto.transport_sequence_number() |
| : absl::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<absl::optional<uint64_t>> transmission_time_offset_values; |
| { |
| const absl::optional<uint64_t> unsigned_base_transmission_time_offset = |
| proto.has_transmission_time_offset() |
| ? ToUnsigned(proto.transmission_time_offset()) |
| : absl::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<absl::optional<uint64_t>> absolute_send_time_values; |
| { |
| const absl::optional<uint64_t> base_absolute_send_time = |
| proto.has_absolute_send_time() ? proto.absolute_send_time() |
| : absl::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<absl::optional<uint64_t>> video_rotation_values; |
| { |
| const absl::optional<uint64_t> base_video_rotation = |
| proto.has_video_rotation() ? proto.video_rotation() |
| : absl::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<absl::optional<uint64_t>> audio_level_values; |
| { |
| const absl::optional<uint64_t> base_audio_level = |
| proto.has_audio_level() ? proto.audio_level() |
| : absl::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<absl::optional<uint64_t>> voice_activity_values; |
| { |
| const absl::optional<uint64_t> base_voice_activity = |
| proto.has_voice_activity() ? proto.voice_activity() |
| : absl::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; |
| header.markerBit = rtc::checked_cast<bool>(*marker_values[i]); |
| header.payloadType = rtc::checked_cast<uint8_t>(*payload_type_values[i]); |
| header.sequenceNumber = |
| rtc::checked_cast<uint16_t>(*sequence_number_values[i]); |
| header.timestamp = rtc::checked_cast<uint32_t>(*rtp_timestamp_values[i]); |
| header.ssrc = rtc::checked_cast<uint32_t>(*ssrc_values[i]); |
| header.numCSRCs = 0; // TODO(terelius): Implement CSRC. |
| header.paddingLength = rtc::checked_cast<size_t>(*padding_size_values[i]); |
| header.headerLength = rtc::checked_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; |
| header.extension.transportSequenceNumber = rtc::checked_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; |
| header.extension.absoluteSendTime = |
| rtc::checked_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; |
| header.extension.videoRotation = ConvertCVOByteToVideoRotation( |
| rtc::checked_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()); |
| header.extension.hasAudioLevel = true; |
| header.extension.voiceActivity = |
| rtc::checked_cast<bool>(voice_activity_values[i].value()); |
| const uint8_t audio_level = |
| rtc::checked_cast<uint8_t>(audio_level_values[i].value()); |
| RTC_PARSE_CHECK_OR_RETURN_LE(audio_level, 0x7Fu); |
| header.extension.audioLevel = 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<absl::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<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::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 |
| webrtc::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; |
| |
| webrtc::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. |
| long signed_filesize = file.FileSize(); // NOLINT(runtime/int) |
| RTC_PARSE_CHECK_OR_RETURN_GE(signed_filesize, 0); |
| RTC_PARSE_CHECK_OR_RETURN_LE(signed_filesize, kMaxLogSize); |
| size_t filesize = rtc::checked_cast<size_t>(signed_filesize); |
| |
| // Read file into memory. |
| std::string buffer(filesize, '\0'); |
| size_t bytes_read = file.Read(&buffer[0], buffer.size()); |
| if (bytes_read != filesize) { |
| RTC_LOG(LS_WARNING) << "Failed to read file " << filename; |
| RTC_PARSE_CHECK_OR_RETURN_EQ(bytes_read, filesize); |
| } |
| |
| 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_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_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(rtc::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()); |
| LoggedAudioNetworkAdaptationEvent ana_event = status_or_value.value(); |
| audio_network_adaptation_events_.push_back(ana_event); |
| 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<int64_t, size_t> indices; |
| uint16_t current_overhead = kDefaultOverhead; |
| Timestamp last_log_time = Timestamp::Zero(); |
| RtpSequenceNumberUnwrapper seq_num_unwrapper; |
| |
| 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)) { |
| seq_num_unwrapper.Reset(); |
| 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 = |
| seq_num_unwrapper.Unwrap(logged.transport_seq_no); |
| if (indices.find(unwrapped_seq_num) != indices.end()) { |
| auto prev = packets[indices[unwrapped_seq_num]]; |
| 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; |
| } |
| indices[unwrapped_seq_num] = packets.size(); |
| } |
| packets.push_back(logged); |
| }; |
| |
| Timestamp feedback_base_time = Timestamp::MinusInfinity(); |
| Timestamp last_feedback_base_time = Timestamp::MinusInfinity(); |
| |
| auto 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 (!last_feedback_base_time.IsFinite()) { |
| feedback_base_time = log_feedback_time; |
| } else { |
| feedback_base_time += feedback.GetBaseDelta(last_feedback_base_time); |
| } |
| 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 = seq_num_unwrapper.Unwrap(sequence_number); |
| auto it = indices.find(unwrapped_seq_num); |
| if (it == 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 = 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; |
| } |
| } |
| }; |
| |
| 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_, feedback_handler, |
| PacketDirection::kIncomingPacket); |
| } else { |
| process.AddEvents(outgoing_transport_feedback_, 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{check.candidate_pair_id, |
| Timestamp::Millis(check.log_time_ms()), |
| check_map[check.type]}); |
| }; |
| auto handle_config = [&](const LoggedIceCandidatePairConfig& conf) { |
| log_events.push_back(LoggedIceEvent{conf.candidate_pair_id, |
| Timestamp::Millis(conf.log_time_ms()), |
| 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; |
| } |
| |
| const 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()); |
| |
| absl::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()); |
| } |
| |
| absl::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.<
|