Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions examples/screensharing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,4 @@ livekit = { path = "../../livekit", features = ["native-tls"]}
livekit-api = { path = "../../livekit-api"}
log = "0.4"
clap = { version = "4.0", features = ["derive"] }

[target.'cfg(target_os = "linux")'.dependencies]
glib = "0.21.1"
inquire = "0.9"
69 changes: 44 additions & 25 deletions examples/screensharing/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ use livekit::options::{TrackPublishOptions, VideoCodec};
use livekit::prelude::*;
use livekit::track::{LocalTrack, LocalVideoTrack, TrackSource};
use livekit::webrtc::desktop_capturer::{
CaptureResult, DesktopCapturer, DesktopCapturerOptions, DesktopFrame,
CaptureResult, CaptureSourceType, DesktopCapturer, DesktopCapturerOptions, DesktopFrame,
};
use livekit::webrtc::native::yuv_helper;
use livekit::webrtc::prelude::{
I420Buffer, RtcVideoSource, VideoBuffer, VideoFrame, VideoResolution, VideoRotation,
};
use livekit::webrtc::video_source::native::NativeVideoSource;
use livekit_api::access_token;
use std::collections::HashMap;
use std::env;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::sync::Mutex;

Expand All @@ -34,19 +36,12 @@ struct Args {

#[tokio::main]
async fn main() {
env_logger::init();
env_logger::builder()
.filter(Some(env!("CARGO_CRATE_NAME")), log::LevelFilter::Info)
.parse_default_env()
.init();
let args = Args::parse();

#[cfg(target_os = "linux")]
{
/* This is needed for getting the system picker for screen sharing. */
use glib::MainLoop;
let main_loop = MainLoop::new(None, false);
let _handle = std::thread::spawn(move || {
main_loop.run();
});
}

let url = env::var("LIVEKIT_URL").expect("LIVEKIT_URL is not set");
let api_key = env::var("LIVEKIT_API_KEY").expect("LIVEKIT_API_KEY is not set");
let api_secret = env::var("LIVEKIT_API_SECRET").expect("LIVEKIT_API_SECRET is not set");
Expand Down Expand Up @@ -96,11 +91,11 @@ async fn main() {
let callback = move |result: CaptureResult, frame: DesktopFrame| {
match result {
CaptureResult::ErrorTemporary => {
log::info!("Error temporary");
log::debug!("Error temporary");
return;
}
CaptureResult::ErrorPermanent => {
log::info!("Error permanent");
log::debug!("Error permanent");
return;
}
_ => {}
Expand Down Expand Up @@ -143,26 +138,50 @@ async fn main() {
{
options.set_sck_system_picker(args.use_system_picker);
}
options.set_window_capturer(args.capture_window);
options.set_capture_source_type(if args.capture_window {
CaptureSourceType::Window
} else {
CaptureSourceType::Screen
});
options.set_include_cursor(args.capture_cursor);
#[cfg(target_os = "linux")]
{
if std::env::var("WAYLAND_DISPLAY").is_ok() {
options.set_pipewire_capturer(true);
}
}

let mut capturer =
DesktopCapturer::new(callback, options).expect("Failed to create desktop capturer");
let sources = capturer.get_source_list();
log::info!("Found {} sources", sources.len());
let selected_source = if sources.len() == 0 {
None
// On Wayland, the XDG Desktop Portal presents a UI for the user
// to select the source and libwebrtc only returns that one source,
// so do not present a redundant UI here.
} else if sources.len() == 1 {
Some(sources.first().unwrap().clone())
} else {
let options: Vec<_> = sources.clone().into_iter().map(|s| s.to_string()).collect();
let map: HashMap<_, _> = sources.into_iter().map(|s| (s.to_string(), s)).collect();
match inquire::Select::new("Select desktop capture source:", options).prompt() {
Ok(s) => Some(map.get(&s).unwrap().clone()),
Err(e) => panic!("{e:?}"),
}
};

let selected_source = sources.first().cloned();
log::info!("Starting desktop capture. Press Ctrl + C to quit.");
capturer.start_capture(selected_source);

let now = tokio::time::Instant::now();
while now.elapsed() < tokio::time::Duration::from_secs(30) {
let ctrl_c_received = Arc::new(AtomicBool::new(false));
tokio::spawn({
let ctrl_c_received = ctrl_c_received.clone();
async move {
tokio::signal::ctrl_c().await.unwrap();
ctrl_c_received.store(true, Ordering::Release);
}
});

loop {
capturer.capture_frame();
if ctrl_c_received.load(Ordering::Acquire) == true {
log::info!("Ctrl + C received, stopping desktop capture.");
break;
}
tokio::time::sleep(tokio::time::Duration::from_millis(16)).await;
}
}
12 changes: 12 additions & 0 deletions libwebrtc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,25 @@ license = "Apache-2.0"
description = "Livekit safe bindings to libwebrtc"
repository = "https://github.com/livekit/rust-sdks"

[features]
default = [ "glib-main-loop" ]
# On Wayland, libwebrtc communicates with the XDG Desktop Portal over dbus using GDBus
# from the gio library. This requires a running glib event loop to work.
# If your application already has a glib event loop running for another reason, for
# example using the GTK or GStreamer Rust bindings, it does not need to setup another
# glib event loop for this and can opt out by disabling this default glib-main-loop feature.
glib-main-loop = [ "dep:glib" ]

[dependencies]
livekit-protocol = { workspace = true }
log = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1.0"

[target.'cfg(target_os = "linux")'.dependencies]
glib = { version = "0.21.3", optional = true }

[target.'cfg(target_os = "android")'.dependencies]
jni = "0.21"

Expand Down
13 changes: 3 additions & 10 deletions libwebrtc/src/desktop_capturer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.

use crate::imp::desktop_capturer as imp_dc;
pub use imp_dc::CaptureSourceType;

/// Configuration options for creating a desktop capturer.
///
Expand Down Expand Up @@ -43,8 +44,8 @@ impl DesktopCapturerOptions {
}

/// Sets whether to capture windows instead of the entire screen.
pub fn set_window_capturer(&mut self, window_capturer: bool) {
self.sys_handle = self.sys_handle.with_window_capturer(window_capturer);
pub fn set_capture_source_type(&mut self, t: CaptureSourceType) {
self.sys_handle = self.sys_handle.with_capture_source_type(t);
}

/// Sets whether to allow the ScreenCaptureKit (SCK) capturer on macOS.
Expand Down Expand Up @@ -81,14 +82,6 @@ impl DesktopCapturerOptions {
pub fn set_directx_capturer(&mut self, allow_directx_capturer: bool) {
self.sys_handle = self.sys_handle.with_directx_capturer(allow_directx_capturer);
}

/// Sets whether to allow the PipeWire capturer on Linux.
///
/// It should be enabled when using Wayland.
#[cfg(target_os = "linux")]
pub fn set_pipewire_capturer(&mut self, allow_pipewire_capturer: bool) {
self.sys_handle = self.sys_handle.with_pipewire_capturer(allow_pipewire_capturer);
}
}

/// A desktop capturer for capturing screens or windows.
Expand Down
47 changes: 26 additions & 21 deletions libwebrtc/src/native/desktop_capturer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,15 @@
use cxx::UniquePtr;
use webrtc_sys::desktop_capturer::{self as sys_dc, ffi::new_desktop_capturer};

#[derive(Copy, Clone, Debug)]
pub enum CaptureSourceType {
Screen,
Window,
}

#[derive(Copy, Clone, Debug)]
pub struct DesktopCapturerOptions {
pub window_capturer: bool,
pub capture_source_type: CaptureSourceType,
pub include_cursor: bool,
#[cfg(target_os = "macos")]
pub allow_sck_capturer: bool,
Expand All @@ -27,14 +33,12 @@ pub struct DesktopCapturerOptions {
pub allow_wgc_capturer: bool,
#[cfg(target_os = "windows")]
pub allow_directx_capturer: bool,
#[cfg(target_os = "linux")]
pub allow_pipewire_capturer: bool,
}

impl Default for DesktopCapturerOptions {
fn default() -> Self {
Self {
window_capturer: false,
capture_source_type: CaptureSourceType::Screen,
include_cursor: false,
#[cfg(target_os = "macos")]
allow_sck_capturer: true,
Expand All @@ -44,24 +48,26 @@ impl Default for DesktopCapturerOptions {
allow_wgc_capturer: true,
#[cfg(target_os = "windows")]
allow_directx_capturer: true,
#[cfg(target_os = "linux")]
allow_pipewire_capturer: false,
}
}
}

impl DesktopCapturerOptions {
pub(crate) fn new() -> Self {
Self { window_capturer: false, include_cursor: false, ..Default::default() }
Self {
capture_source_type: CaptureSourceType::Screen,
include_cursor: false,
..Default::default()
}
}

pub(crate) fn with_cursor(mut self, include: bool) -> Self {
self.include_cursor = include;
self
}

pub(crate) fn with_window_capturer(mut self, window_capturer: bool) -> Self {
self.window_capturer = window_capturer;
pub(crate) fn with_capture_source_type(mut self, t: CaptureSourceType) -> Self {
self.capture_source_type = t;
self
}

Expand Down Expand Up @@ -89,21 +95,17 @@ impl DesktopCapturerOptions {
self
}

#[cfg(target_os = "linux")]
pub(crate) fn with_pipewire_capturer(mut self, allow_pipewire_capturer: bool) -> Self {
self.allow_pipewire_capturer = allow_pipewire_capturer;
self
}

pub(crate) fn to_sys_handle(&self) -> sys_dc::ffi::DesktopCapturerOptions {
let mut sys_handle = sys_dc::ffi::DesktopCapturerOptions {
window_capturer: self.window_capturer,
window_capturer: match self.capture_source_type {
CaptureSourceType::Screen => false,
CaptureSourceType::Window => true,
},
include_cursor: self.include_cursor,
allow_sck_capturer: false,
allow_sck_system_picker: false,
allow_wgc_capturer: false,
allow_directx_capturer: false,
allow_pipewire_capturer: false,
};
#[cfg(target_os = "macos")]
{
Expand All @@ -115,10 +117,6 @@ impl DesktopCapturerOptions {
sys_handle.allow_wgc_capturer = self.allow_wgc_capturer;
sys_handle.allow_directx_capturer = self.allow_directx_capturer;
}
#[cfg(target_os = "linux")]
{
sys_handle.allow_pipewire_capturer = self.allow_pipewire_capturer;
}
sys_handle
}
}
Expand Down Expand Up @@ -147,6 +145,13 @@ impl DesktopCapturer {
}

pub fn start(&mut self) {
#[cfg(all(target_os = "linux", feature = "glib-main-loop"))]
if std::env::var("WAYLAND_DISPLAY").is_ok() {
let main_loop = glib::MainLoop::new(None, false);
let _handle = std::thread::spawn(move || {
main_loop.run();
});
}
let pin_handle = self.sys_handle.pin_mut();
pin_handle.start();
}
Expand Down
55 changes: 15 additions & 40 deletions webrtc-sys/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,22 +155,23 @@ fn main() {
println!("cargo:rustc-link-lib=dylib=pthread");
println!("cargo:rustc-link-lib=dylib=m");

if std::env::var("LK_CUSTOM_WEBRTC").is_ok() {
for lib_name in ["glib-2.0", "gobject-2.0", "gio-2.0"] {
let lib = pkg_config::probe_library(lib_name).unwrap();
for lib_name in [
"glib-2.0",
"gobject-2.0",
"gio-2.0",
"libdrm",
"gbm",
"x11",
"xfixes",
"xdamage",
"xrandr",
"xcomposite",
"xext",
] {
let lib = pkg_config::probe_library(lib_name).unwrap();
if lib_name == "gio-2.0" {
builder.includes(lib.include_paths);
}
} else {
println!("cargo:rustc-link-lib=dylib=glib-2.0");
println!("cargo:rustc-link-lib=dylib=gobject-2.0");
println!("cargo:rustc-link-lib=dylib=gio-2.0");
add_gio_headers(&mut builder);
}

for lib_name in
["libdrm", "gbm", "x11", "xfixes", "xdamage", "xrandr", "xcomposite", "xext"]
{
pkg_config::probe_library(lib_name).unwrap();
}

match target_arch.as_str() {
Expand Down Expand Up @@ -368,29 +369,3 @@ fn configure_android_sysroot(builder: &mut cc::Build) {

builder.flag(format!("-isysroot{}", sysroot.display()).as_str());
}

fn add_gio_headers(builder: &mut cc::Build) {
let webrtc_dir = webrtc_sys_build::webrtc_dir();
let target_arch = webrtc_sys_build::target_arch();
let target_arch_sysroot = match target_arch.as_str() {
"arm64" => "arm64",
"x64" => "amd64",
_ => panic!("unsupported arch"),
};
let sysroot_path = format!("include/build/linux/debian_bullseye_{target_arch_sysroot}-sysroot");
let sysroot = webrtc_dir.join(sysroot_path);
let glib_path = sysroot.join("usr/include/glib-2.0");
println!("cargo:info=add_gio_headers {}", glib_path.display());

builder.include(&glib_path);
let arch_specific_path = match target_arch.as_str() {
"x64" => "x86_64-linux-gnu",
"arm64" => "aarch64-linux-gnu",
_ => panic!("unsupported target"),
};

let glib_path_config = sysroot.join("usr/lib");
let glib_path_config = glib_path_config.join(arch_specific_path);
let glib_path_config = glib_path_config.join("glib-2.0/include");
builder.include(&glib_path_config);
}
6 changes: 3 additions & 3 deletions webrtc-sys/src/desktop_capturer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ std::unique_ptr<DesktopCapturer> new_desktop_capturer(
}
webrtc_options.set_allow_directx_capturer(options.allow_directx_capturer);
#endif
#ifdef __linux__
webrtc_options.set_allow_pipewire(options.allow_pipewire_capturer);
#ifdef WEBRTC_USE_PIPEWIRE
webrtc_options.set_allow_pipewire(true);
#endif

webrtc_options.set_prefer_cursor_embedded(options.include_cursor);
Expand Down Expand Up @@ -96,4 +96,4 @@ rust::Vec<Source> DesktopCapturer::get_source_list() const {
}
return source_list;
}
} // namespace livekit
} // namespace livekit
1 change: 0 additions & 1 deletion webrtc-sys/src/desktop_capturer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ pub mod ffi {
allow_sck_system_picker: bool,
allow_wgc_capturer: bool,
allow_directx_capturer: bool,
allow_pipewire_capturer: bool,
}

enum CaptureResult {
Expand Down