desktop_capturer: Support frame rate negotiation via pipewire
This change adds support for renegotiating the frame rate
via pipewire.
Bug: chromium:1291247
Change-Id: Iacd4a3c924900839a8db75a50b448df6c48c83ab
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/291460
Commit-Queue: Salman Malik <salmanmalik@chromium.org>
Reviewed-by: Alexander Cooper <alcooper@chromium.org>
Cr-Commit-Position: refs/heads/main@{#39216}
diff --git a/modules/desktop_capture/desktop_and_cursor_composer.cc b/modules/desktop_capture/desktop_and_cursor_composer.cc
index dd688ac..3bdc1ee 100644
--- a/modules/desktop_capture/desktop_and_cursor_composer.cc
+++ b/modules/desktop_capture/desktop_and_cursor_composer.cc
@@ -177,6 +177,10 @@
desktop_capturer_->Start(this);
}
+void DesktopAndCursorComposer::SetMaxFrameRate(uint32_t max_frame_rate) {
+ desktop_capturer_->SetMaxFrameRate(max_frame_rate);
+}
+
void DesktopAndCursorComposer::SetSharedMemoryFactory(
std::unique_ptr<SharedMemoryFactory> 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 a078b3e..d9208b0 100644
--- a/modules/desktop_capture/desktop_and_cursor_composer.h
+++ b/modules/desktop_capture/desktop_and_cursor_composer.h
@@ -61,6 +61,7 @@
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 3e8f0dc..9a054b6 100644
--- a/modules/desktop_capture/desktop_capturer.h
+++ b/modules/desktop_capture/desktop_capturer.h
@@ -100,6 +100,12 @@
// 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 cf4f7dc..66d0c30 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::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 4b5cdc4..3b70807 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 @@
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 0c4900d..8177cfd 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 @@
spa_pod* BuildFormat(spa_pod_builder* builder,
uint32_t format,
const std::vector<uint64_t>& 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 @@
&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*>(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 e04d7db..2f44300 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 @@
spa_pod* BuildFormat(spa_pod_builder* builder,
uint32_t format,
const std::vector<uint64_t>& 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 71bde9b..7c4a78d 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 @@
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 @@
// 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 @@
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 @@
std::vector<const spa_pod*> 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 @@
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 @@
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 @@
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 @@
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 9cdd3d8..a130e53 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 @@
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 @@
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 1de5f19..6a72edd 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 @@
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 @@
// 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 @@
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 @@
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 3b82995..ffba137 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 @@
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);