Compare commits

...

2 Commits

Author SHA1 Message Date
Mikayla
f67815df05 better documentation for the pixel format 2024-12-17 10:56:31 -08:00
Mikayla
f2ca21ae44 Add rendering of self screen capture to test app
Fix bugs in screen share stream configuration
2024-12-15 22:47:39 -08:00
11 changed files with 84 additions and 41 deletions

View File

@@ -42,10 +42,10 @@ serde_derive.workspace = true
settings.workspace = true
util.workspace = true
[target.'cfg(target_os = "macos")'.dependencies]
[target.'cfg(any())'.dependencies]
livekit_client_macos = { workspace = true }
[target.'cfg(not(target_os = "macos"))'.dependencies]
[target.'cfg(all())'.dependencies]
livekit_client = { workspace = true }
[dev-dependencies]

View File

@@ -1,13 +1,13 @@
pub mod call_settings;
#[cfg(target_os = "macos")]
#[cfg(any())]
mod macos;
#[cfg(target_os = "macos")]
#[cfg(any())]
pub use macos::*;
#[cfg(not(target_os = "macos"))]
#[cfg(all())]
mod cross_platform;
#[cfg(not(target_os = "macos"))]
#[cfg(all())]
pub use cross_platform::*;

View File

@@ -1441,7 +1441,7 @@ impl Room {
let sources = sources.await??;
let source = sources.first().ok_or_else(|| anyhow!("no display found"))?;
let (track, stream) = capture_local_video_track(&**source).await?;
let (track, stream) = capture_local_video_track(&**source, None).await?;
let publication = participant
.publish_track(

View File

@@ -2078,17 +2078,7 @@ async fn test_mute_deafen(
audio_tracks_playing: participant
.audio_tracks
.values()
.map({
#[cfg(target_os = "macos")]
{
|track| track.is_playing()
}
#[cfg(not(target_os = "macos"))]
{
|(track, _)| track.rtc_track().enabled()
}
})
.map(|(track, _)| track.rtc_track().enabled())
.collect(),
})
.collect::<Vec<_>>()

View File

@@ -239,7 +239,7 @@ pub trait PlatformDisplay: Send + Sync + Debug {
/// A source of on-screen video content that can be captured.
pub trait ScreenCaptureSource {
/// Returns the video resolution of this source.
fn resolution(&self) -> Result<Size<Pixels>>;
fn resolution(&self) -> Size<DevicePixels>;
/// Start capture video from this source, invoking the given callback
/// with each frame.
@@ -253,6 +253,7 @@ pub trait ScreenCaptureSource {
pub trait ScreenCaptureStream {}
/// A frame of video captured from a screen.
#[derive(Clone)]
pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame);
/// An opaque identifier for a hardware display

View File

@@ -1,6 +1,6 @@
use crate::{
platform::{ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream},
px, size, Pixels, Size,
size, DevicePixels, Size,
};
use anyhow::{anyhow, Result};
use block::ConcreteBlock;
@@ -9,6 +9,10 @@ use cocoa::{
foundation::NSArray,
};
use core_foundation::base::TCFType;
use core_graphics::display::{
CGDirectDisplayID, CGDisplayCopyDisplayMode, CGDisplayModeGetPixelHeight,
CGDisplayModeGetPixelWidth, CGDisplayModeRelease,
};
use ctor::ctor;
use futures::channel::oneshot;
use media::core_media::{CMSampleBuffer, CMSampleBufferRef};
@@ -25,6 +29,7 @@ use std::{cell::RefCell, ffi::c_void, mem, ptr, rc::Rc};
#[derive(Clone)]
pub struct MacScreenCaptureSource {
sc_display: id,
size: Size<DevicePixels>,
}
pub struct MacScreenCaptureStream {
@@ -43,12 +48,8 @@ const FRAME_CALLBACK_IVAR: &str = "frame_callback";
const SCStreamOutputTypeScreen: NSInteger = 0;
impl ScreenCaptureSource for MacScreenCaptureSource {
fn resolution(&self) -> Result<Size<Pixels>> {
unsafe {
let width: i64 = msg_send![self.sc_display, width];
let height: i64 = msg_send![self.sc_display, height];
Ok(size(px(width as f32), px(height as f32)))
}
fn resolution(&self) -> Size<DevicePixels> {
self.size
}
fn stream(
@@ -61,13 +62,21 @@ impl ScreenCaptureSource for MacScreenCaptureSource {
let configuration: id = msg_send![class!(SCStreamConfiguration), alloc];
let delegate: id = msg_send![DELEGATE_CLASS, alloc];
let output: id = msg_send![OUTPUT_CLASS, alloc];
let excluded_windows = NSArray::array(nil);
let filter: id = msg_send![filter, initWithDisplay:self.sc_display excludingWindows:excluded_windows];
let configuration: id = msg_send![configuration, init];
let delegate: id = msg_send![delegate, init];
let output: id = msg_send![output, init];
// ASCII for '420f': https://developer.apple.com/documentation/screencapturekit/scstreamconfiguration/pixelformat?language=objc
let format = u32::from_be_bytes([52u8, 50u8, 48u8, 102u8]);
let _: () = msg_send![configuration, setShowsCursor:YES];
let _: () = msg_send![configuration, setWidth:self.size.width];
let _: () = msg_send![configuration, setHeight:self.size.height];
let _: () = msg_send![configuration, setPixelFormat:format];
let _: () = msg_send![configuration, setQueueDepth:5i32];
output.as_mut().unwrap().set_ivar(
FRAME_CALLBACK_IVAR,
Box::into_raw(Box::new(frame_callback)) as *mut c_void,
@@ -94,6 +103,7 @@ impl ScreenCaptureSource for MacScreenCaptureSource {
sc_stream: stream,
sc_stream_output: output,
};
Ok(Box::new(stream) as Box<dyn ScreenCaptureStream>)
} else {
let message: id = msg_send![error, localizedDescription];
@@ -159,8 +169,16 @@ pub(crate) fn get_sources() -> oneshot::Receiver<Result<Vec<Box<dyn ScreenCaptur
let mut result = Vec::new();
for i in 0..displays.count() {
let display = displays.objectAtIndex(i);
let display: id = msg_send![display, retain];
let display_id: CGDirectDisplayID = msg_send![display, displayID];
let display_mode_ref = CGDisplayCopyDisplayMode(display_id);
let width = CGDisplayModeGetPixelWidth(display_mode_ref);
let height = CGDisplayModeGetPixelHeight(display_mode_ref);
CGDisplayModeRelease(display_mode_ref);
let source = MacScreenCaptureSource {
sc_display: msg_send![display, retain],
sc_display: display,
size: size(DevicePixels(width as i32), DevicePixels(height as i32)),
};
result.push(Box::new(source) as Box<dyn ScreenCaptureSource>);
}

View File

@@ -1,7 +1,8 @@
use crate::{
px, size, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, ForegroundExecutor,
Keymap, Platform, PlatformDisplay, PlatformTextSystem, ScreenCaptureFrame, ScreenCaptureSource,
ScreenCaptureStream, Task, TestDisplay, TestWindow, WindowAppearance, WindowParams,
size, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DevicePixels,
ForegroundExecutor, Keymap, Platform, PlatformDisplay, PlatformTextSystem, ScreenCaptureFrame,
ScreenCaptureSource, ScreenCaptureStream, Task, TestDisplay, TestWindow, WindowAppearance,
WindowParams,
};
use anyhow::Result;
use collections::VecDeque;
@@ -46,8 +47,8 @@ pub struct TestScreenCaptureSource {}
pub struct TestScreenCaptureStream {}
impl ScreenCaptureSource for TestScreenCaptureSource {
fn resolution(&self) -> Result<crate::Size<crate::Pixels>> {
Ok(size(px(1.), px(1.)))
fn resolution(&self) -> crate::Size<crate::DevicePixels> {
size(DevicePixels(1), DevicePixels(1))
}
fn stream(

View File

@@ -49,6 +49,7 @@ livekit.workspace = true
[target.'cfg(target_os = "macos")'.dependencies]
core-foundation.workspace = true
coreaudio-rs = "0.12.1"
media.workspace = true
[dev-dependencies]
collections = { workspace = true, features = ["test-support"] }

View File

@@ -3,11 +3,12 @@
// it causes compile errors.
#![cfg_attr(target_os = "macos", allow(unused_imports))]
use futures::StreamExt;
use gpui::{
actions, bounds, div, point,
prelude::{FluentBuilder as _, IntoElement},
px, rgb, size, AsyncAppContext, Bounds, InteractiveElement, KeyBinding, Menu, MenuItem,
ParentElement, Pixels, Render, ScreenCaptureStream, SharedString,
ParentElement, Pixels, Render, ScreenCaptureFrame, ScreenCaptureStream, SharedString,
StatefulInteractiveElement as _, Styled, Task, View, ViewContext, VisualContext, WindowBounds,
WindowHandle, WindowOptions,
};
@@ -22,6 +23,7 @@ use livekit_client::{
track::{LocalTrack, RemoteTrack, RemoteVideoTrack, TrackSource},
AudioStream, RemoteVideoTrackView, Room, RoomEvent, RoomOptions,
};
use media::core_video::CVImageBuffer;
#[cfg(not(target_os = "windows"))]
use postage::stream::Stream;
@@ -108,6 +110,7 @@ struct LivekitWindow {
screen_share_track: Option<LocalTrackPublication>,
microphone_stream: Option<AudioStream>,
screen_share_stream: Option<Box<dyn ScreenCaptureStream>>,
latest_self_frame: Option<ScreenCaptureFrame>,
#[cfg(not(target_os = "windows"))]
remote_participants: Vec<(ParticipantIdentity, ParticipantState)>,
_events_task: Task<()>,
@@ -156,6 +159,7 @@ impl LivekitWindow {
microphone_stream: None,
screen_share_track: None,
screen_share_stream: None,
latest_self_frame: None,
remote_participants: Vec::new(),
_events_task,
}
@@ -312,7 +316,25 @@ impl LivekitWindow {
cx.spawn(|this, mut cx| async move {
let sources = sources.await.unwrap()?;
let source = sources.into_iter().next().unwrap();
let (track, stream) = capture_local_video_track(&*source).await?;
let (self_stream_tx, mut self_stream_rx) = futures::channel::mpsc::unbounded();
let (track, stream) =
capture_local_video_track(&*source, Some(self_stream_tx)).await?;
cx.spawn({
let this = this.clone();
|mut cx| async move {
while let Some(frame) = self_stream_rx.next().await {
this.update(&mut cx, |this, cx| {
this.latest_self_frame = Some(frame);
cx.notify();
})
.ok();
}
}
})
.detach();
let publication = participant
.publish_track(
LocalTrack::Video(track),
@@ -394,6 +416,11 @@ impl Render for LivekitWindow {
.on_click(cx.listener(|this, _, cx| this.toggle_screen_share(cx))),
]),
)
.children(
self.latest_self_frame
.as_ref()
.map(|frame| gpui::surface(frame.0.clone()).size_full()),
)
.child(
div()
.id("remote-participants")
@@ -403,7 +430,7 @@ impl Render for LivekitWindow {
.flex_grow()
.children(self.remote_participants.iter().map(|(identity, state)| {
div()
.h(px(300.0))
.size_full()
.flex()
.flex_col()
.m_2()

View File

@@ -142,8 +142,9 @@ pub fn init(
#[cfg(not(target_os = "windows"))]
pub async fn capture_local_video_track(
capture_source: &dyn ScreenCaptureSource,
show_capture: Option<futures::channel::mpsc::UnboundedSender<ScreenCaptureFrame>>,
) -> Result<(track::LocalVideoTrack, Box<dyn ScreenCaptureStream>)> {
let resolution = capture_source.resolution()?;
let resolution = capture_source.resolution();
let track_source = NativeVideoSource::new(VideoResolution {
width: resolution.width.0 as u32,
height: resolution.height.0 as u32,
@@ -153,6 +154,10 @@ pub async fn capture_local_video_track(
.stream({
let track_source = track_source.clone();
Box::new(move |frame| {
if let Some(show_capture) = show_capture.as_ref() {
show_capture.unbounded_send(frame.clone()).unwrap();
}
if let Some(buffer) = video_frame_buffer_to_webrtc(frame) {
track_source.capture_frame(&VideoFrame {
rotation: VideoRotation::VideoRotation0,

View File

@@ -1,11 +1,11 @@
#[cfg(target_os = "macos")]
#[cfg(any())]
mod macos;
#[cfg(target_os = "macos")]
#[cfg(any())]
pub use macos::*;
#[cfg(not(target_os = "macos"))]
#[cfg(all())]
mod cross_platform;
#[cfg(not(target_os = "macos"))]
#[cfg(all())]
pub use cross_platform::*;