aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJordan Bayles <jophba@chromium.org>2021-04-07 13:43:27 -0700
committerCommit Bot <commit-bot@chromium.org>2021-04-08 20:43:20 +0000
commit46b7d128bc08fceae9d109c0f675bc98c73ddda9 (patch)
tree9ec05f69751444bddae2ca7ffd9ef0397e691fbf
parent98caf900d4681e679b7a6f29df02ae04f7603d55 (diff)
downloadopenscreen-46b7d128bc08fceae9d109c0f675bc98c73ddda9.tar.gz
[Cast] Cleanup Resolution + ReceiverSession
This patch changes the way Dimensions and Resolution objects are used in Open Screen--consolidating them into just one set of Dimensions and Resolution objects that can be used throughout the library, and updates the mirroring control protocol specification to be crisper about the difference between Resolution objects and Dimensions objects--and removing frame rates where they don't really make sense. As part of this cleanup, it also refactors the ReceiverSession to not expose the Answer messaging system at all, instead using new publicly exposed types. Finally, the capture recommendations are removed from the Receiver side, in favor of shared constants. Bug: b/184186374 Change-Id: I5e6a1c6798d07cb7f388ff5179c0bfa325d89229 Reviewed-on: https://chromium-review.googlesource.com/c/openscreen/+/2808336 Reviewed-by: Ryan Keane <rwkeane@google.com> Commit-Queue: Jordan Bayles <jophba@chromium.org>
-rw-r--r--cast/protocol/castv2/streaming_examples/answer.json2
-rw-r--r--cast/protocol/castv2/streaming_schema.json2
-rw-r--r--cast/standalone_sender/looping_file_cast_agent.cc2
-rw-r--r--cast/streaming/BUILD.gn2
-rw-r--r--cast/streaming/answer_messages.cc46
-rw-r--r--cast/streaming/answer_messages.h13
-rw-r--r--cast/streaming/answer_messages_unittest.cc65
-rw-r--r--cast/streaming/capture_configs.h29
-rw-r--r--cast/streaming/capture_recommendations.cc75
-rw-r--r--cast/streaming/capture_recommendations.h30
-rw-r--r--cast/streaming/capture_recommendations_unittest.cc103
-rw-r--r--cast/streaming/constants.h25
-rw-r--r--cast/streaming/message_fields.h1
-rw-r--r--cast/streaming/offer_messages.cc54
-rw-r--r--cast/streaming/offer_messages.h7
-rw-r--r--cast/streaming/offer_messages_unittest.cc2
-rw-r--r--cast/streaming/receiver_session.cc90
-rw-r--r--cast/streaming/receiver_session.h96
-rw-r--r--cast/streaming/receiver_session_unittest.cc38
-rw-r--r--cast/streaming/resolution.cc117
-rw-r--r--cast/streaming/resolution.h60
-rw-r--r--cast/streaming/sender_session.cc58
-rw-r--r--cast/streaming/sender_session_unittest.cc26
-rw-r--r--util/simple_fraction.cc32
-rw-r--r--util/simple_fraction.h25
25 files changed, 557 insertions, 443 deletions
diff --git a/cast/protocol/castv2/streaming_examples/answer.json b/cast/protocol/castv2/streaming_examples/answer.json
index 73c45ea2..1c95b394 100644
--- a/cast/protocol/castv2/streaming_examples/answer.json
+++ b/cast/protocol/castv2/streaming_examples/answer.json
@@ -16,7 +16,7 @@
},
"video": {
"maxPixelsPerSecond": 62208000,
- "minDimensions": {"width": 320, "height": 240, "frameRate": "23/3"},
+ "minResolution": {"width": 320, "height": 240},
"maxDimensions": {"width": 1920, "height": 1080, "frameRate": "60"},
"minBitRate": 300000,
"maxBitRate": 10000000,
diff --git a/cast/protocol/castv2/streaming_schema.json b/cast/protocol/castv2/streaming_schema.json
index 392d135c..a967264d 100644
--- a/cast/protocol/castv2/streaming_schema.json
+++ b/cast/protocol/castv2/streaming_schema.json
@@ -115,7 +115,7 @@
"video_constraints": {
"properties": {
"maxPixelsPerSecond": {"type": "number", "minimum": 0},
- "minDimensions": {"$ref": "#/definitions/dimensions"},
+ "minResolution": {"$ref": "#/definitions/resolution"},
"maxDimensions": {"$ref": "#/definitions/dimensions"},
"minBitRate": {"type": "integer", "minimum": 300000},
"maxBitRate": {"type": "integer", "minimum": 300000},
diff --git a/cast/standalone_sender/looping_file_cast_agent.cc b/cast/standalone_sender/looping_file_cast_agent.cc
index 8c77577b..4608465a 100644
--- a/cast/standalone_sender/looping_file_cast_agent.cc
+++ b/cast/standalone_sender/looping_file_cast_agent.cc
@@ -291,7 +291,7 @@ void LoopingFileCastAgent::CreateAndStartSession() {
video_config.max_bit_rate =
connection_settings_->max_bitrate - audio_config.bit_rate;
// Use default display resolution of 1080P.
- video_config.resolutions.emplace_back(DisplayResolution{});
+ video_config.resolutions.emplace_back(Resolution{});
OSP_VLOG << "Starting session negotiation.";
const Error negotiation_error =
diff --git a/cast/streaming/BUILD.gn b/cast/streaming/BUILD.gn
index 9f7d7252..27e9dd8a 100644
--- a/cast/streaming/BUILD.gn
+++ b/cast/streaming/BUILD.gn
@@ -40,6 +40,8 @@ source_set("common") {
"packet_util.h",
"receiver_message.cc",
"receiver_message.h",
+ "resolution.cc",
+ "resolution.h",
"rpc_broker.cc",
"rpc_broker.h",
"rtcp_common.cc",
diff --git a/cast/streaming/answer_messages.cc b/cast/streaming/answer_messages.cc
index 906e8901..1be73869 100644
--- a/cast/streaming/answer_messages.cc
+++ b/cast/streaming/answer_messages.cc
@@ -47,7 +47,7 @@ static constexpr char kMaxPixelsPerSecond[] = "maxPixelsPerSecond";
// Minimum dimensions. If omitted, the sender will assume a reasonable minimum
// with the same aspect ratio as maxDimensions, as close to 320*180 as possible.
// Should reflect the true operational minimum.
-static constexpr char kMinDimensions[] = "minDimensions";
+static constexpr char kMinResolution[] = "minResolution";
// Maximum dimensions, not necessarily ideal dimensions.
static constexpr char kMaxDimensions[] = "maxDimensions";
@@ -57,15 +57,6 @@ static constexpr char kMaxSampleRate[] = "maxSampleRate";
// Maximum number of audio channels (1 is mono, 2 is stereo, etc.).
static constexpr char kMaxChannels[] = "maxChannels";
-/// Dimension properties.
-// Width in pixels.
-static constexpr char kWidth[] = "width";
-// Height in pixels.
-static constexpr char kHeight[] = "height";
-// Frame rate as a rational decimal number or fraction.
-// E.g. 30 and "3000/1001" are both valid representations.
-static constexpr char kFrameRate[] = "frameRate";
-
/// Display description properties
// If this optional field is included in the ANSWER message, the receiver is
// attached to a fixed display that has the given dimensions and frame rate
@@ -243,37 +234,14 @@ bool AudioConstraints::IsValid() const {
max_bit_rate >= min_bit_rate;
}
-bool Dimensions::ParseAndValidate(const Json::Value& root, Dimensions* out) {
- if (!json::ParseAndValidateInt(root[kWidth], &(out->width)) ||
- !json::ParseAndValidateInt(root[kHeight], &(out->height)) ||
- !json::ParseAndValidateSimpleFraction(root[kFrameRate],
- &(out->frame_rate))) {
- return false;
- }
- return out->IsValid();
-}
-
-bool Dimensions::IsValid() const {
- return width > 0 && height > 0 && frame_rate.is_positive();
-}
-
-Json::Value Dimensions::ToJson() const {
- OSP_DCHECK(IsValid());
- Json::Value root;
- root[kWidth] = width;
- root[kHeight] = height;
- root[kFrameRate] = frame_rate.ToString();
- return root;
-}
-
// static
bool VideoConstraints::ParseAndValidate(const Json::Value& root,
VideoConstraints* out) {
if (!Dimensions::ParseAndValidate(root[kMaxDimensions],
&(out->max_dimensions)) ||
!json::ParseAndValidateInt(root[kMaxBitRate], &(out->max_bit_rate)) ||
- !ParseOptional<Dimensions>(root[kMinDimensions],
- &(out->min_dimensions))) {
+ !ParseOptional<Dimensions>(root[kMinResolution],
+ &(out->min_resolution))) {
return false;
}
@@ -299,8 +267,8 @@ bool VideoConstraints::IsValid() const {
max_bit_rate > min_bit_rate &&
(!max_delay.has_value() || max_delay->count() > 0) &&
max_dimensions.IsValid() &&
- (!min_dimensions.has_value() || min_dimensions->IsValid()) &&
- max_dimensions.frame_rate.numerator > 0;
+ (!min_resolution.has_value() || min_resolution->IsValid()) &&
+ max_dimensions.frame_rate.numerator() > 0;
}
Json::Value VideoConstraints::ToJson() const {
@@ -313,8 +281,8 @@ Json::Value VideoConstraints::ToJson() const {
root[kMaxPixelsPerSecond] = max_pixels_per_second.value();
}
- if (min_dimensions.has_value()) {
- root[kMinDimensions] = min_dimensions->ToJson();
+ if (min_resolution.has_value()) {
+ root[kMinResolution] = min_resolution->ToJson();
}
if (max_delay.has_value()) {
diff --git a/cast/streaming/answer_messages.h b/cast/streaming/answer_messages.h
index 1f62706a..7aa78e7f 100644
--- a/cast/streaming/answer_messages.h
+++ b/cast/streaming/answer_messages.h
@@ -15,6 +15,7 @@
#include <vector>
#include "absl/types/optional.h"
+#include "cast/streaming/resolution.h"
#include "cast/streaming/ssrc.h"
#include "json/value.h"
#include "platform/base/error.h"
@@ -46,23 +47,13 @@ struct AudioConstraints {
absl::optional<std::chrono::milliseconds> max_delay = {};
};
-struct Dimensions {
- static bool ParseAndValidate(const Json::Value& value, Dimensions* out);
- Json::Value ToJson() const;
- bool IsValid() const;
-
- int width = 0;
- int height = 0;
- SimpleFraction frame_rate;
-};
-
struct VideoConstraints {
static bool ParseAndValidate(const Json::Value& value, VideoConstraints* out);
Json::Value ToJson() const;
bool IsValid() const;
absl::optional<double> max_pixels_per_second = {};
- absl::optional<Dimensions> min_dimensions = {};
+ absl::optional<Dimensions> min_resolution = {};
Dimensions max_dimensions = {};
int min_bit_rate = 0; // optional
int max_bit_rate = 0;
diff --git a/cast/streaming/answer_messages_unittest.cc b/cast/streaming/answer_messages_unittest.cc
index e4ec82f4..3ce93aef 100644
--- a/cast/streaming/answer_messages_unittest.cc
+++ b/cast/streaming/answer_messages_unittest.cc
@@ -37,7 +37,7 @@ constexpr char kValidAnswerJson[] = R"({
},
"video": {
"maxPixelsPerSecond": 62208000,
- "minDimensions": {
+ "minResolution": {
"width": 320,
"height": 180,
"frameRate": 0
@@ -81,27 +81,16 @@ const Answer kValidAnswer{
}, // audio
VideoConstraints{
40000.0, // max_pixels_per_second
- absl::optional<Dimensions>(Dimensions{
- 320, // width
- 480, // height
- SimpleFraction{15000, 101} // frame_rate
- }), // min_dimensions
- Dimensions{
- 1920, // width
- 1080, // height
- SimpleFraction{288, 2} // frame_rate
- },
+ absl::optional<Dimensions>(
+ Dimensions{320, 480, SimpleFraction{15000, 101}}),
+ Dimensions{1920, 1080, SimpleFraction{288, 2}},
300000, // min_bit_rate
144000000, // max_bit_rate
milliseconds(3000) // max_delay
} // video
}), // constraints
absl::optional<DisplayDescription>(DisplayDescription{
- absl::optional<Dimensions>(Dimensions{
- 640, // width
- 480, // height
- SimpleFraction{30, 1} // frame_rate
- }),
+ absl::optional<Dimensions>(Dimensions{640, 480, SimpleFraction{30, 1}}),
absl::optional<AspectRatio>(AspectRatio{16, 9}), // aspect_ratio
absl::optional<AspectRatioConstraint>(
AspectRatioConstraint::kFixed), // scaling
@@ -113,7 +102,7 @@ const Answer kValidAnswer{
};
constexpr int kValidMaxPixelsPerSecond = 1920 * 1080 * 30;
-constexpr Dimensions kValidDimensions{1920, 1080, SimpleFraction{60, 1}};
+const Dimensions kValidDimensions{1920, 1080, SimpleFraction{60, 1}};
static const VideoConstraints kValidVideoConstraints{
kValidMaxPixelsPerSecond, absl::optional<Dimensions>(kValidDimensions),
kValidDimensions, 300 * 1000,
@@ -137,10 +126,10 @@ void ExpectEqualsValidAnswerJson(const Answer& answer) {
const VideoConstraints& video = answer.constraints->video;
EXPECT_EQ(62208000, video.max_pixels_per_second);
- ASSERT_TRUE(video.min_dimensions.has_value());
- EXPECT_EQ(320, video.min_dimensions->width);
- EXPECT_EQ(180, video.min_dimensions->height);
- EXPECT_EQ((SimpleFraction{0, 1}), video.min_dimensions->frame_rate);
+ ASSERT_TRUE(video.min_resolution.has_value());
+ EXPECT_EQ(320, video.min_resolution->width);
+ EXPECT_EQ(180, video.min_resolution->height);
+ EXPECT_EQ((SimpleFraction{0, 1}), video.min_resolution->frame_rate);
EXPECT_EQ(1920, video.max_dimensions.width);
EXPECT_EQ(1080, video.max_dimensions.height);
EXPECT_EQ((SimpleFraction{60, 1}), video.max_dimensions.frame_rate);
@@ -223,11 +212,11 @@ TEST(AnswerMessagesTest, ProperlyPopulatedAnswerSerializesProperly) {
EXPECT_EQ(video["maxBitRate"], 144000000);
EXPECT_EQ(video["maxDelay"], 3000);
- Json::Value min_dimensions = std::move(video["minDimensions"]);
- EXPECT_EQ(min_dimensions.type(), Json::ValueType::objectValue);
- EXPECT_EQ(min_dimensions["width"], 320);
- EXPECT_EQ(min_dimensions["height"], 480);
- EXPECT_EQ(min_dimensions["frameRate"], "15000/101");
+ Json::Value min_resolution = std::move(video["minResolution"]);
+ EXPECT_EQ(min_resolution.type(), Json::ValueType::objectValue);
+ EXPECT_EQ(min_resolution["width"], 320);
+ EXPECT_EQ(min_resolution["height"], 480);
+ EXPECT_EQ(min_resolution["frameRate"], "15000/101");
Json::Value max_dimensions = std::move(video["maxDimensions"]);
EXPECT_EQ(max_dimensions.type(), Json::ValueType::objectValue);
@@ -453,15 +442,15 @@ TEST(AnswerMessagesTest, AudioConstraintsIsValid) {
TEST(AnswerMessagesTest, DimensionsIsValid) {
// NOTE: in some cases (such as min dimensions) a frame rate of zero is valid.
- constexpr Dimensions kValidZeroFrameRate{1920, 1080, SimpleFraction{0, 60}};
- constexpr Dimensions kInvalidWidth{0, 1080, SimpleFraction{60, 1}};
- constexpr Dimensions kInvalidHeight{1920, 0, SimpleFraction{60, 1}};
- constexpr Dimensions kInvalidFrameRateZeroDenominator{1920, 1080,
- SimpleFraction{60, 0}};
- constexpr Dimensions kInvalidFrameRateNegativeNumerator{
- 1920, 1080, SimpleFraction{-1, 30}};
- constexpr Dimensions kInvalidFrameRateNegativeDenominator{
- 1920, 1080, SimpleFraction{30, -1}};
+ const Dimensions kValidZeroFrameRate{1920, 1080, SimpleFraction{0, 60}};
+ const Dimensions kInvalidWidth{0, 1080, SimpleFraction{60, 1}};
+ const Dimensions kInvalidHeight{1920, 0, SimpleFraction{60, 1}};
+ const Dimensions kInvalidFrameRateZeroDenominator{1920, 1080,
+ SimpleFraction{60, 0}};
+ const Dimensions kInvalidFrameRateNegativeNumerator{1920, 1080,
+ SimpleFraction{-1, 30}};
+ const Dimensions kInvalidFrameRateNegativeDenominator{1920, 1080,
+ SimpleFraction{30, -1}};
EXPECT_TRUE(kValidDimensions.IsValid());
EXPECT_TRUE(kValidZeroFrameRate.IsValid());
@@ -476,8 +465,8 @@ TEST(AnswerMessagesTest, VideoConstraintsIsValid) {
VideoConstraints invalid_max_pixels_per_second = kValidVideoConstraints;
invalid_max_pixels_per_second.max_pixels_per_second = 0;
- VideoConstraints invalid_min_dimensions = kValidVideoConstraints;
- invalid_min_dimensions.min_dimensions->width = 0;
+ VideoConstraints invalid_min_resolution = kValidVideoConstraints;
+ invalid_min_resolution.min_resolution->width = 0;
VideoConstraints invalid_max_dimensions = kValidVideoConstraints;
invalid_max_dimensions.max_dimensions.height = 0;
@@ -493,7 +482,7 @@ TEST(AnswerMessagesTest, VideoConstraintsIsValid) {
EXPECT_TRUE(kValidVideoConstraints.IsValid());
EXPECT_FALSE(invalid_max_pixels_per_second.IsValid());
- EXPECT_FALSE(invalid_min_dimensions.IsValid());
+ EXPECT_FALSE(invalid_min_resolution.IsValid());
EXPECT_FALSE(invalid_max_dimensions.IsValid());
EXPECT_FALSE(invalid_min_bit_rate.IsValid());
EXPECT_FALSE(invalid_max_bit_rate.IsValid());
diff --git a/cast/streaming/capture_configs.h b/cast/streaming/capture_configs.h
index fd99c17c..a5367e18 100644
--- a/cast/streaming/capture_configs.h
+++ b/cast/streaming/capture_configs.h
@@ -9,6 +9,8 @@
#include <vector>
#include "cast/streaming/constants.h"
+#include "cast/streaming/resolution.h"
+#include "util/simple_fraction.h"
namespace openscreen {
namespace cast {
@@ -35,25 +37,6 @@ struct AudioCaptureConfig {
std::chrono::milliseconds target_playout_delay = kDefaultTargetPlayoutDelay;
};
-// Display resolution in pixels.
-struct DisplayResolution {
- // Width in pixels.
- int width = 1920;
-
- // Height in pixels.
- int height = 1080;
-};
-
-// Frame rates are expressed as a rational number, and must be positive.
-struct FrameRate {
- // For simple cases, the frame rate may be provided by simply setting the
- // number to the desired value, e.g. 30 or 60FPS. Some common frame rates like
- // 23.98 FPS (for NTSC compatibility) are represented as fractions, in this
- // case 24000/1001.
- int numerator = kDefaultFrameRate;
- int denominator = 1;
-};
-
// A configuration set that can be used by the sender to capture video, as
// well as the receiver to playback video. Used by Cast Streaming to provide an
// offer to the receiver.
@@ -62,7 +45,11 @@ struct VideoCaptureConfig {
VideoCodec codec = VideoCodec::kVp8;
// Maximum frame rate in frames per second.
- FrameRate max_frame_rate;
+ // For simple cases, the frame rate may be provided by simply setting the
+ // number to the desired value, e.g. 30 or 60FPS. Some common frame rates like
+ // 23.98 FPS (for NTSC compatibility) are represented as fractions, in this
+ // case 24000/1001.
+ SimpleFraction max_frame_rate{kDefaultFrameRate, 1};
// Number specifying the maximum bit rate for this stream. A value of
// zero means that the maximum bit rate should be automatically selected by
@@ -71,7 +58,7 @@ struct VideoCaptureConfig {
// Resolutions to be offered to the receiver. At least one resolution
// must be provided.
- std::vector<DisplayResolution> resolutions;
+ std::vector<Resolution> resolutions;
// Target playout delay in milliseconds.
std::chrono::milliseconds target_playout_delay = kDefaultTargetPlayoutDelay;
diff --git a/cast/streaming/capture_recommendations.cc b/cast/streaming/capture_recommendations.cc
index b30b5dc1..4b3bcd16 100644
--- a/cast/streaming/capture_recommendations.cc
+++ b/cast/streaming/capture_recommendations.cc
@@ -15,16 +15,6 @@ namespace cast {
namespace capture_recommendations {
namespace {
-bool DoubleEquals(double a, double b) {
- // Choice of epsilon for double comparison allows for proper comparison
- // for both aspect ratios and frame rates. For frame rates, it is based on the
- // broadcast rate of 29.97fps, which is actually 29.976. For aspect ratios, it
- // allows for a one-pixel difference at a 4K resolution, we want it to be
- // relatively high to avoid false negative comparison results.
- const double kEpsilon = .0001;
- return std::abs(a - b) < kEpsilon;
-}
-
void ApplyDisplay(const DisplayDescription& description,
Recommendations* recommendations) {
recommendations->video.supports_scaling =
@@ -35,14 +25,15 @@ void ApplyDisplay(const DisplayDescription& description,
// We should never exceed the display's resolution, since it will always
// force scaling.
if (description.dimensions) {
- const double frame_rate =
- static_cast<double>(description.dimensions->frame_rate);
- recommendations->video.maximum =
- Resolution{description.dimensions->width,
- description.dimensions->height, frame_rate};
+ recommendations->video.maximum = description.dimensions.value();
recommendations->video.bit_rate_limits.maximum =
recommendations->video.maximum.effective_bit_rate();
- recommendations->video.minimum.set_minimum(recommendations->video.maximum);
+
+ if (recommendations->video.maximum.width <
+ recommendations->video.minimum.width) {
+ recommendations->video.minimum =
+ recommendations->video.maximum.ToResolution();
+ }
}
// If the receiver gives us an aspect ratio that doesn't match the display
@@ -53,16 +44,6 @@ void ApplyDisplay(const DisplayDescription& description,
if (description.aspect_ratio) {
aspect_ratio = static_cast<double>(description.aspect_ratio->width) /
description.aspect_ratio->height;
-#if OSP_DCHECK_IS_ON()
- if (description.dimensions) {
- const double from_dims =
- static_cast<double>(description.dimensions->width) /
- description.dimensions->height;
- if (!DoubleEquals(from_dims, aspect_ratio)) {
- OSP_DLOG_WARN << "Received mismatched aspect ratio from the receiver.";
- }
- }
-#endif
recommendations->video.maximum.width =
recommendations->video.maximum.height * aspect_ratio;
} else if (description.dimensions) {
@@ -75,10 +56,6 @@ void ApplyDisplay(const DisplayDescription& description,
recommendations->video.minimum.height * aspect_ratio;
}
-Resolution ToResolution(const Dimensions& dims) {
- return {dims.width, dims.height, static_cast<double>(dims.frame_rate)};
-}
-
void ApplyConstraints(const Constraints& constraints,
Recommendations* recommendations) {
// Audio has no fields in the display description, so we can safely
@@ -109,17 +86,18 @@ void ApplyConstraints(const Constraints& constraints,
recommendations->video.bit_rate_limits.minimum),
std::min(constraints.video.max_bit_rate,
recommendations->video.bit_rate_limits.maximum)};
- Resolution max = ToResolution(constraints.video.max_dimensions);
- if (max <= kDefaultMinResolution) {
- recommendations->video.maximum = kDefaultMinResolution;
- } else if (max < recommendations->video.maximum) {
- recommendations->video.maximum = std::move(max);
+ Dimensions dimensions = constraints.video.max_dimensions;
+ if (dimensions.width <= kDefaultMinResolution.width) {
+ recommendations->video.maximum = {kDefaultMinResolution.width,
+ kDefaultMinResolution.height,
+ kDefaultFrameRate};
+ } else if (dimensions.width < recommendations->video.maximum.width) {
+ recommendations->video.maximum = std::move(dimensions);
}
- // Implicit else: maximum = kDefaultMaxResolution.
- if (constraints.video.min_dimensions) {
- Resolution min = ToResolution(constraints.video.min_dimensions.value());
- if (kDefaultMinResolution < min) {
+ if (constraints.video.min_resolution) {
+ const Resolution& min = constraints.video.min_resolution->ToResolution();
+ if (kDefaultMinResolution.width < min.width) {
recommendations->video.minimum = std::move(min);
}
}
@@ -137,25 +115,6 @@ bool Audio::operator==(const Audio& other) const {
other.max_sample_rate);
}
-bool Resolution::operator==(const Resolution& other) const {
- return (std::tie(width, height) == std::tie(other.width, other.height)) &&
- DoubleEquals(frame_rate, other.frame_rate);
-}
-
-bool Resolution::operator<(const Resolution& other) const {
- return effective_bit_rate() < other.effective_bit_rate();
-}
-
-bool Resolution::operator<=(const Resolution& other) const {
- return (*this == other) || (*this < other);
-}
-
-void Resolution::set_minimum(const Resolution& other) {
- if (other < *this) {
- *this = other;
- }
-}
-
bool Video::operator==(const Video& other) const {
return std::tie(bit_rate_limits, minimum, maximum, supports_scaling,
max_delay, max_pixels_per_second) ==
diff --git a/cast/streaming/capture_recommendations.h b/cast/streaming/capture_recommendations.h
index ccb2475b..643eac88 100644
--- a/cast/streaming/capture_recommendations.h
+++ b/cast/streaming/capture_recommendations.h
@@ -11,7 +11,7 @@
#include <tuple>
#include "cast/streaming/constants.h"
-
+#include "cast/streaming/resolution.h"
namespace openscreen {
namespace cast {
@@ -80,30 +80,12 @@ struct Audio {
int min_sample_rate = kDefaultAudioMinSampleRate;
};
-struct Resolution {
- bool operator==(const Resolution& other) const;
- bool operator<(const Resolution& other) const;
- bool operator<=(const Resolution& other) const;
- void set_minimum(const Resolution& other);
-
- // The effective bit rate is the predicted average bit rate based on the
- // properties of the Resolution instance, and is currently just the product.
- constexpr int effective_bit_rate() const {
- return static_cast<int>(static_cast<double>(width * height) * frame_rate);
- }
-
- int width;
- int height;
- double frame_rate;
-};
-
// The minimum dimensions are as close as possible to low-definition
// television, factoring in the receiver's aspect ratio if provided.
-constexpr Resolution kDefaultMinResolution{kMinVideoWidth, kMinVideoHeight,
- kDefaultFrameRate};
+constexpr Resolution kDefaultMinResolution{kMinVideoWidth, kMinVideoHeight};
// Currently mirroring only supports 1080P.
-constexpr Resolution kDefaultMaxResolution{1920, 1080, kDefaultFrameRate};
+const Dimensions kDefaultMaxResolution{1920, 1080, kDefaultFrameRate};
// The mirroring spec suggests 300kbps as the absolute minimum bitrate.
constexpr int kDefaultVideoMinBitRate = 300 * 1000;
@@ -111,13 +93,13 @@ constexpr int kDefaultVideoMinBitRate = 300 * 1000;
// The theoretical maximum pixels per second is the maximum bit rate
// divided by 8 (the max byte rate). In practice it should generally be
// less.
-constexpr int kDefaultVideoMaxPixelsPerSecond =
+const int kDefaultVideoMaxPixelsPerSecond =
kDefaultMaxResolution.effective_bit_rate() / 8;
// Our default limits are merely the product of the minimum and maximum
// dimensions, and are only used if the receiver fails to give better
// constraint information.
-constexpr BitRateLimits kDefaultVideoBitRateLimits{
+const BitRateLimits kDefaultVideoBitRateLimits{
kDefaultVideoMinBitRate, kDefaultMaxResolution.effective_bit_rate()};
// Video capture recommendations.
@@ -131,7 +113,7 @@ struct Video {
Resolution minimum = kDefaultMinResolution;
// Represents the recommended maximum resolution.
- Resolution maximum = kDefaultMaxResolution;
+ Dimensions maximum = kDefaultMaxResolution;
// Indicates whether the receiver can scale frames from a different aspect
// ratio, or if it needs to be done by the sender. Default is false, meaning
diff --git a/cast/streaming/capture_recommendations_unittest.cc b/cast/streaming/capture_recommendations_unittest.cc
index 4f76b9d9..872b62e3 100644
--- a/cast/streaming/capture_recommendations_unittest.cc
+++ b/cast/streaming/capture_recommendations_unittest.cc
@@ -6,6 +6,7 @@
#include "absl/types/optional.h"
#include "cast/streaming/answer_messages.h"
+#include "cast/streaming/resolution.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "util/chrono_helpers.h"
@@ -15,64 +16,64 @@ namespace cast {
namespace capture_recommendations {
namespace {
-constexpr Recommendations kDefaultRecommendations{
+const Recommendations kDefaultRecommendations{
Audio{BitRateLimits{32000, 256000}, milliseconds(400), 2, 48000, 16000},
- Video{BitRateLimits{300000, 1920 * 1080 * 30}, Resolution{320, 240, 30},
- Resolution{1920, 1080, 30}, false, milliseconds(400),
+ Video{BitRateLimits{300000, 1920 * 1080 * 30}, Resolution{320, 240},
+ Dimensions{1920, 1080, 30}, false, milliseconds(400),
1920 * 1080 * 30 / 8}};
-constexpr DisplayDescription kEmptyDescription{};
+const DisplayDescription kEmptyDescription{};
-constexpr DisplayDescription kValidOnlyResolution{
+const DisplayDescription kValidOnlyResolution{
Dimensions{1024, 768, SimpleFraction{60, 1}}, absl::nullopt, absl::nullopt};
-constexpr DisplayDescription kValidOnlyAspectRatio{
- absl::nullopt, AspectRatio{4, 3}, absl::nullopt};
+const DisplayDescription kValidOnlyAspectRatio{absl::nullopt, AspectRatio{4, 3},
+ absl::nullopt};
-constexpr DisplayDescription kValidOnlyAspectRatioSixteenNine{
+const DisplayDescription kValidOnlyAspectRatioSixteenNine{
absl::nullopt, AspectRatio{16, 9}, absl::nullopt};
-constexpr DisplayDescription kValidOnlyVariable{
- absl::nullopt, absl::nullopt, AspectRatioConstraint::kVariable};
+const DisplayDescription kValidOnlyVariable{absl::nullopt, absl::nullopt,
+ AspectRatioConstraint::kVariable};
-constexpr DisplayDescription kInvalidOnlyFixed{absl::nullopt, absl::nullopt,
- AspectRatioConstraint::kFixed};
+const DisplayDescription kInvalidOnlyFixed{absl::nullopt, absl::nullopt,
+ AspectRatioConstraint::kFixed};
-constexpr DisplayDescription kValidFixedAspectRatio{
+const DisplayDescription kValidFixedAspectRatio{
absl::nullopt, AspectRatio{4, 3}, AspectRatioConstraint::kFixed};
-constexpr DisplayDescription kValidVariableAspectRatio{
+const DisplayDescription kValidVariableAspectRatio{
absl::nullopt, AspectRatio{4, 3}, AspectRatioConstraint::kVariable};
-constexpr DisplayDescription kValidFixedMissingAspectRatio{
+const DisplayDescription kValidFixedMissingAspectRatio{
Dimensions{1024, 768, SimpleFraction{60, 1}}, absl::nullopt,
AspectRatioConstraint::kFixed};
-constexpr DisplayDescription kValidDisplayFhd{
+const DisplayDescription kValidDisplayFhd{
Dimensions{1920, 1080, SimpleFraction{30, 1}}, AspectRatio{16, 9},
AspectRatioConstraint::kVariable};
-constexpr DisplayDescription kValidDisplayXga{
+const DisplayDescription kValidDisplayXga{
Dimensions{1024, 768, SimpleFraction{60, 1}}, AspectRatio{4, 3},
AspectRatioConstraint::kFixed};
-constexpr DisplayDescription kValidDisplayTiny{
+const DisplayDescription kValidDisplayTiny{
Dimensions{300, 200, SimpleFraction{30, 1}}, AspectRatio{3, 2},
AspectRatioConstraint::kFixed};
-constexpr DisplayDescription kValidDisplayMismatched{
+const DisplayDescription kValidDisplayMismatched{
Dimensions{300, 200, SimpleFraction{30, 1}}, AspectRatio{3, 4},
AspectRatioConstraint::kFixed};
-constexpr Constraints kEmptyConstraints{};
+const Constraints kEmptyConstraints{};
-constexpr Constraints kValidConstraintsHighEnd{
+const Constraints kValidConstraintsHighEnd{
{96100, 5, 96000, 500000, std::chrono::seconds(6)},
{6000000, Dimensions{640, 480, SimpleFraction{30, 1}},
Dimensions{3840, 2160, SimpleFraction{144, 1}}, 600000, 6000000,
std::chrono::seconds(6)}};
-constexpr Constraints kValidConstraintsLowEnd{
+const Constraints kValidConstraintsLowEnd{
{22000, 2, 24000, 50000, std::chrono::seconds(1)},
{60000, Dimensions{120, 80, SimpleFraction{10, 1}},
Dimensions{1200, 800, SimpleFraction{30, 1}}, 100000, 1000000,
@@ -92,7 +93,7 @@ TEST(CaptureRecommendationsTest, EmptyDisplayDescription) {
TEST(CaptureRecommendationsTest, OnlyResolution) {
Recommendations expected = kDefaultRecommendations;
- expected.video.maximum = Resolution{1024, 768, 60.0};
+ expected.video.maximum = Dimensions{1024, 768, 60.0};
expected.video.bit_rate_limits.maximum = 47185920;
Answer answer;
answer.display = kValidOnlyResolution;
@@ -101,8 +102,8 @@ TEST(CaptureRecommendationsTest, OnlyResolution) {
TEST(CaptureRecommendationsTest, OnlyAspectRatioFourThirds) {
Recommendations expected = kDefaultRecommendations;
- expected.video.minimum = Resolution{320, 240, 30.0};
- expected.video.maximum = Resolution{1440, 1080, 30.0};
+ expected.video.minimum = Resolution{320, 240};
+ expected.video.maximum = Dimensions{1440, 1080, 30.0};
Answer answer;
answer.display = kValidOnlyAspectRatio;
@@ -111,8 +112,8 @@ TEST(CaptureRecommendationsTest, OnlyAspectRatioFourThirds) {
TEST(CaptureRecommendationsTest, OnlyAspectRatioSixteenNine) {
Recommendations expected = kDefaultRecommendations;
- expected.video.minimum = Resolution{426, 240, 30.0};
- expected.video.maximum = Resolution{1920, 1080, 30.0};
+ expected.video.minimum = Resolution{426, 240};
+ expected.video.maximum = Dimensions{1920, 1080, 30.0};
Answer answer;
answer.display = kValidOnlyAspectRatioSixteenNine;
@@ -139,8 +140,8 @@ TEST(CaptureRecommendationsTest, OnlyInvalidAspectRatioConstraint) {
TEST(CaptureRecommendationsTest, FixedAspectRatioConstraint) {
Recommendations expected = kDefaultRecommendations;
- expected.video.minimum = Resolution{320, 240, 30.0};
- expected.video.maximum = Resolution{1440, 1080, 30.0};
+ expected.video.minimum = Resolution{320, 240};
+ expected.video.maximum = Dimensions{1440, 1080, 30.0};
expected.video.supports_scaling = false;
Answer answer;
answer.display = kValidFixedAspectRatio;
@@ -152,8 +153,8 @@ TEST(CaptureRecommendationsTest, FixedAspectRatioConstraint) {
// frame sizes between minimum and maximum can be properly scaled.
TEST(CaptureRecommendationsTest, VariableAspectRatioConstraint) {
Recommendations expected = kDefaultRecommendations;
- expected.video.minimum = Resolution{320, 240, 30.0};
- expected.video.maximum = Resolution{1440, 1080, 30.0};
+ expected.video.minimum = Resolution{320, 240};
+ expected.video.maximum = Dimensions{1440, 1080, 30.0};
expected.video.supports_scaling = true;
Answer answer;
answer.display = kValidVariableAspectRatio;
@@ -162,8 +163,8 @@ TEST(CaptureRecommendationsTest, VariableAspectRatioConstraint) {
TEST(CaptureRecommendationsTest, ResolutionWithFixedConstraint) {
Recommendations expected = kDefaultRecommendations;
- expected.video.minimum = Resolution{320, 240, 30.0};
- expected.video.maximum = Resolution{1024, 768, 60.0};
+ expected.video.minimum = Resolution{320, 240};
+ expected.video.maximum = Dimensions{1024, 768, 60.0};
expected.video.supports_scaling = false;
expected.video.bit_rate_limits.maximum = 47185920;
Answer answer;
@@ -173,7 +174,7 @@ TEST(CaptureRecommendationsTest, ResolutionWithFixedConstraint) {
TEST(CaptureRecommendationsTest, ExplicitFhdChangesMinimum) {
Recommendations expected = kDefaultRecommendations;
- expected.video.minimum = Resolution{426, 240, 30.0};
+ expected.video.minimum = Resolution{426, 240};
expected.video.supports_scaling = true;
Answer answer;
answer.display = kValidDisplayFhd;
@@ -182,8 +183,8 @@ TEST(CaptureRecommendationsTest, ExplicitFhdChangesMinimum) {
TEST(CaptureRecommendationsTest, XgaResolution) {
Recommendations expected = kDefaultRecommendations;
- expected.video.minimum = Resolution{320, 240, 30.0};
- expected.video.maximum = Resolution{1024, 768, 60.0};
+ expected.video.minimum = Resolution{320, 240};
+ expected.video.maximum = Dimensions{1024, 768, 60.0};
expected.video.supports_scaling = false;
expected.video.bit_rate_limits.maximum = 47185920;
Answer answer;
@@ -193,8 +194,8 @@ TEST(CaptureRecommendationsTest, XgaResolution) {
TEST(CaptureRecommendationsTest, MismatchedDisplayAndAspectRatio) {
Recommendations expected = kDefaultRecommendations;
- expected.video.minimum = Resolution{150, 200, 30.0};
- expected.video.maximum = Resolution{150, 200, 30.0};
+ expected.video.minimum = Resolution{150, 200};
+ expected.video.maximum = Dimensions{150, 200, 30.0};
expected.video.supports_scaling = false;
expected.video.bit_rate_limits.maximum = 300 * 200 * 30;
Answer answer;
@@ -204,8 +205,8 @@ TEST(CaptureRecommendationsTest, MismatchedDisplayAndAspectRatio) {
TEST(CaptureRecommendationsTest, TinyDisplay) {
Recommendations expected = kDefaultRecommendations;
- expected.video.minimum = Resolution{300, 200, 30.0};
- expected.video.maximum = Resolution{300, 200, 30.0};
+ expected.video.minimum = Resolution{300, 200};
+ expected.video.maximum = Dimensions{300, 200, 30.0};
expected.video.supports_scaling = false;
expected.video.bit_rate_limits.maximum = 300 * 200 * 30;
Answer answer;
@@ -225,8 +226,8 @@ TEST(CaptureRecommendationsTest, EmptyConstraints) {
TEST(CaptureRecommendationsTest, HandlesHighEnd) {
const Recommendations kExpected{
Audio{BitRateLimits{96000, 500000}, milliseconds(6000), 5, 96100, 16000},
- Video{BitRateLimits{600000, 6000000}, Resolution{640, 480, 30},
- Resolution{1920, 1080, 30}, false, milliseconds(6000), 6000000}};
+ Video{BitRateLimits{600000, 6000000}, Resolution{640, 480},
+ Dimensions{1920, 1080, 30}, false, milliseconds(6000), 6000000}};
Answer answer;
answer.constraints = kValidConstraintsHighEnd;
EXPECT_EQ(kExpected, GetRecommendations(answer));
@@ -238,8 +239,8 @@ TEST(CaptureRecommendationsTest, HandlesHighEnd) {
TEST(CaptureRecommendationsTest, HandlesLowEnd) {
const Recommendations kExpected{
Audio{BitRateLimits{32000, 50000}, milliseconds(1000), 2, 22000, 16000},
- Video{BitRateLimits{300000, 1000000}, Resolution{320, 240, 30},
- Resolution{1200, 800, 30}, false, milliseconds(1000), 60000}};
+ Video{BitRateLimits{300000, 1000000}, Resolution{320, 240},
+ Dimensions{1200, 800, 30}, false, milliseconds(1000), 60000}};
Answer answer;
answer.constraints = kValidConstraintsLowEnd;
EXPECT_EQ(kExpected, GetRecommendations(answer));
@@ -248,20 +249,20 @@ TEST(CaptureRecommendationsTest, HandlesLowEnd) {
TEST(CaptureRecommendationsTest, HandlesTooSmallScreen) {
const Recommendations kExpected{
Audio{BitRateLimits{32000, 50000}, milliseconds(1000), 2, 22000, 16000},
- Video{BitRateLimits{300000, 1000000}, Resolution{320, 240, 30},
- Resolution{320, 240, 30}, false, milliseconds(1000), 60000}};
+ Video{BitRateLimits{300000, 1000000}, Resolution{320, 240},
+ Dimensions{320, 240, 30}, false, milliseconds(1000), 60000}};
Answer answer;
answer.constraints = kValidConstraintsLowEnd;
answer.constraints->video.max_dimensions =
- answer.constraints->video.min_dimensions.value();
+ answer.constraints->video.min_resolution.value();
EXPECT_EQ(kExpected, GetRecommendations(answer));
}
TEST(CaptureRecommendationsTest, HandlesMinimumSizeScreen) {
const Recommendations kExpected{
Audio{BitRateLimits{32000, 50000}, milliseconds(1000), 2, 22000, 16000},
- Video{BitRateLimits{300000, 1000000}, Resolution{320, 240, 30},
- Resolution{320, 240, 30}, false, milliseconds(1000), 60000}};
+ Video{BitRateLimits{300000, 1000000}, Resolution{320, 240},
+ Dimensions{320, 240, 30}, false, milliseconds(1000), 60000}};
Answer answer;
answer.constraints = kValidConstraintsLowEnd;
answer.constraints->video.max_dimensions =
@@ -272,11 +273,11 @@ TEST(CaptureRecommendationsTest, HandlesMinimumSizeScreen) {
TEST(CaptureRecommendationsTest, UsesIntersectionOfDisplayAndConstraints) {
const Recommendations kExpected{
Audio{BitRateLimits{96000, 500000}, milliseconds(6000), 5, 96100, 16000},
- Video{BitRateLimits{600000, 6000000}, Resolution{640, 480, 30},
+ Video{BitRateLimits{600000, 6000000}, Resolution{640, 480},
// Max resolution should be 1080P, since that's the display
// resolution. No reason to capture at 4K, even though the
// receiver supports it.
- Resolution{1920, 1080, 30}, true, milliseconds(6000), 6000000}};
+ Dimensions{1920, 1080, 30}, true, milliseconds(6000), 6000000}};
Answer answer;
answer.display = kValidDisplayFhd;
answer.constraints = kValidConstraintsHighEnd;
diff --git a/cast/streaming/constants.h b/cast/streaming/constants.h
index ae1e8aa4..2edbde0c 100644
--- a/cast/streaming/constants.h
+++ b/cast/streaming/constants.h
@@ -58,6 +58,27 @@ constexpr int kMinVideoWidth = 320;
// The default frame rate for capture options is 30FPS.
constexpr int kDefaultFrameRate = 30;
+// The mirroring spec suggests 300kbps as the absolute minimum bitrate.
+constexpr int kDefaultVideoMinBitRate = 300 * 1000;
+
+// Default video max bitrate is based on 1080P @ 30FPS, which can be played back
+// at good quality around 10mbps.
+constexpr int kDefaultVideoMaxBitRate = 10 * 1000 * 1000;
+
+// The mirroring control protocol specifies 32kbps as the absolute minimum
+// for audio. Depending on the type of audio content (narrowband, fullband,
+// etc.) Opus specifically can perform very well at this bitrate.
+// See: https://research.google/pubs/pub41650/
+constexpr int kDefaultAudioMinBitRate = 32 * 1000;
+
+// Opus generally sees little improvement above 192kbps, but some older codecs
+// that we may consider supporting improve at up to 256kbps.
+constexpr int kDefaultAudioMaxBitRate = 256 * 1000;
+
+// While generally audio should be captured at the maximum sample rate, 16kHz is
+// the recommended absolute minimum.
+constexpr int kDefaultAudioMinSampleRate = 16000;
+
// The default audio sample rate is 48kHz, slightly higher than standard
// consumer audio.
constexpr int kDefaultAudioSampleRate = 48000;
@@ -65,6 +86,10 @@ constexpr int kDefaultAudioSampleRate = 48000;
// The default audio number of channels is set to stereo.
constexpr int kDefaultAudioChannels = 2;
+// Default maximum delay for both audio and video. Used if the sender fails
+// to provide any constraints.
+constexpr std::chrono::milliseconds kDefaultMaxDelayMs(1500);
+
// Codecs known and understood by cast senders and receivers. Note: receivers
// are required to implement the following codecs to be Cast V2 compliant: H264,
// VP8, AAC, Opus. Senders have to implement at least one codec from this
diff --git a/cast/streaming/message_fields.h b/cast/streaming/message_fields.h
index 524a0135..2d1cb969 100644
--- a/cast/streaming/message_fields.h
+++ b/cast/streaming/message_fields.h
@@ -28,6 +28,7 @@ constexpr char kMessageType[] = "type";
constexpr char kMessageTypeOffer[] = "OFFER";
constexpr char kOfferMessageBody[] = "offer";
constexpr char kSequenceNumber[] = "seqNum";
+constexpr char kCodecName[] = "codecName";
/// ANSWER message fields.
constexpr char kMessageTypeAnswer[] = "ANSWER";
diff --git a/cast/streaming/offer_messages.cc b/cast/streaming/offer_messages.cc
index cea500cd..e45cb1ce 100644
--- a/cast/streaming/offer_messages.cc
+++ b/cast/streaming/offer_messages.cc
@@ -14,7 +14,6 @@
#include "absl/strings/match.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_split.h"
-#include "cast/streaming/capture_recommendations.h"
#include "cast/streaming/constants.h"
#include "platform/base/error.h"
#include "util/big_endian.h"
@@ -60,10 +59,10 @@ ErrorOr<int> ParseRtpTimebase(const Json::Value& parent,
// The spec demands a leading 1, so this isn't really a fraction.
const auto fraction = SimpleFraction::FromString(error_or_raw.value());
if (fraction.is_error() || !fraction.value().is_positive() ||
- fraction.value().numerator != 1) {
+ fraction.value().numerator() != 1) {
return json::CreateParseError("RTP timebase");
}
- return fraction.value().denominator;
+ return fraction.value().denominator();
}
// For a hex byte, the conversion is 4 bits to 1 character, e.g.
@@ -130,8 +129,7 @@ ErrorOr<Stream> ParseStream(const Json::Value& value, Stream::Type type) {
return rtp_timebase.error();
}
if (rtp_timebase.value() <
- std::min(capture_recommendations::kDefaultAudioMinSampleRate,
- kRtpVideoTimebase) ||
+ std::min(kDefaultAudioMinSampleRate, kRtpVideoTimebase) ||
rtp_timebase.value() > kRtpVideoTimebase) {
return json::CreateParameterError("rtp_timebase (sample rate)");
}
@@ -172,7 +170,7 @@ ErrorOr<AudioStream> ParseAudioStream(const Json::Value& value) {
return bit_rate.error();
}
- auto codec_name = json::ParseString(value, "codecName");
+ auto codec_name = json::ParseString(value, kCodecName);
if (!codec_name) {
return codec_name.error();
}
@@ -189,21 +187,6 @@ ErrorOr<AudioStream> ParseAudioStream(const Json::Value& value) {
return AudioStream{stream.value(), codec.value(), bit_rate.value()};
}
-ErrorOr<Resolution> ParseResolution(const Json::Value& value) {
- auto width = json::ParseInt(value, "width");
- if (!width) {
- return width.error();
- }
- auto height = json::ParseInt(value, "height");
- if (!height) {
- return height.error();
- }
- if (width.value() <= 0 || height.value() <= 0) {
- return json::CreateParameterError("resolution");
- }
- return Resolution{width.value(), height.value()};
-}
-
ErrorOr<std::vector<Resolution>> ParseResolutions(const Json::Value& parent,
const std::string& field) {
std::vector<Resolution> resolutions;
@@ -214,11 +197,11 @@ ErrorOr<std::vector<Resolution>> ParseResolutions(const Json::Value& parent,
}
for (Json::ArrayIndex i = 0; i < value.size(); ++i) {
- auto r = ParseResolution(value[i]);
- if (!r) {
- return r.error();
+ Resolution resolution;
+ if (!Resolution::ParseAndValidate(value[i], &resolution)) {
+ return Error(Error::Code::kJsonParseError);
}
- resolutions.push_back(r.value());
+ resolutions.push_back(std::move(resolution));
}
return resolutions;
@@ -229,7 +212,7 @@ ErrorOr<VideoStream> ParseVideoStream(const Json::Value& value) {
if (!stream) {
return stream.error();
}
- auto codec_name = json::ParseString(value, "codecName");
+ auto codec_name = json::ParseString(value, kCodecName);
if (!codec_name) {
return codec_name.error();
}
@@ -323,22 +306,11 @@ ErrorOr<Json::Value> AudioStream::ToJson() const {
return error_or_stream;
}
- error_or_stream.value()["codecName"] = CodecToString(codec);
+ error_or_stream.value()[kCodecName] = CodecToString(codec);
error_or_stream.value()["bitRate"] = bit_rate;
return error_or_stream;
}
-ErrorOr<Json::Value> Resolution::ToJson() const {
- if (width <= 0 || height <= 0) {
- return json::CreateParameterError("Resolution");
- }
-
- Json::Value root;
- root["width"] = width;
- root["height"] = height;
- return root;
-}
-
ErrorOr<Json::Value> VideoStream::ToJson() const {
if (max_bit_rate <= 0 || !max_frame_rate.is_positive()) {
return json::CreateParameterError("VideoStream");
@@ -360,11 +332,7 @@ ErrorOr<Json::Value> VideoStream::ToJson() const {
Json::Value rs;
for (auto resolution : resolutions) {
- auto eoj = resolution.ToJson();
- if (eoj.is_error()) {
- return eoj;
- }
- rs.append(eoj.value());
+ rs.append(resolution.ToJson());
}
stream["resolutions"] = std::move(rs);
return error_or_stream;
diff --git a/cast/streaming/offer_messages.h b/cast/streaming/offer_messages.h
index f62c156d..d9fc5af2 100644
--- a/cast/streaming/offer_messages.h
+++ b/cast/streaming/offer_messages.h
@@ -12,6 +12,7 @@
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "cast/streaming/message_fields.h"
+#include "cast/streaming/resolution.h"
#include "cast/streaming/rtp_defines.h"
#include "cast/streaming/session_config.h"
#include "json/value.h"
@@ -73,12 +74,6 @@ struct AudioStream {
int bit_rate = 0;
};
-struct Resolution {
- ErrorOr<Json::Value> ToJson() const;
-
- int width = 0;
- int height = 0;
-};
struct VideoStream {
ErrorOr<Json::Value> ToJson() const;
diff --git a/cast/streaming/offer_messages_unittest.cc b/cast/streaming/offer_messages_unittest.cc
index a2117f67..81a84ec4 100644
--- a/cast/streaming/offer_messages_unittest.cc
+++ b/cast/streaming/offer_messages_unittest.cc
@@ -491,7 +491,7 @@ TEST(OfferTest, ToJsonFailsWithInvalidStreams) {
const Offer valid_offer = std::move(offer.value());
Offer video_stream_invalid = valid_offer;
- video_stream_invalid.video_streams[0].max_frame_rate.denominator = 0;
+ video_stream_invalid.video_streams[0].max_frame_rate = SimpleFraction{1, 0};
EXPECT_TRUE(video_stream_invalid.ToJson().is_error());
Offer audio_stream_invalid = valid_offer;
diff --git a/cast/streaming/receiver_session.cc b/cast/streaming/receiver_session.cc
index 4e976e01..5529d840 100644
--- a/cast/streaming/receiver_session.cc
+++ b/cast/streaming/receiver_session.cc
@@ -13,6 +13,7 @@
#include "absl/strings/numbers.h"
#include "cast/common/channel/message_util.h"
#include "cast/common/public/message_port.h"
+#include "cast/streaming/answer_messages.h"
#include "cast/streaming/environment.h"
#include "cast/streaming/message_fields.h"
#include "cast/streaming/offer_messages.h"
@@ -23,11 +24,6 @@
namespace openscreen {
namespace cast {
-
-// Using statements for constructor readability.
-using Preferences = ReceiverSession::Preferences;
-using ConfiguredReceivers = ReceiverSession::ConfiguredReceivers;
-
namespace {
template <typename Stream, typename Codec>
@@ -46,35 +42,14 @@ std::unique_ptr<Stream> SelectStream(
return nullptr;
}
-DisplayResolution ToDisplayResolution(const Resolution& resolution) {
- return DisplayResolution{resolution.width, resolution.height};
-}
-
} // namespace
ReceiverSession::Client::~Client() = default;
-Preferences::Preferences() = default;
-Preferences::Preferences(std::vector<VideoCodec> video_codecs,
- std::vector<AudioCodec> audio_codecs)
- : Preferences(video_codecs, audio_codecs, nullptr, nullptr) {}
-
-Preferences::Preferences(std::vector<VideoCodec> video_codecs,
- std::vector<AudioCodec> audio_codecs,
- std::unique_ptr<Constraints> constraints,
- std::unique_ptr<DisplayDescription> description)
- : video_codecs(std::move(video_codecs)),
- audio_codecs(std::move(audio_codecs)),
- constraints(std::move(constraints)),
- display_description(std::move(description)) {}
-
-Preferences::Preferences(Preferences&&) noexcept = default;
-Preferences& Preferences::operator=(Preferences&&) noexcept = default;
-
ReceiverSession::ReceiverSession(Client* const client,
Environment* environment,
MessagePort* message_port,
- Preferences preferences)
+ ReceiverSession::Preferences preferences)
: client_(client),
environment_(environment),
preferences_(std::move(preferences)),
@@ -212,7 +187,7 @@ std::unique_ptr<Receiver> ReceiverSession::ConstructReceiver(
std::move(config));
}
-ConfiguredReceivers ReceiverSession::SpawnReceivers(
+ReceiverSession::ConfiguredReceivers ReceiverSession::SpawnReceivers(
const SessionProperties& properties) {
OSP_DCHECK(properties.IsValid());
ResetReceivers(Client::kRenegotiated);
@@ -233,17 +208,12 @@ ConfiguredReceivers ReceiverSession::SpawnReceivers(
if (properties.selected_video) {
current_video_receiver_ =
ConstructReceiver(properties.selected_video->stream);
- std::vector<DisplayResolution> display_resolutions;
- std::transform(properties.selected_video->resolutions.begin(),
- properties.selected_video->resolutions.end(),
- std::back_inserter(display_resolutions),
- ToDisplayResolution);
- video_config = VideoCaptureConfig{
- properties.selected_video->codec,
- FrameRate{properties.selected_video->max_frame_rate.numerator,
- properties.selected_video->max_frame_rate.denominator},
- properties.selected_video->max_bit_rate, std::move(display_resolutions),
- properties.selected_video->stream.target_delay};
+ video_config =
+ VideoCaptureConfig{properties.selected_video->codec,
+ properties.selected_video->max_frame_rate,
+ properties.selected_video->max_bit_rate,
+ properties.selected_video->resolutions,
+ properties.selected_video->stream.target_delay};
}
return ConfiguredReceivers{
@@ -264,31 +234,59 @@ Answer ReceiverSession::ConstructAnswer(const SessionProperties& properties) {
std::vector<int> stream_indexes;
std::vector<Ssrc> stream_ssrcs;
+ Constraints constraints;
if (properties.selected_audio) {
stream_indexes.push_back(properties.selected_audio->stream.index);
stream_ssrcs.push_back(properties.selected_audio->stream.ssrc + 1);
+
+ for (const auto& limit : preferences_.audio_limits) {
+ if (limit.codec == properties.selected_audio->codec ||
+ limit.applies_to_all_codecs) {
+ constraints.audio = AudioConstraints{
+ limit.max_sample_rate, limit.max_channels, limit.min_bit_rate,
+ limit.max_bit_rate, limit.max_delay,
+ };
+ break;
+ }
+ }
}
if (properties.selected_video) {
stream_indexes.push_back(properties.selected_video->stream.index);
stream_ssrcs.push_back(properties.selected_video->stream.ssrc + 1);
- }
- absl::optional<Constraints> constraints;
- if (preferences_.constraints) {
- constraints = absl::optional<Constraints>(*preferences_.constraints);
+ for (const auto& limit : preferences_.video_limits) {
+ if (limit.codec == properties.selected_video->codec ||
+ limit.applies_to_all_codecs) {
+ constraints.video = VideoConstraints{
+ limit.max_pixels_per_second, absl::nullopt, /* min dimensions */
+ limit.max_dimensions, limit.min_bit_rate,
+ limit.max_bit_rate, limit.max_delay,
+ };
+ break;
+ }
+ }
}
absl::optional<DisplayDescription> display;
if (preferences_.display_description) {
- display =
- absl::optional<DisplayDescription>(*preferences_.display_description);
+ const auto* d = preferences_.display_description.get();
+ display = DisplayDescription{d->dimensions, absl::nullopt,
+ d->can_scale_content
+ ? AspectRatioConstraint::kVariable
+ : AspectRatioConstraint::kFixed};
}
+ // Only set the constraints in the answer if they are valid (meaning we
+ // successfully found limits above).
+ absl::optional<Constraints> answer_constraints;
+ if (constraints.IsValid()) {
+ answer_constraints = std::move(constraints);
+ }
return Answer{environment_->GetBoundLocalEndpoint().port,
std::move(stream_indexes),
std::move(stream_ssrcs),
- std::move(constraints),
+ answer_constraints,
std::move(display),
std::vector<int>{}, // receiver_rtcp_event_log
std::vector<int>{}, // receiver_rtcp_dscp
diff --git a/cast/streaming/receiver_session.h b/cast/streaming/receiver_session.h
index b29b6d52..489ce9b6 100644
--- a/cast/streaming/receiver_session.h
+++ b/cast/streaming/receiver_session.h
@@ -11,14 +11,14 @@
#include <vector>
#include "cast/common/public/message_port.h"
-#include "cast/streaming/answer_messages.h"
#include "cast/streaming/capture_configs.h"
+#include "cast/streaming/constants.h"
#include "cast/streaming/offer_messages.h"
#include "cast/streaming/receiver_packet_router.h"
+#include "cast/streaming/resolution.h"
#include "cast/streaming/sender_message.h"
#include "cast/streaming/session_config.h"
#include "cast/streaming/session_messager.h"
-#include "util/json/json_serialization.h"
namespace openscreen {
namespace cast {
@@ -77,30 +77,86 @@ class ReceiverSession final : public Environment::SocketSubscriber {
virtual ~Client();
};
+ // Information about the display the receiver is attached to.
+ struct Display {
+ // The display limitations of the actual screen, used to provide upper
+ // bounds on mirroring and remoting streams. For example, we will never
+ // send 60FPS if it is going to be displayed on a 30FPS screen.
+ // Note that we may exceed the display width and height for standard
+ // content sizes like 720p or 1080p.
+ Dimensions dimensions;
+
+ // Whether the embedder is capable of scaling content. If set to false,
+ // the sender will manage the aspect ratio scaling.
+ bool can_scale_content = false;
+ };
+
+ // Codec-specific audio limits for playback.
+ struct AudioLimits {
+ // Whether or not these limits apply to all codecs.
+ bool applies_to_all_codecs = false;
+
+ // Audio codec these limits apply to. Note that if |applies_to_all_codecs|
+ // is true this field is ignored.
+ AudioCodec codec;
+
+ // Maximum audio sample rate.
+ int max_sample_rate = kDefaultAudioSampleRate;
+
+ // Maximum audio channels, default is currently stereo.
+ int max_channels = kDefaultAudioChannels;
+
+ // Minimum and maximum bitrates. Generally capture is done at the maximum
+ // bit rate, since audio bandwidth is much lower than video for most
+ // content.
+ int min_bit_rate = kDefaultAudioMinBitRate;
+ int max_bit_rate = kDefaultAudioMaxBitRate;
+
+ // Max playout delay in milliseconds.
+ std::chrono::milliseconds max_delay = kDefaultMaxDelayMs;
+ };
+
+ // Codec-specific video limits for playback.
+ struct VideoLimits {
+ // Whether or not these limits apply to all codecs.
+ bool applies_to_all_codecs = false;
+
+ // Video codec these limits apply to. Note that if |applies_to_all_codecs|
+ // is true this field is ignored.
+ VideoCodec codec;
+
+ // Maximum pixels per second. Value is the standard amount of pixels
+ // for 1080P at 30FPS.
+ int max_pixels_per_second = 1920 * 1080 * 30;
+
+ // Maximum dimensions. Minimum dimensions try to use the same aspect
+ // ratio and are generated from the spec.
+ Dimensions max_dimensions = {1920, 1080, {kDefaultFrameRate, 1}};
+
+ // Minimum and maximum bitrates. Default values are based on default min and
+ // max dimensions, embedders that support different display dimensions
+ // should strongly consider setting these fields.
+ int min_bit_rate = kDefaultVideoMinBitRate;
+ int max_bit_rate = kDefaultVideoMaxBitRate;
+
+ // Max playout delay in milliseconds.
+ std::chrono::milliseconds max_delay = kDefaultMaxDelayMs;
+ };
+
// Note: embedders are required to implement the following
// codecs to be Cast V2 compliant: H264, VP8, AAC, Opus.
struct Preferences {
- Preferences();
- Preferences(std::vector<VideoCodec> video_codecs,
- std::vector<AudioCodec> audio_codecs);
- Preferences(std::vector<VideoCodec> video_codecs,
- std::vector<AudioCodec> audio_codecs,
- std::unique_ptr<Constraints> constraints,
- std::unique_ptr<DisplayDescription> description);
-
- Preferences(Preferences&&) noexcept;
- Preferences(const Preferences&) = delete;
- Preferences& operator=(Preferences&&) noexcept;
- Preferences& operator=(const Preferences&) = delete;
-
std::vector<VideoCodec> video_codecs{VideoCodec::kVp8, VideoCodec::kH264};
std::vector<AudioCodec> audio_codecs{AudioCodec::kOpus, AudioCodec::kAac};
- // The embedder has the option of directly specifying the display
- // information and video/audio constraints that will be passed along to
- // senders during the offer/answer exchange. If nullptr, these are ignored.
- std::unique_ptr<Constraints> constraints;
- std::unique_ptr<DisplayDescription> display_description;
+ // Optional limitation fields that help the sender provide a delightful
+ // cast experience. Although optional, highly recommended.
+ // NOTE: embedders that wish to apply the same limits for all codecs can
+ // pass a vector of size 1 with the |applies_to_all_codecs| field set to
+ // true.
+ std::vector<AudioLimits> audio_limits;
+ std::vector<VideoLimits> video_limits;
+ std::unique_ptr<Display> display_description;
};
ReceiverSession(Client* const client,
diff --git a/cast/streaming/receiver_session_unittest.cc b/cast/streaming/receiver_session_unittest.cc
index afbc556e..e937233c 100644
--- a/cast/streaming/receiver_session_unittest.cc
+++ b/cast/streaming/receiver_session_unittest.cc
@@ -15,6 +15,7 @@
#include "platform/test/fake_clock.h"
#include "platform/test/fake_task_runner.h"
#include "util/chrono_helpers.h"
+#include "util/json/json_serialization.h"
using ::testing::_;
using ::testing::InSequence;
@@ -400,24 +401,28 @@ TEST_F(ReceiverSessionTest, CanNegotiateWithCustomCodecPreferences) {
message_port_->ReceiveMessage(kValidOfferMessage);
}
-TEST_F(ReceiverSessionTest, CanNegotiateWithCustomConstraints) {
- auto constraints = std::make_unique<Constraints>(Constraints{
- AudioConstraints{48001, 2, 32001, 32002, milliseconds(3001)},
- VideoConstraints{3.14159,
- absl::optional<Dimensions>(
- Dimensions{320, 240, SimpleFraction{24, 1}}),
- Dimensions{1920, 1080, SimpleFraction{144, 1}}, 300000,
- 90000000, milliseconds(1000)}});
+TEST_F(ReceiverSessionTest, CanNegotiateWithLimits) {
+ std::vector<ReceiverSession::AudioLimits> audio_limits = {
+ {false, AudioCodec::kOpus, 48001, 2, 32001, 32002, milliseconds(3001)}};
- auto display = std::make_unique<DisplayDescription>(DisplayDescription{
- absl::optional<Dimensions>(Dimensions{640, 480, SimpleFraction{60, 1}}),
- absl::optional<AspectRatio>(AspectRatio{16, 9}),
- absl::optional<AspectRatioConstraint>(AspectRatioConstraint::kFixed)});
+ std::vector<ReceiverSession::VideoLimits> video_limits = {
+ {true,
+ VideoCodec::kVp9,
+ 62208000,
+ {1920, 1080, {144, 1}},
+ 300000,
+ 90000000,
+ milliseconds(1000)}};
+
+ auto display =
+ std::make_unique<ReceiverSession::Display>(ReceiverSession::Display{
+ {640, 480, {60, 1}}, false /* can scale content */});
ReceiverSession session(&client_, environment_.get(), message_port_.get(),
ReceiverSession::Preferences{{VideoCodec::kVp9},
{AudioCodec::kOpus},
- std::move(constraints),
+ std::move(audio_limits),
+ std::move(video_limits),
std::move(display)});
InSequence s;
@@ -434,14 +439,13 @@ TEST_F(ReceiverSessionTest, CanNegotiateWithCustomConstraints) {
const Json::Value answer = std::move(message_body.value());
const Json::Value& answer_body = answer["answer"];
- ASSERT_TRUE(answer_body.isObject());
+ ASSERT_TRUE(answer_body.isObject()) << messages[0];
// Constraints and display should be valid with valid preferences.
ASSERT_FALSE(answer_body["constraints"].isNull());
ASSERT_FALSE(answer_body["display"].isNull());
const Json::Value& display_json = answer_body["display"];
- EXPECT_EQ("16:9", display_json["aspectRatio"].asString());
EXPECT_EQ("60", display_json["dimensions"]["frameRate"].asString());
EXPECT_EQ(640, display_json["dimensions"]["width"].asInt());
EXPECT_EQ(480, display_json["dimensions"]["height"].asInt());
@@ -465,11 +469,7 @@ TEST_F(ReceiverSessionTest, CanNegotiateWithCustomConstraints) {
EXPECT_EQ("144", video["maxDimensions"]["frameRate"].asString());
EXPECT_EQ(1920, video["maxDimensions"]["width"].asInt());
EXPECT_EQ(1080, video["maxDimensions"]["height"].asInt());
- EXPECT_DOUBLE_EQ(3.14159, video["maxPixelsPerSecond"].asDouble());
EXPECT_EQ(300000, video["minBitRate"].asInt());
- EXPECT_EQ("24", video["minDimensions"]["frameRate"].asString());
- EXPECT_EQ(320, video["minDimensions"]["width"].asInt());
- EXPECT_EQ(240, video["minDimensions"]["height"].asInt());
}
TEST_F(ReceiverSessionTest, HandlesNoValidAudioStream) {
diff --git a/cast/streaming/resolution.cc b/cast/streaming/resolution.cc
new file mode 100644
index 00000000..682de444
--- /dev/null
+++ b/cast/streaming/resolution.cc
@@ -0,0 +1,117 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "cast/streaming/resolution.h"
+
+#include <utility>
+
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_split.h"
+#include "cast/streaming/message_fields.h"
+#include "platform/base/error.h"
+#include "util/json/json_helpers.h"
+#include "util/osp_logging.h"
+
+namespace openscreen {
+namespace cast {
+
+namespace {
+
+/// Dimension properties.
+// Width in pixels.
+static constexpr char kWidth[] = "width";
+
+// Height in pixels.
+static constexpr char kHeight[] = "height";
+
+// Frame rate as a rational decimal number or fraction.
+// E.g. 30 and "3000/1001" are both valid representations.
+static constexpr char kFrameRate[] = "frameRate";
+
+// Choice of epsilon for double comparison allows for proper comparison
+// for both aspect ratios and frame rates. For frame rates, it is based on the
+// broadcast rate of 29.97fps, which is actually 29.976. For aspect ratios, it
+// allows for a one-pixel difference at a 4K resolution, we want it to be
+// relatively high to avoid false negative comparison results.
+bool FrameRateEquals(double a, double b) {
+ const double kEpsilonForFrameRateComparisons = .0001;
+ return std::abs(a - b) < kEpsilonForFrameRateComparisons;
+}
+
+} // namespace
+
+bool Resolution::ParseAndValidate(const Json::Value& root, Resolution* out) {
+ if (!json::ParseAndValidateInt(root[kWidth], &(out->width)) ||
+ !json::ParseAndValidateInt(root[kHeight], &(out->height))) {
+ return false;
+ }
+ return out->IsValid();
+}
+
+bool Resolution::IsValid() const {
+ return width > 0 && height > 0;
+}
+
+Json::Value Resolution::ToJson() const {
+ OSP_DCHECK(IsValid());
+ Json::Value root;
+ root[kWidth] = width;
+ root[kHeight] = height;
+
+ return root;
+}
+
+bool Resolution::operator==(const Resolution& other) const {
+ return std::tie(width, height) == std::tie(other.width, other.height);
+}
+
+bool Resolution::operator!=(const Resolution& other) const {
+ return !(*this == other);
+}
+
+bool Dimensions::ParseAndValidate(const Json::Value& root, Dimensions* out) {
+ if (!json::ParseAndValidateInt(root[kWidth], &(out->width)) ||
+ !json::ParseAndValidateInt(root[kHeight], &(out->height)) ||
+ !(root[kFrameRate].isNull() ||
+ json::ParseAndValidateSimpleFraction(root[kFrameRate],
+ &(out->frame_rate)))) {
+ return false;
+ }
+ return out->IsValid();
+}
+
+bool Dimensions::IsValid() const {
+ return width > 0 && height > 0 && frame_rate.is_positive();
+}
+
+Json::Value Dimensions::ToJson() const {
+ OSP_DCHECK(IsValid());
+ Json::Value root;
+ root[kWidth] = width;
+ root[kHeight] = height;
+ root[kFrameRate] = frame_rate.ToString();
+
+ return root;
+}
+
+Resolution Dimensions::ToResolution() const {
+ return {width, height};
+}
+
+int Dimensions::effective_bit_rate() const {
+ return width * height * static_cast<double>(frame_rate);
+}
+
+bool Dimensions::operator==(const Dimensions& other) const {
+ return (std::tie(width, height) == std::tie(other.width, other.height) &&
+ FrameRateEquals(static_cast<double>(frame_rate),
+ static_cast<double>(other.frame_rate)));
+}
+
+bool Dimensions::operator!=(const Dimensions& other) const {
+ return !(*this == other);
+}
+
+} // namespace cast
+} // namespace openscreen
diff --git a/cast/streaming/resolution.h b/cast/streaming/resolution.h
new file mode 100644
index 00000000..60903001
--- /dev/null
+++ b/cast/streaming/resolution.h
@@ -0,0 +1,60 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Resolutions and dimensions (resolutions with a frame rate) are used
+// extensively throughout cast streaming. Since their serialization to and
+// from JSON is stable and standard, we have a single place definition for
+// these for use both in our public APIs and private messages.
+
+#ifndef CAST_STREAMING_RESOLUTION_H_
+#define CAST_STREAMING_RESOLUTION_H_
+
+#include "absl/types/optional.h"
+#include "json/value.h"
+#include "util/simple_fraction.h"
+
+namespace openscreen {
+namespace cast {
+
+// A resolution in pixels.
+struct Resolution {
+ static bool ParseAndValidate(const Json::Value& value, Resolution* out);
+ bool IsValid() const;
+ Json::Value ToJson() const;
+
+ bool operator==(const Resolution& other) const;
+ bool operator!=(const Resolution& other) const;
+
+ // Width and height in pixels.
+ int width = 0;
+ int height = 0;
+};
+
+// A resolution in pixels and a frame rate.
+struct Dimensions {
+ static bool ParseAndValidate(const Json::Value& value, Dimensions* out);
+ bool IsValid() const;
+ Json::Value ToJson() const;
+
+ bool operator==(const Dimensions& other) const;
+ bool operator!=(const Dimensions& other) const;
+
+ // Get just the width and height fields (for comparisons).
+ Resolution ToResolution() const;
+
+ // The effective bit rate is the width * height * frame rate.
+ int effective_bit_rate() const;
+
+ // Width and height in pixels.
+ int width = 0;
+ int height = 0;
+
+ // |frame_rate| is the maximum maintainable frame rate.
+ SimpleFraction frame_rate{0, 1};
+};
+
+} // namespace cast
+} // namespace openscreen
+
+#endif // CAST_STREAMING_RESOLUTION_H_
diff --git a/cast/streaming/sender_session.cc b/cast/streaming/sender_session.cc
index 47782412..6b1a9743 100644
--- a/cast/streaming/sender_session.cc
+++ b/cast/streaming/sender_session.cc
@@ -36,35 +36,24 @@ constexpr int kSupportedRemotingVersion = 3;
AudioStream CreateStream(int index,
const AudioCaptureConfig& config,
bool use_android_rtp_hack) {
- return AudioStream{
- Stream{index,
- Stream::Type::kAudioSource,
- config.channels,
- GetPayloadType(config.codec, use_android_rtp_hack),
- GenerateSsrc(true /*high_priority*/),
- config.target_playout_delay,
- GenerateRandomBytes16(),
- GenerateRandomBytes16(),
- false /* receiver_rtcp_event_log */,
- {} /* receiver_rtcp_dscp */,
- config.sample_rate},
- config.codec,
- (config.bit_rate >= capture_recommendations::kDefaultAudioMinBitRate)
- ? config.bit_rate
- : capture_recommendations::kDefaultAudioMaxBitRate};
-}
-
-Resolution ToResolution(const DisplayResolution& display_resolution) {
- return Resolution{display_resolution.width, display_resolution.height};
+ return AudioStream{Stream{index,
+ Stream::Type::kAudioSource,
+ config.channels,
+ GetPayloadType(config.codec, use_android_rtp_hack),
+ GenerateSsrc(true /*high_priority*/),
+ config.target_playout_delay,
+ GenerateRandomBytes16(),
+ GenerateRandomBytes16(),
+ false /* receiver_rtcp_event_log */,
+ {} /* receiver_rtcp_dscp */,
+ config.sample_rate},
+ config.codec,
+ std::max(config.bit_rate, kDefaultAudioMinBitRate)};
}
VideoStream CreateStream(int index,
const VideoCaptureConfig& config,
bool use_android_rtp_hack) {
- std::vector<Resolution> resolutions;
- std::transform(config.resolutions.begin(), config.resolutions.end(),
- std::back_inserter(resolutions), ToResolution);
-
constexpr int kVideoStreamChannelCount = 1;
return VideoStream{
Stream{index,
@@ -79,16 +68,14 @@ VideoStream CreateStream(int index,
{} /* receiver_rtcp_dscp */,
kRtpVideoTimebase},
config.codec,
- SimpleFraction{config.max_frame_rate.numerator,
- config.max_frame_rate.denominator},
- (config.max_bit_rate >
- capture_recommendations::kDefaultVideoBitRateLimits.minimum)
+ config.max_frame_rate,
+ (config.max_bit_rate >= kDefaultVideoMinBitRate)
? config.max_bit_rate
- : capture_recommendations::kDefaultVideoBitRateLimits.maximum,
+ : kDefaultVideoMaxBitRate,
{}, // protection
{}, // profile
{}, // protection
- std::move(resolutions),
+ config.resolutions,
{} /* error_recovery mode, always "castv2" */
};
}
@@ -148,20 +135,19 @@ bool IsValidAudioCaptureConfig(const AudioCaptureConfig& config) {
return config.channels >= 1 && config.bit_rate >= 0;
}
-bool IsValidResolution(const DisplayResolution& resolution) {
+// We don't support resolutions below our minimums.
+bool IsSupportedResolution(const Resolution& resolution) {
return resolution.width > kMinVideoWidth &&
resolution.height > kMinVideoHeight;
}
bool IsValidVideoCaptureConfig(const VideoCaptureConfig& config) {
- return config.max_frame_rate.numerator > 0 &&
- config.max_frame_rate.denominator > 0 &&
+ return config.max_frame_rate.is_positive() &&
((config.max_bit_rate == 0) ||
- (config.max_bit_rate >=
- capture_recommendations::kDefaultVideoBitRateLimits.minimum)) &&
+ (config.max_bit_rate >= kDefaultVideoMinBitRate)) &&
!config.resolutions.empty() &&
std::all_of(config.resolutions.begin(), config.resolutions.end(),
- IsValidResolution);
+ IsSupportedResolution);
}
bool AreAllValid(const std::vector<AudioCaptureConfig>& audio_configs,
diff --git a/cast/streaming/sender_session_unittest.cc b/cast/streaming/sender_session_unittest.cc
index f8d4f24e..485f2fd9 100644
--- a/cast/streaming/sender_session_unittest.cc
+++ b/cast/streaming/sender_session_unittest.cc
@@ -116,22 +116,28 @@ const AudioCaptureConfig kAudioCaptureConfigValid{
};
const VideoCaptureConfig kVideoCaptureConfigMissingResolutions{
- VideoCodec::kHevc, FrameRate{60, 1}, 300000 /* max_bit_rate */,
- std::vector<DisplayResolution>{}};
+ VideoCodec::kHevc,
+ {60, 1},
+ 300000 /* max_bit_rate */,
+ std::vector<Resolution>{}};
const VideoCaptureConfig kVideoCaptureConfigInvalid{
- VideoCodec::kHevc, FrameRate{60, 1}, -300000 /* max_bit_rate */,
- std::vector<DisplayResolution>{DisplayResolution{1920, 1080},
- DisplayResolution{1280, 720}}};
+ VideoCodec::kHevc,
+ {60, 1},
+ -300000 /* max_bit_rate */,
+ std::vector<Resolution>{Resolution{1920, 1080}, Resolution{1280, 720}}};
const VideoCaptureConfig kVideoCaptureConfigValid{
- VideoCodec::kHevc, FrameRate{60, 1}, 300000 /* max_bit_rate */,
- std::vector<DisplayResolution>{DisplayResolution{1280, 720},
- DisplayResolution{1920, 1080}}};
+ VideoCodec::kHevc,
+ {60, 1},
+ 300000 /* max_bit_rate */,
+ std::vector<Resolution>{Resolution{1280, 720}, Resolution{1920, 1080}}};
const VideoCaptureConfig kVideoCaptureConfigValidSimplest{
- VideoCodec::kHevc, FrameRate{60, 1}, 300000 /* max_bit_rate */,
- std::vector<DisplayResolution>{DisplayResolution{1920, 1080}}};
+ VideoCodec::kHevc,
+ {60, 1},
+ 300000 /* max_bit_rate */,
+ std::vector<Resolution>{Resolution{1920, 1080}}};
class FakeClient : public SenderSession::Client {
public:
diff --git a/util/simple_fraction.cc b/util/simple_fraction.cc
index a98d825c..e020ad3b 100644
--- a/util/simple_fraction.cc
+++ b/util/simple_fraction.cc
@@ -33,18 +33,32 @@ ErrorOr<SimpleFraction> SimpleFraction::FromString(absl::string_view value) {
}
}
- return SimpleFraction{numerator, denominator};
+ return SimpleFraction(numerator, denominator);
}
std::string SimpleFraction::ToString() const {
- if (denominator == 1) {
- return std::to_string(numerator);
+ if (denominator_ == 1) {
+ return std::to_string(numerator_);
}
- return absl::StrCat(numerator, "/", denominator);
+ return absl::StrCat(numerator_, "/", denominator_);
}
+SimpleFraction::SimpleFraction() : numerator_(0), denominator_(1) {}
+
+SimpleFraction::SimpleFraction(int numerator, int denominator)
+ : numerator_(numerator), denominator_(denominator) {}
+
+SimpleFraction::SimpleFraction(int numerator)
+ : numerator_(numerator), denominator_(1) {}
+
+SimpleFraction::SimpleFraction(const SimpleFraction&) = default;
+SimpleFraction::SimpleFraction(SimpleFraction&&) = default;
+SimpleFraction& SimpleFraction::operator=(const SimpleFraction&) = default;
+SimpleFraction& SimpleFraction::operator=(SimpleFraction&&) = default;
+SimpleFraction::~SimpleFraction() = default;
+
bool SimpleFraction::operator==(const SimpleFraction& other) const {
- return numerator == other.numerator && denominator == other.denominator;
+ return numerator_ == other.numerator_ && denominator_ == other.denominator_;
}
bool SimpleFraction::operator!=(const SimpleFraction& other) const {
@@ -52,18 +66,18 @@ bool SimpleFraction::operator!=(const SimpleFraction& other) const {
}
bool SimpleFraction::is_defined() const {
- return denominator != 0;
+ return denominator_ != 0;
}
bool SimpleFraction::is_positive() const {
- return is_defined() && (numerator >= 0) && (denominator > 0);
+ return is_defined() && (numerator_ >= 0) && (denominator_ > 0);
}
SimpleFraction::operator double() const {
- if (denominator == 0) {
+ if (denominator_ == 0) {
return nan("");
}
- return static_cast<double>(numerator) / static_cast<double>(denominator);
+ return static_cast<double>(numerator_) / static_cast<double>(denominator_);
}
} // namespace openscreen
diff --git a/util/simple_fraction.h b/util/simple_fraction.h
index f8ab5083..181c4359 100644
--- a/util/simple_fraction.h
+++ b/util/simple_fraction.h
@@ -14,21 +14,26 @@ namespace openscreen {
// SimpleFraction is used to represent simple (or "common") fractions, composed
// of a rational number written a/b where a and b are both integers.
-
-// Note: Since SimpleFraction is a trivial type, it comes with a
-// default constructor and is copyable, as well as allowing static
-// initialization.
-
// Some helpful notes on SimpleFraction assumptions/limitations:
// 1. SimpleFraction does not perform reductions. 2/4 != 1/2, and -1/-1 != 1/1.
// 2. denominator = 0 is considered undefined.
// 3. numerator = saturates range to int min or int max
// 4. A SimpleFraction is "positive" if and only if it is defined and at least
// equal to zero. Since reductions are not performed, -1/-1 is negative.
-struct SimpleFraction {
+class SimpleFraction {
+ public:
static ErrorOr<SimpleFraction> FromString(absl::string_view value);
std::string ToString() const;
+ SimpleFraction();
+ SimpleFraction(int numerator, int denominator);
+ SimpleFraction(int numerator); // NOLINT
+ SimpleFraction(const SimpleFraction&);
+ SimpleFraction(SimpleFraction&&) noexcept;
+ SimpleFraction& operator=(const SimpleFraction&);
+ SimpleFraction& operator=(SimpleFraction&&);
+ ~SimpleFraction();
+
bool operator==(const SimpleFraction& other) const;
bool operator!=(const SimpleFraction& other) const;
@@ -36,8 +41,12 @@ struct SimpleFraction {
bool is_positive() const;
explicit operator double() const;
- int numerator = 0;
- int denominator = 0;
+ int numerator() const { return numerator_; }
+ int denominator() const { return denominator_; }
+
+ private:
+ int numerator_ = 0;
+ int denominator_ = 0;
};
} // namespace openscreen