1 /*
2 * Copyright 2018 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11 #include "modules/desktop_capture/linux/base_capturer_pipewire.h"
12
13 #include <gio/gunixfdlist.h>
14 #include <glib-object.h>
15
16 #include <spa/param/format-utils.h>
17 #include <spa/param/props.h>
18
19 #include <sys/mman.h>
20 #include <sys/ioctl.h>
21 #include <sys/syscall.h>
22
23 #include <memory>
24 #include <utility>
25
26 #include "modules/desktop_capture/desktop_capture_options.h"
27 #include "modules/desktop_capture/desktop_capturer.h"
28 #include "rtc_base/checks.h"
29 #include "rtc_base/logging.h"
30
31 namespace webrtc {
32
33 const char kDesktopBusName[] = "org.freedesktop.portal.Desktop";
34 const char kDesktopObjectPath[] = "/org/freedesktop/portal/desktop";
35 const char kDesktopRequestObjectPath[] =
36 "/org/freedesktop/portal/desktop/request";
37 const char kSessionInterfaceName[] = "org.freedesktop.portal.Session";
38 const char kRequestInterfaceName[] = "org.freedesktop.portal.Request";
39 const char kScreenCastInterfaceName[] = "org.freedesktop.portal.ScreenCast";
40
41 // static
42 struct dma_buf_sync {
43 uint64_t flags;
44 };
45 #define DMA_BUF_SYNC_READ (1 << 0)
46 #define DMA_BUF_SYNC_START (0 << 2)
47 #define DMA_BUF_SYNC_END (1 << 2)
48 #define DMA_BUF_BASE 'b'
49 #define DMA_BUF_IOCTL_SYNC _IOW(DMA_BUF_BASE, 0, struct dma_buf_sync)
50
SyncDmaBuf(int fd,uint64_t start_or_end)51 static void SyncDmaBuf(int fd, uint64_t start_or_end) {
52 struct dma_buf_sync sync = { 0 };
53
54 sync.flags = start_or_end | DMA_BUF_SYNC_READ;
55
56 while(true) {
57 int ret;
58 ret = ioctl (fd, DMA_BUF_IOCTL_SYNC, &sync);
59 if (ret == -1 && errno == EINTR) {
60 continue;
61 } else if (ret == -1) {
62 RTC_LOG(LS_ERROR) << "Failed to synchronize DMA buffer: " << g_strerror(errno);
63 break;
64 } else {
65 break;
66 }
67 }
68 }
69
70 // static
OnCoreError(void * data,uint32_t id,int seq,int res,const char * message)71 void BaseCapturerPipeWire::OnCoreError(void *data,
72 uint32_t id,
73 int seq,
74 int res,
75 const char *message) {
76 RTC_LOG(LS_ERROR) << "core error: " << message;
77 }
78
79 // static
OnStreamStateChanged(void * data,pw_stream_state old_state,pw_stream_state state,const char * error_message)80 void BaseCapturerPipeWire::OnStreamStateChanged(void* data,
81 pw_stream_state old_state,
82 pw_stream_state state,
83 const char* error_message) {
84 BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(data);
85 RTC_DCHECK(that);
86
87 switch (state) {
88 case PW_STREAM_STATE_ERROR:
89 RTC_LOG(LS_ERROR) << "PipeWire stream state error: " << error_message;
90 break;
91 case PW_STREAM_STATE_PAUSED:
92 case PW_STREAM_STATE_STREAMING:
93 case PW_STREAM_STATE_UNCONNECTED:
94 case PW_STREAM_STATE_CONNECTING:
95 break;
96 }
97 }
98
99 // static
OnStreamParamChanged(void * data,uint32_t id,const struct spa_pod * format)100 void BaseCapturerPipeWire::OnStreamParamChanged(void *data, uint32_t id,
101 const struct spa_pod *format) {
102 BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(data);
103 RTC_DCHECK(that);
104
105 RTC_LOG(LS_INFO) << "PipeWire stream param changed.";
106
107 if (!format || id != SPA_PARAM_Format) {
108 return;
109 }
110
111 spa_format_video_raw_parse(format, &that->spa_video_format_);
112
113 auto width = that->spa_video_format_.size.width;
114 auto height = that->spa_video_format_.size.height;
115 auto stride = SPA_ROUND_UP_N(width * kBytesPerPixel, 4);
116 auto size = height * stride;
117
118 that->desktop_size_ = DesktopSize(width, height);
119
120 uint8_t buffer[1024] = {};
121 auto builder = spa_pod_builder{buffer, sizeof(buffer)};
122
123 // Setup buffers and meta header for new format.
124 const struct spa_pod* params[3];
125 params[0] = reinterpret_cast<spa_pod *>(spa_pod_builder_add_object(&builder,
126 SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
127 SPA_PARAM_BUFFERS_dataType, SPA_POD_Int((1<<SPA_DATA_MemPtr) |
128 (1<<SPA_DATA_MemFd)),
129 SPA_PARAM_BUFFERS_size, SPA_POD_Int(size),
130 SPA_PARAM_BUFFERS_stride, SPA_POD_Int(stride),
131 SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 1, 32)));
132 params[1] = reinterpret_cast<spa_pod *>(spa_pod_builder_add_object(&builder,
133 SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
134 SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header),
135 SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))));
136 params[2] = reinterpret_cast<spa_pod *>(spa_pod_builder_add_object(&builder,
137 SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
138 SPA_PARAM_META_type, SPA_POD_Id (SPA_META_VideoCrop),
139 SPA_PARAM_META_size, SPA_POD_Int (sizeof(struct spa_meta_region))));
140 pw_stream_update_params(that->pw_stream_, params, 3);
141 }
142
143 // static
OnStreamProcess(void * data)144 void BaseCapturerPipeWire::OnStreamProcess(void* data) {
145 BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(data);
146 RTC_DCHECK(that);
147
148 struct pw_buffer *next_buffer;
149 struct pw_buffer *buffer = nullptr;
150
151 next_buffer = pw_stream_dequeue_buffer(that->pw_stream_);
152 while (next_buffer) {
153 buffer = next_buffer;
154 next_buffer = pw_stream_dequeue_buffer(that->pw_stream_);
155
156 if (next_buffer) {
157 pw_stream_queue_buffer (that->pw_stream_, buffer);
158 }
159 }
160
161 if (!buffer) {
162 return;
163 }
164
165 that->HandleBuffer(buffer);
166
167 pw_stream_queue_buffer(that->pw_stream_, buffer);
168 }
169
BaseCapturerPipeWire(CaptureSourceType source_type)170 BaseCapturerPipeWire::BaseCapturerPipeWire(CaptureSourceType source_type)
171 : capture_source_type_(source_type) {}
172
~BaseCapturerPipeWire()173 BaseCapturerPipeWire::~BaseCapturerPipeWire() {
174 if (pw_main_loop_) {
175 pw_thread_loop_stop(pw_main_loop_);
176 }
177
178 if (pw_stream_) {
179 pw_stream_destroy(pw_stream_);
180 }
181
182 if (pw_core_) {
183 pw_core_disconnect(pw_core_);
184 }
185
186 if (pw_context_) {
187 pw_context_destroy(pw_context_);
188 }
189
190 if (pw_main_loop_) {
191 pw_thread_loop_destroy(pw_main_loop_);
192 }
193
194 if (start_request_signal_id_) {
195 g_dbus_connection_signal_unsubscribe(connection_, start_request_signal_id_);
196 }
197 if (sources_request_signal_id_) {
198 g_dbus_connection_signal_unsubscribe(connection_,
199 sources_request_signal_id_);
200 }
201 if (session_request_signal_id_) {
202 g_dbus_connection_signal_unsubscribe(connection_,
203 session_request_signal_id_);
204 }
205
206 if (session_handle_) {
207 GDBusMessage* message = g_dbus_message_new_method_call(
208 kDesktopBusName, session_handle_, kSessionInterfaceName, "Close");
209 if (message) {
210 GError* error = nullptr;
211 g_dbus_connection_send_message(connection_, message,
212 G_DBUS_SEND_MESSAGE_FLAGS_NONE,
213 /*out_serial=*/nullptr, &error);
214 if (error) {
215 RTC_LOG(LS_ERROR) << "Failed to close the session: " << error->message;
216 g_error_free(error);
217 }
218 g_object_unref(message);
219 }
220 }
221
222 g_free(start_handle_);
223 g_free(sources_handle_);
224 g_free(session_handle_);
225 g_free(portal_handle_);
226
227 if (cancellable_) {
228 g_cancellable_cancel(cancellable_);
229 g_object_unref(cancellable_);
230 cancellable_ = nullptr;
231 }
232
233 if (proxy_) {
234 g_object_unref(proxy_);
235 proxy_ = nullptr;
236 }
237 }
238
InitPortal()239 void BaseCapturerPipeWire::InitPortal() {
240 cancellable_ = g_cancellable_new();
241 g_dbus_proxy_new_for_bus(
242 G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, /*info=*/nullptr,
243 kDesktopBusName, kDesktopObjectPath, kScreenCastInterfaceName,
244 cancellable_,
245 reinterpret_cast<GAsyncReadyCallback>(OnProxyRequested), this);
246 }
247
InitPipeWire()248 void BaseCapturerPipeWire::InitPipeWire() {
249 pipewire_init(/*argc=*/nullptr, /*argc=*/nullptr);
250
251 pw_main_loop_ = pw_thread_loop_new("pipewire-main-loop", nullptr);
252 pw_context_ = pw_context_new(pw_thread_loop_get_loop(pw_main_loop_), nullptr, 0);
253 if (!pw_context_) {
254 RTC_LOG(LS_ERROR) << "Failed to create PipeWire context";
255 return;
256 }
257
258 pw_core_ = pw_context_connect_fd(pw_context_, pw_fd_, nullptr, 0);
259 if (!pw_core_) {
260 RTC_LOG(LS_ERROR) << "Failed to connect PipeWire context";
261 return;
262 }
263
264 // Initialize event handlers, remote end and stream-related.
265 pw_core_events_.version = PW_VERSION_CORE_EVENTS;
266 pw_core_events_.error = &OnCoreError;
267
268 pw_stream_events_.version = PW_VERSION_STREAM_EVENTS;
269 pw_stream_events_.state_changed = &OnStreamStateChanged;
270 pw_stream_events_.param_changed = &OnStreamParamChanged;
271 pw_stream_events_.process = &OnStreamProcess;
272
273 pw_core_add_listener(pw_core_, &spa_core_listener_, &pw_core_events_, this);
274
275 pw_stream_ = CreateReceivingStream();
276 if (!pw_stream_) {
277 RTC_LOG(LS_ERROR) << "Failed to create PipeWire stream";
278 return;
279 }
280
281 if (pw_thread_loop_start(pw_main_loop_) < 0) {
282 RTC_LOG(LS_ERROR) << "Failed to start main PipeWire loop";
283 portal_init_failed_ = true;
284 }
285 }
286
CreateReceivingStream()287 pw_stream* BaseCapturerPipeWire::CreateReceivingStream() {
288 spa_rectangle pwMinScreenBounds = spa_rectangle{1, 1};
289 spa_rectangle pwMaxScreenBounds = spa_rectangle{UINT32_MAX, UINT32_MAX};
290
291 auto stream = pw_stream_new(pw_core_, "webrtc-pipewire-stream", nullptr);
292
293 if (!stream) {
294 RTC_LOG(LS_ERROR) << "Could not create receiving stream.";
295 return nullptr;
296 }
297
298 uint8_t buffer[1024] = {};
299 const spa_pod* params[2];
300 spa_pod_builder builder = SPA_POD_BUILDER_INIT(buffer, sizeof (buffer));
301
302 params[0] = reinterpret_cast<spa_pod *>(spa_pod_builder_add_object(&builder,
303 SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
304 SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video),
305 SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
306 SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(5, SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_RGBx, SPA_VIDEO_FORMAT_RGBA,
307 SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_BGRA),
308 SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(&pwMinScreenBounds,
309 &pwMinScreenBounds,
310 &pwMaxScreenBounds),
311 SPA_FORMAT_VIDEO_modifier, SPA_POD_Long(0),
312 0));
313 pw_stream_add_listener(stream, &spa_stream_listener_, &pw_stream_events_, this);
314
315 if (pw_stream_connect(stream, PW_DIRECTION_INPUT, pw_stream_node_id_,
316 PW_STREAM_FLAG_AUTOCONNECT, params, 1) != 0) {
317 RTC_LOG(LS_ERROR) << "Could not connect receiving stream.";
318 portal_init_failed_ = true;
319 }
320
321 return stream;
322 }
323
SpaBufferUnmap(unsigned char * map,int map_size,bool IsDMABuf,int fd)324 static void SpaBufferUnmap(unsigned char *map, int map_size, bool IsDMABuf, int fd) {
325 if (map) {
326 if (IsDMABuf) {
327 SyncDmaBuf(fd, DMA_BUF_SYNC_END);
328 }
329 munmap(map, map_size);
330 }
331 }
332
HandleBuffer(pw_buffer * buffer)333 void BaseCapturerPipeWire::HandleBuffer(pw_buffer* buffer) {
334 spa_buffer* spaBuffer = buffer->buffer;
335 uint8_t *map = nullptr;
336 uint8_t* src = nullptr;
337
338 if (spaBuffer->datas[0].chunk->size == 0) {
339 RTC_LOG(LS_ERROR) << "Failed to get video stream: Zero size.";
340 return;
341 }
342
343 switch (spaBuffer->datas[0].type) {
344 case SPA_DATA_MemFd:
345 case SPA_DATA_DmaBuf:
346 map = static_cast<uint8_t*>(mmap(
347 nullptr, spaBuffer->datas[0].maxsize + spaBuffer->datas[0].mapoffset,
348 PROT_READ, MAP_PRIVATE, spaBuffer->datas[0].fd, 0));
349 if (map == MAP_FAILED) {
350 RTC_LOG(LS_ERROR) << "Failed to mmap memory: " << std::strerror(errno);
351 return;
352 }
353 if (spaBuffer->datas[0].type == SPA_DATA_DmaBuf) {
354 SyncDmaBuf(spaBuffer->datas[0].fd, DMA_BUF_SYNC_START);
355 }
356 src = SPA_MEMBER(map, spaBuffer->datas[0].mapoffset, uint8_t);
357 break;
358 case SPA_DATA_MemPtr:
359 map = nullptr;
360 src = static_cast<uint8_t*>(spaBuffer->datas[0].data);
361 break;
362 default:
363 return;
364 }
365
366 if (!src) {
367 RTC_LOG(LS_ERROR) << "Failed to get video stream: Wrong data after mmap()";
368 SpaBufferUnmap(map,
369 spaBuffer->datas[0].maxsize + spaBuffer->datas[0].mapoffset,
370 spaBuffer->datas[0].type == SPA_DATA_DmaBuf, spaBuffer->datas[0].fd);
371 return;
372 }
373
374 struct spa_meta_region* video_metadata =
375 static_cast<struct spa_meta_region*>(
376 spa_buffer_find_meta_data(spaBuffer, SPA_META_VideoCrop, sizeof(*video_metadata)));
377
378 // Video size from metada is bigger than an actual video stream size.
379 // The metadata are wrong or we should up-scale te video...in both cases
380 // just quit now.
381 if (video_metadata &&
382 (video_metadata->region.size.width > (uint32_t)desktop_size_.width() ||
383 video_metadata->region.size.height > (uint32_t)desktop_size_.height())) {
384 RTC_LOG(LS_ERROR) << "Stream metadata sizes are wrong!";
385 SpaBufferUnmap(map,
386 spaBuffer->datas[0].maxsize + spaBuffer->datas[0].mapoffset,
387 spaBuffer->datas[0].type == SPA_DATA_DmaBuf, spaBuffer->datas[0].fd);
388 return;
389 }
390
391 // Use video metada when video size from metadata is set and smaller than
392 // video stream size, so we need to adjust it.
393 video_metadata_use_ = (video_metadata &&
394 video_metadata->region.size.width != 0 &&
395 video_metadata->region.size.height != 0 &&
396 (video_metadata->region.size.width < (uint32_t)desktop_size_.width() ||
397 video_metadata->region.size.height < (uint32_t)desktop_size_.height()));
398
399 DesktopSize video_size_prev = video_size_;
400 if (video_metadata_use_) {
401 video_size_ = DesktopSize(video_metadata->region.size.width,
402 video_metadata->region.size.height);
403 } else {
404 video_size_ = desktop_size_;
405 }
406
407 rtc::CritScope lock(¤t_frame_lock_);
408 if (!current_frame_ || !video_size_.equals(video_size_prev)) {
409 current_frame_ =
410 std::make_unique<uint8_t[]>
411 (video_size_.width() * video_size_.height() * kBytesPerPixel);
412 }
413
414 const int32_t dstStride = video_size_.width() * kBytesPerPixel;
415 const int32_t srcStride = spaBuffer->datas[0].chunk->stride;
416
417 // Adjust source content based on metadata video position
418 if (video_metadata_use_ &&
419 (video_metadata->region.position.y + video_size_.height() <= desktop_size_.height())) {
420 src += srcStride * video_metadata->region.position.y;
421 }
422 const int xOffset =
423 video_metadata_use_ &&
424 (video_metadata->region.position.x + video_size_.width() <= desktop_size_.width())
425 ? video_metadata->region.position.x * kBytesPerPixel
426 : 0;
427
428 uint8_t* dst = current_frame_.get();
429 for (int i = 0; i < video_size_.height(); ++i) {
430 // Adjust source content based on crop video position if needed
431 src += xOffset;
432 std::memcpy(dst, src, dstStride);
433 // If both sides decided to go with the RGBx format we need to convert it to
434 // BGRx to match color format expected by WebRTC.
435 if (spa_video_format_.format == SPA_VIDEO_FORMAT_RGBx ||
436 spa_video_format_.format == SPA_VIDEO_FORMAT_RGBA) {
437 ConvertRGBxToBGRx(dst, dstStride);
438 }
439 src += srcStride - xOffset;
440 dst += dstStride;
441 }
442
443 SpaBufferUnmap(map,
444 spaBuffer->datas[0].maxsize + spaBuffer->datas[0].mapoffset,
445 spaBuffer->datas[0].type == SPA_DATA_DmaBuf, spaBuffer->datas[0].fd);
446 }
447
ConvertRGBxToBGRx(uint8_t * frame,uint32_t size)448 void BaseCapturerPipeWire::ConvertRGBxToBGRx(uint8_t* frame, uint32_t size) {
449 // Change color format for KDE KWin which uses RGBx and not BGRx
450 for (uint32_t i = 0; i < size; i += 4) {
451 uint8_t tempR = frame[i];
452 uint8_t tempB = frame[i + 2];
453 frame[i] = tempB;
454 frame[i + 2] = tempR;
455 }
456 }
457
SetupRequestResponseSignal(const gchar * object_path,GDBusSignalCallback callback)458 guint BaseCapturerPipeWire::SetupRequestResponseSignal(
459 const gchar* object_path,
460 GDBusSignalCallback callback) {
461 return g_dbus_connection_signal_subscribe(
462 connection_, kDesktopBusName, kRequestInterfaceName, "Response",
463 object_path, /*arg0=*/nullptr, G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
464 callback, this, /*user_data_free_func=*/nullptr);
465 }
466
467 // static
OnProxyRequested(GObject *,GAsyncResult * result,gpointer user_data)468 void BaseCapturerPipeWire::OnProxyRequested(GObject* /*object*/,
469 GAsyncResult* result,
470 gpointer user_data) {
471 BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
472 RTC_DCHECK(that);
473
474 GError* error = nullptr;
475 GDBusProxy *proxy = g_dbus_proxy_new_finish(result, &error);
476 if (!proxy) {
477 if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
478 return;
479 RTC_LOG(LS_ERROR) << "Failed to create a proxy for the screen cast portal: "
480 << error->message;
481 g_error_free(error);
482 that->portal_init_failed_ = true;
483 return;
484 }
485 that->proxy_ = proxy;
486 that->connection_ = g_dbus_proxy_get_connection(that->proxy_);
487
488 RTC_LOG(LS_INFO) << "Created proxy for the screen cast portal.";
489 that->SessionRequest();
490 }
491
492 // static
PrepareSignalHandle(GDBusConnection * connection,const gchar * token)493 gchar* BaseCapturerPipeWire::PrepareSignalHandle(GDBusConnection* connection,
494 const gchar* token) {
495 gchar* sender = g_strdup(g_dbus_connection_get_unique_name(connection) + 1);
496 for (int i = 0; sender[i]; i++) {
497 if (sender[i] == '.') {
498 sender[i] = '_';
499 }
500 }
501
502 gchar* handle = g_strconcat(kDesktopRequestObjectPath, "/", sender, "/",
503 token, /*end of varargs*/ nullptr);
504 g_free(sender);
505
506 return handle;
507 }
508
SessionRequest()509 void BaseCapturerPipeWire::SessionRequest() {
510 GVariantBuilder builder;
511 gchar* variant_string;
512
513 g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
514 variant_string =
515 g_strdup_printf("webrtc_session%d", g_random_int_range(0, G_MAXINT));
516 g_variant_builder_add(&builder, "{sv}", "session_handle_token",
517 g_variant_new_string(variant_string));
518 g_free(variant_string);
519 variant_string = g_strdup_printf("webrtc%d", g_random_int_range(0, G_MAXINT));
520 g_variant_builder_add(&builder, "{sv}", "handle_token",
521 g_variant_new_string(variant_string));
522
523 portal_handle_ = PrepareSignalHandle(connection_, variant_string);
524 session_request_signal_id_ = SetupRequestResponseSignal(
525 portal_handle_, OnSessionRequestResponseSignal);
526 g_free(variant_string);
527
528 RTC_LOG(LS_INFO) << "Screen cast session requested.";
529 g_dbus_proxy_call(
530 proxy_, "CreateSession", g_variant_new("(a{sv})", &builder),
531 G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable_,
532 reinterpret_cast<GAsyncReadyCallback>(OnSessionRequested), this);
533 }
534
535 // static
OnSessionRequested(GDBusProxy * proxy,GAsyncResult * result,gpointer user_data)536 void BaseCapturerPipeWire::OnSessionRequested(GDBusProxy *proxy,
537 GAsyncResult* result,
538 gpointer user_data) {
539 BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
540 RTC_DCHECK(that);
541
542 GError* error = nullptr;
543 GVariant* variant = g_dbus_proxy_call_finish(proxy, result, &error);
544 if (!variant) {
545 if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
546 return;
547 RTC_LOG(LS_ERROR) << "Failed to create a screen cast session: "
548 << error->message;
549 g_error_free(error);
550 that->portal_init_failed_ = true;
551 return;
552 }
553 RTC_LOG(LS_INFO) << "Initializing the screen cast session.";
554
555 gchar* handle = nullptr;
556 g_variant_get_child(variant, 0, "o", &handle);
557 g_variant_unref(variant);
558 if (!handle) {
559 RTC_LOG(LS_ERROR) << "Failed to initialize the screen cast session.";
560 if (that->session_request_signal_id_) {
561 g_dbus_connection_signal_unsubscribe(that->connection_,
562 that->session_request_signal_id_);
563 that->session_request_signal_id_ = 0;
564 }
565 that->portal_init_failed_ = true;
566 return;
567 }
568
569 g_free(handle);
570
571 RTC_LOG(LS_INFO) << "Subscribing to the screen cast session.";
572 }
573
574 // static
OnSessionRequestResponseSignal(GDBusConnection * connection,const gchar * sender_name,const gchar * object_path,const gchar * interface_name,const gchar * signal_name,GVariant * parameters,gpointer user_data)575 void BaseCapturerPipeWire::OnSessionRequestResponseSignal(
576 GDBusConnection* connection,
577 const gchar* sender_name,
578 const gchar* object_path,
579 const gchar* interface_name,
580 const gchar* signal_name,
581 GVariant* parameters,
582 gpointer user_data) {
583 BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
584 RTC_DCHECK(that);
585
586 RTC_LOG(LS_INFO)
587 << "Received response for the screen cast session subscription.";
588
589 guint32 portal_response;
590 GVariant* response_data;
591 g_variant_get(parameters, "(u@a{sv})", &portal_response, &response_data);
592 g_variant_lookup(response_data, "session_handle", "s",
593 &that->session_handle_);
594 g_variant_unref(response_data);
595
596 if (!that->session_handle_ || portal_response) {
597 RTC_LOG(LS_ERROR)
598 << "Failed to request the screen cast session subscription.";
599 that->portal_init_failed_ = true;
600 return;
601 }
602
603 that->SourcesRequest();
604 }
605
SourcesRequest()606 void BaseCapturerPipeWire::SourcesRequest() {
607 GVariantBuilder builder;
608 gchar* variant_string;
609
610 g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
611 // We want to record monitor content.
612 g_variant_builder_add(&builder, "{sv}", "types",
613 g_variant_new_uint32(capture_source_type_));
614 // We don't want to allow selection of multiple sources.
615 g_variant_builder_add(&builder, "{sv}", "multiple",
616 g_variant_new_boolean(false));
617 variant_string = g_strdup_printf("webrtc%d", g_random_int_range(0, G_MAXINT));
618 g_variant_builder_add(&builder, "{sv}", "handle_token",
619 g_variant_new_string(variant_string));
620
621 sources_handle_ = PrepareSignalHandle(connection_, variant_string);
622 sources_request_signal_id_ = SetupRequestResponseSignal(
623 sources_handle_, OnSourcesRequestResponseSignal);
624 g_free(variant_string);
625
626 RTC_LOG(LS_INFO) << "Requesting sources from the screen cast session.";
627 g_dbus_proxy_call(
628 proxy_, "SelectSources",
629 g_variant_new("(oa{sv})", session_handle_, &builder),
630 G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable_,
631 reinterpret_cast<GAsyncReadyCallback>(OnSourcesRequested), this);
632 }
633
634 // static
OnSourcesRequested(GDBusProxy * proxy,GAsyncResult * result,gpointer user_data)635 void BaseCapturerPipeWire::OnSourcesRequested(GDBusProxy *proxy,
636 GAsyncResult* result,
637 gpointer user_data) {
638 BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
639 RTC_DCHECK(that);
640
641 GError* error = nullptr;
642 GVariant* variant = g_dbus_proxy_call_finish(proxy, result, &error);
643 if (!variant) {
644 if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
645 return;
646 RTC_LOG(LS_ERROR) << "Failed to request the sources: " << error->message;
647 g_error_free(error);
648 that->portal_init_failed_ = true;
649 return;
650 }
651
652 RTC_LOG(LS_INFO) << "Sources requested from the screen cast session.";
653
654 gchar* handle = nullptr;
655 g_variant_get_child(variant, 0, "o", &handle);
656 g_variant_unref(variant);
657 if (!handle) {
658 RTC_LOG(LS_ERROR) << "Failed to initialize the screen cast session.";
659 if (that->sources_request_signal_id_) {
660 g_dbus_connection_signal_unsubscribe(that->connection_,
661 that->sources_request_signal_id_);
662 that->sources_request_signal_id_ = 0;
663 }
664 that->portal_init_failed_ = true;
665 return;
666 }
667
668 g_free(handle);
669
670 RTC_LOG(LS_INFO) << "Subscribed to sources signal.";
671 }
672
673 // static
OnSourcesRequestResponseSignal(GDBusConnection * connection,const gchar * sender_name,const gchar * object_path,const gchar * interface_name,const gchar * signal_name,GVariant * parameters,gpointer user_data)674 void BaseCapturerPipeWire::OnSourcesRequestResponseSignal(
675 GDBusConnection* connection,
676 const gchar* sender_name,
677 const gchar* object_path,
678 const gchar* interface_name,
679 const gchar* signal_name,
680 GVariant* parameters,
681 gpointer user_data) {
682 BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
683 RTC_DCHECK(that);
684
685 RTC_LOG(LS_INFO) << "Received sources signal from session.";
686
687 guint32 portal_response;
688 g_variant_get(parameters, "(u@a{sv})", &portal_response, nullptr);
689 if (portal_response) {
690 RTC_LOG(LS_ERROR)
691 << "Failed to select sources for the screen cast session.";
692 that->portal_init_failed_ = true;
693 return;
694 }
695
696 that->StartRequest();
697 }
698
StartRequest()699 void BaseCapturerPipeWire::StartRequest() {
700 GVariantBuilder builder;
701 gchar* variant_string;
702
703 g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
704 variant_string = g_strdup_printf("webrtc%d", g_random_int_range(0, G_MAXINT));
705 g_variant_builder_add(&builder, "{sv}", "handle_token",
706 g_variant_new_string(variant_string));
707
708 start_handle_ = PrepareSignalHandle(connection_, variant_string);
709 start_request_signal_id_ =
710 SetupRequestResponseSignal(start_handle_, OnStartRequestResponseSignal);
711 g_free(variant_string);
712
713 // "Identifier for the application window", this is Wayland, so not "x11:...".
714 const gchar parent_window[] = "";
715
716 RTC_LOG(LS_INFO) << "Starting the screen cast session.";
717 g_dbus_proxy_call(
718 proxy_, "Start",
719 g_variant_new("(osa{sv})", session_handle_, parent_window, &builder),
720 G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable_,
721 reinterpret_cast<GAsyncReadyCallback>(OnStartRequested), this);
722 }
723
724 // static
OnStartRequested(GDBusProxy * proxy,GAsyncResult * result,gpointer user_data)725 void BaseCapturerPipeWire::OnStartRequested(GDBusProxy *proxy,
726 GAsyncResult* result,
727 gpointer user_data) {
728 BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
729 RTC_DCHECK(that);
730
731 GError* error = nullptr;
732 GVariant* variant = g_dbus_proxy_call_finish(proxy, result, &error);
733 if (!variant) {
734 if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
735 return;
736 RTC_LOG(LS_ERROR) << "Failed to start the screen cast session: "
737 << error->message;
738 g_error_free(error);
739 that->portal_init_failed_ = true;
740 return;
741 }
742
743 RTC_LOG(LS_INFO) << "Initializing the start of the screen cast session.";
744
745 gchar* handle = nullptr;
746 g_variant_get_child(variant, 0, "o", &handle);
747 g_variant_unref(variant);
748 if (!handle) {
749 RTC_LOG(LS_ERROR)
750 << "Failed to initialize the start of the screen cast session.";
751 if (that->start_request_signal_id_) {
752 g_dbus_connection_signal_unsubscribe(that->connection_,
753 that->start_request_signal_id_);
754 that->start_request_signal_id_ = 0;
755 }
756 that->portal_init_failed_ = true;
757 return;
758 }
759
760 g_free(handle);
761
762 RTC_LOG(LS_INFO) << "Subscribed to the start signal.";
763 }
764
765 // static
OnStartRequestResponseSignal(GDBusConnection * connection,const gchar * sender_name,const gchar * object_path,const gchar * interface_name,const gchar * signal_name,GVariant * parameters,gpointer user_data)766 void BaseCapturerPipeWire::OnStartRequestResponseSignal(
767 GDBusConnection* connection,
768 const gchar* sender_name,
769 const gchar* object_path,
770 const gchar* interface_name,
771 const gchar* signal_name,
772 GVariant* parameters,
773 gpointer user_data) {
774 BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
775 RTC_DCHECK(that);
776
777 RTC_LOG(LS_INFO) << "Start signal received.";
778 guint32 portal_response;
779 GVariant* response_data;
780 GVariantIter* iter = nullptr;
781 g_variant_get(parameters, "(u@a{sv})", &portal_response, &response_data);
782 if (portal_response || !response_data) {
783 RTC_LOG(LS_ERROR) << "Failed to start the screen cast session.";
784 that->portal_init_failed_ = true;
785 return;
786 }
787
788 // Array of PipeWire streams. See
789 // https://github.com/flatpak/xdg-desktop-portal/blob/master/data/org.freedesktop.portal.ScreenCast.xml
790 // documentation for <method name="Start">.
791 if (g_variant_lookup(response_data, "streams", "a(ua{sv})", &iter)) {
792 GVariant* variant;
793
794 while (g_variant_iter_next(iter, "@(ua{sv})", &variant)) {
795 guint32 stream_id;
796 GVariant* options;
797
798 g_variant_get(variant, "(u@a{sv})", &stream_id, &options);
799 RTC_DCHECK(options != nullptr);
800
801 that->pw_stream_node_id_ = stream_id;
802 g_variant_unref(options);
803 g_variant_unref(variant);
804 }
805 }
806 g_variant_iter_free(iter);
807 g_variant_unref(response_data);
808
809 that->OpenPipeWireRemote();
810 }
811
OpenPipeWireRemote()812 void BaseCapturerPipeWire::OpenPipeWireRemote() {
813 GVariantBuilder builder;
814 g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
815
816 RTC_LOG(LS_INFO) << "Opening the PipeWire remote.";
817
818 g_dbus_proxy_call_with_unix_fd_list(
819 proxy_, "OpenPipeWireRemote",
820 g_variant_new("(oa{sv})", session_handle_, &builder),
821 G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, /*fd_list=*/nullptr,
822 cancellable_,
823 reinterpret_cast<GAsyncReadyCallback>(OnOpenPipeWireRemoteRequested),
824 this);
825 }
826
827 // static
OnOpenPipeWireRemoteRequested(GDBusProxy * proxy,GAsyncResult * result,gpointer user_data)828 void BaseCapturerPipeWire::OnOpenPipeWireRemoteRequested(
829 GDBusProxy *proxy,
830 GAsyncResult* result,
831 gpointer user_data) {
832 BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
833 RTC_DCHECK(that);
834
835 GError* error = nullptr;
836 GUnixFDList* outlist = nullptr;
837 GVariant* variant = g_dbus_proxy_call_with_unix_fd_list_finish(
838 proxy, &outlist, result, &error);
839 if (!variant) {
840 if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
841 return;
842 RTC_LOG(LS_ERROR) << "Failed to open the PipeWire remote: "
843 << error->message;
844 g_error_free(error);
845 that->portal_init_failed_ = true;
846 return;
847 }
848
849 gint32 index;
850 g_variant_get(variant, "(h)", &index);
851
852 if ((that->pw_fd_ = g_unix_fd_list_get(outlist, index, &error)) == -1) {
853 RTC_LOG(LS_ERROR) << "Failed to get file descriptor from the list: "
854 << error->message;
855 g_error_free(error);
856 g_variant_unref(variant);
857 that->portal_init_failed_ = true;
858 return;
859 }
860
861 g_variant_unref(variant);
862 g_object_unref(outlist);
863
864 that->InitPipeWire();
865 RTC_LOG(LS_INFO) << "PipeWire remote opened.";
866 }
867
Start(Callback * callback)868 void BaseCapturerPipeWire::Start(Callback* callback) {
869 RTC_DCHECK(!callback_);
870 RTC_DCHECK(callback);
871
872 InitPortal();
873
874 callback_ = callback;
875 }
876
CaptureFrame()877 void BaseCapturerPipeWire::CaptureFrame() {
878 if (portal_init_failed_) {
879 callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
880 return;
881 }
882
883 rtc::CritScope lock(¤t_frame_lock_);
884 if (!current_frame_) {
885 callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
886 return;
887 }
888
889 DesktopSize frame_size = desktop_size_;
890 if (video_metadata_use_) {
891 frame_size = video_size_;
892 }
893
894 std::unique_ptr<DesktopFrame> result(new BasicDesktopFrame(frame_size));
895 result->CopyPixelsFrom(
896 current_frame_.get(), (frame_size.width() * kBytesPerPixel),
897 DesktopRect::MakeWH(frame_size.width(), frame_size.height()));
898 if (!result) {
899 callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
900 return;
901 }
902 callback_->OnCaptureResult(Result::SUCCESS, std::move(result));
903 }
904
905 // Keep in sync with defines at browser/actors/WebRTCParent.jsm
906 // With PipeWire we can't select which system resource is shared so
907 // we don't create a window/screen list. Instead we place these constants
908 // as window name/id so frontend code can identify PipeWire backend
909 // and does not try to create screen/window preview.
910
911 #define PIPEWIRE_ID 0xaffffff
912 #define PIPEWIRE_NAME "####_PIPEWIRE_PORTAL_####"
913
GetSourceList(SourceList * sources)914 bool BaseCapturerPipeWire::GetSourceList(SourceList* sources) {
915 sources->push_back({PIPEWIRE_ID, 0, PIPEWIRE_NAME});
916 return true;
917 }
918
SelectSource(SourceId id)919 bool BaseCapturerPipeWire::SelectSource(SourceId id) {
920 // Screen selection is handled by the xdg-desktop-portal.
921 return id == PIPEWIRE_ID;
922 }
923
924 // static
925 std::unique_ptr<DesktopCapturer>
CreateRawScreenCapturer(const DesktopCaptureOptions & options)926 BaseCapturerPipeWire::CreateRawScreenCapturer(
927 const DesktopCaptureOptions& options) {
928 std::unique_ptr<BaseCapturerPipeWire> capturer =
929 std::make_unique<BaseCapturerPipeWire>(BaseCapturerPipeWire::CaptureSourceType::kAny);
930 return std::move(capturer);}
931
932 // static
933 std::unique_ptr<DesktopCapturer>
CreateRawWindowCapturer(const DesktopCaptureOptions & options)934 BaseCapturerPipeWire::CreateRawWindowCapturer(
935 const DesktopCaptureOptions& options) {
936
937 std::unique_ptr<BaseCapturerPipeWire> capturer =
938 std::make_unique<BaseCapturerPipeWire>(BaseCapturerPipeWire::CaptureSourceType::kAny);
939 return std::move(capturer);
940 }
941
942 } // namespace webrtc
943