blob: 589850218b09dcb9b989938ad6084e4f3a627d0f [file] [log] [blame]
/*
* 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(), &timestamp_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(), &timestamp_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.<