mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-12 21:30:45 +01:00
Reland "PipeWire capturer: implement proper DMA-BUFs support""
This is a reland of f2177f6612
Original change's description:
> 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
> 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}
Bug: chromium:1233417
Change-Id: I308501d86ec18ab6df9bcee569c4b72df7926549
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/231180
Reviewed-by: Erik Språng <sprang@webrtc.org>
Reviewed-by: Tommi <tommi@webrtc.org>
Commit-Queue: Tommi <tommi@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#35152}
This commit is contained in:
parent
404cd60ecc
commit
e1e05afec7
9 changed files with 849 additions and 81 deletions
|
@ -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) {
|
if (!rtc_link_pipewire) {
|
||||||
# When libpipewire is not directly linked, use stubs to allow for dlopening of
|
# When libpipewire is not directly linked, use stubs to allow for dlopening of
|
||||||
# the binary.
|
# the binary.
|
||||||
|
@ -229,6 +246,7 @@ if (is_linux || is_chromeos) {
|
||||||
configs = [
|
configs = [
|
||||||
"../../:common_config",
|
"../../:common_config",
|
||||||
":pipewire",
|
":pipewire",
|
||||||
|
":libdrm",
|
||||||
]
|
]
|
||||||
deps = [ "../../rtc_base" ]
|
deps = [ "../../rtc_base" ]
|
||||||
extra_header = "linux/pipewire_stub_header.fragment"
|
extra_header = "linux/pipewire_stub_header.fragment"
|
||||||
|
@ -236,7 +254,10 @@ if (is_linux || is_chromeos) {
|
||||||
logging_include = "rtc_base/logging.h"
|
logging_include = "rtc_base/logging.h"
|
||||||
output_name = "linux/pipewire_stubs"
|
output_name = "linux/pipewire_stubs"
|
||||||
path_from_source = "modules/desktop_capture/linux"
|
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 += [
|
sources += [
|
||||||
"linux/base_capturer_pipewire.cc",
|
"linux/base_capturer_pipewire.cc",
|
||||||
"linux/base_capturer_pipewire.h",
|
"linux/base_capturer_pipewire.h",
|
||||||
|
"linux/egl_dmabuf.cc",
|
||||||
|
"linux/egl_dmabuf.h",
|
||||||
]
|
]
|
||||||
|
|
||||||
configs += [
|
configs += [
|
||||||
":pipewire_config",
|
":pipewire_config",
|
||||||
":gio",
|
":gio",
|
||||||
":pipewire",
|
":pipewire",
|
||||||
|
":gbm",
|
||||||
|
":egl",
|
||||||
|
":epoxy",
|
||||||
|
":libdrm",
|
||||||
]
|
]
|
||||||
|
|
||||||
if (!rtc_link_pipewire) {
|
if (!rtc_link_pipewire) {
|
||||||
deps += [ ":pipewire_stubs" ]
|
deps += [ ":pipewire_stubs" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deps += [ "../../rtc_base:sanitizer" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rtc_enable_win_wgc) {
|
if (rtc_enable_win_wgc) {
|
||||||
|
|
|
@ -14,12 +14,13 @@
|
||||||
#include <glib-object.h>
|
#include <glib-object.h>
|
||||||
#include <spa/param/format-utils.h>
|
#include <spa/param/format-utils.h>
|
||||||
#include <spa/param/props.h>
|
#include <spa/param/props.h>
|
||||||
|
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <sys/syscall.h>
|
#include <sys/syscall.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "absl/memory/memory.h"
|
#include "absl/memory/memory.h"
|
||||||
|
@ -27,11 +28,13 @@
|
||||||
#include "modules/desktop_capture/desktop_capturer.h"
|
#include "modules/desktop_capture/desktop_capturer.h"
|
||||||
#include "rtc_base/checks.h"
|
#include "rtc_base/checks.h"
|
||||||
#include "rtc_base/logging.h"
|
#include "rtc_base/logging.h"
|
||||||
|
#include "rtc_base/string_encode.h"
|
||||||
|
|
||||||
#if defined(WEBRTC_DLOPEN_PIPEWIRE)
|
#if defined(WEBRTC_DLOPEN_PIPEWIRE)
|
||||||
#include "modules/desktop_capture/linux/pipewire_stubs.h"
|
#include "modules/desktop_capture/linux/pipewire_stubs.h"
|
||||||
using modules_desktop_capture_linux::InitializeStubs;
|
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;
|
using modules_desktop_capture_linux::StubPathMap;
|
||||||
#endif // defined(WEBRTC_DLOPEN_PIPEWIRE)
|
#endif // defined(WEBRTC_DLOPEN_PIPEWIRE)
|
||||||
|
|
||||||
|
@ -49,67 +52,116 @@ const int kBytesPerPixel = 4;
|
||||||
|
|
||||||
#if defined(WEBRTC_DLOPEN_PIPEWIRE)
|
#if defined(WEBRTC_DLOPEN_PIPEWIRE)
|
||||||
const char kPipeWireLib[] = "libpipewire-0.3.so.0";
|
const char kPipeWireLib[] = "libpipewire-0.3.so.0";
|
||||||
|
const char kDrmLib[] = "libdrm.so.2";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// static
|
#if !PW_CHECK_VERSION(0, 3, 29)
|
||||||
struct dma_buf_sync {
|
#define SPA_POD_PROP_FLAG_MANDATORY (1u << 3)
|
||||||
uint64_t flags;
|
#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) {
|
bool CheckPipeWireVersion(pw_version required_version) {
|
||||||
struct dma_buf_sync sync = {0};
|
std::vector<std::string> 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;
|
if (parsed_version.size() != 3) {
|
||||||
|
return false;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<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};
|
||||||
|
|
||||||
|
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*>(spa_pod_builder_pop(builder, &frames[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScopedBuf {
|
class ScopedBuf {
|
||||||
public:
|
public:
|
||||||
ScopedBuf() {}
|
ScopedBuf() {}
|
||||||
ScopedBuf(unsigned char* map, int map_size, bool is_dma_buf, int fd)
|
ScopedBuf(uint8_t* map, int map_size, int fd)
|
||||||
: map_(map), map_size_(map_size), is_dma_buf_(is_dma_buf), fd_(fd) {}
|
: map_(map), map_size_(map_size), fd_(fd) {}
|
||||||
~ScopedBuf() {
|
~ScopedBuf() {
|
||||||
if (map_ != MAP_FAILED) {
|
if (map_ != MAP_FAILED) {
|
||||||
if (is_dma_buf_) {
|
|
||||||
SyncDmaBuf(fd_, DMA_BUF_SYNC_END);
|
|
||||||
}
|
|
||||||
munmap(map_, map_size_);
|
munmap(map_, map_size_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
operator bool() { return map_ != MAP_FAILED; }
|
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_ = map;
|
||||||
map_size_ = map_size;
|
map_size_ = map_size;
|
||||||
is_dma_buf_ = is_dma_buf;
|
|
||||||
fd_ = fd;
|
fd_ = fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char* get() { return map_; }
|
uint8_t* get() { return map_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
unsigned char* map_ = nullptr;
|
uint8_t* map_ = static_cast<uint8_t*>(MAP_FAILED);
|
||||||
int map_size_;
|
int map_size_;
|
||||||
bool is_dma_buf_;
|
|
||||||
int fd_;
|
int fd_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -234,17 +286,26 @@ void BaseCapturerPipeWire::OnStreamParamChanged(void* data,
|
||||||
auto size = height * stride;
|
auto size = height * stride;
|
||||||
|
|
||||||
that->desktop_size_ = DesktopSize(width, height);
|
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] = {};
|
uint8_t buffer[1024] = {};
|
||||||
auto builder = spa_pod_builder{buffer, sizeof(buffer)};
|
auto builder = spa_pod_builder{buffer, sizeof(buffer)};
|
||||||
|
|
||||||
// Setup buffers and meta header for new format.
|
// Setup buffers and meta header for new format.
|
||||||
const struct spa_pod* params[3];
|
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(
|
params[0] = reinterpret_cast<spa_pod*>(spa_pod_builder_add_object(
|
||||||
&builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
|
&builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
|
||||||
SPA_PARAM_BUFFERS_size, SPA_POD_Int(size), SPA_PARAM_BUFFERS_stride,
|
SPA_PARAM_BUFFERS_size, SPA_POD_Int(size), SPA_PARAM_BUFFERS_stride,
|
||||||
SPA_POD_Int(stride), SPA_PARAM_BUFFERS_buffers,
|
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(
|
params[1] = reinterpret_cast<spa_pod*>(spa_pod_builder_add_object(
|
||||||
&builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type,
|
&builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type,
|
||||||
SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size,
|
SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size,
|
||||||
|
@ -363,12 +424,13 @@ void BaseCapturerPipeWire::InitPortal() {
|
||||||
reinterpret_cast<GAsyncReadyCallback>(OnProxyRequested), this);
|
reinterpret_cast<GAsyncReadyCallback>(OnProxyRequested), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BaseCapturerPipeWire::InitPipeWire() {
|
void BaseCapturerPipeWire::Init() {
|
||||||
#if defined(WEBRTC_DLOPEN_PIPEWIRE)
|
#if defined(WEBRTC_DLOPEN_PIPEWIRE)
|
||||||
StubPathMap paths;
|
StubPathMap paths;
|
||||||
|
|
||||||
// Check if the PipeWire library is available.
|
// Check if the PipeWire and DRM libraries are available.
|
||||||
paths[kModulePipewire03].push_back(kPipeWireLib);
|
paths[kModulePipewire].push_back(kPipeWireLib);
|
||||||
|
paths[kModuleDrm].push_back(kDrmLib);
|
||||||
if (!InitializeStubs(paths)) {
|
if (!InitializeStubs(paths)) {
|
||||||
RTC_LOG(LS_ERROR) << "Failed to load the PipeWire library and symbols.";
|
RTC_LOG(LS_ERROR) << "Failed to load the PipeWire library and symbols.";
|
||||||
portal_init_failed_ = true;
|
portal_init_failed_ = true;
|
||||||
|
@ -376,6 +438,8 @@ void BaseCapturerPipeWire::InitPipeWire() {
|
||||||
}
|
}
|
||||||
#endif // defined(WEBRTC_DLOPEN_PIPEWIRE)
|
#endif // defined(WEBRTC_DLOPEN_PIPEWIRE)
|
||||||
|
|
||||||
|
egl_dmabuf_ = std::make_unique<EglDmaBuf>();
|
||||||
|
|
||||||
pw_init(/*argc=*/nullptr, /*argc=*/nullptr);
|
pw_init(/*argc=*/nullptr, /*argc=*/nullptr);
|
||||||
|
|
||||||
pw_main_loop_ = pw_thread_loop_new("pipewire-main-loop", nullptr);
|
pw_main_loop_ = pw_thread_loop_new("pipewire-main-loop", nullptr);
|
||||||
|
@ -423,34 +487,37 @@ void BaseCapturerPipeWire::InitPipeWire() {
|
||||||
}
|
}
|
||||||
|
|
||||||
pw_stream* BaseCapturerPipeWire::CreateReceivingStream() {
|
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* reuseProps =
|
||||||
pw_properties_new_string("pipewire.client.reuse=1");
|
pw_properties_new_string("pipewire.client.reuse=1");
|
||||||
auto stream = pw_stream_new(pw_core_, "webrtc-consume-stream", reuseProps);
|
auto stream = pw_stream_new(pw_core_, "webrtc-consume-stream", reuseProps);
|
||||||
|
|
||||||
uint8_t buffer[1024] = {};
|
uint8_t buffer[2048] = {};
|
||||||
const spa_pod* params[1];
|
std::vector<uint64_t> modifiers;
|
||||||
|
|
||||||
spa_pod_builder builder = spa_pod_builder{buffer, sizeof(buffer)};
|
spa_pod_builder builder = spa_pod_builder{buffer, sizeof(buffer)};
|
||||||
|
|
||||||
params[0] = reinterpret_cast<spa_pod*>(spa_pod_builder_add_object(
|
std::vector<const spa_pod*> params;
|
||||||
&builder, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
|
const bool has_required_pw_version =
|
||||||
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video),
|
CheckPipeWireVersion(pw_version{0, 3, 29});
|
||||||
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
|
for (uint32_t format : {SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_RGBA,
|
||||||
SPA_FORMAT_VIDEO_format,
|
SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_RGBx}) {
|
||||||
SPA_POD_CHOICE_ENUM_Id(5, SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_RGBx,
|
// Modifiers can be used with PipeWire >= 0.3.29
|
||||||
SPA_VIDEO_FORMAT_RGBA, SPA_VIDEO_FORMAT_BGRx,
|
if (has_required_pw_version) {
|
||||||
SPA_VIDEO_FORMAT_BGRA),
|
modifiers = egl_dmabuf_->QueryDmaBufModifiers(format);
|
||||||
SPA_FORMAT_VIDEO_size,
|
|
||||||
SPA_POD_CHOICE_RANGE_Rectangle(&pwMinScreenBounds, &pwMinScreenBounds,
|
if (!modifiers.empty()) {
|
||||||
&pwMaxScreenBounds),
|
params.push_back(BuildFormat(&builder, format, modifiers));
|
||||||
0));
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
params.push_back(BuildFormat(&builder, format, /*modifiers=*/{}));
|
||||||
|
}
|
||||||
|
|
||||||
pw_stream_add_listener(stream, &spa_stream_listener_, &pw_stream_events_,
|
pw_stream_add_listener(stream, &spa_stream_listener_, &pw_stream_events_,
|
||||||
this);
|
this);
|
||||||
if (pw_stream_connect(stream, PW_DIRECTION_INPUT, pw_stream_node_id_,
|
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.";
|
RTC_LOG(LS_ERROR) << "Could not connect receiving stream.";
|
||||||
portal_init_failed_ = true;
|
portal_init_failed_ = true;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -460,25 +527,26 @@ pw_stream* BaseCapturerPipeWire::CreateReceivingStream() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void BaseCapturerPipeWire::HandleBuffer(pw_buffer* buffer) {
|
void BaseCapturerPipeWire::HandleBuffer(pw_buffer* buffer) {
|
||||||
spa_buffer* spaBuffer = buffer->buffer;
|
spa_buffer* spa_buffer = buffer->buffer;
|
||||||
ScopedBuf map;
|
ScopedBuf map;
|
||||||
|
std::unique_ptr<uint8_t[]> src_unique_ptr;
|
||||||
uint8_t* src = nullptr;
|
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.";
|
RTC_LOG(LS_ERROR) << "Failed to get video stream: Zero size.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spaBuffer->datas[0].type == SPA_DATA_MemFd ||
|
std::function<void()> cleanup;
|
||||||
spaBuffer->datas[0].type == SPA_DATA_DmaBuf) {
|
const int32_t src_stride = spa_buffer->datas[0].chunk->stride;
|
||||||
|
if (spa_buffer->datas[0].type == SPA_DATA_MemFd) {
|
||||||
map.initialize(
|
map.initialize(
|
||||||
static_cast<uint8_t*>(
|
static_cast<uint8_t*>(
|
||||||
mmap(nullptr,
|
mmap(nullptr,
|
||||||
spaBuffer->datas[0].maxsize + spaBuffer->datas[0].mapoffset,
|
spa_buffer->datas[0].maxsize + spa_buffer->datas[0].mapoffset,
|
||||||
PROT_READ, MAP_PRIVATE, spaBuffer->datas[0].fd, 0)),
|
PROT_READ, MAP_PRIVATE, spa_buffer->datas[0].fd, 0)),
|
||||||
spaBuffer->datas[0].maxsize + spaBuffer->datas[0].mapoffset,
|
spa_buffer->datas[0].maxsize + spa_buffer->datas[0].mapoffset,
|
||||||
spaBuffer->datas[0].type == SPA_DATA_DmaBuf,
|
spa_buffer->datas[0].fd);
|
||||||
spaBuffer->datas[0].fd);
|
|
||||||
|
|
||||||
if (!map) {
|
if (!map) {
|
||||||
RTC_LOG(LS_ERROR) << "Failed to mmap the memory: "
|
RTC_LOG(LS_ERROR) << "Failed to mmap the memory: "
|
||||||
|
@ -486,13 +554,25 @@ void BaseCapturerPipeWire::HandleBuffer(pw_buffer* buffer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spaBuffer->datas[0].type == SPA_DATA_DmaBuf) {
|
src = SPA_MEMBER(map.get(), spa_buffer->datas[0].mapoffset, uint8_t);
|
||||||
SyncDmaBuf(spaBuffer->datas[0].fd, DMA_BUF_SYNC_START);
|
} 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);
|
src_unique_ptr = egl_dmabuf_->ImageFromDmaBuf(
|
||||||
} else if (spaBuffer->datas[0].type == SPA_DATA_MemPtr) {
|
desktop_size_, spa_video_format_.format, n_planes, fds, strides,
|
||||||
src = static_cast<uint8_t*>(spaBuffer->datas[0].data);
|
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) {
|
if (!src) {
|
||||||
|
@ -501,7 +581,7 @@ void BaseCapturerPipeWire::HandleBuffer(pw_buffer* buffer) {
|
||||||
|
|
||||||
struct spa_meta_region* video_metadata =
|
struct spa_meta_region* video_metadata =
|
||||||
static_cast<struct spa_meta_region*>(spa_buffer_find_meta_data(
|
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.
|
// 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
|
// 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
|
// Use video metadata when video size from metadata is set and smaller than
|
||||||
// video stream size, so we need to adjust it.
|
// video stream size, so we need to adjust it.
|
||||||
bool video_metadata_use = false;
|
bool video_metadata_use = false;
|
||||||
|
|
||||||
const struct spa_rectangle* video_metadata_size =
|
const struct spa_rectangle* video_metadata_size =
|
||||||
video_metadata ? &video_metadata->region.size : nullptr;
|
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 dst_stride = video_size_.width() * kBytesPerPixel;
|
||||||
const int32_t src_stride = spaBuffer->datas[0].chunk->stride;
|
|
||||||
|
|
||||||
if (src_stride != (desktop_size_.width() * kBytesPerPixel)) {
|
if (src_stride != (desktop_size_.width() * kBytesPerPixel)) {
|
||||||
RTC_LOG(LS_ERROR) << "Got buffer with stride different from screen stride: "
|
RTC_LOG(LS_ERROR) << "Got buffer with stride different from screen stride: "
|
||||||
|
@ -632,7 +710,7 @@ gchar* BaseCapturerPipeWire::PrepareSignalHandle(GDBusConnection* connection,
|
||||||
const gchar* token) {
|
const gchar* token) {
|
||||||
Scoped<gchar> sender(
|
Scoped<gchar> sender(
|
||||||
g_strdup(g_dbus_connection_get_unique_name(connection) + 1));
|
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] == '.') {
|
if (sender.get()[i] == '.') {
|
||||||
sender.get()[i] = '_';
|
sender.get()[i] = '_';
|
||||||
}
|
}
|
||||||
|
@ -1005,7 +1083,7 @@ void BaseCapturerPipeWire::OnOpenPipeWireRemoteRequested(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
that->InitPipeWire();
|
that->Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BaseCapturerPipeWire::Start(Callback* callback) {
|
void BaseCapturerPipeWire::Start(Callback* callback) {
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#include "absl/types/optional.h"
|
#include "absl/types/optional.h"
|
||||||
#include "modules/desktop_capture/desktop_capture_options.h"
|
#include "modules/desktop_capture/desktop_capture_options.h"
|
||||||
#include "modules/desktop_capture/desktop_capturer.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/constructor_magic.h"
|
||||||
#include "rtc_base/synchronization/mutex.h"
|
#include "rtc_base/synchronization/mutex.h"
|
||||||
|
|
||||||
|
@ -88,6 +89,7 @@ class BaseCapturerPipeWire : public DesktopCapturer {
|
||||||
guint sources_request_signal_id_ = 0;
|
guint sources_request_signal_id_ = 0;
|
||||||
guint start_request_signal_id_ = 0;
|
guint start_request_signal_id_ = 0;
|
||||||
|
|
||||||
|
int64_t modifier_;
|
||||||
DesktopSize video_size_;
|
DesktopSize video_size_;
|
||||||
DesktopSize desktop_size_ = {};
|
DesktopSize desktop_size_ = {};
|
||||||
DesktopCaptureOptions options_ = {};
|
DesktopCaptureOptions options_ = {};
|
||||||
|
@ -98,8 +100,10 @@ class BaseCapturerPipeWire : public DesktopCapturer {
|
||||||
|
|
||||||
bool portal_init_failed_ = false;
|
bool portal_init_failed_ = false;
|
||||||
|
|
||||||
|
std::unique_ptr<EglDmaBuf> egl_dmabuf_;
|
||||||
|
|
||||||
|
void Init();
|
||||||
void InitPortal();
|
void InitPortal();
|
||||||
void InitPipeWire();
|
|
||||||
void InitPipeWireTypes();
|
void InitPipeWireTypes();
|
||||||
|
|
||||||
pw_stream* CreateReceivingStream();
|
pw_stream* CreateReceivingStream();
|
||||||
|
|
11
modules/desktop_capture/linux/drm.sigs
Normal file
11
modules/desktop_capture/linux/drm.sigs
Normal file
|
@ -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);
|
584
modules/desktop_capture/linux/egl_dmabuf.cc
Normal file
584
modules/desktop_capture/linux/egl_dmabuf.cc
Normal file
|
@ -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 <asm/ioctl.h>
|
||||||
|
#include <dlfcn.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 "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<std::string> 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<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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<std::string> 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<std::string> 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<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;
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::optional<std::string> 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<drmDevicePtr> 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
|
61
modules/desktop_capture/linux/egl_dmabuf.h
Normal file
61
modules/desktop_capture/linux/egl_dmabuf.h
Normal file
|
@ -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 <epoxy/egl.h>
|
||||||
|
#include <epoxy/gl.h>
|
||||||
|
#include <gbm.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "absl/types/optional.h"
|
||||||
|
#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_;
|
||||||
|
|
||||||
|
absl::optional<std::string> GetRenderNode();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // 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
|
// pipewire.h
|
||||||
void pw_init(int *argc, char **argv[]);
|
void pw_init(int *argc, char **argv[]);
|
||||||
|
const char* pw_get_library_version();
|
||||||
|
|
||||||
// properties.h
|
// properties.h
|
||||||
pw_properties * pw_properties_new_string(const char *args);
|
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);
|
void pw_thread_loop_unlock(pw_thread_loop *loop);
|
||||||
pw_loop * pw_thread_loop_get_loop(pw_thread_loop *loop);
|
pw_loop * pw_thread_loop_get_loop(pw_thread_loop *loop);
|
||||||
|
|
||||||
|
|
||||||
// context.h
|
// context.h
|
||||||
void pw_context_destroy(pw_context *context);
|
void pw_context_destroy(pw_context *context);
|
||||||
pw_context *pw_context_new(pw_loop *main_loop, pw_properties *props, size_t user_data_size);
|
pw_context *pw_context_new(pw_loop *main_loop, pw_properties *props, size_t user_data_size);
|
|
@ -5,4 +5,5 @@ extern "C" {
|
||||||
|
|
||||||
#include <pipewire/pipewire.h>
|
#include <pipewire/pipewire.h>
|
||||||
|
|
||||||
|
#include <xf86drm.h>
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,7 +132,7 @@ declare_args() {
|
||||||
# supported Ubuntu and Debian distributions.
|
# supported Ubuntu and Debian distributions.
|
||||||
rtc_use_pipewire = is_linux && use_sysroot
|
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
|
rtc_link_pipewire = false
|
||||||
|
|
||||||
# Enable to use the Mozilla internal settings.
|
# Enable to use the Mozilla internal settings.
|
||||||
|
|
Loading…
Reference in a new issue