Merge branch 'xdpscreencastbin' into 'main'

Video source bin wrapping pipewiresrc using xdg-desktop-portal

See merge request gstreamer/gst-plugins-rs!1405
This commit is contained in:
Rubén Gonzalez 2024-04-27 23:13:28 +00:00
commit 0ea8200cfb
9 changed files with 448 additions and 0 deletions

View file

@ -45,6 +45,8 @@ members = [
"utils/togglerecord",
"utils/tracers",
"utils/uriplaylistbin",
"utils/xdgscreencapsrc",
"video/cdg",
"video/closedcaption",

View file

@ -145,6 +145,8 @@ You will find the following plugins in this repository:
- `uriplaylistbin`: Helper bin to gaplessly play a list of URIs.
- `xdgscreencapsrc`: GStreamer xdg-desktop-portal screen capture plugin
## Building
gst-plugins-rs relies on [cargo-c](https://github.com/lu-zero/cargo-c/) to

View file

@ -205,6 +205,7 @@ plugins = {
'extra-deps': {'cairo-gobject': []},
},
'gopbuffer': {'library': 'libgstgopbuffer'},
'xdgscreencapsrc': {'library': 'libgstxdgscreencapsrc'},
}
if get_option('examples').allowed()

View file

@ -47,6 +47,7 @@ option('livesync', type: 'feature', value: 'auto', description: 'Build livesync
option('togglerecord', type: 'feature', value: 'auto', description: 'Build togglerecord plugin')
option('tracers', type: 'feature', value: 'auto', description: 'Build tracers plugin')
option('uriplaylistbin', type: 'feature', value: 'auto', description: 'Build uriplaylistbin plugin')
option('xdgscreencapsrc', type: 'feature', value: 'auto', description: 'Build xdgscreencapsrc plugin')
# video
option('cdg', type: 'feature', value: 'auto', description: 'Build cdg plugin')

View file

@ -0,0 +1,47 @@
[package]
name = "gst-plugin-xdgscreencapsrc"
version = "0.12.0-alpha.1"
authors = ["Ruben Gonzalez <rgonzalez@fluendo.com"]
license = "MPL-2.0"
description = "GStreamer xdg-desktop-portal screen capture plugin"
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
edition = "2021"
rust-version = "1.70"
[dependencies]
gst.workspace = true
parking_lot = "0.12"
ashpd = {version = "0.6", default-features = false, features = ["tokio"]}
tokio = { version = "1", features = ["fs", "macros", "rt-multi-thread", "time"] }
once_cell.workspace = true
[dev-dependencies]
either = "1.0"
gst-check.workspace = true
[lib]
name = "gstxdgscreencapsrc"
crate-type = ["cdylib", "rlib"]
path = "src/lib.rs"
[build-dependencies]
gst-plugin-version-helper.workspace = true
[features]
static = []
capi = []
doc = ["gst/v1_18"]
[package.metadata.capi]
min_version = "0.9.21"
[package.metadata.capi.header]
enabled = false
[package.metadata.capi.library]
install_subdir = "gstreamer-1.0"
versioning = false
import_library = false
[package.metadata.capi.pkg_config]
requires_private = "gstreamer-1.0, gstreamer-audio-1.0, gstreamer-video-1.0, gobject-2.0, glib-2.0, gmodule-2.0"

View file

@ -0,0 +1,3 @@
fn main() {
gst_plugin_version_helper::info()
}

View file

@ -0,0 +1,34 @@
// Copyright (C) 2023 Ruben Gonzalez <rgonzalez@fluendo.com
//
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at
// <https://mozilla.org/MPL/2.0/>.
//
// SPDX-License-Identifier: MPL-2.0
#![allow(clippy::non_send_fields_in_send_ty, unused_doc_comments)]
/**
* plugin-xdgscreencapsrc:
*
* Since: plugins-rs-0.12.0
*/
use gst::glib;
mod xdgscreencapsrc;
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
xdgscreencapsrc::register(plugin)
}
gst::plugin_define!(
xdgscreencapsrc,
env!("CARGO_PKG_DESCRIPTION"),
plugin_init,
concat!(env!("CARGO_PKG_VERSION"), "-", "COMMIT_ID"),
// FIXME: MPL-2.0 is only allowed since 1.18.3 (as unknown) and 1.20 (as known)
"MPL",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_REPOSITORY"),
env!("BUILD_REL_DATE")
);

View file

@ -0,0 +1,333 @@
// Copyright (C) 2023 Ruben Gonzalez <rgonzalez@fluendo.com
//
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at
// <https://mozilla.org/MPL/2.0/>.
//
// SPDX-License-Identifier: MPL-2.0
/**
* element-xdgscreencapsrc:
* @short_description: Source element wrapping pipewiresrc using xdg-desktop-portal to start a screencast session.
*
* Based on https://gitlab.gnome.org/-/snippets/19 using https://crates.io/crates/ashpd
*
* ## Example pipeline
* ```bash
* gst-launch-1.0 -v xdgscreencapsrc ! videoconvert ! identity silent=false ! gtkwaylandsink
* ```
*
* Since: plugins-rs-0.12.0
*/
use gst::glib;
use gst::prelude::*;
use gst::subclass::prelude::*;
use ashpd::{
desktop::screencast::{PersistMode, Screencast},
WindowIdentifier,
};
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, glib::Enum)]
#[repr(u32)]
#[enum_type(name = "SourceType")]
pub enum SourceType {
#[enum_value(name = "A monitor", nick = "monitor")]
Monitor,
#[enum_value(name = "A specific window", nick = "window")]
Window,
#[enum_value(name = "Virtual", nick = "virtual")]
Virtual,
#[enum_value(name = "monitor+window+virtual", nick = "all")]
All,
}
impl From<SourceType> for ashpd::enumflags2::BitFlags<ashpd::desktop::screencast::SourceType, u32> {
fn from(v: SourceType) -> Self {
use ashpd::desktop::screencast;
match v {
SourceType::Monitor => screencast::SourceType::Monitor.into(),
SourceType::Window => screencast::SourceType::Window.into(),
SourceType::Virtual => screencast::SourceType::Virtual.into(),
SourceType::All => {
screencast::SourceType::Monitor
| screencast::SourceType::Window
| screencast::SourceType::Virtual
}
}
}
}
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, glib::Enum)]
#[repr(u32)]
#[enum_type(name = "CursorMode")]
pub enum CursorMode {
#[enum_value(
name = "The cursor is not part of the screen cast stream",
nick = "hidden"
)]
Hidden,
#[enum_value(
name = "The cursor is embedded as part of the stream buffers",
nick = "embedded"
)]
Embedded,
#[enum_value(
name = "The cursor is not part of the screen cast stream, but sent as PipeWire stream metadata. Not implemented",
nick = "metadata"
)]
Metadata,
}
impl From<CursorMode> for ashpd::desktop::screencast::CursorMode {
fn from(v: CursorMode) -> Self {
use ashpd::desktop::screencast;
match v {
CursorMode::Hidden => screencast::CursorMode::Hidden,
CursorMode::Embedded => screencast::CursorMode::Embedded,
CursorMode::Metadata => unimplemented!(),
}
}
}
use once_cell::sync::Lazy;
use parking_lot::Mutex;
pub fn block_on<F: std::future::Future>(future: F) -> F::Output {
static TOKIO_RT: once_cell::sync::Lazy<tokio::runtime::Runtime> =
once_cell::sync::Lazy::new(|| {
tokio::runtime::Builder::new_current_thread()
.enable_io()
.enable_time()
.build()
.expect("launch of single-threaded tokio runtime")
});
TOKIO_RT.block_on(future)
}
async fn portal_main(cursor_mode: CursorMode, source_type: SourceType) -> ashpd::Result<u32> {
let proxy = Screencast::new().await?;
let session = proxy.create_session().await?;
proxy
.select_sources(
&session,
cursor_mode.into(),
source_type.into(),
false,
None,
PersistMode::DoNot,
)
.await?;
let response = proxy
.start(&session, &WindowIdentifier::default())
.await?
.response()?;
if let Some(first_value) = response.streams().iter().next() {
let id = first_value.pipe_wire_node_id();
Ok(id)
} else {
Err(ashpd::Error::NoResponse)
}
}
const DEFAULT_CURSOR_MODE: CursorMode = CursorMode::Hidden;
const DEFAULT_SOURCE_TYPE: SourceType = SourceType::All;
#[derive(Debug, Clone, Copy)]
struct Settings {
cursor_mode: CursorMode,
source_type: SourceType,
}
impl Default for Settings {
fn default() -> Self {
Settings {
cursor_mode: DEFAULT_CURSOR_MODE,
source_type: DEFAULT_SOURCE_TYPE,
}
}
}
pub struct XdpScreenCast {
settings: Mutex<Settings>,
src: gst::Element,
srcpad: gst::GhostPad,
}
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
gst::DebugCategory::new(
"xdgscreencapsrc",
gst::DebugColorFlags::empty(),
Some("XDP Screen Cast Bin"),
)
});
impl XdpScreenCast {}
#[glib::object_subclass]
impl ObjectSubclass for XdpScreenCast {
const NAME: &'static str = "GstXdpScreenCast";
type Type = super::XdpScreenCast;
type ParentType = gst::Bin;
fn with_class(klass: &Self::Class) -> Self {
let settings = Mutex::new(Settings::default());
let src = gst::ElementFactory::make("pipewiresrc").build().unwrap();
let templ = klass.pad_template("src").unwrap();
let srcpad = gst::GhostPad::from_template(&templ);
Self {
settings,
src,
srcpad,
}
}
}
impl ObjectImpl for XdpScreenCast {
// based on https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.ScreenCast.html#org-freedesktop-portal-screencast-selectsources
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![
glib::ParamSpecEnum::builder_with_default::<CursorMode>(
"cursor-mode",
DEFAULT_CURSOR_MODE,
)
.nick("cursor mode")
.blurb("Determines how the cursor will be drawn in the screen cast stream")
.mutable_ready()
.build(),
glib::ParamSpecEnum::builder_with_default::<SourceType>(
"source-type",
DEFAULT_SOURCE_TYPE,
)
.nick("source type")
.blurb("Sets the types of content to record")
.mutable_ready()
.build(),
]
});
PROPERTIES.as_ref()
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"cursor-mode" => {
let mut settings = self.settings.lock();
let cursor_mode = value.get().expect("type checked upstream");
gst::debug!(
CAT,
imp: self,
"Setting cursor-mode from {:?} to {:?}",
settings.cursor_mode,
cursor_mode
);
settings.cursor_mode = cursor_mode;
}
"source-type" => {
let mut settings = self.settings.lock();
let source_type = value.get().expect("type checked upstream");
gst::debug!(
CAT,
imp: self,
"Setting source-type from {:?} to {:?}",
settings.source_type,
source_type
);
settings.source_type = source_type;
}
_ => unimplemented!(),
}
}
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"cursor-mode" => {
let settings = self.settings.lock();
settings.cursor_mode.to_value()
}
"source-type" => {
let settings = self.settings.lock();
settings.source_type.to_value()
}
_ => unimplemented!(),
}
}
fn constructed(&self) {
self.parent_constructed();
let obj = self.obj();
obj.add(&self.src).unwrap();
self.srcpad
.set_target(Some(&self.src.static_pad("src").unwrap()))
.unwrap();
obj.add_pad(&self.srcpad).unwrap();
}
}
impl GstObjectImpl for XdpScreenCast {}
impl ElementImpl for XdpScreenCast {
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
gst::subclass::ElementMetadata::new(
"xdg-desktop-portal screen capture",
"Generic",
"Source element wrapping pipewiresrc using \
xdg-desktop-portal to start a screencast session.",
"Ruben Gonzalez <rgonzalez@fluendo.com>",
)
});
Some(&*ELEMENT_METADATA)
}
fn pad_templates() -> &'static [gst::PadTemplate] {
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
let caps = gst::Caps::new_any();
let src_pad_template = gst::PadTemplate::new(
"src",
gst::PadDirection::Src,
gst::PadPresence::Always,
&caps,
)
.unwrap();
vec![src_pad_template]
});
PAD_TEMPLATES.as_ref()
}
fn change_state(
&self,
transition: gst::StateChange,
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
gst::debug!(CAT, imp: self, "Changing state {:?}", transition);
let settings = self.settings.lock();
let success = self.parent_change_state(transition)?;
if transition == gst::StateChange::NullToReady {
if let Ok(fd) = block_on(portal_main(settings.cursor_mode, settings.source_type)) {
self.src.set_property("fd", fd as i32);
} else {
return Err(gst::StateChangeError);
}
}
Ok(success)
}
}
impl BinImpl for XdpScreenCast {}

View file

@ -0,0 +1,25 @@
// Copyright (C) 2023 Ruben Gonzalez <rgonzalez@fluendo.com
//
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at
// <https://mozilla.org/MPL/2.0/>.
//
// SPDX-License-Identifier: MPL-2.0
use gst::glib;
use gst::prelude::*;
mod imp;
glib::wrapper! {
pub struct XdpScreenCast(ObjectSubclass<imp::XdpScreenCast>) @extends gst::Bin, gst::Element, gst::Object;
}
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
gst::Element::register(
Some(plugin),
"xdgscreencapsrc",
gst::Rank::NONE,
XdpScreenCast::static_type(),
)
}