1 /*
2 * Copyright 2022 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/screen_capturer_fuchsia.h"
12
13 #include <fuchsia/sysmem/cpp/fidl.h>
14 #include <fuchsia/ui/composition/cpp/fidl.h>
15 #include <fuchsia/ui/scenic/cpp/fidl.h>
16 #include <lib/sys/cpp/component_context.h>
17
18 #include <algorithm>
19 #include <cstdint>
20 #include <memory>
21 #include <string>
22 #include <utility>
23
24 #include "modules/desktop_capture/blank_detector_desktop_capturer_wrapper.h"
25 #include "modules/desktop_capture/desktop_capture_options.h"
26 #include "modules/desktop_capture/desktop_capture_types.h"
27 #include "modules/desktop_capture/desktop_capturer.h"
28 #include "modules/desktop_capture/desktop_frame.h"
29 #include "modules/desktop_capture/desktop_geometry.h"
30 #include "modules/desktop_capture/fallback_desktop_capturer_wrapper.h"
31 #include "rtc_base/checks.h"
32 #include "rtc_base/logging.h"
33 #include "rtc_base/numerics/divide_round.h"
34 #include "rtc_base/time_utils.h"
35
36 namespace webrtc {
37
38 namespace {
39
40 static constexpr uint32_t kMinBufferCount = 2;
41 static constexpr uint32_t kFuchsiaBytesPerPixel = 4;
42 static constexpr DesktopCapturer::SourceId kFuchsiaScreenId = 1;
43 // 500 milliseconds
44 static constexpr zx::duration kEventDelay = zx::msec(500);
45 static constexpr fuchsia::sysmem::ColorSpaceType kSRGBColorSpace =
46 fuchsia::sysmem::ColorSpaceType::SRGB;
47 static constexpr fuchsia::sysmem::PixelFormatType kBGRA32PixelFormatType =
48 fuchsia::sysmem::PixelFormatType::BGRA32;
49
50 // Round |value| up to the closest multiple of |multiple|
RoundUpToMultiple(size_t value,size_t multiple)51 size_t RoundUpToMultiple(size_t value, size_t multiple) {
52 return DivideRoundUp(value, multiple) * multiple;
53 }
54
55 } // namespace
56
CreateRawScreenCapturer(const DesktopCaptureOptions & options)57 std::unique_ptr<DesktopCapturer> DesktopCapturer::CreateRawScreenCapturer(
58 const DesktopCaptureOptions& options) {
59 if (ScreenCapturerFuchsia::CheckRequirements()) {
60 std::unique_ptr<ScreenCapturerFuchsia> capturer(
61 new ScreenCapturerFuchsia());
62 return capturer;
63 }
64 return nullptr;
65 }
66
ScreenCapturerFuchsia()67 ScreenCapturerFuchsia::ScreenCapturerFuchsia()
68 : component_context_(sys::ComponentContext::Create()) {
69 RTC_DCHECK(CheckRequirements());
70 }
71
~ScreenCapturerFuchsia()72 ScreenCapturerFuchsia::~ScreenCapturerFuchsia() {
73 // unmap virtual memory mapped pointers
74 uint32_t virt_mem_bytes =
75 buffer_collection_info_.settings.buffer_settings.size_bytes;
76 for (uint32_t buffer_index = 0;
77 buffer_index < buffer_collection_info_.buffer_count; buffer_index++) {
78 uintptr_t address =
79 reinterpret_cast<uintptr_t>(virtual_memory_mapped_addrs_[buffer_index]);
80 zx_status_t status = zx::vmar::root_self()->unmap(address, virt_mem_bytes);
81 RTC_DCHECK(status == ZX_OK);
82 }
83 }
84
85 // TODO(fxbug.dev/100303): Remove this function when Flatland is the only API.
CheckRequirements()86 bool ScreenCapturerFuchsia::CheckRequirements() {
87 std::unique_ptr<sys::ComponentContext> component_context =
88 sys::ComponentContext::Create();
89 fuchsia::ui::scenic::ScenicSyncPtr scenic;
90 zx_status_t status = component_context->svc()->Connect(scenic.NewRequest());
91 if (status != ZX_OK) {
92 RTC_LOG(LS_ERROR) << "Failed to connect to Scenic: " << status;
93 return false;
94 }
95
96 bool scenic_uses_flatland = false;
97 scenic->UsesFlatland(&scenic_uses_flatland);
98 if (!scenic_uses_flatland) {
99 RTC_LOG(LS_ERROR) << "Screen capture not supported without Flatland.";
100 }
101
102 return scenic_uses_flatland;
103 }
104
Start(Callback * callback)105 void ScreenCapturerFuchsia::Start(Callback* callback) {
106 RTC_DCHECK(!callback_);
107 RTC_DCHECK(callback);
108 callback_ = callback;
109
110 fatal_error_ = false;
111
112 SetupBuffers();
113 }
114
CaptureFrame()115 void ScreenCapturerFuchsia::CaptureFrame() {
116 if (fatal_error_) {
117 callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
118 return;
119 }
120
121 int64_t capture_start_time_nanos = rtc::TimeNanos();
122
123 zx::event event;
124 zx::event dup;
125 zx_status_t status = zx::event::create(0, &event);
126 if (status != ZX_OK) {
127 RTC_LOG(LS_ERROR) << "Failed to create event: " << status;
128 callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
129 return;
130 }
131 event.duplicate(ZX_RIGHT_SAME_RIGHTS, &dup);
132
133 fuchsia::ui::composition::GetNextFrameArgs next_frame_args;
134 next_frame_args.set_event(std::move(dup));
135
136 fuchsia::ui::composition::ScreenCapture_GetNextFrame_Result result;
137 screen_capture_->GetNextFrame(std::move(next_frame_args), &result);
138 if (result.is_err()) {
139 RTC_LOG(LS_ERROR) << "fuchsia.ui.composition.GetNextFrame() failed: "
140 << result.err() << "\n";
141 callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
142 return;
143 }
144
145 status = event.wait_one(ZX_EVENT_SIGNALED, zx::deadline_after(kEventDelay),
146 nullptr);
147 if (status != ZX_OK) {
148 RTC_LOG(LS_ERROR) << "Timed out waiting for ScreenCapture to render frame: "
149 << status;
150 callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
151 return;
152 }
153 uint32_t buffer_index = result.response().buffer_id();
154
155 // TODO(bugs.webrtc.org/14097): Use SharedMemoryDesktopFrame and
156 // ScreenCaptureFrameQueue
157 std::unique_ptr<BasicDesktopFrame> frame(
158 new BasicDesktopFrame(DesktopSize(width_, height_)));
159
160 uint32_t pixels_per_row = GetPixelsPerRow(
161 buffer_collection_info_.settings.image_format_constraints);
162 uint32_t stride = kFuchsiaBytesPerPixel * pixels_per_row;
163 frame->CopyPixelsFrom(virtual_memory_mapped_addrs_[buffer_index], stride,
164 DesktopRect::MakeWH(width_, height_));
165 // Mark the whole screen as having been updated.
166 frame->mutable_updated_region()->SetRect(
167 DesktopRect::MakeWH(width_, height_));
168
169 fuchsia::ui::composition::ScreenCapture_ReleaseFrame_Result release_result;
170 screen_capture_->ReleaseFrame(buffer_index, &release_result);
171 if (release_result.is_err()) {
172 RTC_LOG(LS_ERROR) << "fuchsia.ui.composition.ReleaseFrame() failed: "
173 << release_result.err();
174 }
175
176 int capture_time_ms = (rtc::TimeNanos() - capture_start_time_nanos) /
177 rtc::kNumNanosecsPerMillisec;
178 frame->set_capture_time_ms(capture_time_ms);
179 callback_->OnCaptureResult(Result::SUCCESS, std::move(frame));
180 }
181
GetSourceList(SourceList * screens)182 bool ScreenCapturerFuchsia::GetSourceList(SourceList* screens) {
183 RTC_DCHECK(screens->size() == 0);
184 // Fuchsia only supports single monitor display at this point
185 screens->push_back({kFuchsiaScreenId, std::string("Fuchsia monitor")});
186 return true;
187 }
188
SelectSource(SourceId id)189 bool ScreenCapturerFuchsia::SelectSource(SourceId id) {
190 if (id == kFuchsiaScreenId || id == kFullDesktopScreenId) {
191 return true;
192 }
193 return false;
194 }
195
196 fuchsia::sysmem::BufferCollectionConstraints
GetBufferConstraints()197 ScreenCapturerFuchsia::GetBufferConstraints() {
198 fuchsia::sysmem::BufferCollectionConstraints constraints;
199 constraints.usage.cpu =
200 fuchsia::sysmem::cpuUsageRead | fuchsia::sysmem::cpuUsageWrite;
201 constraints.min_buffer_count = kMinBufferCount;
202
203 constraints.has_buffer_memory_constraints = true;
204 constraints.buffer_memory_constraints.ram_domain_supported = true;
205 constraints.buffer_memory_constraints.cpu_domain_supported = true;
206
207 constraints.image_format_constraints_count = 1;
208 fuchsia::sysmem::ImageFormatConstraints& image_constraints =
209 constraints.image_format_constraints[0];
210 image_constraints.color_spaces_count = 1;
211 image_constraints.color_space[0] =
212 fuchsia::sysmem::ColorSpace{.type = kSRGBColorSpace};
213 image_constraints.pixel_format.type = kBGRA32PixelFormatType;
214 image_constraints.pixel_format.has_format_modifier = true;
215 image_constraints.pixel_format.format_modifier.value =
216 fuchsia::sysmem::FORMAT_MODIFIER_LINEAR;
217
218 image_constraints.required_min_coded_width = width_;
219 image_constraints.required_min_coded_height = height_;
220 image_constraints.required_max_coded_width = width_;
221 image_constraints.required_max_coded_height = height_;
222
223 image_constraints.bytes_per_row_divisor = kFuchsiaBytesPerPixel;
224
225 return constraints;
226 }
227
SetupBuffers()228 void ScreenCapturerFuchsia::SetupBuffers() {
229 fuchsia::ui::scenic::ScenicSyncPtr scenic;
230 zx_status_t status = component_context_->svc()->Connect(scenic.NewRequest());
231 if (status != ZX_OK) {
232 fatal_error_ = true;
233 RTC_LOG(LS_ERROR) << "Failed to connect to Scenic: " << status;
234 return;
235 }
236
237 fuchsia::ui::gfx::DisplayInfo display_info;
238 status = scenic->GetDisplayInfo(&display_info);
239 if (status != ZX_OK) {
240 fatal_error_ = true;
241 RTC_LOG(LS_ERROR) << "Failed to connect to get display dimensions: "
242 << status;
243 return;
244 }
245 width_ = display_info.width_in_px;
246 height_ = display_info.height_in_px;
247
248 status = component_context_->svc()->Connect(sysmem_allocator_.NewRequest());
249 if (status != ZX_OK) {
250 fatal_error_ = true;
251 RTC_LOG(LS_ERROR) << "Failed to connect to Sysmem Allocator: " << status;
252 return;
253 }
254
255 fuchsia::sysmem::BufferCollectionTokenSyncPtr sysmem_token;
256 status =
257 sysmem_allocator_->AllocateSharedCollection(sysmem_token.NewRequest());
258 if (status != ZX_OK) {
259 fatal_error_ = true;
260 RTC_LOG(LS_ERROR)
261 << "fuchsia.sysmem.Allocator.AllocateSharedCollection() failed: "
262 << status;
263 return;
264 }
265
266 fuchsia::sysmem::BufferCollectionTokenSyncPtr flatland_token;
267 status = sysmem_token->Duplicate(ZX_RIGHT_SAME_RIGHTS,
268 flatland_token.NewRequest());
269 if (status != ZX_OK) {
270 fatal_error_ = true;
271 RTC_LOG(LS_ERROR)
272 << "fuchsia.sysmem.BufferCollectionToken.Duplicate() failed: "
273 << status;
274 return;
275 }
276
277 status = sysmem_token->Sync();
278 if (status != ZX_OK) {
279 fatal_error_ = true;
280 RTC_LOG(LS_ERROR) << "fuchsia.sysmem.BufferCollectionToken.Sync() failed: "
281 << status;
282 return;
283 }
284
285 status = sysmem_allocator_->BindSharedCollection(std::move(sysmem_token),
286 collection_.NewRequest());
287 if (status != ZX_OK) {
288 fatal_error_ = true;
289 RTC_LOG(LS_ERROR)
290 << "fuchsia.sysmem.Allocator.BindSharedCollection() failed: " << status;
291 return;
292 }
293
294 status = collection_->SetConstraints(/*has_constraints=*/true,
295 GetBufferConstraints());
296 if (status != ZX_OK) {
297 fatal_error_ = true;
298 RTC_LOG(LS_ERROR)
299 << "fuchsia.sysmem.BufferCollection.SetConstraints() failed: "
300 << status;
301 return;
302 }
303
304 fuchsia::ui::composition::BufferCollectionImportToken import_token;
305 fuchsia::ui::composition::BufferCollectionExportToken export_token;
306 status = zx::eventpair::create(0, &export_token.value, &import_token.value);
307 if (status != ZX_OK) {
308 fatal_error_ = true;
309 RTC_LOG(LS_ERROR)
310 << "Failed to create BufferCollection import and export tokens: "
311 << status;
312 return;
313 }
314
315 status = component_context_->svc()->Connect(flatland_allocator_.NewRequest());
316 if (status != ZX_OK) {
317 fatal_error_ = true;
318 RTC_LOG(LS_ERROR) << "Failed to connect to Flatland Allocator: " << status;
319 return;
320 }
321
322 fuchsia::ui::composition::RegisterBufferCollectionArgs buffer_collection_args;
323 buffer_collection_args.set_export_token(std::move(export_token));
324 buffer_collection_args.set_buffer_collection_token(std::move(flatland_token));
325 buffer_collection_args.set_usage(
326 fuchsia::ui::composition::RegisterBufferCollectionUsage::SCREENSHOT);
327
328 fuchsia::ui::composition::Allocator_RegisterBufferCollection_Result
329 buffer_collection_result;
330 flatland_allocator_->RegisterBufferCollection(
331 std::move(buffer_collection_args), &buffer_collection_result);
332 if (buffer_collection_result.is_err()) {
333 fatal_error_ = true;
334 RTC_LOG(LS_ERROR) << "fuchsia.ui.composition.Allocator."
335 "RegisterBufferCollection() failed.";
336 return;
337 }
338
339 zx_status_t allocation_status;
340 status = collection_->WaitForBuffersAllocated(&allocation_status,
341 &buffer_collection_info_);
342 if (status != ZX_OK) {
343 fatal_error_ = true;
344 RTC_LOG(LS_ERROR) << "Failed to wait for buffer collection info: "
345 << status;
346 return;
347 }
348 if (allocation_status != ZX_OK) {
349 fatal_error_ = true;
350 RTC_LOG(LS_ERROR) << "Failed to allocate buffer collection: " << status;
351 return;
352 }
353 status = collection_->Close();
354 if (status != ZX_OK) {
355 fatal_error_ = true;
356 RTC_LOG(LS_ERROR) << "Failed to close buffer collection token: " << status;
357 return;
358 }
359
360 status = component_context_->svc()->Connect(screen_capture_.NewRequest());
361 if (status != ZX_OK) {
362 fatal_error_ = true;
363 RTC_LOG(LS_ERROR) << "Failed to connect to Screen Capture: " << status;
364 return;
365 }
366
367 // Configure buffers in ScreenCapture client.
368 fuchsia::ui::composition::ScreenCaptureConfig configure_args;
369 configure_args.set_import_token(std::move(import_token));
370 configure_args.set_buffer_count(buffer_collection_info_.buffer_count);
371 configure_args.set_size({width_, height_});
372
373 fuchsia::ui::composition::ScreenCapture_Configure_Result configure_result;
374 screen_capture_->Configure(std::move(configure_args), &configure_result);
375 if (configure_result.is_err()) {
376 fatal_error_ = true;
377 RTC_LOG(LS_ERROR)
378 << "fuchsia.ui.composition.ScreenCapture.Configure() failed: "
379 << configure_result.err();
380 return;
381 }
382
383 // We have a collection of virtual memory objects which the ScreenCapture
384 // client will write the frame data to when requested. We map each of these
385 // onto a pointer stored in virtual_memory_mapped_addrs_ which we can use to
386 // access this data.
387 uint32_t virt_mem_bytes =
388 buffer_collection_info_.settings.buffer_settings.size_bytes;
389 RTC_DCHECK(virt_mem_bytes > 0);
390 for (uint32_t buffer_index = 0;
391 buffer_index < buffer_collection_info_.buffer_count; buffer_index++) {
392 const zx::vmo& virt_mem = buffer_collection_info_.buffers[buffer_index].vmo;
393 virtual_memory_mapped_addrs_[buffer_index] = nullptr;
394 auto status = zx::vmar::root_self()->map(
395 ZX_VM_PERM_READ, /*vmar_offset*/ 0, virt_mem,
396 /*vmo_offset*/ 0, virt_mem_bytes,
397 reinterpret_cast<uintptr_t*>(
398 &virtual_memory_mapped_addrs_[buffer_index]));
399 if (status != ZX_OK) {
400 fatal_error_ = true;
401 RTC_LOG(LS_ERROR) << "Failed to map virtual memory: " << status;
402 return;
403 }
404 }
405 }
406
GetPixelsPerRow(const fuchsia::sysmem::ImageFormatConstraints & constraints)407 uint32_t ScreenCapturerFuchsia::GetPixelsPerRow(
408 const fuchsia::sysmem::ImageFormatConstraints& constraints) {
409 uint32_t stride = RoundUpToMultiple(
410 std::max(constraints.min_bytes_per_row, width_ * kFuchsiaBytesPerPixel),
411 constraints.bytes_per_row_divisor);
412 uint32_t pixels_per_row = stride / kFuchsiaBytesPerPixel;
413
414 return pixels_per_row;
415 }
416
417 } // namespace webrtc
418