/* * Copyright (c) 2019 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 "rtc_tools/rtp_generator/rtp_generator.h" #include #include #include #include "api/task_queue/default_task_queue_factory.h" #include "api/test/create_frame_generator.h" #include "api/video_codecs/builtin_video_decoder_factory.h" #include "api/video_codecs/builtin_video_encoder_factory.h" #include "api/video_codecs/video_encoder.h" #include "media/base/media_constants.h" #include "rtc_base/strings/json.h" #include "rtc_base/system/file_wrapper.h" #include "rtc_base/thread.h" #include "test/testsupport/file_utils.h" #include "video/config/encoder_stream_factory.h" #include "video/config/video_encoder_config.h" namespace webrtc { namespace { // Payload types. constexpr int kPayloadTypeVp8 = 125; constexpr int kPayloadTypeVp9 = 124; constexpr int kPayloadTypeH264 = 123; constexpr int kFakeVideoSendPayloadType = 122; // Defaults constexpr int kDefaultSsrc = 1337; constexpr int kMaxConfigBufferSize = 8192; // Utility function to validate a correct codec type has been passed in. bool IsValidCodecType(const std::string& codec_name) { return cricket::kVp8CodecName == codec_name || cricket::kVp9CodecName == codec_name || cricket::kH264CodecName == codec_name; } // Utility function to return some base payload type for a codec_name. int GetDefaultTypeForPayloadName(const std::string& codec_name) { if (cricket::kVp8CodecName == codec_name) { return kPayloadTypeVp8; } if (cricket::kVp9CodecName == codec_name) { return kPayloadTypeVp9; } if (cricket::kH264CodecName == codec_name) { return kPayloadTypeH264; } return kFakeVideoSendPayloadType; } // Creates a single VideoSendStream configuration. absl::optional ParseVideoSendStreamConfig(const Json::Value& json) { RtpGeneratorOptions::VideoSendStreamConfig config; // Parse video source settings. if (!rtc::GetIntFromJsonObject(json, "duration_ms", &config.duration_ms)) { RTC_LOG(LS_WARNING) << "duration_ms not specified using default: " << config.duration_ms; } if (!rtc::GetIntFromJsonObject(json, "video_width", &config.video_width)) { RTC_LOG(LS_WARNING) << "video_width not specified using default: " << config.video_width; } if (!rtc::GetIntFromJsonObject(json, "video_height", &config.video_height)) { RTC_LOG(LS_WARNING) << "video_height not specified using default: " << config.video_height; } if (!rtc::GetIntFromJsonObject(json, "video_fps", &config.video_fps)) { RTC_LOG(LS_WARNING) << "video_fps not specified using default: " << config.video_fps; } if (!rtc::GetIntFromJsonObject(json, "num_squares", &config.num_squares)) { RTC_LOG(LS_WARNING) << "num_squares not specified using default: " << config.num_squares; } // Parse RTP settings for this configuration. config.rtp.ssrcs.push_back(kDefaultSsrc); Json::Value rtp_json; if (!rtc::GetValueFromJsonObject(json, "rtp", &rtp_json)) { RTC_LOG(LS_ERROR) << "video_streams must have an rtp section"; return absl::nullopt; } if (!rtc::GetStringFromJsonObject(rtp_json, "payload_name", &config.rtp.payload_name)) { RTC_LOG(LS_ERROR) << "rtp.payload_name must be specified"; return absl::nullopt; } if (!IsValidCodecType(config.rtp.payload_name)) { RTC_LOG(LS_ERROR) << "rtp.payload_name must be VP8,VP9 or H264"; return absl::nullopt; } config.rtp.payload_type = GetDefaultTypeForPayloadName(config.rtp.payload_name); if (!rtc::GetIntFromJsonObject(rtp_json, "payload_type", &config.rtp.payload_type)) { RTC_LOG(LS_WARNING) << "rtp.payload_type not specified using default for codec type" << config.rtp.payload_type; } return config; } } // namespace absl::optional ParseRtpGeneratorOptionsFromFile( const std::string& options_file) { if (!test::FileExists(options_file)) { RTC_LOG(LS_ERROR) << " configuration file does not exist"; return absl::nullopt; } // Read the configuration file from disk. FileWrapper config_file = FileWrapper::OpenReadOnly(options_file); std::vector raw_json_buffer(kMaxConfigBufferSize, 0); size_t bytes_read = config_file.Read(raw_json_buffer.data(), raw_json_buffer.size() - 1); if (bytes_read == 0) { RTC_LOG(LS_ERROR) << "Unable to read the configuration file."; return absl::nullopt; } // Parse the file as JSON Json::CharReaderBuilder builder; Json::Value json; std::string error_message; std::unique_ptr json_reader(builder.newCharReader()); if (!json_reader->parse(raw_json_buffer.data(), raw_json_buffer.data() + raw_json_buffer.size(), &json, &error_message)) { RTC_LOG(LS_ERROR) << "Unable to parse the corpus config json file. Error:" << error_message; return absl::nullopt; } RtpGeneratorOptions gen_options; for (const auto& video_stream_json : json["video_streams"]) { absl::optional video_stream_config = ParseVideoSendStreamConfig(video_stream_json); if (!video_stream_config.has_value()) { RTC_LOG(LS_ERROR) << "Unable to parse the corpus config json file"; return absl::nullopt; } gen_options.video_streams.push_back(*video_stream_config); } return gen_options; } RtpGenerator::RtpGenerator(const RtpGeneratorOptions& options) : options_(options), video_encoder_factory_(CreateBuiltinVideoEncoderFactory()), video_decoder_factory_(CreateBuiltinVideoDecoderFactory()), video_bitrate_allocator_factory_( CreateBuiltinVideoBitrateAllocatorFactory()), event_log_(std::make_unique()), call_(Call::Create(CallConfig(event_log_.get()))), task_queue_(CreateDefaultTaskQueueFactory()) { constexpr int kMinBitrateBps = 30000; // 30 Kbps constexpr int kMaxBitrateBps = 2500000; // 2.5 Mbps int stream_count = 0; webrtc::VideoEncoder::EncoderInfo encoder_info; for (const auto& send_config : options.video_streams) { webrtc::VideoSendStream::Config video_config(this); video_config.encoder_settings.encoder_factory = video_encoder_factory_.get(); video_config.encoder_settings.bitrate_allocator_factory = video_bitrate_allocator_factory_.get(); video_config.rtp = send_config.rtp; // Update some required to be unique values. stream_count++; video_config.rtp.mid = "mid-" + std::to_string(stream_count); // Configure the video encoder configuration. VideoEncoderConfig encoder_config; encoder_config.content_type = VideoEncoderConfig::ContentType::kRealtimeVideo; encoder_config.codec_type = PayloadStringToCodecType(video_config.rtp.payload_name); if (video_config.rtp.payload_name == cricket::kVp8CodecName) { VideoCodecVP8 settings = VideoEncoder::GetDefaultVp8Settings(); encoder_config.encoder_specific_settings = rtc::make_ref_counted( settings); } else if (video_config.rtp.payload_name == cricket::kVp9CodecName) { VideoCodecVP9 settings = VideoEncoder::GetDefaultVp9Settings(); encoder_config.encoder_specific_settings = rtc::make_ref_counted( settings); } else if (video_config.rtp.payload_name == cricket::kH264CodecName) { encoder_config.encoder_specific_settings = nullptr; } encoder_config.video_format.name = video_config.rtp.payload_name; encoder_config.min_transmit_bitrate_bps = 0; encoder_config.max_bitrate_bps = kMaxBitrateBps; encoder_config.content_type = VideoEncoderConfig::ContentType::kRealtimeVideo; // Configure the simulcast layers. encoder_config.number_of_streams = video_config.rtp.ssrcs.size(); encoder_config.bitrate_priority = 1.0; encoder_config.simulcast_layers.resize(encoder_config.number_of_streams); for (size_t i = 0; i < encoder_config.number_of_streams; ++i) { encoder_config.simulcast_layers[i].active = true; encoder_config.simulcast_layers[i].min_bitrate_bps = kMinBitrateBps; encoder_config.simulcast_layers[i].max_bitrate_bps = kMaxBitrateBps; encoder_config.simulcast_layers[i].max_framerate = send_config.video_fps; } encoder_config.video_stream_factory = rtc::make_ref_counted( video_config.rtp.payload_name, /*max qp*/ 56, /*screencast*/ false, /*screenshare enabled*/ false, encoder_info); // Setup the fake video stream for this. std::unique_ptr frame_generator = std::make_unique( Clock::GetRealTimeClock(), test::CreateSquareFrameGenerator(send_config.video_width, send_config.video_height, absl::nullopt, absl::nullopt), send_config.video_fps, *task_queue_); frame_generator->Init(); VideoSendStream* video_send_stream = call_->CreateVideoSendStream( std::move(video_config), std::move(encoder_config)); video_send_stream->SetSource( frame_generator.get(), webrtc::DegradationPreference::MAINTAIN_FRAMERATE); // Store these objects so we can destropy them at the end. frame_generators_.push_back(std::move(frame_generator)); video_send_streams_.push_back(video_send_stream); } } RtpGenerator::~RtpGenerator() { for (VideoSendStream* send_stream : video_send_streams_) { call_->DestroyVideoSendStream(send_stream); } } void RtpGenerator::GenerateRtpDump(const std::string& rtp_dump_path) { rtp_dump_writer_.reset(test::RtpFileWriter::Create( test::RtpFileWriter::kRtpDump, rtp_dump_path)); call_->SignalChannelNetworkState(webrtc::MediaType::VIDEO, webrtc::kNetworkUp); for (VideoSendStream* send_stream : video_send_streams_) { send_stream->Start(); } // Spinlock until all the durations end. WaitUntilAllVideoStreamsFinish(); call_->SignalChannelNetworkState(webrtc::MediaType::VIDEO, webrtc::kNetworkDown); } bool RtpGenerator::SendRtp(const uint8_t* packet, size_t length, const webrtc::PacketOptions& options) { test::RtpPacket rtp_packet = DataToRtpPacket(packet, length); rtp_dump_writer_->WritePacket(&rtp_packet); return true; } bool RtpGenerator::SendRtcp(const uint8_t* packet, size_t length) { test::RtpPacket rtcp_packet = DataToRtpPacket(packet, length); rtp_dump_writer_->WritePacket(&rtcp_packet); return true; } int RtpGenerator::GetMaxDuration() const { int max_end_ms = 0; for (const auto& video_stream : options_.video_streams) { max_end_ms = std::max(video_stream.duration_ms, max_end_ms); } return max_end_ms; } void RtpGenerator::WaitUntilAllVideoStreamsFinish() { // Find the maximum duration required by the streams. start_ms_ = Clock::GetRealTimeClock()->TimeInMilliseconds(); int64_t max_end_ms = start_ms_ + GetMaxDuration(); int64_t current_time = 0; do { int64_t min_wait_time = 0; current_time = Clock::GetRealTimeClock()->TimeInMilliseconds(); // Stop any streams that are no longer active. for (size_t i = 0; i < options_.video_streams.size(); ++i) { const int64_t end_ms = start_ms_ + options_.video_streams[i].duration_ms; if (current_time > end_ms) { video_send_streams_[i]->Stop(); } else { min_wait_time = std::min(min_wait_time, end_ms - current_time); } } rtc::Thread::Current()->SleepMs(min_wait_time); } while (current_time < max_end_ms); } test::RtpPacket RtpGenerator::DataToRtpPacket(const uint8_t* packet, size_t packet_len) { webrtc::test::RtpPacket rtp_packet; memcpy(rtp_packet.data, packet, packet_len); rtp_packet.length = packet_len; rtp_packet.original_length = packet_len; rtp_packet.time_ms = webrtc::Clock::GetRealTimeClock()->TimeInMilliseconds() - start_ms_; return rtp_packet; } } // namespace webrtc