blob: 48c152f32accd6c12908763e2b8e983044445bc4 [file] [log] [blame]
ilnik04f4d122017-06-19 07:18:55 -07001/*
2 * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11#include <vector>
12
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020013#include "modules/video_coding/encoded_frame.h"
14#include "modules/video_coding/generic_encoder.h"
15#include "modules/video_coding/include/video_coding_defines.h"
16#include "test/gtest.h"
ilnik04f4d122017-06-19 07:18:55 -070017
18namespace webrtc {
19namespace test {
20namespace {
21inline size_t FrameSize(const size_t& min_frame_size,
22 const size_t& max_frame_size,
23 const int& s,
24 const int& i) {
25 return min_frame_size + (s + 1) * i % (max_frame_size - min_frame_size);
26}
27
28class FakeEncodedImageCallback : public EncodedImageCallback {
29 public:
Ilya Nikolaevskiyd79314f2017-10-23 10:45:37 +020030 FakeEncodedImageCallback()
31 : last_frame_was_timing_(false), num_frames_dropped_(0) {}
ilnik04f4d122017-06-19 07:18:55 -070032 Result OnEncodedImage(const EncodedImage& encoded_image,
33 const CodecSpecificInfo* codec_specific_info,
34 const RTPFragmentationHeader* fragmentation) override {
sprangba050a62017-08-18 02:51:12 -070035 last_frame_was_timing_ =
Niels Möllerc241af92017-11-10 08:51:29 +010036 encoded_image.timing_.flags != TimingFrameFlags::kInvalid &&
37 encoded_image.timing_.flags != TimingFrameFlags::kNotTriggered;
mflodman351424e2017-08-10 02:43:14 -070038 return Result(Result::OK);
ilnik04f4d122017-06-19 07:18:55 -070039 };
40
Ilya Nikolaevskiyd79314f2017-10-23 10:45:37 +020041 void OnDroppedFrame(DropReason reason) override { ++num_frames_dropped_; }
42
ilnik04f4d122017-06-19 07:18:55 -070043 bool WasTimingFrame() { return last_frame_was_timing_; }
44
Ilya Nikolaevskiyd79314f2017-10-23 10:45:37 +020045 size_t GetNumFramesDropped() { return num_frames_dropped_; }
46
ilnik04f4d122017-06-19 07:18:55 -070047 private:
48 bool last_frame_was_timing_;
Ilya Nikolaevskiyd79314f2017-10-23 10:45:37 +020049 size_t num_frames_dropped_;
ilnik04f4d122017-06-19 07:18:55 -070050};
51
52enum class FrameType {
53 kNormal,
54 kTiming,
55 kDropped,
56};
57
58// Emulates |num_frames| on |num_streams| frames with capture timestamps
59// increased by 1 from 0. Size of each frame is between
60// |min_frame_size| and |max_frame_size|, outliers are counted relatevely to
61// |average_frame_sizes[]| for each stream.
62std::vector<std::vector<FrameType>> GetTimingFrames(
63 const int64_t delay_ms,
64 const size_t min_frame_size,
65 const size_t max_frame_size,
66 std::vector<size_t> average_frame_sizes,
67 const int num_streams,
68 const int num_frames) {
69 FakeEncodedImageCallback sink;
70 VCMEncodedFrameCallback callback(&sink, nullptr);
71 const size_t kFramerate = 30;
72 callback.SetTimingFramesThresholds(
73 {delay_ms, kDefaultOutlierFrameSizePercent});
74 callback.OnFrameRateChanged(kFramerate);
75 int s, i;
76 std::vector<std::vector<FrameType>> result(num_streams);
77 for (s = 0; s < num_streams; ++s)
78 callback.OnTargetBitrateChanged(average_frame_sizes[s] * kFramerate, s);
79 int64_t current_timestamp = 0;
80 for (i = 0; i < num_frames; ++i) {
81 current_timestamp += 1;
82 for (s = 0; s < num_streams; ++s) {
83 // every (5+s)-th frame is dropped on s-th stream by design.
84 bool dropped = i % (5 + s) == 0;
85
86 EncodedImage image;
87 CodecSpecificInfo codec_specific;
88 image._length = FrameSize(min_frame_size, max_frame_size, s, i);
89 image.capture_time_ms_ = current_timestamp;
90 codec_specific.codecType = kVideoCodecGeneric;
91 codec_specific.codecSpecific.generic.simulcast_idx = s;
92 callback.OnEncodeStarted(current_timestamp, s);
93 if (dropped) {
94 result[s].push_back(FrameType::kDropped);
95 continue;
96 }
97 callback.OnEncodedImage(image, &codec_specific, nullptr);
98 if (sink.WasTimingFrame()) {
99 result[s].push_back(FrameType::kTiming);
100 } else {
101 result[s].push_back(FrameType::kNormal);
102 }
103 }
104 }
105 return result;
106}
107} // namespace
108
109TEST(TestVCMEncodedFrameCallback, MarksTimingFramesPeriodicallyTogether) {
110 const int64_t kDelayMs = 29;
111 const size_t kMinFrameSize = 10;
112 const size_t kMaxFrameSize = 20;
113 const int kNumFrames = 1000;
114 const int kNumStreams = 3;
115 // No outliers as 1000 is larger than anything from range [10,20].
116 const std::vector<size_t> kAverageSize = {1000, 1000, 1000};
117 auto frames = GetTimingFrames(kDelayMs, kMinFrameSize, kMaxFrameSize,
118 kAverageSize, kNumStreams, kNumFrames);
119 // Timing frames should be tirggered every delayMs.
120 // As no outliers are expected, frames on all streams have to be
121 // marked together.
122 int last_timing_frame = -1;
123 for (int i = 0; i < kNumFrames; ++i) {
124 int num_normal = 0;
125 int num_timing = 0;
126 int num_dropped = 0;
127 for (int s = 0; s < kNumStreams; ++s) {
128 if (frames[s][i] == FrameType::kTiming) {
129 ++num_timing;
130 } else if (frames[s][i] == FrameType::kNormal) {
131 ++num_normal;
132 } else {
133 ++num_dropped;
134 }
135 }
136 // Can't have both normal and timing frames at the same timstamp.
137 EXPECT_TRUE(num_timing == 0 || num_normal == 0);
138 if (num_dropped < kNumStreams) {
139 if (last_timing_frame == -1 || i >= last_timing_frame + kDelayMs) {
140 // If didn't have timing frames for a period, current sent frame has to
141 // be one. No normal frames should be sent.
142 EXPECT_EQ(num_normal, 0);
143 } else {
144 // No unneeded timing frames should be sent.
145 EXPECT_EQ(num_timing, 0);
146 }
147 }
148 if (num_timing > 0)
149 last_timing_frame = i;
150 }
151}
152
153TEST(TestVCMEncodedFrameCallback, MarksOutliers) {
154 const int64_t kDelayMs = 29;
155 const size_t kMinFrameSize = 2495;
156 const size_t kMaxFrameSize = 2505;
157 const int kNumFrames = 1000;
158 const int kNumStreams = 3;
159 // Possible outliers as 1000 lies in range [995, 1005].
160 const std::vector<size_t> kAverageSize = {998, 1000, 1004};
161 auto frames = GetTimingFrames(kDelayMs, kMinFrameSize, kMaxFrameSize,
162 kAverageSize, kNumStreams, kNumFrames);
163 // All outliers should be marked.
164 for (int i = 0; i < kNumFrames; ++i) {
165 for (int s = 0; s < kNumStreams; ++s) {
166 if (FrameSize(kMinFrameSize, kMaxFrameSize, s, i) >=
167 kAverageSize[s] * kDefaultOutlierFrameSizePercent / 100) {
168 // Too big frame. May be dropped or timing, but not normal.
169 EXPECT_NE(frames[s][i], FrameType::kNormal);
170 }
171 }
172 }
173}
174
sprangba050a62017-08-18 02:51:12 -0700175TEST(TestVCMEncodedFrameCallback, NoTimingFrameIfNoEncodeStartTime) {
176 EncodedImage image;
177 CodecSpecificInfo codec_specific;
178 int64_t timestamp = 1;
179 image._length = 500;
180 image.capture_time_ms_ = timestamp;
181 codec_specific.codecType = kVideoCodecGeneric;
182 codec_specific.codecSpecific.generic.simulcast_idx = 0;
183 FakeEncodedImageCallback sink;
184 VCMEncodedFrameCallback callback(&sink, nullptr);
185 VideoCodec::TimingFrameTriggerThresholds thresholds;
186 thresholds.delay_ms = 1; // Make all frames timing frames.
187 callback.SetTimingFramesThresholds(thresholds);
Ilya Nikolaevskiye0da9ea2017-11-08 14:39:02 +0100188 callback.OnTargetBitrateChanged(500, 0);
sprangba050a62017-08-18 02:51:12 -0700189
190 // Verify a single frame works with encode start time set.
191 callback.OnEncodeStarted(timestamp, 0);
192 callback.OnEncodedImage(image, &codec_specific, nullptr);
193 EXPECT_TRUE(sink.WasTimingFrame());
194
195 // New frame, now skip OnEncodeStarted. Should not result in timing frame.
196 image.capture_time_ms_ = ++timestamp;
197 callback.OnEncodedImage(image, &codec_specific, nullptr);
198 EXPECT_FALSE(sink.WasTimingFrame());
199}
200
Ilya Nikolaevskiyd79314f2017-10-23 10:45:37 +0200201TEST(TestVCMEncodedFrameCallback, NotifiesAboutDroppedFrames) {
202 EncodedImage image;
203 CodecSpecificInfo codec_specific;
204 const int64_t timestamp1 = 100;
205 const int64_t timestamp2 = 110;
206 const int64_t timestamp3 = 120;
207 const int64_t timestamp4 = 130;
208 codec_specific.codecType = kVideoCodecGeneric;
209 codec_specific.codecSpecific.generic.simulcast_idx = 0;
210 FakeEncodedImageCallback sink;
211 VCMEncodedFrameCallback callback(&sink, nullptr);
Ilya Nikolaevskiye0da9ea2017-11-08 14:39:02 +0100212 // Any non-zero bitrate needed to be set before the first frame.
213 callback.OnTargetBitrateChanged(500, 0);
Ilya Nikolaevskiyd79314f2017-10-23 10:45:37 +0200214 callback.OnEncodeStarted(timestamp1, 0);
215 EXPECT_EQ(0u, sink.GetNumFramesDropped());
216 image.capture_time_ms_ = timestamp1;
217 callback.OnEncodedImage(image, &codec_specific, nullptr);
218 callback.OnEncodeStarted(timestamp2, 0);
219 // No OnEncodedImageCall for timestamp2. Yet, at this moment it's not known
220 // that frame with timestamp2 was dropped.
221 EXPECT_EQ(0u, sink.GetNumFramesDropped());
222 callback.OnEncodeStarted(timestamp3, 0);
223 image.capture_time_ms_ = timestamp3;
224 callback.OnEncodedImage(image, &codec_specific, nullptr);
225 EXPECT_EQ(1u, sink.GetNumFramesDropped());
226 callback.OnEncodeStarted(timestamp4, 0);
227 image.capture_time_ms_ = timestamp4;
228 callback.OnEncodedImage(image, &codec_specific, nullptr);
229 EXPECT_EQ(1u, sink.GetNumFramesDropped());
230}
231
ilnik04f4d122017-06-19 07:18:55 -0700232} // namespace test
233} // namespace webrtc