/* * 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 #include #include #include #include #include #include #include "absl/flags/flag.h" #include "absl/flags/parse.h" #include "absl/flags/usage.h" #include "absl/memory/memory.h" #include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "api/array_view.h" #include "api/rtc_event_log/rtc_event_log.h" #include "api/rtp_headers.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 "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. absl::optional ParseSsrc(absl::string_view str) { // If the input string starts with 0x or 0X it indicates a hexadecimal number. uint32_t ssrc; auto read_mode = std::dec; if (str.size() > 2 && (str.substr(0, 2) == "0x" || str.substr(0, 2) == "0X")) { read_mode = std::hex; str = str.substr(2); } std::stringstream ss(std::string{str}); ss >> read_mode >> ssrc; if (str.empty() || (!ss.fail() && ss.eof())) return ssrc; return absl::nullopt; } bool ShouldSkipStream(MediaType media_type, uint32_t ssrc, absl::optional 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(rtc::ArrayView( incoming.rtp.header.arrOfCSRCs, incoming.rtp.header.numCSRCs)); } // Set extensions. if (incoming.rtp.header.extension.hasTransmissionTimeOffset) reconstructed_packet.SetExtension( incoming.rtp.header.extension.transmissionTimeOffset); if (incoming.rtp.header.extension.hasAbsoluteSendTime) reconstructed_packet.SetExtension( incoming.rtp.header.extension.absoluteSendTime); if (incoming.rtp.header.extension.hasTransportSequenceNumber) reconstructed_packet.SetExtension( incoming.rtp.header.extension.transportSequenceNumber); if (incoming.rtp.header.extension.hasAudioLevel) reconstructed_packet.SetExtension( incoming.rtp.header.extension.voiceActivity, incoming.rtp.header.extension.audioLevel); if (incoming.rtp.header.extension.hasVideoRotation) reconstructed_packet.SetExtension( incoming.rtp.header.extension.videoRotation); if (incoming.rtp.header.extension.hasVideoContentType) reconstructed_packet.SetExtension( incoming.rtp.header.extension.videoContentType); if (incoming.rtp.header.extension.has_video_timing) reconstructed_packet.SetExtension( 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 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]; absl::optional 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 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; }