| /* |
| * Copyright (c) 2020 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 <stddef.h> |
| #include <stdint.h> |
| |
| #include <map> |
| #include <memory> |
| #include <ostream> |
| #include <tuple> |
| #include <vector> |
| |
| #include "absl/types/optional.h" |
| #include "api/units/data_size.h" |
| #include "api/units/time_delta.h" |
| #include "api/video_codecs/video_codec.h" |
| #include "api/video_codecs/video_encoder.h" |
| #include "modules/video_coding/codecs/av1/libaom_av1_decoder.h" |
| #include "modules/video_coding/codecs/av1/libaom_av1_encoder.h" |
| #include "modules/video_coding/codecs/test/encoded_video_frame_producer.h" |
| #include "modules/video_coding/include/video_codec_interface.h" |
| #include "modules/video_coding/include/video_error_codes.h" |
| #include "modules/video_coding/svc/create_scalability_structure.h" |
| #include "modules/video_coding/svc/scalability_mode_util.h" |
| #include "modules/video_coding/svc/scalable_video_controller.h" |
| #include "modules/video_coding/svc/scalable_video_controller_no_layering.h" |
| #include "test/gmock.h" |
| #include "test/gtest.h" |
| |
| namespace webrtc { |
| namespace { |
| |
| using ::testing::ContainerEq; |
| using ::testing::Each; |
| using ::testing::ElementsAreArray; |
| using ::testing::Ge; |
| using ::testing::IsEmpty; |
| using ::testing::Not; |
| using ::testing::NotNull; |
| using ::testing::Pointwise; |
| using ::testing::SizeIs; |
| using ::testing::Truly; |
| using ::testing::Values; |
| |
| // Use small resolution for this test to make it faster. |
| constexpr int kWidth = 320; |
| constexpr int kHeight = 180; |
| constexpr int kFramerate = 30; |
| |
| VideoCodec DefaultCodecSettings() { |
| VideoCodec codec_settings; |
| codec_settings.SetScalabilityMode(ScalabilityMode::kL1T1); |
| codec_settings.width = kWidth; |
| codec_settings.height = kHeight; |
| codec_settings.maxFramerate = kFramerate; |
| codec_settings.maxBitrate = 1000; |
| codec_settings.qpMax = 63; |
| return codec_settings; |
| } |
| VideoEncoder::Settings DefaultEncoderSettings() { |
| return VideoEncoder::Settings( |
| VideoEncoder::Capabilities(/*loss_notification=*/false), |
| /*number_of_cores=*/1, /*max_payload_size=*/1200); |
| } |
| |
| class TestAv1Decoder { |
| public: |
| explicit TestAv1Decoder(int decoder_id) |
| : decoder_id_(decoder_id), decoder_(CreateLibaomAv1Decoder()) { |
| if (decoder_ == nullptr) { |
| ADD_FAILURE() << "Failed to create a decoder#" << decoder_id_; |
| return; |
| } |
| EXPECT_TRUE(decoder_->Configure({})); |
| EXPECT_EQ(decoder_->RegisterDecodeCompleteCallback(&callback_), |
| WEBRTC_VIDEO_CODEC_OK); |
| } |
| // This class requires pointer stability and thus not copyable nor movable. |
| TestAv1Decoder(const TestAv1Decoder&) = delete; |
| TestAv1Decoder& operator=(const TestAv1Decoder&) = delete; |
| |
| void Decode(int64_t frame_id, const EncodedImage& image) { |
| ASSERT_THAT(decoder_, NotNull()); |
| int32_t error = decoder_->Decode(image, /*missing_frames=*/false, |
| /*render_time_ms=*/image.capture_time_ms_); |
| if (error != WEBRTC_VIDEO_CODEC_OK) { |
| ADD_FAILURE() << "Failed to decode frame id " << frame_id |
| << " with error code " << error << " by decoder#" |
| << decoder_id_; |
| return; |
| } |
| decoded_ids_.push_back(frame_id); |
| } |
| |
| const std::vector<int64_t>& decoded_frame_ids() const { return decoded_ids_; } |
| size_t num_output_frames() const { return callback_.num_called(); } |
| |
| private: |
| // Decoder callback that only counts how many times it was called. |
| // While it is tempting to replace it with a simple mock, that one requires |
| // to set expectation on number of calls in advance. Tests below unsure about |
| // expected number of calls until after calls are done. |
| class DecoderCallback : public DecodedImageCallback { |
| public: |
| size_t num_called() const { return num_called_; } |
| |
| private: |
| int32_t Decoded(VideoFrame& /*decoded_image*/) override { |
| ++num_called_; |
| return 0; |
| } |
| void Decoded(VideoFrame& /*decoded_image*/, |
| absl::optional<int32_t> /*decode_time_ms*/, |
| absl::optional<uint8_t> /*qp*/) override { |
| ++num_called_; |
| } |
| |
| int num_called_ = 0; |
| }; |
| |
| const int decoder_id_; |
| std::vector<int64_t> decoded_ids_; |
| DecoderCallback callback_; |
| const std::unique_ptr<VideoDecoder> decoder_; |
| }; |
| |
| TEST(LibaomAv1Test, EncodeDecode) { |
| TestAv1Decoder decoder(0); |
| std::unique_ptr<VideoEncoder> encoder = CreateLibaomAv1Encoder(); |
| VideoCodec codec_settings = DefaultCodecSettings(); |
| ASSERT_EQ(encoder->InitEncode(&codec_settings, DefaultEncoderSettings()), |
| WEBRTC_VIDEO_CODEC_OK); |
| |
| VideoBitrateAllocation allocation; |
| allocation.SetBitrate(0, 0, 300000); |
| encoder->SetRates(VideoEncoder::RateControlParameters( |
| allocation, codec_settings.maxFramerate)); |
| |
| std::vector<EncodedVideoFrameProducer::EncodedFrame> encoded_frames = |
| EncodedVideoFrameProducer(*encoder).SetNumInputFrames(4).Encode(); |
| for (size_t frame_id = 0; frame_id < encoded_frames.size(); ++frame_id) { |
| decoder.Decode(static_cast<int64_t>(frame_id), |
| encoded_frames[frame_id].encoded_image); |
| } |
| |
| // Check encoder produced some frames for decoder to decode. |
| ASSERT_THAT(encoded_frames, Not(IsEmpty())); |
| // Check decoder found all of them valid. |
| EXPECT_THAT(decoder.decoded_frame_ids(), SizeIs(encoded_frames.size())); |
| // Check each of them produced an output frame. |
| EXPECT_EQ(decoder.num_output_frames(), decoder.decoded_frame_ids().size()); |
| } |
| |
| struct LayerId { |
| friend bool operator==(const LayerId& lhs, const LayerId& rhs) { |
| return std::tie(lhs.spatial_id, lhs.temporal_id) == |
| std::tie(rhs.spatial_id, rhs.temporal_id); |
| } |
| friend bool operator<(const LayerId& lhs, const LayerId& rhs) { |
| return std::tie(lhs.spatial_id, lhs.temporal_id) < |
| std::tie(rhs.spatial_id, rhs.temporal_id); |
| } |
| friend std::ostream& operator<<(std::ostream& s, const LayerId& layer) { |
| return s << "S" << layer.spatial_id << "T" << layer.temporal_id; |
| } |
| |
| int spatial_id = 0; |
| int temporal_id = 0; |
| }; |
| |
| struct SvcTestParam { |
| ScalabilityMode GetScalabilityMode() const { |
| absl::optional<ScalabilityMode> scalability_mode = |
| ScalabilityModeFromString(name); |
| RTC_CHECK(scalability_mode.has_value()); |
| return *scalability_mode; |
| } |
| |
| std::string name; |
| int num_frames_to_generate; |
| std::map<LayerId, DataRate> configured_bitrates; |
| }; |
| |
| class LibaomAv1SvcTest : public ::testing::TestWithParam<SvcTestParam> {}; |
| |
| TEST_P(LibaomAv1SvcTest, EncodeAndDecodeAllDecodeTargets) { |
| const SvcTestParam param = GetParam(); |
| std::unique_ptr<ScalableVideoController> svc_controller = |
| CreateScalabilityStructure(param.GetScalabilityMode()); |
| ASSERT_TRUE(svc_controller); |
| VideoBitrateAllocation allocation; |
| if (param.configured_bitrates.empty()) { |
| ScalableVideoController::StreamLayersConfig config = |
| svc_controller->StreamConfig(); |
| for (int sid = 0; sid < config.num_spatial_layers; ++sid) { |
| for (int tid = 0; tid < config.num_temporal_layers; ++tid) { |
| allocation.SetBitrate(sid, tid, 100'000); |
| } |
| } |
| } else { |
| for (const auto& kv : param.configured_bitrates) { |
| allocation.SetBitrate(kv.first.spatial_id, kv.first.temporal_id, |
| kv.second.bps()); |
| } |
| } |
| |
| size_t num_decode_targets = |
| svc_controller->DependencyStructure().num_decode_targets; |
| |
| std::unique_ptr<VideoEncoder> encoder = CreateLibaomAv1Encoder(); |
| VideoCodec codec_settings = DefaultCodecSettings(); |
| codec_settings.SetScalabilityMode(GetParam().GetScalabilityMode()); |
| ASSERT_EQ(encoder->InitEncode(&codec_settings, DefaultEncoderSettings()), |
| WEBRTC_VIDEO_CODEC_OK); |
| encoder->SetRates(VideoEncoder::RateControlParameters( |
| allocation, codec_settings.maxFramerate)); |
| std::vector<EncodedVideoFrameProducer::EncodedFrame> encoded_frames = |
| EncodedVideoFrameProducer(*encoder) |
| .SetNumInputFrames(GetParam().num_frames_to_generate) |
| .SetResolution({kWidth, kHeight}) |
| .Encode(); |
| |
| ASSERT_THAT( |
| encoded_frames, |
| Each(Truly([&](const EncodedVideoFrameProducer::EncodedFrame& frame) { |
| return frame.codec_specific_info.generic_frame_info && |
| frame.codec_specific_info.generic_frame_info |
| ->decode_target_indications.size() == num_decode_targets; |
| }))); |
| |
| for (size_t dt = 0; dt < num_decode_targets; ++dt) { |
| TestAv1Decoder decoder(dt); |
| std::vector<int64_t> requested_ids; |
| for (int64_t frame_id = 0; |
| frame_id < static_cast<int64_t>(encoded_frames.size()); ++frame_id) { |
| const EncodedVideoFrameProducer::EncodedFrame& frame = |
| encoded_frames[frame_id]; |
| if (frame.codec_specific_info.generic_frame_info |
| ->decode_target_indications[dt] != |
| DecodeTargetIndication::kNotPresent) { |
| requested_ids.push_back(frame_id); |
| decoder.Decode(frame_id, frame.encoded_image); |
| } |
| } |
| |
| ASSERT_THAT(requested_ids, SizeIs(Ge(2u))); |
| // Check decoder found all of them valid. |
| EXPECT_THAT(decoder.decoded_frame_ids(), ContainerEq(requested_ids)) |
| << "Decoder#" << dt; |
| // Check each of them produced an output frame. |
| EXPECT_EQ(decoder.num_output_frames(), decoder.decoded_frame_ids().size()) |
| << "Decoder#" << dt; |
| } |
| } |
| |
| MATCHER(SameLayerIdAndBitrateIsNear, "") { |
| // First check if layer id is the same. |
| return std::get<0>(arg).first == std::get<1>(arg).first && |
| // check measured bitrate is not much lower than requested. |
| std::get<0>(arg).second >= std::get<1>(arg).second * 0.8 && |
| // check measured bitrate is not much larger than requested. |
| std::get<0>(arg).second <= std::get<1>(arg).second * 1.1; |
| } |
| |
| TEST_P(LibaomAv1SvcTest, SetRatesMatchMeasuredBitrate) { |
| const SvcTestParam param = GetParam(); |
| if (param.configured_bitrates.empty()) { |
| // Rates are not configured for this particular structure, skip the test. |
| return; |
| } |
| constexpr TimeDelta kDuration = TimeDelta::Seconds(5); |
| |
| VideoBitrateAllocation allocation; |
| for (const auto& kv : param.configured_bitrates) { |
| allocation.SetBitrate(kv.first.spatial_id, kv.first.temporal_id, |
| kv.second.bps()); |
| } |
| |
| std::unique_ptr<VideoEncoder> encoder = CreateLibaomAv1Encoder(); |
| ASSERT_TRUE(encoder); |
| VideoCodec codec_settings = DefaultCodecSettings(); |
| codec_settings.SetScalabilityMode(param.GetScalabilityMode()); |
| codec_settings.maxBitrate = allocation.get_sum_kbps(); |
| codec_settings.maxFramerate = 30; |
| ASSERT_EQ(encoder->InitEncode(&codec_settings, DefaultEncoderSettings()), |
| WEBRTC_VIDEO_CODEC_OK); |
| |
| encoder->SetRates(VideoEncoder::RateControlParameters( |
| allocation, codec_settings.maxFramerate)); |
| |
| std::vector<EncodedVideoFrameProducer::EncodedFrame> encoded_frames = |
| EncodedVideoFrameProducer(*encoder) |
| .SetNumInputFrames(codec_settings.maxFramerate * kDuration.seconds()) |
| .SetResolution({codec_settings.width, codec_settings.height}) |
| .SetFramerateFps(codec_settings.maxFramerate) |
| .Encode(); |
| |
| // Calculate size of each layer. |
| std::map<LayerId, DataSize> layer_size; |
| for (const auto& frame : encoded_frames) { |
| ASSERT_TRUE(frame.codec_specific_info.generic_frame_info); |
| const auto& layer = *frame.codec_specific_info.generic_frame_info; |
| LayerId layer_id = {layer.spatial_id, layer.temporal_id}; |
| // This is almost same as |
| // layer_size[layer_id] += DataSize::Bytes(frame.encoded_image.size()); |
| // but avoids calling deleted default constructor for DataSize. |
| layer_size.emplace(layer_id, DataSize::Zero()).first->second += |
| DataSize::Bytes(frame.encoded_image.size()); |
| } |
| // Convert size of the layer into bitrate of that layer. |
| std::vector<std::pair<LayerId, DataRate>> measured_bitrates; |
| for (const auto& kv : layer_size) { |
| measured_bitrates.emplace_back(kv.first, kv.second / kDuration); |
| } |
| EXPECT_THAT(measured_bitrates, Pointwise(SameLayerIdAndBitrateIsNear(), |
| param.configured_bitrates)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| Svc, |
| LibaomAv1SvcTest, |
| Values(SvcTestParam{"L1T1", /*num_frames_to_generate=*/4}, |
| SvcTestParam{"L1T2", |
| /*num_frames_to_generate=*/4, |
| /*configured_bitrates=*/ |
| {{{0, 0}, DataRate::KilobitsPerSec(60)}, |
| {{0, 1}, DataRate::KilobitsPerSec(40)}}}, |
| SvcTestParam{"L1T3", /*num_frames_to_generate=*/8}, |
| SvcTestParam{"L2T1", |
| /*num_frames_to_generate=*/3, |
| /*configured_bitrates=*/ |
| {{{0, 0}, DataRate::KilobitsPerSec(30)}, |
| {{1, 0}, DataRate::KilobitsPerSec(70)}}}, |
| SvcTestParam{"L2T1h", |
| /*num_frames_to_generate=*/3, |
| /*configured_bitrates=*/ |
| {{{0, 0}, DataRate::KilobitsPerSec(30)}, |
| {{1, 0}, DataRate::KilobitsPerSec(70)}}}, |
| SvcTestParam{"L2T1_KEY", /*num_frames_to_generate=*/3}, |
| SvcTestParam{"L3T1", /*num_frames_to_generate=*/3}, |
| SvcTestParam{"L3T3", /*num_frames_to_generate=*/8}, |
| SvcTestParam{"S2T1", /*num_frames_to_generate=*/3}, |
| SvcTestParam{"S3T3", /*num_frames_to_generate=*/8}, |
| SvcTestParam{"L2T2", /*num_frames_to_generate=*/4}, |
| SvcTestParam{"L2T2_KEY", /*num_frames_to_generate=*/4}, |
| SvcTestParam{"L2T2_KEY_SHIFT", |
| /*num_frames_to_generate=*/4, |
| /*configured_bitrates=*/ |
| {{{0, 0}, DataRate::KilobitsPerSec(70)}, |
| {{0, 1}, DataRate::KilobitsPerSec(30)}, |
| {{1, 0}, DataRate::KilobitsPerSec(110)}, |
| {{1, 1}, DataRate::KilobitsPerSec(80)}}}), |
| [](const testing::TestParamInfo<SvcTestParam>& info) { |
| return info.param.name; |
| }); |
| |
| } // namespace |
| } // namespace webrtc |