summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/video/frame_encode_metadata_writer_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libwebrtc/video/frame_encode_metadata_writer_unittest.cc')
-rw-r--r--third_party/libwebrtc/video/frame_encode_metadata_writer_unittest.cc471
1 files changed, 471 insertions, 0 deletions
diff --git a/third_party/libwebrtc/video/frame_encode_metadata_writer_unittest.cc b/third_party/libwebrtc/video/frame_encode_metadata_writer_unittest.cc
new file mode 100644
index 0000000000..e151282b77
--- /dev/null
+++ b/third_party/libwebrtc/video/frame_encode_metadata_writer_unittest.cc
@@ -0,0 +1,471 @@
+/*
+ * Copyright (c) 2019 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 "video/frame_encode_metadata_writer.h"
+
+#include <cstddef>
+#include <vector>
+
+#include "api/video/i420_buffer.h"
+#include "api/video/video_frame.h"
+#include "api/video/video_timing.h"
+#include "common_video/h264/h264_common.h"
+#include "common_video/test/utilities.h"
+#include "modules/video_coding/include/video_coding_defines.h"
+#include "rtc_base/time_utils.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+namespace test {
+namespace {
+
+const rtc::scoped_refptr<I420Buffer> kFrameBuffer = I420Buffer::Create(4, 4);
+
+inline size_t FrameSize(const size_t& min_frame_size,
+ const size_t& max_frame_size,
+ const int& s,
+ const int& i) {
+ return min_frame_size + (s + 1) * i % (max_frame_size - min_frame_size);
+}
+
+class FakeEncodedImageCallback : public EncodedImageCallback {
+ public:
+ FakeEncodedImageCallback() : num_frames_dropped_(0) {}
+ Result OnEncodedImage(const EncodedImage& encoded_image,
+ const CodecSpecificInfo* codec_specific_info) override {
+ return Result(Result::OK);
+ }
+ void OnDroppedFrame(DropReason reason) override { ++num_frames_dropped_; }
+ size_t GetNumFramesDropped() { return num_frames_dropped_; }
+
+ private:
+ size_t num_frames_dropped_;
+};
+
+enum class FrameType {
+ kNormal,
+ kTiming,
+ kDropped,
+};
+
+bool IsTimingFrame(const EncodedImage& image) {
+ return image.timing_.flags != VideoSendTiming::kInvalid &&
+ image.timing_.flags != VideoSendTiming::kNotTriggered;
+}
+
+// Emulates `num_frames` on `num_streams` frames with capture timestamps
+// increased by 1 from 0. Size of each frame is between
+// `min_frame_size` and `max_frame_size`, outliers are counted relatevely to
+// `average_frame_sizes[]` for each stream.
+std::vector<std::vector<FrameType>> GetTimingFrames(
+ const int64_t delay_ms,
+ const size_t min_frame_size,
+ const size_t max_frame_size,
+ std::vector<size_t> average_frame_sizes,
+ const int num_streams,
+ const int num_frames) {
+ FakeEncodedImageCallback sink;
+ FrameEncodeMetadataWriter encode_timer(&sink);
+ VideoCodec codec_settings;
+ codec_settings.numberOfSimulcastStreams = num_streams;
+ codec_settings.timing_frame_thresholds = {delay_ms,
+ kDefaultOutlierFrameSizePercent};
+ encode_timer.OnEncoderInit(codec_settings);
+ const size_t kFramerate = 30;
+ VideoBitrateAllocation bitrate_allocation;
+ for (int si = 0; si < num_streams; ++si) {
+ bitrate_allocation.SetBitrate(si, 0,
+ average_frame_sizes[si] * 8 * kFramerate);
+ }
+ encode_timer.OnSetRates(bitrate_allocation, kFramerate);
+
+ std::vector<std::vector<FrameType>> result(num_streams);
+ int64_t current_timestamp = 0;
+ for (int i = 0; i < num_frames; ++i) {
+ current_timestamp += 1;
+ VideoFrame frame = VideoFrame::Builder()
+ .set_timestamp_rtp(current_timestamp * 90)
+ .set_timestamp_ms(current_timestamp)
+ .set_video_frame_buffer(kFrameBuffer)
+ .build();
+ encode_timer.OnEncodeStarted(frame);
+ for (int si = 0; si < num_streams; ++si) {
+ // every (5+s)-th frame is dropped on s-th stream by design.
+ bool dropped = i % (5 + si) == 0;
+
+ EncodedImage image;
+ image.SetEncodedData(EncodedImageBuffer::Create(max_frame_size));
+ image.set_size(FrameSize(min_frame_size, max_frame_size, si, i));
+ image.capture_time_ms_ = current_timestamp;
+ image.SetTimestamp(static_cast<uint32_t>(current_timestamp * 90));
+ image.SetSpatialIndex(si);
+
+ if (dropped) {
+ result[si].push_back(FrameType::kDropped);
+ continue;
+ }
+
+ encode_timer.FillTimingInfo(si, &image);
+
+ if (IsTimingFrame(image)) {
+ result[si].push_back(FrameType::kTiming);
+ } else {
+ result[si].push_back(FrameType::kNormal);
+ }
+ }
+ }
+ return result;
+}
+} // namespace
+
+TEST(FrameEncodeMetadataWriterTest, MarksTimingFramesPeriodicallyTogether) {
+ const int64_t kDelayMs = 29;
+ const size_t kMinFrameSize = 10;
+ const size_t kMaxFrameSize = 20;
+ const int kNumFrames = 1000;
+ const int kNumStreams = 3;
+ // No outliers as 1000 is larger than anything from range [10,20].
+ const std::vector<size_t> kAverageSize = {1000, 1000, 1000};
+ auto frames = GetTimingFrames(kDelayMs, kMinFrameSize, kMaxFrameSize,
+ kAverageSize, kNumStreams, kNumFrames);
+ // Timing frames should be tirggered every delayMs.
+ // As no outliers are expected, frames on all streams have to be
+ // marked together.
+ int last_timing_frame = -1;
+ for (int i = 0; i < kNumFrames; ++i) {
+ int num_normal = 0;
+ int num_timing = 0;
+ int num_dropped = 0;
+ for (int s = 0; s < kNumStreams; ++s) {
+ if (frames[s][i] == FrameType::kTiming) {
+ ++num_timing;
+ } else if (frames[s][i] == FrameType::kNormal) {
+ ++num_normal;
+ } else {
+ ++num_dropped;
+ }
+ }
+ // Can't have both normal and timing frames at the same timstamp.
+ EXPECT_TRUE(num_timing == 0 || num_normal == 0);
+ if (num_dropped < kNumStreams) {
+ if (last_timing_frame == -1 || i >= last_timing_frame + kDelayMs) {
+ // If didn't have timing frames for a period, current sent frame has to
+ // be one. No normal frames should be sent.
+ EXPECT_EQ(num_normal, 0);
+ } else {
+ // No unneeded timing frames should be sent.
+ EXPECT_EQ(num_timing, 0);
+ }
+ }
+ if (num_timing > 0)
+ last_timing_frame = i;
+ }
+}
+
+TEST(FrameEncodeMetadataWriterTest, MarksOutliers) {
+ const int64_t kDelayMs = 29;
+ const size_t kMinFrameSize = 2495;
+ const size_t kMaxFrameSize = 2505;
+ const int kNumFrames = 1000;
+ const int kNumStreams = 3;
+ // Possible outliers as 1000 lies in range [995, 1005].
+ const std::vector<size_t> kAverageSize = {998, 1000, 1004};
+ auto frames = GetTimingFrames(kDelayMs, kMinFrameSize, kMaxFrameSize,
+ kAverageSize, kNumStreams, kNumFrames);
+ // All outliers should be marked.
+ for (int i = 0; i < kNumFrames; ++i) {
+ for (int s = 0; s < kNumStreams; ++s) {
+ if (FrameSize(kMinFrameSize, kMaxFrameSize, s, i) >=
+ kAverageSize[s] * kDefaultOutlierFrameSizePercent / 100) {
+ // Too big frame. May be dropped or timing, but not normal.
+ EXPECT_NE(frames[s][i], FrameType::kNormal);
+ }
+ }
+ }
+}
+
+TEST(FrameEncodeMetadataWriterTest, NoTimingFrameIfNoEncodeStartTime) {
+ int64_t timestamp = 1;
+ constexpr size_t kFrameSize = 500;
+ EncodedImage image;
+ image.SetEncodedData(EncodedImageBuffer::Create(kFrameSize));
+ image.capture_time_ms_ = timestamp;
+ image.SetTimestamp(static_cast<uint32_t>(timestamp * 90));
+
+ FakeEncodedImageCallback sink;
+ FrameEncodeMetadataWriter encode_timer(&sink);
+ VideoCodec codec_settings;
+ // Make all frames timing frames.
+ codec_settings.timing_frame_thresholds.delay_ms = 1;
+ encode_timer.OnEncoderInit(codec_settings);
+ VideoBitrateAllocation bitrate_allocation;
+ bitrate_allocation.SetBitrate(0, 0, 500000);
+ encode_timer.OnSetRates(bitrate_allocation, 30);
+
+ // Verify a single frame works with encode start time set.
+ VideoFrame frame = VideoFrame::Builder()
+ .set_timestamp_ms(timestamp)
+ .set_timestamp_rtp(timestamp * 90)
+ .set_video_frame_buffer(kFrameBuffer)
+ .build();
+ encode_timer.OnEncodeStarted(frame);
+ encode_timer.FillTimingInfo(0, &image);
+ EXPECT_TRUE(IsTimingFrame(image));
+
+ // New frame, now skip OnEncodeStarted. Should not result in timing frame.
+ image.capture_time_ms_ = ++timestamp;
+ image.SetTimestamp(static_cast<uint32_t>(timestamp * 90));
+ image.timing_ = EncodedImage::Timing();
+ encode_timer.FillTimingInfo(0, &image);
+ EXPECT_FALSE(IsTimingFrame(image));
+}
+
+TEST(FrameEncodeMetadataWriterTest, NotifiesAboutDroppedFrames) {
+ const int64_t kTimestampMs1 = 47721840;
+ const int64_t kTimestampMs2 = 47721850;
+ const int64_t kTimestampMs3 = 47721860;
+ const int64_t kTimestampMs4 = 47721870;
+
+ FakeEncodedImageCallback sink;
+ FrameEncodeMetadataWriter encode_timer(&sink);
+ encode_timer.OnEncoderInit(VideoCodec());
+ // Any non-zero bitrate needed to be set before the first frame.
+ VideoBitrateAllocation bitrate_allocation;
+ bitrate_allocation.SetBitrate(0, 0, 500000);
+ encode_timer.OnSetRates(bitrate_allocation, 30);
+
+ EncodedImage image;
+ VideoFrame frame = VideoFrame::Builder()
+ .set_timestamp_rtp(kTimestampMs1 * 90)
+ .set_timestamp_ms(kTimestampMs1)
+ .set_video_frame_buffer(kFrameBuffer)
+ .build();
+
+ image.capture_time_ms_ = kTimestampMs1;
+ image.SetTimestamp(static_cast<uint32_t>(image.capture_time_ms_ * 90));
+ frame.set_timestamp(image.capture_time_ms_ * 90);
+ frame.set_timestamp_us(image.capture_time_ms_ * 1000);
+ encode_timer.OnEncodeStarted(frame);
+
+ EXPECT_EQ(0u, sink.GetNumFramesDropped());
+ encode_timer.FillTimingInfo(0, &image);
+
+ image.capture_time_ms_ = kTimestampMs2;
+ image.SetTimestamp(static_cast<uint32_t>(image.capture_time_ms_ * 90));
+ image.timing_ = EncodedImage::Timing();
+ frame.set_timestamp(image.capture_time_ms_ * 90);
+ frame.set_timestamp_us(image.capture_time_ms_ * 1000);
+ encode_timer.OnEncodeStarted(frame);
+ // No OnEncodedImageCall for timestamp2. Yet, at this moment it's not known
+ // that frame with timestamp2 was dropped.
+ EXPECT_EQ(0u, sink.GetNumFramesDropped());
+
+ image.capture_time_ms_ = kTimestampMs3;
+ image.SetTimestamp(static_cast<uint32_t>(image.capture_time_ms_ * 90));
+ image.timing_ = EncodedImage::Timing();
+ frame.set_timestamp(image.capture_time_ms_ * 90);
+ frame.set_timestamp_us(image.capture_time_ms_ * 1000);
+ encode_timer.OnEncodeStarted(frame);
+ encode_timer.FillTimingInfo(0, &image);
+ EXPECT_EQ(1u, sink.GetNumFramesDropped());
+
+ image.capture_time_ms_ = kTimestampMs4;
+ image.SetTimestamp(static_cast<uint32_t>(image.capture_time_ms_ * 90));
+ image.timing_ = EncodedImage::Timing();
+ frame.set_timestamp(image.capture_time_ms_ * 90);
+ frame.set_timestamp_us(image.capture_time_ms_ * 1000);
+ encode_timer.OnEncodeStarted(frame);
+ encode_timer.FillTimingInfo(0, &image);
+ EXPECT_EQ(1u, sink.GetNumFramesDropped());
+}
+
+TEST(FrameEncodeMetadataWriterTest, RestoresCaptureTimestamps) {
+ EncodedImage image;
+ const int64_t kTimestampMs = 123456;
+ FakeEncodedImageCallback sink;
+
+ FrameEncodeMetadataWriter encode_timer(&sink);
+ encode_timer.OnEncoderInit(VideoCodec());
+ // Any non-zero bitrate needed to be set before the first frame.
+ VideoBitrateAllocation bitrate_allocation;
+ bitrate_allocation.SetBitrate(0, 0, 500000);
+ encode_timer.OnSetRates(bitrate_allocation, 30);
+
+ image.capture_time_ms_ = kTimestampMs; // Correct timestamp.
+ image.SetTimestamp(static_cast<uint32_t>(image.capture_time_ms_ * 90));
+ VideoFrame frame = VideoFrame::Builder()
+ .set_timestamp_ms(image.capture_time_ms_)
+ .set_timestamp_rtp(image.capture_time_ms_ * 90)
+ .set_video_frame_buffer(kFrameBuffer)
+ .build();
+ encode_timer.OnEncodeStarted(frame);
+ image.capture_time_ms_ = 0; // Incorrect timestamp.
+ encode_timer.FillTimingInfo(0, &image);
+ EXPECT_EQ(kTimestampMs, image.capture_time_ms_);
+}
+
+TEST(FrameEncodeMetadataWriterTest, CopiesRotation) {
+ EncodedImage image;
+ const int64_t kTimestampMs = 123456;
+ FakeEncodedImageCallback sink;
+
+ FrameEncodeMetadataWriter encode_timer(&sink);
+ encode_timer.OnEncoderInit(VideoCodec());
+ // Any non-zero bitrate needed to be set before the first frame.
+ VideoBitrateAllocation bitrate_allocation;
+ bitrate_allocation.SetBitrate(0, 0, 500000);
+ encode_timer.OnSetRates(bitrate_allocation, 30);
+
+ image.SetTimestamp(static_cast<uint32_t>(kTimestampMs * 90));
+ VideoFrame frame = VideoFrame::Builder()
+ .set_timestamp_ms(kTimestampMs)
+ .set_timestamp_rtp(kTimestampMs * 90)
+ .set_rotation(kVideoRotation_180)
+ .set_video_frame_buffer(kFrameBuffer)
+ .build();
+ encode_timer.OnEncodeStarted(frame);
+ encode_timer.FillTimingInfo(0, &image);
+ EXPECT_EQ(kVideoRotation_180, image.rotation_);
+}
+
+TEST(FrameEncodeMetadataWriterTest, SetsContentType) {
+ EncodedImage image;
+ const int64_t kTimestampMs = 123456;
+ FakeEncodedImageCallback sink;
+
+ FrameEncodeMetadataWriter encode_timer(&sink);
+ VideoCodec codec;
+ codec.mode = VideoCodecMode::kScreensharing;
+ encode_timer.OnEncoderInit(codec);
+ // Any non-zero bitrate needed to be set before the first frame.
+ VideoBitrateAllocation bitrate_allocation;
+ bitrate_allocation.SetBitrate(0, 0, 500000);
+ encode_timer.OnSetRates(bitrate_allocation, 30);
+
+ image.SetTimestamp(static_cast<uint32_t>(kTimestampMs * 90));
+ VideoFrame frame = VideoFrame::Builder()
+ .set_timestamp_ms(kTimestampMs)
+ .set_timestamp_rtp(kTimestampMs * 90)
+ .set_rotation(kVideoRotation_180)
+ .set_video_frame_buffer(kFrameBuffer)
+ .build();
+ encode_timer.OnEncodeStarted(frame);
+ encode_timer.FillTimingInfo(0, &image);
+ EXPECT_EQ(VideoContentType::SCREENSHARE, image.content_type_);
+}
+
+TEST(FrameEncodeMetadataWriterTest, CopiesColorSpace) {
+ EncodedImage image;
+ const int64_t kTimestampMs = 123456;
+ FakeEncodedImageCallback sink;
+
+ FrameEncodeMetadataWriter encode_timer(&sink);
+ encode_timer.OnEncoderInit(VideoCodec());
+ // Any non-zero bitrate needed to be set before the first frame.
+ VideoBitrateAllocation bitrate_allocation;
+ bitrate_allocation.SetBitrate(0, 0, 500000);
+ encode_timer.OnSetRates(bitrate_allocation, 30);
+
+ webrtc::ColorSpace color_space =
+ CreateTestColorSpace(/*with_hdr_metadata=*/true);
+ image.SetTimestamp(static_cast<uint32_t>(kTimestampMs * 90));
+ VideoFrame frame = VideoFrame::Builder()
+ .set_timestamp_ms(kTimestampMs)
+ .set_timestamp_rtp(kTimestampMs * 90)
+ .set_color_space(color_space)
+ .set_video_frame_buffer(kFrameBuffer)
+ .build();
+ encode_timer.OnEncodeStarted(frame);
+ encode_timer.FillTimingInfo(0, &image);
+ ASSERT_NE(image.ColorSpace(), nullptr);
+ EXPECT_EQ(color_space, *image.ColorSpace());
+}
+
+TEST(FrameEncodeMetadataWriterTest, CopiesPacketInfos) {
+ EncodedImage image;
+ const int64_t kTimestampMs = 123456;
+ FakeEncodedImageCallback sink;
+
+ FrameEncodeMetadataWriter encode_timer(&sink);
+ encode_timer.OnEncoderInit(VideoCodec());
+ // Any non-zero bitrate needed to be set before the first frame.
+ VideoBitrateAllocation bitrate_allocation;
+ bitrate_allocation.SetBitrate(0, 0, 500000);
+ encode_timer.OnSetRates(bitrate_allocation, 30);
+
+ RtpPacketInfos packet_infos = CreatePacketInfos(3);
+ image.SetTimestamp(static_cast<uint32_t>(kTimestampMs * 90));
+ VideoFrame frame = VideoFrame::Builder()
+ .set_timestamp_ms(kTimestampMs)
+ .set_timestamp_rtp(kTimestampMs * 90)
+ .set_packet_infos(packet_infos)
+ .set_video_frame_buffer(kFrameBuffer)
+ .build();
+ encode_timer.OnEncodeStarted(frame);
+ encode_timer.FillTimingInfo(0, &image);
+ EXPECT_EQ(image.PacketInfos().size(), 3U);
+}
+
+TEST(FrameEncodeMetadataWriterTest, DoesNotRewriteBitstreamWithoutCodecInfo) {
+ uint8_t buffer[] = {1, 2, 3};
+ auto image_buffer = EncodedImageBuffer::Create(buffer, sizeof(buffer));
+ EncodedImage image;
+ image.SetEncodedData(image_buffer);
+
+ FakeEncodedImageCallback sink;
+ FrameEncodeMetadataWriter encode_metadata_writer(&sink);
+ encode_metadata_writer.UpdateBitstream(nullptr, &image);
+ EXPECT_EQ(image.GetEncodedData(), image_buffer);
+ EXPECT_EQ(image.size(), sizeof(buffer));
+}
+
+TEST(FrameEncodeMetadataWriterTest, DoesNotRewriteVp8Bitstream) {
+ uint8_t buffer[] = {1, 2, 3};
+ auto image_buffer = EncodedImageBuffer::Create(buffer, sizeof(buffer));
+ EncodedImage image;
+ image.SetEncodedData(image_buffer);
+ CodecSpecificInfo codec_specific_info;
+ codec_specific_info.codecType = kVideoCodecVP8;
+
+ FakeEncodedImageCallback sink;
+ FrameEncodeMetadataWriter encode_metadata_writer(&sink);
+ encode_metadata_writer.UpdateBitstream(&codec_specific_info, &image);
+ EXPECT_EQ(image.GetEncodedData(), image_buffer);
+ EXPECT_EQ(image.size(), sizeof(buffer));
+}
+
+TEST(FrameEncodeMetadataWriterTest, RewritesH264BitstreamWithNonOptimalSps) {
+ const uint8_t kOriginalSps[] = {0, 0, 0, 1, H264::NaluType::kSps,
+ 0x00, 0x00, 0x03, 0x03, 0xF4,
+ 0x05, 0x03, 0xC7, 0xC0};
+ const uint8_t kRewrittenSps[] = {0, 0, 0, 1, H264::NaluType::kSps,
+ 0x00, 0x00, 0x03, 0x03, 0xF4,
+ 0x05, 0x03, 0xC7, 0xE0, 0x1B,
+ 0x41, 0x10, 0x8D, 0x00};
+
+ EncodedImage image;
+ image.SetEncodedData(
+ EncodedImageBuffer::Create(kOriginalSps, sizeof(kOriginalSps)));
+ image._frameType = VideoFrameType::kVideoFrameKey;
+
+ CodecSpecificInfo codec_specific_info;
+ codec_specific_info.codecType = kVideoCodecH264;
+
+ FakeEncodedImageCallback sink;
+ FrameEncodeMetadataWriter encode_metadata_writer(&sink);
+ encode_metadata_writer.UpdateBitstream(&codec_specific_info, &image);
+
+ EXPECT_THAT(std::vector<uint8_t>(image.data(), image.data() + image.size()),
+ testing::ElementsAreArray(kRewrittenSps));
+}
+
+} // namespace test
+} // namespace webrtc