diff --git a/modules/desktop_capture/BUILD.gn b/modules/desktop_capture/BUILD.gn index 91c8d0f456..787a5c83b0 100644 --- a/modules/desktop_capture/BUILD.gn +++ b/modules/desktop_capture/BUILD.gn @@ -222,6 +222,23 @@ if (is_linux || is_chromeos) { } } + pkg_config("gbm") { + packages = [ "gbm" ] + } + pkg_config("egl") { + packages = [ "egl" ] + } + pkg_config("epoxy") { + packages = [ "epoxy" ] + ignore_libs = true + } + pkg_config("libdrm") { + packages = [ "libdrm" ] + if (!rtc_link_pipewire) { + ignore_libs = true + } + } + if (!rtc_link_pipewire) { # When libpipewire is not directly linked, use stubs to allow for dlopening of # the binary. @@ -229,6 +246,7 @@ if (is_linux || is_chromeos) { configs = [ "../../:common_config", ":pipewire", + ":libdrm", ] deps = [ "../../rtc_base" ] extra_header = "linux/pipewire_stub_header.fragment" @@ -236,7 +254,10 @@ if (is_linux || is_chromeos) { logging_include = "rtc_base/logging.h" output_name = "linux/pipewire_stubs" path_from_source = "modules/desktop_capture/linux" - sigs = [ "linux/pipewire03.sigs" ] + sigs = [ + "linux/pipewire.sigs", + "linux/drm.sigs", + ] } } @@ -542,17 +563,25 @@ rtc_library("desktop_capture_generic") { sources += [ "linux/base_capturer_pipewire.cc", "linux/base_capturer_pipewire.h", + "linux/egl_dmabuf.cc", + "linux/egl_dmabuf.h", ] configs += [ ":pipewire_config", ":gio", ":pipewire", + ":gbm", + ":egl", + ":epoxy", + ":libdrm", ] if (!rtc_link_pipewire) { deps += [ ":pipewire_stubs" ] } + + deps += [ "../../rtc_base:sanitizer" ] } if (rtc_enable_win_wgc) { diff --git a/modules/desktop_capture/linux/base_capturer_pipewire.cc b/modules/desktop_capture/linux/base_capturer_pipewire.cc index 8c3977278d..2d5e973387 100644 --- a/modules/desktop_capture/linux/base_capturer_pipewire.cc +++ b/modules/desktop_capture/linux/base_capturer_pipewire.cc @@ -14,12 +14,13 @@ #include #include #include - #include #include #include +#include #include +#include #include #include "absl/memory/memory.h" @@ -27,11 +28,13 @@ #include "modules/desktop_capture/desktop_capturer.h" #include "rtc_base/checks.h" #include "rtc_base/logging.h" +#include "rtc_base/string_encode.h" #if defined(WEBRTC_DLOPEN_PIPEWIRE) #include "modules/desktop_capture/linux/pipewire_stubs.h" using modules_desktop_capture_linux::InitializeStubs; -using modules_desktop_capture_linux::kModulePipewire03; +using modules_desktop_capture_linux::kModuleDrm; +using modules_desktop_capture_linux::kModulePipewire; using modules_desktop_capture_linux::StubPathMap; #endif // defined(WEBRTC_DLOPEN_PIPEWIRE) @@ -49,67 +52,116 @@ const int kBytesPerPixel = 4; #if defined(WEBRTC_DLOPEN_PIPEWIRE) const char kPipeWireLib[] = "libpipewire-0.3.so.0"; +const char kDrmLib[] = "libdrm.so.2"; #endif -// static -struct dma_buf_sync { - uint64_t flags; +#if !PW_CHECK_VERSION(0, 3, 29) +#define SPA_POD_PROP_FLAG_MANDATORY (1u << 3) +#endif +#if !PW_CHECK_VERSION(0, 3, 33) +#define SPA_POD_PROP_FLAG_DONT_FIXATE (1u << 4) +#endif + +struct pw_version { + int major = 0; + int minor = 0; + int micro = 0; }; -#define DMA_BUF_SYNC_READ (1 << 0) -#define DMA_BUF_SYNC_START (0 << 2) -#define DMA_BUF_SYNC_END (1 << 2) -#define DMA_BUF_BASE 'b' -#define DMA_BUF_IOCTL_SYNC _IOW(DMA_BUF_BASE, 0, struct dma_buf_sync) -static void SyncDmaBuf(int fd, uint64_t start_or_end) { - struct dma_buf_sync sync = {0}; +bool CheckPipeWireVersion(pw_version required_version) { + std::vector parsed_version; + std::string version_string = pw_get_library_version(); + rtc::split(version_string, '.', &parsed_version); - sync.flags = start_or_end | DMA_BUF_SYNC_READ; - - while (true) { - int ret; - ret = ioctl(fd, DMA_BUF_IOCTL_SYNC, &sync); - if (ret == -1 && errno == EINTR) { - continue; - } else if (ret == -1) { - RTC_LOG(LS_ERROR) << "Failed to synchronize DMA buffer: " - << g_strerror(errno); - break; - } else { - break; - } + if (parsed_version.size() != 3) { + return false; } + + pw_version current_version = {std::stoi(parsed_version.at(0)), + std::stoi(parsed_version.at(1)), + std::stoi(parsed_version.at(2))}; + + return (current_version.major > required_version.major) || + (current_version.major == required_version.major && + current_version.minor > required_version.minor) || + (current_version.major == required_version.major && + current_version.minor == required_version.minor && + current_version.micro >= required_version.micro); +} + +spa_pod* BuildFormat(spa_pod_builder* builder, + uint32_t format, + const std::vector& modifiers) { + bool first = true; + spa_pod_frame frames[2]; + spa_rectangle pw_min_screen_bounds = spa_rectangle{1, 1}; + spa_rectangle pw_max_screen_bounds = spa_rectangle{UINT32_MAX, UINT32_MAX}; + + 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), 0); + spa_pod_builder_add(builder, SPA_FORMAT_mediaSubtype, + SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); + spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0); + + if (modifiers.size()) { + // SPA_POD_PROP_FLAG_DONT_FIXATE can be used with PipeWire >= 0.3.33 + if (CheckPipeWireVersion(pw_version{0, 3, 33})) { + spa_pod_builder_prop( + builder, SPA_FORMAT_VIDEO_modifier, + SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE); + } else { + spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_modifier, + SPA_POD_PROP_FLAG_MANDATORY); + } + spa_pod_builder_push_choice(builder, &frames[1], SPA_CHOICE_Enum, 0); + // modifiers from the array + for (int64_t val : modifiers) { + spa_pod_builder_long(builder, val); + // Add the first modifier twice as the very first value is the default + // option + if (first) { + spa_pod_builder_long(builder, val); + first = false; + } + } + spa_pod_builder_pop(builder, &frames[1]); + } + + spa_pod_builder_add( + builder, SPA_FORMAT_VIDEO_size, + SPA_POD_CHOICE_RANGE_Rectangle( + &pw_min_screen_bounds, &pw_min_screen_bounds, &pw_max_screen_bounds), + 0); + + return static_cast(spa_pod_builder_pop(builder, &frames[0])); } class ScopedBuf { public: ScopedBuf() {} - ScopedBuf(unsigned char* map, int map_size, bool is_dma_buf, int fd) - : map_(map), map_size_(map_size), is_dma_buf_(is_dma_buf), fd_(fd) {} + ScopedBuf(uint8_t* map, int map_size, int fd) + : map_(map), map_size_(map_size), fd_(fd) {} ~ScopedBuf() { if (map_ != MAP_FAILED) { - if (is_dma_buf_) { - SyncDmaBuf(fd_, DMA_BUF_SYNC_END); - } munmap(map_, map_size_); } } operator bool() { return map_ != MAP_FAILED; } - void initialize(unsigned char* map, int map_size, bool is_dma_buf, int fd) { + void initialize(uint8_t* map, int map_size, int fd) { map_ = map; map_size_ = map_size; - is_dma_buf_ = is_dma_buf; fd_ = fd; } - unsigned char* get() { return map_; } + uint8_t* get() { return map_; } protected: - unsigned char* map_ = nullptr; + uint8_t* map_ = static_cast(MAP_FAILED); int map_size_; - bool is_dma_buf_; int fd_; }; @@ -234,17 +286,26 @@ void BaseCapturerPipeWire::OnStreamParamChanged(void* data, auto size = height * stride; that->desktop_size_ = DesktopSize(width, height); +#if PW_CHECK_VERSION(0, 3, 0) + that->modifier_ = that->spa_video_format_.modifier; +#endif uint8_t buffer[1024] = {}; auto builder = spa_pod_builder{buffer, sizeof(buffer)}; // Setup buffers and meta header for new format. const struct spa_pod* params[3]; + const int buffer_types = + spa_pod_find_prop(format, nullptr, SPA_FORMAT_VIDEO_modifier) + ? (1 << SPA_DATA_DmaBuf) | (1 << SPA_DATA_MemFd) | + (1 << SPA_DATA_MemPtr) + : (1 << SPA_DATA_MemFd) | (1 << SPA_DATA_MemPtr); params[0] = reinterpret_cast(spa_pod_builder_add_object( &builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_size, SPA_POD_Int(size), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(stride), SPA_PARAM_BUFFERS_buffers, - SPA_POD_CHOICE_RANGE_Int(8, 1, 32))); + SPA_POD_CHOICE_RANGE_Int(8, 1, 32), SPA_PARAM_BUFFERS_dataType, + SPA_POD_CHOICE_FLAGS_Int(buffer_types))); params[1] = reinterpret_cast(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, @@ -363,12 +424,13 @@ void BaseCapturerPipeWire::InitPortal() { reinterpret_cast(OnProxyRequested), this); } -void BaseCapturerPipeWire::InitPipeWire() { +void BaseCapturerPipeWire::Init() { #if defined(WEBRTC_DLOPEN_PIPEWIRE) StubPathMap paths; - // Check if the PipeWire library is available. - paths[kModulePipewire03].push_back(kPipeWireLib); + // Check if the PipeWire and DRM libraries are available. + paths[kModulePipewire].push_back(kPipeWireLib); + paths[kModuleDrm].push_back(kDrmLib); if (!InitializeStubs(paths)) { RTC_LOG(LS_ERROR) << "Failed to load the PipeWire library and symbols."; portal_init_failed_ = true; @@ -376,6 +438,8 @@ void BaseCapturerPipeWire::InitPipeWire() { } #endif // defined(WEBRTC_DLOPEN_PIPEWIRE) + egl_dmabuf_ = std::make_unique(); + pw_init(/*argc=*/nullptr, /*argc=*/nullptr); pw_main_loop_ = pw_thread_loop_new("pipewire-main-loop", nullptr); @@ -423,34 +487,37 @@ void BaseCapturerPipeWire::InitPipeWire() { } pw_stream* BaseCapturerPipeWire::CreateReceivingStream() { - spa_rectangle pwMinScreenBounds = spa_rectangle{1, 1}; - spa_rectangle pwMaxScreenBounds = spa_rectangle{UINT32_MAX, UINT32_MAX}; - pw_properties* reuseProps = pw_properties_new_string("pipewire.client.reuse=1"); auto stream = pw_stream_new(pw_core_, "webrtc-consume-stream", reuseProps); - uint8_t buffer[1024] = {}; - const spa_pod* params[1]; + uint8_t buffer[2048] = {}; + std::vector modifiers; + spa_pod_builder builder = spa_pod_builder{buffer, sizeof(buffer)}; - params[0] = reinterpret_cast(spa_pod_builder_add_object( - &builder, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, - SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), - SPA_FORMAT_VIDEO_format, - SPA_POD_CHOICE_ENUM_Id(5, SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_RGBx, - SPA_VIDEO_FORMAT_RGBA, SPA_VIDEO_FORMAT_BGRx, - SPA_VIDEO_FORMAT_BGRA), - SPA_FORMAT_VIDEO_size, - SPA_POD_CHOICE_RANGE_Rectangle(&pwMinScreenBounds, &pwMinScreenBounds, - &pwMaxScreenBounds), - 0)); + std::vector params; + const bool has_required_pw_version = + CheckPipeWireVersion(pw_version{0, 3, 29}); + for (uint32_t format : {SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_RGBA, + SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_RGBx}) { + // Modifiers can be used with PipeWire >= 0.3.29 + if (has_required_pw_version) { + modifiers = egl_dmabuf_->QueryDmaBufModifiers(format); + + if (!modifiers.empty()) { + params.push_back(BuildFormat(&builder, format, modifiers)); + } + } + + params.push_back(BuildFormat(&builder, format, /*modifiers=*/{})); + } pw_stream_add_listener(stream, &spa_stream_listener_, &pw_stream_events_, this); if (pw_stream_connect(stream, PW_DIRECTION_INPUT, pw_stream_node_id_, - PW_STREAM_FLAG_AUTOCONNECT, params, 1) != 0) { + PW_STREAM_FLAG_AUTOCONNECT, params.data(), + params.size()) != 0) { RTC_LOG(LS_ERROR) << "Could not connect receiving stream."; portal_init_failed_ = true; return nullptr; @@ -460,25 +527,26 @@ pw_stream* BaseCapturerPipeWire::CreateReceivingStream() { } void BaseCapturerPipeWire::HandleBuffer(pw_buffer* buffer) { - spa_buffer* spaBuffer = buffer->buffer; + spa_buffer* spa_buffer = buffer->buffer; ScopedBuf map; + std::unique_ptr src_unique_ptr; uint8_t* src = nullptr; - if (spaBuffer->datas[0].chunk->size == 0) { + if (spa_buffer->datas[0].chunk->size == 0) { RTC_LOG(LS_ERROR) << "Failed to get video stream: Zero size."; return; } - if (spaBuffer->datas[0].type == SPA_DATA_MemFd || - spaBuffer->datas[0].type == SPA_DATA_DmaBuf) { + std::function cleanup; + const int32_t src_stride = spa_buffer->datas[0].chunk->stride; + if (spa_buffer->datas[0].type == SPA_DATA_MemFd) { map.initialize( static_cast( mmap(nullptr, - spaBuffer->datas[0].maxsize + spaBuffer->datas[0].mapoffset, - PROT_READ, MAP_PRIVATE, spaBuffer->datas[0].fd, 0)), - spaBuffer->datas[0].maxsize + spaBuffer->datas[0].mapoffset, - spaBuffer->datas[0].type == SPA_DATA_DmaBuf, - spaBuffer->datas[0].fd); + spa_buffer->datas[0].maxsize + spa_buffer->datas[0].mapoffset, + PROT_READ, MAP_PRIVATE, spa_buffer->datas[0].fd, 0)), + spa_buffer->datas[0].maxsize + spa_buffer->datas[0].mapoffset, + spa_buffer->datas[0].fd); if (!map) { RTC_LOG(LS_ERROR) << "Failed to mmap the memory: " @@ -486,13 +554,25 @@ void BaseCapturerPipeWire::HandleBuffer(pw_buffer* buffer) { return; } - if (spaBuffer->datas[0].type == SPA_DATA_DmaBuf) { - SyncDmaBuf(spaBuffer->datas[0].fd, DMA_BUF_SYNC_START); + src = SPA_MEMBER(map.get(), spa_buffer->datas[0].mapoffset, uint8_t); + } else if (spa_buffer->datas[0].type == SPA_DATA_DmaBuf) { + const uint n_planes = spa_buffer->n_datas; + int fds[n_planes]; + uint32_t offsets[n_planes]; + uint32_t strides[n_planes]; + + for (uint32_t i = 0; i < n_planes; ++i) { + fds[i] = spa_buffer->datas[i].fd; + offsets[i] = spa_buffer->datas[i].chunk->offset; + strides[i] = spa_buffer->datas[i].chunk->stride; } - src = SPA_MEMBER(map.get(), spaBuffer->datas[0].mapoffset, uint8_t); - } else if (spaBuffer->datas[0].type == SPA_DATA_MemPtr) { - src = static_cast(spaBuffer->datas[0].data); + src_unique_ptr = egl_dmabuf_->ImageFromDmaBuf( + desktop_size_, spa_video_format_.format, n_planes, fds, strides, + offsets, modifier_); + src = src_unique_ptr.get(); + } else if (spa_buffer->datas[0].type == SPA_DATA_MemPtr) { + src = static_cast(spa_buffer->datas[0].data); } if (!src) { @@ -501,7 +581,7 @@ void BaseCapturerPipeWire::HandleBuffer(pw_buffer* buffer) { struct spa_meta_region* video_metadata = static_cast(spa_buffer_find_meta_data( - spaBuffer, SPA_META_VideoCrop, sizeof(*video_metadata))); + spa_buffer, SPA_META_VideoCrop, sizeof(*video_metadata))); // Video size from metadata is bigger than an actual video stream size. // The metadata are wrong or we should up-scale the video...in both cases @@ -517,7 +597,6 @@ void BaseCapturerPipeWire::HandleBuffer(pw_buffer* buffer) { // Use video metadata when video size from metadata is set and smaller than // video stream size, so we need to adjust it. bool video_metadata_use = false; - const struct spa_rectangle* video_metadata_size = video_metadata ? &video_metadata->region.size : nullptr; @@ -544,7 +623,6 @@ void BaseCapturerPipeWire::HandleBuffer(pw_buffer* buffer) { } const int32_t dst_stride = video_size_.width() * kBytesPerPixel; - const int32_t src_stride = spaBuffer->datas[0].chunk->stride; if (src_stride != (desktop_size_.width() * kBytesPerPixel)) { RTC_LOG(LS_ERROR) << "Got buffer with stride different from screen stride: " @@ -632,7 +710,7 @@ gchar* BaseCapturerPipeWire::PrepareSignalHandle(GDBusConnection* connection, const gchar* token) { Scoped sender( g_strdup(g_dbus_connection_get_unique_name(connection) + 1)); - for (int i = 0; sender.get()[i]; i++) { + for (int i = 0; sender.get()[i]; ++i) { if (sender.get()[i] == '.') { sender.get()[i] = '_'; } @@ -1005,7 +1083,7 @@ void BaseCapturerPipeWire::OnOpenPipeWireRemoteRequested( return; } - that->InitPipeWire(); + that->Init(); } void BaseCapturerPipeWire::Start(Callback* callback) { diff --git a/modules/desktop_capture/linux/base_capturer_pipewire.h b/modules/desktop_capture/linux/base_capturer_pipewire.h index 7ec5ea6950..787ee5ec2c 100644 --- a/modules/desktop_capture/linux/base_capturer_pipewire.h +++ b/modules/desktop_capture/linux/base_capturer_pipewire.h @@ -19,6 +19,7 @@ #include "absl/types/optional.h" #include "modules/desktop_capture/desktop_capture_options.h" #include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/linux/egl_dmabuf.h" #include "rtc_base/constructor_magic.h" #include "rtc_base/synchronization/mutex.h" @@ -88,6 +89,7 @@ class BaseCapturerPipeWire : public DesktopCapturer { guint sources_request_signal_id_ = 0; guint start_request_signal_id_ = 0; + int64_t modifier_; DesktopSize video_size_; DesktopSize desktop_size_ = {}; DesktopCaptureOptions options_ = {}; @@ -98,8 +100,10 @@ class BaseCapturerPipeWire : public DesktopCapturer { bool portal_init_failed_ = false; + std::unique_ptr egl_dmabuf_; + + void Init(); void InitPortal(); - void InitPipeWire(); void InitPipeWireTypes(); pw_stream* CreateReceivingStream(); diff --git a/modules/desktop_capture/linux/drm.sigs b/modules/desktop_capture/linux/drm.sigs new file mode 100644 index 0000000000..226979fe16 --- /dev/null +++ b/modules/desktop_capture/linux/drm.sigs @@ -0,0 +1,11 @@ +// Copyright 2021 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. + +//------------------------------------------------ +// Functions from DRM used in capturer code. +//-------- + +// xf86drm.h +int drmGetDevices2(uint32_t flags, drmDevicePtr devices[], int max_devices); +void drmFreeDevices(drmDevicePtr devices[], int count); diff --git a/modules/desktop_capture/linux/egl_dmabuf.cc b/modules/desktop_capture/linux/egl_dmabuf.cc new file mode 100644 index 0000000000..ae34b69ed8 --- /dev/null +++ b/modules/desktop_capture/linux/egl_dmabuf.cc @@ -0,0 +1,584 @@ +/* + * Copyright 2021 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/desktop_capture/linux/egl_dmabuf.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "absl/memory/memory.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/sanitizer.h" +#include "rtc_base/string_encode.h" + +namespace webrtc { + +// EGL +typedef EGLBoolean (*eglBindAPI_func)(EGLenum api); +typedef EGLContext (*eglCreateContext_func)(EGLDisplay dpy, + EGLConfig config, + EGLContext share_context, + const EGLint* attrib_list); +typedef EGLImageKHR (*eglCreateImageKHR_func)(EGLDisplay dpy, + EGLContext ctx, + EGLenum target, + EGLClientBuffer buffer, + const EGLint* attrib_list); +typedef EGLBoolean (*eglDestroyImageKHR_func)(EGLDisplay dpy, + EGLImageKHR image); +typedef EGLint (*eglGetError_func)(void); +typedef void* (*eglGetProcAddress_func)(const char*); +typedef EGLDisplay (*eglGetPlatformDisplayEXT_func)(EGLenum platform, + void* native_display, + const EGLint* attrib_list); +typedef EGLBoolean (*eglInitialize_func)(EGLDisplay dpy, + EGLint* major, + EGLint* minor); +typedef EGLBoolean (*eglMakeCurrent_func)(EGLDisplay dpy, + EGLSurface draw, + EGLSurface read, + EGLContext ctx); +typedef EGLBoolean (*eglQueryDmaBufFormatsEXT_func)(EGLDisplay dpy, + EGLint max_formats, + EGLint* formats, + EGLint* num_formats); +typedef EGLBoolean (*eglQueryDmaBufModifiersEXT_func)(EGLDisplay dpy, + EGLint format, + EGLint max_modifiers, + EGLuint64KHR* modifiers, + EGLBoolean* external_only, + EGLint* num_modifiers); +typedef const char* (*eglQueryString_func)(EGLDisplay dpy, EGLint name); +typedef void (*glEGLImageTargetTexture2DOES_func)(GLenum target, + GLeglImageOES image); + +// This doesn't follow naming conventions in WebRTC, where the naming +// should look like e.g. egl_bind_api instead of EglBindAPI, however +// we named them according to the exported functions they map to for +// consistency. +eglBindAPI_func EglBindAPI = nullptr; +eglCreateContext_func EglCreateContext = nullptr; +eglCreateImageKHR_func EglCreateImageKHR = nullptr; +eglDestroyImageKHR_func EglDestroyImageKHR = nullptr; +eglGetError_func EglGetError = nullptr; +eglGetProcAddress_func EglGetProcAddress = nullptr; +eglGetPlatformDisplayEXT_func EglGetPlatformDisplayEXT = nullptr; +eglInitialize_func EglInitialize = nullptr; +eglMakeCurrent_func EglMakeCurrent = nullptr; +eglQueryDmaBufFormatsEXT_func EglQueryDmaBufFormatsEXT = nullptr; +eglQueryDmaBufModifiersEXT_func EglQueryDmaBufModifiersEXT = nullptr; +eglQueryString_func EglQueryString = nullptr; +glEGLImageTargetTexture2DOES_func GlEGLImageTargetTexture2DOES = nullptr; + +// GL +typedef void (*glBindTexture_func)(GLenum target, GLuint texture); +typedef void (*glDeleteTextures_func)(GLsizei n, const GLuint* textures); +typedef void (*glGenTextures_func)(GLsizei n, GLuint* textures); +typedef GLenum (*glGetError_func)(void); +typedef const GLubyte* (*glGetString_func)(GLenum name); +typedef void (*glGetTexImage_func)(GLenum target, + GLint level, + GLenum format, + GLenum type, + void* pixels); +typedef void (*glTexParameteri_func)(GLenum target, GLenum pname, GLint param); +typedef void* (*glXGetProcAddressARB_func)(const char*); + +// This doesn't follow naming conventions in WebRTC, where the naming +// should look like e.g. egl_bind_api instead of EglBindAPI, however +// we named them according to the exported functions they map to for +// consistency. +glBindTexture_func GlBindTexture = nullptr; +glDeleteTextures_func GlDeleteTextures = nullptr; +glGenTextures_func GlGenTextures = nullptr; +glGetError_func GlGetError = nullptr; +glGetString_func GlGetString = nullptr; +glGetTexImage_func GlGetTexImage = nullptr; +glTexParameteri_func GlTexParameteri = nullptr; +glXGetProcAddressARB_func GlXGetProcAddressARB = nullptr; + +static const std::string FormatGLError(GLenum err) { + switch (err) { + case GL_NO_ERROR: + return "GL_NO_ERROR"; + case GL_INVALID_ENUM: + return "GL_INVALID_ENUM"; + case GL_INVALID_VALUE: + return "GL_INVALID_VALUE"; + case GL_INVALID_OPERATION: + return "GL_INVALID_OPERATION"; + case GL_STACK_OVERFLOW: + return "GL_STACK_OVERFLOW"; + case GL_STACK_UNDERFLOW: + return "GL_STACK_UNDERFLOW"; + case GL_OUT_OF_MEMORY: + return "GL_OUT_OF_MEMORY"; + default: + return std::string("0x") + std::to_string(err); + } +} + +static uint32_t SpaPixelFormatToDrmFormat(uint32_t spa_format) { + switch (spa_format) { + case SPA_VIDEO_FORMAT_RGBA: + return DRM_FORMAT_ABGR8888; + case SPA_VIDEO_FORMAT_RGBx: + return DRM_FORMAT_XBGR8888; + case SPA_VIDEO_FORMAT_BGRA: + return DRM_FORMAT_ARGB8888; + case SPA_VIDEO_FORMAT_BGRx: + return DRM_FORMAT_XRGB8888; + default: + return DRM_FORMAT_INVALID; + } +} + +static void CloseLibrary(void* library) { + if (library) { + dlclose(library); + library = nullptr; + } +} + +static void* g_lib_egl = nullptr; + +static bool OpenEGL() { + g_lib_egl = dlopen("libEGL.so.1", RTLD_NOW | RTLD_GLOBAL); + if (g_lib_egl) { + EglGetProcAddress = + (eglGetProcAddress_func)dlsym(g_lib_egl, "eglGetProcAddress"); + return EglGetProcAddress; + } + + return false; +} + +static bool LoadEGL() { + if (OpenEGL()) { + EglBindAPI = (eglBindAPI_func)EglGetProcAddress("eglBindAPI"); + EglCreateContext = + (eglCreateContext_func)EglGetProcAddress("eglCreateContext"); + EglCreateImageKHR = + (eglCreateImageKHR_func)EglGetProcAddress("eglCreateImageKHR"); + EglDestroyImageKHR = + (eglDestroyImageKHR_func)EglGetProcAddress("eglDestroyImageKHR"); + EglGetError = (eglGetError_func)EglGetProcAddress("eglGetError"); + EglGetPlatformDisplayEXT = (eglGetPlatformDisplayEXT_func)EglGetProcAddress( + "eglGetPlatformDisplayEXT"); + EglInitialize = (eglInitialize_func)EglGetProcAddress("eglInitialize"); + EglMakeCurrent = (eglMakeCurrent_func)EglGetProcAddress("eglMakeCurrent"); + EglQueryString = (eglQueryString_func)EglGetProcAddress("eglQueryString"); + GlEGLImageTargetTexture2DOES = + (glEGLImageTargetTexture2DOES_func)EglGetProcAddress( + "glEGLImageTargetTexture2DOES"); + + return EglBindAPI && EglCreateContext && EglCreateImageKHR && + EglDestroyImageKHR && EglGetError && EglGetPlatformDisplayEXT && + EglInitialize && EglMakeCurrent && EglQueryString && + GlEGLImageTargetTexture2DOES; + } + + return false; +} + +static void* g_lib_gl = nullptr; + +static bool OpenGL() { + std::vector names = {"libGL.so.1", "libGL.so"}; + for (const std::string& name : names) { + g_lib_gl = dlopen(name.c_str(), RTLD_NOW | RTLD_GLOBAL); + if (g_lib_gl) { + GlXGetProcAddressARB = + (glXGetProcAddressARB_func)dlsym(g_lib_gl, "glXGetProcAddressARB"); + return GlXGetProcAddressARB; + } + } + + return false; +} + +static bool LoadGL() { + if (OpenGL()) { + GlGetString = (glGetString_func)GlXGetProcAddressARB("glGetString"); + if (!GlGetString) { + return false; + } + + GlBindTexture = (glBindTexture_func)GlXGetProcAddressARB("glBindTexture"); + GlDeleteTextures = + (glDeleteTextures_func)GlXGetProcAddressARB("glDeleteTextures"); + GlGenTextures = (glGenTextures_func)GlXGetProcAddressARB("glGenTextures"); + GlGetError = (glGetError_func)GlXGetProcAddressARB("glGetError"); + GlGetTexImage = (glGetTexImage_func)GlXGetProcAddressARB("glGetTexImage"); + GlTexParameteri = + (glTexParameteri_func)GlXGetProcAddressARB("glTexParameteri"); + + return GlBindTexture && GlDeleteTextures && GlGenTextures && GlGetError && + GlGetTexImage && GlTexParameteri; + } + + return false; +} + +RTC_NO_SANITIZE("cfi-icall") +EglDmaBuf::EglDmaBuf() { + absl::optional render_node = GetRenderNode(); + if (!render_node) { + return; + } + + drm_fd_ = open(render_node->c_str(), O_RDWR); + + if (drm_fd_ < 0) { + RTC_LOG(LS_ERROR) << "Failed to open drm render node: " << strerror(errno); + return; + } + + gbm_device_ = gbm_create_device(drm_fd_); + + if (!gbm_device_) { + RTC_LOG(LS_ERROR) << "Cannot create GBM device: " << strerror(errno); + return; + } + + if (!LoadEGL()) { + RTC_LOG(LS_ERROR) << "Unable to load EGL entry functions."; + return; + } + + if (!LoadGL()) { + RTC_LOG(LS_ERROR) << "Failed to load OpenGL entry functions."; + return; + } + + // Get the list of client extensions + const char* client_extensions_cstring_no_display = + EglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + std::string client_extensions_string = client_extensions_cstring_no_display; + if (!client_extensions_cstring_no_display) { + // If eglQueryString() returned NULL, the implementation doesn't support + // EGL_EXT_client_extensions. Expect an EGL_BAD_DISPLAY error. + RTC_LOG(LS_ERROR) << "No client extensions defined! " + << FormatGLError(EglGetError()); + return; + } + + std::vector client_extensions_no_display; + rtc::split(client_extensions_cstring_no_display, ' ', + &client_extensions_no_display); + for (const auto& extension : client_extensions_no_display) { + egl_.extensions.push_back(std::string(extension)); + } + + bool has_platform_base_ext = false; + bool has_platform_gbm_ext = false; + + for (const auto& extension : egl_.extensions) { + if (extension == "EGL_EXT_platform_base") { + has_platform_base_ext = true; + continue; + } else if (extension == "EGL_MESA_platform_gbm") { + has_platform_gbm_ext = true; + continue; + } + } + + if (!has_platform_base_ext || !has_platform_gbm_ext) { + RTC_LOG(LS_ERROR) << "One of required EGL extensions is missing"; + return; + } + + // Use eglGetPlatformDisplayEXT() to get the display pointer + // if the implementation supports it. + egl_.display = + EglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_MESA, gbm_device_, nullptr); + + if (egl_.display == EGL_NO_DISPLAY) { + RTC_LOG(LS_ERROR) << "Error during obtaining EGL display: " + << FormatGLError(EglGetError()); + return; + } + + EGLint major, minor; + if (EglInitialize(egl_.display, &major, &minor) == EGL_FALSE) { + RTC_LOG(LS_ERROR) << "Error during eglInitialize: " + << FormatGLError(EglGetError()); + return; + } + + if (EglBindAPI(EGL_OPENGL_API) == EGL_FALSE) { + RTC_LOG(LS_ERROR) << "bind OpenGL API failed"; + return; + } + + egl_.context = + EglCreateContext(egl_.display, nullptr, EGL_NO_CONTEXT, nullptr); + + if (egl_.context == EGL_NO_CONTEXT) { + RTC_LOG(LS_ERROR) << "Couldn't create EGL context: " + << FormatGLError(EglGetError()); + return; + } + + const char* client_extensions_cstring_display = + EglQueryString(egl_.display, EGL_EXTENSIONS); + client_extensions_string = client_extensions_cstring_display; + + std::vector client_extensions; + rtc::split(client_extensions_string, ' ', &client_extensions); + for (const auto& extension : client_extensions) { + egl_.extensions.push_back(std::string(extension)); + } + + bool has_image_dma_buf_import_ext = false; + bool has_image_dma_buf_import_modifiers_ext = false; + + for (const auto& extension : egl_.extensions) { + if (extension == "EGL_EXT_image_dma_buf_import") { + has_image_dma_buf_import_ext = true; + continue; + } else if (extension == "EGL_EXT_image_dma_buf_import_modifiers") { + has_image_dma_buf_import_modifiers_ext = true; + continue; + } + } + + if (has_image_dma_buf_import_ext && has_image_dma_buf_import_modifiers_ext) { + EglQueryDmaBufFormatsEXT = (eglQueryDmaBufFormatsEXT_func)EglGetProcAddress( + "eglQueryDmaBufFormatsEXT"); + EglQueryDmaBufModifiersEXT = + (eglQueryDmaBufModifiersEXT_func)EglGetProcAddress( + "eglQueryDmaBufModifiersEXT"); + } + + RTC_LOG(LS_INFO) << "Egl initialization succeeded"; + egl_initialized_ = true; +} + +EglDmaBuf::~EglDmaBuf() { + if (gbm_device_) { + gbm_device_destroy(gbm_device_); + } + + CloseLibrary(g_lib_egl); + CloseLibrary(g_lib_gl); +} + +RTC_NO_SANITIZE("cfi-icall") +std::unique_ptr EglDmaBuf::ImageFromDmaBuf(const DesktopSize& size, + uint32_t format, + uint32_t n_planes, + const int32_t* fds, + const uint32_t* strides, + const uint32_t* offsets, + uint64_t modifier) { + std::unique_ptr src; + + if (!egl_initialized_) { + return src; + } + + if (n_planes <= 0) { + RTC_LOG(LS_ERROR) << "Failed to process buffer: invalid number of planes"; + return src; + } + + gbm_bo* imported; + if (modifier == DRM_FORMAT_MOD_INVALID) { + gbm_import_fd_data import_info = {fds[0], + static_cast(size.width()), + static_cast(size.height()), + strides[0], GBM_BO_FORMAT_ARGB8888}; + + imported = gbm_bo_import(gbm_device_, GBM_BO_IMPORT_FD, &import_info, 0); + } else { + gbm_import_fd_modifier_data import_info = {}; + import_info.format = GBM_BO_FORMAT_ARGB8888; + import_info.width = static_cast(size.width()); + import_info.height = static_cast(size.height()); + import_info.num_fds = n_planes; + import_info.modifier = modifier; + for (uint32_t i = 0; i < n_planes; i++) { + import_info.fds[i] = fds[i]; + import_info.offsets[i] = offsets[i]; + import_info.strides[i] = strides[i]; + } + + imported = + gbm_bo_import(gbm_device_, GBM_BO_IMPORT_FD_MODIFIER, &import_info, 0); + } + + if (!imported) { + RTC_LOG(LS_ERROR) + << "Failed to process buffer: Cannot import passed GBM fd - " + << strerror(errno); + return src; + } + + // bind context to render thread + EglMakeCurrent(egl_.display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_.context); + + // create EGL image from imported BO + EGLImageKHR image = EglCreateImageKHR( + egl_.display, nullptr, EGL_NATIVE_PIXMAP_KHR, imported, nullptr); + + if (image == EGL_NO_IMAGE_KHR) { + RTC_LOG(LS_ERROR) << "Failed to record frame: Error creating EGLImageKHR - " + << FormatGLError(GlGetError()); + gbm_bo_destroy(imported); + return src; + } + + // create GL 2D texture for framebuffer + GLuint texture; + GlGenTextures(1, &texture); + GlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + GlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + GlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + GlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + GlBindTexture(GL_TEXTURE_2D, texture); + GlEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); + + src = std::make_unique(strides[0] * size.height()); + + GLenum gl_format = GL_BGRA; + switch (format) { + case SPA_VIDEO_FORMAT_RGBx: + gl_format = GL_RGBA; + break; + case SPA_VIDEO_FORMAT_RGBA: + gl_format = GL_RGBA; + break; + case SPA_VIDEO_FORMAT_BGRx: + gl_format = GL_BGRA; + break; + case SPA_VIDEO_FORMAT_RGB: + gl_format = GL_RGB; + break; + case SPA_VIDEO_FORMAT_BGR: + gl_format = GL_BGR; + break; + default: + gl_format = GL_BGRA; + break; + } + GlGetTexImage(GL_TEXTURE_2D, 0, gl_format, GL_UNSIGNED_BYTE, src.get()); + + if (GlGetError()) { + RTC_LOG(LS_ERROR) << "Failed to get image from DMA buffer."; + gbm_bo_destroy(imported); + return src; + } + + GlDeleteTextures(1, &texture); + EglDestroyImageKHR(egl_.display, image); + + gbm_bo_destroy(imported); + + return src; +} + +RTC_NO_SANITIZE("cfi-icall") +std::vector EglDmaBuf::QueryDmaBufModifiers(uint32_t format) { + if (!egl_initialized_) { + return {}; + } + + // Modifiers not supported, return just DRM_FORMAT_MOD_INVALID as we can still + // use modifier-less DMA-BUFs + if (EglQueryDmaBufFormatsEXT == nullptr || + EglQueryDmaBufModifiersEXT == nullptr) { + return {DRM_FORMAT_MOD_INVALID}; + } + + uint32_t drm_format = SpaPixelFormatToDrmFormat(format); + if (drm_format == DRM_FORMAT_INVALID) { + RTC_LOG(LS_ERROR) << "Failed to find matching DRM format."; + return {DRM_FORMAT_MOD_INVALID}; + } + + EGLint count = 0; + EGLBoolean success = + EglQueryDmaBufFormatsEXT(egl_.display, 0, nullptr, &count); + + if (!success || !count) { + RTC_LOG(LS_ERROR) << "Failed to query DMA-BUF formats."; + return {DRM_FORMAT_MOD_INVALID}; + } + + std::vector formats(count); + if (!EglQueryDmaBufFormatsEXT(egl_.display, count, + reinterpret_cast(formats.data()), + &count)) { + RTC_LOG(LS_ERROR) << "Failed to query DMA-BUF formats."; + return {DRM_FORMAT_MOD_INVALID}; + } + + if (std::find(formats.begin(), formats.end(), drm_format) == formats.end()) { + RTC_LOG(LS_ERROR) << "Format " << drm_format + << " not supported for modifiers."; + return {DRM_FORMAT_MOD_INVALID}; + } + + success = EglQueryDmaBufModifiersEXT(egl_.display, drm_format, 0, nullptr, + nullptr, &count); + + if (!success || !count) { + RTC_LOG(LS_ERROR) << "Failed to query DMA-BUF modifiers."; + return {DRM_FORMAT_MOD_INVALID}; + } + + std::vector modifiers(count); + if (!EglQueryDmaBufModifiersEXT(egl_.display, drm_format, count, + modifiers.data(), nullptr, &count)) { + RTC_LOG(LS_ERROR) << "Failed to query DMA-BUF modifiers."; + } + + // Support modifier-less buffers + modifiers.push_back(DRM_FORMAT_MOD_INVALID); + return modifiers; +} + +absl::optional EglDmaBuf::GetRenderNode() { + int max_devices = drmGetDevices2(0, nullptr, 0); + if (max_devices <= 0) { + RTC_LOG(LS_ERROR) << "drmGetDevices2() has not found any devices (errno=" + << -max_devices << ")"; + return absl::nullopt; + } + + std::vector devices(max_devices); + int ret = drmGetDevices2(0, devices.data(), max_devices); + if (ret < 0) { + RTC_LOG(LS_ERROR) << "drmGetDevices2() returned an error " << ret; + return absl::nullopt; + } + + std::string render_node; + + for (const drmDevicePtr& device : devices) { + if (device->available_nodes & (1 << DRM_NODE_RENDER)) { + render_node = device->nodes[DRM_NODE_RENDER]; + break; + } + } + + drmFreeDevices(devices.data(), ret); + return render_node; +} + +} // namespace webrtc diff --git a/modules/desktop_capture/linux/egl_dmabuf.h b/modules/desktop_capture/linux/egl_dmabuf.h new file mode 100644 index 0000000000..f7ced99a38 --- /dev/null +++ b/modules/desktop_capture/linux/egl_dmabuf.h @@ -0,0 +1,61 @@ +/* + * Copyright 2021 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_DESKTOP_CAPTURE_LINUX_EGL_DMABUF_H_ +#define MODULES_DESKTOP_CAPTURE_LINUX_EGL_DMABUF_H_ + +#include +#include +#include + +#include +#include +#include + +#include "absl/types/optional.h" +#include "modules/desktop_capture/desktop_geometry.h" + +namespace webrtc { + +class EglDmaBuf { + public: + struct EGLStruct { + std::vector extensions; + EGLDisplay display = EGL_NO_DISPLAY; + EGLContext context = EGL_NO_CONTEXT; + }; + + EglDmaBuf(); + ~EglDmaBuf(); + + std::unique_ptr ImageFromDmaBuf(const DesktopSize& size, + uint32_t format, + uint32_t n_planes, + const int32_t* fds, + const uint32_t* strides, + const uint32_t* offsets, + uint64_t modifiers); + std::vector QueryDmaBufModifiers(uint32_t format); + + bool IsEglInitialized() const { return egl_initialized_; } + + private: + bool egl_initialized_ = false; + int32_t drm_fd_ = -1; // for GBM buffer mmap + gbm_device* gbm_device_ = nullptr; // for passed GBM buffer retrieval + + EGLStruct egl_; + + absl::optional GetRenderNode(); +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_LINUX_EGL_DMABUF_H_ diff --git a/modules/desktop_capture/linux/pipewire03.sigs b/modules/desktop_capture/linux/pipewire.sigs similarity index 98% rename from modules/desktop_capture/linux/pipewire03.sigs rename to modules/desktop_capture/linux/pipewire.sigs index 44e4100db1..ffcd0770a4 100644 --- a/modules/desktop_capture/linux/pipewire03.sigs +++ b/modules/desktop_capture/linux/pipewire.sigs @@ -16,6 +16,7 @@ pw_loop * pw_loop_new(const spa_dict *props); // pipewire.h void pw_init(int *argc, char **argv[]); +const char* pw_get_library_version(); // properties.h pw_properties * pw_properties_new_string(const char *args); @@ -39,7 +40,6 @@ void pw_thread_loop_lock(pw_thread_loop *loop); void pw_thread_loop_unlock(pw_thread_loop *loop); pw_loop * pw_thread_loop_get_loop(pw_thread_loop *loop); - // context.h void pw_context_destroy(pw_context *context); pw_context *pw_context_new(pw_loop *main_loop, pw_properties *props, size_t user_data_size); diff --git a/modules/desktop_capture/linux/pipewire_stub_header.fragment b/modules/desktop_capture/linux/pipewire_stub_header.fragment index 9d7dbd27c5..06ae18dfd4 100644 --- a/modules/desktop_capture/linux/pipewire_stub_header.fragment +++ b/modules/desktop_capture/linux/pipewire_stub_header.fragment @@ -5,4 +5,5 @@ extern "C" { #include +#include } diff --git a/webrtc.gni b/webrtc.gni index 3d8c538c37..90f69f03e0 100644 --- a/webrtc.gni +++ b/webrtc.gni @@ -132,7 +132,7 @@ declare_args() { # supported Ubuntu and Debian distributions. rtc_use_pipewire = is_linux && use_sysroot - # Set this to link PipeWire directly instead of using the dlopen. + # Set this to link PipeWire and required libraries directly instead of using the dlopen. rtc_link_pipewire = false # Enable to use the Mozilla internal settings.