| /* | |
| * Copyright (c) 2015 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 <cstdint> | |
| #include <cstring> | |
| #include <iostream> | |
| #include <memory> | |
| #include <optional> | |
| #include <span> | |
| #include <string> | |
| #include <vector> | |
| #include "absl/flags/flag.h" | |
| #include "absl/flags/parse.h" | |
| #include "absl/flags/usage.h" | |
| #include "absl/strings/string_view.h" | |
| #include "api/rtp_headers.h" | |
| #include "logging/rtc_event_log/events/logged_rtp_rtcp.h" | |
| #include "logging/rtc_event_log/rtc_event_log_parser.h" | |
| #include "logging/rtc_event_log/rtc_event_processor.h" | |
| #include "modules/rtp_rtcp/include/rtp_header_extension_map.h" | |
| #include "modules/rtp_rtcp/source/rtp_header_extensions.h" | |
| #include "modules/rtp_rtcp/source/rtp_packet.h" | |
| #include "rtc_base/checks.h" | |
| #include "rtc_base/string_to_number.h" | |
| #include "test/rtp_file_reader.h" | |
| #include "test/rtp_file_writer.h" | |
| ABSL_FLAG( | |
| bool, | |
| audio, | |
| true, | |
| "Use --noaudio to exclude audio packets from the converted RTPdump file."); | |
| ABSL_FLAG( | |
| bool, | |
| video, | |
| true, | |
| "Use --novideo to exclude video packets from the converted RTPdump file."); | |
| ABSL_FLAG( | |
| bool, | |
| data, | |
| true, | |
| "Use --nodata to exclude data packets from the converted RTPdump file."); | |
| ABSL_FLAG( | |
| bool, | |
| rtp, | |
| true, | |
| "Use --nortp to exclude RTP packets from the converted RTPdump file."); | |
| ABSL_FLAG( | |
| bool, | |
| rtcp, | |
| true, | |
| "Use --nortcp to exclude RTCP packets from the converted RTPdump file."); | |
| ABSL_FLAG(std::string, | |
| ssrc, | |
| "", | |
| "Store only packets with this SSRC (decimal or hex, the latter " | |
| "starting with 0x)."); | |
| namespace { | |
| using MediaType = webrtc::ParsedRtcEventLog::MediaType; | |
| // Parses the input string for a valid SSRC. If a valid SSRC is found, it is | |
| // written to the output variable `ssrc`, and true is returned. Otherwise, | |
| // false is returned. | |
| // The empty string must be validated as true, because it is the default value | |
| // of the command-line flag. In this case, no value is written to the output | |
| // variable. | |
| std::optional<uint32_t> ParseSsrc(absl::string_view str) { | |
| // Set `base` to 0 to allow detection of the "0x" prefix in case hex is used. | |
| return webrtc::StringToNumber<uint32_t>(str, 0); | |
| } | |
| bool ShouldSkipStream(MediaType media_type, | |
| uint32_t ssrc, | |
| std::optional<uint32_t> ssrc_filter) { | |
| if (!absl::GetFlag(FLAGS_audio) && media_type == MediaType::AUDIO) | |
| return true; | |
| if (!absl::GetFlag(FLAGS_video) && media_type == MediaType::VIDEO) | |
| return true; | |
| if (!absl::GetFlag(FLAGS_data) && media_type == MediaType::DATA) | |
| return true; | |
| if (ssrc_filter.has_value() && ssrc != *ssrc_filter) | |
| return true; | |
| return false; | |
| } | |
| // Convert a LoggedRtpPacketIncoming to a test::RtpPacket. Header extension IDs | |
| // are allocated according to the provided extension map. This might not match | |
| // the extension map used in the actual call. | |
| void ConvertRtpPacket( | |
| const webrtc::LoggedRtpPacketIncoming& incoming, | |
| const webrtc::RtpHeaderExtensionMap& default_extension_map, | |
| webrtc::test::RtpPacket* packet) { | |
| webrtc::RtpPacket reconstructed_packet(&default_extension_map); | |
| reconstructed_packet.SetMarker(incoming.rtp.header.markerBit); | |
| reconstructed_packet.SetPayloadType(incoming.rtp.header.payloadType); | |
| reconstructed_packet.SetSequenceNumber(incoming.rtp.header.sequenceNumber); | |
| reconstructed_packet.SetTimestamp(incoming.rtp.header.timestamp); | |
| reconstructed_packet.SetSsrc(incoming.rtp.header.ssrc); | |
| if (incoming.rtp.header.numCSRCs > 0) { | |
| reconstructed_packet.SetCsrcs(std::span<const uint32_t>( | |
| incoming.rtp.header.arrOfCSRCs, incoming.rtp.header.numCSRCs)); | |
| } | |
| // Set extensions. | |
| if (incoming.rtp.header.extension.hasTransmissionTimeOffset) | |
| reconstructed_packet.SetExtension<webrtc::TransmissionOffset>( | |
| incoming.rtp.header.extension.transmissionTimeOffset); | |
| if (incoming.rtp.header.extension.hasAbsoluteSendTime) | |
| reconstructed_packet.SetExtension<webrtc::AbsoluteSendTime>( | |
| incoming.rtp.header.extension.absoluteSendTime); | |
| if (incoming.rtp.header.extension.hasTransportSequenceNumber) | |
| reconstructed_packet.SetExtension<webrtc::TransportSequenceNumber>( | |
| incoming.rtp.header.extension.transportSequenceNumber); | |
| if (incoming.rtp.header.extension.audio_level()) | |
| reconstructed_packet.SetExtension<webrtc::AudioLevelExtension>( | |
| *incoming.rtp.header.extension.audio_level()); | |
| if (incoming.rtp.header.extension.hasVideoRotation) | |
| reconstructed_packet.SetExtension<webrtc::VideoOrientation>( | |
| incoming.rtp.header.extension.videoRotation); | |
| if (incoming.rtp.header.extension.hasVideoContentType) | |
| reconstructed_packet.SetExtension<webrtc::VideoContentTypeExtension>( | |
| incoming.rtp.header.extension.videoContentType); | |
| if (incoming.rtp.header.extension.has_video_timing) | |
| reconstructed_packet.SetExtension<webrtc::VideoTimingExtension>( | |
| incoming.rtp.header.extension.video_timing); | |
| RTC_DCHECK_EQ(reconstructed_packet.size(), incoming.rtp.header_length); | |
| RTC_DCHECK_EQ(reconstructed_packet.headers_size(), | |
| incoming.rtp.header_length); | |
| memcpy(packet->data, reconstructed_packet.data(), | |
| reconstructed_packet.headers_size()); | |
| packet->length = reconstructed_packet.headers_size(); | |
| packet->original_length = incoming.rtp.total_length; | |
| packet->time_ms = incoming.log_time_ms(); | |
| // Set padding bit. | |
| if (incoming.rtp.header.paddingLength > 0) | |
| packet->data[0] = packet->data[0] | 0x20; | |
| } | |
| } // namespace | |
| // This utility will convert a stored event log to the rtpdump format. | |
| int main(int argc, char* argv[]) { | |
| absl::SetProgramUsageMessage( | |
| "Tool for converting an RtcEventLog file to an " | |
| "RTP dump file.\n" | |
| "Example usage:\n" | |
| "./rtc_event_log2rtp_dump input.rel output.rtp\n"); | |
| std::vector<char*> args = absl::ParseCommandLine(argc, argv); | |
| if (args.size() != 3) { | |
| std::cout << absl::ProgramUsageMessage(); | |
| return 1; | |
| } | |
| std::string input_file = args[1]; | |
| std::string output_file = args[2]; | |
| std::optional<uint32_t> ssrc_filter; | |
| if (!absl::GetFlag(FLAGS_ssrc).empty()) { | |
| ssrc_filter = ParseSsrc(absl::GetFlag(FLAGS_ssrc)); | |
| RTC_CHECK(ssrc_filter.has_value()) << "Failed to read SSRC filter flag."; | |
| } | |
| webrtc::ParsedRtcEventLog parsed_stream; | |
| auto status = parsed_stream.ParseFile(input_file); | |
| if (!status.ok()) { | |
| std::cerr << "Failed to parse event log " << input_file << ": " | |
| << status.message() << std::endl; | |
| return -1; | |
| } | |
| std::unique_ptr<webrtc::test::RtpFileWriter> rtp_writer( | |
| webrtc::test::RtpFileWriter::Create( | |
| webrtc::test::RtpFileWriter::FileFormat::kRtpDump, output_file)); | |
| if (!rtp_writer) { | |
| std::cerr << "Error while opening output file: " << output_file | |
| << std::endl; | |
| return -1; | |
| } | |
| int rtp_counter = 0, rtcp_counter = 0; | |
| bool header_only = false; | |
| webrtc::RtpHeaderExtensionMap default_extension_map = | |
| webrtc::ParsedRtcEventLog::GetDefaultHeaderExtensionMap(); | |
| auto handle_rtp = [&default_extension_map, &rtp_writer, &rtp_counter]( | |
| const webrtc::LoggedRtpPacketIncoming& incoming) { | |
| webrtc::test::RtpPacket packet; | |
| ConvertRtpPacket(incoming, default_extension_map, &packet); | |
| rtp_writer->WritePacket(&packet); | |
| rtp_counter++; | |
| }; | |
| auto handle_rtcp = [&rtp_writer, &rtcp_counter]( | |
| const webrtc::LoggedRtcpPacketIncoming& incoming) { | |
| webrtc::test::RtpPacket packet; | |
| memcpy(packet.data, incoming.rtcp.raw_data.data(), | |
| incoming.rtcp.raw_data.size()); | |
| packet.length = incoming.rtcp.raw_data.size(); | |
| // For RTCP packets the original_length should be set to 0 in the | |
| // RTPdump format. | |
| packet.original_length = 0; | |
| packet.time_ms = incoming.log_time_ms(); | |
| rtp_writer->WritePacket(&packet); | |
| rtcp_counter++; | |
| }; | |
| webrtc::RtcEventProcessor event_processor; | |
| for (const auto& stream : parsed_stream.incoming_rtp_packets_by_ssrc()) { | |
| MediaType media_type = | |
| parsed_stream.GetMediaType(stream.ssrc, webrtc::kIncomingPacket); | |
| if (ShouldSkipStream(media_type, stream.ssrc, ssrc_filter)) | |
| continue; | |
| event_processor.AddEvents(stream.incoming_packets, handle_rtp); | |
| } | |
| // Note that `packet_ssrc` is the sender SSRC. An RTCP message may contain | |
| // report blocks for many streams, thus several SSRCs and they don't | |
| // necessarily have to be of the same media type. We therefore don't | |
| // support filtering of RTCP based on SSRC and media type. | |
| event_processor.AddEvents(parsed_stream.incoming_rtcp_packets(), handle_rtcp); | |
| event_processor.ProcessEventsInOrder(); | |
| std::cout << "Wrote " << rtp_counter << (header_only ? " header-only" : "") | |
| << " RTP packets and " << rtcp_counter | |
| << " RTCP packets to the " | |
| "output file." | |
| << std::endl; | |
| return 0; | |
| } |