1 // Copyright 2019 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/answer_messages.h"
6
7 #include <utility>
8
9 #include "absl/strings/str_cat.h"
10 #include "absl/strings/str_split.h"
11 #include "platform/base/error.h"
12 #include "util/enum_name_table.h"
13 #include "util/json/json_helpers.h"
14 #include "util/osp_logging.h"
15 namespace openscreen {
16 namespace cast {
17
18 namespace {
19
20 /// Constraint properties.
21 // Audio constraints. See properties below.
22 static constexpr char kAudio[] = "audio";
23 // Video constraints. See properties below.
24 static constexpr char kVideo[] = "video";
25
26 // An optional field representing the minimum bits per second. If not specified
27 // by the receiver, the sender will use kDefaultAudioMinBitRate and
28 // kDefaultVideoMinBitRate, which represent the true operational minimum.
29 static constexpr char kMinBitRate[] = "minBitRate";
30 // 32kbps is sender default for audio minimum bit rate.
31 static constexpr int kDefaultAudioMinBitRate = 32 * 1000;
32 // 300kbps is sender default for video minimum bit rate.
33 static constexpr int kDefaultVideoMinBitRate = 300 * 1000;
34
35 // Maximum encoded bits per second. This is the lower of (1) the max capability
36 // of the decoder, or (2) the max data transfer rate.
37 static constexpr char kMaxBitRate[] = "maxBitRate";
38 // Maximum supported end-to-end latency, in milliseconds. Proportional to the
39 // size of the data buffers in the receiver.
40 static constexpr char kMaxDelay[] = "maxDelay";
41
42 /// Video constraint properties.
43 // Maximum pixel rate (width * height * framerate). Is often less than
44 // multiplying the fields in maxDimensions. This field is used to set the
45 // maximum processing rate.
46 static constexpr char kMaxPixelsPerSecond[] = "maxPixelsPerSecond";
47 // Minimum dimensions. If omitted, the sender will assume a reasonable minimum
48 // with the same aspect ratio as maxDimensions, as close to 320*180 as possible.
49 // Should reflect the true operational minimum.
50 static constexpr char kMinResolution[] = "minResolution";
51 // Maximum dimensions, not necessarily ideal dimensions.
52 static constexpr char kMaxDimensions[] = "maxDimensions";
53
54 /// Audio constraint properties.
55 // Maximum supported sampling frequency (not necessarily ideal).
56 static constexpr char kMaxSampleRate[] = "maxSampleRate";
57 // Maximum number of audio channels (1 is mono, 2 is stereo, etc.).
58 static constexpr char kMaxChannels[] = "maxChannels";
59
60 /// Display description properties
61 // If this optional field is included in the ANSWER message, the receiver is
62 // attached to a fixed display that has the given dimensions and frame rate
63 // configuration. These may exceed, be the same, or be less than the values in
64 // constraints. If undefined, we assume the display is not fixed (e.g. a Google
65 // Hangouts UI panel).
66 static constexpr char kDimensions[] = "dimensions";
67 // An optional field. When missing and dimensions are specified, the sender
68 // will assume square pixels and the dimensions imply the aspect ratio of the
69 // fixed display. WHen present and dimensions are also specified, implies the
70 // pixels are not square.
71 static constexpr char kAspectRatio[] = "aspectRatio";
72 // The delimeter used for the aspect ratio format ("A:B").
73 static constexpr char kAspectRatioDelimiter[] = ":";
74 // Sets the aspect ratio constraints. Value must be either "sender" or
75 // "receiver", see kScalingSender and kScalingReceiver below.
76 static constexpr char kScaling[] = "scaling";
77 // scaling = "sender" means that the sender must provide video frames of a fixed
78 // aspect ratio. In this case, the dimensions object must be passed or an error
79 // case will occur.
80 static constexpr char kScalingSender[] = "sender";
81 // scaling = "receiver" means that the sender may send arbitrarily sized frames,
82 // and the receiver will handle scaling and letterboxing as necessary.
83 static constexpr char kScalingReceiver[] = "receiver";
84
85 /// Answer properties.
86 // A number specifying the UDP port used for all streams in this session.
87 // Must have a value between kUdpPortMin and kUdpPortMax.
88 static constexpr char kUdpPort[] = "udpPort";
89 static constexpr int kUdpPortMin = 1;
90 static constexpr int kUdpPortMax = 65535;
91 // Numbers specifying the indexes chosen from the offer message.
92 static constexpr char kSendIndexes[] = "sendIndexes";
93 // uint32_t values specifying the RTP SSRC values used to send the RTCP feedback
94 // of the stream indicated in kSendIndexes.
95 static constexpr char kSsrcs[] = "ssrcs";
96 // Provides detailed maximum and minimum capabilities of the receiver for
97 // processing the selected streams. The sender may alter video resolution and
98 // frame rate throughout the session, and the constraints here determine how
99 // much data volume is allowed.
100 static constexpr char kConstraints[] = "constraints";
101 // Provides details about the display on the receiver.
102 static constexpr char kDisplay[] = "display";
103 // absl::optional array of numbers specifying the indexes of streams that will
104 // send event logs through RTCP.
105 static constexpr char kReceiverRtcpEventLog[] = "receiverRtcpEventLog";
106 // OPtional array of numbers specifying the indexes of streams that will use
107 // DSCP values specified in the OFFER message for RTCP packets.
108 static constexpr char kReceiverRtcpDscp[] = "receiverRtcpDscp";
109 // If this optional field is present the receiver supports the specific
110 // RTP extensions (such as adaptive playout delay).
111 static constexpr char kRtpExtensions[] = "rtpExtensions";
112
113 EnumNameTable<AspectRatioConstraint, 2> kAspectRatioConstraintNames{
114 {{kScalingReceiver, AspectRatioConstraint::kVariable},
115 {kScalingSender, AspectRatioConstraint::kFixed}}};
116
AspectRatioConstraintToJson(AspectRatioConstraint aspect_ratio)117 Json::Value AspectRatioConstraintToJson(AspectRatioConstraint aspect_ratio) {
118 return Json::Value(GetEnumName(kAspectRatioConstraintNames, aspect_ratio)
119 .value(kScalingSender));
120 }
121
TryParseAspectRatioConstraint(const Json::Value & value,AspectRatioConstraint * out)122 bool TryParseAspectRatioConstraint(const Json::Value& value,
123 AspectRatioConstraint* out) {
124 std::string aspect_ratio;
125 if (!json::TryParseString(value, &aspect_ratio)) {
126 return false;
127 }
128
129 ErrorOr<AspectRatioConstraint> constraint =
130 GetEnum(kAspectRatioConstraintNames, aspect_ratio);
131 if (constraint.is_error()) {
132 return false;
133 }
134 *out = constraint.value();
135 return true;
136 }
137
138 template <typename T>
PrimitiveVectorToJson(const std::vector<T> & vec)139 Json::Value PrimitiveVectorToJson(const std::vector<T>& vec) {
140 Json::Value array(Json::ValueType::arrayValue);
141 array.resize(vec.size());
142
143 for (Json::Value::ArrayIndex i = 0; i < vec.size(); ++i) {
144 array[i] = Json::Value(vec[i]);
145 }
146
147 return array;
148 }
149
150 template <typename T>
ParseOptional(const Json::Value & value,absl::optional<T> * out)151 bool ParseOptional(const Json::Value& value, absl::optional<T>* out) {
152 // It's fine if the value is empty.
153 if (!value) {
154 return true;
155 }
156 T tentative_out;
157 if (!T::TryParse(value, &tentative_out)) {
158 return false;
159 }
160 *out = tentative_out;
161 return true;
162 }
163
164 } // namespace
165
166 // static
TryParse(const Json::Value & value,AspectRatio * out)167 bool AspectRatio::TryParse(const Json::Value& value, AspectRatio* out) {
168 std::string parsed_value;
169 if (!json::TryParseString(value, &parsed_value)) {
170 return false;
171 }
172
173 std::vector<absl::string_view> fields =
174 absl::StrSplit(parsed_value, kAspectRatioDelimiter);
175 if (fields.size() != 2) {
176 return false;
177 }
178
179 if (!absl::SimpleAtoi(fields[0], &out->width) ||
180 !absl::SimpleAtoi(fields[1], &out->height)) {
181 return false;
182 }
183 return out->IsValid();
184 }
185
IsValid() const186 bool AspectRatio::IsValid() const {
187 return width > 0 && height > 0;
188 }
189
190 // static
TryParse(const Json::Value & root,AudioConstraints * out)191 bool AudioConstraints::TryParse(const Json::Value& root,
192 AudioConstraints* out) {
193 if (!json::TryParseInt(root[kMaxSampleRate], &(out->max_sample_rate)) ||
194 !json::TryParseInt(root[kMaxChannels], &(out->max_channels)) ||
195 !json::TryParseInt(root[kMaxBitRate], &(out->max_bit_rate))) {
196 return false;
197 }
198
199 std::chrono::milliseconds max_delay;
200 if (json::TryParseMilliseconds(root[kMaxDelay], &max_delay)) {
201 out->max_delay = max_delay;
202 }
203
204 if (!json::TryParseInt(root[kMinBitRate], &(out->min_bit_rate))) {
205 out->min_bit_rate = kDefaultAudioMinBitRate;
206 }
207 return out->IsValid();
208 }
209
ToJson() const210 Json::Value AudioConstraints::ToJson() const {
211 OSP_DCHECK(IsValid());
212 Json::Value root;
213 root[kMaxSampleRate] = max_sample_rate;
214 root[kMaxChannels] = max_channels;
215 root[kMinBitRate] = min_bit_rate;
216 root[kMaxBitRate] = max_bit_rate;
217 if (max_delay.has_value()) {
218 root[kMaxDelay] = Json::Value::Int64(max_delay->count());
219 }
220 return root;
221 }
222
IsValid() const223 bool AudioConstraints::IsValid() const {
224 return max_sample_rate > 0 && max_channels > 0 && min_bit_rate > 0 &&
225 max_bit_rate >= min_bit_rate;
226 }
227
228 // static
TryParse(const Json::Value & root,VideoConstraints * out)229 bool VideoConstraints::TryParse(const Json::Value& root,
230 VideoConstraints* out) {
231 if (!Dimensions::TryParse(root[kMaxDimensions], &(out->max_dimensions)) ||
232 !json::TryParseInt(root[kMaxBitRate], &(out->max_bit_rate)) ||
233 !ParseOptional<Dimensions>(root[kMinResolution],
234 &(out->min_resolution))) {
235 return false;
236 }
237
238 std::chrono::milliseconds max_delay;
239 if (json::TryParseMilliseconds(root[kMaxDelay], &max_delay)) {
240 out->max_delay = max_delay;
241 }
242
243 double max_pixels_per_second;
244 if (json::TryParseDouble(root[kMaxPixelsPerSecond], &max_pixels_per_second)) {
245 out->max_pixels_per_second = max_pixels_per_second;
246 }
247
248 if (!json::TryParseInt(root[kMinBitRate], &(out->min_bit_rate))) {
249 out->min_bit_rate = kDefaultVideoMinBitRate;
250 }
251 return out->IsValid();
252 }
253
IsValid() const254 bool VideoConstraints::IsValid() const {
255 return max_pixels_per_second > 0 && min_bit_rate > 0 &&
256 max_bit_rate > min_bit_rate &&
257 (!max_delay.has_value() || max_delay->count() > 0) &&
258 max_dimensions.IsValid() &&
259 (!min_resolution.has_value() || min_resolution->IsValid()) &&
260 max_dimensions.frame_rate.numerator() > 0;
261 }
262
ToJson() const263 Json::Value VideoConstraints::ToJson() const {
264 OSP_DCHECK(IsValid());
265 Json::Value root;
266 root[kMaxDimensions] = max_dimensions.ToJson();
267 root[kMinBitRate] = min_bit_rate;
268 root[kMaxBitRate] = max_bit_rate;
269 if (max_pixels_per_second.has_value()) {
270 root[kMaxPixelsPerSecond] = max_pixels_per_second.value();
271 }
272
273 if (min_resolution.has_value()) {
274 root[kMinResolution] = min_resolution->ToJson();
275 }
276
277 if (max_delay.has_value()) {
278 root[kMaxDelay] = Json::Value::Int64(max_delay->count());
279 }
280 return root;
281 }
282
283 // static
TryParse(const Json::Value & root,Constraints * out)284 bool Constraints::TryParse(const Json::Value& root, Constraints* out) {
285 if (!AudioConstraints::TryParse(root[kAudio], &(out->audio)) ||
286 !VideoConstraints::TryParse(root[kVideo], &(out->video))) {
287 return false;
288 }
289 return out->IsValid();
290 }
291
IsValid() const292 bool Constraints::IsValid() const {
293 return audio.IsValid() && video.IsValid();
294 }
295
ToJson() const296 Json::Value Constraints::ToJson() const {
297 OSP_DCHECK(IsValid());
298 Json::Value root;
299 root[kAudio] = audio.ToJson();
300 root[kVideo] = video.ToJson();
301 return root;
302 }
303
304 // static
TryParse(const Json::Value & root,DisplayDescription * out)305 bool DisplayDescription::TryParse(const Json::Value& root,
306 DisplayDescription* out) {
307 if (!ParseOptional<Dimensions>(root[kDimensions], &(out->dimensions)) ||
308 !ParseOptional<AspectRatio>(root[kAspectRatio], &(out->aspect_ratio))) {
309 return false;
310 }
311
312 AspectRatioConstraint constraint;
313 if (TryParseAspectRatioConstraint(root[kScaling], &constraint)) {
314 out->aspect_ratio_constraint =
315 absl::optional<AspectRatioConstraint>(std::move(constraint));
316 } else {
317 out->aspect_ratio_constraint = absl::nullopt;
318 }
319
320 return out->IsValid();
321 }
322
IsValid() const323 bool DisplayDescription::IsValid() const {
324 // At least one of the properties must be set, and if a property is set
325 // it must be valid.
326 if (aspect_ratio.has_value() && !aspect_ratio->IsValid()) {
327 return false;
328 }
329
330 if (dimensions.has_value() && !dimensions->IsValid()) {
331 return false;
332 }
333
334 // Sender behavior is undefined if the aspect ratio is fixed but no
335 // dimensions or aspect ratio are provided.
336 if (aspect_ratio_constraint.has_value() &&
337 (aspect_ratio_constraint.value() == AspectRatioConstraint::kFixed) &&
338 !dimensions.has_value() && !aspect_ratio.has_value()) {
339 return false;
340 }
341 return aspect_ratio.has_value() || dimensions.has_value() ||
342 aspect_ratio_constraint.has_value();
343 }
344
ToJson() const345 Json::Value DisplayDescription::ToJson() const {
346 OSP_DCHECK(IsValid());
347 Json::Value root;
348 if (aspect_ratio.has_value()) {
349 root[kAspectRatio] = absl::StrCat(
350 aspect_ratio->width, kAspectRatioDelimiter, aspect_ratio->height);
351 }
352 if (dimensions.has_value()) {
353 root[kDimensions] = dimensions->ToJson();
354 }
355 if (aspect_ratio_constraint.has_value()) {
356 root[kScaling] =
357 AspectRatioConstraintToJson(aspect_ratio_constraint.value());
358 }
359 return root;
360 }
361
ParseAndValidate(const Json::Value & value,Answer * out)362 bool Answer::ParseAndValidate(const Json::Value& value, Answer* out) {
363 return TryParse(value, out);
364 }
365
TryParse(const Json::Value & root,Answer * out)366 bool Answer::TryParse(const Json::Value& root, Answer* out) {
367 if (!json::TryParseInt(root[kUdpPort], &(out->udp_port)) ||
368 !json::TryParseIntArray(root[kSendIndexes], &(out->send_indexes)) ||
369 !json::TryParseUintArray(root[kSsrcs], &(out->ssrcs)) ||
370 !ParseOptional<Constraints>(root[kConstraints], &(out->constraints)) ||
371 !ParseOptional<DisplayDescription>(root[kDisplay], &(out->display))) {
372 return false;
373 }
374
375 // These function set to empty array if not present, so we can ignore
376 // the return value for optional values.
377 json::TryParseIntArray(root[kReceiverRtcpEventLog],
378 &(out->receiver_rtcp_event_log));
379 json::TryParseIntArray(root[kReceiverRtcpDscp], &(out->receiver_rtcp_dscp));
380 json::TryParseStringArray(root[kRtpExtensions], &(out->rtp_extensions));
381
382 return out->IsValid();
383 }
384
IsValid() const385 bool Answer::IsValid() const {
386 if (ssrcs.empty() || send_indexes.empty()) {
387 return false;
388 }
389
390 // We don't know what the indexes used in the offer were here, so we just
391 // sanity check.
392 for (const int index : send_indexes) {
393 if (index < 0) {
394 return false;
395 }
396 }
397 if (constraints.has_value() && !constraints->IsValid()) {
398 return false;
399 }
400 if (display.has_value() && !display->IsValid()) {
401 return false;
402 }
403 return kUdpPortMin <= udp_port && udp_port <= kUdpPortMax;
404 }
405
ToJson() const406 Json::Value Answer::ToJson() const {
407 OSP_DCHECK(IsValid());
408 Json::Value root;
409 if (constraints.has_value()) {
410 root[kConstraints] = constraints->ToJson();
411 }
412 if (display.has_value()) {
413 root[kDisplay] = display->ToJson();
414 }
415 root[kUdpPort] = udp_port;
416 root[kSendIndexes] = PrimitiveVectorToJson(send_indexes);
417 root[kSsrcs] = PrimitiveVectorToJson(ssrcs);
418 // Some sender do not handle empty array properly, so we omit these fields
419 // if they are empty.
420 if (!receiver_rtcp_event_log.empty()) {
421 root[kReceiverRtcpEventLog] =
422 PrimitiveVectorToJson(receiver_rtcp_event_log);
423 }
424 if (!receiver_rtcp_dscp.empty()) {
425 root[kReceiverRtcpDscp] = PrimitiveVectorToJson(receiver_rtcp_dscp);
426 }
427 if (!rtp_extensions.empty()) {
428 root[kRtpExtensions] = PrimitiveVectorToJson(rtp_extensions);
429 }
430 return root;
431 }
432
433 } // namespace cast
434 } // namespace openscreen
435