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