Skip to content

Conversation

@iparaskev
Copy link
Contributor

@iparaskev iparaskev commented Oct 5, 2025

Implements screen sharing by exposing libwebrtc's DesktopCapturer.

A few notes:

  • I have exposed only a subset of DesktopCaptureOptions which have been tested with Hopp.
  • On macos the only way for the user to select a source when sharing a display is via the system picker as libwebrtc hasn't implemented getting the source list. In our fork I have extended GetSourceList to actually get the available displays without the system picker. I could port my patch to your libwebrtc fork if you want. For selecting windows GetSourceList seems to work.
  • AFAIK when using pipewire the only way to select a source is via the system picker.
  • I have put the commit on top of another open PR I have, which enables buffer scaling. This makes sharing the buffer much easier, because we don't need to know in advance the source dims. If you have another way for scaling buffers I would be happy to use it instead of my patch.

Tested on:

  • windows 11
  • macOS Tahoe
  • ubuntu 25.04 (wayland). This needs a new libwebrtc build as the published one doesn't include the pipewire capturer. Building it using this commit should work.

This is related to #92 and zed-industries/zed#28754.

Comment on lines +40 to +48
#[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();
});
}
Copy link
Contributor

@Be-ing Be-ing Oct 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the need for this GLib event loop? With the GStreamer Rust bindings for example, GStreamer events are usually handled by a GLib event loop, but GStreamer has an API to register an event callback that the library calls internally, which can be used from Rust to send events to a Rust thread or async stream over a channel. This way the Rust program calling the library doesn't need to setup its own GLib event loop. Here's an example: https://codeberg.org/Be.ing/dance-video-recorder/src/commit/4aab73f5514ff08000ef330f221dc6035831a4ee/src/camera_monitor.rs#L22

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks I didn't know about this. I will update the example to do it this way.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if such an approach is feasible without a clearer idea of what the need for the GLib event loop is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the example I linked above, the GStreamer bindings are creating a futures_channel::mpsc under the hood to create an ergonomic Rust stream API: https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/blob/c533fe960af057c55916ba3fb48c42837da98565/gstreamer/src/bus.rs#L345

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the need for the GLib event loop is

The event loop is needed for opening the system ui picker. It is related to the xdg_portal setup. Maybe there is something here. I need to have a better look.

Copy link
Contributor

@Be-ing Be-ing Oct 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice if Livekit handled starting the GLib main loop so users of the library wouldn't have to, but that wouldn't be good for applications that are already using GLib for something such as the GStreamer or GTK Rust bindings. So, best to leave it to the user of the library with documentation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I agree that is better to leave it to the client to decide what to do with the main loop.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In gethopp#3 I moved this into the library behind a Cargo feature flag. Most Rust applications won't need to be concerned with this, but if they want to opt out, they can disable default features for the libwebrtc crate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer the maintainers of LiveKit to decide how they want to handle this, in the end of the day is their project. I was thinking that is more appropriate to have desktop capturer as a feature in general.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thinking is that this library is likely to get used by cross-platform applications whose developers may or may not know anything about Linux. It would be easy for a downstream developer who doesn't know anything about Linux to overlook starting a GLib event loop and have their application not work on Wayland without realizing it. So I think it'd be good to have it just work by default and let developers opt out if they don't need it. I don't have a strong opinion on this either way though; whatever the Livekit developers decide is fine.

I was thinking that is more appropriate to have desktop capturer as a feature in general.

Sure, though that's orthogonal to this question about the GLib event loop.

@Be-ing
Copy link
Contributor

Be-ing commented Oct 5, 2025

I will give this a closer look and testing this week. Big thanks for adding a new example program!

@Be-ing
Copy link
Contributor

Be-ing commented Oct 6, 2025

I'm getting linker errors trying to build the new screensharing example:

EDIT: Nevermind, I checked out the wrong branch.


error: linking with `cc` failed: exit status: 1
  |
  = note:  "cc" "-m64" "/tmp/rustceTdrkQ/symbols.o" "<141 object files omitted>" "-Wl,--as-needed" "-Wl,-Bstatic" "/home/be/sw/rust-sdks/examples/target/debug/deps/{libenv_logger-050dd1ec420f2605,libtermcolor-69ac49d150a0a656,libis_terminal-65352f38f88102c7,librustix-e9abc0e4c0c5d827,liblinux_raw_sys-ad0327564c029d5d,libhumantime-fdad89bb8e717e48,libregex-e2fd7044d2a3974e,libregex_automata-5ae3fa3fe5c061e4,libaho_corasick-9ddf6fc78e4fd076,libregex_syntax-4bc587ffb87af460,liblivekit-820578cc51711f91,libsemver-e777362a18423fd9,liblivekit_api-731f56a965b8a6cc,libsha2-22026c658ee8b917,libreqwest-d4a5d756b63beddc,libhyper_rustls-e50950a337473132,libhyper_tls-9e7953b76849f1ab,librustls_native_certs-4474b5dcf0299492,librustls_pemfile-fceddc16348569b2,libipnet-dddc7b6e9d9394b2,libtokio_rustls-2d9478ffedc79089,librustls-35dee9f4a596e1d4,libsct-bdf0f28c1a7e0061,libwebpki-46fd501811ecb0c0,libserde_urlencoded-676a1e2170845dab,libmime-dbf3c6ef3de22d0d,libencoding_rs-1aff405f3bb5cf62,libhyper-891c4aeefd48a14e,libwant-75ae21cb51e52757,libtry_lock-2dc6da27d33dbcf4,libh2-872667907a6c0eb8,libindexmap-adcd951582083d67,libequivalent-21b93cddc23938ae,libhashbrown-f3da08db30386b5e,libtokio_util-a61f3dd94c610cbf,libtower_service-32321cd98f2eebcf,libtracing-7565332ceca031e7,libtracing_core-1c950d48012fb5a2,libfutures_channel-eade8bbbaebda2ad,libhttp_body-bd0e6e7163181ba3,libtokio_tungstenite-3d0f084243dfef00,libtokio_native_tls-e30e7299d020b772,libtungstenite-24a786c1f823b9ab,libdata_encoding-f865a9a9fb9ad14c,librand-619d85860e14adf6,librand_chacha-a139d74e5e985e7b,librand_core-985e93f2660ebc70,libnative_tls-854c17f0a8aebb1a,libopenssl_probe-d8025253de36dfe7,libopenssl-dbae55ba99337ff3,libonce_cell-9a8cb83974f2d20b,libbitflags-5181e744cf8c3e70,libforeign_types-95f26c1153a2dd39,libforeign_types_shared-f07dbc3108126a92,libopenssl_sys-04049ba5210ef90a,libutf8-d56b3c3b3a375b68,libbyteorder-5c5f05416a46eab5,libsha1-0bc16dc50a3a0263,libcpufeatures-03aeafacf6c18a40,libdigest-59edb620f5c6971a,libsubtle-4296df22e8f3a4a6,libblock_buffer-3af283d433c1e3c8,libcrypto_common-7655fa28673d7247,libgeneric_array-e327aedf2faa7328,libtypenum-634ba0117f2159ae,libhttparse-e51a3288343725bc,liburl-c8dab6101ef5db4c,libidna-0127b8ab003ffa5e,libunicode_normalization-8c157a9396308751,libtinyvec-943587420ed98d94,libtinyvec_macros-fcc83c2cd47eddaf,libunicode_bidi-988e12b9f369b94d,libform_urlencoded-f421c2debcd45f1a,libpercent_encoding-53723dc0aea3c95f,librand-71b6a3e43fa886f8,librand_chacha-c546d909e59ecd15,libppv_lite86-bafd0f3bec10d337,libzerocopy-7105019aff454ec8,librand_core-23ba33334426e3d4,libgetrandom-04419fbeb24f5d15,libhttp-daf07caba00ce17b,libfnv-c907772a7c73ae1d,libjsonwebtoken-ae23aec79f68e7b5,libring-96063d3fe544afc4,libgetrandom-633eb01973a5107b,libspin-aaf51b35f965fea4,libuntrusted-b4cd29ae63ae4145,libbmrng-0d4c06a2b8e8a428,liblibwebrtc-e440c9580fde78b3,libserde_json-d3775e1c0cd516f5,libryu-6295074edb9a2615,libitoa-3151d7005c591b82,liblog-565195612a829b67,liblivekit_protocol-86386ac22da40336,libpbjson_types-ee800390a3efb4c9,libpbjson-e054a0b456f9d537,libbase64-eb4bda7b4893c2e6,libchrono-20cb31e0e6f90f68,libnum_traits-c26ea26f0c545559,libiana_time_zone-49822745ee550196,libserde-8860fffe9b2e5da6,libprost-bb5debbfa15cb009,liblazy_static-a2baf8eb19f40f3b,liblivekit_runtime-cbc9e63b6f5b4a56,libtokio_stream-7a8a933a5d7dffed,libtokio-7db2fe2128285624,libsignal_hook_registry-6e348127c0b719de,libnum_cpus-62f8f37fb5d026a1,libsocket2-6dd7f3d8721e9668,libbytes-cd7b11585e452a3e,libmio-42a12e0001838a68,libparking_lot-3054911553461481,libparking_lot_core-6954c7ad9190f16f,liblibc-991c84a35a612123,libsmallvec-90cdc7c76e0427a5,liblock_api-6f97804f72872ce4,libscopeguard-4ecac0a6a96d27fb,libwebrtc_sys-ff31a797f488c58d,libcxx-e5fb646f3e6daf58,liblink_cplusplus-92da47ccdb3d2902,libthiserror-85fe2db11d3e3b33,liblibloading-f4abd2311c66a2c8,libcfg_if-1e2261d551a0453d,libfutures_util-880028dab790ee0e,libmemchr-035a7d139d81d94b,libfutures_io-2ad9c1b060fae788,libslab-2492e1f620d52adf,libpin_project_lite-85b1d3211c42302f,libfutures_sink-01e407cf557bd589,libfutures_task-0b0d9271ed693eb4,libpin_utils-7d3b8d5a47f340e8,libfutures_core-df5874696681de02}.rlib" "<sysroot>/lib/rustlib/x86_64-unknown-linux-gnu/lib/{libstd-*,libpanic_unwind-*,libobject-*,libmemchr-*,libaddr2line-*,libgimli-*,librustc_demangle-*,libstd_detect-*,libhashbrown-*,librustc_std_workspace_alloc-*,libminiz_oxide-*,libadler2-*,libunwind-*,libcfg_if-*,liblibc-*,librustc_std_workspace_core-*,liballoc-*,libcore-*,libcompiler_builtins-*}.rlib" "-Wl,-Bdynamic" "-lssl" "-lcrypto" "-lrt" "-ldl" "-lpthread" "-lm" "-lstdc++" "-ldl" "-lgcc_s" "-lutil" "-lrt" "-lpthread" "-lm" "-ldl" "-lc" "-L" "/tmp/rustceTdrkQ/raw-dylibs" "-B<sysroot>/lib/rustlib/x86_64-unknown-linux-gnu/bin/gcc-ld" "-fuse-ld=lld" "-Wl,--eh-frame-hdr" "-Wl,-z,noexecstack" "-L" "/home/be/sw/rust-sdks/examples/target/debug/build/cxx-5a73f9bd78816c7d/out" "-L" "/home/be/sw/rust-sdks/examples/target/debug/build/link-cplusplus-bc2b156afdfa4835/out" "-L" "/home/be/sw/rust-sdks/examples/target/debug/build/webrtc-sys-85bf99c4987e75cc/out" "-L" "/home/be/sw/rust-sdks/examples/target/debug/build/ring-6a77295c4b9d5cac/out" "-L" "/home/be/sw/rust-sdks/examples/target/debug/build/scratch-b9e9af0bcdf7d3b7/out/livekit_webrtc/livekit/linux-x64-release-webrtc-7ec4c03/linux-x64-release/lib" "-L" "<sysroot>/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-o" "/home/be/sw/rust-sdks/examples/target/debug/deps/screensharing-a6b625ec0eaa1be1" "-Wl,--gc-sections" "-pie" "-Wl,-z,relro,-z,now" "-nodefaultlibs"
  = note: some arguments are omitted. use `--verbose` to show all linker arguments
  = note: rust-lld: error: undefined symbol: webrtc::DesktopCaptureOptions::CreateDefault()
          >>> referenced by desktop_capturer.rs.cc
          >>>               6cb4a6c7ef6a8e30-desktop_capturer.rs.o:(livekit::new_desktop_capturer(rust::cxxbridge1::Box<livekit::DesktopCapturerCallbackWrapper>, bool)) in archive /home/be/sw/rust-sdks/examples/target/debug/deps/libwebrtc_sys-ff31a797f488c58d.rlib
          
          rust-lld: error: undefined symbol: webrtc::DesktopCapturer::CreateWindowCapturer(webrtc::DesktopCaptureOptions const&)
          >>> referenced by desktop_capturer.rs.cc
          >>>               6cb4a6c7ef6a8e30-desktop_capturer.rs.o:(livekit::new_desktop_capturer(rust::cxxbridge1::Box<livekit::DesktopCapturerCallbackWrapper>, bool)) in archive /home/be/sw/rust-sdks/examples/target/debug/deps/libwebrtc_sys-ff31a797f488c58d.rlib
          >>> referenced by desktop_capturer.rs.cc
          >>>               6cb4a6c7ef6a8e30-desktop_capturer.rs.o:(livekit::new_desktop_capturer(rust::cxxbridge1::Box<livekit::DesktopCapturerCallbackWrapper>, bool)) in archive /home/be/sw/rust-sdks/examples/target/debug/deps/libwebrtc_sys-ff31a797f488c58d.rlib
          
          rust-lld: error: undefined symbol: webrtc::DesktopCapturer::CreateScreenCapturer(webrtc::DesktopCaptureOptions const&)
          >>> referenced by desktop_capturer.rs.cc
          >>>               6cb4a6c7ef6a8e30-desktop_capturer.rs.o:(livekit::new_desktop_capturer(rust::cxxbridge1::Box<livekit::DesktopCapturerCallbackWrapper>, bool)) in archive /home/be/sw/rust-sdks/examples/target/debug/deps/libwebrtc_sys-ff31a797f488c58d.rlib
          >>> referenced by desktop_capturer.rs.cc
          >>>               6cb4a6c7ef6a8e30-desktop_capturer.rs.o:(livekit::new_desktop_capturer(rust::cxxbridge1::Box<livekit::DesktopCapturerCallbackWrapper>, bool)) in archive /home/be/sw/rust-sdks/examples/target/debug/deps/libwebrtc_sys-ff31a797f488c58d.rlib
          
          rust-lld: error: undefined symbol: webrtc::DesktopCaptureOptions::~DesktopCaptureOptions()
          >>> referenced by desktop_capturer.rs.cc
          >>>               6cb4a6c7ef6a8e30-desktop_capturer.rs.o:(livekit::new_desktop_capturer(rust::cxxbridge1::Box<livekit::DesktopCapturerCallbackWrapper>, bool)) in archive /home/be/sw/rust-sdks/examples/target/debug/deps/libwebrtc_sys-ff31a797f488c58d.rlib
          >>> referenced by desktop_capturer.rs.cc
          >>>               6cb4a6c7ef6a8e30-desktop_capturer.rs.o:(livekit::new_desktop_capturer(rust::cxxbridge1::Box<livekit::DesktopCapturerCallbackWrapper>, bool)) in archive /home/be/sw/rust-sdks/examples/target/debug/deps/libwebrtc_sys-ff31a797f488c58d.rlib
          collect2: error: ld returned 1 exit status

@Be-ing
Copy link
Contributor

Be-ing commented Oct 6, 2025

How do I build the example? I don't see any documentation how this build system works and libwebrtc-sys's build.rs is quite complicated. cargo build in examples/screensharing fails with:

  cargo:warning=src/desktop_capturer.cpp: In function 'std::unique_ptr<livekit::DesktopCapturer> livekit::new_desktop_capturer(rust::cxxbridge1::Box<DesktopCapturerCallbackWrapper>, DesktopCapturerOptions)':
  cargo:warning=src/desktop_capturer.cpp:43:18: error: 'class webrtc::DesktopCaptureOptions' has no member named 'set_allow_pipewire'
  cargo:warning=   43 |   webrtc_options.set_allow_pipewire(options.allow_pipewire_capturer);
  cargo:warning=      |                  ^~~~~~~~~~~~~~~~~~
  exit status: 0
  exit status: 0
  exit status: 1
  cargo:warning=ToolExecError: command did not execute successfully (status code exit status: 1): LC_ALL="C" "sccache" "c++" "-O0" "-ffunction-sections" "-fdata-sections" "-fPIC" "-m64" "-I" "/home/be/sw/rust-sdks/examples/target/debug/build/webrtc-sys-5e9c8eca9c4ddc43/out/cxxbridge/include" "-I" "/home/be/sw/rust-sdks/examples/target/debug/build/webrtc-sys-5e9c8eca9c4ddc43/out/cxxbridge/crate" "-I" "./include" "-I" "/home/be/sw/rust-sdks/examples/target/debug/build/scratch-3650245067ffd690/out/livekit_webrtc/livekit/linux-x64-release-webrtc-f4967ef/linux-x64-release/include" "-I" "/home/be/sw/rust-sdks/examples/target/debug/build/scratch-3650245067ffd690/out/livekit_webrtc/livekit/linux-x64-release-webrtc-f4967ef/linux-x64-release/include/third_party/abseil-cpp/" "-I" "/home/be/sw/rust-sdks/examples/target/debug/build/scratch-3650245067ffd690/out/livekit_webrtc/livekit/linux-x64-release-webrtc-f4967ef/linux-x64-release/include/third_party/libyuv/include/" "-I" "/home/be/sw/rust-sdks/examples/target/debug/build/scratch-3650245067ffd690/out/livekit_webrtc/livekit/linux-x64-release-webrtc-f4967ef/linux-x64-release/include/third_party/libc++/" "-I" "/home/be/sw/rust-sdks/examples/target/debug/build/scratch-3650245067ffd690/out/livekit_webrtc/livekit/linux-x64-release-webrtc-f4967ef/linux-x64-release/include/sdk/objc" "-I" "/home/be/sw/rust-sdks/examples/target/debug/build/scratch-3650245067ffd690/out/livekit_webrtc/livekit/linux-x64-release-webrtc-f4967ef/linux-x64-release/include/sdk/objc/base" "-I" "/home/be/sw/rust-sdks/examples/target/debug/build/scratch-3650245067ffd690/out/livekit_webrtc/livekit/linux-x64-release-webrtc-f4967ef/linux-x64-release/include/build/linux/debian_bullseye_amd64-sysroot/usr/include/glib-2.0" "-I" "/home/be/sw/rust-sdks/examples/target/debug/build/scratch-3650245067ffd690/out/livekit_webrtc/livekit/linux-x64-release-webrtc-f4967ef/linux-x64-release/include/build/linux/debian_bullseye_amd64-sysroot/usr/lib/x86_64-linux-gnu/glib-2.0/include" "-Wno-changes-meaning" "-std=c++20" "-DWEBRTC_APM_DEBUG_DUMP=0" "-DUSE_UDEV" "-DUSE_AURA=1" "-DUSE_GLIB=1" "-DUSE_OZONE=1" "-D__STDC_CONSTANT_MACROS" "-D__STDC_FORMAT_MACROS" "-D_FORTIFY_SOURCE=2" "-D_FILE_OFFSET_BITS=64" "-D_LARGEFILE_SOURCE" "-D_LARGEFILE64_SOURCE" "-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_NONE" "-D_GLIBCXX_ASSERTIONS=1" "-DCR_SYSROOT_KEY=20250129T203412Z-1" "-DNDEBUG" "-DNVALGRIND" "-DDYNAMIC_ANNOTATIONS_ENABLED=0" "-DWEBRTC_ENABLE_PROTOBUF=0" "-DWEBRTC_STRICT_FIELD_TRIALS=0" "-DWEBRTC_INCLUDE_INTERNAL_AUDIO_DEVICE" "-DRTC_USE_LIBAOM_AV1_ENCODER" "-DRTC_ENABLE_VP9" "-DRTC_ENABLE_H265" "-DRTC_DAV1D_IN_INTERNAL_DECODER_FACTORY" "-DWEBRTC_HAVE_SCTP" "-DWEBRTC_USE_H264" "-DWEBRTC_LIBRARY_IMPL" "-DPROTOBUF_ENABLE_DEBUG_LOGGING_MAY_LEAK_PII=0" "-DWEBRTC_NON_STATIC_TRACE_EVENT_HANDLERS=0" "-DWEBRTC_POSIX" "-DWEBRTC_LINUX" "-DWEBRTC_ALLOW_DEPRECATED_NAMESPACES" "-DABSL_ALLOCATOR_NOTHROW=1" "-DCHROMIUM" "-DLIBYUV_DISABLE_NEON" "-DLIBYUV_DISABLE_SVE" "-DLIBYUV_DISABLE_SME" "-DLIBYUV_DISABLE_LSX" "-DLIBYUV_DISABLE_LASX" "-DLIVEKIT_TEST" "-o" "/home/be/sw/rust-sdks/examples/target/debug/build/webrtc-sys-5e9c8eca9c4ddc43/out/0602fb52cb66f316-desktop_capturer.o" "-c" "src/desktop_capturer.cpp"

  --- stderr

  CXX include path:
    /home/be/sw/rust-sdks/examples/target/debug/build/webrtc-sys-5e9c8eca9c4ddc43/out/cxxbridge/include
    /home/be/sw/rust-sdks/examples/target/debug/build/webrtc-sys-5e9c8eca9c4ddc43/out/cxxbridge/crate

@iparaskev
Copy link
Contributor Author

@Be-ing you need to build libwebrtc with pipewire enabled, use this PR and follow the instructions from here.

@Be-ing
Copy link
Contributor

Be-ing commented Oct 6, 2025

That build_linux.sh script fails. I'm puzzled what it's doing. It seems to be downloading a bunch of stuff including a Debian image or something?? And building Abseil... does libwebrtc depend on Abseil? Then it fails to find a header from glibc.

❯ ./build_linux.sh --arch x64
Building LiveKit WebRTC - Linux
Arch: x64
Profile: release
Cloning into 'depot_tools'...
remote: Counting objects: 854, done
remote: Finding sources: 100% (854/854)
remote: Total 854 (delta 139), reused 566 (delta 139)
Receiving objects: 100% (854/854), 1.35 MiB | 1.86 MiB/s, done.
Resolving deltas: 100% (139/139), done.
WARNING: Your metrics.cfg file was invalid or nonexistent. A new one will be created.
Syncing projects:  20% (13/62) src/buildtools/linux64-format:79a7b4e5336339c17b828de10d80611ff0f85961
[0:01:36] Still working on:
[0:01:36]   src/third_party

[0:01:46] Still working on:
[0:01:46]   src/third_party

[0:01:56] Still working on:
[0:01:56]   src/third_party

[0:02:00] Still working on:
[0:02:00]   src/third_party
Syncing projects: 100% (64/64), done.
Running hooks:  17% ( 5/28) sysroot_x86
________ running 'python3 src/build/linux/sysroot_scripts/install-sysroot.py --arch=i386' in '/home/be/sw/rust-sdks/webrtc-sys/libwebrtc'
Installing Debian bullseye i386 root image: /home/be/sw/rust-sdks/webrtc-sys/libwebrtc/src/build/linux/debian_bullseye_i386-sysroot
Downloading https://commondatastorage.googleapis.com/chrome-linux-sysroot/63f0e5128b84f7b0421956a4a40affa472be8da0e58caf27e9acbc84072daee7
Running hooks:  25% ( 7/28) sysroot_x64
________ running 'python3 src/build/linux/sysroot_scripts/install-sysroot.py --arch=amd64' in '/home/be/sw/rust-sdks/webrtc-sys/libwebrtc'
Installing Debian bullseye amd64 root image: /home/be/sw/rust-sdks/webrtc-sys/libwebrtc/src/build/linux/debian_bullseye_amd64-sysroot
Downloading https://commondatastorage.googleapis.com/chrome-linux-sysroot/36a164623d03f525e3dfb783a5e9b8a00e98e1ddd2b5cff4e449bd016dd27e50
Running hooks:  46% (13/28) clang
________ running 'python3 src/tools/clang/scripts/update.py' in '/home/be/sw/rust-sdks/webrtc-sys/libwebrtc'
Downloading https://commondatastorage.googleapis.com/chromium-browser-clang/Linux_x64/clang-llvmorg-21-init-9266-g09006611-1.tar.xz .......... Done.
/home/be/sw/rust-sdks/webrtc-sys/libwebrtc/src/tools/clang/scripts/update.py:179: DeprecationWarning: Python 3.14 will, by default, filter extracted tar archives and reject files or modify their metadata. Use the filter argument to control this behavior.
  t.extractall(path=output_dir, members=members)
Hook 'python3 src/tools/clang/scripts/update.py' took 13.57 secs
Running hooks:  57% (16/28) rust
________ running 'python3 src/tools/rust/update_rust.py' in '/home/be/sw/rust-sdks/webrtc-sys/libwebrtc'
Downloading https://commondatastorage.googleapis.com/chromium-browser-clang/Linux_x64/rust-toolchain-c8f94230282a8e8c1148f3e657f0199aad909228-1-llvmorg-21-init-9266-g09006611.tar.xz .......... Done.
Hook 'python3 src/tools/rust/update_rust.py' took 44.15 secs
Running hooks:  60% (17/28) lastchange
________ running 'python3 src/build/util/lastchange.py -o src/build/util/LASTCHANGE' in '/home/be/sw/rust-sdks/webrtc-sys/libwebrtc'
ERROR:root:Failed to get version info: Git command 'git log -1 --format=%H %ct --grep=^Change-Id: HEAD' in /home/be/sw/rust-sdks/webrtc-sys/libwebrtc/src/build failed: rc=0, stdout='' stderr=''
WARNING:root:Falling back to a version of 0.0.0 to allow script to finish. This is normal if you are bootstrapping a new environment or do not have a git repository for any other reason. If not, this could represent a serious error.
Running hooks:  82% (23/28)
________ running 'download_from_google_storage --directory --recursive --num_threads=10 --no_auth --quiet --bucket chromium-webrtc-resources src/resources' in '/home/be/sw/rust-sdks/webrtc-sys/libwebrtc'
--no_auth is deprecated, this flag has no effect.
Hook 'download_from_google_storage --directory --recursive --num_threads=10 --no_auth --quiet --bucket chromium-webrtc-resources src/resources' took 542.22 secs
Hook 'vpython3 src/testing/generate_location_tags.py --out src/testing/location_tags.json' took 59.95 secs
Running hooks: 100% (28/28), done.
Checking patch tools_webrtc/libs/generate_licenses.py...
Hunk #1 succeeded at 92 (offset 6 lines).
Applied patch tools_webrtc/libs/generate_licenses.py cleanly.
Checking patch rtc_base/boringssl_certificate.cc...
Checking patch rtc_base/boringssl_certificate.h...
Checking patch rtc_base/openssl_adapter.cc...
Checking patch rtc_base/openssl_stream_adapter.cc...
Applied patch rtc_base/boringssl_certificate.cc cleanly.
Applied patch rtc_base/boringssl_certificate.h cleanly.
Applied patch rtc_base/openssl_adapter.cc cleanly.
Applied patch rtc_base/openssl_stream_adapter.cc cleanly.
Checking patch BUILD.gn...
Applied patch BUILD.gn cleanly.
Checking patch config/c++/c++.gni...
Checking patch config/linux/BUILD.gn...
Applied patch config/c++/c++.gni cleanly.
Applied patch config/linux/BUILD.gn cleanly.
Generating compile_commands took 60ms
Done. Made 2010 targets from 361 files in 2760ms
ninja: Entering directory `/home/be/sw/rust-sdks/webrtc-sys/libwebrtc/src/out-x64-release'
[1/4184] CXX obj/third_party/abseil-cpp/absl/base/base/cycleclock.o
FAILED: obj/third_party/abseil-cpp/absl/base/base/cycleclock.o
g++ -MMD -MF obj/third_party/abseil-cpp/absl/base/base/cycleclock.o.d -DUSE_UDEV -DUSE_AURA=1 -DUSE_GLIB=1 -DUSE_OZONE=1 -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_NONE -D_GLIBCXX_ASSERTIONS=1 -DCR_SYSROOT_KEY=20250129T203412Z-1 -DNDEBUG -DNVALGRIND -DDYNAMIC_ANNOTATIONS_ENABLED=0 -DABSL_ALLOCATOR_NOTHROW=1 -I.. -Igen -I../third_party/abseil-cpp -fno-strict-overflow -fno-ident -fno-strict-aliasing -fstack-protector -funwind-tables -fPIC -pipe -pthread -m64 -msse3 -Wno-builtin-macro-redefined -D__DATE__= -D__TIME__= -D__TIMESTAMP__= -O2 -fdata-sections -ffunction-sections -fno-math-errno -fno-omit-frame-pointer -g0 -fvisibility=hidden -Wno-unused-local-typedefs -Wno-maybe-uninitialized -Wno-deprecated-declarations -Wno-comments -Wno-packed-not-aligned -Wno-missing-field-initializers -Wno-unused-parameter -Wno-psabi -std=gnu++2a -Wno-changes-meaning -fno-exceptions --sysroot=../build/linux/debian_bullseye_amd64-sysroot -fvisibility-inlines-hidden -Wno-narrowing -Wno-class-memaccess -Wno-invalid-offsetof  -c ../third_party/abseil-cpp/absl/base/internal/cycleclock.cc -o obj/third_party/abseil-cpp/absl/base/base/cycleclock.o
In file included from /usr/include/c++/15/bits/version.h:51,
                 from /usr/include/c++/15/atomic:50,
                 from ../third_party/abseil-cpp/absl/base/internal/cycleclock.h:45,
                 from ../third_party/abseil-cpp/absl/base/internal/cycleclock.cc:23:
/usr/include/c++/15/x86_64-redhat-linux/bits/c++config.h:3:10: fatal error: bits/wordsize.h: No such file or directory
    3 | #include <bits/wordsize.h>
      |          ^~~~~~~~~~~~~~~~~
compilation terminated.
[2/4184] CXX obj/third_party/abseil-cpp/absl/base/base/thread_identity.o
FAILED: obj/third_party/abseil-cpp/absl/base/base/thread_identity.o
g++ -MMD -MF obj/third_party/abseil-cpp/absl/base/base/thread_identity.o.d -DUSE_UDEV -DUSE_AURA=1 -DUSE_GLIB=1 -DUSE_OZONE=1 -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_NONE -D_GLIBCXX_ASSERTIONS=1 -DCR_SYSROOT_KEY=20250129T203412Z-1 -DNDEBUG -DNVALGRIND -DDYNAMIC_ANNOTATIONS_ENABLED=0 -DABSL_ALLOCATOR_NOTHROW=1 -I.. -Igen -I../third_party/abseil-cpp -fno-strict-overflow -fno-ident -fno-strict-aliasing -fstack-protector -funwind-tables -fPIC -pipe -pthread -m64 -msse3 -Wno-builtin-macro-redefined -D__DATE__= -D__TIME__= -D__TIMESTAMP__= -O2 -fdata-sections -ffunction-sections -fno-math-errno -fno-omit-frame-pointer -g0 -fvisibility=hidden -Wno-unused-local-typedefs -Wno-maybe-uninitialized -Wno-deprecated-declarations -Wno-comments -Wno-packed-not-aligned -Wno-missing-field-initializers -Wno-unused-parameter -Wno-psabi -std=gnu++2a -Wno-changes-meaning -fno-exceptions --sysroot=../build/linux/debian_bullseye_amd64-sysroot -fvisibility-inlines-hidden -Wno-narrowing -Wno-class-memaccess -Wno-invalid-offsetof  -c ../third_party/abseil-cpp/absl/base/internal/thread_identity.cc -o obj/third_party/abseil-cpp/absl/base/base/thread_identity.o
In file included from ../build/linux/debian_bullseye_amd64-sysroot/usr/include/pthread.h:21,
                 from ../third_party/abseil-cpp/absl/base/internal/thread_identity.h:24,
                 from ../third_party/abseil-cpp/absl/base/internal/thread_identity.cc:15:
../build/linux/debian_bullseye_amd64-sysroot/usr/include/features.h:461:12: fatal error: sys/cdefs.h: No such file or directory
  461 | #  include <sys/cdefs.h>
      |            ^~~~~~~~~~~~~
compilation terminated.
[3/4184] CXX obj/third_party/abseil-cpp/absl/base/base/sysinfo.o
FAILED: obj/third_party/abseil-cpp/absl/base/base/sysinfo.o
g++ -MMD -MF obj/third_party/abseil-cpp/absl/base/base/sysinfo.o.d -DUSE_UDEV -DUSE_AURA=1 -DUSE_GLIB=1 -DUSE_OZONE=1 -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_NONE -D_GLIBCXX_ASSERTIONS=1 -DCR_SYSROOT_KEY=20250129T203412Z-1 -DNDEBUG -DNVALGRIND -DDYNAMIC_ANNOTATIONS_ENABLED=0 -DABSL_ALLOCATOR_NOTHROW=1 -I.. -Igen -I../third_party/abseil-cpp -fno-strict-overflow -fno-ident -fno-strict-aliasing -fstack-protector -funwind-tables -fPIC -pipe -pthread -m64 -msse3 -Wno-builtin-macro-redefined -D__DATE__= -D__TIME__= -D__TIMESTAMP__= -O2 -fdata-sections -ffunction-sections -fno-math-errno -fno-omit-frame-pointer -g0 -fvisibility=hidden -Wno-unused-local-typedefs -Wno-maybe-uninitialized -Wno-deprecated-declarations -Wno-comments -Wno-packed-not-aligned -Wno-missing-field-initializers -Wno-unused-parameter -Wno-psabi -std=gnu++2a -Wno-changes-meaning -fno-exceptions --sysroot=../build/linux/debian_bullseye_amd64-sysroot -fvisibility-inlines-hidden -Wno-narrowing -Wno-class-memaccess -Wno-invalid-offsetof  -c ../third_party/abseil-cpp/absl/base/internal/sysinfo.cc -o obj/third_party/abseil-cpp/absl/base/base/sysinfo.o
In file included from ../third_party/abseil-cpp/absl/base/internal/sysinfo.cc:15:
../third_party/abseil-cpp/absl/base/internal/sysinfo.h:28:10: fatal error: sys/types.h: No such file or directory
   28 | #include <sys/types.h>
      |          ^~~~~~~~~~~~~
compilation terminated.
[4/4184] CXX obj/third_party/abseil-cpp/absl/base/base/spinlock.o
FAILED: obj/third_party/abseil-cpp/absl/base/base/spinlock.o
g++ -MMD -MF obj/third_party/abseil-cpp/absl/base/base/spinlock.o.d -DUSE_UDEV -DUSE_AURA=1 -DUSE_GLIB=1 -DUSE_OZONE=1 -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_NONE -D_GLIBCXX_ASSERTIONS=1 -DCR_SYSROOT_KEY=20250129T203412Z-1 -DNDEBUG -DNVALGRIND -DDYNAMIC_ANNOTATIONS_ENABLED=0 -DABSL_ALLOCATOR_NOTHROW=1 -I.. -Igen -I../third_party/abseil-cpp -fno-strict-overflow -fno-ident -fno-strict-aliasing -fstack-protector -funwind-tables -fPIC -pipe -pthread -m64 -msse3 -Wno-builtin-macro-redefined -D__DATE__= -D__TIME__= -D__TIMESTAMP__= -O2 -fdata-sections -ffunction-sections -fno-math-errno -fno-omit-frame-pointer -g0 -fvisibility=hidden -Wno-unused-local-typedefs -Wno-maybe-uninitialized -Wno-deprecated-declarations -Wno-comments -Wno-packed-not-aligned -Wno-missing-field-initializers -Wno-unused-parameter -Wno-psabi -std=gnu++2a -Wno-changes-meaning -fno-exceptions --sysroot=../build/linux/debian_bullseye_amd64-sysroot -fvisibility-inlines-hidden -Wno-narrowing -Wno-class-memaccess -Wno-invalid-offsetof  -c ../third_party/abseil-cpp/absl/base/internal/spinlock.cc -o obj/third_party/abseil-cpp/absl/base/base/spinlock.o
In file included from /usr/include/c++/15/bits/version.h:51,
                 from /usr/include/c++/15/atomic:50,
                 from ../third_party/abseil-cpp/absl/base/internal/spinlock.h:32,
                 from ../third_party/abseil-cpp/absl/base/internal/spinlock.cc:15:
/usr/include/c++/15/x86_64-redhat-linux/bits/c++config.h:3:10: fatal error: bits/wordsize.h: No such file or directory
    3 | #include <bits/wordsize.h>
      |          ^~~~~~~~~~~~~~~~~
compilation terminated.
[5/4184] CXX obj/third_party/abseil-cpp/absl/base/base/unscaledcycleclock.o
FAILED: obj/third_party/abseil-cpp/absl/base/base/unscaledcycleclock.o
g++ -MMD -MF obj/third_party/abseil-cpp/absl/base/base/unscaledcycleclock.o.d -DUSE_UDEV -DUSE_AURA=1 -DUSE_GLIB=1 -DUSE_OZONE=1 -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_NONE -D_GLIBCXX_ASSERTIONS=1 -DCR_SYSROOT_KEY=20250129T203412Z-1 -DNDEBUG -DNVALGRIND -DDYNAMIC_ANNOTATIONS_ENABLED=0 -DABSL_ALLOCATOR_NOTHROW=1 -I.. -Igen -I../third_party/abseil-cpp -fno-strict-overflow -fno-ident -fno-strict-aliasing -fstack-protector -funwind-tables -fPIC -pipe -pthread -m64 -msse3 -Wno-builtin-macro-redefined -D__DATE__= -D__TIME__= -D__TIMESTAMP__= -O2 -fdata-sections -ffunction-sections -fno-math-errno -fno-omit-frame-pointer -g0 -fvisibility=hidden -Wno-unused-local-typedefs -Wno-maybe-uninitialized -Wno-deprecated-declarations -Wno-comments -Wno-packed-not-aligned -Wno-missing-field-initializers -Wno-unused-parameter -Wno-psabi -std=gnu++2a -Wno-changes-meaning -fno-exceptions --sysroot=../build/linux/debian_bullseye_amd64-sysroot -fvisibility-inlines-hidden -Wno-narrowing -Wno-class-memaccess -Wno-invalid-offsetof  -c ../third_party/abseil-cpp/absl/base/internal/unscaledcycleclock.cc -o obj/third_party/abseil-cpp/absl/base/base/unscaledcycleclock.o
In file included from /usr/include/c++/15/cstdint:40,
                 from ../third_party/abseil-cpp/absl/base/internal/unscaledcycleclock.h:39,
                 from ../third_party/abseil-cpp/absl/base/internal/unscaledcycleclock.cc:15:
/usr/include/c++/15/x86_64-redhat-linux/bits/c++config.h:3:10: fatal error: bits/wordsize.h: No such file or directory
    3 | #include <bits/wordsize.h>
      |          ^~~~~~~~~~~~~~~~~
compilation terminated.
[6/4184] ACTION //build:chromeos_buildflags(//build/toolchain/linux:x64)
ninja: build stopped: subcommand failed.
INFO:root:List of licenses: webrtc, abseil-cpp, boringssl, crc32c, dav1d, ffmpeg, fft, fiat, g711, g722, libaom, libjpeg_turbo, libsrtp, libvpx, libyuv, nasm, ooura, openh264, opus, perfetto, pffft, protobuf, protobuf-javascript, rnnoise, sigslot, spl_sqrt_floor, zlib
812084 blocks
7713 blocks

@Be-ing
Copy link
Contributor

Be-ing commented Oct 7, 2025

I think I need to rebase #648 to get this to build... this build system needs some work.

@Be-ing
Copy link
Contributor

Be-ing commented Oct 8, 2025

After 3 days of yak shaving, I got libwebrtc to build locally with #730. I had to make a few changes to this branch to get it to build, so I made a PR for your fork: gethopp#2.

Now I am wondering what LIVEKIT_URL to use for development. Is there a test server I can use? Or a tool for running a test server locally?

@iparaskev
Copy link
Contributor Author

@Be-ing you can either

  • create an account for livekit (their free tier will be more than enough for testing), generate a new api key, use the cli to create the tokens, and then connect to the room via https://meet.livekit.io/?tab=custom
  • do the same but with a local server and client

@Be-ing
Copy link
Contributor

Be-ing commented Oct 10, 2025

I tested and this works on KDE Plasma Wayland! 🎉 I will do more extensive testing on different desktops, Wayland and X11, tomorrow.

@iparaskev
Copy link
Contributor Author

@Be-ing thanks for testing it. I haven't found time during the week to check X11. I will do it during the weekend.

iparaskev and others added 3 commits October 13, 2025 22:52
Implements screen sharing by exposing libwebrtc's
DesktopCapturer.

A few platform specific notes:
  - macos:
    * It is using screen capture kit and the system picker by
      default. If the system picker is disabled then get_sources
      returns an empty list when trying to capture a display. The
      display native id needs to be acquired using different means
      from the client.
  - linux:
    * With pipewire the only way to select window or display is via
      the system picker.
@iparaskev iparaskev force-pushed the expose_desktop_capturer branch from 21c54ef to c60d7be Compare October 13, 2025 21:56
@Be-ing
Copy link
Contributor

Be-ing commented Oct 15, 2025

Could you rebase this branch on the main branch to incorporate #731, add the new example to the workspace root Cargo.toml, and set publish = false in the example's Cargo.toml?

@Be-ing
Copy link
Contributor

Be-ing commented Oct 15, 2025

I opened another PR for your fork: gethopp#3

@Be-ing
Copy link
Contributor

Be-ing commented Oct 15, 2025

I've tested this on:
KDE Plasma (Wayland, X11)
GNOME (Wayland)
COSMIC (Wayland)
Cinnamon (X11)
Xfce (X11)

and confirm both desktop and window capture work on all of them.

Comment on lines +141 to +146
let source_type = if args.capture_window {
DesktopCaptureSourceType::WINDOW
} else {
DesktopCaptureSourceType::SCREEN
};
let mut options = DesktopCapturerOptions::new(source_type);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I like this API better, nice idea. It's a bit odd that the WINDOW and SCREEN enum variants are all caps though.

Comment on lines +125 to +129
if cfg!(target_os = "linux") {
files.push(desktop_capture_path);
} else if desktop_capture_path.exists() {
files.push(desktop_capture_path);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The conditions of this if-else block don't make much sense to me. Shouldn't this just check if the path exists regardless of target_os?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just got lazy here. I will update the scripts for every platform.

Comment on lines +68 to +75
let stream_width = 1920;
let stream_height = 1080;
let buffer_source =
NativeVideoSource::new(VideoResolution { width: stream_width, height: stream_height });
let track = LocalVideoTrack::create_video_track(
"screen_share",
RtcVideoSource::Native(buffer_source.clone()),
);
Copy link
Contributor

@Be-ing Be-ing Oct 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused what the right thing to do is here for the resolution. Unless I'm missing something, libwebrtc doesn't expose an API to get the resolution of a DesktopCapturer::Source; you can only get the resolution of the captured screen/window from the DesktopFrame passed to the callback after the stream has started. Nor is there a cross-platform way to change the resolution of the VideoTrackSource after it is created. There is a method on DesktopCapturer void UpdateResolution(uint32_t width, uint32_t height) which is guarded by defined(WEBRTC_USE_PIPEWIRE) || defined(WEBRTC_USE_X11) though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe someone from Livekit has some idea about this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Digging deeper into the NativeVideoSource code, it seems the requirement for the video resolution is for the scaling performed by VideoTrackSource::InternalSource::on_captured_frame. I was wondering if this was a requirement imposed by libwebrtc somehow, but in that case, it wouldn't make sense that libwebrtc doesn't provide an API to get the resolution before starting the stream. If I understand that correctly, then I think we can add a method to NativeVideoSource to change the VideoResolution after the NativeVideoSource is created and call that in the callback. Or better yet, we could add a method to VideoTrackSource that takes a DesktopFrame and takes care of this automatically.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what is the problem here TBH.

You should know with which resolution you want to share your stream (of course you need to do change the dims to match the aspect ration of the source to avoid distortions), which of course most of the times will be different from the capture res.

In platforms that allow you to choose your source without a system picker you should be able to get the source's dims before starting capturing. When you are using the system picker and you can't know which source the user selected you can simply start publishing your track after capturing has started.

IMO the only improvement that could be done here is to change VideoTrackSource::InternalSource::on_captured_frame to scale the buffer to match resolution_ (with the aspect ratio change).

Copy link
Contributor

@Be-ing Be-ing Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should know with which resolution you want to share your stream

How can you know that without any idea what the resolution of the source is? There's no way to know in advance if you're capturing a 200 x 200 window or a 7680 × 4320 screen. Up/down scaling either of these to some arbitrary assumed sized is not going to look good.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For context, I started looking into this because the current screen capturing code in Zed assumes the resolution is known in advance and sets the stream dimensions based on that. That assumption is true for the current capture mechanisms (the scap crate and macOS APIs), but not for libwebrtc.

Copy link
Contributor

@Be-ing Be-ing Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Digging more into the code, I'm getting more confused. I don't understand the point of calling AdaptedVideoTrackSource::AdaptFrame in VideoTrackSource::InternalSource::on_captured_frame because the AdaptedVideoTrackSource's VideoAdapter is never really configured with a VideoSinkWants... there's just this code with a default constructed VideoSinkWants:

void VideoTrack::add_sink(const std::shared_ptr<NativeVideoSink>& sink) const {
  webrtc::MutexLock lock(&mutex_);
  track()->AddOrUpdateSink(sink.get(),
                           webrtc::VideoSinkWants());  // TODO(theomonnom): Expose
                                                    // VideoSinkWants to Rust?
  sinks_.push_back(sink);
}

When the AdaptFrame call does change the resolution, it always outputs a square resolution (height and width equal) regardless of the input resolution.

Another confusing bit of code I found is this in libwebrtc::NativeVideoSource::new

        livekit_runtime::spawn({
            let source = source.clone();
            let i420 = I420Buffer::new(resolution.width, resolution.height);
            async move {
                let mut interval = interval(Duration::from_millis(100)); // 10 fps

                loop {
                    interval.tick().await;

                    let inner = source.inner.lock();
                    if inner.captured_frames > 0 {
                        break;
                    }

                    let mut builder = vf_sys::ffi::new_video_frame_builder();
                    builder.pin_mut().set_rotation(VideoRotation::VideoRotation0);
                    builder.pin_mut().set_video_frame_buffer(i420.as_ref().sys_handle());

                    let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
                    builder.pin_mut().set_timestamp_us(now.as_micros() as i64);

                    source.sys_handle.on_captured_frame(&builder.pin_mut().build());
                }
            }
        });

With desktop capturing, we're calling on_captured_frame when the DesktopCapturer's callback is invoked, so it doesn't make sense to me to have livekit_runtime running this loop. And I definitely want more than 10 FPS for screen captures. I also don't understand why livekit_runtime is a dependency of the libwebrtc crate.

I found that code because I tried setting the VideoResolution for NativeVideoSource::new to 0,0 to test this code in VideoTrackSource::InternalSource::on_captured_frame:

  if (resolution_.height == 0 || resolution_.width == 0) {
    resolution_ = VideoResolution{static_cast<uint32_t>(buffer->width()),
                                  static_cast<uint32_t>(buffer->height())};
  }

However, I have to comment out the call to livekit_runtime::spawn above because I420Buffer::new asserts that the width and height aren't 0, so this code I pasted from VideoTrackSource::InternalSource::on_captured_frame is effectively unusable.

@theomonnom as the author of much of this code, can you explain what's going on here? What is really the purpose of the VideoResolution passed to NativeVideoSource::new?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see, the NativeVideoSource's resolution is read in livekit::LocalParticipant::publish_track. So I think what is needed is a new API to change the resolution of an existing track after it has been published.

let (y, u, v) = capture_buffer.data_mut();
yuv_helper::argb_to_i420(data, stride, y, s_y, u, s_u, v, s_v, width, height);

let scaled_buffer = capture_buffer.scale(stream_width as i32, stream_height as i32);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This scaling is redundant. NativeVideoSource::capture_frame will scale it automatically.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes you are right, I had missed the VideoTrackSource::InternalSource::on_captured_frame implementation. Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I had another look and I think the scaling is actually needed. When setting the NativeVideoSource to 1080p and the frame dims are 2880x1800, adapted_width/height are becoming 2880x1800 and not 1080p.

@Be-ing
Copy link
Contributor

Be-ing commented Oct 27, 2025

There are some merge conflicts now, I think from #753.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants