char/char-rtc-obs

git clone https://git.t4t.associates/char/char-rtc-obs

videogame hackerInitial commiteeaf4aa

main
4.9 KiB174 linesraw
1use crate::output::{EncodedPacket, EncodedPacketType, OutputStream, OutputStreamError};
2use anyhow::Result;
3use log::{error, info};
4use std::{
5    os::raw::{c_char, c_void},
6    slice,
7    time::Duration,
8};
9use tokio::runtime::Runtime;
10
11pub struct RTCOutput {
12    stream: OutputStream,
13    runtime: Runtime,
14}
15/// cbindgen:prefix-with-name
16#[repr(C)]
17pub enum RTCOutputError {
18    ConnectFailed,
19    NetworkError,
20}
21
22impl From<OutputStreamError> for RTCOutputError {
23    fn from(ose: OutputStreamError) -> Self {
24        match ose {
25            OutputStreamError::ConnectFailed => RTCOutputError::ConnectFailed,
26            OutputStreamError::NetworkError => RTCOutputError::NetworkError,
27            OutputStreamError::WriteError(_) => RTCOutputError::NetworkError,
28        }
29    }
30}
31
32// You must call `char_rtc_output_free` on the returned value
33#[no_mangle]
34pub extern "C" fn char_rtc_output_new() -> *mut RTCOutput {
35    (|| -> Result<*mut RTCOutput> {
36        let runtime = tokio::runtime::Runtime::new()?;
37        let stream = runtime.block_on(OutputStream::new())?;
38        Ok(Box::into_raw(Box::new(RTCOutput { stream, runtime })))
39    })()
40    .unwrap_or_else(|e| {
41        error!("Unable to create whip output: {e:?}");
42        std::ptr::null_mut::<RTCOutput>()
43    })
44}
45
46#[no_mangle]
47pub unsafe extern "C" fn char_rtc_output_free(output: *mut RTCOutput) {
48    info!("Freeing whip output");
49    if !output.is_null() {
50        drop(Box::from_raw(output));
51    }
52}
53
54#[no_mangle]
55pub unsafe extern "C" fn char_rtc_output_bytes_sent(output: *const RTCOutput) -> u64 {
56    let output = output.as_ref().unwrap();
57    output.stream.bytes_sent()
58}
59
60#[no_mangle]
61pub unsafe extern "C" fn char_rtc_output_dropped_frames(output: *const RTCOutput) -> i32 {
62    let output = output.as_ref().unwrap();
63    output.stream.dropped_frames()
64}
65
66#[no_mangle]
67pub unsafe extern "C" fn char_rtc_output_congestion(output: *const RTCOutput) -> f64 {
68    let output = output.as_ref().unwrap();
69    output.stream.congestion()
70}
71
72#[no_mangle]
73pub unsafe extern "C" fn char_rtc_output_connect_time_ms(output: *const RTCOutput) -> i32 {
74    let output = output.as_ref().unwrap();
75    output.stream.connect_time().as_millis() as i32
76}
77
78#[no_mangle]
79pub unsafe extern "C" fn char_rtc_output_connect(
80    output: *const RTCOutput,
81    url: *const c_char,
82    bearer_token: *const c_char,
83) {
84    let output = output.as_ref().unwrap();
85
86    let url = std::ffi::CStr::from_ptr(url).to_str().unwrap().to_owned();
87    let bearer_token = if !bearer_token.is_null() {
88        Some(
89            std::ffi::CStr::from_ptr(bearer_token)
90                .to_str()
91                .unwrap()
92                .to_owned(),
93        )
94    } else {
95        None
96    };
97
98    output.runtime.spawn(async move {
99        output.stream.connect(&url, bearer_token.as_deref()).await;
100    });
101}
102
103// Once closed, you cannot call `char_rtc_output_connect` again
104#[no_mangle]
105pub unsafe extern "C" fn char_rtc_output_close(output: *const RTCOutput) {
106    let output = output.as_ref().unwrap();
107
108    info!("Closing whip output");
109    output
110        .runtime
111        .block_on(async {
112            info!("I'm on a thread!");
113            output.stream.close().await
114        })
115        .unwrap_or_else(|e| error!("Failed closing whip output: {e:?}"))
116}
117
118/// Write an audio or video packet to the whip output
119#[no_mangle]
120pub unsafe extern "C" fn char_rtc_output_write(
121    output: *const RTCOutput,
122    data: *const u8,
123    size: usize,
124    duration: u64,
125    is_audio: bool,
126) -> bool {
127    let output = output.as_ref().unwrap();
128
129    let slice: &[u8] = slice::from_raw_parts(data, size);
130    let encoded_packet = EncodedPacket {
131        data: slice.to_owned(),
132        duration: Duration::from_micros(duration),
133        typ: if is_audio {
134            EncodedPacketType::Audio
135        } else {
136            EncodedPacketType::Video
137        },
138    };
139
140    output
141        .stream
142        .write(encoded_packet)
143        .map(|_| true)
144        .unwrap_or_else(|e| {
145            error!("Failed to write packets to whip output: {e:?}");
146            false
147        })
148}
149
150pub struct ErrorCallbackUserdata(*mut c_void);
151unsafe impl Send for ErrorCallbackUserdata {}
152unsafe impl Sync for ErrorCallbackUserdata {}
153
154impl ErrorCallbackUserdata {
155    fn as_ptr(&self) -> *mut c_void {
156        self.0
157    }
158}
159
160type RTCOutputErrorCallback = unsafe extern "C" fn(user_data: *mut c_void, error: RTCOutputError);
161
162#[no_mangle]
163pub unsafe extern "C" fn char_rtc_output_set_error_callback(
164    output: *const RTCOutput,
165    cb: RTCOutputErrorCallback,
166    user_data: *mut c_void,
167) {
168    let output = output.as_ref().unwrap();
169
170    let user_data = ErrorCallbackUserdata(user_data);
171    output
172        .stream
173        .set_error_callback(Box::new(move |error| cb(user_data.as_ptr(), error.into())))
174}