aboutsummaryrefslogtreecommitdiff
path: root/cast
diff options
context:
space:
mode:
authorRyan Keane <rwkeane@google.com>2021-06-23 19:24:35 -0700
committerOpenscreen LUCI CQ <openscreen-scoped@luci-project-accounts.iam.gserviceaccount.com>2021-06-24 05:06:36 +0000
commit1fcefe1f610c87c8a8a5b86838590574ab4514ee (patch)
treec4867986e6a49c2bd0e054b1f91455b0bf84eacb /cast
parent5df61d7305d4122d4724cab3edd03946c12326d7 (diff)
downloadopenscreen-1fcefe1f610c87c8a8a5b86838590574ab4514ee.tar.gz
Add SupportsAllAllowedBy() function to ReceiverSession inner classes
This CL does the following: - Add copy ctors for ReceiverSession inner classes. This is required so that a Preferences object may be copied to a unique_ptr for initialization of the `cast_streaming` component in Chromium, while also maintaining a local instance for use with the below function. - Add SupportsAllAllowedBy() functions for comparing the support provided by two different Preferences instances. This is required because the Cast Web Runtime allows for updating of supported AV settings during runtime, so it must be validated that the configuration used for negotiation is no more strict than that supported at any time by the runtime. Bug: 182427395 Change-Id: Ie7a8625ffbf8156e49e36c13dbcb91e8da214e7b Reviewed-on: https://chromium-review.googlesource.com/c/openscreen/+/2983166 Reviewed-by: Jordan Bayles <jophba@chromium.org> Commit-Queue: Ryan Keane <rwkeane@google.com>
Diffstat (limited to 'cast')
-rw-r--r--cast/streaming/receiver_session.cc175
-rw-r--r--cast/streaming/receiver_session.h28
-rw-r--r--cast/streaming/receiver_session_unittest.cc241
-rw-r--r--cast/streaming/resolution.cc14
-rw-r--r--cast/streaming/resolution.h8
5 files changed, 453 insertions, 13 deletions
diff --git a/cast/streaming/receiver_session.cc b/cast/streaming/receiver_session.cc
index 02c57e60..36a47bca 100644
--- a/cast/streaming/receiver_session.cc
+++ b/cast/streaming/receiver_session.cc
@@ -70,18 +70,104 @@ MediaCapability ToCapability(VideoCodec codec) {
}
}
+// Calculates whether any codecs present in |second| are not present in |first|.
+template <typename T>
+bool IsMissingCodecs(const std::vector<T>& first,
+ const std::vector<T>& second) {
+ if (second.size() > first.size()) {
+ return true;
+ }
+
+ for (auto codec : second) {
+ if (std::find(first.begin(), first.end(), codec) == first.end()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Calculates whether the limits defined by |first| are less restrictive than
+// those defined by |second|.
+// NOTE: These variables are intentionally passed by copy - the function will
+// mutate them.
+template <typename T>
+bool HasLessRestrictiveLimits(std::vector<T> first, std::vector<T> second) {
+ // Sort both vectors to allow for element-by-element comparison between the
+ // two. All elements with |applies_to_all_codecs| set are sorted to the front.
+ std::function<bool(const T&, const T&)> sorter = [](const T& first,
+ const T& second) {
+ if (first.applies_to_all_codecs != second.applies_to_all_codecs) {
+ return first.applies_to_all_codecs;
+ }
+ return static_cast<int>(first.codec) < static_cast<int>(second.codec);
+ };
+ std::sort(first.begin(), first.end(), sorter);
+ std::sort(second.begin(), second.end(), sorter);
+ auto first_it = first.begin();
+ auto second_it = second.begin();
+
+ // |applies_to_all_codecs| is a special case, so handle that first.
+ T fake_applies_to_all_codecs_struct;
+ fake_applies_to_all_codecs_struct.applies_to_all_codecs = true;
+ T* first_applies_to_all_codecs_struct =
+ !first.empty() && first.front().applies_to_all_codecs
+ ? &(*first_it++)
+ : &fake_applies_to_all_codecs_struct;
+ T* second_applies_to_all_codecs_struct =
+ !second.empty() && second.front().applies_to_all_codecs
+ ? &(*second_it++)
+ : &fake_applies_to_all_codecs_struct;
+ if (!first_applies_to_all_codecs_struct->IsSupersetOf(
+ *second_applies_to_all_codecs_struct)) {
+ return false;
+ }
+
+ // Now all elements of the vectors can be assumed to NOT have
+ // |applies_to_all_codecs| set. So iterate through all codecs set in either
+ // vector and check that the first has the less restrictive configuration set.
+ while (first_it != first.end() || second_it != second.end()) {
+ // Calculate the current codec to process, and whether each vector contains
+ // an instance of this codec.
+ decltype(T::codec) current_codec;
+ bool use_first_fake = false;
+ bool use_second_fake = false;
+ if (first_it == first.end()) {
+ current_codec = second_it->codec;
+ use_first_fake = true;
+ } else if (second_it == second.end()) {
+ current_codec = first_it->codec;
+ use_second_fake = true;
+ } else {
+ current_codec = std::min(first_it->codec, second_it->codec);
+ use_first_fake = first_it->codec != current_codec;
+ use_second_fake = second_it->codec != current_codec;
+ }
+
+ // Compare each vector's limit associated with this codec, or compare
+ // against the default limits if no such codec limits are set.
+ T fake_codecs_struct;
+ fake_codecs_struct.codec = current_codec;
+ T* first_codec_struct =
+ use_first_fake ? &fake_codecs_struct : &(*first_it++);
+ T* second_codec_struct =
+ use_second_fake ? &fake_codecs_struct : &(*second_it++);
+ OSP_DCHECK(!first_codec_struct->applies_to_all_codecs);
+ OSP_DCHECK(!second_codec_struct->applies_to_all_codecs);
+ if (!first_codec_struct->IsSupersetOf(*second_codec_struct)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
} // namespace
ReceiverSession::Client::~Client() = default;
using RemotingPreferences = ReceiverSession::RemotingPreferences;
-RemotingPreferences::RemotingPreferences() = default;
-RemotingPreferences::RemotingPreferences(RemotingPreferences&&) noexcept =
- default;
-RemotingPreferences& RemotingPreferences::operator=(
- RemotingPreferences&&) noexcept = default;
-
using Preferences = ReceiverSession::Preferences;
Preferences::Preferences() = default;
@@ -104,6 +190,24 @@ Preferences::Preferences(std::vector<VideoCodec> video_codecs,
Preferences::Preferences(Preferences&&) noexcept = default;
Preferences& Preferences::operator=(Preferences&&) noexcept = default;
+Preferences::Preferences(const Preferences& other) {
+ *this = other;
+}
+
+Preferences& Preferences::operator=(const Preferences& other) {
+ video_codecs = other.video_codecs;
+ audio_codecs = other.audio_codecs;
+ audio_limits = other.audio_limits;
+ video_limits = other.video_limits;
+ if (other.display_description) {
+ display_description = std::make_unique<Display>(*other.display_description);
+ }
+ if (other.remoting) {
+ remoting = std::make_unique<RemotingPreferences>(*other.remoting);
+ }
+ return *this;
+}
+
ReceiverSession::ReceiverSession(Client* const client,
Environment* environment,
MessagePort* message_port,
@@ -458,5 +562,64 @@ void ReceiverSession::SendErrorAnswerReply(int sequence_number,
}
}
+bool ReceiverSession::VideoLimits::IsSupersetOf(
+ const ReceiverSession::VideoLimits& second) const {
+ return (applies_to_all_codecs == second.applies_to_all_codecs) &&
+ (applies_to_all_codecs || codec == second.codec) &&
+ (max_pixels_per_second >= second.max_pixels_per_second) &&
+ (min_bit_rate <= second.min_bit_rate) &&
+ (max_bit_rate >= second.max_bit_rate) &&
+ (max_delay >= second.max_delay) &&
+ (max_dimensions.IsSupersetOf(second.max_dimensions));
+}
+
+bool ReceiverSession::AudioLimits::IsSupersetOf(
+ const ReceiverSession::AudioLimits& second) const {
+ return (applies_to_all_codecs == second.applies_to_all_codecs) &&
+ (applies_to_all_codecs || codec == second.codec) &&
+ (max_sample_rate >= second.max_sample_rate) &&
+ (max_channels >= second.max_channels) &&
+ (min_bit_rate <= second.min_bit_rate) &&
+ (max_bit_rate >= second.max_bit_rate) &&
+ (max_delay >= second.max_delay);
+}
+
+bool ReceiverSession::Display::IsSupersetOf(
+ const ReceiverSession::Display& other) const {
+ return dimensions.IsSupersetOf(other.dimensions) &&
+ (can_scale_content || !other.can_scale_content);
+}
+
+bool ReceiverSession::RemotingPreferences::IsSupersetOf(
+ const ReceiverSession::RemotingPreferences& other) const {
+ return (supports_chrome_audio_codecs ||
+ !other.supports_chrome_audio_codecs) &&
+ (supports_4k || !other.supports_4k);
+}
+
+bool ReceiverSession::Preferences::IsSupersetOf(
+ const ReceiverSession::Preferences& other) const {
+ // Check simple cases first.
+ if ((!!display_description != !!other.display_description) ||
+ (display_description &&
+ !display_description->IsSupersetOf(*other.display_description))) {
+ return false;
+ } else if (other.remoting &&
+ (!remoting || !remoting->IsSupersetOf(*other.remoting))) {
+ return false;
+ }
+
+ // Then check set codecs.
+ if (IsMissingCodecs(video_codecs, other.video_codecs) ||
+ IsMissingCodecs(audio_codecs, other.audio_codecs)) {
+ return false;
+ }
+
+ // Then check limits. Do this last because it's the most resource intensive to
+ // check.
+ return HasLessRestrictiveLimits(video_limits, other.video_limits) &&
+ HasLessRestrictiveLimits(audio_limits, other.audio_limits);
+}
+
} // namespace cast
} // namespace openscreen
diff --git a/cast/streaming/receiver_session.h b/cast/streaming/receiver_session.h
index 60ed7c29..fe4f9481 100644
--- a/cast/streaming/receiver_session.h
+++ b/cast/streaming/receiver_session.h
@@ -115,6 +115,10 @@ class ReceiverSession final : public Environment::SocketSubscriber {
// Information about the display the receiver is attached to.
struct Display {
+ // Returns true if all configurations supported by |other| are also
+ // supported by this instance.
+ bool IsSupersetOf(const Display& other) const;
+
// The display limitations of the actual screen, used to provide upper
// bounds on streams. For example, we will never
// send 60FPS if it is going to be displayed on a 30FPS screen.
@@ -129,6 +133,10 @@ class ReceiverSession final : public Environment::SocketSubscriber {
// Codec-specific audio limits for playback.
struct AudioLimits {
+ // Returns true if all configurations supported by |other| are also
+ // supported by this instance.
+ bool IsSupersetOf(const AudioLimits& other) const;
+
// Whether or not these limits apply to all codecs.
bool applies_to_all_codecs = false;
@@ -154,6 +162,10 @@ class ReceiverSession final : public Environment::SocketSubscriber {
// Codec-specific video limits for playback.
struct VideoLimits {
+ // Returns true if all configurations supported by |other| are also
+ // supported by this instance.
+ bool IsSupersetOf(const VideoLimits& other) const;
+
// Whether or not these limits apply to all codecs.
bool applies_to_all_codecs = false;
@@ -183,11 +195,9 @@ class ReceiverSession final : public Environment::SocketSubscriber {
// remoting streams. These properties are based on the current control
// protocol and allow remoting with current senders.
struct RemotingPreferences {
- RemotingPreferences();
- RemotingPreferences(RemotingPreferences&&) noexcept;
- RemotingPreferences(const RemotingPreferences&) = delete;
- RemotingPreferences& operator=(RemotingPreferences&&) noexcept;
- RemotingPreferences& operator=(const RemotingPreferences&) = delete;
+ // Returns true if all configurations supported by |other| are also
+ // supported by this instance.
+ bool IsSupersetOf(const RemotingPreferences& other) const;
// Current remoting senders take an "all or nothing" support for audio
// codec support. While Opus and AAC support is handled in our Preferences'
@@ -224,9 +234,13 @@ class ReceiverSession final : public Environment::SocketSubscriber {
std::unique_ptr<Display> description);
Preferences(Preferences&&) noexcept;
- Preferences(const Preferences&) = delete;
+ Preferences(const Preferences&);
Preferences& operator=(Preferences&&) noexcept;
- Preferences& operator=(const Preferences&) = delete;
+ Preferences& operator=(const Preferences&);
+
+ // Returns true if all configurations supported by |other| are also
+ // supported by this instance.
+ bool IsSupersetOf(const Preferences& other) const;
// Audio and video codec preferences. Should be supplied in order of
// preference, e.g. in this example if we get both VP8 and H264 we will
diff --git a/cast/streaming/receiver_session_unittest.cc b/cast/streaming/receiver_session_unittest.cc
index 0a7ad05f..422aecbe 100644
--- a/cast/streaming/receiver_session_unittest.cc
+++ b/cast/streaming/receiver_session_unittest.cc
@@ -752,5 +752,246 @@ TEST_F(ReceiverSessionTest, ReturnsCapabilitiesWithRemotingPreferences) {
MediaCapability::k4k));
}
+TEST_F(ReceiverSessionTest, VideoLimitsIsSupersetOf) {
+ ReceiverSession::VideoLimits first;
+ ReceiverSession::VideoLimits second = first;
+
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_TRUE(second.IsSupersetOf(first));
+
+ first.max_pixels_per_second += 1;
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_FALSE(second.IsSupersetOf(first));
+ first.max_pixels_per_second = second.max_pixels_per_second;
+
+ first.max_dimensions = {1921, 1090, {kDefaultFrameRate, 1}};
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_FALSE(second.IsSupersetOf(first));
+
+ second.max_dimensions = {1921, 1090, {kDefaultFrameRate + 1, 1}};
+ EXPECT_FALSE(first.IsSupersetOf(second));
+ EXPECT_TRUE(second.IsSupersetOf(first));
+
+ second.max_dimensions = {2000, 1000, {kDefaultFrameRate, 1}};
+ EXPECT_FALSE(first.IsSupersetOf(second));
+ EXPECT_FALSE(second.IsSupersetOf(first));
+ second.max_dimensions = first.max_dimensions;
+
+ first.min_bit_rate += 1;
+ EXPECT_FALSE(first.IsSupersetOf(second));
+ EXPECT_TRUE(second.IsSupersetOf(first));
+ first.min_bit_rate = second.min_bit_rate;
+
+ first.max_bit_rate += 1;
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_FALSE(second.IsSupersetOf(first));
+ first.max_bit_rate = second.max_bit_rate;
+
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_TRUE(second.IsSupersetOf(first));
+
+ first.applies_to_all_codecs = true;
+ EXPECT_FALSE(first.IsSupersetOf(second));
+ EXPECT_FALSE(second.IsSupersetOf(first));
+ second.applies_to_all_codecs = true;
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_TRUE(second.IsSupersetOf(first));
+ first.codec = VideoCodec::kVp8;
+ second.codec = VideoCodec::kVp9;
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_TRUE(second.IsSupersetOf(first));
+ first.applies_to_all_codecs = false;
+ second.applies_to_all_codecs = false;
+ EXPECT_FALSE(first.IsSupersetOf(second));
+ EXPECT_FALSE(second.IsSupersetOf(first));
+}
+
+TEST_F(ReceiverSessionTest, AudioLimitsIsSupersetOf) {
+ ReceiverSession::AudioLimits first;
+ ReceiverSession::AudioLimits second = first;
+
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_TRUE(second.IsSupersetOf(first));
+
+ first.max_sample_rate += 1;
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_FALSE(second.IsSupersetOf(first));
+ first.max_sample_rate = second.max_sample_rate;
+
+ first.max_channels += 1;
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_FALSE(second.IsSupersetOf(first));
+ first.max_channels = second.max_channels;
+
+ first.min_bit_rate += 1;
+ EXPECT_FALSE(first.IsSupersetOf(second));
+ EXPECT_TRUE(second.IsSupersetOf(first));
+ first.min_bit_rate = second.min_bit_rate;
+
+ first.max_bit_rate += 1;
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_FALSE(second.IsSupersetOf(first));
+ first.max_bit_rate = second.max_bit_rate;
+
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_TRUE(second.IsSupersetOf(first));
+
+ first.applies_to_all_codecs = true;
+ EXPECT_FALSE(first.IsSupersetOf(second));
+ EXPECT_FALSE(second.IsSupersetOf(first));
+ second.applies_to_all_codecs = true;
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_TRUE(second.IsSupersetOf(first));
+ first.codec = AudioCodec::kOpus;
+ second.codec = AudioCodec::kAac;
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_TRUE(second.IsSupersetOf(first));
+ first.applies_to_all_codecs = false;
+ second.applies_to_all_codecs = false;
+ EXPECT_FALSE(first.IsSupersetOf(second));
+ EXPECT_FALSE(second.IsSupersetOf(first));
+}
+
+TEST_F(ReceiverSessionTest, DisplayIsSupersetOf) {
+ ReceiverSession::Display first;
+ ReceiverSession::Display second = first;
+
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_TRUE(second.IsSupersetOf(first));
+
+ first.dimensions = {1921, 1090, {kDefaultFrameRate, 1}};
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_FALSE(second.IsSupersetOf(first));
+
+ second.dimensions = {1921, 1090, {kDefaultFrameRate + 1, 1}};
+ EXPECT_FALSE(first.IsSupersetOf(second));
+ EXPECT_TRUE(second.IsSupersetOf(first));
+
+ second.dimensions = {2000, 1000, {kDefaultFrameRate, 1}};
+ EXPECT_FALSE(first.IsSupersetOf(second));
+ EXPECT_FALSE(second.IsSupersetOf(first));
+ second.dimensions = first.dimensions;
+
+ first.can_scale_content = true;
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_FALSE(second.IsSupersetOf(first));
+}
+
+TEST_F(ReceiverSessionTest, RemotingPreferencesIsSupersetOf) {
+ ReceiverSession::RemotingPreferences first;
+ ReceiverSession::RemotingPreferences second = first;
+
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_TRUE(second.IsSupersetOf(first));
+
+ first.supports_chrome_audio_codecs = true;
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_FALSE(second.IsSupersetOf(first));
+
+ second.supports_4k = true;
+ EXPECT_FALSE(first.IsSupersetOf(second));
+ EXPECT_FALSE(second.IsSupersetOf(first));
+
+ second.supports_chrome_audio_codecs = true;
+ EXPECT_FALSE(first.IsSupersetOf(second));
+ EXPECT_TRUE(second.IsSupersetOf(first));
+}
+
+TEST_F(ReceiverSessionTest, PreferencesIsSupersetOf) {
+ ReceiverSession::Preferences first;
+ ReceiverSession::Preferences second(first);
+
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_TRUE(second.IsSupersetOf(first));
+
+ // Modified |display_description|.
+ first.display_description = std::make_unique<ReceiverSession::Display>();
+ first.display_description->dimensions = {1920, 1080, {kDefaultFrameRate, 1}};
+ EXPECT_FALSE(first.IsSupersetOf(second));
+ EXPECT_FALSE(second.IsSupersetOf(first));
+ second = first;
+
+ first.display_description->dimensions = {192, 1080, {kDefaultFrameRate, 1}};
+ EXPECT_FALSE(first.IsSupersetOf(second));
+ EXPECT_TRUE(second.IsSupersetOf(first));
+ second = first;
+
+ // Modified |remoting|.
+ first.remoting = std::make_unique<ReceiverSession::RemotingPreferences>();
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_FALSE(second.IsSupersetOf(first));
+ second = first;
+
+ second.remoting->supports_4k = true;
+ EXPECT_FALSE(first.IsSupersetOf(second));
+ EXPECT_TRUE(second.IsSupersetOf(first));
+ second = first;
+
+ // Modified |video_codecs|.
+ first.video_codecs = {VideoCodec::kVp8, VideoCodec::kVp9};
+ second.video_codecs = {};
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_FALSE(second.IsSupersetOf(first));
+ second.video_codecs = {VideoCodec::kHevc};
+ EXPECT_FALSE(first.IsSupersetOf(second));
+ EXPECT_FALSE(second.IsSupersetOf(first));
+ first.video_codecs.emplace_back(VideoCodec::kHevc);
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_FALSE(second.IsSupersetOf(first));
+ first = second;
+
+ // Modified |audio_codecs|.
+ first.audio_codecs = {AudioCodec::kOpus};
+ second.audio_codecs = {};
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_FALSE(second.IsSupersetOf(first));
+ second.audio_codecs = {AudioCodec::kAac};
+ EXPECT_FALSE(first.IsSupersetOf(second));
+ EXPECT_FALSE(second.IsSupersetOf(first));
+ first.audio_codecs.emplace_back(AudioCodec::kAac);
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_FALSE(second.IsSupersetOf(first));
+ first = second;
+
+ // Modified |video_limits|.
+ first.video_limits.push_back({true, VideoCodec::kVp8});
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_TRUE(second.IsSupersetOf(first));
+ first.video_limits.front().min_bit_rate = -1;
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_FALSE(second.IsSupersetOf(first));
+ second.video_limits.push_back({true, VideoCodec::kVp9});
+ second.video_limits.front().min_bit_rate = -1;
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_TRUE(second.IsSupersetOf(first));
+ first.video_limits.front().applies_to_all_codecs = false;
+ first.video_limits.push_back({false, VideoCodec::kHevc, 123});
+ second.video_limits.front().applies_to_all_codecs = false;
+ EXPECT_FALSE(first.IsSupersetOf(second));
+ EXPECT_FALSE(second.IsSupersetOf(first));
+ second.video_limits.front().min_bit_rate = kDefaultVideoMinBitRate;
+ first.video_limits.front().min_bit_rate = kDefaultVideoMinBitRate;
+ EXPECT_FALSE(first.IsSupersetOf(second));
+ EXPECT_TRUE(second.IsSupersetOf(first));
+ second = first;
+
+ // Modified |audio_limits|.
+ first.audio_limits.push_back({true, AudioCodec::kOpus});
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_TRUE(second.IsSupersetOf(first));
+ first.audio_limits.front().min_bit_rate = -1;
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_FALSE(second.IsSupersetOf(first));
+ second.audio_limits.push_back({true, AudioCodec::kAac});
+ second.audio_limits.front().min_bit_rate = -1;
+ EXPECT_TRUE(first.IsSupersetOf(second));
+ EXPECT_TRUE(second.IsSupersetOf(first));
+ first.audio_limits.front().applies_to_all_codecs = false;
+ first.audio_limits.push_back({false, AudioCodec::kOpus, -1});
+ second.audio_limits.front().applies_to_all_codecs = false;
+ EXPECT_FALSE(first.IsSupersetOf(second));
+ EXPECT_FALSE(second.IsSupersetOf(first));
+}
+
} // namespace cast
} // namespace openscreen
diff --git a/cast/streaming/resolution.cc b/cast/streaming/resolution.cc
index 0bcf5067..9c763cfe 100644
--- a/cast/streaming/resolution.cc
+++ b/cast/streaming/resolution.cc
@@ -70,6 +70,10 @@ bool Resolution::operator!=(const Resolution& other) const {
return !(*this == other);
}
+bool Resolution::IsSupersetOf(const Resolution& other) const {
+ return width >= other.width && height >= other.height;
+}
+
bool Dimensions::TryParse(const Json::Value& root, Dimensions* out) {
if (!json::TryParseInt(root[kWidth], &(out->width)) ||
!json::TryParseInt(root[kHeight], &(out->height)) ||
@@ -104,5 +108,15 @@ bool Dimensions::operator!=(const Dimensions& other) const {
return !(*this == other);
}
+bool Dimensions::IsSupersetOf(const Dimensions& other) const {
+ if (static_cast<double>(frame_rate) !=
+ static_cast<double>(other.frame_rate)) {
+ return static_cast<double>(frame_rate) >=
+ static_cast<double>(other.frame_rate);
+ }
+
+ return ToResolution().IsSupersetOf(other.ToResolution());
+}
+
} // namespace cast
} // namespace openscreen
diff --git a/cast/streaming/resolution.h b/cast/streaming/resolution.h
index ba370820..47815556 100644
--- a/cast/streaming/resolution.h
+++ b/cast/streaming/resolution.h
@@ -23,6 +23,10 @@ struct Resolution {
bool IsValid() const;
Json::Value ToJson() const;
+ // Returns true if both |width| and |height| of this instance are greater than
+ // or equal to that of |other|.
+ bool IsSupersetOf(const Resolution& other) const;
+
bool operator==(const Resolution& other) const;
bool operator!=(const Resolution& other) const;
@@ -37,6 +41,10 @@ struct Dimensions {
bool IsValid() const;
Json::Value ToJson() const;
+ // Returns true if all properties of this instance are greater than or equal
+ // to those of |other|.
+ bool IsSupersetOf(const Dimensions& other) const;
+
bool operator==(const Dimensions& other) const;
bool operator!=(const Dimensions& other) const;