mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-12 21:30:45 +01:00
PipeWire capturer: implement proper DMA-BUFs support
Currently both KWin (KDE) and Mutter (GNOME) window managers don't use DMA-BUFs by default, but only when client asks specifically for them (KWin) or when experimental DMA-BUF support is enabled (Mutter). While current implementation works just fine on integrated graphics cards, it causes issues on dedicated GPUs (AMD and NVidia) where the code either crashes or screensharing is slow and unusable. To fix this, DMA-BUFs has to be opened using OpenGL context and not being directly mmaped(). This implementation requires to use DMA-BUF modifiers, as they are now mandatory for DMA-BUFs usage. Documentation for this behavior can be found here: https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/master/doc/dma-buf.dox Bug: chromium:1233417, webrtc:13137 Change-Id: I0cecf16d6bb0f576954b9e8f071cab526f7baf2c Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/227022 Commit-Queue: Tommi <tommi@webrtc.org> Reviewed-by: Tommi <tommi@webrtc.org> Reviewed-by: Erik Språng <sprang@webrtc.org> Cr-Commit-Position: refs/heads/main@{#34889}
This commit is contained in:
parent
338d31435d
commit
f2177f6612
8 changed files with 639 additions and 69 deletions
|
@ -225,6 +225,19 @@ if (is_linux || is_chromeos) {
|
|||
}
|
||||
}
|
||||
|
||||
pkg_config("gbm") {
|
||||
packages = [ "gbm" ]
|
||||
}
|
||||
pkg_config("egl") {
|
||||
packages = [ "egl" ]
|
||||
}
|
||||
pkg_config("epoxy") {
|
||||
packages = [ "epoxy" ]
|
||||
}
|
||||
pkg_config("libdrm") {
|
||||
packages = [ "libdrm" ]
|
||||
}
|
||||
|
||||
if (!rtc_link_pipewire) {
|
||||
# When libpipewire is not directly linked, use stubs to allow for dlopening of
|
||||
# the binary.
|
||||
|
@ -542,12 +555,18 @@ 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) {
|
||||
|
|
|
@ -13,6 +13,9 @@ specific_include_rules = {
|
|||
"desktop_frame_provider\.h": [
|
||||
"+sdk/objc",
|
||||
],
|
||||
"egl_dmabuf\.cc": [
|
||||
"+absl/strings/str_split.h",
|
||||
],
|
||||
"screen_capturer_mac\.mm": [
|
||||
"+sdk/objc",
|
||||
],
|
||||
|
|
|
@ -14,12 +14,13 @@
|
|||
#include <glib-object.h>
|
||||
#include <spa/param/format-utils.h>
|
||||
#include <spa/param/props.h>
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/memory/memory.h"
|
||||
|
@ -51,65 +52,102 @@ const int kBytesPerPixel = 4;
|
|||
const char kPipeWireLib[] = "libpipewire-0.3.so.0";
|
||||
#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};
|
||||
pw_version ParsePipeWireVersion(const char* version) {
|
||||
pw_version pw_version;
|
||||
sscanf(version, "%d.%d.%d", &pw_version.major, &pw_version.minor,
|
||||
&pw_version.micro);
|
||||
return pw_version;
|
||||
}
|
||||
|
||||
sync.flags = start_or_end | DMA_BUF_SYNC_READ;
|
||||
spa_pod* BuildFormat(spa_pod_builder* builder,
|
||||
uint32_t format,
|
||||
const std::vector<uint64_t>& 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};
|
||||
|
||||
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;
|
||||
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()) {
|
||||
pw_version pw_version = ParsePipeWireVersion(pw_get_library_version());
|
||||
|
||||
// SPA_POD_PROP_FLAG_DONT_FIXATE can be used with PipeWire >= 0.3.33
|
||||
if (pw_version.major >= 0 && pw_version.minor >= 3 &&
|
||||
pw_version.micro >= 33) {
|
||||
spa_pod_builder_prop(
|
||||
builder, SPA_FORMAT_VIDEO_modifier,
|
||||
SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE);
|
||||
} else {
|
||||
break;
|
||||
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*>(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(unsigned char* 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(unsigned char* 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_; }
|
||||
|
||||
protected:
|
||||
unsigned char* map_ = nullptr;
|
||||
unsigned char* map_ = static_cast<unsigned char*>(MAP_FAILED);
|
||||
int map_size_;
|
||||
bool is_dma_buf_;
|
||||
int fd_;
|
||||
};
|
||||
|
||||
|
@ -234,17 +272,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*>(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*>(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,
|
||||
|
@ -350,6 +397,12 @@ BaseCapturerPipeWire::~BaseCapturerPipeWire() {
|
|||
}
|
||||
}
|
||||
|
||||
#if PW_CHECK_VERSION(0, 3, 0)
|
||||
void BaseCapturerPipeWire::InitEGL() {
|
||||
egl_dmabuf_ = std::make_unique<EglDmaBuf>();
|
||||
}
|
||||
#endif
|
||||
|
||||
void BaseCapturerPipeWire::InitPortal() {
|
||||
cancellable_ = g_cancellable_new();
|
||||
g_dbus_proxy_new_for_bus(
|
||||
|
@ -419,34 +472,39 @@ 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];
|
||||
spa_pod_builder builder = spa_pod_builder{buffer, sizeof(buffer)};
|
||||
spa_pod_builder builder;
|
||||
uint8_t buffer[2048] = {};
|
||||
std::vector<uint64_t> modifiers;
|
||||
|
||||
params[0] = reinterpret_cast<spa_pod*>(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));
|
||||
builder = spa_pod_builder{buffer, sizeof(buffer)};
|
||||
|
||||
std::vector<const spa_pod*> params;
|
||||
for (uint32_t format : {SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_RGBA,
|
||||
SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_RGBx}) {
|
||||
pw_version pw_version = ParsePipeWireVersion(pw_get_library_version());
|
||||
|
||||
// Modifiers can be used with PipeWire >= 0.3.29
|
||||
if (pw_version.major >= 0 && pw_version.minor >= 3 &&
|
||||
pw_version.micro >= 29) {
|
||||
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;
|
||||
|
@ -456,25 +514,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<uint8_t[]> 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<void()> 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<uint8_t*>(
|
||||
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: "
|
||||
|
@ -482,13 +541,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 (uint 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<uint8_t*>(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<uint8_t*>(spa_buffer->datas[0].data);
|
||||
}
|
||||
|
||||
if (!src) {
|
||||
|
@ -497,7 +568,7 @@ void BaseCapturerPipeWire::HandleBuffer(pw_buffer* buffer) {
|
|||
|
||||
struct spa_meta_region* video_metadata =
|
||||
static_cast<struct spa_meta_region*>(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
|
||||
|
@ -513,7 +584,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;
|
||||
|
||||
|
@ -540,7 +610,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: "
|
||||
|
@ -1001,6 +1070,9 @@ void BaseCapturerPipeWire::OnOpenPipeWireRemoteRequested(
|
|||
return;
|
||||
}
|
||||
|
||||
#if PW_CHECK_VERSION(0, 3, 0)
|
||||
that->InitEGL();
|
||||
#endif
|
||||
that->InitPipeWire();
|
||||
}
|
||||
|
||||
|
|
|
@ -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,6 +100,9 @@ class BaseCapturerPipeWire : public DesktopCapturer {
|
|||
|
||||
bool portal_init_failed_ = false;
|
||||
|
||||
std::unique_ptr<EglDmaBuf> egl_dmabuf_;
|
||||
|
||||
void InitEGL();
|
||||
void InitPortal();
|
||||
void InitPipeWire();
|
||||
void InitPipeWireTypes();
|
||||
|
|
411
modules/desktop_capture/linux/egl_dmabuf.cc
Normal file
411
modules/desktop_capture/linux/egl_dmabuf.cc
Normal file
|
@ -0,0 +1,411 @@
|
|||
/*
|
||||
* 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 <asm/ioctl.h>
|
||||
#include <fcntl.h>
|
||||
#include <libdrm/drm_fourcc.h>
|
||||
#include <linux/types.h>
|
||||
#include <spa/param/video/format-utils.h>
|
||||
#include <unistd.h>
|
||||
#include <xf86drm.h>
|
||||
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/strings/str_split.h"
|
||||
#include "absl/types/optional.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/sanitizer.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
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);
|
||||
eglQueryDmaBufFormatsEXT_func EglQueryDmaBufFormatsEXT = nullptr;
|
||||
eglQueryDmaBufModifiersEXT_func EglQueryDmaBufModifiersEXT = 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 absl::optional<std::string> GetRenderNode() {
|
||||
int ret, max_devices;
|
||||
std::string render_node;
|
||||
|
||||
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<drmDevicePtr> devices(max_devices);
|
||||
ret = drmGetDevices2(0, devices.data(), max_devices);
|
||||
if (ret < 0) {
|
||||
RTC_LOG(LS_ERROR) << "drmGetDevices2() returned an error " << ret;
|
||||
return absl::nullopt;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
RTC_NO_SANITIZE("cfi-icall")
|
||||
EglDmaBuf::EglDmaBuf() {
|
||||
absl::optional<std::string> 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
for (const auto& extension :
|
||||
absl::StrSplit(client_extensions_cstring_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;
|
||||
|
||||
for (const auto& extension : absl::StrSplit(client_extensions_string, " ")) {
|
||||
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_);
|
||||
}
|
||||
}
|
||||
|
||||
RTC_NO_SANITIZE("cfi-icall")
|
||||
std::unique_ptr<uint8_t[]> 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<uint8_t[]> 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<uint32_t>(size.width()),
|
||||
static_cast<uint32_t>(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<uint32_t>(size.width());
|
||||
import_info.height = static_cast<uint32_t>(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<uint8_t[]>(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<uint64_t> 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<uint32_t> formats(count);
|
||||
if (!EglQueryDmaBufFormatsEXT(egl_.display, count,
|
||||
reinterpret_cast<EGLint*>(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<uint64_t> 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;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
58
modules/desktop_capture/linux/egl_dmabuf.h
Normal file
58
modules/desktop_capture/linux/egl_dmabuf.h
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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 <epoxy/egl.h>
|
||||
#include <epoxy/gl.h>
|
||||
#include <gbm.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "modules/desktop_capture/desktop_geometry.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class EglDmaBuf {
|
||||
public:
|
||||
struct EGLStruct {
|
||||
std::vector<std::string> extensions;
|
||||
EGLDisplay display = EGL_NO_DISPLAY;
|
||||
EGLContext context = EGL_NO_CONTEXT;
|
||||
};
|
||||
|
||||
EglDmaBuf();
|
||||
~EglDmaBuf();
|
||||
|
||||
std::unique_ptr<uint8_t[]> 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<uint64_t> 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_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_LINUX_EGL_DMABUF_H_
|
|
@ -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);
|
||||
|
|
|
@ -130,7 +130,8 @@ declare_args() {
|
|||
# By default it's only enabled on desktop Linux (excludes ChromeOS) and
|
||||
# only when using the sysroot as PipeWire is not available in older and
|
||||
# supported Ubuntu and Debian distributions.
|
||||
rtc_use_pipewire = is_linux && use_sysroot
|
||||
# TODO: remove !is_msan (https://bugs.chromium.org/p/webrtc/issues/detail?id=13137)
|
||||
rtc_use_pipewire = is_linux && use_sysroot && !is_msan
|
||||
|
||||
# Set this to link PipeWire directly instead of using the dlopen.
|
||||
rtc_link_pipewire = false
|
||||
|
|
Loading…
Reference in a new issue