diff --git a/modules/desktop_capture/desktop_and_cursor_composer.cc b/modules/desktop_capture/desktop_and_cursor_composer.cc index dd688ac5f2..3bdc1ee008 100644 --- a/modules/desktop_capture/desktop_and_cursor_composer.cc +++ b/modules/desktop_capture/desktop_and_cursor_composer.cc @@ -177,6 +177,10 @@ void DesktopAndCursorComposer::Start(DesktopCapturer::Callback* callback) { desktop_capturer_->Start(this); } +void DesktopAndCursorComposer::SetMaxFrameRate(uint32_t max_frame_rate) { + desktop_capturer_->SetMaxFrameRate(max_frame_rate); +} + void DesktopAndCursorComposer::SetSharedMemoryFactory( std::unique_ptr shared_memory_factory) { desktop_capturer_->SetSharedMemoryFactory(std::move(shared_memory_factory)); diff --git a/modules/desktop_capture/desktop_and_cursor_composer.h b/modules/desktop_capture/desktop_and_cursor_composer.h index a078b3eeef..d9208b04a7 100644 --- a/modules/desktop_capture/desktop_and_cursor_composer.h +++ b/modules/desktop_capture/desktop_and_cursor_composer.h @@ -61,6 +61,7 @@ class RTC_EXPORT DesktopAndCursorComposer bool SelectSource(SourceId id) override; bool FocusOnSelectedSource() override; bool IsOccluded(const DesktopVector& pos) override; + void SetMaxFrameRate(uint32_t max_frame_rate) override; #if defined(WEBRTC_USE_GIO) DesktopCaptureMetadata GetMetadata() override; #endif // defined(WEBRTC_USE_GIO) diff --git a/modules/desktop_capture/desktop_capturer.h b/modules/desktop_capture/desktop_capturer.h index 3e8f0dcac5..9a054b6f13 100644 --- a/modules/desktop_capture/desktop_capturer.h +++ b/modules/desktop_capture/desktop_capturer.h @@ -100,6 +100,12 @@ class RTC_EXPORT DesktopCapturer { // valid until capturer is destroyed. virtual void Start(Callback* callback) = 0; + // Sets max frame rate for the capturer. This is best effort and may not be + // supported by all capturers. This will only affect the frequency at which + // new frames are available, not the frequency at which you are allowed to + // capture the frames. + virtual void SetMaxFrameRate(uint32_t max_frame_rate) {} + // Returns a valid pointer if the capturer requires the user to make a // selection from a source list provided by the capturer. // Returns nullptr if the capturer does not provide a UI for the user to make diff --git a/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc b/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc index cf4f7dc9aa..66d0c30f22 100644 --- a/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc +++ b/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc @@ -118,6 +118,13 @@ void BaseCapturerPipeWire::UpdateResolution(uint32_t width, uint32_t height) { } } +void BaseCapturerPipeWire::SetMaxFrameRate(uint32_t max_frame_rate) { + if (!capturer_failed_) { + options_.screencast_stream()->UpdateScreenCastStreamFrameRate( + max_frame_rate); + } +} + void BaseCapturerPipeWire::Start(Callback* callback) { RTC_DCHECK(!callback_); RTC_DCHECK(callback); diff --git a/modules/desktop_capture/linux/wayland/base_capturer_pipewire.h b/modules/desktop_capture/linux/wayland/base_capturer_pipewire.h index 4b5cdc4a65..3b708078ad 100644 --- a/modules/desktop_capture/linux/wayland/base_capturer_pipewire.h +++ b/modules/desktop_capture/linux/wayland/base_capturer_pipewire.h @@ -49,6 +49,7 @@ class RTC_EXPORT BaseCapturerPipeWire bool GetSourceList(SourceList* sources) override; bool SelectSource(SourceId id) override; DelegatedSourceListController* GetDelegatedSourceListController() override; + void SetMaxFrameRate(uint32_t max_frame_rate) override; // DelegatedSourceListController void Observe(Observer* observer) override; diff --git a/modules/desktop_capture/linux/wayland/screencast_stream_utils.cc b/modules/desktop_capture/linux/wayland/screencast_stream_utils.cc index 0c4900d1cd..8177cfd211 100644 --- a/modules/desktop_capture/linux/wayland/screencast_stream_utils.cc +++ b/modules/desktop_capture/linux/wayland/screencast_stream_utils.cc @@ -67,11 +67,11 @@ bool PipeWireVersion::operator<=(const PipeWireVersion& other) { spa_pod* BuildFormat(spa_pod_builder* builder, uint32_t format, const std::vector& modifiers, - const struct spa_rectangle* resolution) { + const struct spa_rectangle* resolution, + const struct spa_fraction* frame_rate) { spa_pod_frame frames[2]; spa_rectangle pw_min_screen_bounds = spa_rectangle{1, 1}; spa_rectangle pw_max_screen_bounds = spa_rectangle{UINT32_MAX, UINT32_MAX}; - spa_pod_builder_push_object(builder, &frames[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, @@ -116,7 +116,17 @@ spa_pod* BuildFormat(spa_pod_builder* builder, &pw_max_screen_bounds), 0); } - + if (frame_rate) { + static const spa_fraction pw_min_frame_rate = spa_fraction{0, 1}; + spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_framerate, + SPA_POD_CHOICE_RANGE_Fraction( + frame_rate, &pw_min_frame_rate, frame_rate), + 0); + spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_maxFramerate, + SPA_POD_CHOICE_RANGE_Fraction( + frame_rate, &pw_min_frame_rate, frame_rate), + 0); + } return static_cast(spa_pod_builder_pop(builder, &frames[0])); } diff --git a/modules/desktop_capture/linux/wayland/screencast_stream_utils.h b/modules/desktop_capture/linux/wayland/screencast_stream_utils.h index e04d7db931..2f44300f73 100644 --- a/modules/desktop_capture/linux/wayland/screencast_stream_utils.h +++ b/modules/desktop_capture/linux/wayland/screencast_stream_utils.h @@ -21,6 +21,7 @@ struct spa_pod; struct spa_pod_builder; struct spa_rectangle; +struct spa_fraction; namespace webrtc { @@ -44,7 +45,8 @@ struct PipeWireVersion { spa_pod* BuildFormat(spa_pod_builder* builder, uint32_t format, const std::vector& modifiers, - const struct spa_rectangle* resolution); + const struct spa_rectangle* resolution, + const struct spa_fraction* frame_rate); } // namespace webrtc diff --git a/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc b/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc index 71bde9b212..7c4a78de64 100644 --- a/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc +++ b/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc @@ -80,6 +80,7 @@ class SharedScreenCastStreamPrivate { uint32_t height = 0, bool is_cursor_embedded = false); void UpdateScreenCastStreamResolution(uint32_t width, uint32_t height); + void UpdateScreenCastStreamFrameRate(uint32_t frame_rate); void SetUseDamageRegion(bool use_damage_region) { use_damage_region_ = use_damage_region; } @@ -138,9 +139,8 @@ class SharedScreenCastStreamPrivate { // Resolution parameters. uint32_t width_ = 0; uint32_t height_ = 0; - webrtc::Mutex resolution_lock_; - // Resolution changes are processed during buffer processing. - bool pending_resolution_change_ RTC_GUARDED_BY(&resolution_lock_) = false; + // Frame rate. + uint32_t frame_rate_ = 60; bool use_damage_region_ = true; @@ -256,6 +256,12 @@ void SharedScreenCastStreamPrivate::OnStreamParamChanged( spa_format_video_raw_parse(format, &that->spa_video_format_); + if (that->observer_ && that->spa_video_format_.max_framerate.denom) { + that->observer_->OnFrameRateChanged( + that->spa_video_format_.max_framerate.num / + that->spa_video_format_.max_framerate.denom); + } + auto width = that->spa_video_format_.size.width; auto height = that->spa_video_format_.size.height; auto stride = SPA_ROUND_UP_N(width * kBytesPerPixel, 4); @@ -355,22 +361,22 @@ void SharedScreenCastStreamPrivate::OnRenegotiateFormat(void* data, uint64_t) { std::vector params; struct spa_rectangle resolution = SPA_RECTANGLE(that->width_, that->height_); + struct spa_fraction frame_rate = SPA_FRACTION(that->frame_rate_, 1); - webrtc::MutexLock lock(&that->resolution_lock_); for (uint32_t format : {SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_RGBA, SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_RGBx}) { if (!that->modifiers_.empty()) { - params.push_back(BuildFormat( - &builder, format, that->modifiers_, - that->pending_resolution_change_ ? &resolution : nullptr)); + params.push_back( + BuildFormat(&builder, format, that->modifiers_, + that->width_ && that->height_ ? &resolution : nullptr, + &frame_rate)); } params.push_back(BuildFormat( &builder, format, /*modifiers=*/{}, - that->pending_resolution_change_ ? &resolution : nullptr)); + that->width_ && that->height_ ? &resolution : nullptr, &frame_rate)); } pw_stream_update_params(that->pw_stream_, params.data(), params.size()); - that->pending_resolution_change_ = false; } } @@ -479,6 +485,7 @@ bool SharedScreenCastStreamPrivate::StartScreenCastStream( resolution = SPA_RECTANGLE(width, height); set_resolution = true; } + struct spa_fraction default_frame_rate = SPA_FRACTION(frame_rate_, 1); for (uint32_t format : {SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_RGBA, SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_RGBx}) { // Modifiers can be used with PipeWire >= 0.3.33 @@ -487,12 +494,14 @@ bool SharedScreenCastStreamPrivate::StartScreenCastStream( if (!modifiers_.empty()) { params.push_back(BuildFormat(&builder, format, modifiers_, - set_resolution ? &resolution : nullptr)); + set_resolution ? &resolution : nullptr, + &default_frame_rate)); } } params.push_back(BuildFormat(&builder, format, /*modifiers=*/{}, - set_resolution ? &resolution : nullptr)); + set_resolution ? &resolution : nullptr, + &default_frame_rate)); } if (pw_stream_connect(pw_stream_, PW_DIRECTION_INPUT, pw_stream_node_id_, @@ -528,10 +537,24 @@ void SharedScreenCastStreamPrivate::UpdateScreenCastStreamResolution( if (width_ != width || height_ != height) { width_ = width; height_ = height; - { - webrtc::MutexLock lock(&resolution_lock_); - pending_resolution_change_ = true; - } + pw_loop_signal_event(pw_thread_loop_get_loop(pw_main_loop_), renegotiate_); + } +} + +RTC_NO_SANITIZE("cfi-icall") +void SharedScreenCastStreamPrivate::UpdateScreenCastStreamFrameRate( + uint32_t frame_rate) { + if (!pw_main_loop_) { + RTC_LOG(LS_WARNING) << "No main pipewire loop, ignoring frame rate change"; + return; + } + if (!renegotiate_) { + RTC_LOG(LS_WARNING) << "Can not renegotiate stream params, ignoring " + << "frame rate change"; + return; + } + if (frame_rate_ != frame_rate) { + frame_rate_ = frame_rate; pw_loop_signal_event(pw_thread_loop_get_loop(pw_main_loop_), renegotiate_); } } @@ -925,6 +948,11 @@ void SharedScreenCastStream::UpdateScreenCastStreamResolution(uint32_t width, private_->UpdateScreenCastStreamResolution(width, height); } +void SharedScreenCastStream::UpdateScreenCastStreamFrameRate( + uint32_t frame_rate) { + private_->UpdateScreenCastStreamFrameRate(frame_rate); +} + void SharedScreenCastStream::SetUseDamageRegion(bool use_damage_region) { private_->SetUseDamageRegion(use_damage_region); } diff --git a/modules/desktop_capture/linux/wayland/shared_screencast_stream.h b/modules/desktop_capture/linux/wayland/shared_screencast_stream.h index 9cdd3d89be..a130e53bbe 100644 --- a/modules/desktop_capture/linux/wayland/shared_screencast_stream.h +++ b/modules/desktop_capture/linux/wayland/shared_screencast_stream.h @@ -35,6 +35,7 @@ class RTC_EXPORT SharedScreenCastStream virtual void OnDesktopFrameChanged() = 0; virtual void OnFailedToProcessBuffer() = 0; virtual void OnStreamConfigured() = 0; + virtual void OnFrameRateChanged(uint32_t frame_rate) = 0; protected: Observer() = default; @@ -50,6 +51,7 @@ class RTC_EXPORT SharedScreenCastStream uint32_t height = 0, bool is_cursor_embedded = false); void UpdateScreenCastStreamResolution(uint32_t width, uint32_t height); + void UpdateScreenCastStreamFrameRate(uint32_t frame_rate); void SetUseDamageRegion(bool use_damage_region); void SetObserver(SharedScreenCastStream::Observer* observer); void StopScreenCastStream(); diff --git a/modules/desktop_capture/linux/wayland/shared_screencast_stream_unittest.cc b/modules/desktop_capture/linux/wayland/shared_screencast_stream_unittest.cc index 1de5f19013..6a72edd025 100644 --- a/modules/desktop_capture/linux/wayland/shared_screencast_stream_unittest.cc +++ b/modules/desktop_capture/linux/wayland/shared_screencast_stream_unittest.cc @@ -56,6 +56,7 @@ class PipeWireStreamTest : public ::testing::Test, MOCK_METHOD(void, OnDesktopFrameChanged, (), (override)); MOCK_METHOD(void, OnFailedToProcessBuffer, (), (override)); MOCK_METHOD(void, OnStreamConfigured, (), (override)); + MOCK_METHOD(void, OnFrameRateChanged, (uint32_t), (override)); void SetUp() override { shared_screencast_stream_ = SharedScreenCastStream::CreateDefault(); @@ -80,6 +81,8 @@ TEST_F(PipeWireStreamTest, TestPipeWire) { // Set expectations for PipeWire to successfully connect both streams rtc::Event waitConnectEvent; rtc::Event waitStartStreamingEvent; + rtc::Event waitStreamParamChangedEvent1; + rtc::Event waitStreamParamChangedEvent2; EXPECT_CALL(*this, OnStreamReady(_)) .WillOnce(Invoke(this, &PipeWireStreamTest::StartScreenCastStream)); @@ -90,6 +93,7 @@ TEST_F(PipeWireStreamTest, TestPipeWire) { EXPECT_CALL(*this, OnStartStreaming).WillOnce([&waitStartStreamingEvent] { waitStartStreamingEvent.Set(); }); + EXPECT_CALL(*this, OnFrameRateChanged(60)).Times(1); // Default frame rate. // Give it some time to connect, the order between these shouldn't matter, but // we need to be sure we are connected before we proceed to work with frames. @@ -152,6 +156,23 @@ TEST_F(PipeWireStreamTest, TestPipeWire) { frameRetrievedEvent.Wait(kShortWait); EXPECT_EQ(RgbaColor(frame->data()), blue_color); + // Update stream parameters. + EXPECT_CALL(*this, OnFrameRateChanged(0)) + .Times(1) + .WillOnce([&waitStreamParamChangedEvent1] { + waitStreamParamChangedEvent1.Set(); + }); + shared_screencast_stream_->UpdateScreenCastStreamFrameRate(0); + waitStreamParamChangedEvent1.Wait(kShortWait); + + EXPECT_CALL(*this, OnFrameRateChanged(22)) + .Times(1) + .WillOnce([&waitStreamParamChangedEvent2] { + waitStreamParamChangedEvent2.Set(); + }); + shared_screencast_stream_->UpdateScreenCastStreamFrameRate(22); + waitStreamParamChangedEvent2.Wait(kShortWait); + // Test disconnection from stream EXPECT_CALL(*this, OnStopStreaming); shared_screencast_stream_->StopScreenCastStream(); diff --git a/modules/desktop_capture/linux/wayland/test/test_screencast_stream_provider.cc b/modules/desktop_capture/linux/wayland/test/test_screencast_stream_provider.cc index 3b829959ac..ffba137753 100644 --- a/modules/desktop_capture/linux/wayland/test/test_screencast_stream_provider.cc +++ b/modules/desktop_capture/linux/wayland/test/test_screencast_stream_provider.cc @@ -90,8 +90,10 @@ TestScreenCastStreamProvider::TestScreenCastStreamProvider(Observer* observer, spa_rectangle resolution = SPA_RECTANGLE(uint32_t(width_), uint32_t(height_)); + struct spa_fraction default_frame_rate = SPA_FRACTION(60, 1); params.push_back(BuildFormat(&builder, SPA_VIDEO_FORMAT_BGRx, - /*modifiers=*/{}, &resolution)); + /*modifiers=*/{}, &resolution, + &default_frame_rate)); auto flags = pw_stream_flags(PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_ALLOC_BUFFERS);