|
1 diff --git a/config/system-headers.mozbuild b/config/system-headers.mozbuild |
|
2 index 2081d0c683a4..641133bf1ea4 100644 |
|
3 --- a/config/system-headers.mozbuild |
|
4 +++ b/config/system-headers.mozbuild |
|
5 @@ -314,6 +314,7 @@ system_headers = [ |
|
6 'Gestalt.h', |
|
7 'getopt.h', |
|
8 'gio/gio.h', |
|
9 + 'gio/gunixfdlist.h', |
|
10 'glibconfig.h', |
|
11 'glib.h', |
|
12 'glib-object.h', |
|
13 @@ -607,6 +608,7 @@ system_headers = [ |
|
14 'Pgenerr.h', |
|
15 'PGenErr.h', |
|
16 'Ph.h', |
|
17 + 'pipewire/pipewire.h', |
|
18 'pixman.h', |
|
19 'pk11func.h', |
|
20 'pk11pqg.h', |
|
21 diff --git a/media/webrtc/trunk/webrtc/modules/desktop_capture/BUILD.gn b/media/webrtc/trunk/webrtc/modules/desktop_capture/BUILD.gn |
|
22 index ba885217b3ba..201d3b755221 100644 |
|
23 --- a/media/webrtc/trunk/webrtc/modules/desktop_capture/BUILD.gn |
|
24 +++ b/media/webrtc/trunk/webrtc/modules/desktop_capture/BUILD.gn |
|
25 @@ -158,7 +158,7 @@ if (rtc_include_tests) { |
|
26 if (is_linux) { |
|
27 if (rtc_use_pipewire) { |
|
28 pkg_config("pipewire") { |
|
29 - packages = [ "libpipewire-0.2" ] |
|
30 + packages = [ "libpipewire-0.3" ] |
|
31 |
|
32 defines = [ "WEBRTC_USE_PIPEWIRE" ] |
|
33 } |
|
34 diff --git a/media/webrtc/trunk/webrtc/modules/desktop_capture/desktop_capture_generic_gn/moz.build b/media/webrtc/trunk/webrtc/modules/desktop_capture/desktop_capture_generic_gn/moz.build |
|
35 index 90b40431c7e4..d844aa79d591 100644 |
|
36 --- a/media/webrtc/trunk/webrtc/modules/desktop_capture/desktop_capture_generic_gn/moz.build |
|
37 +++ b/media/webrtc/trunk/webrtc/modules/desktop_capture/desktop_capture_generic_gn/moz.build |
|
38 @@ -194,6 +194,30 @@ if CONFIG["OS_TARGET"] == "Linux": |
|
39 "/media/webrtc/trunk/webrtc/modules/desktop_capture/window_capturer_linux.cc" |
|
40 ] |
|
41 |
|
42 +# PipeWire specific files |
|
43 +if CONFIG["OS_TARGET"] == "Linux": |
|
44 + |
|
45 + DEFINES["WEBRTC_USE_PIPEWIRE"] = "1" |
|
46 + |
|
47 + OS_LIBS += [ |
|
48 + "rt", |
|
49 + "pipewire-0.3", |
|
50 + "glib-2.0", |
|
51 + "gio-2.0", |
|
52 + "gobject-2.0" |
|
53 + ] |
|
54 + |
|
55 + CXXFLAGS += CONFIG['TK_CFLAGS'] |
|
56 + CXXFLAGS += [ "-I/usr/include/pipewire-0.3" ] |
|
57 + CXXFLAGS += [ "-I/usr/include/spa-0.2" ] |
|
58 + |
|
59 + UNIFIED_SOURCES += [ |
|
60 + "/media/webrtc/trunk/webrtc/modules/desktop_capture/linux/base_capturer_pipewire.cc", |
|
61 + "/media/webrtc/trunk/webrtc/modules/desktop_capture/linux/screen_capturer_pipewire.cc", |
|
62 + "/media/webrtc/trunk/webrtc/modules/desktop_capture/linux/window_capturer_pipewire.cc" |
|
63 + ] |
|
64 + |
|
65 + |
|
66 if CONFIG["OS_TARGET"] == "NetBSD": |
|
67 |
|
68 DEFINES["USE_X11"] = "1" |
|
69 diff --git a/media/webrtc/trunk/webrtc/modules/desktop_capture/desktop_capture_options.h b/media/webrtc/trunk/webrtc/modules/desktop_capture/desktop_capture_options.h |
|
70 index 1eb8ead26efa..316468eed1fc 100644 |
|
71 --- a/media/webrtc/trunk/webrtc/modules/desktop_capture/desktop_capture_options.h |
|
72 +++ b/media/webrtc/trunk/webrtc/modules/desktop_capture/desktop_capture_options.h |
|
73 @@ -141,7 +141,7 @@ class DesktopCaptureOptions { |
|
74 bool disable_effects_ = true; |
|
75 bool detect_updated_region_ = false; |
|
76 #if defined(WEBRTC_USE_PIPEWIRE) |
|
77 - bool allow_pipewire_ = false; |
|
78 + bool allow_pipewire_ = true; |
|
79 #endif |
|
80 }; |
|
81 |
|
82 diff --git a/media/webrtc/trunk/webrtc/modules/desktop_capture/linux/base_capturer_pipewire.cc b/media/webrtc/trunk/webrtc/modules/desktop_capture/linux/base_capturer_pipewire.cc |
|
83 index 379341c833de..76349f1fbd4d 100644 |
|
84 --- a/media/webrtc/trunk/webrtc/modules/desktop_capture/linux/base_capturer_pipewire.cc |
|
85 +++ b/media/webrtc/trunk/webrtc/modules/desktop_capture/linux/base_capturer_pipewire.cc |
|
86 @@ -15,8 +15,11 @@ |
|
87 |
|
88 #include <spa/param/format-utils.h> |
|
89 #include <spa/param/props.h> |
|
90 -#include <spa/param/video/raw-utils.h> |
|
91 -#include <spa/support/type-map.h> |
|
92 + |
|
93 +#include <linux/dma-buf.h> |
|
94 +#include <sys/mman.h> |
|
95 +#include <sys/ioctl.h> |
|
96 +#include <sys/syscall.h> |
|
97 |
|
98 #include <memory> |
|
99 #include <utility> |
|
100 @@ -36,31 +39,36 @@ const char kSessionInterfaceName[] = "org.freedesktop.portal.Session"; |
|
101 const char kRequestInterfaceName[] = "org.freedesktop.portal.Request"; |
|
102 const char kScreenCastInterfaceName[] = "org.freedesktop.portal.ScreenCast"; |
|
103 |
|
104 -// static |
|
105 -void BaseCapturerPipeWire::OnStateChanged(void* data, |
|
106 - pw_remote_state old_state, |
|
107 - pw_remote_state state, |
|
108 - const char* error_message) { |
|
109 - BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(data); |
|
110 - RTC_DCHECK(that); |
|
111 |
|
112 - switch (state) { |
|
113 - case PW_REMOTE_STATE_ERROR: |
|
114 - RTC_LOG(LS_ERROR) << "PipeWire remote state error: " << error_message; |
|
115 - break; |
|
116 - case PW_REMOTE_STATE_CONNECTED: |
|
117 - RTC_LOG(LS_INFO) << "PipeWire remote state: connected."; |
|
118 - that->CreateReceivingStream(); |
|
119 - break; |
|
120 - case PW_REMOTE_STATE_CONNECTING: |
|
121 - RTC_LOG(LS_INFO) << "PipeWire remote state: connecting."; |
|
122 +// static |
|
123 +void BaseCapturerPipeWire::SyncDmaBuf(int fd, uint64_t start_or_end) { |
|
124 + struct dma_buf_sync sync = { 0 }; |
|
125 + |
|
126 + sync.flags = start_or_end | DMA_BUF_SYNC_READ; |
|
127 + |
|
128 + while(true) { |
|
129 + int ret; |
|
130 + ret = ioctl (fd, DMA_BUF_IOCTL_SYNC, &sync); |
|
131 + if (ret == -1 && errno == EINTR) { |
|
132 + continue; |
|
133 + } else if (ret == -1) { |
|
134 + RTC_LOG(LS_ERROR) << "Failed to synchronize DMA buffer: " << g_strerror(errno); |
|
135 break; |
|
136 - case PW_REMOTE_STATE_UNCONNECTED: |
|
137 - RTC_LOG(LS_INFO) << "PipeWire remote state: unconnected."; |
|
138 + } else { |
|
139 break; |
|
140 + } |
|
141 } |
|
142 } |
|
143 |
|
144 +// static |
|
145 +void BaseCapturerPipeWire::OnCoreError(void *data, |
|
146 + uint32_t id, |
|
147 + int seq, |
|
148 + int res, |
|
149 + const char *message) { |
|
150 + RTC_LOG(LS_ERROR) << "core error: " << message; |
|
151 +} |
|
152 + |
|
153 // static |
|
154 void BaseCapturerPipeWire::OnStreamStateChanged(void* data, |
|
155 pw_stream_state old_state, |
|
156 @@ -73,76 +81,54 @@ void BaseCapturerPipeWire::OnStreamStateChanged(void* data, |
|
157 case PW_STREAM_STATE_ERROR: |
|
158 RTC_LOG(LS_ERROR) << "PipeWire stream state error: " << error_message; |
|
159 break; |
|
160 - case PW_STREAM_STATE_CONFIGURE: |
|
161 - pw_stream_set_active(that->pw_stream_, true); |
|
162 - break; |
|
163 - case PW_STREAM_STATE_UNCONNECTED: |
|
164 - case PW_STREAM_STATE_CONNECTING: |
|
165 - case PW_STREAM_STATE_READY: |
|
166 case PW_STREAM_STATE_PAUSED: |
|
167 case PW_STREAM_STATE_STREAMING: |
|
168 + case PW_STREAM_STATE_UNCONNECTED: |
|
169 + case PW_STREAM_STATE_CONNECTING: |
|
170 break; |
|
171 } |
|
172 } |
|
173 |
|
174 // static |
|
175 -void BaseCapturerPipeWire::OnStreamFormatChanged(void* data, |
|
176 - const struct spa_pod* format) { |
|
177 +void BaseCapturerPipeWire::OnStreamParamChanged(void *data, uint32_t id, |
|
178 + const struct spa_pod *format) { |
|
179 BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(data); |
|
180 RTC_DCHECK(that); |
|
181 |
|
182 - RTC_LOG(LS_INFO) << "PipeWire stream format changed."; |
|
183 + RTC_LOG(LS_INFO) << "PipeWire stream param changed."; |
|
184 |
|
185 - if (!format) { |
|
186 - pw_stream_finish_format(that->pw_stream_, /*res=*/0, /*params=*/nullptr, |
|
187 - /*n_params=*/0); |
|
188 + if (!format || id != SPA_PARAM_Format) { |
|
189 return; |
|
190 } |
|
191 |
|
192 - that->spa_video_format_ = new spa_video_info_raw(); |
|
193 - spa_format_video_raw_parse(format, that->spa_video_format_, |
|
194 - &that->pw_type_->format_video); |
|
195 + spa_format_video_raw_parse(format, &that->spa_video_format_); |
|
196 |
|
197 - auto width = that->spa_video_format_->size.width; |
|
198 - auto height = that->spa_video_format_->size.height; |
|
199 + auto width = that->spa_video_format_.size.width; |
|
200 + auto height = that->spa_video_format_.size.height; |
|
201 auto stride = SPA_ROUND_UP_N(width * kBytesPerPixel, 4); |
|
202 auto size = height * stride; |
|
203 |
|
204 + that->desktop_size_ = DesktopSize(width, height); |
|
205 + |
|
206 uint8_t buffer[1024] = {}; |
|
207 auto builder = spa_pod_builder{buffer, sizeof(buffer)}; |
|
208 |
|
209 // Setup buffers and meta header for new format. |
|
210 - const struct spa_pod* params[2]; |
|
211 - params[0] = reinterpret_cast<spa_pod*>(spa_pod_builder_object( |
|
212 - &builder, |
|
213 - // id to enumerate buffer requirements |
|
214 - that->pw_core_type_->param.idBuffers, |
|
215 - that->pw_core_type_->param_buffers.Buffers, |
|
216 - // Size: specified as integer (i) and set to specified size |
|
217 - ":", that->pw_core_type_->param_buffers.size, "i", size, |
|
218 - // Stride: specified as integer (i) and set to specified stride |
|
219 - ":", that->pw_core_type_->param_buffers.stride, "i", stride, |
|
220 - // Buffers: specifies how many buffers we want to deal with, set as |
|
221 - // integer (i) where preferred number is 8, then allowed number is defined |
|
222 - // as range (r) from min and max values and it is undecided (u) to allow |
|
223 - // negotiation |
|
224 - ":", that->pw_core_type_->param_buffers.buffers, "iru", 8, |
|
225 - SPA_POD_PROP_MIN_MAX(1, 32), |
|
226 - // Align: memory alignment of the buffer, set as integer (i) to specified |
|
227 - // value |
|
228 - ":", that->pw_core_type_->param_buffers.align, "i", 16)); |
|
229 - params[1] = reinterpret_cast<spa_pod*>(spa_pod_builder_object( |
|
230 - &builder, |
|
231 - // id to enumerate supported metadata |
|
232 - that->pw_core_type_->param.idMeta, that->pw_core_type_->param_meta.Meta, |
|
233 - // Type: specified as id or enum (I) |
|
234 - ":", that->pw_core_type_->param_meta.type, "I", |
|
235 - that->pw_core_type_->meta.Header, |
|
236 - // Size: size of the metadata, specified as integer (i) |
|
237 - ":", that->pw_core_type_->param_meta.size, "i", |
|
238 - sizeof(struct spa_meta_header))); |
|
239 - |
|
240 - pw_stream_finish_format(that->pw_stream_, /*res=*/0, params, /*n_params=*/2); |
|
241 + const struct spa_pod* params[3]; |
|
242 + params[0] = reinterpret_cast<spa_pod *>(spa_pod_builder_add_object(&builder, |
|
243 + SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, |
|
244 + SPA_PARAM_BUFFERS_size, SPA_POD_Int(size), |
|
245 + SPA_PARAM_BUFFERS_stride, SPA_POD_Int(stride), |
|
246 + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 1, 32))); |
|
247 + params[1] = reinterpret_cast<spa_pod *>(spa_pod_builder_add_object(&builder, |
|
248 + SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, |
|
249 + SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), |
|
250 + SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header)))); |
|
251 + params[2] = reinterpret_cast<spa_pod *>(spa_pod_builder_add_object(&builder, |
|
252 + SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, |
|
253 + SPA_PARAM_META_type, SPA_POD_Id (SPA_META_VideoCrop), |
|
254 + SPA_PARAM_META_size, SPA_POD_Int (sizeof(struct spa_meta_region)))); |
|
255 + pw_stream_update_params(that->pw_stream_, params, 3); |
|
256 } |
|
257 |
|
258 // static |
|
259 @@ -150,15 +136,25 @@ void BaseCapturerPipeWire::OnStreamProcess(void* data) { |
|
260 BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(data); |
|
261 RTC_DCHECK(that); |
|
262 |
|
263 - pw_buffer* buf = nullptr; |
|
264 + struct pw_buffer *next_buffer; |
|
265 + struct pw_buffer *buffer = nullptr; |
|
266 + |
|
267 + next_buffer = pw_stream_dequeue_buffer(that->pw_stream_); |
|
268 + while (next_buffer) { |
|
269 + buffer = next_buffer; |
|
270 + next_buffer = pw_stream_dequeue_buffer(that->pw_stream_); |
|
271 |
|
272 - if (!(buf = pw_stream_dequeue_buffer(that->pw_stream_))) { |
|
273 + if (next_buffer) |
|
274 + pw_stream_queue_buffer (that->pw_stream_, buffer); |
|
275 + } |
|
276 + |
|
277 + if (!buffer) { |
|
278 return; |
|
279 } |
|
280 |
|
281 - that->HandleBuffer(buf); |
|
282 + that->HandleBuffer(buffer); |
|
283 |
|
284 - pw_stream_queue_buffer(that->pw_stream_, buf); |
|
285 + pw_stream_queue_buffer(that->pw_stream_, buffer); |
|
286 } |
|
287 |
|
288 BaseCapturerPipeWire::BaseCapturerPipeWire(CaptureSourceType source_type) |
|
289 @@ -169,38 +165,22 @@ BaseCapturerPipeWire::~BaseCapturerPipeWire() { |
|
290 pw_thread_loop_stop(pw_main_loop_); |
|
291 } |
|
292 |
|
293 - if (pw_type_) { |
|
294 - delete pw_type_; |
|
295 - } |
|
296 - |
|
297 - if (spa_video_format_) { |
|
298 - delete spa_video_format_; |
|
299 - } |
|
300 - |
|
301 if (pw_stream_) { |
|
302 pw_stream_destroy(pw_stream_); |
|
303 } |
|
304 |
|
305 - if (pw_remote_) { |
|
306 - pw_remote_destroy(pw_remote_); |
|
307 + if (pw_core_) { |
|
308 + pw_core_disconnect(pw_core_); |
|
309 } |
|
310 |
|
311 - if (pw_core_) { |
|
312 - pw_core_destroy(pw_core_); |
|
313 + if (pw_context_) { |
|
314 + pw_context_destroy(pw_context_); |
|
315 } |
|
316 |
|
317 if (pw_main_loop_) { |
|
318 pw_thread_loop_destroy(pw_main_loop_); |
|
319 } |
|
320 |
|
321 - if (pw_loop_) { |
|
322 - pw_loop_destroy(pw_loop_); |
|
323 - } |
|
324 - |
|
325 - if (current_frame_) { |
|
326 - free(current_frame_); |
|
327 - } |
|
328 - |
|
329 if (start_request_signal_id_) { |
|
330 g_dbus_connection_signal_unsubscribe(connection_, start_request_signal_id_); |
|
331 } |
|
332 @@ -250,27 +230,35 @@ void BaseCapturerPipeWire::InitPortal() { |
|
333 void BaseCapturerPipeWire::InitPipeWire() { |
|
334 pw_init(/*argc=*/nullptr, /*argc=*/nullptr); |
|
335 |
|
336 - pw_loop_ = pw_loop_new(/*properties=*/nullptr); |
|
337 - pw_main_loop_ = pw_thread_loop_new(pw_loop_, "pipewire-main-loop"); |
|
338 - |
|
339 - pw_core_ = pw_core_new(pw_loop_, /*properties=*/nullptr); |
|
340 - pw_core_type_ = pw_core_get_type(pw_core_); |
|
341 - pw_remote_ = pw_remote_new(pw_core_, nullptr, /*user_data_size=*/0); |
|
342 + pw_main_loop_ = pw_thread_loop_new("pipewire-main-loop", nullptr); |
|
343 + pw_context_ = pw_context_new(pw_thread_loop_get_loop(pw_main_loop_), nullptr, 0); |
|
344 + if (!pw_context_) { |
|
345 + RTC_LOG(LS_ERROR) << "Failed to create PipeWire context"; |
|
346 + return; |
|
347 + } |
|
348 |
|
349 - InitPipeWireTypes(); |
|
350 + pw_core_ = pw_context_connect(pw_context_, nullptr, 0); |
|
351 + if (!pw_core_) { |
|
352 + RTC_LOG(LS_ERROR) << "Failed to connect PipeWire context"; |
|
353 + return; |
|
354 + } |
|
355 |
|
356 // Initialize event handlers, remote end and stream-related. |
|
357 - pw_remote_events_.version = PW_VERSION_REMOTE_EVENTS; |
|
358 - pw_remote_events_.state_changed = &OnStateChanged; |
|
359 + pw_core_events_.version = PW_VERSION_CORE_EVENTS; |
|
360 + pw_core_events_.error = &OnCoreError; |
|
361 |
|
362 pw_stream_events_.version = PW_VERSION_STREAM_EVENTS; |
|
363 pw_stream_events_.state_changed = &OnStreamStateChanged; |
|
364 - pw_stream_events_.format_changed = &OnStreamFormatChanged; |
|
365 + pw_stream_events_.param_changed = &OnStreamParamChanged; |
|
366 pw_stream_events_.process = &OnStreamProcess; |
|
367 |
|
368 - pw_remote_add_listener(pw_remote_, &spa_remote_listener_, &pw_remote_events_, |
|
369 - this); |
|
370 - pw_remote_connect_fd(pw_remote_, pw_fd_); |
|
371 + pw_core_add_listener(pw_core_, &spa_core_listener_, &pw_core_events_, this); |
|
372 + |
|
373 + pw_stream_ = CreateReceivingStream(); |
|
374 + if (!pw_stream_) { |
|
375 + RTC_LOG(LS_ERROR) << "Failed to create PipeWire stream"; |
|
376 + return; |
|
377 + } |
|
378 |
|
379 if (pw_thread_loop_start(pw_main_loop_) < 0) { |
|
380 RTC_LOG(LS_ERROR) << "Failed to start main PipeWire loop"; |
|
381 @@ -278,81 +266,132 @@ void BaseCapturerPipeWire::InitPipeWire() { |
|
382 } |
|
383 } |
|
384 |
|
385 -void BaseCapturerPipeWire::InitPipeWireTypes() { |
|
386 - spa_type_map* map = pw_core_type_->map; |
|
387 - pw_type_ = new PipeWireType(); |
|
388 - |
|
389 - spa_type_media_type_map(map, &pw_type_->media_type); |
|
390 - spa_type_media_subtype_map(map, &pw_type_->media_subtype); |
|
391 - spa_type_format_video_map(map, &pw_type_->format_video); |
|
392 - spa_type_video_format_map(map, &pw_type_->video_format); |
|
393 -} |
|
394 - |
|
395 -void BaseCapturerPipeWire::CreateReceivingStream() { |
|
396 +pw_stream* BaseCapturerPipeWire::CreateReceivingStream() { |
|
397 spa_rectangle pwMinScreenBounds = spa_rectangle{1, 1}; |
|
398 - spa_rectangle pwScreenBounds = |
|
399 - spa_rectangle{static_cast<uint32_t>(desktop_size_.width()), |
|
400 - static_cast<uint32_t>(desktop_size_.height())}; |
|
401 + spa_rectangle pwMaxScreenBounds = spa_rectangle{INT32_MAX, INT32_MAX}; |
|
402 |
|
403 - spa_fraction pwFrameRateMin = spa_fraction{0, 1}; |
|
404 - spa_fraction pwFrameRateMax = spa_fraction{60, 1}; |
|
405 + auto stream = pw_stream_new(pw_core_, "webrtc-pipewire-stream", nullptr); |
|
406 |
|
407 - pw_properties* reuseProps = pw_properties_new("pipewire.client.reuse", "1", |
|
408 - /*end of varargs*/ nullptr); |
|
409 - pw_stream_ = pw_stream_new(pw_remote_, "webrtc-consume-stream", reuseProps); |
|
410 + if (!stream) { |
|
411 + RTC_LOG(LS_ERROR) << "Could not create receiving stream."; |
|
412 + return nullptr; |
|
413 + } |
|
414 |
|
415 uint8_t buffer[1024] = {}; |
|
416 - const spa_pod* params[1]; |
|
417 - spa_pod_builder builder = spa_pod_builder{buffer, sizeof(buffer)}; |
|
418 - params[0] = reinterpret_cast<spa_pod*>(spa_pod_builder_object( |
|
419 - &builder, |
|
420 - // id to enumerate formats |
|
421 - pw_core_type_->param.idEnumFormat, pw_core_type_->spa_format, "I", |
|
422 - pw_type_->media_type.video, "I", pw_type_->media_subtype.raw, |
|
423 - // Video format: specified as id or enum (I), preferred format is BGRx, |
|
424 - // then allowed formats are enumerated (e) and the format is undecided (u) |
|
425 - // to allow negotiation |
|
426 - ":", pw_type_->format_video.format, "Ieu", pw_type_->video_format.BGRx, |
|
427 - SPA_POD_PROP_ENUM(2, pw_type_->video_format.RGBx, |
|
428 - pw_type_->video_format.BGRx), |
|
429 - // Video size: specified as rectangle (R), preferred size is specified as |
|
430 - // first parameter, then allowed size is defined as range (r) from min and |
|
431 - // max values and the format is undecided (u) to allow negotiation |
|
432 - ":", pw_type_->format_video.size, "Rru", &pwScreenBounds, 2, |
|
433 - &pwMinScreenBounds, &pwScreenBounds, |
|
434 - // Frame rate: specified as fraction (F) and set to minimum frame rate |
|
435 - // value |
|
436 - ":", pw_type_->format_video.framerate, "F", &pwFrameRateMin, |
|
437 - // Max frame rate: specified as fraction (F), preferred frame rate is set |
|
438 - // to maximum value, then allowed frame rate is defined as range (r) from |
|
439 - // min and max values and it is undecided (u) to allow negotiation |
|
440 - ":", pw_type_->format_video.max_framerate, "Fru", &pwFrameRateMax, 2, |
|
441 - &pwFrameRateMin, &pwFrameRateMax)); |
|
442 - |
|
443 - pw_stream_add_listener(pw_stream_, &spa_stream_listener_, &pw_stream_events_, |
|
444 - this); |
|
445 + const spa_pod* params[2]; |
|
446 + spa_pod_builder builder = SPA_POD_BUILDER_INIT(buffer, sizeof (buffer)); |
|
447 + |
|
448 + params[0] = reinterpret_cast<spa_pod *>(spa_pod_builder_add_object(&builder, |
|
449 + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, |
|
450 + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), |
|
451 + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), |
|
452 + SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(5, SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_RGBx, SPA_VIDEO_FORMAT_RGBA, |
|
453 + SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_BGRA), |
|
454 + SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(&pwMinScreenBounds, |
|
455 + &pwMinScreenBounds, |
|
456 + &pwMaxScreenBounds), |
|
457 + 0)); |
|
458 + pw_stream_add_listener(stream, &spa_stream_listener_, &pw_stream_events_, this); |
|
459 + |
|
460 pw_stream_flags flags = static_cast<pw_stream_flags>( |
|
461 - PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE | |
|
462 - PW_STREAM_FLAG_MAP_BUFFERS); |
|
463 - if (pw_stream_connect(pw_stream_, PW_DIRECTION_INPUT, /*port_path=*/nullptr, |
|
464 - flags, params, |
|
465 - /*n_params=*/1) != 0) { |
|
466 + PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE); |
|
467 + |
|
468 + if (pw_stream_connect(stream, PW_DIRECTION_INPUT, pw_stream_node_id_, PW_STREAM_FLAG_AUTOCONNECT, params, 1) != 0) { |
|
469 RTC_LOG(LS_ERROR) << "Could not connect receiving stream."; |
|
470 portal_init_failed_ = true; |
|
471 - return; |
|
472 } |
|
473 + |
|
474 + return stream; |
|
475 } |
|
476 |
|
477 void BaseCapturerPipeWire::HandleBuffer(pw_buffer* buffer) { |
|
478 + struct spa_meta_region* video_crop; |
|
479 spa_buffer* spaBuffer = buffer->buffer; |
|
480 - void* src = nullptr; |
|
481 + uint8_t *map = nullptr; |
|
482 + uint8_t* src = nullptr; |
|
483 + uint8_t* dst = nullptr; |
|
484 + |
|
485 + if (spaBuffer->datas[0].chunk->size == 0) { |
|
486 + map = nullptr; |
|
487 + src = nullptr; |
|
488 + } else if (spaBuffer->datas[0].type == SPA_DATA_MemFd) { |
|
489 + map = static_cast<uint8_t*>(mmap( |
|
490 + nullptr, spaBuffer->datas[0].maxsize + spaBuffer->datas[0].mapoffset, |
|
491 + PROT_READ, MAP_PRIVATE, spaBuffer->datas[0].fd, 0)); |
|
492 + |
|
493 + if (map == MAP_FAILED) { |
|
494 + RTC_LOG(LS_ERROR) << "Failed to mmap the memory: " << std::strerror(errno); |
|
495 + return; |
|
496 + } |
|
497 + |
|
498 + src = SPA_MEMBER(map, spaBuffer->datas[0].mapoffset, uint8_t); |
|
499 + } else if (spaBuffer->datas[0].type == SPA_DATA_DmaBuf) { |
|
500 + int fd; |
|
501 + fd = spaBuffer->datas[0].fd; |
|
502 |
|
503 - if (!(src = spaBuffer->datas[0].data)) { |
|
504 + map = static_cast<uint8_t*>(mmap( |
|
505 + nullptr, spaBuffer->datas[0].maxsize + spaBuffer->datas[0].mapoffset, |
|
506 + PROT_READ, MAP_PRIVATE, fd, 0)); |
|
507 + |
|
508 + if (map == MAP_FAILED) { |
|
509 + RTC_LOG(LS_ERROR) << "Failed to mmap the memory: " << std::strerror(errno); |
|
510 + return; |
|
511 + } |
|
512 + |
|
513 + SyncDmaBuf(fd, DMA_BUF_SYNC_START); |
|
514 + |
|
515 + src = SPA_MEMBER(map, spaBuffer->datas[0].mapoffset, uint8_t); |
|
516 + } else if (spaBuffer->datas[0].type == SPA_DATA_MemPtr) { |
|
517 + map = nullptr; |
|
518 + src = static_cast<uint8_t*>(spaBuffer->datas[0].data); |
|
519 + } else { |
|
520 return; |
|
521 } |
|
522 |
|
523 - uint32_t maxSize = spaBuffer->datas[0].maxsize; |
|
524 - int32_t srcStride = spaBuffer->datas[0].chunk->stride; |
|
525 + if (!src) { |
|
526 + return; |
|
527 + } |
|
528 + |
|
529 + DesktopSize prev_crop_size = DesktopSize(0, 0); |
|
530 + if (video_crop_size_initialized_) { |
|
531 + prev_crop_size = video_crop_size_; |
|
532 + } |
|
533 + |
|
534 + if ((video_crop = static_cast<struct spa_meta_region*>( |
|
535 + spa_buffer_find_meta_data(spaBuffer, SPA_META_VideoCrop, sizeof(*video_crop))))) { |
|
536 + RTC_DCHECK(video_crop->region.size.width <= desktop_size_.width() && |
|
537 + video_crop->region.size.height <= desktop_size_.height()); |
|
538 + if ((video_crop->region.size.width != desktop_size_.width() || |
|
539 + video_crop->region.size.height != desktop_size_.height()) && video_crop->region.size.width && video_crop->region.size.height) { |
|
540 + video_crop_size_ = DesktopSize(video_crop->region.size.width, video_crop->region.size.height); |
|
541 + video_crop_size_initialized_ = true; |
|
542 + } else { |
|
543 + video_crop_size_initialized_ = false; |
|
544 + } |
|
545 + } else { |
|
546 + video_crop_size_initialized_ = false; |
|
547 + } |
|
548 + |
|
549 + size_t frame_size; |
|
550 + if (video_crop_size_initialized_) { |
|
551 + frame_size = |
|
552 + video_crop_size_.width() * video_crop_size_.height() * kBytesPerPixel; |
|
553 + } else { |
|
554 + frame_size = |
|
555 + desktop_size_.width() * desktop_size_.height() * kBytesPerPixel; |
|
556 + } |
|
557 + |
|
558 + if (!current_frame_ || |
|
559 + (video_crop_size_initialized_ && !video_crop_size_.equals(prev_crop_size))) { |
|
560 + current_frame_ = std::make_unique<uint8_t[]>(frame_size); |
|
561 + } |
|
562 + RTC_DCHECK(current_frame_ != nullptr); |
|
563 + |
|
564 + const int32_t dstStride = video_crop_size_initialized_ |
|
565 + ? video_crop_size_.width() * kBytesPerPixel |
|
566 + : desktop_size_.width() * kBytesPerPixel; |
|
567 + const int32_t srcStride = spaBuffer->datas[0].chunk->stride; |
|
568 + |
|
569 if (srcStride != (desktop_size_.width() * kBytesPerPixel)) { |
|
570 RTC_LOG(LS_ERROR) << "Got buffer with stride different from screen stride: " |
|
571 << srcStride |
|
572 @@ -361,21 +400,40 @@ void BaseCapturerPipeWire::HandleBuffer(pw_buffer* buffer) { |
|
573 return; |
|
574 } |
|
575 |
|
576 - if (!current_frame_) { |
|
577 - current_frame_ = static_cast<uint8_t*>(malloc(maxSize)); |
|
578 + dst = current_frame_.get(); |
|
579 + |
|
580 + // Adjust source content based on crop video position |
|
581 + if (video_crop_size_initialized_ && |
|
582 + (video_crop->region.position.y + video_crop_size_.height() <= desktop_size_.height())) { |
|
583 + for (int i = 0; i < video_crop->region.position.y; ++i) { |
|
584 + src += srcStride; |
|
585 + } |
|
586 + } |
|
587 + const int xOffset = |
|
588 + video_crop_size_initialized_ && (video_crop->region.position.x + video_crop_size_.width() <= |
|
589 + desktop_size_.width()) |
|
590 + ? video_crop->region.position.x * kBytesPerPixel |
|
591 + : 0; |
|
592 + const int height = video_crop_size_initialized_ ? video_crop_size_.height() : desktop_size_.height(); |
|
593 + for (int i = 0; i < height; ++i) { |
|
594 + // Adjust source content based on crop video position if needed |
|
595 + src += xOffset; |
|
596 + std::memcpy(dst, src, dstStride); |
|
597 + // If both sides decided to go with the RGBx format we need to convert it to |
|
598 + // BGRx to match color format expected by WebRTC. |
|
599 + if (spa_video_format_.format == SPA_VIDEO_FORMAT_RGBx || |
|
600 + spa_video_format_.format == SPA_VIDEO_FORMAT_RGBA) { |
|
601 + ConvertRGBxToBGRx(dst, dstStride); |
|
602 + } |
|
603 + src += srcStride - xOffset; |
|
604 + dst += dstStride; |
|
605 } |
|
606 - RTC_DCHECK(current_frame_ != nullptr); |
|
607 |
|
608 - // If both sides decided to go with the RGBx format we need to convert it to |
|
609 - // BGRx to match color format expected by WebRTC. |
|
610 - if (spa_video_format_->format == pw_type_->video_format.RGBx) { |
|
611 - uint8_t* tempFrame = static_cast<uint8_t*>(malloc(maxSize)); |
|
612 - std::memcpy(tempFrame, src, maxSize); |
|
613 - ConvertRGBxToBGRx(tempFrame, maxSize); |
|
614 - std::memcpy(current_frame_, tempFrame, maxSize); |
|
615 - free(tempFrame); |
|
616 - } else { |
|
617 - std::memcpy(current_frame_, src, maxSize); |
|
618 + if (map) { |
|
619 + if (spaBuffer->datas[0].type == SPA_DATA_DmaBuf) { |
|
620 + SyncDmaBuf(spaBuffer->datas[0].fd, DMA_BUF_SYNC_END); |
|
621 + } |
|
622 + munmap(map, spaBuffer->datas[0].maxsize + spaBuffer->datas[0].mapoffset); |
|
623 } |
|
624 } |
|
625 |
|
626 @@ -725,10 +783,7 @@ void BaseCapturerPipeWire::OnStartRequestResponseSignal( |
|
627 g_variant_get(variant, "(u@a{sv})", &stream_id, &options); |
|
628 RTC_DCHECK(options != nullptr); |
|
629 |
|
630 - g_variant_lookup(options, "size", "(ii)", &width, &height); |
|
631 - |
|
632 - that->desktop_size_.set(width, height); |
|
633 - |
|
634 + that->pw_stream_node_id_ = stream_id; |
|
635 g_variant_unref(options); |
|
636 g_variant_unref(variant); |
|
637 } |
|
638 @@ -813,10 +868,15 @@ void BaseCapturerPipeWire::CaptureFrame() { |
|
639 return; |
|
640 } |
|
641 |
|
642 - std::unique_ptr<DesktopFrame> result(new BasicDesktopFrame(desktop_size_)); |
|
643 + DesktopSize frame_size = desktop_size_; |
|
644 + if (video_crop_size_initialized_) { |
|
645 + frame_size = video_crop_size_; |
|
646 + } |
|
647 + |
|
648 + std::unique_ptr<DesktopFrame> result(new BasicDesktopFrame(frame_size)); |
|
649 result->CopyPixelsFrom( |
|
650 - current_frame_, (desktop_size_.width() * kBytesPerPixel), |
|
651 - DesktopRect::MakeWH(desktop_size_.width(), desktop_size_.height())); |
|
652 + current_frame_.get(), (frame_size.width() * kBytesPerPixel), |
|
653 + DesktopRect::MakeWH(frame_size.width(), frame_size.height())); |
|
654 if (!result) { |
|
655 callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); |
|
656 return; |
|
657 @@ -837,4 +897,22 @@ bool BaseCapturerPipeWire::SelectSource(SourceId id) { |
|
658 return true; |
|
659 } |
|
660 |
|
661 +// static |
|
662 +std::unique_ptr<DesktopCapturer> |
|
663 +BaseCapturerPipeWire::CreateRawScreenCapturer( |
|
664 + const DesktopCaptureOptions& options) { |
|
665 + std::unique_ptr<BaseCapturerPipeWire> capturer = |
|
666 + std::make_unique<BaseCapturerPipeWire>(BaseCapturerPipeWire::CaptureSourceType::kAny); |
|
667 + return std::move(capturer);} |
|
668 + |
|
669 +// static |
|
670 +std::unique_ptr<DesktopCapturer> |
|
671 +BaseCapturerPipeWire::CreateRawWindowCapturer( |
|
672 + const DesktopCaptureOptions& options) { |
|
673 + |
|
674 + std::unique_ptr<BaseCapturerPipeWire> capturer = |
|
675 + std::make_unique<BaseCapturerPipeWire>(BaseCapturerPipeWire::CaptureSourceType::kAny); |
|
676 + return std::move(capturer); |
|
677 +} |
|
678 + |
|
679 } // namespace webrtc |
|
680 diff --git a/media/webrtc/trunk/webrtc/modules/desktop_capture/linux/base_capturer_pipewire.h b/media/webrtc/trunk/webrtc/modules/desktop_capture/linux/base_capturer_pipewire.h |
|
681 index 56b101acbaa6..de54157d1a2a 100644 |
|
682 --- a/media/webrtc/trunk/webrtc/modules/desktop_capture/linux/base_capturer_pipewire.h |
|
683 +++ b/media/webrtc/trunk/webrtc/modules/desktop_capture/linux/base_capturer_pipewire.h |
|
684 @@ -22,17 +22,13 @@ |
|
685 |
|
686 namespace webrtc { |
|
687 |
|
688 -class PipeWireType { |
|
689 - public: |
|
690 - spa_type_media_type media_type; |
|
691 - spa_type_media_subtype media_subtype; |
|
692 - spa_type_format_video format_video; |
|
693 - spa_type_video_format video_format; |
|
694 -}; |
|
695 - |
|
696 class BaseCapturerPipeWire : public DesktopCapturer { |
|
697 public: |
|
698 - enum CaptureSourceType { Screen = 1, Window }; |
|
699 + enum CaptureSourceType : uint32_t { |
|
700 + kScreen = 0b01, |
|
701 + kWindow = 0b10, |
|
702 + kAny = 0b11 |
|
703 + }; |
|
704 |
|
705 explicit BaseCapturerPipeWire(CaptureSourceType source_type); |
|
706 ~BaseCapturerPipeWire() override; |
|
707 @@ -43,28 +39,32 @@ class BaseCapturerPipeWire : public DesktopCapturer { |
|
708 bool GetSourceList(SourceList* sources) override; |
|
709 bool SelectSource(SourceId id) override; |
|
710 |
|
711 + static std::unique_ptr<DesktopCapturer> CreateRawScreenCapturer( |
|
712 + const DesktopCaptureOptions& options); |
|
713 + |
|
714 + static std::unique_ptr<DesktopCapturer> CreateRawWindowCapturer( |
|
715 + const DesktopCaptureOptions& options); |
|
716 + |
|
717 private: |
|
718 // PipeWire types --> |
|
719 + pw_context* pw_context_ = nullptr; |
|
720 pw_core* pw_core_ = nullptr; |
|
721 - pw_type* pw_core_type_ = nullptr; |
|
722 pw_stream* pw_stream_ = nullptr; |
|
723 - pw_remote* pw_remote_ = nullptr; |
|
724 - pw_loop* pw_loop_ = nullptr; |
|
725 pw_thread_loop* pw_main_loop_ = nullptr; |
|
726 - PipeWireType* pw_type_ = nullptr; |
|
727 |
|
728 + spa_hook spa_core_listener_ = {}; |
|
729 spa_hook spa_stream_listener_ = {}; |
|
730 - spa_hook spa_remote_listener_ = {}; |
|
731 |
|
732 + pw_core_events pw_core_events_ = {}; |
|
733 pw_stream_events pw_stream_events_ = {}; |
|
734 - pw_remote_events pw_remote_events_ = {}; |
|
735 |
|
736 - spa_video_info_raw* spa_video_format_ = nullptr; |
|
737 + struct spa_video_info_raw spa_video_format_; |
|
738 |
|
739 + guint32 pw_stream_node_id_ = 0; |
|
740 gint32 pw_fd_ = -1; |
|
741 |
|
742 CaptureSourceType capture_source_type_ = |
|
743 - BaseCapturerPipeWire::CaptureSourceType::Screen; |
|
744 + BaseCapturerPipeWire::CaptureSourceType::kAny; |
|
745 |
|
746 // <-- end of PipeWire types |
|
747 |
|
748 @@ -78,33 +78,37 @@ class BaseCapturerPipeWire : public DesktopCapturer { |
|
749 guint sources_request_signal_id_ = 0; |
|
750 guint start_request_signal_id_ = 0; |
|
751 |
|
752 + bool video_crop_size_initialized_ = false; |
|
753 + DesktopSize video_crop_size_;; |
|
754 DesktopSize desktop_size_ = {}; |
|
755 DesktopCaptureOptions options_ = {}; |
|
756 |
|
757 - uint8_t* current_frame_ = nullptr; |
|
758 + std::unique_ptr<uint8_t[]> current_frame_; |
|
759 Callback* callback_ = nullptr; |
|
760 |
|
761 bool portal_init_failed_ = false; |
|
762 |
|
763 void InitPortal(); |
|
764 void InitPipeWire(); |
|
765 - void InitPipeWireTypes(); |
|
766 |
|
767 - void CreateReceivingStream(); |
|
768 + pw_stream* CreateReceivingStream(); |
|
769 void HandleBuffer(pw_buffer* buffer); |
|
770 |
|
771 void ConvertRGBxToBGRx(uint8_t* frame, uint32_t size); |
|
772 |
|
773 - static void OnStateChanged(void* data, |
|
774 - pw_remote_state old_state, |
|
775 - pw_remote_state state, |
|
776 - const char* error); |
|
777 + static void SyncDmaBuf(int fd, uint64_t start_or_end); |
|
778 + static void OnCoreError(void *data, |
|
779 + uint32_t id, |
|
780 + int seq, |
|
781 + int res, |
|
782 + const char *message); |
|
783 + static void OnStreamParamChanged(void *data, |
|
784 + uint32_t id, |
|
785 + const struct spa_pod *format); |
|
786 static void OnStreamStateChanged(void* data, |
|
787 pw_stream_state old_state, |
|
788 pw_stream_state state, |
|
789 const char* error_message); |
|
790 - |
|
791 - static void OnStreamFormatChanged(void* data, const struct spa_pod* format); |
|
792 static void OnStreamProcess(void* data); |
|
793 static void OnNewBuffer(void* data, uint32_t id); |
|
794 |
|
795 diff --git a/media/webrtc/trunk/webrtc/modules/desktop_capture/linux/screen_capturer_pipewire.cc b/media/webrtc/trunk/webrtc/modules/desktop_capture/linux/screen_capturer_pipewire.cc |
|
796 index 26956fc67dc8..3813d697bb38 100644 |
|
797 --- a/media/webrtc/trunk/webrtc/modules/desktop_capture/linux/screen_capturer_pipewire.cc |
|
798 +++ b/media/webrtc/trunk/webrtc/modules/desktop_capture/linux/screen_capturer_pipewire.cc |
|
799 @@ -15,7 +15,7 @@ |
|
800 namespace webrtc { |
|
801 |
|
802 ScreenCapturerPipeWire::ScreenCapturerPipeWire() |
|
803 - : BaseCapturerPipeWire(BaseCapturerPipeWire::CaptureSourceType::Screen) {} |
|
804 + : BaseCapturerPipeWire(BaseCapturerPipeWire::CaptureSourceType::kScreen) {} |
|
805 ScreenCapturerPipeWire::~ScreenCapturerPipeWire() {} |
|
806 |
|
807 // static |
|
808 diff --git a/media/webrtc/trunk/webrtc/modules/desktop_capture/linux/window_capturer_pipewire.cc b/media/webrtc/trunk/webrtc/modules/desktop_capture/linux/window_capturer_pipewire.cc |
|
809 index 35436475cb4d..c43a1f1a0c4e 100644 |
|
810 --- a/media/webrtc/trunk/webrtc/modules/desktop_capture/linux/window_capturer_pipewire.cc |
|
811 +++ b/media/webrtc/trunk/webrtc/modules/desktop_capture/linux/window_capturer_pipewire.cc |
|
812 @@ -15,7 +15,7 @@ |
|
813 namespace webrtc { |
|
814 |
|
815 WindowCapturerPipeWire::WindowCapturerPipeWire() |
|
816 - : BaseCapturerPipeWire(BaseCapturerPipeWire::CaptureSourceType::Window) {} |
|
817 + : BaseCapturerPipeWire(BaseCapturerPipeWire::CaptureSourceType::kWindow) {} |
|
818 WindowCapturerPipeWire::~WindowCapturerPipeWire() {} |
|
819 |
|
820 // static |
|
821 diff --git a/media/webrtc/trunk/webrtc/modules/desktop_capture/screen_capturer_linux.cc b/media/webrtc/trunk/webrtc/modules/desktop_capture/screen_capturer_linux.cc |
|
822 index cf8a9dd0e0db..d27fab8d28d9 100644 |
|
823 --- a/media/webrtc/trunk/webrtc/modules/desktop_capture/screen_capturer_linux.cc |
|
824 +++ b/media/webrtc/trunk/webrtc/modules/desktop_capture/screen_capturer_linux.cc |
|
825 @@ -26,7 +26,7 @@ std::unique_ptr<DesktopCapturer> DesktopCapturer::CreateRawScreenCapturer( |
|
826 const DesktopCaptureOptions& options) { |
|
827 #if defined(WEBRTC_USE_PIPEWIRE) |
|
828 if (options.allow_pipewire() && DesktopCapturer::IsRunningUnderWayland()) { |
|
829 - return ScreenCapturerPipeWire::CreateRawScreenCapturer(options); |
|
830 + return BaseCapturerPipeWire::CreateRawScreenCapturer(options); |
|
831 } |
|
832 #endif // defined(WEBRTC_USE_PIPEWIRE) |
|
833 |
|
834 diff --git a/media/webrtc/trunk/webrtc/modules/desktop_capture/window_capturer_linux.cc b/media/webrtc/trunk/webrtc/modules/desktop_capture/window_capturer_linux.cc |
|
835 index 82359e50c2db..bb9724cf7cc2 100644 |
|
836 --- a/media/webrtc/trunk/webrtc/modules/desktop_capture/window_capturer_linux.cc |
|
837 +++ b/media/webrtc/trunk/webrtc/modules/desktop_capture/window_capturer_linux.cc |
|
838 @@ -26,7 +26,7 @@ std::unique_ptr<DesktopCapturer> DesktopCapturer::CreateRawWindowCapturer( |
|
839 const DesktopCaptureOptions& options) { |
|
840 #if defined(WEBRTC_USE_PIPEWIRE) |
|
841 if (options.allow_pipewire() && DesktopCapturer::IsRunningUnderWayland()) { |
|
842 - return WindowCapturerPipeWire::CreateRawWindowCapturer(options); |
|
843 + return BaseCapturerPipeWire::CreateRawWindowCapturer(options); |
|
844 } |
|
845 #endif // defined(WEBRTC_USE_PIPEWIRE) |
|
846 |