From 28da36a6ea16b50719b9919f39601f09c0495abc Mon Sep 17 00:00:00 2001 From: Danil Chapovalov Date: Fri, 8 May 2020 12:53:13 +0200 Subject: [PATCH] Add unittest for av1 wrappers to test Encode and Decode functions while helpful by itself, it is also a preparation for adding unittests for (to be added) svc features of the encoder. Bug: webrtc:11404 Change-Id: I62b0645f44579f21f228d406a206b4c01d80dd02 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/174580 Commit-Queue: Danil Chapovalov Reviewed-by: Philip Eliasson Cr-Commit-Position: refs/heads/master@{#31189} --- modules/video_coding/codecs/av1/BUILD.gn | 29 ++- .../codecs/av1/libaom_av1_unittest.cc | 206 ++++++++++++++++++ 2 files changed, 225 insertions(+), 10 deletions(-) create mode 100644 modules/video_coding/codecs/av1/libaom_av1_unittest.cc diff --git a/modules/video_coding/codecs/av1/BUILD.gn b/modules/video_coding/codecs/av1/BUILD.gn index 4faa6a6789..b2b82d4947 100644 --- a/modules/video_coding/codecs/av1/BUILD.gn +++ b/modules/video_coding/codecs/av1/BUILD.gn @@ -64,16 +64,25 @@ rtc_library("libaom_av1_encoder") { } } -rtc_library("video_coding_codecs_av1_tests") { - testonly = true +if (rtc_include_tests) { + rtc_library("video_coding_codecs_av1_tests") { + testonly = true - if (enable_libaom) { - sources = [ "libaom_av1_encoder_unittest.cc" ] - deps = [ - ":libaom_av1_encoder", - "../..:video_codec_interface", - "../../../../api/video_codecs:video_codecs_api", - "../../../../test:test_support", - ] + if (enable_libaom) { + sources = [ + "libaom_av1_encoder_unittest.cc", + "libaom_av1_unittest.cc", + ] + deps = [ + ":libaom_av1_decoder", + ":libaom_av1_encoder", + "../..:video_codec_interface", + "../../../../api:create_frame_generator", + "../../../../api:frame_generator_api", + "../../../../api/video_codecs:video_codecs_api", + "../../../../test:test_support", + "//third_party/abseil-cpp/absl/types:optional", + ] + } } } diff --git a/modules/video_coding/codecs/av1/libaom_av1_unittest.cc b/modules/video_coding/codecs/av1/libaom_av1_unittest.cc new file mode 100644 index 0000000000..4a549ea453 --- /dev/null +++ b/modules/video_coding/codecs/av1/libaom_av1_unittest.cc @@ -0,0 +1,206 @@ +/* + * 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 +#include + +#include +#include + +#include "absl/types/optional.h" +#include "api/test/create_frame_generator.h" +#include "api/test/frame_generator_interface.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/include/video_codec_interface.h" +#include "modules/video_coding/include/video_error_codes.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +using ::testing::ElementsAreArray; +using ::testing::IsEmpty; +using ::testing::Not; +using ::testing::NotNull; + +// Use small resolution for this test to make it faster. +constexpr int kWidth = 320; +constexpr int kHeight = 180; +constexpr int kFramerate = 30; +constexpr int kRtpTicksPerSecond = 90000; + +class TestAv1Encoder { + public: + struct Encoded { + EncodedImage encoded_image; + CodecSpecificInfo codec_specific_info; + }; + + TestAv1Encoder() : encoder_(CreateLibaomAv1Encoder()) { + RTC_CHECK(encoder_); + VideoCodec codec_settings; + codec_settings.width = kWidth; + codec_settings.height = kHeight; + codec_settings.maxFramerate = kFramerate; + VideoEncoder::Settings encoder_settings( + VideoEncoder::Capabilities(/*loss_notification=*/false), + /*number_of_cores=*/1, /*max_payload_size=*/1200); + EXPECT_EQ(encoder_->InitEncode(&codec_settings, encoder_settings), + WEBRTC_VIDEO_CODEC_OK); + EXPECT_EQ(encoder_->RegisterEncodeCompleteCallback(&callback_), + WEBRTC_VIDEO_CODEC_OK); + } + // This class requires pointer stability and thus not copyable nor movable. + TestAv1Encoder(const TestAv1Encoder&) = delete; + TestAv1Encoder& operator=(const TestAv1Encoder&) = delete; + + void EncodeAndAppend(const VideoFrame& frame, std::vector* encoded) { + callback_.SetEncodeStorage(encoded); + std::vector frame_types = { + VideoFrameType::kVideoFrameDelta}; + EXPECT_EQ(encoder_->Encode(frame, &frame_types), WEBRTC_VIDEO_CODEC_OK); + // Prefer to crash checking nullptr rather than writing to random memory. + callback_.SetEncodeStorage(nullptr); + } + + private: + class EncoderCallback : public EncodedImageCallback { + public: + void SetEncodeStorage(std::vector* storage) { storage_ = storage; } + + private: + Result OnEncodedImage( + const EncodedImage& encoded_image, + const CodecSpecificInfo* codec_specific_info, + const RTPFragmentationHeader* /*fragmentation*/) override { + RTC_CHECK(storage_); + storage_->push_back({encoded_image, *codec_specific_info}); + return Result(Result::Error::OK); + } + + std::vector* storage_ = nullptr; + }; + + EncoderCallback callback_; + std::unique_ptr encoder_; +}; + +class TestAv1Decoder { + public: + TestAv1Decoder() { + decoder_ = CreateLibaomAv1Decoder(); + if (decoder_ == nullptr) { + ADD_FAILURE() << "Failed to create a decoder"; + return; + } + EXPECT_EQ(decoder_->InitDecode(/*codec_settings=*/nullptr, + /*number_of_cores=*/1), + WEBRTC_VIDEO_CODEC_OK); + 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()); + requested_ids_.push_back(frame_id); + 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; + return; + } + decoded_ids_.push_back(frame_id); + } + + const std::vector& requested_frame_ids() const { + return requested_ids_; + } + const std::vector& 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 /*decode_time_ms*/, + absl::optional /*qp*/) override { + ++num_called_; + } + + int num_called_ = 0; + }; + + std::vector requested_ids_; + std::vector decoded_ids_; + DecoderCallback callback_; + std::unique_ptr decoder_; +}; + +std::vector GenerateFrames(size_t num_frames) { + std::vector frames; + frames.reserve(num_frames); + + auto input_frame_generator = test::CreateSquareFrameGenerator( + kWidth, kHeight, test::FrameGeneratorInterface::OutputType::kI420, + absl::nullopt); + uint32_t timestamp = 1000; + for (size_t i = 0; i < num_frames; ++i) { + frames.push_back( + VideoFrame::Builder() + .set_video_frame_buffer(input_frame_generator->NextFrame().buffer) + .set_timestamp_rtp(timestamp += kRtpTicksPerSecond / kFramerate) + .build()); + } + return frames; +} + +TEST(LibaomAv1Test, EncodeDecode) { + TestAv1Decoder decoder; + TestAv1Encoder encoder; + + std::vector encoded_frames; + for (const VideoFrame& frame : GenerateFrames(/*num_frames=*/4)) { + encoder.EncodeAndAppend(frame, &encoded_frames); + } + for (size_t frame_idx = 0; frame_idx < encoded_frames.size(); ++frame_idx) { + decoder.Decode(static_cast(frame_idx), + encoded_frames[frame_idx].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(), + ElementsAreArray(decoder.requested_frame_ids())); + // Check each of them produced an output frame. + EXPECT_EQ(decoder.num_output_frames(), decoder.decoded_frame_ids().size()); +} + +} // namespace +} // namespace webrtc