[DVQA] Allow processing of frames dropped by decoder

Bug: b/257402861
Change-Id: I4d495c33c162c4e3a0afef5b83adf19b6d79dfce
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/284160
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Commit-Queue: Artem Titov <titovartem@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#38693}
This commit is contained in:
Artem Titov 2022-11-19 13:20:21 +01:00 committed by WebRTC LUCI CQ
parent bbdb768989
commit 6d91a718c8
3 changed files with 112 additions and 14 deletions

View File

@ -126,16 +126,6 @@ FrameComparison ValidateFrameComparison(FrameComparison comparison) {
RTC_DCHECK(comparison.frame_stats.decoded_frame_height.has_value()) RTC_DCHECK(comparison.frame_stats.decoded_frame_height.has_value())
<< "Dropped frame comparison has to have decoded_frame_height when " << "Dropped frame comparison has to have decoded_frame_height when "
<< "decode_end_time is set"; << "decode_end_time is set";
} else {
RTC_DCHECK(!comparison.frame_stats.received_time.IsFinite())
<< "Dropped frame comparison can't have received_time when "
<< "decode_end_time is not set and there were no decoder failures";
RTC_DCHECK(!comparison.frame_stats.decode_start_time.IsFinite())
<< "Dropped frame comparison can't have decode_start_time when "
<< "decode_end_time is not set and there were no decoder failures";
RTC_DCHECK(!comparison.frame_stats.used_decoder.has_value())
<< "Dropped frame comparison can't have used_decoder when "
<< "decode_end_time is not set and there were no decoder failures";
} }
RTC_DCHECK(!comparison.frame_stats.rendered_time.IsFinite()) RTC_DCHECK(!comparison.frame_stats.rendered_time.IsFinite())
<< "Dropped frame comparison can't have rendered_time"; << "Dropped frame comparison can't have rendered_time";
@ -448,8 +438,7 @@ void DefaultVideoQualityAnalyzerFramesComparator::ProcessComparison(
FrameDropPhase dropped_phase; FrameDropPhase dropped_phase;
if (frame_stats.decode_end_time.IsFinite()) { if (frame_stats.decode_end_time.IsFinite()) {
dropped_phase = FrameDropPhase::kAfterDecoder; dropped_phase = FrameDropPhase::kAfterDecoder;
} else if (frame_stats.decode_start_time.IsFinite() && } else if (frame_stats.decode_start_time.IsFinite()) {
frame_stats.decoder_failed) {
dropped_phase = FrameDropPhase::kByDecoder; dropped_phase = FrameDropPhase::kByDecoder;
} else if (frame_stats.encoded_time.IsFinite()) { } else if (frame_stats.encoded_time.IsFinite()) {
dropped_phase = FrameDropPhase::kTransport; dropped_phase = FrameDropPhase::kTransport;

View File

@ -1092,8 +1092,78 @@ TEST(DefaultVideoQualityAnalyzerFramesComparatorTest,
EXPECT_THAT(stats.decoders, IsEmpty()); EXPECT_THAT(stats.decoders, IsEmpty());
} }
// TODO(titovartem): add test that just pre decoded frame can't be received as TEST(DefaultVideoQualityAnalyzerFramesComparatorTest,
// dropped one because decoder always returns either decoded frame or error. PreDecodedDroppedKeyFrameAccountedInStats) {
DefaultVideoQualityAnalyzerCpuMeasurer cpu_measurer;
DefaultVideoQualityAnalyzerFramesComparator comparator(
Clock::GetRealTimeClock(), cpu_measurer,
DefaultVideoQualityAnalyzerOptions());
Timestamp captured_time = Clock::GetRealTimeClock()->CurrentTime();
uint16_t frame_id = 1;
size_t stream = 0;
size_t sender = 0;
size_t receiver = 1;
InternalStatsKey stats_key(stream, sender, receiver);
// Frame captured
FrameStats frame_stats(/*frame_id=*/1, captured_time);
// Frame pre encoded
frame_stats.pre_encode_time = captured_time + TimeDelta::Millis(10);
// Frame encoded
frame_stats.encoded_time = captured_time + TimeDelta::Millis(20);
frame_stats.used_encoder =
Vp8CodecForOneFrame(frame_id, frame_stats.encoded_time);
frame_stats.encoded_frame_type = VideoFrameType::kVideoFrameKey;
frame_stats.encoded_image_size = DataSize::Bytes(1000);
frame_stats.target_encode_bitrate = 2000;
// Frame pre decoded
frame_stats.pre_decoded_frame_type = VideoFrameType::kVideoFrameKey;
frame_stats.pre_decoded_image_size = DataSize::Bytes(500);
frame_stats.received_time = captured_time + TimeDelta::Millis(30);
frame_stats.decode_start_time = captured_time + TimeDelta::Millis(40);
comparator.Start(/*max_threads_count=*/1);
comparator.EnsureStatsForStream(stream, sender, /*peers_count=*/2,
captured_time, captured_time);
comparator.AddComparison(stats_key,
/*captured=*/absl::nullopt,
/*rendered=*/absl::nullopt,
FrameComparisonType::kDroppedFrame, frame_stats);
comparator.Stop(/*last_rendered_frame_times=*/{});
EXPECT_EQ(comparator.stream_stats().size(), 1lu);
StreamStats stats = comparator.stream_stats().at(stats_key);
EXPECT_EQ(stats.stream_started_time, captured_time);
expectEmpty(stats.psnr);
expectEmpty(stats.ssim);
expectEmpty(stats.transport_time_ms);
expectEmpty(stats.total_delay_incl_transport_ms);
expectEmpty(stats.time_between_rendered_frames_ms);
expectEmpty(stats.encode_frame_rate);
EXPECT_DOUBLE_EQ(GetFirstOrDie(stats.encode_time_ms), 10.0);
expectEmpty(stats.decode_time_ms);
expectEmpty(stats.receive_to_render_time_ms);
expectEmpty(stats.skipped_between_rendered);
expectEmpty(stats.freeze_time_ms);
expectEmpty(stats.time_between_freezes_ms);
expectEmpty(stats.resolution_of_decoded_frame);
EXPECT_DOUBLE_EQ(GetFirstOrDie(stats.target_encode_bitrate), 2000.0);
expectEmpty(stats.recv_key_frame_size_bytes);
expectEmpty(stats.recv_delta_frame_size_bytes);
EXPECT_EQ(stats.total_encoded_images_payload, 1000);
EXPECT_EQ(stats.num_send_key_frames, 1);
EXPECT_EQ(stats.num_recv_key_frames, 0);
EXPECT_THAT(stats.dropped_by_phase, Eq(std::map<FrameDropPhase, int64_t>{
{FrameDropPhase::kBeforeEncoder, 0},
{FrameDropPhase::kByEncoder, 0},
{FrameDropPhase::kTransport, 0},
{FrameDropPhase::kByDecoder, 1},
{FrameDropPhase::kAfterDecoder, 0}}));
EXPECT_EQ(stats.encoders,
std::vector<StreamCodecInfo>{*frame_stats.used_encoder});
EXPECT_THAT(stats.decoders, IsEmpty());
}
TEST(DefaultVideoQualityAnalyzerFramesComparatorTest, TEST(DefaultVideoQualityAnalyzerFramesComparatorTest,
DecodedDroppedKeyFrameAccountedInStats) { DecodedDroppedKeyFrameAccountedInStats) {

View File

@ -2123,6 +2123,45 @@ TEST(DefaultVideoQualityAnalyzerTest, InfraMetricsNotCollectedByDefault) {
EXPECT_EQ(stats.on_decoder_error_processing_time_ms.NumSamples(), 0); EXPECT_EQ(stats.on_decoder_error_processing_time_ms.NumSamples(), 0);
} }
TEST(DefaultVideoQualityAnalyzerTest,
FrameDroppedByDecoderIsAccountedCorrectly) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/absl::nullopt,
/*num_squares=*/absl::nullopt);
DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest();
options.report_infra_metrics = false;
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(), options);
analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"},
kAnalyzerMaxThreadsCount);
VideoFrame to_be_dropped_frame =
NextFrame(frame_generator.get(), /*timestamp_us=*/1);
uint16_t frame_id =
analyzer.OnFrameCaptured("alice", "alice_video", to_be_dropped_frame);
to_be_dropped_frame.set_id(frame_id);
analyzer.OnFramePreEncode("alice", to_be_dropped_frame);
analyzer.OnFrameEncoded("alice", to_be_dropped_frame.id(),
FakeEncode(to_be_dropped_frame),
VideoQualityAnalyzerInterface::EncoderStats(), false);
VideoFrame received_to_be_dropped_frame = DeepCopy(to_be_dropped_frame);
analyzer.OnFramePreDecode("bob", received_to_be_dropped_frame.id(),
FakeEncode(received_to_be_dropped_frame));
PassFramesThroughAnalyzer(analyzer, "alice", "alice_video", {"bob"},
/*frames_count=*/1, *frame_generator);
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(100);
analyzer.Stop();
StreamStats stats = analyzer.GetStats().at(StatsKey("alice_video", "bob"));
ASSERT_EQ(stats.dropped_by_phase[FrameDropPhase::kByDecoder], 1);
}
class DefaultVideoQualityAnalyzerTimeBetweenFreezesTest class DefaultVideoQualityAnalyzerTimeBetweenFreezesTest
: public TestWithParam<bool> {}; : public TestWithParam<bool> {};