xref: /aosp_15_r20/external/openscreen/cast/streaming/answer_messages.cc (revision 3f982cf4871df8771c9d4abe6e9a6f8d829b2736)
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