tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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_, &registry_listener_, &registry_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