1
2 /*
3 * Copyright 2022 The WebRTC project authors. All Rights Reserved.
4 *
5 * Use of this source code is governed by a BSD-style license
6 * that can be found in the LICENSE file in the root of the source
7 * tree. An additional intellectual property rights grant can be found
8 * in the file PATENTS. All contributing project authors may
9 * be found in the AUTHORS file in the root of the source tree.
10 */
11
12 #include "modules/desktop_capture/linux/wayland/test/test_screencast_stream_provider.h"
13
14 #include <fcntl.h>
15 #include <sys/mman.h>
16 #include <sys/types.h>
17 #include <unistd.h>
18
19 #include <string>
20 #include <utility>
21 #include <vector>
22
23 #include "modules/portal/pipewire_utils.h"
24 #include "rtc_base/logging.h"
25
26 namespace webrtc {
27
28 constexpr int kBytesPerPixel = 4;
29
TestScreenCastStreamProvider(Observer * observer,uint32_t width,uint32_t height)30 TestScreenCastStreamProvider::TestScreenCastStreamProvider(Observer* observer,
31 uint32_t width,
32 uint32_t height)
33 : observer_(observer), width_(width), height_(height) {
34 if (!InitializePipeWire()) {
35 RTC_LOG(LS_ERROR) << "Unable to open PipeWire";
36 return;
37 }
38
39 pw_init(/*argc=*/nullptr, /*argc=*/nullptr);
40
41 pw_main_loop_ = pw_thread_loop_new("pipewire-test-main-loop", nullptr);
42
43 pw_context_ =
44 pw_context_new(pw_thread_loop_get_loop(pw_main_loop_), nullptr, 0);
45 if (!pw_context_) {
46 RTC_LOG(LS_ERROR) << "PipeWire test: Failed to create PipeWire context";
47 return;
48 }
49
50 if (pw_thread_loop_start(pw_main_loop_) < 0) {
51 RTC_LOG(LS_ERROR) << "PipeWire test: Failed to start main PipeWire loop";
52 return;
53 }
54
55 // Initialize event handlers, remote end and stream-related.
56 pw_core_events_.version = PW_VERSION_CORE_EVENTS;
57 pw_core_events_.error = &OnCoreError;
58
59 pw_stream_events_.version = PW_VERSION_STREAM_EVENTS;
60 pw_stream_events_.add_buffer = &OnStreamAddBuffer;
61 pw_stream_events_.remove_buffer = &OnStreamRemoveBuffer;
62 pw_stream_events_.state_changed = &OnStreamStateChanged;
63 pw_stream_events_.param_changed = &OnStreamParamChanged;
64
65 {
66 PipeWireThreadLoopLock thread_loop_lock(pw_main_loop_);
67
68 pw_core_ = pw_context_connect(pw_context_, nullptr, 0);
69 if (!pw_core_) {
70 RTC_LOG(LS_ERROR) << "PipeWire test: Failed to connect PipeWire context";
71 return;
72 }
73
74 pw_core_add_listener(pw_core_, &spa_core_listener_, &pw_core_events_, this);
75
76 pw_stream_ = pw_stream_new(pw_core_, "webrtc-test-stream", nullptr);
77
78 if (!pw_stream_) {
79 RTC_LOG(LS_ERROR) << "PipeWire test: Failed to create PipeWire stream";
80 return;
81 }
82
83 pw_stream_add_listener(pw_stream_, &spa_stream_listener_,
84 &pw_stream_events_, this);
85 uint8_t buffer[2048] = {};
86
87 spa_pod_builder builder = spa_pod_builder{buffer, sizeof(buffer)};
88
89 std::vector<const spa_pod*> params;
90
91 spa_rectangle resolution =
92 SPA_RECTANGLE(uint32_t(width_), uint32_t(height_));
93 params.push_back(BuildFormat(&builder, SPA_VIDEO_FORMAT_BGRx,
94 /*modifiers=*/{}, &resolution));
95
96 auto flags =
97 pw_stream_flags(PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_ALLOC_BUFFERS);
98 if (pw_stream_connect(pw_stream_, PW_DIRECTION_OUTPUT, SPA_ID_INVALID,
99 flags, params.data(), params.size()) != 0) {
100 RTC_LOG(LS_ERROR) << "PipeWire test: Could not connect receiving stream.";
101 pw_stream_destroy(pw_stream_);
102 pw_stream_ = nullptr;
103 return;
104 }
105 }
106
107 return;
108 }
109
~TestScreenCastStreamProvider()110 TestScreenCastStreamProvider::~TestScreenCastStreamProvider() {
111 if (pw_main_loop_) {
112 pw_thread_loop_stop(pw_main_loop_);
113 }
114
115 if (pw_stream_) {
116 pw_stream_destroy(pw_stream_);
117 }
118
119 if (pw_core_) {
120 pw_core_disconnect(pw_core_);
121 }
122
123 if (pw_context_) {
124 pw_context_destroy(pw_context_);
125 }
126
127 if (pw_main_loop_) {
128 pw_thread_loop_destroy(pw_main_loop_);
129 }
130 }
131
RecordFrame(RgbaColor rgba_color)132 void TestScreenCastStreamProvider::RecordFrame(RgbaColor rgba_color) {
133 const char* error;
134 if (pw_stream_get_state(pw_stream_, &error) != PW_STREAM_STATE_STREAMING) {
135 if (error) {
136 RTC_LOG(LS_ERROR)
137 << "PipeWire test: Failed to record frame: stream is not active: "
138 << error;
139 }
140 }
141
142 struct pw_buffer* buffer = pw_stream_dequeue_buffer(pw_stream_);
143 if (!buffer) {
144 RTC_LOG(LS_ERROR) << "PipeWire test: No available buffer";
145 return;
146 }
147
148 struct spa_buffer* spa_buffer = buffer->buffer;
149 struct spa_data* spa_data = spa_buffer->datas;
150 uint8_t* data = static_cast<uint8_t*>(spa_data->data);
151 if (!data) {
152 RTC_LOG(LS_ERROR)
153 << "PipeWire test: Failed to record frame: invalid buffer data";
154 pw_stream_queue_buffer(pw_stream_, buffer);
155 return;
156 }
157
158 const int stride = SPA_ROUND_UP_N(width_ * kBytesPerPixel, 4);
159
160 spa_data->chunk->offset = 0;
161 spa_data->chunk->size = height_ * stride;
162 spa_data->chunk->stride = stride;
163
164 uint32_t color = rgba_color.ToUInt32();
165 for (uint32_t i = 0; i < height_; i++) {
166 uint32_t* column = reinterpret_cast<uint32_t*>(data);
167 for (uint32_t j = 0; j < width_; j++) {
168 column[j] = color;
169 }
170 data += stride;
171 }
172
173 pw_stream_queue_buffer(pw_stream_, buffer);
174 if (observer_) {
175 observer_->OnFrameRecorded();
176 }
177 }
178
StartStreaming()179 void TestScreenCastStreamProvider::StartStreaming() {
180 if (pw_stream_ && pw_node_id_ != 0) {
181 pw_stream_set_active(pw_stream_, true);
182 }
183 }
184
StopStreaming()185 void TestScreenCastStreamProvider::StopStreaming() {
186 if (pw_stream_ && pw_node_id_ != 0) {
187 pw_stream_set_active(pw_stream_, false);
188 }
189 }
190
191 // static
OnCoreError(void * data,uint32_t id,int seq,int res,const char * message)192 void TestScreenCastStreamProvider::OnCoreError(void* data,
193 uint32_t id,
194 int seq,
195 int res,
196 const char* message) {
197 TestScreenCastStreamProvider* that =
198 static_cast<TestScreenCastStreamProvider*>(data);
199 RTC_DCHECK(that);
200
201 RTC_LOG(LS_ERROR) << "PipeWire test: PipeWire remote error: " << message;
202 }
203
204 // static
OnStreamStateChanged(void * data,pw_stream_state old_state,pw_stream_state state,const char * error_message)205 void TestScreenCastStreamProvider::OnStreamStateChanged(
206 void* data,
207 pw_stream_state old_state,
208 pw_stream_state state,
209 const char* error_message) {
210 TestScreenCastStreamProvider* that =
211 static_cast<TestScreenCastStreamProvider*>(data);
212 RTC_DCHECK(that);
213
214 switch (state) {
215 case PW_STREAM_STATE_ERROR:
216 RTC_LOG(LS_ERROR) << "PipeWire test: PipeWire stream state error: "
217 << error_message;
218 break;
219 case PW_STREAM_STATE_PAUSED:
220 if (that->pw_node_id_ == 0 && that->pw_stream_) {
221 that->pw_node_id_ = pw_stream_get_node_id(that->pw_stream_);
222 that->observer_->OnStreamReady(that->pw_node_id_);
223 } else {
224 // Stop streaming
225 that->is_streaming_ = false;
226 that->observer_->OnStopStreaming();
227 }
228 break;
229 case PW_STREAM_STATE_STREAMING:
230 // Start streaming
231 that->is_streaming_ = true;
232 that->observer_->OnStartStreaming();
233 break;
234 case PW_STREAM_STATE_CONNECTING:
235 break;
236 case PW_STREAM_STATE_UNCONNECTED:
237 if (that->is_streaming_) {
238 // Stop streaming
239 that->is_streaming_ = false;
240 that->observer_->OnStopStreaming();
241 }
242 break;
243 }
244 }
245
246 // static
OnStreamParamChanged(void * data,uint32_t id,const struct spa_pod * format)247 void TestScreenCastStreamProvider::OnStreamParamChanged(
248 void* data,
249 uint32_t id,
250 const struct spa_pod* format) {
251 TestScreenCastStreamProvider* that =
252 static_cast<TestScreenCastStreamProvider*>(data);
253 RTC_DCHECK(that);
254
255 RTC_LOG(LS_INFO) << "PipeWire test: PipeWire stream format changed.";
256 if (!format || id != SPA_PARAM_Format) {
257 return;
258 }
259
260 spa_format_video_raw_parse(format, &that->spa_video_format_);
261
262 auto stride = SPA_ROUND_UP_N(that->width_ * kBytesPerPixel, 4);
263
264 uint8_t buffer[1024] = {};
265 auto builder = spa_pod_builder{buffer, sizeof(buffer)};
266
267 // Setup buffers and meta header for new format.
268
269 std::vector<const spa_pod*> params;
270 const int buffer_types = (1 << SPA_DATA_MemFd);
271 spa_rectangle resolution = SPA_RECTANGLE(that->width_, that->height_);
272
273 params.push_back(reinterpret_cast<spa_pod*>(spa_pod_builder_add_object(
274 &builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
275 SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&resolution),
276 SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(16, 2, 16),
277 SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_stride,
278 SPA_POD_Int(stride), SPA_PARAM_BUFFERS_size,
279 SPA_POD_Int(stride * that->height_), SPA_PARAM_BUFFERS_align,
280 SPA_POD_Int(16), SPA_PARAM_BUFFERS_dataType,
281 SPA_POD_CHOICE_FLAGS_Int(buffer_types))));
282 params.push_back(reinterpret_cast<spa_pod*>(spa_pod_builder_add_object(
283 &builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type,
284 SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size,
285 SPA_POD_Int(sizeof(struct spa_meta_header)))));
286
287 pw_stream_update_params(that->pw_stream_, params.data(), params.size());
288 }
289
290 // static
OnStreamAddBuffer(void * data,pw_buffer * buffer)291 void TestScreenCastStreamProvider::OnStreamAddBuffer(void* data,
292 pw_buffer* buffer) {
293 TestScreenCastStreamProvider* that =
294 static_cast<TestScreenCastStreamProvider*>(data);
295 RTC_DCHECK(that);
296
297 struct spa_data* spa_data = buffer->buffer->datas;
298
299 spa_data->mapoffset = 0;
300 spa_data->flags = SPA_DATA_FLAG_READWRITE;
301
302 if (!(spa_data[0].type & (1 << SPA_DATA_MemFd))) {
303 RTC_LOG(LS_ERROR)
304 << "PipeWire test: Client doesn't support memfd buffer data type";
305 return;
306 }
307
308 const int stride = SPA_ROUND_UP_N(that->width_ * kBytesPerPixel, 4);
309 spa_data->maxsize = stride * that->height_;
310 spa_data->type = SPA_DATA_MemFd;
311 spa_data->fd =
312 memfd_create("pipewire-test-memfd", MFD_CLOEXEC | MFD_ALLOW_SEALING);
313 if (spa_data->fd == -1) {
314 RTC_LOG(LS_ERROR) << "PipeWire test: Can't create memfd";
315 return;
316 }
317
318 spa_data->mapoffset = 0;
319
320 if (ftruncate(spa_data->fd, spa_data->maxsize) < 0) {
321 RTC_LOG(LS_ERROR) << "PipeWire test: Can't truncate to"
322 << spa_data->maxsize;
323 return;
324 }
325
326 unsigned int seals = F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL;
327 if (fcntl(spa_data->fd, F_ADD_SEALS, seals) == -1) {
328 RTC_LOG(LS_ERROR) << "PipeWire test: Failed to add seals";
329 }
330
331 spa_data->data = mmap(nullptr, spa_data->maxsize, PROT_READ | PROT_WRITE,
332 MAP_SHARED, spa_data->fd, spa_data->mapoffset);
333 if (spa_data->data == MAP_FAILED) {
334 RTC_LOG(LS_ERROR) << "PipeWire test: Failed to mmap memory";
335 } else {
336 that->observer_->OnBufferAdded();
337 RTC_LOG(LS_INFO) << "PipeWire test: Memfd created successfully: "
338 << spa_data->data << spa_data->maxsize;
339 }
340 }
341
342 // static
OnStreamRemoveBuffer(void * data,pw_buffer * buffer)343 void TestScreenCastStreamProvider::OnStreamRemoveBuffer(void* data,
344 pw_buffer* buffer) {
345 TestScreenCastStreamProvider* that =
346 static_cast<TestScreenCastStreamProvider*>(data);
347 RTC_DCHECK(that);
348
349 struct spa_buffer* spa_buffer = buffer->buffer;
350 struct spa_data* spa_data = spa_buffer->datas;
351 if (spa_data && spa_data->type == SPA_DATA_MemFd) {
352 munmap(spa_data->data, spa_data->maxsize);
353 close(spa_data->fd);
354 }
355 }
356
PipeWireNodeId()357 uint32_t TestScreenCastStreamProvider::PipeWireNodeId() {
358 return pw_node_id_;
359 }
360
361 } // namespace webrtc
362