blob: 9c879e3add430650c8d19f9abe3753463fd2ff8b [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_ =
36 encoded_image.timing_.flags != TimingFrameFlags::kInvalid;
mflodman351424e2017-08-10 02:43:14 -070037 return Result(Result::OK);
ilnik04f4d122017-06-19 07:18:55 -070038 };
39
Ilya Nikolaevskiyd79314f2017-10-23 10:45:37 +020040 void OnDroppedFrame(DropReason reason) override { ++num_frames_dropped_; }
41
ilnik04f4d122017-06-19 07:18:55 -070042 bool WasTimingFrame() { return last_frame_was_timing_; }
43
Ilya Nikolaevskiyd79314f2017-10-23 10:45:37 +020044 size_t GetNumFramesDropped() { return num_frames_dropped_; }
45
ilnik04f4d122017-06-19 07:18:55 -070046 private:
47 bool last_frame_was_timing_;
Ilya Nikolaevskiyd79314f2017-10-23 10:45:37 +020048 size_t num_frames_dropped_;
ilnik04f4d122017-06-19 07:18:55 -070049};
50
51enum class FrameType {
52 kNormal,
53 kTiming,
54 kDropped,
55};
56
57// Emulates |num_frames| on |num_streams| frames with capture timestamps
58// increased by 1 from 0. Size of each frame is between
59// |min_frame_size| and |max_frame_size|, outliers are counted relatevely to
60// |average_frame_sizes[]| for each stream.
61std::vector<std::vector<FrameType>> GetTimingFrames(
62 const int64_t delay_ms,
63 const size_t min_frame_size,
64 const size_t max_frame_size,
65 std::vector<size_t> average_frame_sizes,
66 const int num_streams,
67 const int num_frames) {
68 FakeEncodedImageCallback sink;
69 VCMEncodedFrameCallback callback(&sink, nullptr);
70 const size_t kFramerate = 30;
71 callback.SetTimingFramesThresholds(
72 {delay_ms, kDefaultOutlierFrameSizePercent});
73 callback.OnFrameRateChanged(kFramerate);
74 int s, i;
75 std::vector<std::vector<FrameType>> result(num_streams);
76 for (s = 0; s < num_streams; ++s)
77 callback.OnTargetBitrateChanged(average_frame_sizes[s] * kFramerate, s);
78 int64_t current_timestamp = 0;
79 for (i = 0; i < num_frames; ++i) {
80 current_timestamp += 1;
81 for (s = 0; s < num_streams; ++s) {
82 // every (5+s)-th frame is dropped on s-th stream by design.
83 bool dropped = i % (5 + s) == 0;
84
85 EncodedImage image;
86 CodecSpecificInfo codec_specific;
87 image._length = FrameSize(min_frame_size, max_frame_size, s, i);
88 image.capture_time_ms_ = current_timestamp;
89 codec_specific.codecType = kVideoCodecGeneric;
90 codec_specific.codecSpecific.generic.simulcast_idx = s;
91 callback.OnEncodeStarted(current_timestamp, s);
92 if (dropped) {
93 result[s].push_back(FrameType::kDropped);
94 continue;
95 }
96 callback.OnEncodedImage(image, &codec_specific, nullptr);
97 if (sink.WasTimingFrame()) {
98 result[s].push_back(FrameType::kTiming);
99 } else {
100 result[s].push_back(FrameType::kNormal);
101 }
102 }
103 }
104 return result;
105}
106} // namespace
107
108TEST(TestVCMEncodedFrameCallback, MarksTimingFramesPeriodicallyTogether) {
109 const int64_t kDelayMs = 29;
110 const size_t kMinFrameSize = 10;
111 const size_t kMaxFrameSize = 20;
112 const int kNumFrames = 1000;
113 const int kNumStreams = 3;
114 // No outliers as 1000 is larger than anything from range [10,20].
115 const std::vector<size_t> kAverageSize = {1000, 1000, 1000};
116 auto frames = GetTimingFrames(kDelayMs, kMinFrameSize, kMaxFrameSize,
117 kAverageSize, kNumStreams, kNumFrames);
118 // Timing frames should be tirggered every delayMs.
119 // As no outliers are expected, frames on all streams have to be
120 // marked together.
121 int last_timing_frame = -1;
122 for (int i = 0; i < kNumFrames; ++i) {
123 int num_normal = 0;
124 int num_timing = 0;
125 int num_dropped = 0;
126 for (int s = 0; s < kNumStreams; ++s) {
127 if (frames[s][i] == FrameType::kTiming) {
128 ++num_timing;
129 } else if (frames[s][i] == FrameType::kNormal) {
130 ++num_normal;
131 } else {
132 ++num_dropped;
133 }
134 }
135 // Can't have both normal and timing frames at the same timstamp.
136 EXPECT_TRUE(num_timing == 0 || num_normal == 0);
137 if (num_dropped < kNumStreams) {
138 if (last_timing_frame == -1 || i >= last_timing_frame + kDelayMs) {
139 // If didn't have timing frames for a period, current sent frame has to
140 // be one. No normal frames should be sent.
141 EXPECT_EQ(num_normal, 0);
142 } else {
143 // No unneeded timing frames should be sent.
144 EXPECT_EQ(num_timing, 0);
145 }
146 }
147 if (num_timing > 0)
148 last_timing_frame = i;
149 }
150}
151
152TEST(TestVCMEncodedFrameCallback, MarksOutliers) {
153 const int64_t kDelayMs = 29;
154 const size_t kMinFrameSize = 2495;
155 const size_t kMaxFrameSize = 2505;
156 const int kNumFrames = 1000;
157 const int kNumStreams = 3;
158 // Possible outliers as 1000 lies in range [995, 1005].
159 const std::vector<size_t> kAverageSize = {998, 1000, 1004};
160 auto frames = GetTimingFrames(kDelayMs, kMinFrameSize, kMaxFrameSize,
161 kAverageSize, kNumStreams, kNumFrames);
162 // All outliers should be marked.
163 for (int i = 0; i < kNumFrames; ++i) {
164 for (int s = 0; s < kNumStreams; ++s) {
165 if (FrameSize(kMinFrameSize, kMaxFrameSize, s, i) >=
166 kAverageSize[s] * kDefaultOutlierFrameSizePercent / 100) {
167 // Too big frame. May be dropped or timing, but not normal.
168 EXPECT_NE(frames[s][i], FrameType::kNormal);
169 }
170 }
171 }
172}
173
sprangba050a62017-08-18 02:51:12 -0700174TEST(TestVCMEncodedFrameCallback, NoTimingFrameIfNoEncodeStartTime) {
175 EncodedImage image;
176 CodecSpecificInfo codec_specific;
177 int64_t timestamp = 1;
178 image._length = 500;
179 image.capture_time_ms_ = timestamp;
180 codec_specific.codecType = kVideoCodecGeneric;
181 codec_specific.codecSpecific.generic.simulcast_idx = 0;
182 FakeEncodedImageCallback sink;
183 VCMEncodedFrameCallback callback(&sink, nullptr);
184 VideoCodec::TimingFrameTriggerThresholds thresholds;
185 thresholds.delay_ms = 1; // Make all frames timing frames.
186 callback.SetTimingFramesThresholds(thresholds);
187
188 // Verify a single frame works with encode start time set.
189 callback.OnEncodeStarted(timestamp, 0);
190 callback.OnEncodedImage(image, &codec_specific, nullptr);
191 EXPECT_TRUE(sink.WasTimingFrame());
192
193 // New frame, now skip OnEncodeStarted. Should not result in timing frame.
194 image.capture_time_ms_ = ++timestamp;
195 callback.OnEncodedImage(image, &codec_specific, nullptr);
196 EXPECT_FALSE(sink.WasTimingFrame());
197}
198
Ilya Nikolaevskiyd79314f2017-10-23 10:45:37 +0200199TEST(TestVCMEncodedFrameCallback, NotifiesAboutDroppedFrames) {
200 EncodedImage image;
201 CodecSpecificInfo codec_specific;
202 const int64_t timestamp1 = 100;
203 const int64_t timestamp2 = 110;
204 const int64_t timestamp3 = 120;
205 const int64_t timestamp4 = 130;
206 codec_specific.codecType = kVideoCodecGeneric;
207 codec_specific.codecSpecific.generic.simulcast_idx = 0;
208 FakeEncodedImageCallback sink;
209 VCMEncodedFrameCallback callback(&sink, nullptr);
210 callback.OnEncodeStarted(timestamp1, 0);
211 EXPECT_EQ(0u, sink.GetNumFramesDropped());
212 image.capture_time_ms_ = timestamp1;
213 callback.OnEncodedImage(image, &codec_specific, nullptr);
214 callback.OnEncodeStarted(timestamp2, 0);
215 // No OnEncodedImageCall for timestamp2. Yet, at this moment it's not known
216 // that frame with timestamp2 was dropped.
217 EXPECT_EQ(0u, sink.GetNumFramesDropped());
218 callback.OnEncodeStarted(timestamp3, 0);
219 image.capture_time_ms_ = timestamp3;
220 callback.OnEncodedImage(image, &codec_specific, nullptr);
221 EXPECT_EQ(1u, sink.GetNumFramesDropped());
222 callback.OnEncodeStarted(timestamp4, 0);
223 image.capture_time_ms_ = timestamp4;
224 callback.OnEncodedImage(image, &codec_specific, nullptr);
225 EXPECT_EQ(1u, sink.GetNumFramesDropped());
226}
227
ilnik04f4d122017-06-19 07:18:55 -0700228} // namespace test
229} // namespace webrtc