pipewire_session.cc (15297B)
1 /* 2 * Copyright (c) 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/video_capture/linux/pipewire_session.h" 12 13 #include <pipewire/pipewire.h> 14 #include <spa/monitor/device.h> 15 #include <spa/param/format-utils.h> 16 #include <spa/param/format.h> 17 #include <spa/param/param.h> 18 #include <spa/param/video/raw.h> 19 #include <spa/pod/iter.h> 20 #include <spa/pod/pod.h> 21 #include <spa/utils/defs.h> 22 #include <spa/utils/dict.h> 23 #include <spa/utils/hook.h> 24 #include <spa/utils/type.h> 25 26 #include <algorithm> 27 #include <cstdint> 28 #include <cstdio> 29 #include <cstring> 30 #include <memory> 31 #include <optional> 32 33 #include "absl/strings/string_view.h" 34 #include "common_video/libyuv/include/webrtc_libyuv.h" 35 #include "modules/portal/pipewire_utils.h" 36 #include "modules/portal/portal_request_response.h" 37 #include "modules/video_capture/linux/camera_portal.h" 38 #include "modules/video_capture/linux/device_info_pipewire.h" 39 #include "modules/video_capture/video_capture_defines.h" 40 #include "modules/video_capture/video_capture_options.h" 41 #include "rtc_base/logging.h" 42 #include "rtc_base/sanitizer.h" 43 #include "rtc_base/string_to_number.h" 44 #include "rtc_base/synchronization/mutex.h" 45 46 namespace webrtc { 47 namespace videocapturemodule { 48 49 VideoType PipeWireRawFormatToVideoType(uint32_t id) { 50 switch (id) { 51 case SPA_VIDEO_FORMAT_I420: 52 return VideoType::kI420; 53 case SPA_VIDEO_FORMAT_NV12: 54 return VideoType::kNV12; 55 case SPA_VIDEO_FORMAT_YUY2: 56 return VideoType::kYUY2; 57 case SPA_VIDEO_FORMAT_UYVY: 58 return VideoType::kUYVY; 59 case SPA_VIDEO_FORMAT_RGB16: 60 return VideoType::kRGB565; 61 case SPA_VIDEO_FORMAT_RGB: 62 return VideoType::kBGR24; 63 case SPA_VIDEO_FORMAT_BGR: 64 return VideoType::kRGB24; 65 case SPA_VIDEO_FORMAT_BGRA: 66 return VideoType::kARGB; 67 case SPA_VIDEO_FORMAT_RGBA: 68 return VideoType::kABGR; 69 case SPA_VIDEO_FORMAT_ARGB: 70 return VideoType::kBGRA; 71 default: 72 return VideoType::kUnknown; 73 } 74 } 75 76 void PipeWireNode::PipeWireNodeDeleter::operator()( 77 PipeWireNode* node) const noexcept { 78 spa_hook_remove(&node->node_listener_); 79 pw_proxy_destroy(node->proxy_); 80 } 81 82 // static 83 PipeWireNode::PipeWireNodePtr PipeWireNode::Create(PipeWireSession* session, 84 uint32_t id, 85 const spa_dict* props) { 86 return PipeWireNodePtr(new PipeWireNode(session, id, props)); 87 } 88 89 RTC_NO_SANITIZE("cfi-icall") 90 PipeWireNode::PipeWireNode(PipeWireSession* session, 91 uint32_t id, 92 const spa_dict* props) 93 : session_(session), 94 id_(id), 95 display_name_(spa_dict_lookup(props, PW_KEY_NODE_DESCRIPTION)), 96 unique_id_(spa_dict_lookup(props, PW_KEY_NODE_NAME)) { 97 RTC_LOG(LS_VERBOSE) << "Found Camera: " << display_name_; 98 99 proxy_ = static_cast<pw_proxy*>(pw_registry_bind( 100 session_->pw_registry_, id, PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, 0)); 101 102 static const pw_node_events node_events{ 103 .version = PW_VERSION_NODE_EVENTS, 104 .info = OnNodeInfo, 105 .param = OnNodeParam, 106 }; 107 108 pw_node_add_listener(reinterpret_cast<pw_node*>(proxy_), &node_listener_, 109 &node_events, this); 110 } 111 112 // static 113 RTC_NO_SANITIZE("cfi-icall") 114 void PipeWireNode::OnNodeInfo(void* data, const pw_node_info* info) { 115 PipeWireNode* that = static_cast<PipeWireNode*>(data); 116 117 if (info->change_mask & PW_NODE_CHANGE_MASK_PROPS) { 118 const char* vid_str; 119 const char* pid_str; 120 std::optional<int> vid; 121 std::optional<int> pid; 122 123 vid_str = spa_dict_lookup(info->props, SPA_KEY_DEVICE_VENDOR_ID); 124 pid_str = spa_dict_lookup(info->props, SPA_KEY_DEVICE_PRODUCT_ID); 125 vid = vid_str ? webrtc::StringToNumber<int>(vid_str) : std::nullopt; 126 pid = pid_str ? webrtc::StringToNumber<int>(pid_str) : std::nullopt; 127 128 if (vid && pid) { 129 char model_str[10]; 130 snprintf(model_str, sizeof(model_str), "%04x:%04x", vid.value(), 131 pid.value()); 132 that->model_id_ = model_str; 133 } 134 } 135 136 if (info->change_mask & PW_NODE_CHANGE_MASK_PARAMS) { 137 for (uint32_t i = 0; i < info->n_params; i++) { 138 uint32_t id = info->params[i].id; 139 if (id == SPA_PARAM_EnumFormat && 140 info->params[i].flags & SPA_PARAM_INFO_READ) { 141 pw_node_enum_params(reinterpret_cast<pw_node*>(that->proxy_), 0, id, 0, 142 UINT32_MAX, nullptr); 143 break; 144 } 145 } 146 that->session_->PipeWireSync(); 147 } 148 } 149 150 // static 151 RTC_NO_SANITIZE("cfi-icall") 152 void PipeWireNode::OnNodeParam(void* data, 153 int seq, 154 uint32_t id, 155 uint32_t index, 156 uint32_t next, 157 const spa_pod* param) { 158 PipeWireNode* that = static_cast<PipeWireNode*>(data); 159 auto* obj = reinterpret_cast<const spa_pod_object*>(param); 160 const spa_pod_prop* prop = nullptr; 161 VideoCaptureCapability cap; 162 spa_pod* val; 163 uint32_t n_items, choice; 164 165 cap.videoType = VideoType::kUnknown; 166 cap.maxFPS = 0; 167 168 prop = spa_pod_object_find_prop(obj, prop, SPA_FORMAT_VIDEO_framerate); 169 if (prop) { 170 val = spa_pod_get_values(&prop->value, &n_items, &choice); 171 if (val->type == SPA_TYPE_Fraction) { 172 spa_fraction* fract; 173 174 fract = static_cast<spa_fraction*>(SPA_POD_BODY(val)); 175 176 if (choice == SPA_CHOICE_None) { 177 cap.maxFPS = 1.0 * fract[0].num / fract[0].denom; 178 } else if (choice == SPA_CHOICE_Enum) { 179 for (uint32_t i = 1; i < n_items; i++) { 180 cap.maxFPS = std::max( 181 static_cast<int32_t>(1.0 * fract[i].num / fract[i].denom), 182 cap.maxFPS); 183 } 184 } else if (choice == SPA_CHOICE_Range && fract[1].num > 0) { 185 cap.maxFPS = 1.0 * fract[1].num / fract[1].denom; 186 } 187 } 188 } 189 190 prop = spa_pod_object_find_prop(obj, prop, SPA_FORMAT_VIDEO_size); 191 if (!prop) 192 return; 193 194 val = spa_pod_get_values(&prop->value, &n_items, &choice); 195 if (val->type != SPA_TYPE_Rectangle) 196 return; 197 198 if (choice != SPA_CHOICE_None) 199 return; 200 201 if (!ParseFormat(param, &cap)) 202 return; 203 204 spa_rectangle* rect; 205 rect = static_cast<spa_rectangle*>(SPA_POD_BODY(val)); 206 cap.width = rect[0].width; 207 cap.height = rect[0].height; 208 209 RTC_LOG(LS_VERBOSE) << "Found Format(" << that->display_name_ 210 << "): " << static_cast<int>(cap.videoType) << "(" 211 << cap.width << "x" << cap.height << "@" << cap.maxFPS 212 << ")"; 213 214 that->capabilities_.push_back(cap); 215 } 216 217 // static 218 bool PipeWireNode::ParseFormat(const spa_pod* param, 219 VideoCaptureCapability* cap) { 220 auto* obj = reinterpret_cast<const spa_pod_object*>(param); 221 uint32_t media_type, media_subtype; 222 223 if (spa_format_parse(param, &media_type, &media_subtype) < 0) { 224 RTC_LOG(LS_ERROR) << "Failed to parse video format."; 225 return false; 226 } 227 228 if (media_type != SPA_MEDIA_TYPE_video) 229 return false; 230 231 if (media_subtype == SPA_MEDIA_SUBTYPE_raw) { 232 const spa_pod_prop* prop = nullptr; 233 uint32_t n_items, choice; 234 spa_pod* val; 235 uint32_t* id; 236 237 prop = spa_pod_object_find_prop(obj, prop, SPA_FORMAT_VIDEO_format); 238 if (!prop) 239 return false; 240 241 val = spa_pod_get_values(&prop->value, &n_items, &choice); 242 if (val->type != SPA_TYPE_Id) 243 return false; 244 245 if (choice != SPA_CHOICE_None) 246 return false; 247 248 id = static_cast<uint32_t*>(SPA_POD_BODY(val)); 249 250 cap->videoType = PipeWireRawFormatToVideoType(id[0]); 251 if (cap->videoType == VideoType::kUnknown) { 252 RTC_LOG(LS_INFO) << "Unsupported PipeWire pixel format " << id[0]; 253 return false; 254 } 255 256 } else if (media_subtype == SPA_MEDIA_SUBTYPE_mjpg) { 257 cap->videoType = VideoType::kMJPEG; 258 } else { 259 RTC_LOG(LS_INFO) << "Unsupported PipeWire media subtype " << media_subtype; 260 } 261 262 return cap->videoType != VideoType::kUnknown; 263 } 264 265 CameraPortalNotifier::CameraPortalNotifier(PipeWireSession* session) 266 : session_(session) {} 267 268 void CameraPortalNotifier::OnCameraRequestResult( 269 xdg_portal::RequestResponse result, 270 int fd) { 271 if (result == xdg_portal::RequestResponse::kSuccess) { 272 session_->InitPipeWire(fd); 273 } else if (result == xdg_portal::RequestResponse::kUserCancelled) { 274 session_->Finish(VideoCaptureOptions::Status::DENIED); 275 } else { 276 session_->Finish(VideoCaptureOptions::Status::ERROR); 277 } 278 } 279 280 PipeWireSession::PipeWireSession() 281 : status_(VideoCaptureOptions::Status::UNINITIALIZED) {} 282 283 PipeWireSession::~PipeWireSession() { 284 Cleanup(); 285 } 286 287 void PipeWireSession::Init(VideoCaptureOptions::Callback* callback, int fd) { 288 { 289 webrtc::MutexLock lock(&callback_lock_); 290 callback_ = callback; 291 } 292 293 if (fd != kInvalidPipeWireFd) { 294 InitPipeWire(fd); 295 } else { 296 portal_notifier_ = std::make_unique<CameraPortalNotifier>(this); 297 portal_ = std::make_unique<CameraPortal>(portal_notifier_.get()); 298 portal_->Start(); 299 } 300 } 301 302 void PipeWireSession::InitPipeWire(int fd) { 303 if (!InitializePipeWire()) 304 Finish(VideoCaptureOptions::Status::UNAVAILABLE); 305 306 if (!StartPipeWire(fd)) 307 Finish(VideoCaptureOptions::Status::ERROR); 308 } 309 310 bool PipeWireSession::RegisterDeviceInfo(DeviceInfoPipeWire* device_info) { 311 RTC_CHECK(device_info); 312 MutexLock lock(&device_info_lock_); 313 auto it = std::find(device_info_list_.begin(), device_info_list_.end(), 314 device_info); 315 if (it == device_info_list_.end()) { 316 device_info_list_.push_back(device_info); 317 return true; 318 } 319 return false; 320 } 321 322 bool PipeWireSession::DeRegisterDeviceInfo(DeviceInfoPipeWire* device_info) { 323 RTC_CHECK(device_info); 324 MutexLock lock(&device_info_lock_); 325 auto it = std::find(device_info_list_.begin(), device_info_list_.end(), 326 device_info); 327 if (it != device_info_list_.end()) { 328 device_info_list_.erase(it); 329 return true; 330 } 331 return false; 332 } 333 334 RTC_NO_SANITIZE("cfi-icall") 335 bool PipeWireSession::StartPipeWire(int fd) { 336 pw_init(/*argc=*/nullptr, /*argv=*/nullptr); 337 338 pw_main_loop_ = pw_thread_loop_new("pipewire-main-loop", nullptr); 339 340 pw_context_ = 341 pw_context_new(pw_thread_loop_get_loop(pw_main_loop_), nullptr, 0); 342 if (!pw_context_) { 343 RTC_LOG(LS_ERROR) << "Failed to create PipeWire context"; 344 return false; 345 } 346 347 pw_core_ = pw_context_connect_fd(pw_context_, fd, nullptr, 0); 348 if (!pw_core_) { 349 RTC_LOG(LS_ERROR) << "Failed to connect PipeWire context"; 350 return false; 351 } 352 353 static const pw_core_events core_events{ 354 .version = PW_VERSION_CORE_EVENTS, 355 .done = &OnCoreDone, 356 .error = &OnCoreError, 357 }; 358 359 pw_core_add_listener(pw_core_, &core_listener_, &core_events, this); 360 361 static const pw_registry_events registry_events{ 362 .version = PW_VERSION_REGISTRY_EVENTS, 363 .global = OnRegistryGlobal, 364 .global_remove = OnRegistryGlobalRemove, 365 }; 366 367 pw_registry_ = pw_core_get_registry(pw_core_, PW_VERSION_REGISTRY, 0); 368 pw_registry_add_listener(pw_registry_, ®istry_listener_, ®istry_events, 369 this); 370 371 PipeWireSync(); 372 373 if (pw_thread_loop_start(pw_main_loop_) < 0) { 374 RTC_LOG(LS_ERROR) << "Failed to start main PipeWire loop"; 375 return false; 376 } 377 378 return true; 379 } 380 381 void PipeWireSession::StopPipeWire() { 382 if (pw_main_loop_) 383 pw_thread_loop_stop(pw_main_loop_); 384 385 if (pw_core_) { 386 pw_core_disconnect(pw_core_); 387 pw_core_ = nullptr; 388 } 389 390 if (pw_context_) { 391 pw_context_destroy(pw_context_); 392 pw_context_ = nullptr; 393 } 394 395 if (pw_main_loop_) { 396 pw_thread_loop_destroy(pw_main_loop_); 397 pw_main_loop_ = nullptr; 398 } 399 } 400 401 RTC_NO_SANITIZE("cfi-icall") 402 void PipeWireSession::PipeWireSync() { 403 sync_seq_ = pw_core_sync(pw_core_, PW_ID_CORE, sync_seq_); 404 } 405 406 void PipeWireSession::NotifyDeviceChange() { 407 RTC_LOG(LS_INFO) << "Notify about device list changes"; 408 MutexLock lock(&device_info_lock_); 409 410 // It makes sense to notify about device changes only once we are 411 // properly initialized. 412 if (status_ != VideoCaptureOptions::Status::SUCCESS) { 413 return; 414 } 415 416 for (auto* deviceInfo : device_info_list_) { 417 deviceInfo->DeviceChange(); 418 } 419 } 420 421 // static 422 void PipeWireSession::OnCoreError(void* data, 423 uint32_t id, 424 int seq, 425 int res, 426 const char* message) { 427 RTC_LOG(LS_ERROR) << "PipeWire remote error: " << message; 428 } 429 430 // static 431 void PipeWireSession::OnCoreDone(void* data, uint32_t id, int seq) { 432 PipeWireSession* that = static_cast<PipeWireSession*>(data); 433 434 if (id == PW_ID_CORE) { 435 if (seq == that->sync_seq_) { 436 RTC_LOG(LS_VERBOSE) << "Enumerating PipeWire camera devices complete."; 437 438 // Remove camera devices with no capabilities 439 std::erase_if(that->nodes_, 440 [](const PipeWireNode::PipeWireNodePtr& node) { 441 return node->capabilities().empty(); 442 }); 443 444 that->Finish(VideoCaptureOptions::Status::SUCCESS); 445 } 446 } 447 } 448 449 // static 450 RTC_NO_SANITIZE("cfi-icall") 451 void PipeWireSession::OnRegistryGlobal(void* data, 452 uint32_t id, 453 uint32_t permissions, 454 const char* type, 455 uint32_t version, 456 const spa_dict* props) { 457 PipeWireSession* that = static_cast<PipeWireSession*>(data); 458 459 // Skip already added nodes to avoid duplicate camera entries 460 if (std::find_if(that->nodes_.begin(), that->nodes_.end(), 461 [id](const PipeWireNode::PipeWireNodePtr& node) { 462 return node->id() == id; 463 }) != that->nodes_.end()) 464 return; 465 466 if (type != absl::string_view(PW_TYPE_INTERFACE_Node)) 467 return; 468 469 if (!spa_dict_lookup(props, PW_KEY_NODE_DESCRIPTION)) 470 return; 471 472 auto node_role = spa_dict_lookup(props, PW_KEY_MEDIA_ROLE); 473 if (!node_role || strcmp(node_role, "Camera")) 474 return; 475 476 that->nodes_.push_back(PipeWireNode::Create(that, id, props)); 477 that->PipeWireSync(); 478 479 that->NotifyDeviceChange(); 480 } 481 482 // static 483 void PipeWireSession::OnRegistryGlobalRemove(void* data, uint32_t id) { 484 PipeWireSession* that = static_cast<PipeWireSession*>(data); 485 486 std::erase_if(that->nodes_, [id](const PipeWireNode::PipeWireNodePtr& node) { 487 return node->id() == id; 488 }); 489 490 that->NotifyDeviceChange(); 491 } 492 493 void PipeWireSession::Finish(VideoCaptureOptions::Status status) { 494 { 495 MutexLock lock(&device_info_lock_); 496 status_ = status; 497 } 498 499 webrtc::MutexLock lock(&callback_lock_); 500 501 if (callback_) { 502 callback_->OnInitialized(status); 503 callback_ = nullptr; 504 } 505 } 506 507 void PipeWireSession::Cleanup() { 508 webrtc::MutexLock lock(&callback_lock_); 509 callback_ = nullptr; 510 511 StopPipeWire(); 512 } 513 514 } // namespace videocapturemodule 515 } // namespace webrtc