webrtc/modules/video_coding/frame_buffer3_unittest.cc
philipel ceac5d560e New FrameBuffer3.
FrameBuffer3 keep track of order, decodability and continuity of the inserted frames. Compared to FrameBuffer2 which schedule frames for decoding and is thread safe, FrameBuffer3 does not schedule decoding and is thread unsafe.

Change-Id: Ic3bd540c4f69cec26fce53a40425f3bcd9afe085
Bug: webrtc:13343
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/238985
Reviewed-by: Danil Chapovalov <danilchap@webrtc.org>
Reviewed-by: Ilya Nikolaevskiy <ilnik@webrtc.org>
Reviewed-by: Evan Shrubsole <eshr@webrtc.org>
Commit-Queue: Philip Eliasson <philipel@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#35494}
2021-12-07 18:31:37 +00:00

344 lines
14 KiB
C++

/*
* Copyright (c) 2021 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 "modules/video_coding/frame_buffer3.h"
#include <vector>
#include "api/video/encoded_frame.h"
#include "test/field_trial.h"
#include "test/gmock.h"
#include "test/gtest.h"
namespace webrtc {
namespace {
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::IsEmpty;
using ::testing::Matches;
MATCHER_P(FrameWithId, id, "") {
return Matches(Eq(id))(arg->Id());
}
class FakeEncodedFrame : public EncodedFrame {
public:
int64_t ReceivedTime() const override { return 0; }
int64_t RenderTime() const override { return 0; }
};
class Builder {
public:
Builder& Time(uint32_t rtp_timestamp) {
rtp_timestamp_ = rtp_timestamp;
return *this;
}
Builder& Id(int64_t frame_id) {
frame_id_ = frame_id;
return *this;
}
Builder& AsLast() {
last_spatial_layer_ = true;
return *this;
}
Builder& Refs(const std::vector<int64_t>& references) {
references_ = references;
return *this;
}
std::unique_ptr<FakeEncodedFrame> Build() {
RTC_CHECK_LE(references_.size(), EncodedFrame::kMaxFrameReferences);
RTC_CHECK(rtp_timestamp_.has_value());
RTC_CHECK(frame_id_.has_value());
auto frame = std::make_unique<FakeEncodedFrame>();
frame->SetTimestamp(*rtp_timestamp_);
frame->SetId(*frame_id_);
frame->is_last_spatial_layer = last_spatial_layer_;
for (int64_t ref : references_) {
frame->references[frame->num_references] = ref;
frame->num_references++;
}
return frame;
}
private:
absl::optional<uint32_t> rtp_timestamp_;
absl::optional<int64_t> frame_id_;
bool last_spatial_layer_ = false;
std::vector<int64_t> references_;
};
TEST(FrameBuffer3Test, RejectInvalidRefs) {
FrameBuffer buffer(/*max_frame_slots=*/10, /*max_decode_history=*/100);
// Ref must be less than the id of this frame.
buffer.InsertFrame(Builder().Time(0).Id(0).Refs({0}).AsLast().Build());
EXPECT_THAT(buffer.LastContinuousFrameId(), Eq(absl::nullopt));
// Duplicate ids are also invalid.
buffer.InsertFrame(Builder().Time(10).Id(1).AsLast().Build());
buffer.InsertFrame(Builder().Time(20).Id(2).Refs({1, 1}).AsLast().Build());
EXPECT_THAT(buffer.LastContinuousFrameId(), Eq(1));
}
TEST(FrameBuffer3Test, LastContinuousUpdatesOnInsertedFrames) {
FrameBuffer buffer(/*max_frame_slots=*/10, /*max_decode_history=*/100);
EXPECT_THAT(buffer.LastContinuousFrameId(), Eq(absl::nullopt));
EXPECT_THAT(buffer.LastContinuousTemporalUnitFrameId(), Eq(absl::nullopt));
buffer.InsertFrame(Builder().Time(10).Id(1).Build());
EXPECT_THAT(buffer.LastContinuousFrameId(), Eq(1));
EXPECT_THAT(buffer.LastContinuousTemporalUnitFrameId(), Eq(absl::nullopt));
buffer.InsertFrame(Builder().Time(10).Id(2).Refs({1}).AsLast().Build());
EXPECT_THAT(buffer.LastContinuousFrameId(), Eq(2));
EXPECT_THAT(buffer.LastContinuousTemporalUnitFrameId(), Eq(2));
}
TEST(FrameBuffer3Test, LastContinuousFrameReordering) {
FrameBuffer buffer(/*max_frame_slots=*/10, /*max_decode_history=*/100);
buffer.InsertFrame(Builder().Time(10).Id(1).AsLast().Build());
buffer.InsertFrame(Builder().Time(30).Id(3).Refs({2}).AsLast().Build());
EXPECT_THAT(buffer.LastContinuousFrameId(), Eq(1));
buffer.InsertFrame(Builder().Time(20).Id(2).Refs({1}).AsLast().Build());
EXPECT_THAT(buffer.LastContinuousFrameId(), Eq(3));
}
TEST(FrameBuffer3Test, LastContinuousTemporalUnit) {
FrameBuffer buffer(/*max_frame_slots=*/10, /*max_decode_history=*/100);
buffer.InsertFrame(Builder().Time(10).Id(1).Build());
EXPECT_THAT(buffer.LastContinuousTemporalUnitFrameId(), Eq(absl::nullopt));
buffer.InsertFrame(Builder().Time(10).Id(2).Refs({1}).AsLast().Build());
EXPECT_THAT(buffer.LastContinuousTemporalUnitFrameId(), Eq(2));
}
TEST(FrameBuffer3Test, LastContinuousTemporalUnitReordering) {
FrameBuffer buffer(/*max_frame_slots=*/10, /*max_decode_history=*/100);
buffer.InsertFrame(Builder().Time(10).Id(1).Build());
buffer.InsertFrame(Builder().Time(20).Id(3).Refs({1}).Build());
buffer.InsertFrame(Builder().Time(20).Id(4).Refs({2, 3}).AsLast().Build());
EXPECT_THAT(buffer.LastContinuousTemporalUnitFrameId(), Eq(absl::nullopt));
buffer.InsertFrame(Builder().Time(10).Id(2).Refs({1}).AsLast().Build());
EXPECT_THAT(buffer.LastContinuousTemporalUnitFrameId(), Eq(4));
}
TEST(FrameBuffer3Test, NextDecodable) {
FrameBuffer buffer(/*max_frame_slots=*/10, /*max_decode_history=*/100);
EXPECT_THAT(buffer.NextDecodableTemporalUnitRtpTimestamp(),
Eq(absl::nullopt));
buffer.InsertFrame(Builder().Time(10).Id(1).AsLast().Build());
EXPECT_THAT(buffer.NextDecodableTemporalUnitRtpTimestamp(), Eq(10U));
}
TEST(FrameBuffer3Test, AdvanceNextDecodableOnExtraction) {
FrameBuffer buffer(/*max_frame_slots=*/10, /*max_decode_history=*/100);
buffer.InsertFrame(Builder().Time(10).Id(1).AsLast().Build());
buffer.InsertFrame(Builder().Time(20).Id(2).AsLast().Build());
buffer.InsertFrame(Builder().Time(30).Id(3).Refs({2}).AsLast().Build());
EXPECT_THAT(buffer.NextDecodableTemporalUnitRtpTimestamp(), Eq(10U));
EXPECT_THAT(buffer.ExtractNextDecodableTemporalUnit(),
ElementsAre(FrameWithId(1)));
EXPECT_THAT(buffer.NextDecodableTemporalUnitRtpTimestamp(), Eq(20U));
EXPECT_THAT(buffer.ExtractNextDecodableTemporalUnit(),
ElementsAre(FrameWithId(2)));
EXPECT_THAT(buffer.NextDecodableTemporalUnitRtpTimestamp(), Eq(30U));
EXPECT_THAT(buffer.ExtractNextDecodableTemporalUnit(),
ElementsAre(FrameWithId(3)));
}
TEST(FrameBuffer3Test, AdvanceLastDecodableOnExtraction) {
FrameBuffer buffer(/*max_frame_slots=*/10, /*max_decode_history=*/100);
buffer.InsertFrame(Builder().Time(10).Id(1).AsLast().Build());
buffer.InsertFrame(Builder().Time(20).Id(2).Refs({1}).AsLast().Build());
buffer.InsertFrame(Builder().Time(30).Id(3).Refs({1}).AsLast().Build());
EXPECT_THAT(buffer.LastDecodableTemporalUnitRtpTimestamp(), Eq(10U));
EXPECT_THAT(buffer.ExtractNextDecodableTemporalUnit(),
ElementsAre(FrameWithId(1)));
EXPECT_THAT(buffer.LastDecodableTemporalUnitRtpTimestamp(), Eq(30U));
}
TEST(FrameBuffer3Test, FrameUpdatesNextDecodable) {
FrameBuffer buffer(/*max_frame_slots=*/10, /*max_decode_history=*/100);
buffer.InsertFrame(Builder().Time(20).Id(2).AsLast().Build());
EXPECT_THAT(buffer.NextDecodableTemporalUnitRtpTimestamp(), Eq(20U));
buffer.InsertFrame(Builder().Time(10).Id(1).AsLast().Build());
EXPECT_THAT(buffer.NextDecodableTemporalUnitRtpTimestamp(), Eq(10U));
}
TEST(FrameBuffer3Test, KeyframeClearsFullBuffer) {
FrameBuffer buffer(/*max_frame_slots=*/5, /*max_decode_history=*/10);
buffer.InsertFrame(Builder().Time(10).Id(1).AsLast().Build());
buffer.InsertFrame(Builder().Time(20).Id(2).Refs({1}).AsLast().Build());
buffer.InsertFrame(Builder().Time(30).Id(3).Refs({2}).AsLast().Build());
buffer.InsertFrame(Builder().Time(40).Id(4).Refs({3}).AsLast().Build());
buffer.InsertFrame(Builder().Time(50).Id(5).Refs({4}).AsLast().Build());
EXPECT_THAT(buffer.LastContinuousFrameId(), Eq(5));
// Frame buffer is full
buffer.InsertFrame(Builder().Time(60).Id(6).Refs({5}).AsLast().Build());
EXPECT_THAT(buffer.LastContinuousFrameId(), Eq(5));
buffer.InsertFrame(Builder().Time(70).Id(7).AsLast().Build());
EXPECT_THAT(buffer.LastContinuousFrameId(), Eq(7));
}
TEST(FrameBuffer3Test, DropNextDecodableTemporalUnit) {
FrameBuffer buffer(/*max_frame_slots=*/10, /*max_decode_history=*/100);
buffer.InsertFrame(Builder().Time(10).Id(1).AsLast().Build());
buffer.InsertFrame(Builder().Time(20).Id(2).Refs({1}).AsLast().Build());
buffer.InsertFrame(Builder().Time(30).Id(3).Refs({1}).AsLast().Build());
buffer.ExtractNextDecodableTemporalUnit();
buffer.DropNextDecodableTemporalUnit();
EXPECT_THAT(buffer.ExtractNextDecodableTemporalUnit(),
ElementsAre(FrameWithId(3)));
}
TEST(FrameBuffer3Test, OldFramesAreIgnored) {
FrameBuffer buffer(/*max_frame_slots=*/10, /*max_decode_history=*/100);
buffer.InsertFrame(Builder().Time(10).Id(1).AsLast().Build());
buffer.InsertFrame(Builder().Time(20).Id(2).Refs({1}).AsLast().Build());
buffer.ExtractNextDecodableTemporalUnit();
buffer.ExtractNextDecodableTemporalUnit();
buffer.InsertFrame(Builder().Time(10).Id(1).AsLast().Build());
buffer.InsertFrame(Builder().Time(20).Id(2).Refs({1}).AsLast().Build());
buffer.InsertFrame(Builder().Time(30).Id(3).Refs({1}).AsLast().Build());
EXPECT_THAT(buffer.ExtractNextDecodableTemporalUnit(),
ElementsAre(FrameWithId(3)));
}
TEST(FrameBuffer3Test, ReturnFullTemporalUnitKSVC) {
FrameBuffer buffer(/*max_frame_slots=*/10, /*max_decode_history=*/100);
buffer.InsertFrame(Builder().Time(10).Id(1).Build());
buffer.InsertFrame(Builder().Time(10).Id(2).Refs({1}).Build());
buffer.InsertFrame(Builder().Time(10).Id(3).Refs({2}).AsLast().Build());
EXPECT_THAT(buffer.ExtractNextDecodableTemporalUnit(),
ElementsAre(FrameWithId(1), FrameWithId(2), FrameWithId(3)));
buffer.InsertFrame(Builder().Time(20).Id(4).Refs({3}).AsLast().Build());
EXPECT_THAT(buffer.ExtractNextDecodableTemporalUnit(),
ElementsAre(FrameWithId(4)));
}
TEST(FrameBuffer3Test, InterleavedStream) {
FrameBuffer buffer(/*max_frame_slots=*/10, /*max_decode_history=*/100);
buffer.InsertFrame(Builder().Time(10).Id(1).AsLast().Build());
buffer.InsertFrame(Builder().Time(20).Id(2).Refs({1}).AsLast().Build());
buffer.InsertFrame(Builder().Time(30).Id(3).Refs({1}).AsLast().Build());
buffer.InsertFrame(Builder().Time(40).Id(4).Refs({2}).AsLast().Build());
buffer.InsertFrame(Builder().Time(50).Id(5).Refs({3}).AsLast().Build());
EXPECT_THAT(buffer.ExtractNextDecodableTemporalUnit(),
ElementsAre(FrameWithId(1)));
EXPECT_THAT(buffer.ExtractNextDecodableTemporalUnit(),
ElementsAre(FrameWithId(2)));
EXPECT_THAT(buffer.ExtractNextDecodableTemporalUnit(),
ElementsAre(FrameWithId(3)));
EXPECT_THAT(buffer.ExtractNextDecodableTemporalUnit(),
ElementsAre(FrameWithId(4)));
EXPECT_THAT(buffer.ExtractNextDecodableTemporalUnit(),
ElementsAre(FrameWithId(5)));
buffer.InsertFrame(Builder().Time(70).Id(7).Refs({5}).AsLast().Build());
EXPECT_THAT(buffer.ExtractNextDecodableTemporalUnit(),
ElementsAre(FrameWithId(7)));
buffer.InsertFrame(Builder().Time(60).Id(6).Refs({4}).AsLast().Build());
EXPECT_THAT(buffer.ExtractNextDecodableTemporalUnit(), IsEmpty());
buffer.InsertFrame(Builder().Time(90).Id(9).Refs({7}).AsLast().Build());
EXPECT_THAT(buffer.ExtractNextDecodableTemporalUnit(),
ElementsAre(FrameWithId(9)));
}
TEST(FrameBuffer3Test, LegacyFrameIdJumpBehavior) {
{
// WebRTC-LegacyFrameIdJumpBehavior is disabled by default.
FrameBuffer buffer(/*max_frame_slots=*/10, /*max_decode_history=*/100);
buffer.InsertFrame(Builder().Time(20).Id(3).AsLast().Build());
EXPECT_THAT(buffer.ExtractNextDecodableTemporalUnit(),
ElementsAre(FrameWithId(3)));
buffer.InsertFrame(Builder().Time(30).Id(2).AsLast().Build());
EXPECT_THAT(buffer.ExtractNextDecodableTemporalUnit(), IsEmpty());
}
{
test::ScopedFieldTrials field_trial(
"WebRTC-LegacyFrameIdJumpBehavior/Enabled/");
FrameBuffer buffer(/*max_frame_slots=*/10, /*max_decode_history=*/100);
buffer.InsertFrame(Builder().Time(20).Id(3).AsLast().Build());
EXPECT_THAT(buffer.ExtractNextDecodableTemporalUnit(),
ElementsAre(FrameWithId(3)));
buffer.InsertFrame(Builder().Time(30).Id(2).Refs({1}).AsLast().Build());
EXPECT_THAT(buffer.ExtractNextDecodableTemporalUnit(), IsEmpty());
buffer.InsertFrame(Builder().Time(40).Id(1).AsLast().Build());
EXPECT_THAT(buffer.ExtractNextDecodableTemporalUnit(),
ElementsAre(FrameWithId(1)));
}
}
TEST(FrameBuffer3Test, TotalNumberOfContinuousTemporalUnits) {
FrameBuffer buffer(/*max_frame_slots=*/10, /*max_decode_history=*/100);
EXPECT_THAT(buffer.GetTotalNumberOfContinuousTemporalUnits(), Eq(0));
buffer.InsertFrame(Builder().Time(10).Id(1).AsLast().Build());
EXPECT_THAT(buffer.GetTotalNumberOfContinuousTemporalUnits(), Eq(1));
buffer.InsertFrame(Builder().Time(20).Id(2).Refs({1}).Build());
EXPECT_THAT(buffer.GetTotalNumberOfContinuousTemporalUnits(), Eq(1));
buffer.InsertFrame(Builder().Time(40).Id(4).Refs({2}).Build());
buffer.InsertFrame(Builder().Time(40).Id(5).Refs({3, 4}).AsLast().Build());
EXPECT_THAT(buffer.GetTotalNumberOfContinuousTemporalUnits(), Eq(1));
// Reordered
buffer.InsertFrame(Builder().Time(20).Id(3).Refs({2}).AsLast().Build());
EXPECT_THAT(buffer.GetTotalNumberOfContinuousTemporalUnits(), Eq(3));
}
TEST(FrameBuffer3Test, TotalNumberOfDroppedFrames) {
FrameBuffer buffer(/*max_frame_slots=*/10, /*max_decode_history=*/100);
EXPECT_THAT(buffer.GetTotalNumberOfDroppedFrames(), Eq(0));
buffer.InsertFrame(Builder().Time(10).Id(1).AsLast().Build());
buffer.InsertFrame(Builder().Time(20).Id(2).Refs({1}).Build());
buffer.InsertFrame(Builder().Time(20).Id(3).Refs({2}).AsLast().Build());
buffer.InsertFrame(Builder().Time(40).Id(4).Refs({1}).Build());
buffer.InsertFrame(Builder().Time(40).Id(5).Refs({4}).AsLast().Build());
buffer.ExtractNextDecodableTemporalUnit();
EXPECT_THAT(buffer.GetTotalNumberOfDroppedFrames(), Eq(0));
buffer.DropNextDecodableTemporalUnit();
EXPECT_THAT(buffer.GetTotalNumberOfDroppedFrames(), Eq(2));
buffer.ExtractNextDecodableTemporalUnit();
EXPECT_THAT(buffer.GetTotalNumberOfDroppedFrames(), Eq(2));
}
} // namespace
} // namespace webrtc