diff --git a/video/BUILD.gn b/video/BUILD.gn index d6e529fb3a..a25b42d85e 100644 --- a/video/BUILD.gn +++ b/video/BUILD.gn @@ -358,6 +358,7 @@ if (rtc_include_tests) { "../modules/rtp_rtcp", "../rtc_base:checks", "../rtc_base:rtc_base_approved", + "../rtc_base:rtc_json", "../system_wrappers", "../system_wrappers:metrics_default", "../system_wrappers:runtime_enabled_features_default", diff --git a/video/replay.cc b/video/replay.cc index 15920d9d9a..44cefd1077 100644 --- a/video/replay.cc +++ b/video/replay.cc @@ -10,6 +10,7 @@ #include +#include #include #include #include @@ -20,7 +21,9 @@ #include "logging/rtc_event_log/rtc_event_log.h" #include "modules/rtp_rtcp/include/rtp_header_parser.h" #include "rtc_base/checks.h" +#include "rtc_base/file.h" #include "rtc_base/flags.h" +#include "rtc_base/json.h" #include "rtc_base/string_to_number.h" #include "rtc_base/timeutils.h" #include "system_wrappers/include/clock.h" @@ -145,6 +148,11 @@ static std::string InputFile() { return static_cast(FLAG_input_file); } +DEFINE_string(config_file, "", "config file"); +static std::string ConfigFile() { + return static_cast(FLAG_config_file); +} + // Flag for raw output files. DEFINE_string(out_base, "", "Basename (excluding .jpg) for raw output"); static std::string OutBase() { @@ -224,132 +232,291 @@ class DecoderBitstreamFileWriter : public test::FakeDecoder { FILE* file_; }; -void RtpReplay() { - std::stringstream window_title; - window_title << "Playback Video (" << flags::InputFile() << ")"; - std::unique_ptr playback_video( - test::VideoRenderer::Create(window_title.str().c_str(), 640, 480)); - FileRenderPassthrough file_passthrough(flags::OutBase(), - playback_video.get()); +// Deserializes a JSON representation of the VideoReceiveStream::Config back +// into a valid object. This will not initialize the decoders or the renderer. +class VideoReceiveStreamConfigDeserializer final { + public: + static VideoReceiveStream::Config Deserialize(webrtc::Transport* transport, + const Json::Value& json) { + auto receive_config = VideoReceiveStream::Config(transport); + for (const auto decoder_json : json["decoders"]) { + VideoReceiveStream::Decoder decoder; + decoder.payload_name = decoder_json["payload_name"].asString(); + decoder.payload_type = decoder_json["payload_type"].asInt64(); + for (const auto& params_json : decoder_json["codec_params"]) { + std::vector members = params_json.getMemberNames(); + RTC_CHECK_EQ(members.size(), 1); + decoder.codec_params[members[0]] = params_json[members[0]].asString(); + } + receive_config.decoders.push_back(decoder); + } + receive_config.render_delay_ms = json["render_delay_ms"].asInt64(); + receive_config.target_delay_ms = json["target_delay_ms"].asInt64(); + receive_config.rtp.remote_ssrc = json["remote_ssrc"].asInt64(); + receive_config.rtp.local_ssrc = json["local_ssrc"].asInt64(); + receive_config.rtp.rtcp_mode = + json["rtcp_mode"].asString() == "RtcpMode::kCompound" + ? RtcpMode::kCompound + : RtcpMode::kReducedSize; + receive_config.rtp.remb = json["remb"].asBool(); + receive_config.rtp.transport_cc = json["transport_cc"].asBool(); + receive_config.rtp.nack.rtp_history_ms = + json["nack"]["rtp_history_ms"].asInt64(); + receive_config.rtp.ulpfec_payload_type = + json["ulpfec_payload_type"].asInt64(); + receive_config.rtp.red_payload_type = json["red_payload_type"].asInt64(); + receive_config.rtp.rtx_ssrc = json["rtx_ssrc"].asInt64(); - webrtc::RtcEventLogNullImpl event_log; - std::unique_ptr call(Call::Create(Call::Config(&event_log))); - - test::NullTransport transport; - VideoReceiveStream::Config receive_config(&transport); - receive_config.rtp.remote_ssrc = flags::Ssrc(); - receive_config.rtp.local_ssrc = kReceiverLocalSsrc; - receive_config.rtp.rtx_ssrc = flags::SsrcRtx(); - receive_config.rtp - .rtx_associated_payload_types[flags::MediaPayloadTypeRtx()] = - flags::MediaPayloadType(); - receive_config.rtp.rtx_associated_payload_types[flags::RedPayloadTypeRtx()] = - flags::RedPayloadType(); - receive_config.rtp.ulpfec_payload_type = flags::UlpfecPayloadType(); - receive_config.rtp.red_payload_type = flags::RedPayloadType(); - receive_config.rtp.nack.rtp_history_ms = 1000; - if (flags::TransmissionOffsetId() != -1) { - receive_config.rtp.extensions.push_back(RtpExtension( - RtpExtension::kTimestampOffsetUri, flags::TransmissionOffsetId())); + for (const auto& pl_json : json["rtx_payload_types"]) { + std::vector members = pl_json.getMemberNames(); + RTC_CHECK_EQ(members.size(), 1); + Json::Value rtx_payload_type = pl_json[members[0]]; + receive_config.rtp.rtx_associated_payload_types[std::stoi(members[0])] = + rtx_payload_type.asInt64(); + } + for (const auto& ext_json : json["extensions"]) { + receive_config.rtp.extensions.emplace_back(ext_json["uri"].asString(), + ext_json["id"].asInt64(), + ext_json["encrypt"].asBool()); + } + return receive_config; } - if (flags::AbsSendTimeId() != -1) { - receive_config.rtp.extensions.push_back( - RtpExtension(RtpExtension::kAbsSendTimeUri, flags::AbsSendTimeId())); +}; + +// The RtpReplayer is responsible for parsing the configuration provided by the +// user, setting up the windows, recieve streams and decoders and then replaying +// the provided RTP dump. +class RtpReplayer final { + public: + // Replay a rtp dump with an optional json configuration. + static void Replay(const std::string& replay_config_path, + const std::string& rtp_dump_path) { + webrtc::RtcEventLogNullImpl event_log; + Call::Config call_config(&event_log); + std::unique_ptr call(Call::Create(std::move(call_config))); + std::unique_ptr stream_state; + // Attempt to load the configuration + if (replay_config_path.empty()) { + stream_state = ConfigureFromFlags(rtp_dump_path, call.get()); + } else { + stream_state = ConfigureFromFile(replay_config_path, call.get()); + } + if (stream_state == nullptr) { + return; + } + // Attempt to create an RtpReader from the input file. + std::unique_ptr rtp_reader = + CreateRtpReader(rtp_dump_path); + if (rtp_reader == nullptr) { + return; + } + // Start replaying the provided stream now that it has been configured. + for (const auto& receive_stream : stream_state->receive_streams) { + receive_stream->Start(); + } + ReplayPackets(call.get(), rtp_reader.get()); + for (const auto& receive_stream : stream_state->receive_streams) { + call->DestroyVideoReceiveStream(receive_stream); + } } - receive_config.renderer = &file_passthrough; - VideoReceiveStream::Decoder decoder; - decoder = - test::CreateMatchingDecoder(flags::MediaPayloadType(), flags::Codec()); - if (!flags::DecoderBitstreamFilename().empty()) { - // Replace decoder with file writer if we're writing the bitstream to a file - // instead. - delete decoder.decoder; - decoder.decoder = new DecoderBitstreamFileWriter( - flags::DecoderBitstreamFilename().c_str()); + private: + // Holds all the shared memory structures required for a recieve stream. This + // structure is used to prevent members being deallocated before the replay + // has been finished. + struct StreamState { + test::NullTransport transport; + std::vector>> sinks; + std::vector receive_streams; + }; + + // Loads multiple configurations from the provided configuration file. + static std::unique_ptr ConfigureFromFile( + const std::string& config_path, + Call* call) { + auto stream_state = absl::make_unique(); + // Parse the configuration file. + std::ifstream config_file(config_path); + std::stringstream raw_json_buffer; + raw_json_buffer << config_file.rdbuf(); + std::string raw_json = raw_json_buffer.str(); + Json::Reader json_reader; + Json::Value json_configs; + if (!json_reader.parse(raw_json, json_configs)) { + fprintf(stderr, "Error parsing JSON config\n"); + fprintf(stderr, "%s\n", json_reader.getFormatedErrorMessages().c_str()); + return nullptr; + } + + size_t config_count = 0; + for (const auto& json : json_configs) { + // Create the configuration and parse the JSON into the config. + auto receive_config = VideoReceiveStreamConfigDeserializer::Deserialize( + &(stream_state->transport), json); + // Instantiate the underlying decoder. + for (auto& decoder : receive_config.decoders) { + decoder.decoder = test::CreateMatchingDecoder(decoder.payload_type, + decoder.payload_name) + .decoder; + } + // Create a window for this config. + std::stringstream window_title; + window_title << "Playback Video (" << config_count++ << ")"; + stream_state->sinks.emplace_back( + test::VideoRenderer::Create(window_title.str().c_str(), 640, 480)); + // Create a receive stream for this config. + receive_config.renderer = stream_state->sinks.back().get(); + stream_state->receive_streams.emplace_back( + call->CreateVideoReceiveStream(std::move(receive_config))); + } + return stream_state; } - receive_config.decoders.push_back(decoder); - VideoReceiveStream* receive_stream = - call->CreateVideoReceiveStream(std::move(receive_config)); + // Loads the base configuration from flags passed in on the commandline. + static std::unique_ptr ConfigureFromFlags( + const std::string& rtp_dump_path, + Call* call) { + auto stream_state = absl::make_unique(); + // Create the video renderers. We must add both to the stream state to keep + // them from deallocating. + std::stringstream window_title; + window_title << "Playback Video (" << rtp_dump_path << ")"; + std::unique_ptr playback_video( + test::VideoRenderer::Create(window_title.str().c_str(), 640, 480)); + auto file_passthrough = absl::make_unique( + flags::OutBase(), playback_video.get()); + stream_state->sinks.push_back(std::move(playback_video)); + stream_state->sinks.push_back(std::move(file_passthrough)); + // Setup the configuration from the flags. + VideoReceiveStream::Config receive_config(&(stream_state->transport)); + receive_config.rtp.remote_ssrc = flags::Ssrc(); + receive_config.rtp.local_ssrc = kReceiverLocalSsrc; + receive_config.rtp.rtx_ssrc = flags::SsrcRtx(); + receive_config.rtp + .rtx_associated_payload_types[flags::MediaPayloadTypeRtx()] = + flags::MediaPayloadType(); + receive_config.rtp + .rtx_associated_payload_types[flags::RedPayloadTypeRtx()] = + flags::RedPayloadType(); + receive_config.rtp.ulpfec_payload_type = flags::UlpfecPayloadType(); + receive_config.rtp.red_payload_type = flags::RedPayloadType(); + receive_config.rtp.nack.rtp_history_ms = 1000; + if (flags::TransmissionOffsetId() != -1) { + receive_config.rtp.extensions.push_back(RtpExtension( + RtpExtension::kTimestampOffsetUri, flags::TransmissionOffsetId())); + } + if (flags::AbsSendTimeId() != -1) { + receive_config.rtp.extensions.push_back( + RtpExtension(RtpExtension::kAbsSendTimeUri, flags::AbsSendTimeId())); + } + receive_config.renderer = stream_state->sinks.back().get(); - std::unique_ptr rtp_reader(test::RtpFileReader::Create( - test::RtpFileReader::kRtpDump, flags::InputFile())); - if (!rtp_reader) { - rtp_reader.reset(test::RtpFileReader::Create(test::RtpFileReader::kPcap, - flags::InputFile())); + // Setup the receiving stream + VideoReceiveStream::Decoder decoder; + decoder = + test::CreateMatchingDecoder(flags::MediaPayloadType(), flags::Codec()); + if (!flags::DecoderBitstreamFilename().empty()) { + // Replace decoder with file writer if we're writing the bitstream to a + // file instead. + delete decoder.decoder; + decoder.decoder = new DecoderBitstreamFileWriter( + flags::DecoderBitstreamFilename().c_str()); + } + receive_config.decoders.push_back(decoder); + + stream_state->receive_streams.emplace_back( + call->CreateVideoReceiveStream(std::move(receive_config))); + return stream_state; + } + + static std::unique_ptr CreateRtpReader( + const std::string& rtp_dump_path) { + std::unique_ptr rtp_reader(test::RtpFileReader::Create( + test::RtpFileReader::kRtpDump, rtp_dump_path)); if (!rtp_reader) { - fprintf(stderr, - "Couldn't open input file as either a rtpdump or .pcap. Note " - "that .pcapng is not supported.\nTrying to interpret the file as " - "length/packet interleaved.\n"); - rtp_reader.reset(test::RtpFileReader::Create( - test::RtpFileReader::kLengthPacketInterleaved, flags::InputFile())); + rtp_reader.reset(test::RtpFileReader::Create(test::RtpFileReader::kPcap, + rtp_dump_path)); if (!rtp_reader) { - fprintf(stderr, - "Unable to open input file with any supported format\n"); - return; + fprintf( + stderr, + "Couldn't open input file as either a rtpdump or .pcap. Note " + "that .pcapng is not supported.\nTrying to interpret the file as " + "length/packet interleaved.\n"); + rtp_reader.reset(test::RtpFileReader::Create( + test::RtpFileReader::kLengthPacketInterleaved, rtp_dump_path)); + if (!rtp_reader) { + fprintf(stderr, + "Unable to open input file with any supported format\n"); + return nullptr; + } } } + return rtp_reader; } - receive_stream->Start(); - int64_t replay_start_ms = -1; - int num_packets = 0; - std::map unknown_packets; - while (true) { - int64_t now_ms = rtc::TimeMillis(); - if (replay_start_ms == -1) - replay_start_ms = now_ms; + static void ReplayPackets(Call* call, test::RtpFileReader* rtp_reader) { + int64_t replay_start_ms = -1; + int num_packets = 0; + std::map unknown_packets; + while (true) { + int64_t now_ms = rtc::TimeMillis(); + if (replay_start_ms == -1) { + replay_start_ms = now_ms; + } - test::RtpPacket packet; - if (!rtp_reader->NextPacket(&packet)) - break; - - int64_t deliver_in_ms = replay_start_ms + packet.time_ms - now_ms; - if (deliver_in_ms > 0) - SleepMs(deliver_in_ms); - - ++num_packets; - switch (call->Receiver()->DeliverPacket( - webrtc::MediaType::VIDEO, - rtc::CopyOnWriteBuffer(packet.data, packet.length), - /* packet_time_us */ -1)) { - case PacketReceiver::DELIVERY_OK: - break; - case PacketReceiver::DELIVERY_UNKNOWN_SSRC: { - RTPHeader header; - std::unique_ptr parser(RtpHeaderParser::Create()); - parser->Parse(packet.data, packet.length, &header); - if (unknown_packets[header.ssrc] == 0) - fprintf(stderr, "Unknown SSRC: %u!\n", header.ssrc); - ++unknown_packets[header.ssrc]; + test::RtpPacket packet; + if (!rtp_reader->NextPacket(&packet)) { break; } - case PacketReceiver::DELIVERY_PACKET_ERROR: { - fprintf(stderr, "Packet error, corrupt packets or incorrect setup?\n"); - RTPHeader header; - std::unique_ptr parser(RtpHeaderParser::Create()); - parser->Parse(packet.data, packet.length, &header); - fprintf(stderr, "Packet len=%zu pt=%u seq=%u ts=%u ssrc=0x%8x\n", - packet.length, header.payloadType, header.sequenceNumber, - header.timestamp, header.ssrc); - break; + + int64_t deliver_in_ms = replay_start_ms + packet.time_ms - now_ms; + if (deliver_in_ms > 0) { + SleepMs(deliver_in_ms); + } + + ++num_packets; + switch (call->Receiver()->DeliverPacket( + webrtc::MediaType::VIDEO, + rtc::CopyOnWriteBuffer(packet.data, packet.length), + /* packet_time_us */ -1)) { + case PacketReceiver::DELIVERY_OK: + break; + case PacketReceiver::DELIVERY_UNKNOWN_SSRC: { + RTPHeader header; + std::unique_ptr parser(RtpHeaderParser::Create()); + parser->Parse(packet.data, packet.length, &header); + if (unknown_packets[header.ssrc] == 0) + fprintf(stderr, "Unknown SSRC: %u!\n", header.ssrc); + ++unknown_packets[header.ssrc]; + break; + } + case PacketReceiver::DELIVERY_PACKET_ERROR: { + fprintf(stderr, + "Packet error, corrupt packets or incorrect setup?\n"); + RTPHeader header; + std::unique_ptr parser(RtpHeaderParser::Create()); + parser->Parse(packet.data, packet.length, &header); + fprintf(stderr, "Packet len=%zu pt=%u seq=%u ts=%u ssrc=0x%8x\n", + packet.length, header.payloadType, header.sequenceNumber, + header.timestamp, header.ssrc); + break; + } } } + fprintf(stderr, "num_packets: %d\n", num_packets); + + for (std::map::const_iterator it = unknown_packets.begin(); + it != unknown_packets.end(); ++it) { + fprintf(stderr, "Packets for unknown ssrc '%u': %d\n", it->first, + it->second); + } } - fprintf(stderr, "num_packets: %d\n", num_packets); +}; // class RtpReplayer - for (std::map::const_iterator it = unknown_packets.begin(); - it != unknown_packets.end(); ++it) { - fprintf(stderr, "Packets for unknown ssrc '%u': %d\n", it->first, - it->second); - } - - call->DestroyVideoReceiveStream(receive_stream); - - delete decoder.decoder; +void RtpReplay() { + RtpReplayer::Replay(flags::ConfigFile(), flags::InputFile()); } + } // namespace webrtc int main(int argc, char* argv[]) {