Skip to main content

gosuto_libwebrtc/native/
video_source.rs

1// Copyright 2025 LiveKit, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{
16    sync::Arc,
17    time::{Duration, SystemTime, UNIX_EPOCH},
18};
19
20use cxx::SharedPtr;
21use livekit_runtime::interval;
22use parking_lot::Mutex;
23use gosuto_webrtc_sys::{video_frame as vf_sys, video_frame::ffi::VideoRotation, video_track as vt_sys};
24
25use crate::{
26    video_frame::{I420Buffer, VideoBuffer, VideoFrame},
27    video_source::VideoResolution,
28};
29
30impl From<vt_sys::ffi::VideoResolution> for VideoResolution {
31    fn from(res: vt_sys::ffi::VideoResolution) -> Self {
32        Self { width: res.width, height: res.height }
33    }
34}
35
36impl From<VideoResolution> for vt_sys::ffi::VideoResolution {
37    fn from(res: VideoResolution) -> Self {
38        Self { width: res.width, height: res.height }
39    }
40}
41
42#[derive(Clone)]
43pub struct NativeVideoSource {
44    sys_handle: SharedPtr<vt_sys::ffi::VideoTrackSource>,
45    inner: Arc<Mutex<VideoSourceInner>>,
46}
47
48struct VideoSourceInner {
49    captured_frames: usize,
50}
51
52impl NativeVideoSource {
53    pub fn new(resolution: VideoResolution, is_screencast: bool) -> NativeVideoSource {
54        let source = Self {
55            sys_handle: vt_sys::ffi::new_video_track_source(
56                &vt_sys::ffi::VideoResolution::from(resolution.clone()),
57                is_screencast,
58            ),
59            inner: Arc::new(Mutex::new(VideoSourceInner { captured_frames: 0 })),
60        };
61
62        livekit_runtime::spawn({
63            let source = source.clone();
64            let i420 = I420Buffer::new(resolution.width, resolution.height);
65            async move {
66                let mut interval = interval(Duration::from_millis(100)); // 10 fps
67
68                loop {
69                    interval.tick().await;
70
71                    let inner = source.inner.lock();
72                    if inner.captured_frames > 0 {
73                        break;
74                    }
75
76                    let mut builder = vf_sys::ffi::new_video_frame_builder();
77                    builder.pin_mut().set_rotation(VideoRotation::VideoRotation0);
78                    builder.pin_mut().set_video_frame_buffer(i420.as_ref().sys_handle());
79
80                    let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
81                    builder.pin_mut().set_timestamp_us(now.as_micros() as i64);
82
83                    source.sys_handle.on_captured_frame(&builder.pin_mut().build());
84                }
85            }
86        });
87
88        source
89    }
90
91    pub fn sys_handle(&self) -> SharedPtr<vt_sys::ffi::VideoTrackSource> {
92        self.sys_handle.clone()
93    }
94
95    pub fn capture_frame<T: AsRef<dyn VideoBuffer>>(&self, frame: &VideoFrame<T>) {
96        let mut inner = self.inner.lock();
97        inner.captured_frames += 1;
98
99        let mut builder = vf_sys::ffi::new_video_frame_builder();
100        builder.pin_mut().set_rotation(frame.rotation.into());
101        builder.pin_mut().set_video_frame_buffer(frame.buffer.as_ref().sys_handle());
102
103        if frame.timestamp_us == 0 {
104            // If the timestamp is set to 0, default to now
105            let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
106            builder.pin_mut().set_timestamp_us(now.as_micros() as i64);
107        } else {
108            builder.pin_mut().set_timestamp_us(frame.timestamp_us);
109        }
110
111        self.sys_handle.on_captured_frame(&builder.pin_mut().build());
112    }
113
114    pub fn video_resolution(&self) -> VideoResolution {
115        self.sys_handle.video_resolution().into()
116    }
117}