| /* |
| * 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 <stdint.h> |
| #include <string.h> |
| |
| #include <iostream> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/flags/flag.h" |
| #include "absl/flags/parse.h" |
| #include "absl/flags/usage.h" |
| #include "absl/memory/memory.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<uint32_t> ParseSsrc(std::string 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(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<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(rtc::ArrayView<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.hasAudioLevel) |
| reconstructed_packet.SetExtension<webrtc::AudioLevel>( |
| incoming.rtp.header.extension.voiceActivity, |
| incoming.rtp.header.extension.audioLevel); |
| 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]; |
| |
| absl::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; |
| } |