1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "cast/streaming/capture_recommendations.h"
6
7 #include "absl/types/optional.h"
8 #include "cast/streaming/answer_messages.h"
9 #include "cast/streaming/resolution.h"
10 #include "gmock/gmock.h"
11 #include "gtest/gtest.h"
12 #include "util/chrono_helpers.h"
13
14 namespace openscreen {
15 namespace cast {
16 namespace capture_recommendations {
17 namespace {
18
19 const Recommendations kDefaultRecommendations{
20 Audio{BitRateLimits{32000, 256000}, milliseconds(400), 2, 48000, 16000},
21 Video{BitRateLimits{300000, 1920 * 1080 * 30}, Resolution{320, 240},
22 Dimensions{1920, 1080, 30}, false, milliseconds(400),
23 1920 * 1080 * 30 / 8}};
24
25 const DisplayDescription kEmptyDescription{};
26
27 const DisplayDescription kValidOnlyResolution{
28 Dimensions{1024, 768, SimpleFraction{60, 1}}, absl::nullopt, absl::nullopt};
29
30 const DisplayDescription kValidOnlyAspectRatio{absl::nullopt, AspectRatio{4, 3},
31 absl::nullopt};
32
33 const DisplayDescription kValidOnlyAspectRatioSixteenNine{
34 absl::nullopt, AspectRatio{16, 9}, absl::nullopt};
35
36 const DisplayDescription kValidOnlyVariable{absl::nullopt, absl::nullopt,
37 AspectRatioConstraint::kVariable};
38
39 const DisplayDescription kInvalidOnlyFixed{absl::nullopt, absl::nullopt,
40 AspectRatioConstraint::kFixed};
41
42 const DisplayDescription kValidFixedAspectRatio{
43 absl::nullopt, AspectRatio{4, 3}, AspectRatioConstraint::kFixed};
44
45 const DisplayDescription kValidVariableAspectRatio{
46 absl::nullopt, AspectRatio{4, 3}, AspectRatioConstraint::kVariable};
47
48 const DisplayDescription kValidFixedMissingAspectRatio{
49 Dimensions{1024, 768, SimpleFraction{60, 1}}, absl::nullopt,
50 AspectRatioConstraint::kFixed};
51
52 const DisplayDescription kValidDisplayFhd{
53 Dimensions{1920, 1080, SimpleFraction{30, 1}}, AspectRatio{16, 9},
54 AspectRatioConstraint::kVariable};
55
56 const DisplayDescription kValidDisplayXga{
57 Dimensions{1024, 768, SimpleFraction{60, 1}}, AspectRatio{4, 3},
58 AspectRatioConstraint::kFixed};
59
60 const DisplayDescription kValidDisplayTiny{
61 Dimensions{300, 200, SimpleFraction{30, 1}}, AspectRatio{3, 2},
62 AspectRatioConstraint::kFixed};
63
64 const DisplayDescription kValidDisplayMismatched{
65 Dimensions{300, 200, SimpleFraction{30, 1}}, AspectRatio{3, 4},
66 AspectRatioConstraint::kFixed};
67
68 const Constraints kEmptyConstraints{};
69
70 const Constraints kValidConstraintsHighEnd{
71 {96100, 5, 96000, 500000, std::chrono::seconds(6)},
72 {6000000, Dimensions{640, 480, SimpleFraction{30, 1}},
73 Dimensions{3840, 2160, SimpleFraction{144, 1}}, 600000, 6000000,
74 std::chrono::seconds(6)}};
75
76 const Constraints kValidConstraintsLowEnd{
77 {22000, 2, 24000, 50000, std::chrono::seconds(1)},
78 {60000, Dimensions{120, 80, SimpleFraction{10, 1}},
79 Dimensions{1200, 800, SimpleFraction{30, 1}}, 100000, 1000000,
80 std::chrono::seconds(1)}};
81
82 } // namespace
83
TEST(CaptureRecommendationsTest,UsesDefaultsIfNoReceiverInformationAvailable)84 TEST(CaptureRecommendationsTest, UsesDefaultsIfNoReceiverInformationAvailable) {
85 EXPECT_EQ(kDefaultRecommendations, GetRecommendations(Answer{}));
86 }
87
TEST(CaptureRecommendationsTest,EmptyDisplayDescription)88 TEST(CaptureRecommendationsTest, EmptyDisplayDescription) {
89 Answer answer;
90 answer.display = kEmptyDescription;
91 EXPECT_EQ(kDefaultRecommendations, GetRecommendations(answer));
92 }
93
TEST(CaptureRecommendationsTest,OnlyResolution)94 TEST(CaptureRecommendationsTest, OnlyResolution) {
95 Recommendations expected = kDefaultRecommendations;
96 expected.video.maximum = Dimensions{1024, 768, 60.0};
97 expected.video.bit_rate_limits.maximum = 47185920;
98 Answer answer;
99 answer.display = kValidOnlyResolution;
100 EXPECT_EQ(expected, GetRecommendations(answer));
101 }
102
TEST(CaptureRecommendationsTest,OnlyAspectRatioFourThirds)103 TEST(CaptureRecommendationsTest, OnlyAspectRatioFourThirds) {
104 Recommendations expected = kDefaultRecommendations;
105 expected.video.minimum = Resolution{320, 240};
106 expected.video.maximum = Dimensions{1440, 1080, 30.0};
107 Answer answer;
108 answer.display = kValidOnlyAspectRatio;
109
110 EXPECT_EQ(expected, GetRecommendations(answer));
111 }
112
TEST(CaptureRecommendationsTest,OnlyAspectRatioSixteenNine)113 TEST(CaptureRecommendationsTest, OnlyAspectRatioSixteenNine) {
114 Recommendations expected = kDefaultRecommendations;
115 expected.video.minimum = Resolution{426, 240};
116 expected.video.maximum = Dimensions{1920, 1080, 30.0};
117 Answer answer;
118 answer.display = kValidOnlyAspectRatioSixteenNine;
119
120 EXPECT_EQ(expected, GetRecommendations(answer));
121 }
122
TEST(CaptureRecommendationsTest,OnlyAspectRatioConstraint)123 TEST(CaptureRecommendationsTest, OnlyAspectRatioConstraint) {
124 Recommendations expected = kDefaultRecommendations;
125 expected.video.supports_scaling = true;
126 Answer answer;
127 answer.display = kValidOnlyVariable;
128 EXPECT_EQ(expected, GetRecommendations(answer));
129 }
130
131 // It doesn't make sense to just provide a "fixed" aspect ratio with no
132 // other dimension information, so we just return default recommendations
133 // in this case and assume the sender will handle it elsewhere, e.g. on
134 // ANSWER message parsing.
TEST(CaptureRecommendationsTest,OnlyInvalidAspectRatioConstraint)135 TEST(CaptureRecommendationsTest, OnlyInvalidAspectRatioConstraint) {
136 Answer answer;
137 answer.display = kInvalidOnlyFixed;
138 EXPECT_EQ(kDefaultRecommendations, GetRecommendations(answer));
139 }
140
TEST(CaptureRecommendationsTest,FixedAspectRatioConstraint)141 TEST(CaptureRecommendationsTest, FixedAspectRatioConstraint) {
142 Recommendations expected = kDefaultRecommendations;
143 expected.video.minimum = Resolution{320, 240};
144 expected.video.maximum = Dimensions{1440, 1080, 30.0};
145 expected.video.supports_scaling = false;
146 Answer answer;
147 answer.display = kValidFixedAspectRatio;
148 EXPECT_EQ(expected, GetRecommendations(answer));
149 }
150
151 // Our behavior is actually the same whether the constraint is passed, we
152 // just percolate the constraint up to the capture devices so that intermediate
153 // frame sizes between minimum and maximum can be properly scaled.
TEST(CaptureRecommendationsTest,VariableAspectRatioConstraint)154 TEST(CaptureRecommendationsTest, VariableAspectRatioConstraint) {
155 Recommendations expected = kDefaultRecommendations;
156 expected.video.minimum = Resolution{320, 240};
157 expected.video.maximum = Dimensions{1440, 1080, 30.0};
158 expected.video.supports_scaling = true;
159 Answer answer;
160 answer.display = kValidVariableAspectRatio;
161 EXPECT_EQ(expected, GetRecommendations(answer));
162 }
163
TEST(CaptureRecommendationsTest,ResolutionWithFixedConstraint)164 TEST(CaptureRecommendationsTest, ResolutionWithFixedConstraint) {
165 Recommendations expected = kDefaultRecommendations;
166 expected.video.minimum = Resolution{320, 240};
167 expected.video.maximum = Dimensions{1024, 768, 60.0};
168 expected.video.supports_scaling = false;
169 expected.video.bit_rate_limits.maximum = 47185920;
170 Answer answer;
171 answer.display = kValidFixedMissingAspectRatio;
172 EXPECT_EQ(expected, GetRecommendations(answer));
173 }
174
TEST(CaptureRecommendationsTest,ExplicitFhdChangesMinimum)175 TEST(CaptureRecommendationsTest, ExplicitFhdChangesMinimum) {
176 Recommendations expected = kDefaultRecommendations;
177 expected.video.minimum = Resolution{426, 240};
178 expected.video.supports_scaling = true;
179 Answer answer;
180 answer.display = kValidDisplayFhd;
181 EXPECT_EQ(expected, GetRecommendations(answer));
182 }
183
TEST(CaptureRecommendationsTest,XgaResolution)184 TEST(CaptureRecommendationsTest, XgaResolution) {
185 Recommendations expected = kDefaultRecommendations;
186 expected.video.minimum = Resolution{320, 240};
187 expected.video.maximum = Dimensions{1024, 768, 60.0};
188 expected.video.supports_scaling = false;
189 expected.video.bit_rate_limits.maximum = 47185920;
190 Answer answer;
191 answer.display = kValidDisplayXga;
192 EXPECT_EQ(expected, GetRecommendations(answer));
193 }
194
TEST(CaptureRecommendationsTest,MismatchedDisplayAndAspectRatio)195 TEST(CaptureRecommendationsTest, MismatchedDisplayAndAspectRatio) {
196 Recommendations expected = kDefaultRecommendations;
197 expected.video.minimum = Resolution{150, 200};
198 expected.video.maximum = Dimensions{150, 200, 30.0};
199 expected.video.supports_scaling = false;
200 expected.video.bit_rate_limits.maximum = 300 * 200 * 30;
201 Answer answer;
202 answer.display = kValidDisplayMismatched;
203 EXPECT_EQ(expected, GetRecommendations(answer));
204 }
205
TEST(CaptureRecommendationsTest,TinyDisplay)206 TEST(CaptureRecommendationsTest, TinyDisplay) {
207 Recommendations expected = kDefaultRecommendations;
208 expected.video.minimum = Resolution{300, 200};
209 expected.video.maximum = Dimensions{300, 200, 30.0};
210 expected.video.supports_scaling = false;
211 expected.video.bit_rate_limits.maximum = 300 * 200 * 30;
212 Answer answer;
213 answer.display = kValidDisplayTiny;
214 EXPECT_EQ(expected, GetRecommendations(answer));
215 }
216
TEST(CaptureRecommendationsTest,EmptyConstraints)217 TEST(CaptureRecommendationsTest, EmptyConstraints) {
218 Answer answer;
219 answer.constraints = kEmptyConstraints;
220 EXPECT_EQ(kDefaultRecommendations, GetRecommendations(answer));
221 }
222
223 // Generally speaking, if the receiver gives us constraints higher than our
224 // defaults we will accept them, with the exception of maximum resolutions
225 // exceeding 1080P.
TEST(CaptureRecommendationsTest,HandlesHighEnd)226 TEST(CaptureRecommendationsTest, HandlesHighEnd) {
227 const Recommendations kExpected{
228 Audio{BitRateLimits{96000, 500000}, milliseconds(6000), 5, 96100, 16000},
229 Video{BitRateLimits{600000, 6000000}, Resolution{640, 480},
230 Dimensions{1920, 1080, 30}, false, milliseconds(6000), 6000000}};
231 Answer answer;
232 answer.constraints = kValidConstraintsHighEnd;
233 EXPECT_EQ(kExpected, GetRecommendations(answer));
234 }
235
236 // However, if the receiver gives us constraints lower than our minimum
237 // defaults, we will ignore them--they would result in an unacceptable cast
238 // experience.
TEST(CaptureRecommendationsTest,HandlesLowEnd)239 TEST(CaptureRecommendationsTest, HandlesLowEnd) {
240 const Recommendations kExpected{
241 Audio{BitRateLimits{32000, 50000}, milliseconds(1000), 2, 22000, 16000},
242 Video{BitRateLimits{300000, 1000000}, Resolution{320, 240},
243 Dimensions{1200, 800, 30}, false, milliseconds(1000), 60000}};
244 Answer answer;
245 answer.constraints = kValidConstraintsLowEnd;
246 EXPECT_EQ(kExpected, GetRecommendations(answer));
247 }
248
TEST(CaptureRecommendationsTest,HandlesTooSmallScreen)249 TEST(CaptureRecommendationsTest, HandlesTooSmallScreen) {
250 const Recommendations kExpected{
251 Audio{BitRateLimits{32000, 50000}, milliseconds(1000), 2, 22000, 16000},
252 Video{BitRateLimits{300000, 1000000}, Resolution{320, 240},
253 Dimensions{320, 240, 30}, false, milliseconds(1000), 60000}};
254 Answer answer;
255 answer.constraints = kValidConstraintsLowEnd;
256 answer.constraints->video.max_dimensions =
257 answer.constraints->video.min_resolution.value();
258 EXPECT_EQ(kExpected, GetRecommendations(answer));
259 }
260
TEST(CaptureRecommendationsTest,HandlesMinimumSizeScreen)261 TEST(CaptureRecommendationsTest, HandlesMinimumSizeScreen) {
262 const Recommendations kExpected{
263 Audio{BitRateLimits{32000, 50000}, milliseconds(1000), 2, 22000, 16000},
264 Video{BitRateLimits{300000, 1000000}, Resolution{320, 240},
265 Dimensions{320, 240, 30}, false, milliseconds(1000), 60000}};
266 Answer answer;
267 answer.constraints = kValidConstraintsLowEnd;
268 answer.constraints->video.max_dimensions =
269 Dimensions{320, 240, SimpleFraction{30, 1}};
270 EXPECT_EQ(kExpected, GetRecommendations(answer));
271 }
272
TEST(CaptureRecommendationsTest,UsesIntersectionOfDisplayAndConstraints)273 TEST(CaptureRecommendationsTest, UsesIntersectionOfDisplayAndConstraints) {
274 const Recommendations kExpected{
275 Audio{BitRateLimits{96000, 500000}, milliseconds(6000), 5, 96100, 16000},
276 Video{BitRateLimits{600000, 6000000}, Resolution{640, 480},
277 // Max resolution should be 1080P, since that's the display
278 // resolution. No reason to capture at 4K, even though the
279 // receiver supports it.
280 Dimensions{1920, 1080, 30}, true, milliseconds(6000), 6000000}};
281 Answer answer;
282 answer.display = kValidDisplayFhd;
283 answer.constraints = kValidConstraintsHighEnd;
284 EXPECT_EQ(kExpected, GetRecommendations(answer));
285 }
286
287 } // namespace capture_recommendations
288 } // namespace cast
289 } // namespace openscreen
290