diff --git a/call/BUILD.gn b/call/BUILD.gn index b512e09e7c..ee06825c2c 100644 --- a/call/BUILD.gn +++ b/call/BUILD.gn @@ -347,11 +347,13 @@ if (rtc_include_tests) { "../system_wrappers", "../test:audio_codec_mocks", "../test:direct_transport", + "../test:fake_video_codecs", "../test:field_trial", "../test:test_common", "../test:test_support", "../test:video_test_common", "../video:video", + "//testing/gmock", "//testing/gtest", "//third_party/abseil-cpp/absl/memory", ] @@ -390,6 +392,7 @@ if (rtc_include_tests) { "../system_wrappers:metrics_default", "../system_wrappers:runtime_enabled_features_default", "../test:direct_transport", + "../test:fake_video_codecs", "../test:field_trial", "../test:fileutils", "../test:perf_test", diff --git a/call/rtp_video_sender.cc b/call/rtp_video_sender.cc index 23116b2d25..2b980361b1 100644 --- a/call/rtp_video_sender.cc +++ b/call/rtp_video_sender.cc @@ -357,6 +357,9 @@ void RtpVideoSender::OnBitrateAllocationUpdated( // inactive. if (layer_bitrates[i]) { rtp_modules_[i]->SetVideoBitrateAllocation(*layer_bitrates[i]); + } else { + // Signal a 0 bitrate on a simulcast stream. + rtp_modules_[i]->SetVideoBitrateAllocation(VideoBitrateAllocation()); } } } diff --git a/modules/video_coding/utility/default_video_bitrate_allocator.cc b/modules/video_coding/utility/default_video_bitrate_allocator.cc index a4a40dd08b..b9d20c5c03 100644 --- a/modules/video_coding/utility/default_video_bitrate_allocator.cc +++ b/modules/video_coding/utility/default_video_bitrate_allocator.cc @@ -12,6 +12,8 @@ #include +#include + namespace webrtc { DefaultVideoBitrateAllocator::DefaultVideoBitrateAllocator( @@ -27,14 +29,23 @@ VideoBitrateAllocation DefaultVideoBitrateAllocator::GetAllocation( if (total_bitrate_bps == 0 || !codec_.active) return allocation; - if (total_bitrate_bps < codec_.minBitrate * 1000) { - allocation.SetBitrate(0, 0, codec_.minBitrate * 1000); - } else if (codec_.maxBitrate > 0 && - total_bitrate_bps > codec_.maxBitrate * 1000) { - allocation.SetBitrate(0, 0, codec_.maxBitrate * 1000); - } else { - allocation.SetBitrate(0, 0, total_bitrate_bps); + uint32_t allocated_bitrate_bps = total_bitrate_bps; + allocated_bitrate_bps = + std::max(allocated_bitrate_bps, codec_.minBitrate * 1000); + if (codec_.maxBitrate > 0) { + allocated_bitrate_bps = + std::min(allocated_bitrate_bps, codec_.maxBitrate * 1000); } + size_t num_simulcast_streams = + std::max(1, codec_.numberOfSimulcastStreams); + // The bitrate is split between all the streams in proportion of powers of 2 + // e.g. 1:2, 1:2:4, etc. + for (size_t i = 0; i < num_simulcast_streams; i++) { + allocation.SetBitrate( + i, 0, + allocated_bitrate_bps * (1 << i) / ((1 << num_simulcast_streams) - 1)); + } + return allocation; } diff --git a/test/BUILD.gn b/test/BUILD.gn index ac56bc6a01..b5ec01bf4f 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -299,23 +299,32 @@ if (rtc_include_tests) { rtc_test("test_support_unittests") { deps = [ ":direct_transport", + ":fake_video_codecs", ":fileutils", + ":fileutils_unittests", ":perf_test", ":rtp_test_utils", + ":test_common", ":test_main", ":test_support", ":test_support_test_artifacts", ":video_test_common", ":video_test_support", + "../api:create_simulcast_test_fixture_api", + "../api:simulcast_test_fixture_api", "../api/video:video_frame_i420", "../modules/rtp_rtcp:rtp_rtcp", + "../modules/video_capture", + "../modules/video_coding:simulcast_test_fixture_impl", "../rtc_base:rtc_base_approved", "../test:single_threaded_task_queue", + "//testing/gmock", "//testing/gtest", "//third_party/abseil-cpp/absl/memory", ] sources = [ "direct_transport_unittest.cc", + "fake_vp8_encoder_unittest.cc", "frame_generator_unittest.cc", "rtp_file_reader_unittest.cc", "rtp_file_writer_unittest.cc", @@ -342,6 +351,7 @@ if (rtc_include_tests) { if (is_ios) { deps += [ ":test_support_unittests_bundle_data" ] } + if (!is_android && !build_with_chromium) { # This is needed in order to avoid: # undefined symbol: webrtc::videocapturemodule::VideoCaptureImpl::Create @@ -488,13 +498,46 @@ rtc_source_set("single_threaded_task_queue") { ] } +rtc_source_set("fake_video_codecs") { + testonly = true + visibility = [ "*" ] + sources = [ + "configurable_frame_size_encoder.cc", + "configurable_frame_size_encoder.h", + "fake_decoder.cc", + "fake_decoder.h", + "fake_encoder.cc", + "fake_encoder.h", + "fake_vp8_encoder.cc", + "fake_vp8_encoder.h", + ] + if (!build_with_chromium && is_clang) { + # Suppress warnings from the Chromium Clang plugin (bugs.webrtc.org/163). + suppressed_configs += [ "//build/config/clang:find_bad_constructs" ] + } + deps = [ + "..:webrtc_common", + "../api/video:video_frame_i420", + "../api/video_codecs:video_codecs_api", + "../common_video:common_video", + "../modules/video_coding:video_codec_interface", + "../modules/video_coding:video_coding_utility", + "../modules/video_coding:webrtc_h264", + "../modules/video_coding:webrtc_vp8", + "../modules/video_coding:webrtc_vp9", + "../rtc_base:checks", + "../rtc_base:rtc_base_approved", + "../rtc_base:rtc_task_queue", + "../rtc_base:sequenced_task_checker", + "../system_wrappers", + ] +} + rtc_source_set("test_common") { testonly = true sources = [ "call_test.cc", "call_test.h", - "configurable_frame_size_encoder.cc", - "configurable_frame_size_encoder.h", "constants.cc", "constants.h", "drifting_clock.cc", @@ -502,10 +545,6 @@ rtc_source_set("test_common") { "encoder_proxy_factory.h", "encoder_settings.cc", "encoder_settings.h", - "fake_decoder.cc", - "fake_decoder.h", - "fake_encoder.cc", - "fake_encoder.h", "fake_videorenderer.h", "function_video_decoder_factory.h", "function_video_encoder_factory.h", @@ -534,18 +573,17 @@ rtc_source_set("test_common") { deps = [ ":direct_transport", + ":fake_video_codecs", ":fileutils", ":rtp_test_utils", ":test_support", ":video_test_common", - "..:webrtc_common", "../api:libjingle_peerconnection_api", "../api:simulated_network_api", "../api:transport_api", "../api/audio_codecs:builtin_audio_decoder_factory", "../api/audio_codecs:builtin_audio_encoder_factory", "../api/video:video_frame", - "../api/video:video_frame_i420", "../api/video_codecs:video_codecs_api", "../audio", "../call", @@ -555,7 +593,6 @@ rtc_source_set("test_common") { "../call:simulated_network", "../call:simulated_packet_receiver", "../call:video_stream_api", - "../common_video", "../logging:rtc_event_log_api", "../logging:rtc_event_log_impl_base", "../media:rtc_internal_video_codecs", @@ -569,7 +606,6 @@ rtc_source_set("test_common") { "../modules/rtp_rtcp", "../modules/rtp_rtcp:mock_rtp_rtcp", "../modules/rtp_rtcp:rtp_rtcp_format", - "../modules/video_coding:video_codec_interface", "../modules/video_coding:video_coding_utility", "../modules/video_coding:webrtc_h264", "../modules/video_coding:webrtc_multiplex", @@ -577,8 +613,6 @@ rtc_source_set("test_common") { "../modules/video_coding:webrtc_vp9", "../rtc_base:checks", "../rtc_base:rtc_base_approved", - "../rtc_base:rtc_task_queue", - "../rtc_base:sequenced_task_checker", "../rtc_base/experiments:congestion_controller_experiment", "../system_wrappers", "../system_wrappers:field_trial_api", diff --git a/test/call_test.cc b/test/call_test.cc index bf6ce2ad27..3b609c300f 100644 --- a/test/call_test.cc +++ b/test/call_test.cc @@ -43,9 +43,14 @@ CallTest::CallTest() audio_send_stream_(nullptr), bbr_network_controller_factory_(new BbrNetworkControllerFactory()), fake_encoder_factory_([this]() { - auto encoder = absl::make_unique(clock_); - encoder->SetMaxBitrate(fake_encoder_max_bitrate_); - return encoder; + std::unique_ptr fake_encoder; + if (video_encoder_configs_[0].codec_type == kVideoCodecVP8) { + fake_encoder = absl::make_unique(clock_); + } else { + fake_encoder = absl::make_unique(clock_); + } + fake_encoder->SetMaxBitrate(fake_encoder_max_bitrate_); + return fake_encoder; }), num_video_streams_(1), num_audio_streams_(0), diff --git a/test/call_test.h b/test/call_test.h index f6c7e7fbf7..eb96cfde48 100644 --- a/test/call_test.h +++ b/test/call_test.h @@ -20,6 +20,7 @@ #include "test/encoder_settings.h" #include "test/fake_decoder.h" #include "test/fake_videorenderer.h" +#include "test/fake_vp8_encoder.h" #include "test/frame_generator_capturer.h" #include "test/function_video_encoder_factory.h" #include "test/rtp_rtcp_observer.h" diff --git a/test/configurable_frame_size_encoder.cc b/test/configurable_frame_size_encoder.cc index 7671a3c4b6..c18c93866e 100644 --- a/test/configurable_frame_size_encoder.cc +++ b/test/configurable_frame_size_encoder.cc @@ -15,7 +15,6 @@ #include "common_video/include/video_frame.h" #include "modules/video_coding/include/video_codec_interface.h" #include "rtc_base/checks.h" -#include "test/gtest.h" namespace webrtc { namespace test { diff --git a/test/configurable_frame_size_encoder.h b/test/configurable_frame_size_encoder.h index b8c3b830d4..4ad6749e1b 100644 --- a/test/configurable_frame_size_encoder.h +++ b/test/configurable_frame_size_encoder.h @@ -22,7 +22,7 @@ namespace test { class ConfigurableFrameSizeEncoder : public VideoEncoder { public: explicit ConfigurableFrameSizeEncoder(size_t max_frame_size); - virtual ~ConfigurableFrameSizeEncoder(); + ~ConfigurableFrameSizeEncoder() override; int32_t InitEncode(const VideoCodec* codec_settings, int32_t number_of_cores, diff --git a/test/fake_decoder.cc b/test/fake_decoder.cc index 2155008e9c..5b299ef828 100644 --- a/test/fake_decoder.cc +++ b/test/fake_decoder.cc @@ -12,16 +12,17 @@ #include "api/video/i420_buffer.h" #include "rtc_base/timeutils.h" -#include "test/call_test.h" -#include "test/gtest.h" namespace webrtc { namespace test { +namespace { +const int kDefaultWidth = 320; +const int kDefaultHeight = 180; +} // namespace + FakeDecoder::FakeDecoder() - : callback_(NULL), - width_(CallTest::kDefaultWidth), - height_(CallTest::kDefaultHeight) {} + : callback_(NULL), width_(kDefaultWidth), height_(kDefaultHeight) {} int32_t FakeDecoder::InitDecode(const VideoCodec* config, int32_t number_of_cores) { @@ -75,7 +76,7 @@ int32_t FakeH264Decoder::Decode(const EncodedImage& input, i += sizeof(kStartCode) + 1; // Skip start code and NAL header. } if (input._buffer[i] != value) { - EXPECT_EQ(value, input._buffer[i]) + RTC_CHECK_EQ(value, input._buffer[i]) << "Bitstream mismatch between sender and receiver."; return -1; } diff --git a/test/fake_encoder.cc b/test/fake_encoder.cc index 5a3e2b8146..1f90c29deb 100644 --- a/test/fake_encoder.cc +++ b/test/fake_encoder.cc @@ -16,10 +16,10 @@ #include #include "common_types.h" // NOLINT(build/include) +#include "modules/video_coding/codecs/vp8/temporal_layers.h" #include "modules/video_coding/include/video_codec_interface.h" #include "rtc_base/checks.h" #include "system_wrappers/include/sleep.h" -#include "test/gtest.h" namespace webrtc { namespace test { @@ -37,6 +37,9 @@ FakeEncoder::FakeEncoder(Clock* clock) for (size_t i = 0; i < sizeof(encoded_buffer_); ++i) { encoded_buffer_[i] = static_cast(i); } + for (bool& used : used_layers_) { + used = false; + } } void FakeEncoder::SetMaxBitrate(int max_kbps) { @@ -53,6 +56,7 @@ int32_t FakeEncoder::InitEncode(const VideoCodec* config, target_bitrate_.SetBitrate(0, 0, config_.startBitrate * 1000); configured_input_framerate_ = config_.maxFramerate; pending_keyframe_ = true; + last_frame_info_ = FrameInfo(); return 0; } @@ -63,9 +67,7 @@ int32_t FakeEncoder::Encode(const VideoFrame& input_image, unsigned char num_simulcast_streams; SimulcastStream simulcast_streams[kMaxSimulcastStreams]; EncodedImageCallback* callback; - uint32_t target_bitrate_sum_kbps; - int max_target_bitrate_kbps; - size_t num_encoded_bytes; + VideoBitrateAllocation target_bitrate; int framerate; VideoCodecMode mode; bool keyframe; @@ -77,9 +79,7 @@ int32_t FakeEncoder::Encode(const VideoFrame& input_image, simulcast_streams[i] = config_.simulcastStream[i]; } callback = callback_; - target_bitrate_sum_kbps = target_bitrate_.get_sum_kbps(); - max_target_bitrate_kbps = max_target_bitrate_kbps_; - num_encoded_bytes = sizeof(encoded_buffer_); + target_bitrate = target_bitrate_; mode = config_.mode; if (configured_input_framerate_ > 0) { framerate = configured_input_framerate_; @@ -90,63 +90,28 @@ int32_t FakeEncoder::Encode(const VideoFrame& input_image, pending_keyframe_ = false; } - for (FrameType frame_type : *frame_types) { - if (frame_type == kVideoFrameKey) { - keyframe = true; - break; + FrameInfo frame_info = + NextFrame(frame_types, keyframe, num_simulcast_streams, target_bitrate, + simulcast_streams, framerate); + for (uint8_t i = 0; i < frame_info.layers.size(); ++i) { + if (frame_info.layers[i].size == 0) { + // Drop this temporal layer. + continue; } - } - RTC_DCHECK_GT(max_framerate, 0); - - size_t bitrate = - std::max(target_bitrate_sum_kbps, simulcast_streams[0].minBitrate); - if (max_target_bitrate_kbps > 0) - bitrate = std::min(bitrate, static_cast(max_target_bitrate_kbps)); - - size_t bits_available = bitrate * 1000 / framerate; - - RTC_DCHECK_GT(num_simulcast_streams, 0); - for (unsigned char i = 0; i < num_simulcast_streams; ++i) { CodecSpecificInfo specifics; memset(&specifics, 0, sizeof(specifics)); specifics.codecType = kVideoCodecGeneric; specifics.codecSpecific.generic.simulcast_idx = i; - size_t min_stream_bits = static_cast( - (simulcast_streams[i].minBitrate * 1000) / framerate); - size_t max_stream_bits = static_cast( - (simulcast_streams[i].maxBitrate * 1000) / framerate); - size_t stream_bits = - (bits_available > max_stream_bits) ? max_stream_bits : bits_available; - size_t stream_bytes = (stream_bits + 7) / 8; - if (keyframe) { - // The first frame is a key frame and should be larger. - // Store the overshoot bytes and distribute them over the coming frames, - // so that we on average meet the bitrate target. - debt_bytes_ += (kKeyframeSizeFactor - 1) * stream_bytes; - stream_bytes *= kKeyframeSizeFactor; - } else { - if (debt_bytes_ > 0) { - // Pay at most half of the frame size for old debts. - size_t payment_size = std::min(stream_bytes / 2, debt_bytes_); - debt_bytes_ -= payment_size; - stream_bytes -= payment_size; - } - } - - if (stream_bytes > num_encoded_bytes) - stream_bytes = num_encoded_bytes; - - // Always encode something on the first frame. - if (min_stream_bits > bits_available && i > 0) - continue; - - std::unique_ptr encoded_buffer(new uint8_t[num_encoded_bytes]); - memcpy(encoded_buffer.get(), encoded_buffer_, num_encoded_bytes); - EncodedImage encoded(encoded_buffer.get(), stream_bytes, num_encoded_bytes); - encoded.SetTimestamp(input_image.timestamp()); + std::unique_ptr encoded_buffer( + new uint8_t[frame_info.layers[i].size]); + memcpy(encoded_buffer.get(), encoded_buffer_, frame_info.layers[i].size); + EncodedImage encoded(encoded_buffer.get(), frame_info.layers[i].size, + sizeof(encoded_buffer_)); + encoded._timeStamp = input_image.timestamp(); encoded.capture_time_ms_ = input_image.render_time_ms(); - encoded._frameType = (*frame_types)[i]; + encoded._frameType = + frame_info.keyframe ? kVideoFrameKey : kVideoFrameDelta; encoded._encodedWidth = simulcast_streams[i].width; encoded._encodedHeight = simulcast_streams[i].height; encoded.rotation_ = input_image.rotation(); @@ -154,17 +119,77 @@ int32_t FakeEncoder::Encode(const VideoFrame& input_image, ? VideoContentType::SCREENSHARE : VideoContentType::UNSPECIFIED; specifics.codec_name = ImplementationName(); - specifics.codecSpecific.generic.simulcast_idx = i; - RTC_DCHECK(callback); if (callback->OnEncodedImage(encoded, &specifics, nullptr).error != EncodedImageCallback::Result::OK) { return -1; } - bits_available -= std::min(encoded._length * 8, bits_available); } return 0; } +FakeEncoder::FrameInfo FakeEncoder::NextFrame( + const std::vector* frame_types, + bool keyframe, + uint8_t num_simulcast_streams, + const VideoBitrateAllocation& target_bitrate, + SimulcastStream simulcast_streams[kMaxSimulcastStreams], + int framerate) { + FrameInfo frame_info; + frame_info.keyframe = keyframe; + + if (frame_types) { + for (FrameType frame_type : *frame_types) { + if (frame_type == kVideoFrameKey) { + frame_info.keyframe = true; + break; + } + } + } + + for (uint8_t i = 0; i < num_simulcast_streams; ++i) { + if (target_bitrate.GetBitrate(i, 0) > 0) { + int temporal_id = last_frame_info_.layers.size() > i + ? ++last_frame_info_.layers[i].temporal_id % + simulcast_streams[i].numberOfTemporalLayers + : 0; + frame_info.layers.emplace_back(0, temporal_id); + } + } + + if (last_frame_info_.layers.size() < frame_info.layers.size()) { + // A new keyframe is needed since a new layer will be added. + frame_info.keyframe = true; + } + + for (uint8_t i = 0; i < frame_info.layers.size(); ++i) { + FrameInfo::SpatialLayer& layer_info = frame_info.layers[i]; + if (frame_info.keyframe) { + layer_info.temporal_id = 0; + size_t avg_frame_size = + (target_bitrate.GetBitrate(i, 0) + 7) / (8 * framerate); + + // The first frame is a key frame and should be larger. + // Store the overshoot bytes and distribute them over the coming frames, + // so that we on average meet the bitrate target. + debt_bytes_ += (kKeyframeSizeFactor - 1) * avg_frame_size; + layer_info.size = kKeyframeSizeFactor * avg_frame_size; + } else { + size_t avg_frame_size = + (target_bitrate.GetBitrate(i, layer_info.temporal_id) + 7) / + (8 * framerate); + layer_info.size = avg_frame_size; + if (debt_bytes_ > 0) { + // Pay at most half of the frame size for old debts. + size_t payment_size = std::min(avg_frame_size / 2, debt_bytes_); + debt_bytes_ -= payment_size; + layer_info.size -= payment_size; + } + } + } + last_frame_info_ = frame_info; + return frame_info; +} + int32_t FakeEncoder::RegisterEncodeCompleteCallback( EncodedImageCallback* callback) { rtc::CritScope cs(&crit_sect_); diff --git a/test/fake_encoder.h b/test/fake_encoder.h index 071019abf3..67acebec69 100644 --- a/test/fake_encoder.h +++ b/test/fake_encoder.h @@ -50,7 +50,30 @@ class FakeEncoder : public VideoEncoder { static const char* kImplementationName; protected: + struct FrameInfo { + bool keyframe; + struct SpatialLayer { + SpatialLayer() = default; + SpatialLayer(int size, int temporal_id) + : size(size), temporal_id(temporal_id) {} + // Size of a current frame in the layer. + int size = 0; + // Temporal index of a current frame in the layer. + int temporal_id = 0; + }; + std::vector layers; + }; + + FrameInfo NextFrame(const std::vector* frame_types, + bool keyframe, + uint8_t num_simulcast_streams, + const VideoBitrateAllocation& target_bitrate, + SimulcastStream simulcast_streams[kMaxSimulcastStreams], + int framerate); + + FrameInfo last_frame_info_; Clock* const clock_; + VideoCodec config_ RTC_GUARDED_BY(crit_sect_); EncodedImageCallback* callback_ RTC_GUARDED_BY(crit_sect_); VideoBitrateAllocation target_bitrate_ RTC_GUARDED_BY(crit_sect_); @@ -60,6 +83,7 @@ class FakeEncoder : public VideoEncoder { rtc::CriticalSection crit_sect_; uint8_t encoded_buffer_[100000]; + bool used_layers_[kMaxSimulcastStreams]; // Current byte debt to be payed over a number of frames. // The debt is acquired by keyframes overshooting the bitrate target. diff --git a/test/fake_vp8_encoder.cc b/test/fake_vp8_encoder.cc new file mode 100644 index 0000000000..9d0529740c --- /dev/null +++ b/test/fake_vp8_encoder.cc @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2018 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 "test/fake_vp8_encoder.h" + +#include "common_types.h" // NOLINT(build/include) +#include "modules/video_coding/codecs/vp8/temporal_layers.h" +#include "modules/video_coding/include/video_codec_interface.h" +#include "modules/video_coding/include/video_error_codes.h" +#include "modules/video_coding/utility/simulcast_utility.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/random.h" +#include "rtc_base/timeutils.h" + +namespace webrtc { + +namespace test { + +FakeVP8Encoder::FakeVP8Encoder(Clock* clock) + : FakeEncoder(clock), callback_(nullptr) { + FakeEncoder::RegisterEncodeCompleteCallback(this); + sequence_checker_.Detach(); +} + +int32_t FakeVP8Encoder::RegisterEncodeCompleteCallback( + EncodedImageCallback* callback) { + RTC_DCHECK_CALLED_SEQUENTIALLY(&sequence_checker_); + callback_ = callback; + return 0; +} + +int32_t FakeVP8Encoder::InitEncode(const VideoCodec* config, + int32_t number_of_cores, + size_t max_payload_size) { + RTC_DCHECK_CALLED_SEQUENTIALLY(&sequence_checker_); + auto result = + FakeEncoder::InitEncode(config, number_of_cores, max_payload_size); + if (result != WEBRTC_VIDEO_CODEC_OK) { + return result; + } + + int number_of_streams = SimulcastUtility::NumberOfSimulcastStreams(*config); + bool doing_simulcast = number_of_streams > 1; + + int num_temporal_layers = + doing_simulcast ? config->simulcastStream[0].numberOfTemporalLayers + : config->VP8().numberOfTemporalLayers; + RTC_DCHECK_GT(num_temporal_layers, 0); + + SetupTemporalLayers(number_of_streams, num_temporal_layers, *config); + + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t FakeVP8Encoder::Release() { + auto result = FakeEncoder::Release(); + sequence_checker_.Detach(); + return result; +} + +void FakeVP8Encoder::SetupTemporalLayers(int num_streams, + int num_temporal_layers, + const VideoCodec& codec) { + RTC_DCHECK_CALLED_SEQUENTIALLY(&sequence_checker_); + + temporal_layers_.clear(); + for (int i = 0; i < num_streams; ++i) { + temporal_layers_.emplace_back( + TemporalLayers::CreateTemporalLayers(codec, i)); + } +} + +void FakeVP8Encoder::PopulateCodecSpecific( + CodecSpecificInfo* codec_specific, + const TemporalLayers::FrameConfig& tl_config, + FrameType frame_type, + int stream_idx, + uint32_t timestamp) { + RTC_DCHECK_CALLED_SEQUENTIALLY(&sequence_checker_); + codec_specific->codecType = kVideoCodecVP8; + codec_specific->codec_name = ImplementationName(); + CodecSpecificInfoVP8* vp8Info = &(codec_specific->codecSpecific.VP8); + vp8Info->simulcastIdx = stream_idx; + vp8Info->keyIdx = kNoKeyIdx; + vp8Info->nonReference = false; + temporal_layers_[stream_idx]->PopulateCodecSpecific( + frame_type == kVideoFrameKey, tl_config, vp8Info, timestamp); +} + +EncodedImageCallback::Result FakeVP8Encoder::OnEncodedImage( + const EncodedImage& encoded_image, + const CodecSpecificInfo* codec_specific_info, + const RTPFragmentationHeader* fragments) { + RTC_DCHECK_CALLED_SEQUENTIALLY(&sequence_checker_); + uint8_t stream_idx = codec_specific_info->codecSpecific.generic.simulcast_idx; + CodecSpecificInfo overrided_specific_info; + TemporalLayers::FrameConfig tl_config = + temporal_layers_[stream_idx]->UpdateLayerConfig(encoded_image._timeStamp); + PopulateCodecSpecific(&overrided_specific_info, tl_config, + encoded_image._frameType, stream_idx, + encoded_image._timeStamp); + temporal_layers_[stream_idx]->FrameEncoded(encoded_image._timeStamp, + encoded_image._length, -1); + + return callback_->OnEncodedImage(encoded_image, &overrided_specific_info, + fragments); +} + +} // namespace test +} // namespace webrtc diff --git a/test/fake_vp8_encoder.h b/test/fake_vp8_encoder.h new file mode 100644 index 0000000000..407361bc7d --- /dev/null +++ b/test/fake_vp8_encoder.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2018 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. + */ + +#ifndef TEST_FAKE_VP8_ENCODER_H_ +#define TEST_FAKE_VP8_ENCODER_H_ + +#include +#include + +#include "modules/video_coding/codecs/vp8/temporal_layers.h" +#include "test/fake_encoder.h" + +#include "rtc_base/criticalsection.h" +#include "rtc_base/sequenced_task_checker.h" + +namespace webrtc { +namespace test { + +class FakeVP8Encoder : public FakeEncoder, public EncodedImageCallback { + public: + explicit FakeVP8Encoder(Clock* clock); + virtual ~FakeVP8Encoder() = default; + + int32_t RegisterEncodeCompleteCallback( + EncodedImageCallback* callback) override; + + int32_t InitEncode(const VideoCodec* config, + int32_t number_of_cores, + size_t max_payload_size) override; + + int32_t Release() override; + + const char* ImplementationName() const override { return "FakeVp8Encoder"; } + + Result OnEncodedImage(const EncodedImage& encodedImage, + const CodecSpecificInfo* codecSpecificInfo, + const RTPFragmentationHeader* fragments) override; + + private: + void SetupTemporalLayers(int num_streams, + int num_temporal_layers, + const VideoCodec& codec); + void PopulateCodecSpecific(CodecSpecificInfo* codec_specific, + const TemporalLayers::FrameConfig& tl_config, + FrameType frame_type, + int stream_idx, + uint32_t timestamp); + + rtc::SequencedTaskChecker sequence_checker_; + EncodedImageCallback* callback_ RTC_GUARDED_BY(sequence_checker_); + + std::vector> temporal_layers_ + RTC_GUARDED_BY(sequence_checker_); +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_FAKE_VP8_ENCODER_H_ diff --git a/test/fake_vp8_encoder_unittest.cc b/test/fake_vp8_encoder_unittest.cc new file mode 100644 index 0000000000..c79ba0c551 --- /dev/null +++ b/test/fake_vp8_encoder_unittest.cc @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2018 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 "absl/memory/memory.h" +#include "api/test/create_simulcast_test_fixture.h" +#include "api/test/simulcast_test_fixture.h" +#include "modules/video_coding/utility/simulcast_test_fixture_impl.h" +#include "test/fake_decoder.h" +#include "test/fake_vp8_encoder.h" +#include "test/function_video_decoder_factory.h" +#include "test/function_video_encoder_factory.h" + +namespace webrtc { +namespace test { + +namespace { + +std::unique_ptr CreateSpecificSimulcastTestFixture() { + std::unique_ptr encoder_factory = + absl::make_unique([]() { + return absl::make_unique(Clock::GetRealTimeClock()); + }); + std::unique_ptr decoder_factory = + absl::make_unique( + []() { return absl::make_unique(); }); + return CreateSimulcastTestFixture(std::move(encoder_factory), + std::move(decoder_factory), + SdpVideoFormat("VP8")); +} +} // namespace + +TEST(TestFakeVp8Codec, TestKeyFrameRequestsOnAllStreams) { + auto fixture = CreateSpecificSimulcastTestFixture(); + fixture->TestKeyFrameRequestsOnAllStreams(); +} + +TEST(TestFakeVp8Codec, TestPaddingAllStreams) { + auto fixture = CreateSpecificSimulcastTestFixture(); + fixture->TestPaddingAllStreams(); +} + +TEST(TestFakeVp8Codec, TestPaddingTwoStreams) { + auto fixture = CreateSpecificSimulcastTestFixture(); + fixture->TestPaddingTwoStreams(); +} + +TEST(TestFakeVp8Codec, TestPaddingTwoStreamsOneMaxedOut) { + auto fixture = CreateSpecificSimulcastTestFixture(); + fixture->TestPaddingTwoStreamsOneMaxedOut(); +} + +TEST(TestFakeVp8Codec, TestPaddingOneStream) { + auto fixture = CreateSpecificSimulcastTestFixture(); + fixture->TestPaddingOneStream(); +} + +TEST(TestFakeVp8Codec, TestPaddingOneStreamTwoMaxedOut) { + auto fixture = CreateSpecificSimulcastTestFixture(); + fixture->TestPaddingOneStreamTwoMaxedOut(); +} + +TEST(TestFakeVp8Codec, TestSendAllStreams) { + auto fixture = CreateSpecificSimulcastTestFixture(); + fixture->TestSendAllStreams(); +} + +TEST(TestFakeVp8Codec, TestDisablingStreams) { + auto fixture = CreateSpecificSimulcastTestFixture(); + fixture->TestDisablingStreams(); +} + +TEST(TestFakeVp8Codec, TestSwitchingToOneStream) { + auto fixture = CreateSpecificSimulcastTestFixture(); + fixture->TestSwitchingToOneStream(); +} + +TEST(TestFakeVp8Codec, TestSwitchingToOneOddStream) { + auto fixture = CreateSpecificSimulcastTestFixture(); + fixture->TestSwitchingToOneOddStream(); +} + +TEST(TestFakeVp8Codec, TestSwitchingToOneSmallStream) { + auto fixture = CreateSpecificSimulcastTestFixture(); + fixture->TestSwitchingToOneSmallStream(); +} + +TEST(TestFakeVp8Codec, TestSpatioTemporalLayers333PatternEncoder) { + auto fixture = CreateSpecificSimulcastTestFixture(); + fixture->TestSpatioTemporalLayers333PatternEncoder(); +} + +} // namespace test +} // namespace webrtc diff --git a/video/BUILD.gn b/video/BUILD.gn index d2a52fb536..d6e529fb3a 100644 --- a/video/BUILD.gn +++ b/video/BUILD.gn @@ -361,6 +361,7 @@ if (rtc_include_tests) { "../system_wrappers", "../system_wrappers:metrics_default", "../system_wrappers:runtime_enabled_features_default", + "../test:fake_video_codecs", "../test:field_trial", "../test:rtp_test_utils", "../test:run_test", @@ -472,6 +473,7 @@ if (rtc_include_tests) { "../system_wrappers:metrics_api", "../system_wrappers:metrics_default", "../test:direct_transport", + "../test:fake_video_codecs", "../test:field_trial", "../test:fileutils", "../test:perf_test", diff --git a/video/end_to_end_tests/extended_reports_tests.cc b/video/end_to_end_tests/extended_reports_tests.cc index 322a9c301f..acffcd6be5 100644 --- a/video/end_to_end_tests/extended_reports_tests.cc +++ b/video/end_to_end_tests/extended_reports_tests.cc @@ -30,7 +30,12 @@ class RtcpXrObserver : public test::EndToEndTest { sent_rtcp_rrtr_(0), sent_rtcp_target_bitrate_(false), sent_zero_rtcp_target_bitrate_(false), - sent_rtcp_dlrr_(0) {} + sent_rtcp_dlrr_(0), + send_transport_(nullptr) { + forward_transport_config_.link_capacity_kbps = 500; + forward_transport_config_.queue_delay_ms = 0; + forward_transport_config_.loss_percent = 0; + } private: // Receive stream should send RR packets (and RRTR packets if enabled). @@ -56,6 +61,14 @@ class RtcpXrObserver : public test::EndToEndTest { test::RtcpPacketParser parser; EXPECT_TRUE(parser.Parse(packet, length)); + if (parser.sender_ssrc() == test::CallTest::kVideoSendSsrcs[1] && + enable_zero_target_bitrate_) { + // Reduce bandwidth restriction to disable second stream after it was + // enabled for some time. + forward_transport_config_.link_capacity_kbps = 200; + send_transport_->SetConfig(forward_transport_config_); + } + sent_rtcp_sr_ += parser.sender_report()->num_packets(); EXPECT_LE(parser.xr()->num_packets(), 1); if (parser.xr()->num_packets() > 0) { @@ -64,8 +77,12 @@ class RtcpXrObserver : public test::EndToEndTest { ++sent_rtcp_dlrr_; if (parser.xr()->target_bitrate()) { sent_rtcp_target_bitrate_ = true; - for (const rtcp::TargetBitrate::BitrateItem& item : - parser.xr()->target_bitrate()->GetTargetBitrates()) { + auto target_bitrates = + parser.xr()->target_bitrate()->GetTargetBitrates(); + if (target_bitrates.empty()) { + sent_zero_rtcp_target_bitrate_ = true; + } + for (const rtcp::TargetBitrate::BitrateItem& item : target_bitrates) { if (item.target_bitrate_kbps == 0) { sent_zero_rtcp_target_bitrate_ = true; break; @@ -98,39 +115,20 @@ class RtcpXrObserver : public test::EndToEndTest { return enable_zero_target_bitrate_ ? 2 : 1; } - // This test uses VideoStream settings different from the the default one - // implemented in DefaultVideoStreamFactory, so it implements its own - // VideoEncoderConfig::VideoStreamFactoryInterface which is created - // in ModifyVideoConfigs. - class ZeroTargetVideoStreamFactory - : public VideoEncoderConfig::VideoStreamFactoryInterface { - public: - ZeroTargetVideoStreamFactory() {} - - private: - std::vector CreateEncoderStreams( - int width, - int height, - const VideoEncoderConfig& encoder_config) override { - std::vector streams = - test::CreateVideoStreams(width, height, encoder_config); - // Set one of the streams' target bitrates to zero to test that a - // bitrate of 0 can be signalled. - streams[encoder_config.number_of_streams - 1].min_bitrate_bps = 0; - streams[encoder_config.number_of_streams - 1].target_bitrate_bps = 0; - streams[encoder_config.number_of_streams - 1].max_bitrate_bps = 0; - return streams; - } - }; + test::PacketTransport* CreateSendTransport( + test::SingleThreadedTaskQueueForTesting* task_queue, + Call* sender_call) { + send_transport_ = new test::PacketTransport( + task_queue, sender_call, this, test::PacketTransport::kSender, + test::CallTest::payload_type_map_, forward_transport_config_); + return send_transport_; + } void ModifyVideoConfigs( VideoSendStream::Config* send_config, std::vector* receive_configs, VideoEncoderConfig* encoder_config) override { if (enable_zero_target_bitrate_) { - encoder_config->video_stream_factory = - new rtc::RefCountedObject(); - // Configure VP8 to be able to use simulcast. send_config->rtp.payload_name = "VP8"; encoder_config->codec_type = kVideoCodecVP8; @@ -166,6 +164,8 @@ class RtcpXrObserver : public test::EndToEndTest { bool sent_rtcp_target_bitrate_ RTC_GUARDED_BY(&crit_); bool sent_zero_rtcp_target_bitrate_ RTC_GUARDED_BY(&crit_); int sent_rtcp_dlrr_; + DefaultNetworkSimulationConfig forward_transport_config_; + test::PacketTransport* send_transport_; }; TEST_F(ExtendedReportsEndToEndTest,