aboutsummaryrefslogtreecommitdiff
path: root/cast
diff options
context:
space:
mode:
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;