diff options
Diffstat (limited to 'third_party/libwebrtc/modules/video_capture/linux')
14 files changed, 2758 insertions, 0 deletions
diff --git a/third_party/libwebrtc/modules/video_capture/linux/camera_portal.cc b/third_party/libwebrtc/modules/video_capture/linux/camera_portal.cc new file mode 100644 index 0000000000..85b9f20228 --- /dev/null +++ b/third_party/libwebrtc/modules/video_capture/linux/camera_portal.cc @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2023 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/video_capture/linux/camera_portal.h" + +#include <gio/gio.h> +#include <gio/gunixfdlist.h> + +#include "modules/portal/pipewire_utils.h" +#include "modules/portal/xdg_desktop_portal_utils.h" + +namespace webrtc { + +using xdg_portal::RequestResponse; +using xdg_portal::RequestResponseFromPortalResponse; +using xdg_portal::RequestSessionProxy; + +constexpr char kCameraInterfaceName[] = "org.freedesktop.portal.Camera"; + +class CameraPortalPrivate { + public: + explicit CameraPortalPrivate(CameraPortal::PortalNotifier* notifier); + ~CameraPortalPrivate(); + + void Start(); + + private: + void OnPortalDone(xdg_portal::RequestResponse result, + int fd = kInvalidPipeWireFd); + + static void OnProxyRequested(GObject* object, + GAsyncResult* result, + gpointer user_data); + void ProxyRequested(GDBusProxy* proxy); + + static void OnAccessResponse(GDBusProxy* proxy, + GAsyncResult* result, + gpointer user_data); + static void OnResponseSignalEmitted(GDBusConnection* connection, + const char* sender_name, + const char* object_path, + const char* interface_name, + const char* signal_name, + GVariant* parameters, + gpointer user_data); + static void OnOpenResponse(GDBusProxy* proxy, + GAsyncResult* result, + gpointer user_data); + + CameraPortal::PortalNotifier* notifier_ = nullptr; + + GDBusConnection* connection_ = nullptr; + GDBusProxy* proxy_ = nullptr; + GCancellable* cancellable_ = nullptr; + guint access_request_signal_id_ = 0; +}; + +CameraPortalPrivate::CameraPortalPrivate(CameraPortal::PortalNotifier* notifier) + : notifier_(notifier) {} + +CameraPortalPrivate::~CameraPortalPrivate() { + if (access_request_signal_id_) { + g_dbus_connection_signal_unsubscribe(connection_, + access_request_signal_id_); + access_request_signal_id_ = 0; + } + if (cancellable_) { + g_cancellable_cancel(cancellable_); + g_object_unref(cancellable_); + cancellable_ = nullptr; + } + if (proxy_) { + g_object_unref(proxy_); + proxy_ = nullptr; + connection_ = nullptr; + } +} + +void CameraPortalPrivate::Start() { + cancellable_ = g_cancellable_new(); + Scoped<GError> error; + RequestSessionProxy(kCameraInterfaceName, OnProxyRequested, cancellable_, + this); +} + +// static +void CameraPortalPrivate::OnProxyRequested(GObject* gobject, + GAsyncResult* result, + gpointer user_data) { + CameraPortalPrivate* that = static_cast<CameraPortalPrivate*>(user_data); + Scoped<GError> error; + GDBusProxy* proxy = g_dbus_proxy_new_finish(result, error.receive()); + if (!proxy) { + // Ignore the error caused by user cancelling the request via `cancellable_` + if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + RTC_LOG(LS_ERROR) << "Failed to get a proxy for the portal: " + << error->message; + that->OnPortalDone(RequestResponse::kError); + return; + } + + RTC_LOG(LS_VERBOSE) << "Successfully created proxy for the portal."; + that->ProxyRequested(proxy); +} + +void CameraPortalPrivate::ProxyRequested(GDBusProxy* proxy) { + GVariantBuilder builder; + Scoped<char> variant_string; + std::string access_handle; + + proxy_ = proxy; + connection_ = g_dbus_proxy_get_connection(proxy); + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + variant_string = + g_strdup_printf("capture%d", g_random_int_range(0, G_MAXINT)); + g_variant_builder_add(&builder, "{sv}", "handle_token", + g_variant_new_string(variant_string.get())); + + access_handle = + xdg_portal::PrepareSignalHandle(variant_string.get(), connection_); + access_request_signal_id_ = xdg_portal::SetupRequestResponseSignal( + access_handle.c_str(), OnResponseSignalEmitted, this, connection_); + + RTC_LOG(LS_VERBOSE) << "Requesting camera access from the portal."; + g_dbus_proxy_call(proxy_, "AccessCamera", g_variant_new("(a{sv})", &builder), + G_DBUS_CALL_FLAGS_NONE, /*timeout_msec=*/-1, cancellable_, + reinterpret_cast<GAsyncReadyCallback>(OnAccessResponse), + this); +} + +// static +void CameraPortalPrivate::OnAccessResponse(GDBusProxy* proxy, + GAsyncResult* result, + gpointer user_data) { + CameraPortalPrivate* that = static_cast<CameraPortalPrivate*>(user_data); + RTC_DCHECK(that); + + Scoped<GError> error; + Scoped<GVariant> variant( + g_dbus_proxy_call_finish(proxy, result, error.receive())); + if (!variant) { + if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + RTC_LOG(LS_ERROR) << "Failed to access portal:" << error->message; + if (that->access_request_signal_id_) { + g_dbus_connection_signal_unsubscribe(that->connection_, + that->access_request_signal_id_); + that->access_request_signal_id_ = 0; + } + that->OnPortalDone(RequestResponse::kError); + } +} + +// static +void CameraPortalPrivate::OnResponseSignalEmitted(GDBusConnection* connection, + const char* sender_name, + const char* object_path, + const char* interface_name, + const char* signal_name, + GVariant* parameters, + gpointer user_data) { + CameraPortalPrivate* that = static_cast<CameraPortalPrivate*>(user_data); + RTC_DCHECK(that); + + uint32_t portal_response; + g_variant_get(parameters, "(u@a{sv})", &portal_response, nullptr); + if (portal_response) { + RTC_LOG(LS_INFO) << "Camera access denied by the XDG portal."; + that->OnPortalDone(RequestResponseFromPortalResponse(portal_response)); + return; + } + + RTC_LOG(LS_VERBOSE) << "Camera access granted by the XDG portal."; + + GVariantBuilder builder; + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + + g_dbus_proxy_call( + that->proxy_, "OpenPipeWireRemote", g_variant_new("(a{sv})", &builder), + G_DBUS_CALL_FLAGS_NONE, /*timeout_msec=*/-1, that->cancellable_, + reinterpret_cast<GAsyncReadyCallback>(OnOpenResponse), that); +} + +void CameraPortalPrivate::OnOpenResponse(GDBusProxy* proxy, + GAsyncResult* result, + gpointer user_data) { + CameraPortalPrivate* that = static_cast<CameraPortalPrivate*>(user_data); + RTC_DCHECK(that); + + Scoped<GError> error; + Scoped<GUnixFDList> outlist; + Scoped<GVariant> variant(g_dbus_proxy_call_with_unix_fd_list_finish( + proxy, outlist.receive(), result, error.receive())); + if (!variant) { + if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + RTC_LOG(LS_ERROR) << "Failed to open PipeWire remote:" << error->message; + if (that->access_request_signal_id_) { + g_dbus_connection_signal_unsubscribe(that->connection_, + that->access_request_signal_id_); + that->access_request_signal_id_ = 0; + } + that->OnPortalDone(RequestResponse::kError); + return; + } + + int32_t index; + g_variant_get(variant.get(), "(h)", &index); + + int fd = g_unix_fd_list_get(outlist.get(), index, error.receive()); + + if (fd == kInvalidPipeWireFd) { + RTC_LOG(LS_ERROR) << "Failed to get file descriptor from the list: " + << error->message; + that->OnPortalDone(RequestResponse::kError); + return; + } + + that->OnPortalDone(RequestResponse::kSuccess, fd); +} + +void CameraPortalPrivate::OnPortalDone(RequestResponse result, int fd) { + notifier_->OnCameraRequestResult(result, fd); +} + +CameraPortal::CameraPortal(PortalNotifier* notifier) + : private_(std::make_unique<CameraPortalPrivate>(notifier)) {} + +CameraPortal::~CameraPortal() {} + +void CameraPortal::Start() { + private_->Start(); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/video_capture/linux/camera_portal.h b/third_party/libwebrtc/modules/video_capture/linux/camera_portal.h new file mode 100644 index 0000000000..36f2ec8b8a --- /dev/null +++ b/third_party/libwebrtc/modules/video_capture/linux/camera_portal.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_VIDEO_CAPTURE_LINUX_CAMERA_PORTAL_H_ +#define MODULES_VIDEO_CAPTURE_LINUX_CAMERA_PORTAL_H_ + +#include <memory> +#include <string> + +#include "modules/portal/portal_request_response.h" +#include "rtc_base/system/rtc_export.h" + +namespace webrtc { + +class CameraPortalPrivate; + +class RTC_EXPORT CameraPortal { + public: + class PortalNotifier { + public: + virtual void OnCameraRequestResult(xdg_portal::RequestResponse result, + int fd) = 0; + + protected: + PortalNotifier() = default; + virtual ~PortalNotifier() = default; + }; + + explicit CameraPortal(PortalNotifier* notifier); + ~CameraPortal(); + + void Start(); + + private: + std::unique_ptr<CameraPortalPrivate> private_; +}; + +} // namespace webrtc + +#endif // MODULES_VIDEO_CAPTURE_LINUX_CAMERA_PORTAL_H_ diff --git a/third_party/libwebrtc/modules/video_capture/linux/device_info_linux.cc b/third_party/libwebrtc/modules/video_capture/linux/device_info_linux.cc new file mode 100644 index 0000000000..cae63c7c2d --- /dev/null +++ b/third_party/libwebrtc/modules/video_capture/linux/device_info_linux.cc @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <unistd.h> +// v4l includes +#if defined(__NetBSD__) || defined(__OpenBSD__) // WEBRTC_BSD +#include <sys/videoio.h> +#elif defined(__sun) +#include <sys/videodev2.h> +#else +#include <linux/videodev2.h> +#endif + +#include <vector> + +#if defined(WEBRTC_USE_PIPEWIRE) +#include "modules/video_capture/linux/device_info_pipewire.h" +#endif +#include "modules/video_capture/linux/device_info_v4l2.h" +#include "modules/video_capture/video_capture.h" +#include "modules/video_capture/video_capture_defines.h" +#include "modules/video_capture/video_capture_impl.h" +#include "modules/video_capture/video_capture_options.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace videocapturemodule { +VideoCaptureModule::DeviceInfo* VideoCaptureImpl::CreateDeviceInfo() { + return new videocapturemodule::DeviceInfoV4l2(); +} + +VideoCaptureModule::DeviceInfo* VideoCaptureImpl::CreateDeviceInfo( + VideoCaptureOptions* options) { +#if defined(WEBRTC_USE_PIPEWIRE) + if (options->allow_pipewire()) { + return new videocapturemodule::DeviceInfoPipeWire(options); + } +#endif + if (options->allow_v4l2()) + return new videocapturemodule::DeviceInfoV4l2(); + + return nullptr; +} +} // namespace videocapturemodule +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/video_capture/linux/device_info_pipewire.cc b/third_party/libwebrtc/modules/video_capture/linux/device_info_pipewire.cc new file mode 100644 index 0000000000..ad6cea57b8 --- /dev/null +++ b/third_party/libwebrtc/modules/video_capture/linux/device_info_pipewire.cc @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/video_capture/linux/device_info_pipewire.h" + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <unistd.h> + +#include <vector> + +#include "modules/video_capture/video_capture.h" +#include "modules/video_capture/video_capture_defines.h" +#include "modules/video_capture/video_capture_impl.h" +#include "modules/video_capture/video_capture_options.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace videocapturemodule { +DeviceInfoPipeWire::DeviceInfoPipeWire(VideoCaptureOptions* options) + : DeviceInfoImpl(), pipewire_session_(options->pipewire_session()) {} + +int32_t DeviceInfoPipeWire::Init() { + return 0; +} + +DeviceInfoPipeWire::~DeviceInfoPipeWire() = default; + +uint32_t DeviceInfoPipeWire::NumberOfDevices() { + return pipewire_session_->nodes().size(); +} + +int32_t DeviceInfoPipeWire::GetDeviceName(uint32_t deviceNumber, + char* deviceNameUTF8, + uint32_t deviceNameLength, + char* deviceUniqueIdUTF8, + uint32_t deviceUniqueIdUTF8Length, + char* productUniqueIdUTF8, + uint32_t productUniqueIdUTF8Length, + pid_t* pid, + bool* deviceIsPlaceholder) { + if (deviceNumber >= NumberOfDevices()) + return -1; + + const PipeWireNode& node = pipewire_session_->nodes().at(deviceNumber); + + if (deviceNameLength <= node.display_name().length()) { + RTC_LOG(LS_INFO) << "deviceNameUTF8 buffer passed is too small"; + return -1; + } + if (deviceUniqueIdUTF8Length <= node.unique_id().length()) { + RTC_LOG(LS_INFO) << "deviceUniqueIdUTF8 buffer passed is too small"; + return -1; + } + if (productUniqueIdUTF8 && + productUniqueIdUTF8Length <= node.model_id().length()) { + RTC_LOG(LS_INFO) << "productUniqueIdUTF8 buffer passed is too small"; + return -1; + } + + memset(deviceNameUTF8, 0, deviceNameLength); + node.display_name().copy(deviceNameUTF8, deviceNameLength); + + memset(deviceUniqueIdUTF8, 0, deviceUniqueIdUTF8Length); + node.unique_id().copy(deviceUniqueIdUTF8, deviceUniqueIdUTF8Length); + + if (productUniqueIdUTF8) { + memset(productUniqueIdUTF8, 0, productUniqueIdUTF8Length); + node.model_id().copy(productUniqueIdUTF8, productUniqueIdUTF8Length); + } + + return 0; +} + +int32_t DeviceInfoPipeWire::CreateCapabilityMap( + const char* deviceUniqueIdUTF8) { + for (auto& node : pipewire_session_->nodes()) { + if (node.unique_id().compare(deviceUniqueIdUTF8) != 0) + continue; + + _captureCapabilities = node.capabilities(); + _lastUsedDeviceNameLength = node.unique_id().length(); + _lastUsedDeviceName = static_cast<char*>( + realloc(_lastUsedDeviceName, _lastUsedDeviceNameLength + 1)); + memcpy(_lastUsedDeviceName, deviceUniqueIdUTF8, + _lastUsedDeviceNameLength + 1); + return _captureCapabilities.size(); + } + return -1; +} + +int32_t DeviceInfoPipeWire::DisplayCaptureSettingsDialogBox( + const char* /*deviceUniqueIdUTF8*/, + const char* /*dialogTitleUTF8*/, + void* /*parentWindow*/, + uint32_t /*positionX*/, + uint32_t /*positionY*/) { + return -1; +} + +} // namespace videocapturemodule +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/video_capture/linux/device_info_pipewire.h b/third_party/libwebrtc/modules/video_capture/linux/device_info_pipewire.h new file mode 100644 index 0000000000..1a1324e92b --- /dev/null +++ b/third_party/libwebrtc/modules/video_capture/linux/device_info_pipewire.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_VIDEO_CAPTURE_LINUX_DEVICE_INFO_PIPEWIRE_H_ +#define MODULES_VIDEO_CAPTURE_LINUX_DEVICE_INFO_PIPEWIRE_H_ + +#include <stdint.h> + +#include "modules/video_capture/device_info_impl.h" +#include "modules/video_capture/linux/pipewire_session.h" + +namespace webrtc { +namespace videocapturemodule { +class DeviceInfoPipeWire : public DeviceInfoImpl { + public: + explicit DeviceInfoPipeWire(VideoCaptureOptions* options); + ~DeviceInfoPipeWire() override; + uint32_t NumberOfDevices() override; + int32_t GetDeviceName(uint32_t deviceNumber, + char* deviceNameUTF8, + uint32_t deviceNameLength, + char* deviceUniqueIdUTF8, + uint32_t deviceUniqueIdUTF8Length, + char* productUniqueIdUTF8 = nullptr, + uint32_t productUniqueIdUTF8Length = 0, + pid_t* pid = 0, + bool* deviceIsPlaceholder = 0) override; + /* + * Fills the membervariable _captureCapabilities with capabilites for the + * given device name. + */ + int32_t CreateCapabilityMap(const char* deviceUniqueIdUTF8) override + RTC_EXCLUSIVE_LOCKS_REQUIRED(_apiLock); + int32_t DisplayCaptureSettingsDialogBox(const char* /*deviceUniqueIdUTF8*/, + const char* /*dialogTitleUTF8*/, + void* /*parentWindow*/, + uint32_t /*positionX*/, + uint32_t /*positionY*/) override; + int32_t Init() override; + + private: + rtc::scoped_refptr<PipeWireSession> pipewire_session_; +}; +} // namespace videocapturemodule +} // namespace webrtc +#endif // MODULES_VIDEO_CAPTURE_LINUX_DEVICE_INFO_PIPEWIRE_H_ diff --git a/third_party/libwebrtc/modules/video_capture/linux/device_info_v4l2.cc b/third_party/libwebrtc/modules/video_capture/linux/device_info_v4l2.cc new file mode 100644 index 0000000000..eaeed26b7c --- /dev/null +++ b/third_party/libwebrtc/modules/video_capture/linux/device_info_v4l2.cc @@ -0,0 +1,528 @@ +/* + * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/video_capture/linux/device_info_v4l2.h" + +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <unistd.h> +// v4l includes +#if defined(__NetBSD__) || defined(__OpenBSD__) // WEBRTC_BSD +#include <sys/videoio.h> +#elif defined(__sun) +#include <sys/videodev2.h> +#else +#include <linux/videodev2.h> +#endif + +#include <vector> + +#include "modules/video_capture/video_capture.h" +#include "modules/video_capture/video_capture_defines.h" +#include "modules/video_capture/video_capture_impl.h" +#include "rtc_base/logging.h" + +// These defines are here to support building on kernel 3.16 which some +// downstream projects, e.g. Firefox, use. +// TODO(apehrson): Remove them and their undefs when no longer needed. +#ifndef V4L2_PIX_FMT_ABGR32 +#define ABGR32_OVERRIDE 1 +#define V4L2_PIX_FMT_ABGR32 v4l2_fourcc('A', 'R', '2', '4') +#endif + +#ifndef V4L2_PIX_FMT_ARGB32 +#define ARGB32_OVERRIDE 1 +#define V4L2_PIX_FMT_ARGB32 v4l2_fourcc('B', 'A', '2', '4') +#endif + +#ifndef V4L2_PIX_FMT_RGBA32 +#define RGBA32_OVERRIDE 1 +#define V4L2_PIX_FMT_RGBA32 v4l2_fourcc('A', 'B', '2', '4') +#endif + +#ifdef WEBRTC_LINUX +#define EVENT_SIZE ( sizeof (struct inotify_event) ) +#define BUF_LEN ( 1024 * ( EVENT_SIZE + 16 ) ) +#endif + +// These defines are here to support building on kernel 3.16 which some +// downstream projects, e.g. Firefox, use. +// TODO(apehrson): Remove them and their undefs when no longer needed. +#ifndef V4L2_PIX_FMT_ABGR32 +#define ABGR32_OVERRIDE 1 +#define V4L2_PIX_FMT_ABGR32 v4l2_fourcc('A', 'R', '2', '4') +#endif + +#ifndef V4L2_PIX_FMT_ARGB32 +#define ARGB32_OVERRIDE 1 +#define V4L2_PIX_FMT_ARGB32 v4l2_fourcc('B', 'A', '2', '4') +#endif + +#ifndef V4L2_PIX_FMT_RGBA32 +#define RGBA32_OVERRIDE 1 +#define V4L2_PIX_FMT_RGBA32 v4l2_fourcc('A', 'B', '2', '4') +#endif + +namespace webrtc { +namespace videocapturemodule { +#ifdef WEBRTC_LINUX +void DeviceInfoV4l2::HandleEvent(inotify_event* event, int fd) +{ + if (event->mask & IN_CREATE) { + if (fd == _fd_v4l) { + DeviceChange(); + } else if ((event->mask & IN_ISDIR) && (fd == _fd_dev)) { + if (_wd_v4l < 0) { + // Sometimes inotify_add_watch failed if we call it immediately after receiving this event + // Adding 5ms delay to let file system settle down + usleep(5*1000); + _wd_v4l = inotify_add_watch(_fd_v4l, "/dev/v4l/by-path/", IN_CREATE | IN_DELETE | IN_DELETE_SELF); + if (_wd_v4l >= 0) { + DeviceChange(); + } + } + } + } else if (event->mask & IN_DELETE) { + if (fd == _fd_v4l) { + DeviceChange(); + } + } else if (event->mask & IN_DELETE_SELF) { + if (fd == _fd_v4l) { + inotify_rm_watch(_fd_v4l, _wd_v4l); + _wd_v4l = -1; + } else { + assert(false); + } + } +} + +int DeviceInfoV4l2::EventCheck(int fd) +{ + struct pollfd fds = { + .fd = fd, + .events = POLLIN, + .revents = 0 + }; + + return poll(&fds, 1, 100); +} + +int DeviceInfoV4l2::HandleEvents(int fd) +{ + char buffer[BUF_LEN]; + + ssize_t r = read(fd, buffer, BUF_LEN); + + if (r <= 0) { + return r; + } + + ssize_t buffer_i = 0; + inotify_event* pevent; + size_t eventSize; + int count = 0; + + while (buffer_i < r) + { + pevent = (inotify_event *) (&buffer[buffer_i]); + eventSize = sizeof(inotify_event) + pevent->len; + char event[sizeof(inotify_event) + FILENAME_MAX + 1] // null-terminated + __attribute__ ((aligned(__alignof__(struct inotify_event)))); + + memcpy(event, pevent, eventSize); + + HandleEvent((inotify_event*)(event), fd); + + buffer_i += eventSize; + count++; + } + + return count; +} + +int DeviceInfoV4l2::ProcessInotifyEvents() +{ + while (!_isShutdown) { + if (EventCheck(_fd_dev) > 0) { + if (HandleEvents(_fd_dev) < 0) { + break; + } + } + if (EventCheck(_fd_v4l) > 0) { + if (HandleEvents(_fd_v4l) < 0) { + break; + } + } + } + return 0; +} + +void DeviceInfoV4l2::InotifyProcess() +{ + _fd_v4l = inotify_init(); + _fd_dev = inotify_init(); + if (_fd_v4l >= 0 && _fd_dev >= 0) { + _wd_v4l = inotify_add_watch(_fd_v4l, "/dev/v4l/by-path/", IN_CREATE | IN_DELETE | IN_DELETE_SELF); + _wd_dev = inotify_add_watch(_fd_dev, "/dev/", IN_CREATE); + ProcessInotifyEvents(); + + if (_wd_v4l >= 0) { + inotify_rm_watch(_fd_v4l, _wd_v4l); + } + + if (_wd_dev >= 0) { + inotify_rm_watch(_fd_dev, _wd_dev); + } + + close(_fd_v4l); + close(_fd_dev); + } +} +#endif + +DeviceInfoV4l2::DeviceInfoV4l2() : DeviceInfoImpl() +#ifdef WEBRTC_LINUX + , _isShutdown(false) +#endif +{ +#ifdef WEBRTC_LINUX + _inotifyEventThread = rtc::PlatformThread::SpawnJoinable( + [this] { + InotifyProcess(); + }, "InotifyEventThread"); +#endif +} + +int32_t DeviceInfoV4l2::Init() { + return 0; +} + +DeviceInfoV4l2::~DeviceInfoV4l2() { +#ifdef WEBRTC_LINUX + _isShutdown = true; + + if (!_inotifyEventThread.empty()) { + _inotifyEventThread.Finalize(); + } +#endif +} + +uint32_t DeviceInfoV4l2::NumberOfDevices() { + uint32_t count = 0; + char device[20]; + int fd = -1; + struct v4l2_capability cap; + + /* detect /dev/video [0-63]VideoCaptureModule entries */ + for (int n = 0; n < 64; n++) { + snprintf(device, sizeof(device), "/dev/video%d", n); + if ((fd = open(device, O_RDONLY)) != -1) { + // query device capabilities and make sure this is a video capture device + if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0 || !IsVideoCaptureDevice(&cap)) { + close(fd); + continue; + } + + close(fd); + count++; + } + } + + return count; +} + +int32_t DeviceInfoV4l2::GetDeviceName(uint32_t deviceNumber, + char* deviceNameUTF8, + uint32_t deviceNameLength, + char* deviceUniqueIdUTF8, + uint32_t deviceUniqueIdUTF8Length, + char* /*productUniqueIdUTF8*/, + uint32_t /*productUniqueIdUTF8Length*/, + pid_t* /*pid*/, + bool* /*deviceIsPlaceholder*/) { + // Travel through /dev/video [0-63] + uint32_t count = 0; + char device[20]; + int fd = -1; + bool found = false; + struct v4l2_capability cap; + for (int n = 0; n < 64; n++) { + snprintf(device, sizeof(device), "/dev/video%d", n); + if ((fd = open(device, O_RDONLY)) != -1) { + // query device capabilities and make sure this is a video capture device + if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0 || !IsVideoCaptureDevice(&cap)) { + close(fd); + continue; + } + if (count == deviceNumber) { + // Found the device + found = true; + break; + } else { + close(fd); + count++; + } + } + } + + if (!found) + return -1; + + // query device capabilities + if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) { + RTC_LOG(LS_INFO) << "error in querying the device capability for device " + << device << ". errno = " << errno; + close(fd); + return -1; + } + + close(fd); + + char cameraName[64]; + memset(deviceNameUTF8, 0, deviceNameLength); + memcpy(cameraName, cap.card, sizeof(cap.card)); + + if (deviceNameLength > strlen(cameraName)) { + memcpy(deviceNameUTF8, cameraName, strlen(cameraName)); + } else { + RTC_LOG(LS_INFO) << "buffer passed is too small"; + return -1; + } + + if (cap.bus_info[0] != 0) { // may not available in all drivers + // copy device id + size_t len = strlen(reinterpret_cast<const char*>(cap.bus_info)); + if (deviceUniqueIdUTF8Length > len) { + memset(deviceUniqueIdUTF8, 0, deviceUniqueIdUTF8Length); + memcpy(deviceUniqueIdUTF8, cap.bus_info, len); + } else { + RTC_LOG(LS_INFO) << "buffer passed is too small"; + return -1; + } + } + + return 0; +} + +int32_t DeviceInfoV4l2::CreateCapabilityMap(const char* deviceUniqueIdUTF8) { + int fd; + char device[32]; + bool found = false; + + const int32_t deviceUniqueIdUTF8Length = strlen(deviceUniqueIdUTF8); + if (deviceUniqueIdUTF8Length >= kVideoCaptureUniqueNameLength) { + RTC_LOG(LS_INFO) << "Device name too long"; + return -1; + } + RTC_LOG(LS_INFO) << "CreateCapabilityMap called for device " + << deviceUniqueIdUTF8; + + /* detect /dev/video [0-63] entries */ + for (int n = 0; n < 64; ++n) { + snprintf(device, sizeof(device), "/dev/video%d", n); + fd = open(device, O_RDONLY); + if (fd == -1) + continue; + + // query device capabilities + struct v4l2_capability cap; + if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == 0) { + // skip devices without video capture capability + if (!IsVideoCaptureDevice(&cap)) { + close(fd); + continue; + } + + if (cap.bus_info[0] != 0) { + if (strncmp(reinterpret_cast<const char*>(cap.bus_info), + deviceUniqueIdUTF8, + strlen(deviceUniqueIdUTF8)) == 0) { // match with device id + found = true; + break; // fd matches with device unique id supplied + } + } else { // match for device name + if (IsDeviceNameMatches(reinterpret_cast<const char*>(cap.card), + deviceUniqueIdUTF8)) { + found = true; + break; + } + } + } + close(fd); // close since this is not the matching device + } + + if (!found) { + RTC_LOG(LS_INFO) << "no matching device found"; + return -1; + } + + // now fd will point to the matching device + // reset old capability list. + _captureCapabilities.clear(); + + int size = FillCapabilities(fd); + close(fd); + + // Store the new used device name + _lastUsedDeviceNameLength = deviceUniqueIdUTF8Length; + _lastUsedDeviceName = reinterpret_cast<char*>( + realloc(_lastUsedDeviceName, _lastUsedDeviceNameLength + 1)); + memcpy(_lastUsedDeviceName, deviceUniqueIdUTF8, + _lastUsedDeviceNameLength + 1); + + RTC_LOG(LS_INFO) << "CreateCapabilityMap " << _captureCapabilities.size(); + + return size; +} + +int32_t DeviceInfoV4l2::DisplayCaptureSettingsDialogBox( + const char* /*deviceUniqueIdUTF8*/, + const char* /*dialogTitleUTF8*/, + void* /*parentWindow*/, + uint32_t /*positionX*/, + uint32_t /*positionY*/) { + return -1; +} + +bool DeviceInfoV4l2::IsDeviceNameMatches(const char* name, + const char* deviceUniqueIdUTF8) { + if (strncmp(deviceUniqueIdUTF8, name, strlen(name)) == 0) + return true; + return false; +} + +bool DeviceInfoV4l2::IsVideoCaptureDevice(struct v4l2_capability* cap) +{ + if (cap->capabilities & V4L2_CAP_DEVICE_CAPS) { + return cap->device_caps & V4L2_CAP_VIDEO_CAPTURE; + } else { + return cap->capabilities & V4L2_CAP_VIDEO_CAPTURE; + } +} + +int32_t DeviceInfoV4l2::FillCapabilities(int fd) { + // set image format + struct v4l2_format video_fmt; + memset(&video_fmt, 0, sizeof(struct v4l2_format)); + + video_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + video_fmt.fmt.pix.sizeimage = 0; + + unsigned int videoFormats[] = { + V4L2_PIX_FMT_MJPEG, V4L2_PIX_FMT_JPEG, V4L2_PIX_FMT_YUV420, + V4L2_PIX_FMT_YVU420, V4L2_PIX_FMT_YUYV, V4L2_PIX_FMT_UYVY, + V4L2_PIX_FMT_NV12, V4L2_PIX_FMT_BGR24, V4L2_PIX_FMT_RGB24, + V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_ABGR32, V4L2_PIX_FMT_ARGB32, + V4L2_PIX_FMT_RGBA32, V4L2_PIX_FMT_BGR32, V4L2_PIX_FMT_RGB32, + }; + constexpr int totalFmts = sizeof(videoFormats) / sizeof(unsigned int); + + int sizes = 13; + unsigned int size[][2] = {{128, 96}, {160, 120}, {176, 144}, {320, 240}, + {352, 288}, {640, 480}, {704, 576}, {800, 600}, + {960, 720}, {1280, 720}, {1024, 768}, {1440, 1080}, + {1920, 1080}}; + + for (int fmts = 0; fmts < totalFmts; fmts++) { + for (int i = 0; i < sizes; i++) { + video_fmt.fmt.pix.pixelformat = videoFormats[fmts]; + video_fmt.fmt.pix.width = size[i][0]; + video_fmt.fmt.pix.height = size[i][1]; + + if (ioctl(fd, VIDIOC_TRY_FMT, &video_fmt) >= 0) { + if ((video_fmt.fmt.pix.width == size[i][0]) && + (video_fmt.fmt.pix.height == size[i][1])) { + VideoCaptureCapability cap; + cap.width = video_fmt.fmt.pix.width; + cap.height = video_fmt.fmt.pix.height; + if (videoFormats[fmts] == V4L2_PIX_FMT_YUYV) { + cap.videoType = VideoType::kYUY2; + } else if (videoFormats[fmts] == V4L2_PIX_FMT_YUV420) { + cap.videoType = VideoType::kI420; + } else if (videoFormats[fmts] == V4L2_PIX_FMT_YVU420) { + cap.videoType = VideoType::kYV12; + } else if (videoFormats[fmts] == V4L2_PIX_FMT_MJPEG || + videoFormats[fmts] == V4L2_PIX_FMT_JPEG) { + cap.videoType = VideoType::kMJPEG; + } else if (videoFormats[fmts] == V4L2_PIX_FMT_UYVY) { + cap.videoType = VideoType::kUYVY; + } else if (videoFormats[fmts] == V4L2_PIX_FMT_NV12) { + cap.videoType = VideoType::kNV12; + } else if (videoFormats[fmts] == V4L2_PIX_FMT_BGR24) { + // NB that for RGB formats, `VideoType` follows naming conventions + // of libyuv[1], where e.g. the format for FOURCC "ARGB" stores + // pixels in BGRA order in memory. V4L2[2] on the other hand names + // its formats based on the order of the RGB components as stored in + // memory. Applies to all RGB formats below. + // [1]https://chromium.googlesource.com/libyuv/libyuv/+/refs/heads/main/docs/formats.md#the-argb-fourcc + // [2]https://www.kernel.org/doc/html/v6.2/userspace-api/media/v4l/pixfmt-rgb.html#bits-per-component + cap.videoType = VideoType::kRGB24; + } else if (videoFormats[fmts] == V4L2_PIX_FMT_RGB24) { + cap.videoType = VideoType::kBGR24; + } else if (videoFormats[fmts] == V4L2_PIX_FMT_RGB565) { + cap.videoType = VideoType::kRGB565; + } else if (videoFormats[fmts] == V4L2_PIX_FMT_ABGR32) { + cap.videoType = VideoType::kARGB; + } else if (videoFormats[fmts] == V4L2_PIX_FMT_ARGB32) { + cap.videoType = VideoType::kBGRA; + } else if (videoFormats[fmts] == V4L2_PIX_FMT_BGR32) { + cap.videoType = VideoType::kARGB; + } else if (videoFormats[fmts] == V4L2_PIX_FMT_RGB32) { + cap.videoType = VideoType::kBGRA; + } else if (videoFormats[fmts] == V4L2_PIX_FMT_RGBA32) { + cap.videoType = VideoType::kABGR; + } else { + RTC_DCHECK_NOTREACHED(); + } + + // get fps of current camera mode + // V4l2 does not have a stable method of knowing so we just guess. + if (cap.width >= 800 && cap.videoType != VideoType::kMJPEG) { + cap.maxFPS = 15; + } else { + cap.maxFPS = 30; + } + + _captureCapabilities.push_back(cap); + RTC_LOG(LS_VERBOSE) << "Camera capability, width:" << cap.width + << " height:" << cap.height + << " type:" << static_cast<int32_t>(cap.videoType) + << " fps:" << cap.maxFPS; + } + } + } + } + + RTC_LOG(LS_INFO) << "CreateCapabilityMap " << _captureCapabilities.size(); + return _captureCapabilities.size(); +} + +} // namespace videocapturemodule +} // namespace webrtc + +#ifdef ABGR32_OVERRIDE +#undef ABGR32_OVERRIDE +#undef V4L2_PIX_FMT_ABGR32 +#endif + +#ifdef ARGB32_OVERRIDE +#undef ARGB32_OVERRIDE +#undef V4L2_PIX_FMT_ARGB32 +#endif + +#ifdef RGBA32_OVERRIDE +#undef RGBA32_OVERRIDE +#undef V4L2_PIX_FMT_RGBA32 +#endif diff --git a/third_party/libwebrtc/modules/video_capture/linux/device_info_v4l2.h b/third_party/libwebrtc/modules/video_capture/linux/device_info_v4l2.h new file mode 100644 index 0000000000..55415845ad --- /dev/null +++ b/third_party/libwebrtc/modules/video_capture/linux/device_info_v4l2.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_VIDEO_CAPTURE_LINUX_DEVICE_INFO_V4L2_H_ +#define MODULES_VIDEO_CAPTURE_LINUX_DEVICE_INFO_V4L2_H_ + +#include <stdint.h> + +#include "modules/video_capture/device_info_impl.h" + +#include "rtc_base/platform_thread.h" +#ifdef WEBRTC_LINUX +#include <sys/inotify.h> +#endif + +struct v4l2_capability; + +namespace webrtc { +namespace videocapturemodule { +class DeviceInfoV4l2 : public DeviceInfoImpl { + public: + DeviceInfoV4l2(); + ~DeviceInfoV4l2() override; + uint32_t NumberOfDevices() override; + int32_t GetDeviceName(uint32_t deviceNumber, + char* deviceNameUTF8, + uint32_t deviceNameLength, + char* deviceUniqueIdUTF8, + uint32_t deviceUniqueIdUTF8Length, + char* productUniqueIdUTF8 = 0, + uint32_t productUniqueIdUTF8Length = 0, + pid_t* pid = 0, + bool* deviceIsPlaceholder = 0) override; + /* + * Fills the membervariable _captureCapabilities with capabilites for the + * given device name. + */ + int32_t CreateCapabilityMap(const char* deviceUniqueIdUTF8) override + RTC_EXCLUSIVE_LOCKS_REQUIRED(_apiLock); + int32_t DisplayCaptureSettingsDialogBox(const char* /*deviceUniqueIdUTF8*/, + const char* /*dialogTitleUTF8*/, + void* /*parentWindow*/, + uint32_t /*positionX*/, + uint32_t /*positionY*/) override; + int32_t FillCapabilities(int fd) RTC_EXCLUSIVE_LOCKS_REQUIRED(_apiLock); + int32_t Init() override; + + private: + bool IsDeviceNameMatches(const char* name, const char* deviceUniqueIdUTF8); + bool IsVideoCaptureDevice(struct v4l2_capability* cap); + +#ifdef WEBRTC_LINUX + void HandleEvent(inotify_event* event, int fd); + int EventCheck(int fd); + int HandleEvents(int fd); + int ProcessInotifyEvents(); + rtc::PlatformThread _inotifyEventThread; + void InotifyProcess(); + int _fd_v4l, _fd_dev, _wd_v4l, _wd_dev; /* accessed on InotifyEventThread thread */ + std::atomic<bool> _isShutdown; +#endif +}; +} // namespace videocapturemodule +} // namespace webrtc +#endif // MODULES_VIDEO_CAPTURE_LINUX_DEVICE_INFO_V4L2_H_ diff --git a/third_party/libwebrtc/modules/video_capture/linux/pipewire_session.cc b/third_party/libwebrtc/modules/video_capture/linux/pipewire_session.cc new file mode 100644 index 0000000000..4d1b200aca --- /dev/null +++ b/third_party/libwebrtc/modules/video_capture/linux/pipewire_session.cc @@ -0,0 +1,400 @@ +/* + * Copyright (c) 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/video_capture/linux/pipewire_session.h" + +#include <spa/monitor/device.h> +#include <spa/param/format-utils.h> +#include <spa/param/format.h> +#include <spa/param/video/raw.h> +#include <spa/pod/parser.h> + +#include "common_video/libyuv/include/webrtc_libyuv.h" +#include "modules/video_capture/device_info_impl.h" +#include "rtc_base/logging.h" +#include "rtc_base/string_encode.h" +#include "rtc_base/string_to_number.h" + +namespace webrtc { +namespace videocapturemodule { + +VideoType PipeWireRawFormatToVideoType(uint32_t id) { + switch (id) { + case SPA_VIDEO_FORMAT_I420: + return VideoType::kI420; + case SPA_VIDEO_FORMAT_NV12: + return VideoType::kNV12; + case SPA_VIDEO_FORMAT_YUY2: + return VideoType::kYUY2; + case SPA_VIDEO_FORMAT_UYVY: + return VideoType::kUYVY; + case SPA_VIDEO_FORMAT_RGB: + return VideoType::kRGB24; + default: + return VideoType::kUnknown; + } +} + +PipeWireNode::PipeWireNode(PipeWireSession* session, + uint32_t id, + const spa_dict* props) + : session_(session), + id_(id), + display_name_(spa_dict_lookup(props, PW_KEY_NODE_DESCRIPTION)), + unique_id_(rtc::ToString(id)) { + RTC_LOG(LS_VERBOSE) << "Found Camera: " << display_name_; + + proxy_ = static_cast<pw_proxy*>(pw_registry_bind( + session_->pw_registry_, id, PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, 0)); + + static const pw_node_events node_events{ + .version = PW_VERSION_NODE_EVENTS, + .info = OnNodeInfo, + .param = OnNodeParam, + }; + + pw_node_add_listener(proxy_, &node_listener_, &node_events, this); +} + +PipeWireNode::~PipeWireNode() { + pw_proxy_destroy(proxy_); + spa_hook_remove(&node_listener_); +} + +// static +void PipeWireNode::OnNodeInfo(void* data, const pw_node_info* info) { + PipeWireNode* that = static_cast<PipeWireNode*>(data); + + if (info->change_mask & PW_NODE_CHANGE_MASK_PROPS) { + const char* vid_str; + const char* pid_str; + absl::optional<int> vid; + absl::optional<int> pid; + + vid_str = spa_dict_lookup(info->props, SPA_KEY_DEVICE_VENDOR_ID); + pid_str = spa_dict_lookup(info->props, SPA_KEY_DEVICE_PRODUCT_ID); + vid = vid_str ? rtc::StringToNumber<int>(vid_str) : absl::nullopt; + pid = pid_str ? rtc::StringToNumber<int>(pid_str) : absl::nullopt; + + if (vid && pid) { + char model_str[10]; + snprintf(model_str, sizeof(model_str), "%04x:%04x", vid.value(), + pid.value()); + that->model_id_ = model_str; + } + } else if (info->change_mask & PW_NODE_CHANGE_MASK_PARAMS) { + for (uint32_t i = 0; i < info->n_params; i++) { + uint32_t id = info->params[i].id; + if (id == SPA_PARAM_EnumFormat && + info->params[i].flags & SPA_PARAM_INFO_READ) { + pw_node_enum_params(that->proxy_, 0, id, 0, UINT32_MAX, nullptr); + break; + } + } + that->session_->PipeWireSync(); + } +} + +// static +void PipeWireNode::OnNodeParam(void* data, + int seq, + uint32_t id, + uint32_t index, + uint32_t next, + const spa_pod* param) { + PipeWireNode* that = static_cast<PipeWireNode*>(data); + auto* obj = reinterpret_cast<const spa_pod_object*>(param); + const spa_pod_prop* prop = nullptr; + VideoCaptureCapability cap; + spa_pod* val; + uint32_t n_items, choice; + + cap.videoType = VideoType::kUnknown; + cap.maxFPS = 0; + + prop = spa_pod_object_find_prop(obj, prop, SPA_FORMAT_VIDEO_framerate); + if (prop) { + val = spa_pod_get_values(&prop->value, &n_items, &choice); + if (val->type == SPA_TYPE_Fraction) { + spa_fraction* fract; + + fract = static_cast<spa_fraction*>(SPA_POD_BODY(val)); + + if (choice == SPA_CHOICE_None) + cap.maxFPS = 1.0 * fract[0].num / fract[0].denom; + else if (choice == SPA_CHOICE_Range && fract[1].num > 0) + cap.maxFPS = 1.0 * fract[1].num / fract[1].denom; + } + } + + prop = spa_pod_object_find_prop(obj, prop, SPA_FORMAT_VIDEO_size); + if (!prop) + return; + + val = spa_pod_get_values(&prop->value, &n_items, &choice); + if (val->type != SPA_TYPE_Rectangle) + return; + + if (choice != SPA_CHOICE_None) + return; + + if (!ParseFormat(param, &cap)) + return; + + spa_rectangle* rect; + rect = static_cast<spa_rectangle*>(SPA_POD_BODY(val)); + cap.width = rect[0].width; + cap.height = rect[0].height; + + RTC_LOG(LS_VERBOSE) << "Found Format(" << that->display_name_ + << "): " << static_cast<int>(cap.videoType) << "(" + << cap.width << "x" << cap.height << "@" << cap.maxFPS + << ")"; + + that->capabilities_.push_back(cap); +} + +// static +bool PipeWireNode::ParseFormat(const spa_pod* param, + VideoCaptureCapability* cap) { + auto* obj = reinterpret_cast<const spa_pod_object*>(param); + uint32_t media_type, media_subtype; + + if (spa_format_parse(param, &media_type, &media_subtype) < 0) { + RTC_LOG(LS_ERROR) << "Failed to parse video format."; + return false; + } + + if (media_type != SPA_MEDIA_TYPE_video) + return false; + + if (media_subtype == SPA_MEDIA_SUBTYPE_raw) { + const spa_pod_prop* prop = nullptr; + uint32_t n_items, choice; + spa_pod* val; + uint32_t* id; + + prop = spa_pod_object_find_prop(obj, prop, SPA_FORMAT_VIDEO_format); + if (!prop) + return false; + + val = spa_pod_get_values(&prop->value, &n_items, &choice); + if (val->type != SPA_TYPE_Id) + return false; + + if (choice != SPA_CHOICE_None) + return false; + + id = static_cast<uint32_t*>(SPA_POD_BODY(val)); + + cap->videoType = PipeWireRawFormatToVideoType(id[0]); + if (cap->videoType == VideoType::kUnknown) { + RTC_LOG(LS_INFO) << "Unsupported PipeWire pixel format " << id[0]; + return false; + } + + } else if (media_subtype == SPA_MEDIA_SUBTYPE_mjpg) { + cap->videoType = VideoType::kMJPEG; + } else { + RTC_LOG(LS_INFO) << "Unsupported PipeWire media subtype " << media_subtype; + } + + return cap->videoType != VideoType::kUnknown; +} + +CameraPortalNotifier::CameraPortalNotifier(PipeWireSession* session) + : session_(session) {} + +void CameraPortalNotifier::OnCameraRequestResult( + xdg_portal::RequestResponse result, + int fd) { + if (result == xdg_portal::RequestResponse::kSuccess) { + session_->InitPipeWire(fd); + } else if (result == xdg_portal::RequestResponse::kUserCancelled) { + session_->Finish(VideoCaptureOptions::Status::DENIED); + } else { + session_->Finish(VideoCaptureOptions::Status::ERROR); + } +} + +PipeWireSession::PipeWireSession() + : status_(VideoCaptureOptions::Status::UNINITIALIZED) {} + +PipeWireSession::~PipeWireSession() { + Cleanup(); +} + +void PipeWireSession::Init(VideoCaptureOptions::Callback* callback, int fd) { + { + webrtc::MutexLock lock(&callback_lock_); + callback_ = callback; + } + + if (fd != kInvalidPipeWireFd) { + InitPipeWire(fd); + } else { + portal_notifier_ = std::make_unique<CameraPortalNotifier>(this); + portal_ = std::make_unique<CameraPortal>(portal_notifier_.get()); + portal_->Start(); + } +} + +void PipeWireSession::InitPipeWire(int fd) { + if (!InitializePipeWire()) + Finish(VideoCaptureOptions::Status::UNAVAILABLE); + + if (!StartPipeWire(fd)) + Finish(VideoCaptureOptions::Status::ERROR); +} + +bool PipeWireSession::StartPipeWire(int fd) { + pw_init(/*argc=*/nullptr, /*argv=*/nullptr); + + pw_main_loop_ = pw_thread_loop_new("pipewire-main-loop", nullptr); + + pw_context_ = + pw_context_new(pw_thread_loop_get_loop(pw_main_loop_), nullptr, 0); + if (!pw_context_) { + RTC_LOG(LS_ERROR) << "Failed to create PipeWire context"; + return false; + } + + pw_core_ = pw_context_connect_fd(pw_context_, fd, nullptr, 0); + if (!pw_core_) { + RTC_LOG(LS_ERROR) << "Failed to connect PipeWire context"; + return false; + } + + static const pw_core_events core_events{ + .version = PW_VERSION_CORE_EVENTS, + .done = &OnCoreDone, + .error = &OnCoreError, + }; + + pw_core_add_listener(pw_core_, &core_listener_, &core_events, this); + + static const pw_registry_events registry_events{ + .version = PW_VERSION_REGISTRY_EVENTS, + .global = OnRegistryGlobal, + .global_remove = OnRegistryGlobalRemove, + }; + + pw_registry_ = pw_core_get_registry(pw_core_, PW_VERSION_REGISTRY, 0); + pw_registry_add_listener(pw_registry_, ®istry_listener_, ®istry_events, + this); + + PipeWireSync(); + + if (pw_thread_loop_start(pw_main_loop_) < 0) { + RTC_LOG(LS_ERROR) << "Failed to start main PipeWire loop"; + return false; + } + + return true; +} + +void PipeWireSession::StopPipeWire() { + if (pw_main_loop_) + pw_thread_loop_stop(pw_main_loop_); + + if (pw_core_) { + pw_core_disconnect(pw_core_); + pw_core_ = nullptr; + } + + if (pw_context_) { + pw_context_destroy(pw_context_); + pw_context_ = nullptr; + } + + if (pw_main_loop_) { + pw_thread_loop_destroy(pw_main_loop_); + pw_main_loop_ = nullptr; + } +} + +void PipeWireSession::PipeWireSync() { + sync_seq_ = pw_core_sync(pw_core_, PW_ID_CORE, sync_seq_); +} + +// static +void PipeWireSession::OnCoreError(void* data, + uint32_t id, + int seq, + int res, + const char* message) { + RTC_LOG(LS_ERROR) << "PipeWire remote error: " << message; +} + +// static +void PipeWireSession::OnCoreDone(void* data, uint32_t id, int seq) { + PipeWireSession* that = static_cast<PipeWireSession*>(data); + + if (id == PW_ID_CORE) { + if (seq == that->sync_seq_) { + RTC_LOG(LS_VERBOSE) << "Enumerating PipeWire camera devices complete."; + that->Finish(VideoCaptureOptions::Status::SUCCESS); + } + } +} + +// static +void PipeWireSession::OnRegistryGlobal(void* data, + uint32_t id, + uint32_t permissions, + const char* type, + uint32_t version, + const spa_dict* props) { + PipeWireSession* that = static_cast<PipeWireSession*>(data); + + if (type != absl::string_view(PW_TYPE_INTERFACE_Node)) + return; + + if (!spa_dict_lookup(props, PW_KEY_NODE_DESCRIPTION)) + return; + + auto node_role = spa_dict_lookup(props, PW_KEY_MEDIA_ROLE); + if (!node_role || strcmp(node_role, "Camera")) + return; + + that->nodes_.emplace_back(that, id, props); + that->PipeWireSync(); +} + +// static +void PipeWireSession::OnRegistryGlobalRemove(void* data, uint32_t id) { + PipeWireSession* that = static_cast<PipeWireSession*>(data); + + for (auto it = that->nodes_.begin(); it != that->nodes().end(); ++it) { + if ((*it).id() == id) { + that->nodes_.erase(it); + break; + } + } +} + +void PipeWireSession::Finish(VideoCaptureOptions::Status status) { + webrtc::MutexLock lock(&callback_lock_); + + if (callback_) { + callback_->OnInitialized(status); + callback_ = nullptr; + } +} + +void PipeWireSession::Cleanup() { + webrtc::MutexLock lock(&callback_lock_); + callback_ = nullptr; + + StopPipeWire(); +} + +} // namespace videocapturemodule +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/video_capture/linux/pipewire_session.h b/third_party/libwebrtc/modules/video_capture/linux/pipewire_session.h new file mode 100644 index 0000000000..fdc06a6b2a --- /dev/null +++ b/third_party/libwebrtc/modules/video_capture/linux/pipewire_session.h @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_VIDEO_CAPTURE_LINUX_PIPEWIRE_SESSION_H_ +#define MODULES_VIDEO_CAPTURE_LINUX_PIPEWIRE_SESSION_H_ + +#include <pipewire/core.h> +#include <pipewire/pipewire.h> + +#include <deque> +#include <string> +#include <vector> + +#include "api/ref_counted_base.h" +#include "api/scoped_refptr.h" +#include "modules/portal/pipewire_utils.h" +#include "modules/video_capture/linux/camera_portal.h" +#include "modules/video_capture/video_capture.h" +#include "modules/video_capture/video_capture_options.h" +#include "rtc_base/synchronization/mutex.h" + +namespace webrtc { +namespace videocapturemodule { + +class PipeWireSession; +class VideoCaptureModulePipeWire; + +// PipeWireNode objects are the local representation of PipeWire node objects. +// The portal API ensured that only camera nodes are visible to the client. +// So they all represent one camera that is available via PipeWire. +class PipeWireNode { + public: + PipeWireNode(PipeWireSession* session, uint32_t id, const spa_dict* props); + ~PipeWireNode(); + + uint32_t id() const { return id_; } + std::string display_name() const { return display_name_; } + std::string unique_id() const { return unique_id_; } + std::string model_id() const { return model_id_; } + std::vector<VideoCaptureCapability> capabilities() const { + return capabilities_; + } + + private: + static void OnNodeInfo(void* data, const pw_node_info* info); + static void OnNodeParam(void* data, + int seq, + uint32_t id, + uint32_t index, + uint32_t next, + const spa_pod* param); + static bool ParseFormat(const spa_pod* param, VideoCaptureCapability* cap); + + pw_proxy* proxy_; + spa_hook node_listener_; + PipeWireSession* session_; + uint32_t id_; + std::string display_name_; + std::string unique_id_; + std::string model_id_; + std::vector<VideoCaptureCapability> capabilities_; +}; + +class CameraPortalNotifier : public CameraPortal::PortalNotifier { + public: + CameraPortalNotifier(PipeWireSession* session); + ~CameraPortalNotifier() = default; + + void OnCameraRequestResult(xdg_portal::RequestResponse result, + int fd) override; + + private: + PipeWireSession* session_; +}; + +class PipeWireSession : public rtc::RefCountedNonVirtual<PipeWireSession> { + public: + PipeWireSession(); + ~PipeWireSession(); + + void Init(VideoCaptureOptions::Callback* callback, + int fd = kInvalidPipeWireFd); + + const std::deque<PipeWireNode>& nodes() const { return nodes_; } + + friend class CameraPortalNotifier; + friend class PipeWireNode; + friend class VideoCaptureModulePipeWire; + + private: + void InitPipeWire(int fd); + bool StartPipeWire(int fd); + void StopPipeWire(); + void PipeWireSync(); + + static void OnCoreError(void* data, + uint32_t id, + int seq, + int res, + const char* message); + static void OnCoreDone(void* data, uint32_t id, int seq); + + static void OnRegistryGlobal(void* data, + uint32_t id, + uint32_t permissions, + const char* type, + uint32_t version, + const spa_dict* props); + static void OnRegistryGlobalRemove(void* data, uint32_t id); + + void Finish(VideoCaptureOptions::Status status); + void Cleanup(); + + webrtc::Mutex callback_lock_; + VideoCaptureOptions::Callback* callback_ RTC_GUARDED_BY(&callback_lock_) = + nullptr; + + VideoCaptureOptions::Status status_; + + struct pw_thread_loop* pw_main_loop_ = nullptr; + struct pw_context* pw_context_ = nullptr; + struct pw_core* pw_core_ = nullptr; + struct spa_hook core_listener_; + + struct pw_registry* pw_registry_ = nullptr; + struct spa_hook registry_listener_; + + int sync_seq_ = 0; + + std::deque<PipeWireNode> nodes_; + std::unique_ptr<CameraPortal> portal_; + std::unique_ptr<CameraPortalNotifier> portal_notifier_; +}; + +} // namespace videocapturemodule +} // namespace webrtc + +#endif // MODULES_VIDEO_CAPTURE_LINUX_PIPEWIRE_SESSION_H_ diff --git a/third_party/libwebrtc/modules/video_capture/linux/video_capture_linux.cc b/third_party/libwebrtc/modules/video_capture/linux/video_capture_linux.cc new file mode 100644 index 0000000000..b2c206d775 --- /dev/null +++ b/third_party/libwebrtc/modules/video_capture/linux/video_capture_linux.cc @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <stdio.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/select.h> +#include <time.h> +#include <unistd.h> +// v4l includes +#if defined(__NetBSD__) || defined(__OpenBSD__) // WEBRTC_BSD +#include <sys/videoio.h> +#elif defined(__sun) +#include <sys/videodev2.h> +#else +#include <linux/videodev2.h> +#endif + +#include <new> +#include <string> + +#include "api/scoped_refptr.h" +#include "media/base/video_common.h" +#if defined(WEBRTC_USE_PIPEWIRE) +#include "modules/video_capture/linux/video_capture_pipewire.h" +#endif +#include "modules/video_capture/linux/video_capture_v4l2.h" +#include "modules/video_capture/video_capture.h" +#include "modules/video_capture/video_capture_options.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace videocapturemodule { +rtc::scoped_refptr<VideoCaptureModule> VideoCaptureImpl::Create( + const char* deviceUniqueId) { + auto implementation = rtc::make_ref_counted<VideoCaptureModuleV4L2>(); + + if (implementation->Init(deviceUniqueId) != 0) + return nullptr; + + return implementation; +} + +rtc::scoped_refptr<VideoCaptureModule> VideoCaptureImpl::Create( + VideoCaptureOptions* options, + const char* deviceUniqueId) { +#if defined(WEBRTC_USE_PIPEWIRE) + if (options->allow_pipewire()) { + auto implementation = + rtc::make_ref_counted<VideoCaptureModulePipeWire>(options); + + if (implementation->Init(deviceUniqueId) == 0) + return implementation; + } +#endif + if (options->allow_v4l2()) { + auto implementation = rtc::make_ref_counted<VideoCaptureModuleV4L2>(); + + if (implementation->Init(deviceUniqueId) == 0) + return implementation; + } + return nullptr; +} +} // namespace videocapturemodule +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/video_capture/linux/video_capture_pipewire.cc b/third_party/libwebrtc/modules/video_capture/linux/video_capture_pipewire.cc new file mode 100644 index 0000000000..8af483636a --- /dev/null +++ b/third_party/libwebrtc/modules/video_capture/linux/video_capture_pipewire.cc @@ -0,0 +1,414 @@ +/* + * Copyright (c) 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/video_capture/linux/video_capture_pipewire.h" + +#include <spa/param/format.h> +#include <spa/param/video/format-utils.h> +#include <spa/pod/builder.h> +#include <spa/utils/result.h> + +#include <vector> + +#include "common_video/libyuv/include/webrtc_libyuv.h" +#include "modules/portal/pipewire_utils.h" +#include "rtc_base/logging.h" +#include "rtc_base/string_to_number.h" + +namespace webrtc { +namespace videocapturemodule { + +struct { + uint32_t spa_format; + VideoType video_type; +} constexpr kSupportedFormats[] = { + {SPA_VIDEO_FORMAT_I420, VideoType::kI420}, + {SPA_VIDEO_FORMAT_NV12, VideoType::kNV12}, + {SPA_VIDEO_FORMAT_YUY2, VideoType::kYUY2}, + {SPA_VIDEO_FORMAT_UYVY, VideoType::kUYVY}, + {SPA_VIDEO_FORMAT_RGB, VideoType::kRGB24}, +}; + +VideoType VideoCaptureModulePipeWire::PipeWireRawFormatToVideoType( + uint32_t spa_format) { + for (const auto& spa_and_pixel_format : kSupportedFormats) { + if (spa_and_pixel_format.spa_format == spa_format) + return spa_and_pixel_format.video_type; + } + RTC_LOG(LS_INFO) << "Unsupported pixel format: " << spa_format; + return VideoType::kUnknown; +} + +VideoCaptureModulePipeWire::VideoCaptureModulePipeWire( + VideoCaptureOptions* options) + : VideoCaptureImpl(), + session_(options->pipewire_session()), + initialized_(false), + started_(false) {} + +VideoCaptureModulePipeWire::~VideoCaptureModulePipeWire() { + RTC_DCHECK_RUN_ON(&api_checker_); + + StopCapture(); +} + +int32_t VideoCaptureModulePipeWire::Init(const char* deviceUniqueId) { + RTC_CHECK_RUNS_SERIALIZED(&capture_checker_); + RTC_DCHECK_RUN_ON(&api_checker_); + + absl::optional<int> id; + id = rtc::StringToNumber<int>(deviceUniqueId); + if (id == absl::nullopt) + return -1; + + node_id_ = id.value(); + + const int len = strlen(deviceUniqueId); + _deviceUniqueId = new (std::nothrow) char[len + 1]; + memcpy(_deviceUniqueId, deviceUniqueId, len + 1); + + return 0; +} + +static spa_pod* BuildFormat(spa_pod_builder* builder, + uint32_t format, + uint32_t width, + uint32_t height, + float frame_rate) { + spa_pod_frame frames[2]; + + spa_pod_builder_push_object(builder, &frames[0], SPA_TYPE_OBJECT_Format, + SPA_PARAM_EnumFormat); + spa_pod_builder_add(builder, SPA_FORMAT_mediaType, + SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, + SPA_POD_Id(format), 0); + + if (format == SPA_MEDIA_SUBTYPE_raw) { + spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_format, 0); + spa_pod_builder_push_choice(builder, &frames[1], SPA_CHOICE_Enum, 0); + spa_pod_builder_id(builder, kSupportedFormats[0].spa_format); + for (const auto& spa_and_pixel_format : kSupportedFormats) + spa_pod_builder_id(builder, spa_and_pixel_format.spa_format); + spa_pod_builder_pop(builder, &frames[1]); + } + + spa_rectangle preferred_size = spa_rectangle{width, height}; + spa_rectangle min_size = spa_rectangle{1, 1}; + spa_rectangle max_size = spa_rectangle{4096, 4096}; + spa_pod_builder_add( + builder, SPA_FORMAT_VIDEO_size, + SPA_POD_CHOICE_RANGE_Rectangle(&preferred_size, &min_size, &max_size), 0); + + spa_fraction preferred_frame_rate = + spa_fraction{static_cast<uint32_t>(frame_rate), 1}; + spa_fraction min_frame_rate = spa_fraction{0, 1}; + spa_fraction max_frame_rate = spa_fraction{INT32_MAX, 1}; + spa_pod_builder_add( + builder, SPA_FORMAT_VIDEO_framerate, + SPA_POD_CHOICE_RANGE_Fraction(&preferred_frame_rate, &min_frame_rate, + &max_frame_rate), + 0); + + return static_cast<spa_pod*>(spa_pod_builder_pop(builder, &frames[0])); +} + +int32_t VideoCaptureModulePipeWire::StartCapture( + const VideoCaptureCapability& capability) { + RTC_DCHECK_RUN_ON(&api_checker_); + + if (initialized_) { + if (capability == _requestedCapability) { + return 0; + } else { + StopCapture(); + } + } + + uint8_t buffer[1024] = {}; + + // We don't want members above to be guarded by capture_checker_ as + // it's meant to be for members that are accessed on the API thread + // only when we are not capturing. The code above can be called many + // times while sharing instance of VideoCapturePipeWire between + // websites and therefore it would not follow the requirements of this + // checker. + RTC_CHECK_RUNS_SERIALIZED(&capture_checker_); + PipeWireThreadLoopLock thread_loop_lock(session_->pw_main_loop_); + + RTC_LOG(LS_VERBOSE) << "Creating new PipeWire stream for node " << node_id_; + + pw_properties* reuse_props = + pw_properties_new_string("pipewire.client.reuse=1"); + stream_ = pw_stream_new(session_->pw_core_, "camera-stream", reuse_props); + + if (!stream_) { + RTC_LOG(LS_ERROR) << "Failed to create camera stream!"; + return -1; + } + + static const pw_stream_events stream_events{ + .version = PW_VERSION_STREAM_EVENTS, + .state_changed = &OnStreamStateChanged, + .param_changed = &OnStreamParamChanged, + .process = &OnStreamProcess, + }; + + pw_stream_add_listener(stream_, &stream_listener_, &stream_events, this); + + spa_pod_builder builder = spa_pod_builder{buffer, sizeof(buffer)}; + std::vector<const spa_pod*> params; + uint32_t width = capability.width; + uint32_t height = capability.height; + uint32_t frame_rate = capability.maxFPS; + bool prefer_jpeg = (width > 640) || (height > 480); + + params.push_back( + BuildFormat(&builder, SPA_MEDIA_SUBTYPE_raw, width, height, frame_rate)); + params.insert( + prefer_jpeg ? params.begin() : params.end(), + BuildFormat(&builder, SPA_MEDIA_SUBTYPE_mjpg, width, height, frame_rate)); + + int res = pw_stream_connect( + stream_, PW_DIRECTION_INPUT, node_id_, + static_cast<enum pw_stream_flags>(PW_STREAM_FLAG_AUTOCONNECT | + PW_STREAM_FLAG_DONT_RECONNECT | + PW_STREAM_FLAG_MAP_BUFFERS), + params.data(), params.size()); + if (res != 0) { + RTC_LOG(LS_ERROR) << "Could not connect to camera stream: " + << spa_strerror(res); + return -1; + } + + _requestedCapability = capability; + initialized_ = true; + + return 0; +} + +int32_t VideoCaptureModulePipeWire::StopCapture() { + RTC_DCHECK_RUN_ON(&api_checker_); + + PipeWireThreadLoopLock thread_loop_lock(session_->pw_main_loop_); + // PipeWireSession is guarded by API checker so just make sure we do + // race detection when the PipeWire loop is locked/stopped to not run + // any callback at this point. + RTC_CHECK_RUNS_SERIALIZED(&capture_checker_); + if (stream_) { + pw_stream_destroy(stream_); + stream_ = nullptr; + } + + _requestedCapability = VideoCaptureCapability(); + return 0; +} + +bool VideoCaptureModulePipeWire::CaptureStarted() { + RTC_DCHECK_RUN_ON(&api_checker_); + MutexLock lock(&api_lock_); + + return started_; +} + +int32_t VideoCaptureModulePipeWire::CaptureSettings( + VideoCaptureCapability& settings) { + RTC_DCHECK_RUN_ON(&api_checker_); + + settings = _requestedCapability; + + return 0; +} + +void VideoCaptureModulePipeWire::OnStreamParamChanged( + void* data, + uint32_t id, + const struct spa_pod* format) { + VideoCaptureModulePipeWire* that = + static_cast<VideoCaptureModulePipeWire*>(data); + RTC_DCHECK(that); + RTC_CHECK_RUNS_SERIALIZED(&that->capture_checker_); + + if (format && id == SPA_PARAM_Format) + that->OnFormatChanged(format); +} + +void VideoCaptureModulePipeWire::OnFormatChanged(const struct spa_pod* format) { + RTC_CHECK_RUNS_SERIALIZED(&capture_checker_); + + uint32_t media_type, media_subtype; + + if (spa_format_parse(format, &media_type, &media_subtype) < 0) { + RTC_LOG(LS_ERROR) << "Failed to parse video format."; + return; + } + + switch (media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: { + struct spa_video_info_raw f; + spa_format_video_raw_parse(format, &f); + configured_capability_.width = f.size.width; + configured_capability_.height = f.size.height; + configured_capability_.videoType = PipeWireRawFormatToVideoType(f.format); + configured_capability_.maxFPS = f.framerate.num / f.framerate.denom; + break; + } + case SPA_MEDIA_SUBTYPE_mjpg: { + struct spa_video_info_mjpg f; + spa_format_video_mjpg_parse(format, &f); + configured_capability_.width = f.size.width; + configured_capability_.height = f.size.height; + configured_capability_.videoType = VideoType::kMJPEG; + configured_capability_.maxFPS = f.framerate.num / f.framerate.denom; + break; + } + default: + configured_capability_.videoType = VideoType::kUnknown; + } + + if (configured_capability_.videoType == VideoType::kUnknown) { + RTC_LOG(LS_ERROR) << "Unsupported video format."; + return; + } + + RTC_LOG(LS_VERBOSE) << "Configured capture format = " + << static_cast<int>(configured_capability_.videoType); + + uint8_t buffer[1024] = {}; + auto builder = spa_pod_builder{buffer, sizeof(buffer)}; + + // Setup buffers and meta header for new format. + std::vector<const spa_pod*> params; + spa_pod_frame frame; + spa_pod_builder_push_object(&builder, &frame, SPA_TYPE_OBJECT_ParamBuffers, + SPA_PARAM_Buffers); + + if (media_subtype == SPA_MEDIA_SUBTYPE_raw) { + // Enforce stride without padding. + size_t stride; + switch (configured_capability_.videoType) { + case VideoType::kI420: + case VideoType::kNV12: + stride = configured_capability_.width; + break; + case VideoType::kYUY2: + case VideoType::kUYVY: + stride = configured_capability_.width * 2; + break; + case VideoType::kRGB24: + stride = configured_capability_.width * 3; + break; + default: + RTC_LOG(LS_ERROR) << "Unsupported video format."; + return; + } + spa_pod_builder_add(&builder, SPA_PARAM_BUFFERS_stride, SPA_POD_Int(stride), + 0); + } + + spa_pod_builder_add( + &builder, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 1, 32), + SPA_PARAM_BUFFERS_dataType, + SPA_POD_CHOICE_FLAGS_Int((1 << SPA_DATA_MemFd) | (1 << SPA_DATA_MemPtr)), + 0); + params.push_back( + static_cast<spa_pod*>(spa_pod_builder_pop(&builder, &frame))); + + params.push_back(reinterpret_cast<spa_pod*>(spa_pod_builder_add_object( + &builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, + SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, + SPA_POD_Int(sizeof(struct spa_meta_header))))); + params.push_back(reinterpret_cast<spa_pod*>(spa_pod_builder_add_object( + &builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, + SPA_POD_Id(SPA_META_VideoTransform), SPA_PARAM_META_size, + SPA_POD_Int(sizeof(struct spa_meta_videotransform))))); + pw_stream_update_params(stream_, params.data(), params.size()); +} + +void VideoCaptureModulePipeWire::OnStreamStateChanged( + void* data, + pw_stream_state old_state, + pw_stream_state state, + const char* error_message) { + VideoCaptureModulePipeWire* that = + static_cast<VideoCaptureModulePipeWire*>(data); + RTC_DCHECK(that); + + MutexLock lock(&that->api_lock_); + switch (state) { + case PW_STREAM_STATE_STREAMING: + that->started_ = true; + break; + case PW_STREAM_STATE_ERROR: + RTC_LOG(LS_ERROR) << "PipeWire stream state error: " << error_message; + [[fallthrough]]; + case PW_STREAM_STATE_PAUSED: + case PW_STREAM_STATE_UNCONNECTED: + case PW_STREAM_STATE_CONNECTING: + that->started_ = false; + break; + } + RTC_LOG(LS_VERBOSE) << "PipeWire stream state change: " + << pw_stream_state_as_string(old_state) << " -> " + << pw_stream_state_as_string(state); +} + +void VideoCaptureModulePipeWire::OnStreamProcess(void* data) { + VideoCaptureModulePipeWire* that = + static_cast<VideoCaptureModulePipeWire*>(data); + RTC_DCHECK(that); + RTC_CHECK_RUNS_SERIALIZED(&that->capture_checker_); + that->ProcessBuffers(); +} + +static VideoRotation VideorotationFromPipeWireTransform(uint32_t transform) { + switch (transform) { + case SPA_META_TRANSFORMATION_90: + return kVideoRotation_90; + case SPA_META_TRANSFORMATION_180: + return kVideoRotation_180; + case SPA_META_TRANSFORMATION_270: + return kVideoRotation_270; + default: + return kVideoRotation_0; + } +} + +void VideoCaptureModulePipeWire::ProcessBuffers() { + RTC_CHECK_RUNS_SERIALIZED(&capture_checker_); + + while (pw_buffer* buffer = pw_stream_dequeue_buffer(stream_)) { + struct spa_meta_header* h; + h = static_cast<struct spa_meta_header*>( + spa_buffer_find_meta_data(buffer->buffer, SPA_META_Header, sizeof(*h))); + + struct spa_meta_videotransform* videotransform; + videotransform = + static_cast<struct spa_meta_videotransform*>(spa_buffer_find_meta_data( + buffer->buffer, SPA_META_VideoTransform, sizeof(*videotransform))); + if (videotransform) { + VideoRotation rotation = + VideorotationFromPipeWireTransform(videotransform->transform); + SetCaptureRotation(rotation); + SetApplyRotation(rotation != kVideoRotation_0); + } + + if (h->flags & SPA_META_HEADER_FLAG_CORRUPTED) { + RTC_LOG(LS_INFO) << "Dropping corruped frame."; + } else { + IncomingFrame(static_cast<unsigned char*>(buffer->buffer->datas[0].data), + buffer->buffer->datas[0].chunk->size, + configured_capability_); + } + pw_stream_queue_buffer(stream_, buffer); + } +} + +} // namespace videocapturemodule +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/video_capture/linux/video_capture_pipewire.h b/third_party/libwebrtc/modules/video_capture/linux/video_capture_pipewire.h new file mode 100644 index 0000000000..eeb3b9497c --- /dev/null +++ b/third_party/libwebrtc/modules/video_capture/linux/video_capture_pipewire.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_VIDEO_CAPTURE_LINUX_VIDEO_CAPTURE_PIPEWIRE_H_ +#define MODULES_VIDEO_CAPTURE_LINUX_VIDEO_CAPTURE_PIPEWIRE_H_ + +#include "modules/video_capture/linux/pipewire_session.h" +#include "modules/video_capture/video_capture_defines.h" +#include "modules/video_capture/video_capture_impl.h" + +namespace webrtc { +namespace videocapturemodule { +class VideoCaptureModulePipeWire : public VideoCaptureImpl { + public: + explicit VideoCaptureModulePipeWire(VideoCaptureOptions* options); + ~VideoCaptureModulePipeWire() override; + int32_t Init(const char* deviceUniqueId); + int32_t StartCapture(const VideoCaptureCapability& capability) override; + int32_t StopCapture() override; + bool CaptureStarted() override; + int32_t CaptureSettings(VideoCaptureCapability& settings) override; + + static VideoType PipeWireRawFormatToVideoType(uint32_t format); + + private: + static void OnStreamParamChanged(void* data, + uint32_t id, + const struct spa_pod* format); + static void OnStreamStateChanged(void* data, + pw_stream_state old_state, + pw_stream_state state, + const char* error_message); + + static void OnStreamProcess(void* data); + + void OnFormatChanged(const struct spa_pod* format); + void ProcessBuffers(); + + const rtc::scoped_refptr<PipeWireSession> session_ + RTC_GUARDED_BY(api_checker_); + bool initialized_ RTC_GUARDED_BY(api_checker_); + bool started_ RTC_GUARDED_BY(api_lock_); + int node_id_ RTC_GUARDED_BY(capture_checker_); + VideoCaptureCapability configured_capability_ + RTC_GUARDED_BY(capture_checker_); + + struct pw_stream* stream_ RTC_GUARDED_BY(capture_checker_) = nullptr; + struct spa_hook stream_listener_ RTC_GUARDED_BY(capture_checker_); +}; +} // namespace videocapturemodule +} // namespace webrtc + +#endif // MODULES_VIDEO_CAPTURE_LINUX_VIDEO_CAPTURE_PIPEWIRE_H_ diff --git a/third_party/libwebrtc/modules/video_capture/linux/video_capture_v4l2.cc b/third_party/libwebrtc/modules/video_capture/linux/video_capture_v4l2.cc new file mode 100644 index 0000000000..c887683dc8 --- /dev/null +++ b/third_party/libwebrtc/modules/video_capture/linux/video_capture_v4l2.cc @@ -0,0 +1,485 @@ +/* + * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/video_capture/linux/video_capture_v4l2.h" + +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <stdio.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/select.h> +#include <time.h> +#include <unistd.h> +// v4l includes +#if defined(__NetBSD__) || defined(__OpenBSD__) // WEBRTC_BSD +#include <sys/videoio.h> +#elif defined(__sun) +#include <sys/videodev2.h> +#else +#include <linux/videodev2.h> +#endif + +#include <new> +#include <string> + +#include "api/scoped_refptr.h" +#include "media/base/video_common.h" +#include "modules/video_capture/video_capture.h" +#include "rtc_base/logging.h" + +// These defines are here to support building on kernel 3.16 which some +// downstream projects, e.g. Firefox, use. +// TODO(apehrson): Remove them and their undefs when no longer needed. +#ifndef V4L2_PIX_FMT_ABGR32 +#define ABGR32_OVERRIDE 1 +#define V4L2_PIX_FMT_ABGR32 v4l2_fourcc('A', 'R', '2', '4') +#endif + +#ifndef V4L2_PIX_FMT_ARGB32 +#define ARGB32_OVERRIDE 1 +#define V4L2_PIX_FMT_ARGB32 v4l2_fourcc('B', 'A', '2', '4') +#endif + +#ifndef V4L2_PIX_FMT_RGBA32 +#define RGBA32_OVERRIDE 1 +#define V4L2_PIX_FMT_RGBA32 v4l2_fourcc('A', 'B', '2', '4') +#endif + +namespace webrtc { +namespace videocapturemodule { +VideoCaptureModuleV4L2::VideoCaptureModuleV4L2() + : VideoCaptureImpl(), + _deviceId(-1), + _deviceFd(-1), + _buffersAllocatedByDevice(-1), + _captureStarted(false), + _pool(NULL) {} + +int32_t VideoCaptureModuleV4L2::Init(const char* deviceUniqueIdUTF8) { + RTC_DCHECK_RUN_ON(&api_checker_); + + int len = strlen((const char*)deviceUniqueIdUTF8); + _deviceUniqueId = new (std::nothrow) char[len + 1]; + if (_deviceUniqueId) { + memcpy(_deviceUniqueId, deviceUniqueIdUTF8, len + 1); + } + + int fd; + char device[32]; + bool found = false; + + /* detect /dev/video [0-63] entries */ + int n; + for (n = 0; n < 64; n++) { + snprintf(device, sizeof(device), "/dev/video%d", n); + if ((fd = open(device, O_RDONLY)) != -1) { + // query device capabilities + struct v4l2_capability cap; + if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == 0) { + if (cap.bus_info[0] != 0) { + if (strncmp((const char*)cap.bus_info, + (const char*)deviceUniqueIdUTF8, + strlen((const char*)deviceUniqueIdUTF8)) == + 0) { // match with device id + close(fd); + found = true; + break; // fd matches with device unique id supplied + } + } + } + close(fd); // close since this is not the matching device + } + } + if (!found) { + RTC_LOG(LS_INFO) << "no matching device found"; + return -1; + } + _deviceId = n; // store the device id + return 0; +} + +VideoCaptureModuleV4L2::~VideoCaptureModuleV4L2() { + RTC_DCHECK_RUN_ON(&api_checker_); + + StopCapture(); + if (_deviceFd != -1) + close(_deviceFd); +} + +int32_t VideoCaptureModuleV4L2::StartCapture( + const VideoCaptureCapability& capability) { + RTC_DCHECK_RUN_ON(&api_checker_); + + if (_captureStarted) { + if (capability == _requestedCapability) { + return 0; + } else { + StopCapture(); + } + } + + // Set a baseline of configured parameters. It is updated here during + // configuration, then read from the capture thread. + configured_capability_ = capability; + + MutexLock lock(&capture_lock_); + // first open /dev/video device + char device[20]; + snprintf(device, sizeof(device), "/dev/video%d", _deviceId); + + if ((_deviceFd = open(device, O_RDWR | O_NONBLOCK, 0)) < 0) { + RTC_LOG(LS_INFO) << "error in opening " << device << " errono = " << errno; + return -1; + } + + // Supported video formats in preferred order. + // If the requested resolution is larger than VGA, we prefer MJPEG. Go for + // I420 otherwise. + unsigned int hdFmts[] = { + V4L2_PIX_FMT_MJPEG, V4L2_PIX_FMT_YUV420, V4L2_PIX_FMT_YVU420, + V4L2_PIX_FMT_YUYV, V4L2_PIX_FMT_UYVY, V4L2_PIX_FMT_NV12, + V4L2_PIX_FMT_ABGR32, V4L2_PIX_FMT_ARGB32, V4L2_PIX_FMT_RGBA32, + V4L2_PIX_FMT_BGR32, V4L2_PIX_FMT_RGB32, V4L2_PIX_FMT_BGR24, + V4L2_PIX_FMT_RGB24, V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_JPEG, + }; + unsigned int sdFmts[] = { + V4L2_PIX_FMT_YUV420, V4L2_PIX_FMT_YVU420, V4L2_PIX_FMT_YUYV, + V4L2_PIX_FMT_UYVY, V4L2_PIX_FMT_NV12, V4L2_PIX_FMT_ABGR32, + V4L2_PIX_FMT_ARGB32, V4L2_PIX_FMT_RGBA32, V4L2_PIX_FMT_BGR32, + V4L2_PIX_FMT_RGB32, V4L2_PIX_FMT_BGR24, V4L2_PIX_FMT_RGB24, + V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_MJPEG, V4L2_PIX_FMT_JPEG, + }; + const bool isHd = capability.width > 640 || capability.height > 480; + unsigned int* fmts = isHd ? hdFmts : sdFmts; + static_assert(sizeof(hdFmts) == sizeof(sdFmts)); + constexpr int nFormats = sizeof(hdFmts) / sizeof(unsigned int); + + // Enumerate image formats. + struct v4l2_fmtdesc fmt; + int fmtsIdx = nFormats; + memset(&fmt, 0, sizeof(fmt)); + fmt.index = 0; + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + RTC_LOG(LS_INFO) << "Video Capture enumerats supported image formats:"; + while (ioctl(_deviceFd, VIDIOC_ENUM_FMT, &fmt) == 0) { + RTC_LOG(LS_INFO) << " { pixelformat = " + << cricket::GetFourccName(fmt.pixelformat) + << ", description = '" << fmt.description << "' }"; + // Match the preferred order. + for (int i = 0; i < nFormats; i++) { + if (fmt.pixelformat == fmts[i] && i < fmtsIdx) + fmtsIdx = i; + } + // Keep enumerating. + fmt.index++; + } + + if (fmtsIdx == nFormats) { + RTC_LOG(LS_INFO) << "no supporting video formats found"; + return -1; + } else { + RTC_LOG(LS_INFO) << "We prefer format " + << cricket::GetFourccName(fmts[fmtsIdx]); + } + + struct v4l2_format video_fmt; + memset(&video_fmt, 0, sizeof(struct v4l2_format)); + video_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + video_fmt.fmt.pix.sizeimage = 0; + video_fmt.fmt.pix.width = capability.width; + video_fmt.fmt.pix.height = capability.height; + video_fmt.fmt.pix.pixelformat = fmts[fmtsIdx]; + + if (video_fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) + configured_capability_.videoType = VideoType::kYUY2; + else if (video_fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) + configured_capability_.videoType = VideoType::kI420; + else if (video_fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_YVU420) + configured_capability_.videoType = VideoType::kYV12; + else if (video_fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_UYVY) + configured_capability_.videoType = VideoType::kUYVY; + else if (video_fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_NV12) + configured_capability_.videoType = VideoType::kNV12; + else if (video_fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_BGR24) + configured_capability_.videoType = VideoType::kRGB24; + else if (video_fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB24) + configured_capability_.videoType = VideoType::kBGR24; + else if (video_fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB565) + configured_capability_.videoType = VideoType::kRGB565; + else if (video_fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_ABGR32 || + video_fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_BGR32) + configured_capability_.videoType = VideoType::kARGB; + else if (video_fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_ARGB32 || + video_fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB32) + configured_capability_.videoType = VideoType::kBGRA; + else if (video_fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_RGBA32) + configured_capability_.videoType = VideoType::kABGR; + else if (video_fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG || + video_fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_JPEG) + configured_capability_.videoType = VideoType::kMJPEG; + else + RTC_DCHECK_NOTREACHED(); + + // set format and frame size now + if (ioctl(_deviceFd, VIDIOC_S_FMT, &video_fmt) < 0) { + RTC_LOG(LS_INFO) << "error in VIDIOC_S_FMT, errno = " << errno; + return -1; + } + + // initialize current width and height + configured_capability_.width = video_fmt.fmt.pix.width; + configured_capability_.height = video_fmt.fmt.pix.height; + + // Trying to set frame rate, before check driver capability. + bool driver_framerate_support = true; + struct v4l2_streamparm streamparms; + memset(&streamparms, 0, sizeof(streamparms)); + streamparms.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (ioctl(_deviceFd, VIDIOC_G_PARM, &streamparms) < 0) { + RTC_LOG(LS_INFO) << "error in VIDIOC_G_PARM errno = " << errno; + driver_framerate_support = false; + // continue + } else { + // check the capability flag is set to V4L2_CAP_TIMEPERFRAME. + if (streamparms.parm.capture.capability & V4L2_CAP_TIMEPERFRAME) { + // driver supports the feature. Set required framerate. + memset(&streamparms, 0, sizeof(streamparms)); + streamparms.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + streamparms.parm.capture.timeperframe.numerator = 1; + streamparms.parm.capture.timeperframe.denominator = capability.maxFPS; + if (ioctl(_deviceFd, VIDIOC_S_PARM, &streamparms) < 0) { + RTC_LOG(LS_INFO) << "Failed to set the framerate. errno=" << errno; + driver_framerate_support = false; + } + } + } + // If driver doesn't support framerate control, need to hardcode. + // Hardcoding the value based on the frame size. + if (!driver_framerate_support) { + if (configured_capability_.width >= 800 && + configured_capability_.videoType != VideoType::kMJPEG) { + configured_capability_.maxFPS = 15; + } else { + configured_capability_.maxFPS = 30; + } + } + + if (!AllocateVideoBuffers()) { + RTC_LOG(LS_INFO) << "failed to allocate video capture buffers"; + return -1; + } + + // Needed to start UVC camera - from the uvcview application + enum v4l2_buf_type type; + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (ioctl(_deviceFd, VIDIOC_STREAMON, &type) == -1) { + RTC_LOG(LS_INFO) << "Failed to turn on stream"; + return -1; + } + + _requestedCapability = capability; + _captureStarted = true; + + // start capture thread; + if (_captureThread.empty()) { + quit_ = false; + _captureThread = rtc::PlatformThread::SpawnJoinable( + [self = rtc::scoped_refptr(this)] { + while (self->CaptureProcess()) { + } + }, + "CaptureThread", + rtc::ThreadAttributes().SetPriority(rtc::ThreadPriority::kHigh)); + } + return 0; +} + +int32_t VideoCaptureModuleV4L2::StopCapture() { + RTC_DCHECK_RUN_ON(&api_checker_); + + if (!_captureThread.empty()) { + { + MutexLock lock(&capture_lock_); + quit_ = true; + } + // Make sure the capture thread stops using the mutex. + _captureThread.Finalize(); + } + + MutexLock lock(&capture_lock_); + if (_captureStarted) { + _captureStarted = false; + + DeAllocateVideoBuffers(); + close(_deviceFd); + _deviceFd = -1; + + _requestedCapability = configured_capability_ = VideoCaptureCapability(); + } + + return 0; +} + +// critical section protected by the caller + +bool VideoCaptureModuleV4L2::AllocateVideoBuffers() { + struct v4l2_requestbuffers rbuffer; + memset(&rbuffer, 0, sizeof(v4l2_requestbuffers)); + + rbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + rbuffer.memory = V4L2_MEMORY_MMAP; + rbuffer.count = kNoOfV4L2Bufffers; + + if (ioctl(_deviceFd, VIDIOC_REQBUFS, &rbuffer) < 0) { + RTC_LOG(LS_INFO) << "Could not get buffers from device. errno = " << errno; + return false; + } + + if (rbuffer.count > kNoOfV4L2Bufffers) + rbuffer.count = kNoOfV4L2Bufffers; + + _buffersAllocatedByDevice = rbuffer.count; + + // Map the buffers + _pool = new Buffer[rbuffer.count]; + + for (unsigned int i = 0; i < rbuffer.count; i++) { + struct v4l2_buffer buffer; + memset(&buffer, 0, sizeof(v4l2_buffer)); + buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buffer.memory = V4L2_MEMORY_MMAP; + buffer.index = i; + + if (ioctl(_deviceFd, VIDIOC_QUERYBUF, &buffer) < 0) { + return false; + } + + _pool[i].start = mmap(NULL, buffer.length, PROT_READ | PROT_WRITE, + MAP_SHARED, _deviceFd, buffer.m.offset); + + if (MAP_FAILED == _pool[i].start) { + for (unsigned int j = 0; j < i; j++) + munmap(_pool[j].start, _pool[j].length); + return false; + } + + _pool[i].length = buffer.length; + + if (ioctl(_deviceFd, VIDIOC_QBUF, &buffer) < 0) { + return false; + } + } + return true; +} + +bool VideoCaptureModuleV4L2::DeAllocateVideoBuffers() { + // unmap buffers + for (int i = 0; i < _buffersAllocatedByDevice; i++) + munmap(_pool[i].start, _pool[i].length); + + delete[] _pool; + + // turn off stream + enum v4l2_buf_type type; + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (ioctl(_deviceFd, VIDIOC_STREAMOFF, &type) < 0) { + RTC_LOG(LS_INFO) << "VIDIOC_STREAMOFF error. errno: " << errno; + } + + return true; +} + +bool VideoCaptureModuleV4L2::CaptureStarted() { + return _captureStarted; +} + +bool VideoCaptureModuleV4L2::CaptureProcess() { + + int retVal = 0; + struct pollfd rSet; + + rSet.fd = _deviceFd; + rSet.events = POLLIN; + rSet.revents = 0; + + retVal = poll(&rSet, 1, 1000); + + { + MutexLock lock(&capture_lock_); + + if (quit_) { + return false; + } + + if (retVal < 0 && errno != EINTR) { // continue if interrupted + // poll failed + return false; + } else if (retVal == 0) { + // poll timed out + return true; + } else if (!(rSet.revents & POLLIN)) { + // not event on camera handle + return true; + } + + if (_captureStarted) { + struct v4l2_buffer buf; + memset(&buf, 0, sizeof(struct v4l2_buffer)); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + // dequeue a buffer - repeat until dequeued properly! + while (ioctl(_deviceFd, VIDIOC_DQBUF, &buf) < 0) { + if (errno != EINTR) { + RTC_LOG(LS_INFO) << "could not sync on a buffer on device " + << strerror(errno); + return true; + } + } + + // convert to to I420 if needed + IncomingFrame(reinterpret_cast<uint8_t*>(_pool[buf.index].start), + buf.bytesused, configured_capability_); + // enqueue the buffer again + if (ioctl(_deviceFd, VIDIOC_QBUF, &buf) == -1) { + RTC_LOG(LS_INFO) << "Failed to enqueue capture buffer"; + } + } + } + usleep(0); + return true; +} + +int32_t VideoCaptureModuleV4L2::CaptureSettings( + VideoCaptureCapability& settings) { + RTC_DCHECK_RUN_ON(&api_checker_); + settings = _requestedCapability; + + return 0; +} +} // namespace videocapturemodule +} // namespace webrtc + +#ifdef ABGR32_OVERRIDE +#undef ABGR32_OVERRIDE +#undef V4L2_PIX_FMT_ABGR32 +#endif + +#ifdef ARGB32_OVERRIDE +#undef ARGB32_OVERRIDE +#undef V4L2_PIX_FMT_ARGB32 +#endif + +#ifdef RGBA32_OVERRIDE +#undef RGBA32_OVERRIDE +#undef V4L2_PIX_FMT_RGBA32 +#endif diff --git a/third_party/libwebrtc/modules/video_capture/linux/video_capture_v4l2.h b/third_party/libwebrtc/modules/video_capture/linux/video_capture_v4l2.h new file mode 100644 index 0000000000..61358d0325 --- /dev/null +++ b/third_party/libwebrtc/modules/video_capture/linux/video_capture_v4l2.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_VIDEO_CAPTURE_LINUX_VIDEO_CAPTURE_V4L2_H_ +#define MODULES_VIDEO_CAPTURE_LINUX_VIDEO_CAPTURE_V4L2_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <memory> + +#include "modules/video_capture/video_capture_defines.h" +#include "modules/video_capture/video_capture_impl.h" +#include "rtc_base/platform_thread.h" +#include "rtc_base/synchronization/mutex.h" + +namespace webrtc { +namespace videocapturemodule { +class VideoCaptureModuleV4L2 : public VideoCaptureImpl { + public: + VideoCaptureModuleV4L2(); + ~VideoCaptureModuleV4L2() override; + int32_t Init(const char* deviceUniqueId); + int32_t StartCapture(const VideoCaptureCapability& capability) override; + int32_t StopCapture() override; + bool CaptureStarted() override; + int32_t CaptureSettings(VideoCaptureCapability& settings) override; + + private: + enum { kNoOfV4L2Bufffers = 4 }; + + static void CaptureThread(void*); + bool CaptureProcess(); + bool AllocateVideoBuffers() RTC_EXCLUSIVE_LOCKS_REQUIRED(capture_lock_); + bool DeAllocateVideoBuffers() RTC_EXCLUSIVE_LOCKS_REQUIRED(capture_lock_); + + rtc::PlatformThread _captureThread RTC_GUARDED_BY(api_checker_); + Mutex capture_lock_ RTC_ACQUIRED_BEFORE(api_lock_); + bool quit_ RTC_GUARDED_BY(capture_lock_); + int32_t _deviceId RTC_GUARDED_BY(api_checker_); + int32_t _deviceFd; + + int32_t _buffersAllocatedByDevice RTC_GUARDED_BY(capture_lock_); + VideoCaptureCapability configured_capability_; + bool _captureStarted; + struct Buffer { + void* start; + size_t length; + }; + Buffer* _pool RTC_GUARDED_BY(capture_lock_); +}; +} // namespace videocapturemodule +} // namespace webrtc + +#endif // MODULES_VIDEO_CAPTURE_LINUX_VIDEO_CAPTURE_V4L2_H_ |