From: Jan Grulich <jgrulich@redhat.com>
Date: Thu, 5 Sep 2024 16:04:00 +0000
Subject: Bug 1830275 - Add missing support for device change notifications
 r=pehrsons,webrtc-reviewers

Registers each DeviceInfoPipeWire in PipeWireSession we use and calls
DeviceChange() for each once there is a new camera added or removed to
invoke OnDeviceChange() for every registered VideoInputFeedback.

Differential Revision: https://phabricator.services.mozilla.com/D219218
Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/60eb5ef11c8df7eb6e0d616cb76885d9109f114d
---
 .../linux/device_info_pipewire.cc             | 10 +++-
 .../video_capture/linux/pipewire_session.cc   | 47 ++++++++++++++++++-
 .../video_capture/linux/pipewire_session.h    | 26 +++++++++-
 3 files changed, 79 insertions(+), 4 deletions(-)

diff --git a/modules/video_capture/linux/device_info_pipewire.cc b/modules/video_capture/linux/device_info_pipewire.cc
index db2a3c7099..a0607b4aba 100644
--- a/modules/video_capture/linux/device_info_pipewire.cc
+++ b/modules/video_capture/linux/device_info_pipewire.cc
@@ -29,13 +29,19 @@
 namespace webrtc {
 namespace videocapturemodule {
 DeviceInfoPipeWire::DeviceInfoPipeWire(VideoCaptureOptions* options)
-    : DeviceInfoImpl(), pipewire_session_(options->pipewire_session()) {}
+    : DeviceInfoImpl(), pipewire_session_(options->pipewire_session()) {
+  const bool ret = pipewire_session_->RegisterDeviceInfo(this);
+  RTC_CHECK(ret);
+}
 
 int32_t DeviceInfoPipeWire::Init() {
   return 0;
 }
 
-DeviceInfoPipeWire::~DeviceInfoPipeWire() = default;
+DeviceInfoPipeWire::~DeviceInfoPipeWire() {
+  const bool ret = pipewire_session_->DeRegisterDeviceInfo(this);
+  RTC_CHECK(ret);
+}
 
 uint32_t DeviceInfoPipeWire::NumberOfDevices() {
   RTC_CHECK(pipewire_session_);
diff --git a/modules/video_capture/linux/pipewire_session.cc b/modules/video_capture/linux/pipewire_session.cc
index 990bfde912..25c4dfae4e 100644
--- a/modules/video_capture/linux/pipewire_session.cc
+++ b/modules/video_capture/linux/pipewire_session.cc
@@ -9,6 +9,7 @@
  */
 
 #include "modules/video_capture/linux/pipewire_session.h"
+#include "modules/video_capture/linux/device_info_pipewire.h"
 
 #include <spa/monitor/device.h>
 #include <spa/param/format-utils.h>
@@ -286,6 +287,28 @@ void PipeWireSession::InitPipeWire(int fd) {
     Finish(VideoCaptureOptions::Status::ERROR);
 }
 
+bool PipeWireSession::RegisterDeviceInfo(DeviceInfoPipeWire* device_info) {
+  RTC_CHECK(device_info);
+  MutexLock lock(&device_info_lock_);
+  auto it = std::find(device_info_list_.begin(), device_info_list_.end(), device_info);
+  if (it == device_info_list_.end()) {
+    device_info_list_.push_back(device_info);
+    return true;
+  }
+  return false;
+}
+
+bool PipeWireSession::DeRegisterDeviceInfo(DeviceInfoPipeWire* device_info) {
+  RTC_CHECK(device_info);
+  MutexLock lock(&device_info_lock_);
+  auto it = std::find(device_info_list_.begin(), device_info_list_.end(), device_info);
+  if (it != device_info_list_.end()) {
+    device_info_list_.erase(it);
+    return true;
+  }
+  return false;
+}
+
 RTC_NO_SANITIZE("cfi-icall")
 bool PipeWireSession::StartPipeWire(int fd) {
   pw_init(/*argc=*/nullptr, /*argv=*/nullptr);
@@ -358,6 +381,21 @@ void PipeWireSession::PipeWireSync() {
   sync_seq_ = pw_core_sync(pw_core_, PW_ID_CORE, sync_seq_);
 }
 
+void PipeWireSession::NotifyDeviceChange() {
+  RTC_LOG(LS_INFO) << "Notify about device list changes";
+  MutexLock lock(&device_info_lock_);
+
+  // It makes sense to notify about device changes only once we are
+  // properly initialized.
+  if (status_ != VideoCaptureOptions::Status::SUCCESS) {
+    return;
+  }
+
+  for (auto* deviceInfo : device_info_list_) {
+    deviceInfo->DeviceChange();
+  }
+}
+
 // static
 void PipeWireSession::OnCoreError(void* data,
                                   uint32_t id,
@@ -416,6 +454,8 @@ void PipeWireSession::OnRegistryGlobal(void* data,
 
   that->nodes_.push_back(PipeWireNode::Create(that, id, props));
   that->PipeWireSync();
+
+  that->NotifyDeviceChange();
 }
 
 // static
@@ -427,10 +467,15 @@ void PipeWireSession::OnRegistryGlobalRemove(void* data, uint32_t id) {
                              return node->id() == id;
                            });
   that->nodes_.erase(it, that->nodes_.end());
+
+  that->NotifyDeviceChange();
 }
 
 void PipeWireSession::Finish(VideoCaptureOptions::Status status) {
-  status_ = status;
+  {
+    MutexLock lock(&device_info_lock_);
+    status_ = status;
+  }
 
   webrtc::MutexLock lock(&callback_lock_);
 
diff --git a/modules/video_capture/linux/pipewire_session.h b/modules/video_capture/linux/pipewire_session.h
index aec268e008..7dbba23691 100644
--- a/modules/video_capture/linux/pipewire_session.h
+++ b/modules/video_capture/linux/pipewire_session.h
@@ -28,6 +28,7 @@
 namespace webrtc {
 namespace videocapturemodule {
 
+class DeviceInfoPipeWire;
 class PipeWireSession;
 class VideoCaptureModulePipeWire;
 
@@ -96,6 +97,21 @@ class PipeWireSession : public webrtc::RefCountedNonVirtual<PipeWireSession> {
 
   void Init(VideoCaptureOptions::Callback* callback,
             int fd = kInvalidPipeWireFd);
+
+  // [De]Register DeviceInfo for device change updates
+  // These methods will add or remove references to DeviceInfo
+  // objects that we want to notify about device changes.
+  // NOTE: We do not take ownership of these objects and
+  // they should never be released by us. All the instances
+  // of DeviceInfoPipeWire must outlive their registration.
+
+  // Returns true when DeviceInfo was successfuly registered
+  // or false otherwise, when it was already registered before.
+  bool RegisterDeviceInfo(DeviceInfoPipeWire* device_info);
+  // Returns true when DeviceInfo was successfuly unregistered
+  // or false otherwise, when it was not previously registered.
+  bool DeRegisterDeviceInfo(DeviceInfoPipeWire* device_info);
+
   const std::deque<PipeWireNode::PipeWireNodePtr>& nodes() const {
     return nodes_;
   }
@@ -110,6 +126,8 @@ class PipeWireSession : public webrtc::RefCountedNonVirtual<PipeWireSession> {
   void StopPipeWire();
   void PipeWireSync();
 
+  void NotifyDeviceChange();
+
   static void OnCoreError(void* data,
                           uint32_t id,
                           int seq,
@@ -132,7 +150,13 @@ class PipeWireSession : public webrtc::RefCountedNonVirtual<PipeWireSession> {
   VideoCaptureOptions::Callback* callback_ RTC_GUARDED_BY(&callback_lock_) =
       nullptr;
 
-  VideoCaptureOptions::Status status_;
+  webrtc::Mutex device_info_lock_;
+  std::vector<DeviceInfoPipeWire*> device_info_list_
+      RTC_GUARDED_BY(device_info_lock_);
+  // Guard with device_info_lock, because currently it's the only place where
+  // we use this status information.
+  VideoCaptureOptions::Status status_
+      RTC_GUARDED_BY(device_info_lock_);
 
   struct pw_thread_loop* pw_main_loop_ = nullptr;
   struct pw_context* pw_context_ = nullptr;
