linux-capture: Add PipeWire-based capture

Add a new Linux capture based on PipeWire [1] and the Desktop portal [2].

This new capture starts by asking the Desktop portal for a screencapture session.
There are quite a few D-Bus calls involved in this, but the key points are:

 1. A connection to org.freedesktop.portal.ScreenCast is estabilished, and the
    available cursor modes are updated.

 2. CreateSession() is called. This is the first step of the negotiation.

 3. SelectSources() is called. This is when a system dialog pops up asking the
    user to either select a monitor (desktop capture) or a window (window capture).

 4. Start() is called. This signals the compositor that it can setup a PipeWire
    stream, and start sending buffers.

The reply to this fourth call gives OBS Studio the PipeWire fd, and the id of the
PipeWire node where the buffers are being sent to. This allows creating a consumer
PipeWire stream, and receive the buffers.

Metadata cursor is always preferred, but on the lack of it, we ask the stream for
an embedded cursor (i.e. the cursor is drawn at the buffer, and OBS Studio has no
control over it.)

Window capturing is implemented as a crop operation on the buffer. Compositors
can send big buffers, and a crop rectangle, and this is used to paint a subregion
of the buffer in the scene.

The new capture is only loaded when running on EGL, since it depends on EGL to
call gs_texture_create_from_dmabuf().

[1] https://pipewire.org/
[2] https://github.com/flatpak/xdg-desktop-portal/
This commit is contained in:
Georges Basile Stavracas Neto 2021-02-25 16:57:33 -03:00
parent a0464b0f8f
commit c2f8b2058b
8 changed files with 1642 additions and 6 deletions

View file

@ -0,0 +1,121 @@
#.rst:
# FindPipeWire
# -------
#
# Try to find PipeWire on a Unix system.
#
# This will define the following variables:
#
# ``PIPEWIRE_FOUND``
# True if (the requested version of) PipeWire is available
# ``PIPEWIRE_VERSION``
# The version of PipeWire
# ``PIPEWIRE_LIBRARIES``
# This can be passed to target_link_libraries() instead of the ``PipeWire::PipeWire``
# target
# ``PIPEWIRE_INCLUDE_DIRS``
# This should be passed to target_include_directories() if the target is not
# used for linking
# ``PIPEWIRE_DEFINITIONS``
# This should be passed to target_compile_options() if the target is not
# used for linking
#
# If ``PIPEWIRE_FOUND`` is TRUE, it will also define the following imported target:
#
# ``PipeWire::PipeWire``
# The PipeWire library
#
# In general we recommend using the imported target, as it is easier to use.
# Bear in mind, however, that if the target is in the link interface of an
# exported library, it must be made available by the package config file.
#=============================================================================
# Copyright 2014 Alex Merry <alex.merry@kde.org>
# Copyright 2014 Martin Gräßlin <mgraesslin@kde.org>
# Copyright 2018-2020 Jan Grulich <jgrulich@redhat.com>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. The name of the author may not be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#=============================================================================
# Use pkg-config to get the directories and then use these values
# in the FIND_PATH() and FIND_LIBRARY() calls
find_package(PkgConfig QUIET)
pkg_search_module(PKG_PIPEWIRE QUIET libpipewire-0.3)
pkg_search_module(PKG_SPA QUIET libspa-0.2)
set(PIPEWIRE_DEFINITIONS "${PKG_PIPEWIRE_CFLAGS}" "${PKG_SPA_CFLAGS}")
set(PIPEWIRE_VERSION "${PKG_PIPEWIRE_VERSION}")
find_path(PIPEWIRE_INCLUDE_DIRS
NAMES
pipewire/pipewire.h
HINTS
${PKG_PIPEWIRE_INCLUDE_DIRS}
${PKG_PIPEWIRE_INCLUDE_DIRS}/pipewire-0.3
)
find_path(SPA_INCLUDE_DIRS
NAMES
spa/param/props.h
HINTS
${PKG_SPA_INCLUDE_DIRS}
${PKG_SPA_INCLUDE_DIRS}/spa-0.2
)
find_library(PIPEWIRE_LIBRARIES
NAMES
pipewire-0.3
HINTS
${PKG_PIPEWIRE_LIBRARY_DIRS}
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(PipeWire
FOUND_VAR
PIPEWIRE_FOUND
REQUIRED_VARS
PIPEWIRE_LIBRARIES
PIPEWIRE_INCLUDE_DIRS
SPA_INCLUDE_DIRS
VERSION_VAR
PIPEWIRE_VERSION
)
if(PIPEWIRE_FOUND AND NOT TARGET PipeWire::PipeWire)
add_library(PipeWire::PipeWire UNKNOWN IMPORTED)
set_target_properties(PipeWire::PipeWire PROPERTIES
IMPORTED_LOCATION "${PIPEWIRE_LIBRARIES}"
INTERFACE_COMPILE_OPTIONS "${PIPEWIRE_DEFINITIONS}"
INTERFACE_INCLUDE_DIRECTORIES "${PIPEWIRE_INCLUDE_DIRS};${SPA_INCLUDE_DIRS}"
)
endif()
mark_as_advanced(PIPEWIRE_LIBRARIES PIPEWIRE_INCLUDE_DIRS)
include(FeatureSummary)
set_package_properties(PipeWire PROPERTIES
URL "https://www.pipewire.org"
DESCRIPTION "PipeWire - multimedia processing"
)

View file

@ -44,6 +44,41 @@ set(linux-capture_LIBRARIES
${XCB_LIBRARIES}
)
option(ENABLE_PIPEWIRE "Enable PipeWire support" ON)
if(ENABLE_PIPEWIRE)
find_package(PipeWire REQUIRED)
find_package(Gio REQUIRED)
add_definitions(-DENABLE_PIPEWIRE)
set(linux-capture_INCLUDES
${linux-capture_INCLUDES}
${GIO_INCLUDE_DIRS}
${PIPEWIRE_INCLUDE_DIRS}
)
add_definitions(
${GIO_DEFINITIONS}
${PIPEWIRE_DEFINITIONS}
)
set(linux-capture_SOURCES
${linux-capture_SOURCES}
pipewire.c
pipewire-capture.c
)
set(linux-capture_HEADERS
${linux-capture_HEADERS}
pipewire.h
pipewire-capture.h
)
set(linux-capture_LIBRARIES
${linux-capture_LIBRARIES}
${GIO_LIBRARIES}
${PIPEWIRE_LIBRARIES}
)
endif()
include_directories(SYSTEM
${linux-capture_INCLUDES}
)

View file

@ -13,3 +13,8 @@ SwapRedBlue="Swap red and blue"
LockX="Lock X server when capturing"
IncludeXBorder="Include X Border"
ExcludeAlpha="Use alpha-less texture format (Mesa workaround)"
PipeWireDesktopCapture="Screen Capture (PipeWire)"
PipeWireSelectMonitor="Select Monitor"
PipeWireSelectWindow="Select Window"
PipeWireWindowCapture="Window Capture (PipeWire)"
ShowCursor="Show Cursor"

View file

@ -17,6 +17,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <obs-module.h>
#include <obs-nix-platform.h>
#ifdef ENABLE_PIPEWIRE
#include "pipewire-capture.h"
#endif
OBS_DECLARE_MODULE()
OBS_MODULE_USE_DEFAULT_LOCALE("linux-xshm", "en-US")
MODULE_EXPORT const char *obs_module_description(void)
@ -31,17 +35,24 @@ extern void xcomposite_unload(void);
bool obs_module_load(void)
{
if (obs_get_nix_platform() != OBS_NIX_PLATFORM_X11_GLX) {
blog(LOG_ERROR, "linux-capture cannot run on EGL platforms");
return false;
if (obs_get_nix_platform() == OBS_NIX_PLATFORM_X11_GLX) {
obs_register_source(&xshm_input);
xcomposite_load();
#ifdef ENABLE_PIPEWIRE
} else {
pipewire_capture_load();
#endif
}
obs_register_source(&xshm_input);
xcomposite_load();
return true;
}
void obs_module_unload(void)
{
xcomposite_unload();
if (obs_get_nix_platform() == OBS_NIX_PLATFORM_X11_GLX)
xcomposite_unload();
#ifdef ENABLE_PIPEWIRE
else
pipewire_capture_unload();
#endif
}

View file

@ -0,0 +1,155 @@
/* pipewire-capture.c
*
* Copyright 2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "pipewire.h"
/* obs_source_info methods */
static const char *pipewire_desktop_capture_get_name(void *data)
{
UNUSED_PARAMETER(data);
return obs_module_text("PipeWireDesktopCapture");
}
static const char *pipewire_window_capture_get_name(void *data)
{
UNUSED_PARAMETER(data);
return obs_module_text("PipeWireWindowCapture");
}
static void *pipewire_desktop_capture_create(obs_data_t *settings,
obs_source_t *source)
{
return obs_pipewire_create(DESKTOP_CAPTURE, settings, source);
}
static void *pipewire_window_capture_create(obs_data_t *settings,
obs_source_t *source)
{
return obs_pipewire_create(WINDOW_CAPTURE, settings, source);
}
static void pipewire_capture_destroy(void *data)
{
obs_pipewire_destroy(data);
}
static void pipewire_capture_get_defaults(obs_data_t *settings)
{
obs_pipewire_get_defaults(settings);
}
static obs_properties_t *pipewire_capture_get_properties(void *data)
{
enum obs_pw_capture_type capture_type;
obs_pipewire_data *obs_pw = data;
capture_type = obs_pipewire_get_capture_type(obs_pw);
switch (capture_type) {
case DESKTOP_CAPTURE:
return obs_pipewire_get_properties(data,
"PipeWireSelectMonitor");
case WINDOW_CAPTURE:
return obs_pipewire_get_properties(data,
"PipeWireSelectWindow");
default:
return NULL;
}
}
static void pipewire_capture_update(void *data, obs_data_t *settings)
{
obs_pipewire_update(data, settings);
}
static void pipewire_capture_show(void *data)
{
obs_pipewire_show(data);
}
static void pipewire_capture_hide(void *data)
{
obs_pipewire_hide(data);
}
static uint32_t pipewire_capture_get_width(void *data)
{
return obs_pipewire_get_width(data);
}
static uint32_t pipewire_capture_get_height(void *data)
{
return obs_pipewire_get_height(data);
}
static void pipewire_capture_video_render(void *data, gs_effect_t *effect)
{
obs_pipewire_video_render(data, effect);
}
void pipewire_capture_load(void)
{
// Desktop capture
const struct obs_source_info pipewire_desktop_capture_info = {
.id = "pipewire-desktop-capture-source",
.type = OBS_SOURCE_TYPE_INPUT,
.output_flags = OBS_SOURCE_VIDEO,
.get_name = pipewire_desktop_capture_get_name,
.create = pipewire_desktop_capture_create,
.destroy = pipewire_capture_destroy,
.get_defaults = pipewire_capture_get_defaults,
.get_properties = pipewire_capture_get_properties,
.update = pipewire_capture_update,
.show = pipewire_capture_show,
.hide = pipewire_capture_hide,
.get_width = pipewire_capture_get_width,
.get_height = pipewire_capture_get_height,
.video_render = pipewire_capture_video_render,
.icon_type = OBS_ICON_TYPE_DESKTOP_CAPTURE,
};
obs_register_source(&pipewire_desktop_capture_info);
// Window capture
const struct obs_source_info pipewire_window_capture_info = {
.id = "pipewire-window-capture-source",
.type = OBS_SOURCE_TYPE_INPUT,
.output_flags = OBS_SOURCE_VIDEO,
.get_name = pipewire_window_capture_get_name,
.create = pipewire_window_capture_create,
.destroy = pipewire_capture_destroy,
.get_defaults = pipewire_capture_get_defaults,
.get_properties = pipewire_capture_get_properties,
.update = pipewire_capture_update,
.show = pipewire_capture_show,
.hide = pipewire_capture_hide,
.get_width = pipewire_capture_get_width,
.get_height = pipewire_capture_get_height,
.video_render = pipewire_capture_video_render,
.icon_type = OBS_ICON_TYPE_WINDOW_CAPTURE,
};
obs_register_source(&pipewire_window_capture_info);
pw_init(NULL, NULL);
}
void pipewire_capture_unload(void)
{
pw_deinit();
}

View file

@ -0,0 +1,24 @@
/* pipewire-capture.h
*
* Copyright 2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
void pipewire_capture_load(void);
void pipewire_capture_unload(void);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,53 @@
/* pipewire.h
*
* Copyright 2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <obs-module.h>
#include <pipewire/pipewire.h>
typedef struct _obs_pipewire_data obs_pipewire_data;
enum obs_pw_capture_type {
DESKTOP_CAPTURE = 1,
WINDOW_CAPTURE = 2,
};
void *obs_pipewire_create(enum obs_pw_capture_type capture_type,
obs_data_t *settings, obs_source_t *source);
void obs_pipewire_destroy(obs_pipewire_data *obs_pw);
void obs_pipewire_get_defaults(obs_data_t *settings);
obs_properties_t *obs_pipewire_get_properties(obs_pipewire_data *obs_pw,
const char *reload_string_id);
void obs_pipewire_update(obs_pipewire_data *obs_pw, obs_data_t *settings);
void obs_pipewire_show(obs_pipewire_data *obs_pw);
void obs_pipewire_hide(obs_pipewire_data *obs_pw);
uint32_t obs_pipewire_get_width(obs_pipewire_data *obs_pw);
uint32_t obs_pipewire_get_height(obs_pipewire_data *obs_pw);
void obs_pipewire_video_render(obs_pipewire_data *obs_pw, gs_effect_t *effect);
enum obs_pw_capture_type
obs_pipewire_get_capture_type(obs_pipewire_data *obs_pw);