diff options
Diffstat (limited to 'cast')
-rw-r--r-- | cast/streaming/receiver_session.cc | 175 | ||||
-rw-r--r-- | cast/streaming/receiver_session.h | 28 | ||||
-rw-r--r-- | cast/streaming/receiver_session_unittest.cc | 241 | ||||
-rw-r--r-- | cast/streaming/resolution.cc | 14 | ||||
-rw-r--r-- | cast/streaming/resolution.h | 8 |
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; |