diff --git a/webrtc/BUILD.gn b/webrtc/BUILD.gn index b31a07c83d..e1aeb45720 100644 --- a/webrtc/BUILD.gn +++ b/webrtc/BUILD.gn @@ -282,6 +282,29 @@ source_set("rtc_event_log") { } } +if (rtc_enable_protobuf) { + source_set("rtc_event_log_parser") { + sources = [ + "call/rtc_event_log_parser.cc", + "call/rtc_event_log_parser.h", + ] + + configs += [ ":common_config" ] + public_configs = [ ":common_inherited_config" ] + + deps = [ + ":rtc_event_log_proto", + ":webrtc_common", + ] + + if (is_clang && !is_nacl) { + # Suppress warnings from Chrome's Clang plugins. + # See http://code.google.com/p/webrtc/issues/detail?id=163 for details. + configs -= [ "//build/config/clang:find_bad_constructs" ] + } + } +} + if (use_libfuzzer || use_drfuzz) { # This target is only here for gn to discover fuzzer build targets under # webrtc/test/fuzzers/. diff --git a/webrtc/call/rtc_event_log2rtp_dump.cc b/webrtc/call/rtc_event_log2rtp_dump.cc index ef0be9a1b7..5733cfa31d 100644 --- a/webrtc/call/rtc_event_log2rtp_dump.cc +++ b/webrtc/call/rtc_event_log2rtp_dump.cc @@ -15,17 +15,12 @@ #include "gflags/gflags.h" #include "webrtc/base/checks.h" +#include "webrtc/call.h" #include "webrtc/call/rtc_event_log.h" +#include "webrtc/call/rtc_event_log_parser.h" #include "webrtc/modules/rtp_rtcp/source/byte_io.h" #include "webrtc/test/rtp_file_writer.h" -// Files generated at build-time by the protobuf compiler. -#ifdef WEBRTC_ANDROID_PLATFORM_BUILD -#include "external/webrtc/webrtc/call/rtc_event_log.pb.h" -#else -#include "webrtc/call/rtc_event_log.pb.h" -#endif - namespace { DEFINE_bool(noaudio, @@ -94,8 +89,8 @@ int main(int argc, char* argv[]) { RTC_CHECK(ParseSsrc(FLAGS_ssrc, &ssrc_filter)) << "Flag verification has failed."; - webrtc::rtclog::EventStream event_stream; - if (!webrtc::RtcEventLog::ParseRtcEventLog(input_file, &event_stream)) { + webrtc::ParsedRtcEventLog parsed_stream; + if (!parsed_stream.ParseFile(input_file)) { std::cerr << "Error while parsing input file: " << input_file << std::endl; return -1; } @@ -110,94 +105,78 @@ int main(int argc, char* argv[]) { return -1; } - std::cout << "Found " << event_stream.stream_size() + std::cout << "Found " << parsed_stream.GetNumberOfEvents() << " events in the input file." << std::endl; int rtp_counter = 0, rtcp_counter = 0; bool header_only = false; - // TODO(ivoc): This can be refactored once the packet interpretation - // functions are finished. - for (int i = 0; i < event_stream.stream_size(); i++) { - const webrtc::rtclog::Event& event = event_stream.stream(i); - if (!FLAGS_nortp && event.has_type() && event.type() == event.RTP_EVENT) { - if (event.has_timestamp_us() && event.has_rtp_packet() && - event.rtp_packet().has_header() && - event.rtp_packet().header().size() >= 12 && - event.rtp_packet().has_packet_length() && - event.rtp_packet().has_type()) { - const webrtc::rtclog::RtpPacket& rtp_packet = event.rtp_packet(); - if (FLAGS_noaudio && rtp_packet.type() == webrtc::rtclog::AUDIO) - continue; - if (FLAGS_novideo && rtp_packet.type() == webrtc::rtclog::VIDEO) - continue; - if (FLAGS_nodata && rtp_packet.type() == webrtc::rtclog::DATA) - continue; - if (!FLAGS_ssrc.empty()) { - const uint32_t packet_ssrc = - webrtc::ByteReader::ReadBigEndian( - reinterpret_cast(rtp_packet.header().data() + - 8)); - if (packet_ssrc != ssrc_filter) - continue; - } + for (size_t i = 0; i < parsed_stream.GetNumberOfEvents(); i++) { + // The parsed_stream will assert if the protobuf event is missing + // some required fields and we attempt to access them. We could consider + // a softer failure option, but it does not seem useful to generate + // RTP dumps based on broken event logs. + if (!FLAGS_nortp && + parsed_stream.GetEventType(i) == webrtc::ParsedRtcEventLog::RTP_EVENT) { + webrtc::test::RtpPacket packet; + webrtc::PacketDirection direction; + webrtc::MediaType media_type; + parsed_stream.GetRtpHeader(i, &direction, &media_type, packet.data, + &packet.length, &packet.original_length); + if (packet.original_length > packet.length) + header_only = true; + packet.time_ms = parsed_stream.GetTimestamp(i) / 1000; - webrtc::test::RtpPacket packet; - packet.length = rtp_packet.header().size(); - if (packet.length > packet.kMaxPacketBufferSize) { - std::cout << "Skipping packet with size " << packet.length - << ", the maximum supported size is " - << packet.kMaxPacketBufferSize << std::endl; + // TODO(terelius): Maybe add a flag to dump outgoing traffic instead? + if (direction == webrtc::kOutgoingPacket) + continue; + if (FLAGS_noaudio && media_type == webrtc::MediaType::AUDIO) + continue; + if (FLAGS_novideo && media_type == webrtc::MediaType::VIDEO) + continue; + if (FLAGS_nodata && media_type == webrtc::MediaType::DATA) + continue; + if (!FLAGS_ssrc.empty()) { + const uint32_t packet_ssrc = + webrtc::ByteReader::ReadBigEndian( + reinterpret_cast(packet.data + 8)); + if (packet_ssrc != ssrc_filter) continue; - } - packet.original_length = rtp_packet.packet_length(); - if (packet.original_length > packet.length) - header_only = true; - packet.time_ms = event.timestamp_us() / 1000; - memcpy(packet.data, rtp_packet.header().data(), packet.length); - rtp_writer->WritePacket(&packet); - rtp_counter++; - } else { - std::cout << "Skipping malformed event." << std::endl; } + + rtp_writer->WritePacket(&packet); + rtp_counter++; } - if (!FLAGS_nortcp && event.has_type() && event.type() == event.RTCP_EVENT) { - if (event.has_timestamp_us() && event.has_rtcp_packet() && - event.rtcp_packet().has_type() && - event.rtcp_packet().has_packet_data() && - event.rtcp_packet().packet_data().size() > 0) { - const webrtc::rtclog::RtcpPacket& rtcp_packet = event.rtcp_packet(); - if (FLAGS_noaudio && rtcp_packet.type() == webrtc::rtclog::AUDIO) - continue; - if (FLAGS_novideo && rtcp_packet.type() == webrtc::rtclog::VIDEO) - continue; - if (FLAGS_nodata && rtcp_packet.type() == webrtc::rtclog::DATA) - continue; - if (!FLAGS_ssrc.empty()) { - const uint32_t packet_ssrc = - webrtc::ByteReader::ReadBigEndian( - reinterpret_cast( - rtcp_packet.packet_data().data() + 4)); - if (packet_ssrc != ssrc_filter) - continue; - } + if (!FLAGS_nortcp && + parsed_stream.GetEventType(i) == + webrtc::ParsedRtcEventLog::RTCP_EVENT) { + webrtc::test::RtpPacket packet; + webrtc::PacketDirection direction; + webrtc::MediaType media_type; + parsed_stream.GetRtcpPacket(i, &direction, &media_type, packet.data, + &packet.length); + // For RTCP packets the original_length should be set to 0 in the + // RTPdump format. + packet.original_length = 0; + packet.time_ms = parsed_stream.GetTimestamp(i) / 1000; - webrtc::test::RtpPacket packet; - packet.length = rtcp_packet.packet_data().size(); - if (packet.length > packet.kMaxPacketBufferSize) { - std::cout << "Skipping packet with size " << packet.length - << ", the maximum supported size is " - << packet.kMaxPacketBufferSize << std::endl; + // TODO(terelius): Maybe add a flag to dump outgoing traffic instead? + if (direction == webrtc::kOutgoingPacket) + continue; + if (FLAGS_noaudio && media_type == webrtc::MediaType::AUDIO) + continue; + if (FLAGS_novideo && media_type == webrtc::MediaType::VIDEO) + continue; + if (FLAGS_nodata && media_type == webrtc::MediaType::DATA) + continue; + if (!FLAGS_ssrc.empty()) { + const uint32_t packet_ssrc = + webrtc::ByteReader::ReadBigEndian( + reinterpret_cast(packet.data + 4)); + if (packet_ssrc != ssrc_filter) continue; - } - // For RTCP packets the original_length should be set to 0 in the - // RTPdump format. - packet.original_length = 0; - packet.time_ms = event.timestamp_us() / 1000; - memcpy(packet.data, rtcp_packet.packet_data().data(), packet.length); - rtp_writer->WritePacket(&packet); - rtcp_counter++; - } else { - std::cout << "Skipping malformed event." << std::endl; } + + rtp_writer->WritePacket(&packet); + rtcp_counter++; } } std::cout << "Wrote " << rtp_counter << (header_only ? " header-only" : "") diff --git a/webrtc/call/rtc_event_log_parser.cc b/webrtc/call/rtc_event_log_parser.cc new file mode 100644 index 0000000000..244f17c491 --- /dev/null +++ b/webrtc/call/rtc_event_log_parser.cc @@ -0,0 +1,395 @@ +/* + * 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 "webrtc/call/rtc_event_log_parser.h" + +#include + +#include + +#include "webrtc/base/checks.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/call.h" +#include "webrtc/call/rtc_event_log.h" +#include "webrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "webrtc/system_wrappers/include/file_wrapper.h" + +namespace webrtc { + +namespace { +MediaType GetRuntimeMediaType(rtclog::MediaType media_type) { + switch (media_type) { + case rtclog::MediaType::ANY: + return MediaType::ANY; + case rtclog::MediaType::AUDIO: + return MediaType::AUDIO; + case rtclog::MediaType::VIDEO: + return MediaType::VIDEO; + case rtclog::MediaType::DATA: + return MediaType::DATA; + } + RTC_NOTREACHED(); + return MediaType::ANY; +} + +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_NOTREACHED(); + return RtcpMode::kOff; +} + +ParsedRtcEventLog::EventType GetRuntimeEventType( + rtclog::Event::EventType event_type) { + switch (event_type) { + case rtclog::Event::UNKNOWN_EVENT: + return ParsedRtcEventLog::EventType::UNKNOWN_EVENT; + case rtclog::Event::LOG_START: + return ParsedRtcEventLog::EventType::LOG_START; + case rtclog::Event::LOG_END: + return ParsedRtcEventLog::EventType::LOG_END; + case rtclog::Event::RTP_EVENT: + return ParsedRtcEventLog::EventType::RTP_EVENT; + case rtclog::Event::RTCP_EVENT: + return ParsedRtcEventLog::EventType::RTCP_EVENT; + case rtclog::Event::AUDIO_PLAYOUT_EVENT: + return ParsedRtcEventLog::EventType::AUDIO_PLAYOUT_EVENT; + case rtclog::Event::BWE_PACKET_LOSS_EVENT: + return ParsedRtcEventLog::EventType::BWE_PACKET_LOSS_EVENT; + case rtclog::Event::BWE_PACKET_DELAY_EVENT: + return ParsedRtcEventLog::EventType::BWE_PACKET_DELAY_EVENT; + case rtclog::Event::VIDEO_RECEIVER_CONFIG_EVENT: + return ParsedRtcEventLog::EventType::VIDEO_RECEIVER_CONFIG_EVENT; + case rtclog::Event::VIDEO_SENDER_CONFIG_EVENT: + return ParsedRtcEventLog::EventType::VIDEO_SENDER_CONFIG_EVENT; + case rtclog::Event::AUDIO_RECEIVER_CONFIG_EVENT: + return ParsedRtcEventLog::EventType::AUDIO_RECEIVER_CONFIG_EVENT; + case rtclog::Event::AUDIO_SENDER_CONFIG_EVENT: + return ParsedRtcEventLog::EventType::AUDIO_SENDER_CONFIG_EVENT; + } + RTC_NOTREACHED(); + return ParsedRtcEventLog::EventType::UNKNOWN_EVENT; +} + +bool ParseVarInt(std::FILE* file, uint64_t* varint, size_t* bytes_read) { + uint8_t byte; + *varint = 0; + for (*bytes_read = 0; *bytes_read < 10 && fread(&byte, 1, 1, file) == 1; + ++(*bytes_read)) { + // The most significant bit of each byte is 0 if it is the last byte in + // the varint and 1 otherwise. Thus, we take the 7 least significant bits + // of each byte and shift them 7 bits for each byte read previously to get + // the (unsigned) integer. + *varint |= static_cast(byte & 0x7F) << (7 * *bytes_read); + if ((byte & 0x80) == 0) { + return true; + } + } + return false; +} + +} // namespace + +bool ParsedRtcEventLog::ParseFile(const std::string& filename) { + stream_.clear(); + const size_t kMaxEventSize = (1u << 16) - 1; + char tmp_buffer[kMaxEventSize]; + + std::FILE* file = fopen(filename.c_str(), "rb"); + if (!file) { + LOG(LS_WARNING) << "Could not open file for reading."; + return false; + } + + while (1) { + // Peek at the next message tag. The tag number is defined as + // (fieldnumber << 3) | wire_type. In our case, the field number is + // supposed to be 1 and the wire type for an length-delimited field is 2. + const uint64_t kExpectedTag = (1 << 3) | 2; + uint64_t tag; + size_t bytes_read; + if (!ParseVarInt(file, &tag, &bytes_read) || tag != kExpectedTag) { + fclose(file); + if (bytes_read == 0) { + return true; // Reached end of file. + } + LOG(LS_WARNING) << "Missing field tag from beginning of protobuf event."; + return false; + } + + // Peek at the length field. + uint64_t message_length; + if (!ParseVarInt(file, &message_length, &bytes_read)) { + LOG(LS_WARNING) << "Missing message length after protobuf field tag."; + fclose(file); + return false; + } else if (message_length > kMaxEventSize) { + LOG(LS_WARNING) << "Protobuf message length is too large."; + fclose(file); + return false; + } + + if (fread(tmp_buffer, 1, message_length, file) != message_length) { + LOG(LS_WARNING) << "Failed to read protobuf message from file."; + fclose(file); + return false; + } + + rtclog::Event event; + if (!event.ParseFromArray(tmp_buffer, message_length)) { + LOG(LS_WARNING) << "Failed to parse protobuf message."; + fclose(file); + return false; + } + stream_.push_back(event); + } +} + +size_t ParsedRtcEventLog::GetNumberOfEvents() const { + return stream_.size(); +} + +int64_t ParsedRtcEventLog::GetTimestamp(size_t index) const { + RTC_CHECK_LT(index, GetNumberOfEvents()); + const rtclog::Event& event = stream_[index]; + RTC_CHECK(event.has_timestamp_us()); + return event.timestamp_us(); +} + +ParsedRtcEventLog::EventType ParsedRtcEventLog::GetEventType( + size_t index) const { + RTC_CHECK_LT(index, GetNumberOfEvents()); + const rtclog::Event& event = stream_[index]; + RTC_CHECK(event.has_type()); + return GetRuntimeEventType(event.type()); +} + +// The header must have space for at least IP_PACKET_SIZE bytes. +void ParsedRtcEventLog::GetRtpHeader(size_t index, + PacketDirection* incoming, + MediaType* media_type, + uint8_t* header, + size_t* header_length, + size_t* total_length) const { + RTC_CHECK_LT(index, GetNumberOfEvents()); + const rtclog::Event& event = stream_[index]; + RTC_CHECK(event.has_type()); + RTC_CHECK_EQ(event.type(), rtclog::Event::RTP_EVENT); + RTC_CHECK(event.has_rtp_packet()); + const rtclog::RtpPacket& rtp_packet = event.rtp_packet(); + // Get direction of packet. + RTC_CHECK(rtp_packet.has_incoming()); + if (incoming != nullptr) { + *incoming = rtp_packet.incoming() ? kIncomingPacket : kOutgoingPacket; + } + // Get media type. + RTC_CHECK(rtp_packet.has_type()); + if (media_type != nullptr) { + *media_type = GetRuntimeMediaType(rtp_packet.type()); + } + // Get packet length. + RTC_CHECK(rtp_packet.has_packet_length()); + if (total_length != nullptr) { + *total_length = rtp_packet.packet_length(); + } + // Get header length. + RTC_CHECK(rtp_packet.has_header()); + if (header_length != nullptr) { + *header_length = rtp_packet.header().size(); + } + // Get header contents. + if (header != nullptr) { + const size_t kMinRtpHeaderSize = 12; + RTC_CHECK_GE(rtp_packet.header().size(), kMinRtpHeaderSize); + RTC_CHECK_LE(rtp_packet.header().size(), + static_cast(IP_PACKET_SIZE)); + memcpy(header, rtp_packet.header().data(), rtp_packet.header().size()); + } +} + +// The packet must have space for at least IP_PACKET_SIZE bytes. +void ParsedRtcEventLog::GetRtcpPacket(size_t index, + PacketDirection* incoming, + MediaType* media_type, + uint8_t* packet, + size_t* length) const { + RTC_CHECK_LT(index, GetNumberOfEvents()); + const rtclog::Event& event = stream_[index]; + RTC_CHECK(event.has_type()); + RTC_CHECK_EQ(event.type(), rtclog::Event::RTCP_EVENT); + RTC_CHECK(event.has_rtcp_packet()); + const rtclog::RtcpPacket& rtcp_packet = event.rtcp_packet(); + // Get direction of packet. + RTC_CHECK(rtcp_packet.has_incoming()); + if (incoming != nullptr) { + *incoming = rtcp_packet.incoming() ? kIncomingPacket : kOutgoingPacket; + } + // Get media type. + RTC_CHECK(rtcp_packet.has_type()); + if (media_type != nullptr) { + *media_type = GetRuntimeMediaType(rtcp_packet.type()); + } + // Get packet length. + RTC_CHECK(rtcp_packet.has_packet_data()); + if (length != nullptr) { + *length = rtcp_packet.packet_data().size(); + } + // Get packet contents. + if (packet != nullptr) { + RTC_CHECK_LE(rtcp_packet.packet_data().size(), + static_cast(IP_PACKET_SIZE)); + memcpy(packet, rtcp_packet.packet_data().data(), + rtcp_packet.packet_data().size()); + } +} + +void ParsedRtcEventLog::GetVideoReceiveConfig( + size_t index, + VideoReceiveStream::Config* config) const { + RTC_CHECK_LT(index, GetNumberOfEvents()); + const rtclog::Event& event = stream_[index]; + RTC_CHECK(config != nullptr); + RTC_CHECK(event.has_type()); + RTC_CHECK_EQ(event.type(), rtclog::Event::VIDEO_RECEIVER_CONFIG_EVENT); + RTC_CHECK(event.has_video_receiver_config()); + const rtclog::VideoReceiveConfig& receiver_config = + event.video_receiver_config(); + // Get SSRCs. + RTC_CHECK(receiver_config.has_remote_ssrc()); + config->rtp.remote_ssrc = receiver_config.remote_ssrc(); + RTC_CHECK(receiver_config.has_local_ssrc()); + config->rtp.local_ssrc = receiver_config.local_ssrc(); + // Get RTCP settings. + RTC_CHECK(receiver_config.has_rtcp_mode()); + config->rtp.rtcp_mode = GetRuntimeRtcpMode(receiver_config.rtcp_mode()); + RTC_CHECK(receiver_config.has_remb()); + config->rtp.remb = receiver_config.remb(); + // Get RTX map. + config->rtp.rtx.clear(); + for (int i = 0; i < receiver_config.rtx_map_size(); i++) { + const rtclog::RtxMap& map = receiver_config.rtx_map(i); + RTC_CHECK(map.has_payload_type()); + RTC_CHECK(map.has_config()); + RTC_CHECK(map.config().has_rtx_ssrc()); + RTC_CHECK(map.config().has_rtx_payload_type()); + webrtc::VideoReceiveStream::Config::Rtp::Rtx rtx_pair; + rtx_pair.ssrc = map.config().rtx_ssrc(); + rtx_pair.payload_type = map.config().rtx_payload_type(); + config->rtp.rtx.insert(std::make_pair(map.payload_type(), rtx_pair)); + } + // Get header extensions. + config->rtp.extensions.clear(); + for (int i = 0; i < receiver_config.header_extensions_size(); i++) { + RTC_CHECK(receiver_config.header_extensions(i).has_name()); + RTC_CHECK(receiver_config.header_extensions(i).has_id()); + const std::string& name = receiver_config.header_extensions(i).name(); + int id = receiver_config.header_extensions(i).id(); + config->rtp.extensions.push_back(RtpExtension(name, id)); + } + // Get decoders. + config->decoders.clear(); + for (int i = 0; i < receiver_config.decoders_size(); i++) { + RTC_CHECK(receiver_config.decoders(i).has_name()); + RTC_CHECK(receiver_config.decoders(i).has_payload_type()); + VideoReceiveStream::Decoder decoder; + decoder.payload_name = receiver_config.decoders(i).name(); + decoder.payload_type = receiver_config.decoders(i).payload_type(); + config->decoders.push_back(decoder); + } +} + +void ParsedRtcEventLog::GetVideoSendConfig( + size_t index, + VideoSendStream::Config* config) const { + RTC_CHECK_LT(index, GetNumberOfEvents()); + const rtclog::Event& event = stream_[index]; + RTC_CHECK(config != nullptr); + RTC_CHECK(event.has_type()); + RTC_CHECK_EQ(event.type(), rtclog::Event::VIDEO_SENDER_CONFIG_EVENT); + RTC_CHECK(event.has_video_sender_config()); + const rtclog::VideoSendConfig& sender_config = event.video_sender_config(); + // Get SSRCs. + config->rtp.ssrcs.clear(); + for (int i = 0; i < sender_config.ssrcs_size(); i++) { + config->rtp.ssrcs.push_back(sender_config.ssrcs(i)); + } + // Get header extensions. + config->rtp.extensions.clear(); + for (int i = 0; i < sender_config.header_extensions_size(); i++) { + RTC_CHECK(sender_config.header_extensions(i).has_name()); + RTC_CHECK(sender_config.header_extensions(i).has_id()); + const std::string& name = sender_config.header_extensions(i).name(); + int id = sender_config.header_extensions(i).id(); + config->rtp.extensions.push_back(RtpExtension(name, id)); + } + // Get RTX settings. + config->rtp.rtx.ssrcs.clear(); + for (int i = 0; i < sender_config.rtx_ssrcs_size(); i++) { + config->rtp.rtx.ssrcs.push_back(sender_config.rtx_ssrcs(i)); + } + if (sender_config.rtx_ssrcs_size() > 0) { + RTC_CHECK(sender_config.has_rtx_payload_type()); + config->rtp.rtx.payload_type = sender_config.rtx_payload_type(); + } else { + // Reset RTX payload type default value if no RTX SSRCs are used. + config->rtp.rtx.payload_type = -1; + } + // Get encoder. + RTC_CHECK(sender_config.has_encoder()); + RTC_CHECK(sender_config.encoder().has_name()); + RTC_CHECK(sender_config.encoder().has_payload_type()); + config->encoder_settings.payload_name = sender_config.encoder().name(); + config->encoder_settings.payload_type = + sender_config.encoder().payload_type(); +} + +void ParsedRtcEventLog::GetAudioPlayout(size_t index, uint32_t* ssrc) const { + RTC_CHECK_LT(index, GetNumberOfEvents()); + const rtclog::Event& event = stream_[index]; + RTC_CHECK(event.has_type()); + RTC_CHECK_EQ(event.type(), rtclog::Event::AUDIO_PLAYOUT_EVENT); + RTC_CHECK(event.has_audio_playout_event()); + const rtclog::AudioPlayoutEvent& loss_event = event.audio_playout_event(); + RTC_CHECK(loss_event.has_local_ssrc()); + if (ssrc != nullptr) { + *ssrc = loss_event.local_ssrc(); + } +} + +void ParsedRtcEventLog::GetBwePacketLossEvent(size_t index, + int32_t* bitrate, + uint8_t* fraction_loss, + int32_t* total_packets) const { + RTC_CHECK_LT(index, GetNumberOfEvents()); + const rtclog::Event& event = stream_[index]; + RTC_CHECK(event.has_type()); + RTC_CHECK_EQ(event.type(), rtclog::Event::BWE_PACKET_LOSS_EVENT); + RTC_CHECK(event.has_bwe_packet_loss_event()); + const rtclog::BwePacketLossEvent& loss_event = event.bwe_packet_loss_event(); + RTC_CHECK(loss_event.has_bitrate()); + if (bitrate != nullptr) { + *bitrate = loss_event.bitrate(); + } + RTC_CHECK(loss_event.has_fraction_loss()); + if (fraction_loss != nullptr) { + *fraction_loss = loss_event.fraction_loss(); + } + RTC_CHECK(loss_event.has_total_packets()); + if (total_packets != nullptr) { + *total_packets = loss_event.total_packets(); + } +} + +} // namespace webrtc diff --git a/webrtc/call/rtc_event_log_parser.h b/webrtc/call/rtc_event_log_parser.h new file mode 100644 index 0000000000..acdfa77da1 --- /dev/null +++ b/webrtc/call/rtc_event_log_parser.h @@ -0,0 +1,114 @@ +/* + * 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. + */ +#ifndef WEBRTC_CALL_RTC_EVENT_LOG_PARSER_H_ +#define WEBRTC_CALL_RTC_EVENT_LOG_PARSER_H_ + +#include +#include + +#include "webrtc/call/rtc_event_log.h" +#include "webrtc/video_receive_stream.h" +#include "webrtc/video_send_stream.h" + +// Files generated at build-time by the protobuf compiler. +#ifdef WEBRTC_ANDROID_PLATFORM_BUILD +#include "external/webrtc/webrtc/call/rtc_event_log.pb.h" +#else +#include "webrtc/call/rtc_event_log.pb.h" +#endif + +namespace webrtc { + +enum class MediaType; + +class ParsedRtcEventLog { + friend class RtcEventLogTestHelper; + + public: + enum EventType { + UNKNOWN_EVENT = 0, + LOG_START = 1, + LOG_END = 2, + RTP_EVENT = 3, + RTCP_EVENT = 4, + AUDIO_PLAYOUT_EVENT = 5, + BWE_PACKET_LOSS_EVENT = 6, + BWE_PACKET_DELAY_EVENT = 7, + VIDEO_RECEIVER_CONFIG_EVENT = 8, + VIDEO_SENDER_CONFIG_EVENT = 9, + AUDIO_RECEIVER_CONFIG_EVENT = 10, + AUDIO_SENDER_CONFIG_EVENT = 11 + }; + + // Reads an RtcEventLog file and returns true if parsing was successful. + bool ParseFile(const std::string& file_name); + + // Returns the number of events in an EventStream. + size_t GetNumberOfEvents() const; + + // Reads the arrival timestamp (in microseconds) from a rtclog::Event. + int64_t GetTimestamp(size_t index) const; + + // Reads the event type of the rtclog::Event at |index|. + EventType GetEventType(size_t index) const; + + // Reads the header, direction, media type, header length and packet length + // from the RTP event at |index|, and stores the values in the corresponding + // output parameters. The output parameters can be set to nullptr if those + // values aren't needed. + // NB: The header must have space for at least IP_PACKET_SIZE bytes. + void GetRtpHeader(size_t index, + PacketDirection* incoming, + MediaType* media_type, + uint8_t* header, + size_t* header_length, + size_t* total_length) const; + + // Reads packet, direction, media type and packet length from the RTCP event + // at |index|, and stores the values in the corresponding output parameters. + // The output parameters can be set to nullptr if those values aren't needed. + // NB: The packet must have space for at least IP_PACKET_SIZE bytes. + void GetRtcpPacket(size_t index, + PacketDirection* incoming, + MediaType* media_type, + uint8_t* packet, + size_t* length) const; + + // Reads a config event to a (non-NULL) VideoReceiveStream::Config struct. + // Only the fields that are stored in the protobuf will be written. + void GetVideoReceiveConfig(size_t index, + VideoReceiveStream::Config* config) const; + + // Reads a config event to a (non-NULL) VideoSendStream::Config struct. + // Only the fields that are stored in the protobuf will be written. + void GetVideoSendConfig(size_t index, VideoSendStream::Config* config) const; + + // Reads the SSRC from the audio playout event at |index|. The SSRC is stored + // in the output parameter ssrc. The output parameter can be set to nullptr + // and in that case the function only asserts that the event is well formed. + void GetAudioPlayout(size_t index, uint32_t* ssrc) const; + + // Reads bitrate, fraction loss (as defined in RFC 1889) and total number of + // expected packets from the BWE event at |index| and stores the values in + // the corresponding output parameters. The output parameters can be set to + // nullptr if those values aren't needed. + // NB: The packet must have space for at least IP_PACKET_SIZE bytes. + void GetBwePacketLossEvent(size_t index, + int32_t* bitrate, + uint8_t* fraction_loss, + int32_t* total_packets) const; + + private: + std::vector stream_; +}; + +} // namespace webrtc + +#endif // WEBRTC_CALL_RTC_EVENT_LOG_PARSER_H_ diff --git a/webrtc/call/rtc_event_log_unittest.cc b/webrtc/call/rtc_event_log_unittest.cc index 0779f8e013..067c44123b 100644 --- a/webrtc/call/rtc_event_log_unittest.cc +++ b/webrtc/call/rtc_event_log_unittest.cc @@ -22,6 +22,8 @@ #include "webrtc/base/random.h" #include "webrtc/call.h" #include "webrtc/call/rtc_event_log.h" +#include "webrtc/call/rtc_event_log_parser.h" +#include "webrtc/call/rtc_event_log_unittest_helper.h" #include "webrtc/modules/rtp_rtcp/source/rtcp_packet.h" #include "webrtc/modules/rtp_rtcp/source/rtcp_packet/sender_report.h" #include "webrtc/modules/rtp_rtcp/source/rtp_sender.h" @@ -53,250 +55,51 @@ const char* kExtensionNames[] = {RtpExtension::kTOffset, RtpExtension::kTransportSequenceNumber}; const size_t kNumExtensions = 5; +void PrintActualEvents(const ParsedRtcEventLog& parsed_log) { + std::map actual_event_counts; + for (size_t i = 0; i < parsed_log.GetNumberOfEvents(); i++) { + actual_event_counts[parsed_log.GetEventType(i)]++; + } + printf("Actual events: "); + for (auto kv : actual_event_counts) { + printf("%d_count = %zu, ", kv.first, kv.second); + } + printf("\n"); + for (size_t i = 0; i < parsed_log.GetNumberOfEvents(); i++) { + printf("%4d ", parsed_log.GetEventType(i)); + } + printf("\n"); +} + +void PrintExpectedEvents(size_t rtp_count, + size_t rtcp_count, + size_t playout_count, + size_t bwe_loss_count) { + printf( + "Expected events: rtp_count = %zu, rtcp_count = %zu," + "playout_count = %zu, bwe_loss_count = %zu\n", + rtp_count, rtcp_count, playout_count, bwe_loss_count); + size_t rtcp_index = 1, playout_index = 1, bwe_loss_index = 1; + printf("strt cfg cfg "); + for (size_t i = 1; i <= rtp_count; i++) { + printf(" rtp "); + if (i * rtcp_count >= rtcp_index * rtp_count) { + printf("rtcp "); + rtcp_index++; + } + if (i * playout_count >= playout_index * rtp_count) { + printf("play "); + playout_index++; + } + if (i * bwe_loss_count >= bwe_loss_index * rtp_count) { + printf("loss "); + bwe_loss_index++; + } + } + printf("end \n"); +} } // namespace -// TODO(terelius): Place this definition with other parsing functions? -MediaType GetRuntimeMediaType(rtclog::MediaType media_type) { - switch (media_type) { - case rtclog::MediaType::ANY: - return MediaType::ANY; - case rtclog::MediaType::AUDIO: - return MediaType::AUDIO; - case rtclog::MediaType::VIDEO: - return MediaType::VIDEO; - case rtclog::MediaType::DATA: - return MediaType::DATA; - } - RTC_NOTREACHED(); - return MediaType::ANY; -} - -// Checks that the event has a timestamp, a type and exactly the data field -// corresponding to the type. -::testing::AssertionResult IsValidBasicEvent(const rtclog::Event& event) { - if (!event.has_timestamp_us()) - return ::testing::AssertionFailure() << "Event has no timestamp"; - if (!event.has_type()) - return ::testing::AssertionFailure() << "Event has no event type"; - rtclog::Event_EventType type = event.type(); - if ((type == rtclog::Event::RTP_EVENT) != event.has_rtp_packet()) - return ::testing::AssertionFailure() - << "Event of type " << type << " has " - << (event.has_rtp_packet() ? "" : "no ") << "RTP packet"; - if ((type == rtclog::Event::RTCP_EVENT) != event.has_rtcp_packet()) - return ::testing::AssertionFailure() - << "Event of type " << type << " has " - << (event.has_rtcp_packet() ? "" : "no ") << "RTCP packet"; - if ((type == rtclog::Event::AUDIO_PLAYOUT_EVENT) != - event.has_audio_playout_event()) - return ::testing::AssertionFailure() - << "Event of type " << type << " has " - << (event.has_audio_playout_event() ? "" : "no ") - << "audio_playout event"; - if ((type == rtclog::Event::VIDEO_RECEIVER_CONFIG_EVENT) != - event.has_video_receiver_config()) - return ::testing::AssertionFailure() - << "Event of type " << type << " has " - << (event.has_video_receiver_config() ? "" : "no ") - << "receiver config"; - if ((type == rtclog::Event::VIDEO_SENDER_CONFIG_EVENT) != - event.has_video_sender_config()) - return ::testing::AssertionFailure() - << "Event of type " << type << " has " - << (event.has_video_sender_config() ? "" : "no ") << "sender config"; - if ((type == rtclog::Event::AUDIO_RECEIVER_CONFIG_EVENT) != - event.has_audio_receiver_config()) { - return ::testing::AssertionFailure() - << "Event of type " << type << " has " - << (event.has_audio_receiver_config() ? "" : "no ") - << "audio receiver config"; - } - if ((type == rtclog::Event::AUDIO_SENDER_CONFIG_EVENT) != - event.has_audio_sender_config()) { - return ::testing::AssertionFailure() - << "Event of type " << type << " has " - << (event.has_audio_sender_config() ? "" : "no ") - << "audio sender config"; - } - return ::testing::AssertionSuccess(); -} - -void VerifyReceiveStreamConfig(const rtclog::Event& event, - const VideoReceiveStream::Config& config) { - ASSERT_TRUE(IsValidBasicEvent(event)); - ASSERT_EQ(rtclog::Event::VIDEO_RECEIVER_CONFIG_EVENT, event.type()); - const rtclog::VideoReceiveConfig& receiver_config = - event.video_receiver_config(); - // Check SSRCs. - ASSERT_TRUE(receiver_config.has_remote_ssrc()); - EXPECT_EQ(config.rtp.remote_ssrc, receiver_config.remote_ssrc()); - ASSERT_TRUE(receiver_config.has_local_ssrc()); - EXPECT_EQ(config.rtp.local_ssrc, receiver_config.local_ssrc()); - // Check RTCP settings. - ASSERT_TRUE(receiver_config.has_rtcp_mode()); - if (config.rtp.rtcp_mode == RtcpMode::kCompound) - EXPECT_EQ(rtclog::VideoReceiveConfig::RTCP_COMPOUND, - receiver_config.rtcp_mode()); - else - EXPECT_EQ(rtclog::VideoReceiveConfig::RTCP_REDUCEDSIZE, - receiver_config.rtcp_mode()); - ASSERT_TRUE(receiver_config.has_remb()); - EXPECT_EQ(config.rtp.remb, receiver_config.remb()); - // Check RTX map. - ASSERT_EQ(static_cast(config.rtp.rtx.size()), - receiver_config.rtx_map_size()); - for (const rtclog::RtxMap& rtx_map : receiver_config.rtx_map()) { - ASSERT_TRUE(rtx_map.has_payload_type()); - ASSERT_TRUE(rtx_map.has_config()); - EXPECT_EQ(1u, config.rtp.rtx.count(rtx_map.payload_type())); - const rtclog::RtxConfig& rtx_config = rtx_map.config(); - const VideoReceiveStream::Config::Rtp::Rtx& rtx = - config.rtp.rtx.at(rtx_map.payload_type()); - ASSERT_TRUE(rtx_config.has_rtx_ssrc()); - ASSERT_TRUE(rtx_config.has_rtx_payload_type()); - EXPECT_EQ(rtx.ssrc, rtx_config.rtx_ssrc()); - EXPECT_EQ(rtx.payload_type, rtx_config.rtx_payload_type()); - } - // Check header extensions. - ASSERT_EQ(static_cast(config.rtp.extensions.size()), - receiver_config.header_extensions_size()); - for (int i = 0; i < receiver_config.header_extensions_size(); i++) { - ASSERT_TRUE(receiver_config.header_extensions(i).has_name()); - ASSERT_TRUE(receiver_config.header_extensions(i).has_id()); - const std::string& name = receiver_config.header_extensions(i).name(); - int id = receiver_config.header_extensions(i).id(); - EXPECT_EQ(config.rtp.extensions[i].id, id); - EXPECT_EQ(config.rtp.extensions[i].name, name); - } - // Check decoders. - ASSERT_EQ(static_cast(config.decoders.size()), - receiver_config.decoders_size()); - for (int i = 0; i < receiver_config.decoders_size(); i++) { - ASSERT_TRUE(receiver_config.decoders(i).has_name()); - ASSERT_TRUE(receiver_config.decoders(i).has_payload_type()); - const std::string& decoder_name = receiver_config.decoders(i).name(); - int decoder_type = receiver_config.decoders(i).payload_type(); - EXPECT_EQ(config.decoders[i].payload_name, decoder_name); - EXPECT_EQ(config.decoders[i].payload_type, decoder_type); - } -} - -void VerifySendStreamConfig(const rtclog::Event& event, - const VideoSendStream::Config& config) { - ASSERT_TRUE(IsValidBasicEvent(event)); - ASSERT_EQ(rtclog::Event::VIDEO_SENDER_CONFIG_EVENT, event.type()); - const rtclog::VideoSendConfig& sender_config = event.video_sender_config(); - // Check SSRCs. - ASSERT_EQ(static_cast(config.rtp.ssrcs.size()), - sender_config.ssrcs_size()); - for (int i = 0; i < sender_config.ssrcs_size(); i++) { - EXPECT_EQ(config.rtp.ssrcs[i], sender_config.ssrcs(i)); - } - // Check header extensions. - ASSERT_EQ(static_cast(config.rtp.extensions.size()), - sender_config.header_extensions_size()); - for (int i = 0; i < sender_config.header_extensions_size(); i++) { - ASSERT_TRUE(sender_config.header_extensions(i).has_name()); - ASSERT_TRUE(sender_config.header_extensions(i).has_id()); - const std::string& name = sender_config.header_extensions(i).name(); - int id = sender_config.header_extensions(i).id(); - EXPECT_EQ(config.rtp.extensions[i].id, id); - EXPECT_EQ(config.rtp.extensions[i].name, name); - } - // Check RTX settings. - ASSERT_EQ(static_cast(config.rtp.rtx.ssrcs.size()), - sender_config.rtx_ssrcs_size()); - for (int i = 0; i < sender_config.rtx_ssrcs_size(); i++) { - EXPECT_EQ(config.rtp.rtx.ssrcs[i], sender_config.rtx_ssrcs(i)); - } - if (sender_config.rtx_ssrcs_size() > 0) { - ASSERT_TRUE(sender_config.has_rtx_payload_type()); - EXPECT_EQ(config.rtp.rtx.payload_type, sender_config.rtx_payload_type()); - } - // Check encoder. - ASSERT_TRUE(sender_config.has_encoder()); - ASSERT_TRUE(sender_config.encoder().has_name()); - ASSERT_TRUE(sender_config.encoder().has_payload_type()); - EXPECT_EQ(config.encoder_settings.payload_name, - sender_config.encoder().name()); - EXPECT_EQ(config.encoder_settings.payload_type, - sender_config.encoder().payload_type()); -} - -void VerifyRtpEvent(const rtclog::Event& event, - PacketDirection direction, - MediaType media_type, - const uint8_t* header, - size_t header_size, - size_t total_size) { - ASSERT_TRUE(IsValidBasicEvent(event)); - ASSERT_EQ(rtclog::Event::RTP_EVENT, event.type()); - const rtclog::RtpPacket& rtp_packet = event.rtp_packet(); - ASSERT_TRUE(rtp_packet.has_incoming()); - EXPECT_EQ(direction == kIncomingPacket, rtp_packet.incoming()); - ASSERT_TRUE(rtp_packet.has_type()); - EXPECT_EQ(media_type, GetRuntimeMediaType(rtp_packet.type())); - ASSERT_TRUE(rtp_packet.has_packet_length()); - EXPECT_EQ(total_size, rtp_packet.packet_length()); - ASSERT_TRUE(rtp_packet.has_header()); - ASSERT_EQ(header_size, rtp_packet.header().size()); - for (size_t i = 0; i < header_size; i++) { - EXPECT_EQ(header[i], static_cast(rtp_packet.header()[i])); - } -} - -void VerifyRtcpEvent(const rtclog::Event& event, - PacketDirection direction, - MediaType media_type, - const uint8_t* packet, - size_t total_size) { - ASSERT_TRUE(IsValidBasicEvent(event)); - ASSERT_EQ(rtclog::Event::RTCP_EVENT, event.type()); - const rtclog::RtcpPacket& rtcp_packet = event.rtcp_packet(); - ASSERT_TRUE(rtcp_packet.has_incoming()); - EXPECT_EQ(direction == kIncomingPacket, rtcp_packet.incoming()); - ASSERT_TRUE(rtcp_packet.has_type()); - EXPECT_EQ(media_type, GetRuntimeMediaType(rtcp_packet.type())); - ASSERT_TRUE(rtcp_packet.has_packet_data()); - ASSERT_EQ(total_size, rtcp_packet.packet_data().size()); - for (size_t i = 0; i < total_size; i++) { - EXPECT_EQ(packet[i], static_cast(rtcp_packet.packet_data()[i])); - } -} - -void VerifyPlayoutEvent(const rtclog::Event& event, uint32_t ssrc) { - ASSERT_TRUE(IsValidBasicEvent(event)); - ASSERT_EQ(rtclog::Event::AUDIO_PLAYOUT_EVENT, event.type()); - const rtclog::AudioPlayoutEvent& playout_event = event.audio_playout_event(); - ASSERT_TRUE(playout_event.has_local_ssrc()); - EXPECT_EQ(ssrc, playout_event.local_ssrc()); -} - -void VerifyBweLossEvent(const rtclog::Event& event, - int32_t bitrate, - uint8_t fraction_loss, - int32_t total_packets) { - ASSERT_TRUE(IsValidBasicEvent(event)); - ASSERT_EQ(rtclog::Event::BWE_PACKET_LOSS_EVENT, event.type()); - const rtclog::BwePacketLossEvent& bwe_event = event.bwe_packet_loss_event(); - ASSERT_TRUE(bwe_event.has_bitrate()); - EXPECT_EQ(bitrate, bwe_event.bitrate()); - ASSERT_TRUE(bwe_event.has_fraction_loss()); - EXPECT_EQ(fraction_loss, bwe_event.fraction_loss()); - ASSERT_TRUE(bwe_event.has_total_packets()); - EXPECT_EQ(total_packets, bwe_event.total_packets()); -} - -void VerifyLogStartEvent(const rtclog::Event& event) { - ASSERT_TRUE(IsValidBasicEvent(event)); - EXPECT_EQ(rtclog::Event::LOG_START, event.type()); -} - -void VerifyLogEndEvent(const rtclog::Event& event) { - ASSERT_TRUE(IsValidBasicEvent(event)); - EXPECT_EQ(rtclog::Event::LOG_END, event.type()); -} - /* * Bit number i of extension_bitvector is set to indicate the * presence of extension number i from kExtensionTypes / kExtensionNames. @@ -523,90 +326,58 @@ void LogSessionAndReadBack(size_t rtp_count, } // Read the generated file from disk. - rtclog::EventStream parsed_stream; + ParsedRtcEventLog parsed_log; - ASSERT_TRUE(RtcEventLog::ParseRtcEventLog(temp_filename, &parsed_stream)); + ASSERT_TRUE(parsed_log.ParseFile(temp_filename)); // Verify that what we read back from the event log is the same as // what we wrote down. For RTCP we log the full packets, but for // RTP we should only log the header. - const int event_count = config_count + playout_count + bwe_loss_count + - rtcp_count + rtp_count + 2; - EXPECT_GE(1000, event_count); // The events must fit in the message queue. - EXPECT_EQ(event_count, parsed_stream.stream_size()); - if (event_count != parsed_stream.stream_size()) { + const size_t event_count = config_count + playout_count + bwe_loss_count + + rtcp_count + rtp_count + 2; + EXPECT_GE(1000u, event_count); // The events must fit in the message queue. + EXPECT_EQ(event_count, parsed_log.GetNumberOfEvents()); + if (event_count != parsed_log.GetNumberOfEvents()) { // Print the expected and actual event types for easier debugging. - std::map actual_event_counts; - for (size_t i = 0; i < static_cast(parsed_stream.stream_size()); - i++) { - actual_event_counts[parsed_stream.stream(i).type()]++; - } - printf("Actual events: "); - for (auto kv : actual_event_counts) { - printf("%d_count = %zu, ", kv.first, kv.second); - } - printf("\n"); - for (size_t i = 0; i < static_cast(parsed_stream.stream_size()); - i++) { - printf("%4d ", parsed_stream.stream(i).type()); - } - printf("\n"); - printf( - "Expected events: rtp_count = %zu, rtcp_count = %zu," - "playout_count = %zu, bwe_loss_count = %zu\n", - rtp_count, rtcp_count, playout_count, bwe_loss_count); - size_t rtcp_index = 1, playout_index = 1, bwe_loss_index = 1; - printf("strt cfg cfg "); - for (size_t i = 1; i <= rtp_count; i++) { - printf(" rtp "); - if (i * rtcp_count >= rtcp_index * rtp_count) { - printf("rtcp "); - rtcp_index++; - } - if (i * playout_count >= playout_index * rtp_count) { - printf("play "); - playout_index++; - } - if (i * bwe_loss_count >= bwe_loss_index * rtp_count) { - printf("loss "); - bwe_loss_index++; - } - } - printf("\n"); + PrintActualEvents(parsed_log); + PrintExpectedEvents(rtp_count, rtcp_count, playout_count, bwe_loss_count); } - VerifyLogStartEvent(parsed_stream.stream(0)); - VerifyReceiveStreamConfig(parsed_stream.stream(1), receiver_config); - VerifySendStreamConfig(parsed_stream.stream(2), sender_config); + RtcEventLogTestHelper::VerifyLogStartEvent(parsed_log, 0); + RtcEventLogTestHelper::VerifyReceiveStreamConfig(parsed_log, 1, + receiver_config); + RtcEventLogTestHelper::VerifySendStreamConfig(parsed_log, 2, sender_config); size_t event_index = config_count + 1; size_t rtcp_index = 1; size_t playout_index = 1; size_t bwe_loss_index = 1; for (size_t i = 1; i <= rtp_count; i++) { - VerifyRtpEvent(parsed_stream.stream(event_index), - (i % 2 == 0) ? kIncomingPacket : kOutgoingPacket, - (i % 3 == 0) ? MediaType::AUDIO : MediaType::VIDEO, - rtp_packets[i - 1].data(), rtp_header_sizes[i - 1], - rtp_packets[i - 1].size()); + RtcEventLogTestHelper::VerifyRtpEvent( + parsed_log, event_index, + (i % 2 == 0) ? kIncomingPacket : kOutgoingPacket, + (i % 3 == 0) ? MediaType::AUDIO : MediaType::VIDEO, + rtp_packets[i - 1].data(), rtp_header_sizes[i - 1], + rtp_packets[i - 1].size()); event_index++; if (i * rtcp_count >= rtcp_index * rtp_count) { - VerifyRtcpEvent(parsed_stream.stream(event_index), - (rtcp_index % 2 == 0) ? kIncomingPacket : kOutgoingPacket, - rtcp_index % 3 == 0 ? MediaType::AUDIO : MediaType::VIDEO, - rtcp_packets[rtcp_index - 1].data(), - rtcp_packets[rtcp_index - 1].size()); + RtcEventLogTestHelper::VerifyRtcpEvent( + parsed_log, event_index, + rtcp_index % 2 == 0 ? kIncomingPacket : kOutgoingPacket, + rtcp_index % 3 == 0 ? MediaType::AUDIO : MediaType::VIDEO, + rtcp_packets[rtcp_index - 1].data(), + rtcp_packets[rtcp_index - 1].size()); event_index++; rtcp_index++; } if (i * playout_count >= playout_index * rtp_count) { - VerifyPlayoutEvent(parsed_stream.stream(event_index), - playout_ssrcs[playout_index - 1]); + RtcEventLogTestHelper::VerifyPlayoutEvent( + parsed_log, event_index, playout_ssrcs[playout_index - 1]); event_index++; playout_index++; } if (i * bwe_loss_count >= bwe_loss_index * rtp_count) { - VerifyBweLossEvent(parsed_stream.stream(event_index), - bwe_loss_updates[bwe_loss_index - 1].first, - bwe_loss_updates[bwe_loss_index - 1].second, i); + RtcEventLogTestHelper::VerifyBweLossEvent( + parsed_log, event_index, bwe_loss_updates[bwe_loss_index - 1].first, + bwe_loss_updates[bwe_loss_index - 1].second, i); event_index++; bwe_loss_index++; } @@ -683,22 +454,24 @@ TEST(RtcEventLogTest, LogEventAndReadBack) { log_dumper->StopLogging(); // Read the generated file from disk. - rtclog::EventStream parsed_stream; - ASSERT_TRUE(RtcEventLog::ParseRtcEventLog(temp_filename, &parsed_stream)); + ParsedRtcEventLog parsed_log; + ASSERT_TRUE(parsed_log.ParseFile(temp_filename)); // Verify that what we read back from the event log is the same as // what we wrote down. - EXPECT_EQ(4, parsed_stream.stream_size()); + EXPECT_EQ(4u, parsed_log.GetNumberOfEvents()); - VerifyLogStartEvent(parsed_stream.stream(0)); + RtcEventLogTestHelper::VerifyLogStartEvent(parsed_log, 0); - VerifyRtpEvent(parsed_stream.stream(1), kIncomingPacket, MediaType::VIDEO, - rtp_packet.data(), header_size, rtp_packet.size()); + RtcEventLogTestHelper::VerifyRtpEvent(parsed_log, 1, kIncomingPacket, + MediaType::VIDEO, rtp_packet.data(), + header_size, rtp_packet.size()); - VerifyRtcpEvent(parsed_stream.stream(2), kOutgoingPacket, MediaType::VIDEO, - rtcp_packet.data(), rtcp_packet.size()); + RtcEventLogTestHelper::VerifyRtcpEvent(parsed_log, 2, kOutgoingPacket, + MediaType::VIDEO, rtcp_packet.data(), + rtcp_packet.size()); - VerifyLogEndEvent(parsed_stream.stream(3)); + RtcEventLogTestHelper::VerifyLogEndEvent(parsed_log, 3); // Clean up temporary file - can be pretty slow. remove(temp_filename.c_str()); diff --git a/webrtc/call/rtc_event_log_unittest_helper.cc b/webrtc/call/rtc_event_log_unittest_helper.cc new file mode 100644 index 0000000000..5a06d97170 --- /dev/null +++ b/webrtc/call/rtc_event_log_unittest_helper.cc @@ -0,0 +1,409 @@ +/* + * 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. + */ + +#ifdef ENABLE_RTC_EVENT_LOG + +#include "webrtc/call/rtc_event_log_unittest_helper.h" + +#include + +#include + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/base/checks.h" +#include "webrtc/test/test_suite.h" +#include "webrtc/test/testsupport/fileutils.h" + +// Files generated at build-time by the protobuf compiler. +#ifdef WEBRTC_ANDROID_PLATFORM_BUILD +#include "external/webrtc/webrtc/call/rtc_event_log.pb.h" +#else +#include "webrtc/call/rtc_event_log.pb.h" +#endif + +namespace webrtc { + +namespace { +MediaType GetRuntimeMediaType(rtclog::MediaType media_type) { + switch (media_type) { + case rtclog::MediaType::ANY: + return MediaType::ANY; + case rtclog::MediaType::AUDIO: + return MediaType::AUDIO; + case rtclog::MediaType::VIDEO: + return MediaType::VIDEO; + case rtclog::MediaType::DATA: + return MediaType::DATA; + } + RTC_NOTREACHED(); + return MediaType::ANY; +} +} // namespace + +// Checks that the event has a timestamp, a type and exactly the data field +// corresponding to the type. +::testing::AssertionResult IsValidBasicEvent(const rtclog::Event& event) { + if (!event.has_timestamp_us()) { + return ::testing::AssertionFailure() << "Event has no timestamp"; + } + if (!event.has_type()) { + return ::testing::AssertionFailure() << "Event has no event type"; + } + rtclog::Event_EventType type = event.type(); + if ((type == rtclog::Event::RTP_EVENT) != event.has_rtp_packet()) { + return ::testing::AssertionFailure() + << "Event of type " << type << " has " + << (event.has_rtp_packet() ? "" : "no ") << "RTP packet"; + } + if ((type == rtclog::Event::RTCP_EVENT) != event.has_rtcp_packet()) { + return ::testing::AssertionFailure() + << "Event of type " << type << " has " + << (event.has_rtcp_packet() ? "" : "no ") << "RTCP packet"; + } + if ((type == rtclog::Event::AUDIO_PLAYOUT_EVENT) != + event.has_audio_playout_event()) { + return ::testing::AssertionFailure() + << "Event of type " << type << " has " + << (event.has_audio_playout_event() ? "" : "no ") + << "audio_playout event"; + } + if ((type == rtclog::Event::VIDEO_RECEIVER_CONFIG_EVENT) != + event.has_video_receiver_config()) { + return ::testing::AssertionFailure() + << "Event of type " << type << " has " + << (event.has_video_receiver_config() ? "" : "no ") + << "receiver config"; + } + if ((type == rtclog::Event::VIDEO_SENDER_CONFIG_EVENT) != + event.has_video_sender_config()) { + return ::testing::AssertionFailure() + << "Event of type " << type << " has " + << (event.has_video_sender_config() ? "" : "no ") << "sender config"; + } + if ((type == rtclog::Event::AUDIO_RECEIVER_CONFIG_EVENT) != + event.has_audio_receiver_config()) { + return ::testing::AssertionFailure() + << "Event of type " << type << " has " + << (event.has_audio_receiver_config() ? "" : "no ") + << "audio receiver config"; + } + if ((type == rtclog::Event::AUDIO_SENDER_CONFIG_EVENT) != + event.has_audio_sender_config()) { + return ::testing::AssertionFailure() + << "Event of type " << type << " has " + << (event.has_audio_sender_config() ? "" : "no ") + << "audio sender config"; + } + return ::testing::AssertionSuccess(); +} + +void RtcEventLogTestHelper::VerifyReceiveStreamConfig( + const ParsedRtcEventLog& parsed_log, + size_t index, + const VideoReceiveStream::Config& config) { + const rtclog::Event& event = parsed_log.stream_[index]; + ASSERT_TRUE(IsValidBasicEvent(event)); + ASSERT_EQ(rtclog::Event::VIDEO_RECEIVER_CONFIG_EVENT, event.type()); + const rtclog::VideoReceiveConfig& receiver_config = + event.video_receiver_config(); + // Check SSRCs. + ASSERT_TRUE(receiver_config.has_remote_ssrc()); + EXPECT_EQ(config.rtp.remote_ssrc, receiver_config.remote_ssrc()); + ASSERT_TRUE(receiver_config.has_local_ssrc()); + EXPECT_EQ(config.rtp.local_ssrc, receiver_config.local_ssrc()); + // Check RTCP settings. + ASSERT_TRUE(receiver_config.has_rtcp_mode()); + if (config.rtp.rtcp_mode == RtcpMode::kCompound) { + EXPECT_EQ(rtclog::VideoReceiveConfig::RTCP_COMPOUND, + receiver_config.rtcp_mode()); + } else { + EXPECT_EQ(rtclog::VideoReceiveConfig::RTCP_REDUCEDSIZE, + receiver_config.rtcp_mode()); + } + ASSERT_TRUE(receiver_config.has_remb()); + EXPECT_EQ(config.rtp.remb, receiver_config.remb()); + // Check RTX map. + ASSERT_EQ(static_cast(config.rtp.rtx.size()), + receiver_config.rtx_map_size()); + for (const rtclog::RtxMap& rtx_map : receiver_config.rtx_map()) { + ASSERT_TRUE(rtx_map.has_payload_type()); + ASSERT_TRUE(rtx_map.has_config()); + EXPECT_EQ(1u, config.rtp.rtx.count(rtx_map.payload_type())); + const rtclog::RtxConfig& rtx_config = rtx_map.config(); + const VideoReceiveStream::Config::Rtp::Rtx& rtx = + config.rtp.rtx.at(rtx_map.payload_type()); + ASSERT_TRUE(rtx_config.has_rtx_ssrc()); + ASSERT_TRUE(rtx_config.has_rtx_payload_type()); + EXPECT_EQ(rtx.ssrc, rtx_config.rtx_ssrc()); + EXPECT_EQ(rtx.payload_type, rtx_config.rtx_payload_type()); + } + // Check header extensions. + ASSERT_EQ(static_cast(config.rtp.extensions.size()), + receiver_config.header_extensions_size()); + for (int i = 0; i < receiver_config.header_extensions_size(); i++) { + ASSERT_TRUE(receiver_config.header_extensions(i).has_name()); + ASSERT_TRUE(receiver_config.header_extensions(i).has_id()); + const std::string& name = receiver_config.header_extensions(i).name(); + int id = receiver_config.header_extensions(i).id(); + EXPECT_EQ(config.rtp.extensions[i].id, id); + EXPECT_EQ(config.rtp.extensions[i].name, name); + } + // Check decoders. + ASSERT_EQ(static_cast(config.decoders.size()), + receiver_config.decoders_size()); + for (int i = 0; i < receiver_config.decoders_size(); i++) { + ASSERT_TRUE(receiver_config.decoders(i).has_name()); + ASSERT_TRUE(receiver_config.decoders(i).has_payload_type()); + const std::string& decoder_name = receiver_config.decoders(i).name(); + int decoder_type = receiver_config.decoders(i).payload_type(); + EXPECT_EQ(config.decoders[i].payload_name, decoder_name); + EXPECT_EQ(config.decoders[i].payload_type, decoder_type); + } + + // Check consistency of the parser. + VideoReceiveStream::Config parsed_config(nullptr); + parsed_log.GetVideoReceiveConfig(index, &parsed_config); + EXPECT_EQ(config.rtp.remote_ssrc, parsed_config.rtp.remote_ssrc); + EXPECT_EQ(config.rtp.local_ssrc, parsed_config.rtp.local_ssrc); + // Check RTCP settings. + EXPECT_EQ(config.rtp.rtcp_mode, parsed_config.rtp.rtcp_mode); + EXPECT_EQ(config.rtp.remb, parsed_config.rtp.remb); + // Check RTX map. + EXPECT_EQ(config.rtp.rtx.size(), parsed_config.rtp.rtx.size()); + for (const auto& kv : config.rtp.rtx) { + auto parsed_kv = parsed_config.rtp.rtx.find(kv.first); + EXPECT_EQ(kv.first, parsed_kv->first); + EXPECT_EQ(kv.second.ssrc, parsed_kv->second.ssrc); + EXPECT_EQ(kv.second.payload_type, parsed_kv->second.payload_type); + } + // Check header extensions. + EXPECT_EQ(config.rtp.extensions.size(), parsed_config.rtp.extensions.size()); + for (size_t i = 0; i < parsed_config.rtp.extensions.size(); i++) { + EXPECT_EQ(config.rtp.extensions[i].name, + parsed_config.rtp.extensions[i].name); + EXPECT_EQ(config.rtp.extensions[i].id, parsed_config.rtp.extensions[i].id); + } + // Check decoders. + EXPECT_EQ(config.decoders.size(), parsed_config.decoders.size()); + for (size_t i = 0; i < parsed_config.decoders.size(); i++) { + EXPECT_EQ(config.decoders[i].payload_name, + parsed_config.decoders[i].payload_name); + EXPECT_EQ(config.decoders[i].payload_type, + parsed_config.decoders[i].payload_type); + } +} + +void RtcEventLogTestHelper::VerifySendStreamConfig( + const ParsedRtcEventLog& parsed_log, + size_t index, + const VideoSendStream::Config& config) { + const rtclog::Event& event = parsed_log.stream_[index]; + ASSERT_TRUE(IsValidBasicEvent(event)); + ASSERT_EQ(rtclog::Event::VIDEO_SENDER_CONFIG_EVENT, event.type()); + const rtclog::VideoSendConfig& sender_config = event.video_sender_config(); + // Check SSRCs. + ASSERT_EQ(static_cast(config.rtp.ssrcs.size()), + sender_config.ssrcs_size()); + for (int i = 0; i < sender_config.ssrcs_size(); i++) { + EXPECT_EQ(config.rtp.ssrcs[i], sender_config.ssrcs(i)); + } + // Check header extensions. + ASSERT_EQ(static_cast(config.rtp.extensions.size()), + sender_config.header_extensions_size()); + for (int i = 0; i < sender_config.header_extensions_size(); i++) { + ASSERT_TRUE(sender_config.header_extensions(i).has_name()); + ASSERT_TRUE(sender_config.header_extensions(i).has_id()); + const std::string& name = sender_config.header_extensions(i).name(); + int id = sender_config.header_extensions(i).id(); + EXPECT_EQ(config.rtp.extensions[i].id, id); + EXPECT_EQ(config.rtp.extensions[i].name, name); + } + // Check RTX settings. + ASSERT_EQ(static_cast(config.rtp.rtx.ssrcs.size()), + sender_config.rtx_ssrcs_size()); + for (int i = 0; i < sender_config.rtx_ssrcs_size(); i++) { + EXPECT_EQ(config.rtp.rtx.ssrcs[i], sender_config.rtx_ssrcs(i)); + } + if (sender_config.rtx_ssrcs_size() > 0) { + ASSERT_TRUE(sender_config.has_rtx_payload_type()); + EXPECT_EQ(config.rtp.rtx.payload_type, sender_config.rtx_payload_type()); + } + // Check encoder. + ASSERT_TRUE(sender_config.has_encoder()); + ASSERT_TRUE(sender_config.encoder().has_name()); + ASSERT_TRUE(sender_config.encoder().has_payload_type()); + EXPECT_EQ(config.encoder_settings.payload_name, + sender_config.encoder().name()); + EXPECT_EQ(config.encoder_settings.payload_type, + sender_config.encoder().payload_type()); + + // Check consistency of the parser. + VideoSendStream::Config parsed_config(nullptr); + parsed_log.GetVideoSendConfig(index, &parsed_config); + // Check SSRCs + EXPECT_EQ(config.rtp.ssrcs.size(), parsed_config.rtp.ssrcs.size()); + for (size_t i = 0; i < config.rtp.ssrcs.size(); i++) { + EXPECT_EQ(config.rtp.ssrcs[i], parsed_config.rtp.ssrcs[i]); + } + // Check header extensions. + EXPECT_EQ(config.rtp.extensions.size(), parsed_config.rtp.extensions.size()); + for (size_t i = 0; i < parsed_config.rtp.extensions.size(); i++) { + EXPECT_EQ(config.rtp.extensions[i].name, + parsed_config.rtp.extensions[i].name); + EXPECT_EQ(config.rtp.extensions[i].id, parsed_config.rtp.extensions[i].id); + } + // Check RTX settings. + EXPECT_EQ(config.rtp.rtx.ssrcs.size(), parsed_config.rtp.rtx.ssrcs.size()); + for (size_t i = 0; i < config.rtp.rtx.ssrcs.size(); i++) { + EXPECT_EQ(config.rtp.rtx.ssrcs[i], parsed_config.rtp.rtx.ssrcs[i]); + } + EXPECT_EQ(config.rtp.rtx.payload_type, parsed_config.rtp.rtx.payload_type); + // Check encoder. + EXPECT_EQ(config.encoder_settings.payload_name, + parsed_config.encoder_settings.payload_name); + EXPECT_EQ(config.encoder_settings.payload_type, + parsed_config.encoder_settings.payload_type); +} + +void RtcEventLogTestHelper::VerifyRtpEvent(const ParsedRtcEventLog& parsed_log, + size_t index, + PacketDirection direction, + MediaType media_type, + const uint8_t* header, + size_t header_size, + size_t total_size) { + const rtclog::Event& event = parsed_log.stream_[index]; + ASSERT_TRUE(IsValidBasicEvent(event)); + ASSERT_EQ(rtclog::Event::RTP_EVENT, event.type()); + const rtclog::RtpPacket& rtp_packet = event.rtp_packet(); + ASSERT_TRUE(rtp_packet.has_incoming()); + EXPECT_EQ(direction == kIncomingPacket, rtp_packet.incoming()); + ASSERT_TRUE(rtp_packet.has_type()); + EXPECT_EQ(media_type, GetRuntimeMediaType(rtp_packet.type())); + ASSERT_TRUE(rtp_packet.has_packet_length()); + EXPECT_EQ(total_size, rtp_packet.packet_length()); + ASSERT_TRUE(rtp_packet.has_header()); + ASSERT_EQ(header_size, rtp_packet.header().size()); + for (size_t i = 0; i < header_size; i++) { + EXPECT_EQ(header[i], static_cast(rtp_packet.header()[i])); + } + + // Check consistency of the parser. + PacketDirection parsed_direction; + MediaType parsed_media_type; + uint8_t parsed_header[1500]; + size_t parsed_header_size, parsed_total_size; + parsed_log.GetRtpHeader(index, &parsed_direction, &parsed_media_type, + parsed_header, &parsed_header_size, + &parsed_total_size); + EXPECT_EQ(direction, parsed_direction); + EXPECT_EQ(media_type, parsed_media_type); + ASSERT_EQ(header_size, parsed_header_size); + EXPECT_EQ(0, std::memcmp(header, parsed_header, header_size)); + EXPECT_EQ(total_size, parsed_total_size); +} + +void RtcEventLogTestHelper::VerifyRtcpEvent(const ParsedRtcEventLog& parsed_log, + size_t index, + PacketDirection direction, + MediaType media_type, + const uint8_t* packet, + size_t total_size) { + const rtclog::Event& event = parsed_log.stream_[index]; + ASSERT_TRUE(IsValidBasicEvent(event)); + ASSERT_EQ(rtclog::Event::RTCP_EVENT, event.type()); + const rtclog::RtcpPacket& rtcp_packet = event.rtcp_packet(); + ASSERT_TRUE(rtcp_packet.has_incoming()); + EXPECT_EQ(direction == kIncomingPacket, rtcp_packet.incoming()); + ASSERT_TRUE(rtcp_packet.has_type()); + EXPECT_EQ(media_type, GetRuntimeMediaType(rtcp_packet.type())); + ASSERT_TRUE(rtcp_packet.has_packet_data()); + ASSERT_EQ(total_size, rtcp_packet.packet_data().size()); + for (size_t i = 0; i < total_size; i++) { + EXPECT_EQ(packet[i], static_cast(rtcp_packet.packet_data()[i])); + } + + // Check consistency of the parser. + PacketDirection parsed_direction; + MediaType parsed_media_type; + uint8_t parsed_packet[1500]; + size_t parsed_total_size; + parsed_log.GetRtcpPacket(index, &parsed_direction, &parsed_media_type, + parsed_packet, &parsed_total_size); + EXPECT_EQ(direction, parsed_direction); + EXPECT_EQ(media_type, parsed_media_type); + ASSERT_EQ(total_size, parsed_total_size); + EXPECT_EQ(0, std::memcmp(packet, parsed_packet, total_size)); +} + +void RtcEventLogTestHelper::VerifyPlayoutEvent( + const ParsedRtcEventLog& parsed_log, + size_t index, + uint32_t ssrc) { + const rtclog::Event& event = parsed_log.stream_[index]; + ASSERT_TRUE(IsValidBasicEvent(event)); + ASSERT_EQ(rtclog::Event::AUDIO_PLAYOUT_EVENT, event.type()); + const rtclog::AudioPlayoutEvent& playout_event = event.audio_playout_event(); + ASSERT_TRUE(playout_event.has_local_ssrc()); + EXPECT_EQ(ssrc, playout_event.local_ssrc()); + + // Check consistency of the parser. + uint32_t parsed_ssrc; + parsed_log.GetAudioPlayout(index, &parsed_ssrc); + EXPECT_EQ(ssrc, parsed_ssrc); +} + +void RtcEventLogTestHelper::VerifyBweLossEvent( + const ParsedRtcEventLog& parsed_log, + size_t index, + int32_t bitrate, + uint8_t fraction_loss, + int32_t total_packets) { + const rtclog::Event& event = parsed_log.stream_[index]; + ASSERT_TRUE(IsValidBasicEvent(event)); + ASSERT_EQ(rtclog::Event::BWE_PACKET_LOSS_EVENT, event.type()); + const rtclog::BwePacketLossEvent& bwe_event = event.bwe_packet_loss_event(); + ASSERT_TRUE(bwe_event.has_bitrate()); + EXPECT_EQ(bitrate, bwe_event.bitrate()); + ASSERT_TRUE(bwe_event.has_fraction_loss()); + EXPECT_EQ(fraction_loss, bwe_event.fraction_loss()); + ASSERT_TRUE(bwe_event.has_total_packets()); + EXPECT_EQ(total_packets, bwe_event.total_packets()); + + // Check consistency of the parser. + int32_t parsed_bitrate; + uint8_t parsed_fraction_loss; + int32_t parsed_total_packets; + parsed_log.GetBwePacketLossEvent( + index, &parsed_bitrate, &parsed_fraction_loss, &parsed_total_packets); + EXPECT_EQ(bitrate, parsed_bitrate); + EXPECT_EQ(fraction_loss, parsed_fraction_loss); + EXPECT_EQ(total_packets, parsed_total_packets); +} + +void RtcEventLogTestHelper::VerifyLogStartEvent( + const ParsedRtcEventLog& parsed_log, + size_t index) { + const rtclog::Event& event = parsed_log.stream_[index]; + ASSERT_TRUE(IsValidBasicEvent(event)); + EXPECT_EQ(rtclog::Event::LOG_START, event.type()); +} + +void RtcEventLogTestHelper::VerifyLogEndEvent( + const ParsedRtcEventLog& parsed_log, + size_t index) { + const rtclog::Event& event = parsed_log.stream_[index]; + ASSERT_TRUE(IsValidBasicEvent(event)); + EXPECT_EQ(rtclog::Event::LOG_END, event.type()); +} + +} // namespace webrtc + +#endif // ENABLE_RTC_EVENT_LOG diff --git a/webrtc/call/rtc_event_log_unittest_helper.h b/webrtc/call/rtc_event_log_unittest_helper.h new file mode 100644 index 0000000000..b662c3ccc3 --- /dev/null +++ b/webrtc/call/rtc_event_log_unittest_helper.h @@ -0,0 +1,58 @@ +/* + * 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. + */ + +#ifndef WEBRTC_CALL_RTC_EVENT_LOG_UNITTEST_HELPER_H_ +#define WEBRTC_CALL_RTC_EVENT_LOG_UNITTEST_HELPER_H_ + +#include "webrtc/call.h" +#include "webrtc/call/rtc_event_log_parser.h" + +namespace webrtc { + +class RtcEventLogTestHelper { + public: + static void VerifyReceiveStreamConfig( + const ParsedRtcEventLog& parsed_log, + size_t index, + const VideoReceiveStream::Config& config); + static void VerifySendStreamConfig(const ParsedRtcEventLog& parsed_log, + size_t index, + const VideoSendStream::Config& config); + static void VerifyRtpEvent(const ParsedRtcEventLog& parsed_log, + size_t index, + PacketDirection direction, + MediaType media_type, + const uint8_t* header, + size_t header_size, + size_t total_size); + static void VerifyRtcpEvent(const ParsedRtcEventLog& parsed_log, + size_t index, + PacketDirection direction, + MediaType media_type, + const uint8_t* packet, + size_t total_size); + static void VerifyPlayoutEvent(const ParsedRtcEventLog& parsed_log, + size_t index, + uint32_t ssrc); + static void VerifyBweLossEvent(const ParsedRtcEventLog& parsed_log, + size_t index, + int32_t bitrate, + uint8_t fraction_loss, + int32_t total_packets); + + static void VerifyLogStartEvent(const ParsedRtcEventLog& parsed_log, + size_t index); + static void VerifyLogEndEvent(const ParsedRtcEventLog& parsed_log, + size_t index); +}; + +} // namespace webrtc + +#endif // WEBRTC_CALL_RTC_EVENT_LOG_UNITTEST_HELPER_H_ diff --git a/webrtc/modules/audio_coding/neteq/neteq_tests.gypi b/webrtc/modules/audio_coding/neteq/neteq_tests.gypi index f02d3deee9..bb316e8a81 100644 --- a/webrtc/modules/audio_coding/neteq/neteq_tests.gypi +++ b/webrtc/modules/audio_coding/neteq/neteq_tests.gypi @@ -14,9 +14,12 @@ 'target_name': 'rtc_event_log_source', 'type': 'static_library', 'dependencies': [ - '<(webrtc_root)/webrtc.gyp:rtc_event_log', + '<(webrtc_root)/webrtc.gyp:rtc_event_log_parser', '<(webrtc_root)/webrtc.gyp:rtc_event_log_proto', ], + 'export_dependent_settings': [ + '<(webrtc_root)/webrtc.gyp:rtc_event_log_parser', + ], 'sources': [ 'tools/rtc_event_log_source.h', 'tools/rtc_event_log_source.cc', diff --git a/webrtc/modules/audio_coding/neteq/tools/rtc_event_log_source.cc b/webrtc/modules/audio_coding/neteq/tools/rtc_event_log_source.cc index dad72eaecd..9192839be3 100644 --- a/webrtc/modules/audio_coding/neteq/tools/rtc_event_log_source.cc +++ b/webrtc/modules/audio_coding/neteq/tools/rtc_event_log_source.cc @@ -16,51 +16,15 @@ #include #include "webrtc/base/checks.h" +#include "webrtc/call.h" #include "webrtc/call/rtc_event_log.h" #include "webrtc/modules/audio_coding/neteq/tools/packet.h" #include "webrtc/modules/rtp_rtcp/include/rtp_header_parser.h" -// Files generated at build-time by the protobuf compiler. -#ifdef WEBRTC_ANDROID_PLATFORM_BUILD -#include "external/webrtc/webrtc/call/rtc_event_log.pb.h" -#else -#include "webrtc/call/rtc_event_log.pb.h" -#endif namespace webrtc { namespace test { -namespace { - -const rtclog::RtpPacket* GetRtpPacket(const rtclog::Event& event) { - if (!event.has_type() || event.type() != rtclog::Event::RTP_EVENT) - return nullptr; - if (!event.has_timestamp_us() || !event.has_rtp_packet()) - return nullptr; - const rtclog::RtpPacket& rtp_packet = event.rtp_packet(); - if (!rtp_packet.has_type() || rtp_packet.type() != rtclog::AUDIO || - !rtp_packet.has_incoming() || !rtp_packet.incoming() || - !rtp_packet.has_packet_length() || rtp_packet.packet_length() == 0 || - !rtp_packet.has_header() || rtp_packet.header().size() == 0 || - rtp_packet.packet_length() < rtp_packet.header().size()) - return nullptr; - return &rtp_packet; -} - -const rtclog::AudioPlayoutEvent* GetAudioPlayoutEvent( - const rtclog::Event& event) { - if (!event.has_type() || event.type() != rtclog::Event::AUDIO_PLAYOUT_EVENT) - return nullptr; - if (!event.has_timestamp_us() || !event.has_audio_playout_event()) - return nullptr; - const rtclog::AudioPlayoutEvent& playout_event = event.audio_playout_event(); - if (!playout_event.has_local_ssrc()) - return nullptr; - return &playout_event; -} - -} // namespace - RtcEventLogSource* RtcEventLogSource::Create(const std::string& file_name) { RtcEventLogSource* source = new RtcEventLogSource(); RTC_CHECK(source->OpenFile(file_name)); @@ -76,42 +40,57 @@ bool RtcEventLogSource::RegisterRtpHeaderExtension(RTPExtensionType type, } Packet* RtcEventLogSource::NextPacket() { - while (rtp_packet_index_ < event_log_->stream_size()) { - const rtclog::Event& event = event_log_->stream(rtp_packet_index_); - const rtclog::RtpPacket* rtp_packet = GetRtpPacket(event); - rtp_packet_index_++; - if (rtp_packet) { - uint8_t* packet_header = new uint8_t[rtp_packet->header().size()]; - memcpy(packet_header, rtp_packet->header().data(), - rtp_packet->header().size()); - Packet* packet = new Packet(packet_header, rtp_packet->header().size(), - rtp_packet->packet_length(), - event.timestamp_us() / 1000, *parser_.get()); - if (packet->valid_header()) { - // Check if the packet should not be filtered out. - if (!filter_.test(packet->header().payloadType) && - !(use_ssrc_filter_ && packet->header().ssrc != ssrc_)) - return packet; - } else { - std::cout << "Warning: Packet with index " << (rtp_packet_index_ - 1) - << " has an invalid header and will be ignored." << std::endl; + while (rtp_packet_index_ < parsed_stream_.GetNumberOfEvents()) { + if (parsed_stream_.GetEventType(rtp_packet_index_) == + ParsedRtcEventLog::RTP_EVENT) { + PacketDirection direction; + MediaType media_type; + size_t header_length; + size_t packet_length; + uint64_t timestamp_us = parsed_stream_.GetTimestamp(rtp_packet_index_); + parsed_stream_.GetRtpHeader(rtp_packet_index_, &direction, &media_type, + nullptr, &header_length, &packet_length); + if (direction == kIncomingPacket && media_type == MediaType::AUDIO) { + uint8_t* packet_header = new uint8_t[header_length]; + parsed_stream_.GetRtpHeader(rtp_packet_index_, nullptr, nullptr, + packet_header, nullptr, nullptr); + Packet* packet = new Packet(packet_header, header_length, packet_length, + static_cast(timestamp_us) / 1000, + *parser_.get()); + if (packet->valid_header()) { + // Check if the packet should not be filtered out. + if (!filter_.test(packet->header().payloadType) && + !(use_ssrc_filter_ && packet->header().ssrc != ssrc_)) { + rtp_packet_index_++; + return packet; + } + } else { + std::cout << "Warning: Packet with index " << rtp_packet_index_ + << " has an invalid header and will be ignored." + << std::endl; + } + // The packet has either an invalid header or needs to be filtered out, + // so it can be deleted. + delete packet; } - // The packet has either an invalid header or needs to be filtered out, so - // it can be deleted. - delete packet; } + rtp_packet_index_++; } return nullptr; } int64_t RtcEventLogSource::NextAudioOutputEventMs() { - while (audio_output_index_ < event_log_->stream_size()) { - const rtclog::Event& event = event_log_->stream(audio_output_index_); - const rtclog::AudioPlayoutEvent* playout_event = - GetAudioPlayoutEvent(event); + while (audio_output_index_ < parsed_stream_.GetNumberOfEvents()) { + if (parsed_stream_.GetEventType(audio_output_index_) == + ParsedRtcEventLog::AUDIO_PLAYOUT_EVENT) { + uint64_t timestamp_us = parsed_stream_.GetTimestamp(audio_output_index_); + // We call GetAudioPlayout only to check that the protobuf event is + // well-formed. + parsed_stream_.GetAudioPlayout(audio_output_index_, nullptr); + audio_output_index_++; + return timestamp_us / 1000; + } audio_output_index_++; - if (playout_event) - return event.timestamp_us() / 1000; } return std::numeric_limits::max(); } @@ -120,8 +99,7 @@ RtcEventLogSource::RtcEventLogSource() : PacketSource(), parser_(RtpHeaderParser::Create()) {} bool RtcEventLogSource::OpenFile(const std::string& file_name) { - event_log_.reset(new rtclog::EventStream()); - return RtcEventLog::ParseRtcEventLog(file_name, event_log_.get()); + return parsed_stream_.ParseFile(file_name); } } // namespace test diff --git a/webrtc/modules/audio_coding/neteq/tools/rtc_event_log_source.h b/webrtc/modules/audio_coding/neteq/tools/rtc_event_log_source.h index 312338ee08..ad7add154c 100644 --- a/webrtc/modules/audio_coding/neteq/tools/rtc_event_log_source.h +++ b/webrtc/modules/audio_coding/neteq/tools/rtc_event_log_source.h @@ -15,6 +15,7 @@ #include #include "webrtc/base/constructormagic.h" +#include "webrtc/call/rtc_event_log_parser.h" #include "webrtc/modules/audio_coding/neteq/tools/packet_source.h" #include "webrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h" @@ -22,10 +23,6 @@ namespace webrtc { class RtpHeaderParser; -namespace rtclog { -class EventStream; -} // namespace rtclog - namespace test { class Packet; @@ -55,10 +52,10 @@ class RtcEventLogSource : public PacketSource { bool OpenFile(const std::string& file_name); - int rtp_packet_index_ = 0; - int audio_output_index_ = 0; + size_t rtp_packet_index_ = 0; + size_t audio_output_index_ = 0; - std::unique_ptr event_log_; + ParsedRtcEventLog parsed_stream_; std::unique_ptr parser_; RTC_DISALLOW_COPY_AND_ASSIGN(RtcEventLogSource); diff --git a/webrtc/webrtc.gyp b/webrtc/webrtc.gyp index 21387448ef..793bf96335 100644 --- a/webrtc/webrtc.gyp +++ b/webrtc/webrtc.gyp @@ -67,6 +67,24 @@ }, ], }], + ['enable_protobuf==1', { + 'targets': [ + { + 'target_name': 'rtc_event_log_parser', + 'type': 'static_library', + 'sources': [ + 'call/rtc_event_log_parser.cc', + 'call/rtc_event_log_parser.h', + ], + 'dependencies': [ + 'rtc_event_log_proto', + ], + 'export_dependent_settings': [ + 'rtc_event_log_proto', + ], + }, + ], + }], ['include_tests==1 and enable_protobuf==1', { 'targets': [ { @@ -75,7 +93,7 @@ 'sources': ['call/rtc_event_log2rtp_dump.cc',], 'dependencies': [ '<(DEPTH)/third_party/gflags/gflags.gyp:gflags', - 'rtc_event_log', + 'rtc_event_log_parser', 'rtc_event_log_proto', 'test/test.gyp:rtp_test_utils' ], diff --git a/webrtc/webrtc_tests.gypi b/webrtc/webrtc_tests.gypi index 8c3512a7c8..ae31c6c33c 100644 --- a/webrtc/webrtc_tests.gypi +++ b/webrtc/webrtc_tests.gypi @@ -213,10 +213,12 @@ ], 'dependencies': [ 'webrtc.gyp:rtc_event_log', + 'webrtc.gyp:rtc_event_log_parser', 'webrtc.gyp:rtc_event_log_proto', ], 'sources': [ 'call/rtc_event_log_unittest.cc', + 'call/rtc_event_log_unittest_helper.cc' ], }], ],