publish client-sdk-native (#12)
* Create README.md * add example * crates & examples * fix syntax * add LICENSE * thirdparty LICENSE * rearrange repo * fix build * egui versions * prepare publish * fix demo compilation * add test ci * Update rust.yml * forgot runs-on * install protoc before building * avoid rate limit * include submodules * updates to readme * cache rust builds Co-authored-by: David Zhao <[email protected]> Co-authored-by: David Zhao <[email protected]>
This commit is contained in:
co-authored by
David Zhao
David Zhao
parent
a927baac94
commit
a07b3451a3
@@ -0,0 +1,190 @@
|
||||
use std::error::Error;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use cxx::UniquePtr;
|
||||
use log::trace;
|
||||
|
||||
pub use sys_dc::ffi::{DataState, Priority};
|
||||
use webrtc_sys::data_channel as sys_dc;
|
||||
|
||||
pub struct DataChannel {
|
||||
cxx_handle: UniquePtr<sys_dc::ffi::DataChannel>,
|
||||
observer: Box<InternalDataChannelObserver>,
|
||||
|
||||
// Keep alive for C++
|
||||
native_observer: UniquePtr<sys_dc::ffi::NativeDataChannelObserver>,
|
||||
}
|
||||
|
||||
impl Debug for DataChannel {
|
||||
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
|
||||
f.debug_struct("DataChannel")
|
||||
.field("label", &self.label())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DataSendError;
|
||||
|
||||
impl Display for DataSendError {
|
||||
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
|
||||
write!(f, "failed to send data to the DataChannel")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for DataSendError {}
|
||||
|
||||
impl DataChannel {
|
||||
pub(crate) fn new(cxx_handle: UniquePtr<sys_dc::ffi::DataChannel>) -> Self {
|
||||
let mut observer = Box::new(InternalDataChannelObserver::default());
|
||||
|
||||
let mut dc = unsafe {
|
||||
Self {
|
||||
cxx_handle,
|
||||
native_observer: sys_dc::ffi::create_native_data_channel_observer(Box::new(
|
||||
sys_dc::DataChannelObserverWrapper::new(&mut *observer),
|
||||
)),
|
||||
observer,
|
||||
}
|
||||
};
|
||||
|
||||
unsafe {
|
||||
dc.cxx_handle
|
||||
.pin_mut()
|
||||
.register_observer(dc.native_observer.pin_mut());
|
||||
}
|
||||
|
||||
dc
|
||||
}
|
||||
|
||||
pub fn send(&self, data: &[u8], binary: bool) -> Result<(), DataSendError> {
|
||||
let buffer = sys_dc::ffi::DataBuffer {
|
||||
ptr: data.as_ptr(),
|
||||
len: data.len(),
|
||||
binary,
|
||||
};
|
||||
|
||||
self.cxx_handle
|
||||
.send(&buffer)
|
||||
.then_some(())
|
||||
.ok_or(DataSendError {})
|
||||
}
|
||||
|
||||
pub fn label(&self) -> String {
|
||||
self.cxx_handle.label()
|
||||
}
|
||||
|
||||
pub fn state(&self) -> DataState {
|
||||
self.cxx_handle.state()
|
||||
}
|
||||
|
||||
pub fn close(&self) {
|
||||
self.cxx_handle.close();
|
||||
}
|
||||
|
||||
pub fn on_state_change(&mut self, handler: OnStateChangeHandler) {
|
||||
*self.observer.on_state_change_handler.lock().unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_message(&mut self, handler: OnMessageHandler) {
|
||||
*self.observer.on_message_handler.lock().unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_buffered_amount_change(&mut self, handler: OnBufferedAmountChangeHandler) {
|
||||
*self
|
||||
.observer
|
||||
.on_buffered_amount_change_handler
|
||||
.lock()
|
||||
.unwrap() = Some(handler);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DataChannel {
|
||||
fn drop(&mut self) {
|
||||
self.cxx_handle.pin_mut().unregister_observer();
|
||||
}
|
||||
}
|
||||
|
||||
pub type OnStateChangeHandler = Box<dyn FnMut() + Send + Sync>;
|
||||
pub type OnMessageHandler = Box<dyn FnMut(&[u8], bool) + Send + Sync>;
|
||||
pub type OnBufferedAmountChangeHandler = Box<dyn FnMut(u64) + Send + Sync>;
|
||||
|
||||
#[derive(Default)]
|
||||
struct InternalDataChannelObserver {
|
||||
on_state_change_handler: Mutex<Option<OnStateChangeHandler>>,
|
||||
on_message_handler: Mutex<Option<OnMessageHandler>>,
|
||||
on_buffered_amount_change_handler: Mutex<Option<OnBufferedAmountChangeHandler>>,
|
||||
}
|
||||
|
||||
impl sys_dc::DataChannelObserver for InternalDataChannelObserver {
|
||||
fn on_state_change(&self) {
|
||||
trace!("DataChannel: on_state_change");
|
||||
let mut handler = self.on_state_change_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f();
|
||||
}
|
||||
}
|
||||
|
||||
fn on_message(&self, data: &[u8], is_binary: bool) {
|
||||
trace!("DataChannel: on_message");
|
||||
let mut handler = self.on_message_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f(data, is_binary);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_buffered_amount_change(&self, sent_data_size: u64) {
|
||||
trace!("DataChannel: on_buffered_amount_change");
|
||||
let mut handler = self.on_buffered_amount_change_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f(sent_data_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DataChannelInit {
|
||||
#[deprecated]
|
||||
pub reliable: bool,
|
||||
pub ordered: bool,
|
||||
pub max_retransmit_time: Option<i32>,
|
||||
pub max_retransmits: Option<i32>,
|
||||
pub protocol: String,
|
||||
pub negotiated: bool,
|
||||
pub id: i32,
|
||||
pub priority: Option<Priority>,
|
||||
}
|
||||
|
||||
impl Default for DataChannelInit {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
reliable: false,
|
||||
ordered: true,
|
||||
max_retransmit_time: None,
|
||||
max_retransmits: None,
|
||||
protocol: "".to_string(),
|
||||
negotiated: false,
|
||||
id: -1,
|
||||
priority: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DataChannelInit> for sys_dc::ffi::DataChannelInit {
|
||||
fn from(init: DataChannelInit) -> Self {
|
||||
Self {
|
||||
reliable: init.reliable,
|
||||
ordered: init.ordered,
|
||||
has_max_retransmit_time: init.max_retransmit_time.is_some(),
|
||||
max_retransmit_time: init.max_retransmit_time.unwrap_or_default(),
|
||||
has_max_retransmits: init.max_retransmits.is_some(),
|
||||
max_retransmits: init.max_retransmits.unwrap_or_default(),
|
||||
protocol: init.protocol,
|
||||
negotiated: init.negotiated,
|
||||
id: init.id,
|
||||
has_priority: init.priority.is_some(),
|
||||
priority: init.priority.unwrap_or(Priority::Low),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
use std::fmt::{Debug, Formatter};
|
||||
|
||||
use cxx::UniquePtr;
|
||||
|
||||
pub use sys_jsep::ffi::{SdpParseError, SdpType};
|
||||
use webrtc_sys::jsep as sys_jsep;
|
||||
|
||||
// TODO Maybe we can replace that by a serialized IceCandidateInit
|
||||
pub struct IceCandidate {
|
||||
cxx_handle: UniquePtr<sys_jsep::ffi::IceCandidate>,
|
||||
}
|
||||
|
||||
impl Debug for IceCandidate {
|
||||
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
|
||||
write!(f, "IceCandidate[{}]", self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl IceCandidate {
|
||||
pub fn from(
|
||||
sdp_mid: &str,
|
||||
sdp_mline_index: i32,
|
||||
sdp: &str,
|
||||
) -> Result<IceCandidate, SdpParseError> {
|
||||
let res = sys_jsep::ffi::create_ice_candidate(
|
||||
sdp_mid.to_string(),
|
||||
sdp_mline_index,
|
||||
sdp.to_string(),
|
||||
);
|
||||
|
||||
match res {
|
||||
Ok(cxx_handle) => Ok(IceCandidate::new(cxx_handle)),
|
||||
Err(e) => Err(unsafe { SdpParseError::from(e.what()) }),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new(cxx_handle: UniquePtr<sys_jsep::ffi::IceCandidate>) -> Self {
|
||||
Self { cxx_handle }
|
||||
}
|
||||
|
||||
pub(crate) fn release(self) -> UniquePtr<sys_jsep::ffi::IceCandidate> {
|
||||
self.cxx_handle
|
||||
}
|
||||
|
||||
pub fn sdp_mid(&self) -> String {
|
||||
self.cxx_handle.sdp_mid()
|
||||
}
|
||||
|
||||
pub fn sdp_mline_index(&self) -> i32 {
|
||||
self.cxx_handle.sdp_mline_index()
|
||||
}
|
||||
|
||||
pub fn candidate(&self) -> String {
|
||||
self.cxx_handle.candidate()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for IceCandidate {
|
||||
fn to_string(&self) -> String {
|
||||
self.cxx_handle.stringify()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SessionDescription {
|
||||
cxx_handle: UniquePtr<sys_jsep::ffi::SessionDescription>,
|
||||
}
|
||||
|
||||
impl Debug for SessionDescription {
|
||||
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
|
||||
write!(f, "SessionDescription[{}]", self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl SessionDescription {
|
||||
pub fn from(sdp_type: SdpType, description: &str) -> Result<SessionDescription, SdpParseError> {
|
||||
let res = sys_jsep::ffi::create_session_description(sdp_type, description.to_string());
|
||||
|
||||
match res {
|
||||
Ok(cxx_handle) => Ok(SessionDescription::new(cxx_handle)),
|
||||
Err(e) => Err(unsafe { SdpParseError::from(e.what()) }),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new(cxx_handle: UniquePtr<sys_jsep::ffi::SessionDescription>) -> Self {
|
||||
Self { cxx_handle }
|
||||
}
|
||||
|
||||
pub(crate) fn release(self) -> UniquePtr<sys_jsep::ffi::SessionDescription> {
|
||||
self.cxx_handle
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for SessionDescription {
|
||||
fn to_string(&self) -> String {
|
||||
self.cxx_handle.stringify()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for SessionDescription {
|
||||
fn clone(&self) -> Self {
|
||||
SessionDescription::new(self.cxx_handle.clone())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
pub mod data_channel;
|
||||
pub mod jsep;
|
||||
pub mod media_stream;
|
||||
pub mod peer_connection;
|
||||
pub mod peer_connection_factory;
|
||||
pub mod rtc_error;
|
||||
pub mod rtp_receiver;
|
||||
pub mod rtp_transceiver;
|
||||
pub mod video_frame;
|
||||
pub mod video_frame_buffer;
|
||||
pub mod webrtc;
|
||||
pub mod yuv_helper;
|
||||
@@ -0,0 +1,265 @@
|
||||
use cxx::UniquePtr;
|
||||
use livekit_utils::enum_dispatch;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::pin::Pin;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use webrtc_sys::media_stream as sys_ms;
|
||||
use webrtc_sys::MEDIA_TYPE_VIDEO;
|
||||
|
||||
pub use sys_ms::ffi::ContentHint;
|
||||
pub use sys_ms::ffi::TrackState;
|
||||
|
||||
use crate::video_frame::VideoFrame;
|
||||
use crate::video_frame_buffer::VideoFrameBuffer;
|
||||
|
||||
pub trait MediaStreamTrackTrait {
|
||||
fn kind(&self) -> String;
|
||||
fn id(&self) -> String;
|
||||
fn enabled(&self) -> bool;
|
||||
fn set_enabled(&self, enabled: bool) -> bool;
|
||||
fn state(&self) -> TrackState;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum MediaStreamTrackHandle {
|
||||
Audio(Arc<AudioTrack>),
|
||||
Video(Arc<VideoTrack>),
|
||||
}
|
||||
|
||||
impl MediaStreamTrackHandle {
|
||||
pub(crate) fn new(cxx_handle: UniquePtr<sys_ms::ffi::MediaStreamTrack>) -> Self {
|
||||
unsafe {
|
||||
if cxx_handle.kind() == MEDIA_TYPE_VIDEO {
|
||||
Self::Video(VideoTrack::new(UniquePtr::from_raw(
|
||||
sys_ms::ffi::media_to_video(cxx_handle.into_raw())
|
||||
as *mut sys_ms::ffi::VideoTrack,
|
||||
)))
|
||||
} else {
|
||||
Self::Audio(AudioTrack::new(UniquePtr::from_raw(
|
||||
sys_ms::ffi::media_to_audio(cxx_handle.into_raw())
|
||||
as *mut sys_ms::ffi::AudioTrack,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for MediaStreamTrackHandle {
|
||||
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
|
||||
f.debug_struct("MediaStreamTrack")
|
||||
.field("id", &self.id())
|
||||
.field("kind", &self.kind())
|
||||
.field("enabled", &self.enabled())
|
||||
.field("state", &self.state())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl MediaStreamTrackTrait for MediaStreamTrackHandle {
|
||||
enum_dispatch!(
|
||||
[Audio, Video]
|
||||
fnc!(kind, &Self, [], String);
|
||||
fnc!(id, &Self, [], String);
|
||||
fnc!(enabled, &Self, [], bool);
|
||||
fnc!(state, &Self, [], TrackState);
|
||||
fnc!(set_enabled, &Self, [enabled: bool], bool);
|
||||
);
|
||||
}
|
||||
|
||||
pub struct AudioTrack {
|
||||
cxx_handle: Mutex<UniquePtr<sys_ms::ffi::AudioTrack>>,
|
||||
}
|
||||
|
||||
impl AudioTrack {
|
||||
pub(crate) fn new(cxx_handle: UniquePtr<sys_ms::ffi::AudioTrack>) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
cxx_handle: Mutex::new(cxx_handle),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VideoTrack {
|
||||
cxx_handle: Mutex<UniquePtr<sys_ms::ffi::VideoTrack>>,
|
||||
observer: Box<InternalVideoTrackSink>,
|
||||
|
||||
// Keep alive for c++
|
||||
native_observer: UniquePtr<sys_ms::ffi::NativeVideoFrameSink>,
|
||||
}
|
||||
|
||||
macro_rules! impl_media_stream_track_trait {
|
||||
($x:ty, $cast:ident) => {
|
||||
impl MediaStreamTrackTrait for $x {
|
||||
fn kind(&self) -> String {
|
||||
unsafe { (*sys_ms::ffi::$cast(&**self.cxx_handle.lock().unwrap())).kind() }
|
||||
}
|
||||
|
||||
fn id(&self) -> String {
|
||||
unsafe { (*sys_ms::ffi::$cast(&**self.cxx_handle.lock().unwrap())).id() }
|
||||
}
|
||||
|
||||
fn enabled(&self) -> bool {
|
||||
unsafe { (*sys_ms::ffi::$cast(&**self.cxx_handle.lock().unwrap())).enabled() }
|
||||
}
|
||||
|
||||
fn set_enabled(&self, enabled: bool) -> bool {
|
||||
unsafe {
|
||||
let media = sys_ms::ffi::$cast(&**self.cxx_handle.lock().unwrap())
|
||||
as *mut sys_ms::ffi::MediaStreamTrack;
|
||||
|
||||
Pin::new_unchecked(&mut *media).set_enabled(enabled)
|
||||
}
|
||||
}
|
||||
|
||||
fn state(&self) -> TrackState {
|
||||
unsafe { (*sys_ms::ffi::$cast(&**self.cxx_handle.lock().unwrap())).state() }
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_media_stream_track_trait!(VideoTrack, video_to_media);
|
||||
impl_media_stream_track_trait!(AudioTrack, audio_to_media);
|
||||
|
||||
pub type OnFrameHandler = Box<dyn FnMut(VideoFrame, VideoFrameBuffer) + Send + Sync>;
|
||||
pub type OnDiscardedFrameHandler = Box<dyn FnMut() + Send + Sync>;
|
||||
pub type OnConstraintsChanged = Box<dyn FnMut(VideoTrackSourceConstraints) + Send + Sync>;
|
||||
|
||||
#[derive(Default)]
|
||||
struct InternalVideoTrackSink {
|
||||
on_frame_handler: Mutex<Option<OnFrameHandler>>,
|
||||
on_discarded_frame_handler: Mutex<Option<OnDiscardedFrameHandler>>,
|
||||
on_constraints_changed_handler: Mutex<Option<OnConstraintsChanged>>,
|
||||
}
|
||||
|
||||
pub struct VideoTrackSourceConstraints {
|
||||
pub min_fps: Option<f64>,
|
||||
pub max_fps: Option<f64>,
|
||||
}
|
||||
|
||||
impl From<sys_ms::ffi::VideoTrackSourceConstraints> for VideoTrackSourceConstraints {
|
||||
fn from(cst: sys_ms::ffi::VideoTrackSourceConstraints) -> Self {
|
||||
Self {
|
||||
min_fps: (cst.min_fps != 1.0).then_some(cst.min_fps),
|
||||
max_fps: (cst.max_fps != 1.0).then_some(cst.max_fps),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl sys_ms::VideoFrameSink for InternalVideoTrackSink {
|
||||
fn on_frame(&self, frame: UniquePtr<webrtc_sys::video_frame::ffi::VideoFrame>) {
|
||||
if let Some(cb) = self.on_frame_handler.lock().unwrap().as_mut() {
|
||||
let frame = VideoFrame::new(frame);
|
||||
let video_frame_buffer = unsafe { frame.video_frame_buffer() };
|
||||
cb(frame, video_frame_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_discarded_frame(&self) {
|
||||
if let Some(cb) = self.on_discarded_frame_handler.lock().unwrap().as_mut() {
|
||||
cb();
|
||||
}
|
||||
}
|
||||
|
||||
fn on_constraints_changed(&self, constraints: sys_ms::ffi::VideoTrackSourceConstraints) {
|
||||
if let Some(cb) = self.on_constraints_changed_handler.lock().unwrap().as_mut() {
|
||||
cb(constraints.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VideoTrack {
|
||||
fn new(cxx_handle: UniquePtr<sys_ms::ffi::VideoTrack>) -> Arc<Self> {
|
||||
let mut observer = Box::new(InternalVideoTrackSink::default());
|
||||
|
||||
let mut track = unsafe {
|
||||
Self {
|
||||
cxx_handle: Mutex::new(cxx_handle),
|
||||
native_observer: sys_ms::ffi::create_native_video_frame_sink(Box::new(
|
||||
sys_ms::VideoFrameSinkWrapper::new(&mut *observer),
|
||||
)),
|
||||
observer,
|
||||
}
|
||||
};
|
||||
|
||||
unsafe {
|
||||
track
|
||||
.cxx_handle
|
||||
.lock()
|
||||
.unwrap()
|
||||
.pin_mut()
|
||||
.add_sink(track.native_observer.pin_mut());
|
||||
}
|
||||
|
||||
Arc::new(track)
|
||||
}
|
||||
|
||||
pub fn set_should_receive(&self, should_receive: bool) {
|
||||
self.cxx_handle
|
||||
.lock()
|
||||
.unwrap()
|
||||
.pin_mut()
|
||||
.set_should_receive(should_receive)
|
||||
}
|
||||
|
||||
pub fn set_content_hint(&self, hint: ContentHint) {
|
||||
self.cxx_handle
|
||||
.lock()
|
||||
.unwrap()
|
||||
.pin_mut()
|
||||
.set_content_hint(hint)
|
||||
}
|
||||
|
||||
pub fn should_receive(&self) -> bool {
|
||||
self.cxx_handle.lock().unwrap().should_receive()
|
||||
}
|
||||
|
||||
pub fn content_hint(&self) -> ContentHint {
|
||||
self.cxx_handle.lock().unwrap().content_hint()
|
||||
}
|
||||
|
||||
pub fn on_frame(&self, handler: OnFrameHandler) {
|
||||
*self.observer.on_frame_handler.lock().unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_discarded_frame(&self, handler: OnDiscardedFrameHandler) {
|
||||
*self.observer.on_discarded_frame_handler.lock().unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_constraints_changed(&self, handler: OnConstraintsChanged) {
|
||||
*self.observer.on_constraints_changed_handler.lock().unwrap() = Some(handler);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for VideoTrack {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
self.cxx_handle
|
||||
.lock()
|
||||
.unwrap()
|
||||
.pin_mut()
|
||||
.remove_sink(self.native_observer.pin_mut());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MediaStream {
|
||||
cxx_handle: UniquePtr<sys_ms::ffi::MediaStream>,
|
||||
}
|
||||
|
||||
impl Debug for MediaStream {
|
||||
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
|
||||
f.debug_struct("MediaStream")
|
||||
.field("id", &self.id())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl MediaStream {
|
||||
pub(crate) fn new(cxx_handle: UniquePtr<sys_ms::ffi::MediaStream>) -> Self {
|
||||
Self { cxx_handle }
|
||||
}
|
||||
|
||||
pub fn id(&self) -> String {
|
||||
self.cxx_handle.id()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,687 @@
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use cxx::UniquePtr;
|
||||
use log::trace;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
|
||||
use webrtc_sys::data_channel as sys_dc;
|
||||
use webrtc_sys::jsep as sys_jsep;
|
||||
use webrtc_sys::peer_connection as sys_pc;
|
||||
pub use webrtc_sys::peer_connection::ffi::IceConnectionState;
|
||||
pub use webrtc_sys::peer_connection::ffi::IceGatheringState;
|
||||
use webrtc_sys::peer_connection::ffi::NativeCreateSdpObserverHandle;
|
||||
pub use webrtc_sys::peer_connection::ffi::PeerConnectionState;
|
||||
pub use webrtc_sys::peer_connection::ffi::RTCOfferAnswerOptions;
|
||||
pub use webrtc_sys::peer_connection::ffi::SignalingState;
|
||||
|
||||
use crate::data_channel::{DataChannel, DataChannelInit};
|
||||
use crate::jsep::{IceCandidate, SessionDescription};
|
||||
use crate::media_stream::{AudioTrack, MediaStream, VideoTrack};
|
||||
use crate::rtc_error::RTCError;
|
||||
use crate::rtp_receiver::RtpReceiver;
|
||||
use crate::rtp_transceiver::RtpTransceiver;
|
||||
|
||||
pub struct PeerConnection {
|
||||
cxx_handle: UniquePtr<sys_pc::ffi::PeerConnection>,
|
||||
observer: Box<InternalObserver>,
|
||||
|
||||
// Keep alive for C++
|
||||
#[allow(unused)]
|
||||
native_observer: UniquePtr<sys_pc::ffi::NativePeerConnectionObserver>,
|
||||
}
|
||||
|
||||
impl Debug for PeerConnection {
|
||||
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
|
||||
f.debug_struct("PeerConnection")
|
||||
.field("signaling_state", &self.signaling_state())
|
||||
.field("ice_connection_state", &self.ice_connection_state())
|
||||
.field("ice_gathering_state", &self.ice_gathering_state())
|
||||
.field("local_description", &self.local_description())
|
||||
.field("remote_description", &self.remote_description())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl PeerConnection {
|
||||
pub(crate) fn new(
|
||||
cxx_handle: UniquePtr<sys_pc::ffi::PeerConnection>,
|
||||
observer: Box<InternalObserver>,
|
||||
native_observer: UniquePtr<sys_pc::ffi::NativePeerConnectionObserver>,
|
||||
) -> Self {
|
||||
Self {
|
||||
cxx_handle,
|
||||
observer,
|
||||
native_observer,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_sdp_observer() -> (
|
||||
UniquePtr<NativeCreateSdpObserverHandle>,
|
||||
mpsc::Receiver<Result<SessionDescription, RTCError>>,
|
||||
) {
|
||||
let (tx, rx) = mpsc::channel(1);
|
||||
let wrapper = sys_jsep::CreateSdpObserverWrapper {
|
||||
on_success: ManuallyDrop::new(Box::new({
|
||||
let tx = tx.clone();
|
||||
move |session_description| {
|
||||
let _ = tx.blocking_send(Ok(SessionDescription::new(session_description)));
|
||||
}
|
||||
})),
|
||||
on_failure: ManuallyDrop::new(Box::new(move |error| {
|
||||
let _ = tx.blocking_send(Err(error));
|
||||
})),
|
||||
};
|
||||
|
||||
(
|
||||
sys_jsep::ffi::create_native_create_sdp_observer(Box::new(wrapper)),
|
||||
rx,
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn create_offer(
|
||||
&mut self,
|
||||
options: RTCOfferAnswerOptions,
|
||||
) -> Result<SessionDescription, RTCError> {
|
||||
let (mut native_wrapper, mut rx) = Self::create_sdp_observer();
|
||||
|
||||
unsafe {
|
||||
self.cxx_handle
|
||||
.pin_mut()
|
||||
.create_offer(native_wrapper.pin_mut(), options);
|
||||
}
|
||||
|
||||
rx.recv().await.unwrap()
|
||||
}
|
||||
|
||||
pub async fn create_answer(
|
||||
&mut self,
|
||||
options: RTCOfferAnswerOptions,
|
||||
) -> Result<SessionDescription, RTCError> {
|
||||
let (mut native_wrapper, mut rx) = Self::create_sdp_observer();
|
||||
|
||||
unsafe {
|
||||
self.cxx_handle
|
||||
.pin_mut()
|
||||
.create_answer(native_wrapper.pin_mut(), options);
|
||||
}
|
||||
|
||||
rx.recv().await.unwrap()
|
||||
}
|
||||
|
||||
pub async fn set_local_description(
|
||||
&mut self,
|
||||
desc: SessionDescription,
|
||||
) -> Result<(), RTCError> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let wrapper =
|
||||
sys_jsep::SetLocalSdpObserverWrapper(ManuallyDrop::new(Box::new(move |error| {
|
||||
let _ = tx.send(if error.ok() { Ok(()) } else { Err(error) });
|
||||
})));
|
||||
let mut native_wrapper =
|
||||
sys_jsep::ffi::create_native_set_local_sdp_observer(Box::new(wrapper));
|
||||
|
||||
unsafe {
|
||||
self.cxx_handle
|
||||
.pin_mut()
|
||||
.set_local_description(desc.release(), native_wrapper.pin_mut());
|
||||
}
|
||||
|
||||
rx.await.unwrap()
|
||||
}
|
||||
|
||||
pub async fn set_remote_description(
|
||||
&mut self,
|
||||
desc: SessionDescription,
|
||||
) -> Result<(), RTCError> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let wrapper =
|
||||
sys_jsep::SetRemoteSdpObserverWrapper(ManuallyDrop::new(Box::new(move |error| {
|
||||
let _ = tx.send(if error.ok() { Ok(()) } else { Err(error) });
|
||||
})));
|
||||
let mut native_wrapper =
|
||||
sys_jsep::ffi::create_native_set_remote_sdp_observer(Box::new(wrapper));
|
||||
|
||||
unsafe {
|
||||
self.cxx_handle
|
||||
.pin_mut()
|
||||
.set_remote_description(desc.release(), native_wrapper.pin_mut());
|
||||
}
|
||||
|
||||
rx.await.unwrap()
|
||||
}
|
||||
|
||||
pub fn create_data_channel(
|
||||
&mut self,
|
||||
label: &str,
|
||||
init: DataChannelInit,
|
||||
) -> Result<DataChannel, RTCError> {
|
||||
let native_init = sys_dc::ffi::create_data_channel_init(init.into());
|
||||
let res = self
|
||||
.cxx_handle
|
||||
.pin_mut()
|
||||
.create_data_channel(label.to_string(), native_init);
|
||||
|
||||
match res {
|
||||
Ok(cxx_handle) => Ok(DataChannel::new(cxx_handle)),
|
||||
Err(e) => Err(unsafe { RTCError::from(e.what()) }),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(theomonnom) Use IceCandidateInit instead of IceCandidate
|
||||
pub async fn add_ice_candidate(&mut self, candidate: IceCandidate) -> Result<(), RTCError> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let observer =
|
||||
sys_pc::AddIceCandidateObserverWrapper(ManuallyDrop::new(Box::new(|error| {
|
||||
let _ = tx.send(if error.ok() { Ok(()) } else { Err(error) });
|
||||
})));
|
||||
|
||||
let mut native_observer =
|
||||
sys_pc::ffi::create_native_add_ice_candidate_observer(Box::new(observer));
|
||||
self.cxx_handle
|
||||
.pin_mut()
|
||||
.add_ice_candidate(candidate.release(), native_observer.pin_mut());
|
||||
|
||||
rx.await.unwrap()
|
||||
}
|
||||
|
||||
pub fn local_description(&self) -> Option<SessionDescription> {
|
||||
let local_description = self.cxx_handle.local_description();
|
||||
if local_description.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(SessionDescription::new(local_description))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remote_description(&self) -> Option<SessionDescription> {
|
||||
let remote_description = self.cxx_handle.remote_description();
|
||||
if remote_description.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(SessionDescription::new(remote_description))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn signaling_state(&self) -> SignalingState {
|
||||
self.cxx_handle.signaling_state()
|
||||
}
|
||||
|
||||
pub fn ice_gathering_state(&self) -> IceGatheringState {
|
||||
self.cxx_handle.ice_gathering_state()
|
||||
}
|
||||
|
||||
pub fn ice_connection_state(&self) -> IceConnectionState {
|
||||
self.cxx_handle.ice_connection_state()
|
||||
}
|
||||
|
||||
pub fn close(&mut self) {
|
||||
self.cxx_handle.pin_mut().close();
|
||||
}
|
||||
|
||||
pub fn on_signaling_change(&mut self, handler: OnSignalingChangeHandler) {
|
||||
*self.observer.on_signaling_change_handler.lock().unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_add_stream(&mut self, handler: OnAddStreamHandler) {
|
||||
*self.observer.on_add_stream_handler.lock().unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_remove_stream(&mut self, handler: OnRemoveStreamHandler) {
|
||||
*self.observer.on_remove_stream_handler.lock().unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_data_channel(&mut self, handler: OnDataChannelHandler) {
|
||||
*self.observer.on_data_channel_handler.lock().unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_renegotiation_needed(&mut self, handler: OnRenegotiationNeededHandler) {
|
||||
*self
|
||||
.observer
|
||||
.on_renegotiation_needed_handler
|
||||
.lock()
|
||||
.unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_ice_connection_change(&mut self, handler: OnIceConnectionChangeHandler) {
|
||||
*self
|
||||
.observer
|
||||
.on_ice_connection_change_handler
|
||||
.lock()
|
||||
.unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_standardized_ice_connection_change(
|
||||
&mut self,
|
||||
handler: OnStandardizedIceConnectionChangeHandler,
|
||||
) {
|
||||
*self
|
||||
.observer
|
||||
.on_standardized_ice_connection_change_handler
|
||||
.lock()
|
||||
.unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_connection_change(&mut self, handler: OnConnectionChangeHandler) {
|
||||
*self.observer.on_connection_change_handler.lock().unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_ice_gathering_change(&mut self, handler: OnIceGatheringChangeHandler) {
|
||||
*self
|
||||
.observer
|
||||
.on_ice_gathering_change_handler
|
||||
.lock()
|
||||
.unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_ice_candidate(&mut self, handler: OnIceCandidateHandler) {
|
||||
*self.observer.on_ice_candidate_handler.lock().unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_ice_candidate_error(&mut self, handler: OnIceCandidateErrorHandler) {
|
||||
*self.observer.on_ice_candidate_error_handler.lock().unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_ice_candidates_removed(&mut self, handler: OnIceCandidatesRemovedHandler) {
|
||||
*self
|
||||
.observer
|
||||
.on_ice_candidates_removed_handler
|
||||
.lock()
|
||||
.unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_ice_connection_receiving_change(
|
||||
&mut self,
|
||||
handler: OnIceConnectionReceivingChangeHandler,
|
||||
) {
|
||||
*self
|
||||
.observer
|
||||
.on_ice_connection_receiving_change_handler
|
||||
.lock()
|
||||
.unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_ice_selected_candidate_pair_changed(
|
||||
&mut self,
|
||||
handler: OnIceSelectedCandidatePairChangedHandler,
|
||||
) {
|
||||
*self
|
||||
.observer
|
||||
.on_ice_selected_candidate_pair_changed_handler
|
||||
.lock()
|
||||
.unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_add_track(&mut self, handler: OnAddTrackHandler) {
|
||||
*self.observer.on_add_track_handler.lock().unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_track(&mut self, handler: OnTrackHandler) {
|
||||
*self.observer.on_track_handler.lock().unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_remove_track(&mut self, handler: OnRemoveTrackHandler) {
|
||||
*self.observer.on_remove_track_handler.lock().unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_interesting_usage(&mut self, handler: OnInterestingUsageHandler) {
|
||||
*self.observer.on_interesting_usage_handler.lock().unwrap() = Some(handler);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(theomonnom) Should we return futures?
|
||||
pub type OnSignalingChangeHandler = Box<dyn FnMut(SignalingState) + Send + Sync>;
|
||||
pub type OnAddStreamHandler = Box<dyn FnMut(MediaStream) + Send + Sync>;
|
||||
pub type OnRemoveStreamHandler = Box<dyn FnMut(MediaStream) + Send + Sync>;
|
||||
pub type OnDataChannelHandler = Box<dyn FnMut(DataChannel) + Send + Sync>;
|
||||
pub type OnRenegotiationNeededHandler = Box<dyn FnMut() + Send + Sync>;
|
||||
pub type OnNegotiationNeededEventHandler = Box<dyn FnMut(u32) + Send + Sync>;
|
||||
pub type OnIceConnectionChangeHandler = Box<dyn FnMut(IceConnectionState) + Send + Sync>;
|
||||
pub type OnStandardizedIceConnectionChangeHandler =
|
||||
Box<dyn FnMut(IceConnectionState) + Send + Sync>;
|
||||
pub type OnConnectionChangeHandler = Box<dyn FnMut(PeerConnectionState) + Send + Sync>;
|
||||
pub type OnIceGatheringChangeHandler = Box<dyn FnMut(IceGatheringState) + Send + Sync>;
|
||||
pub type OnIceCandidateHandler = Box<dyn FnMut(IceCandidate) + Send + Sync>;
|
||||
pub type OnIceCandidateErrorHandler =
|
||||
Box<dyn FnMut(String, i32, String, i32, String) + Send + Sync>;
|
||||
pub type OnIceCandidatesRemovedHandler = Box<dyn FnMut(Vec<IceCandidate>) + Send + Sync>;
|
||||
pub type OnIceConnectionReceivingChangeHandler = Box<dyn FnMut(bool) + Send + Sync>;
|
||||
pub type OnIceSelectedCandidatePairChangedHandler =
|
||||
Box<dyn FnMut(webrtc_sys::peer_connection::ffi::CandidatePairChangeEvent) + Send + Sync>;
|
||||
pub type OnAddTrackHandler = Box<dyn FnMut(RtpReceiver, Vec<MediaStream>) + Send + Sync>;
|
||||
pub type OnTrackHandler = Box<dyn FnMut(RtpTransceiver) + Send + Sync>;
|
||||
pub type OnRemoveTrackHandler = Box<dyn FnMut(RtpReceiver) + Send + Sync>;
|
||||
pub type OnInterestingUsageHandler = Box<dyn FnMut(i32) + Send + Sync>;
|
||||
|
||||
pub(crate) struct InternalObserver {
|
||||
on_signaling_change_handler: Arc<Mutex<Option<OnSignalingChangeHandler>>>,
|
||||
on_add_stream_handler: Arc<Mutex<Option<OnAddStreamHandler>>>,
|
||||
on_remove_stream_handler: Arc<Mutex<Option<OnRemoveStreamHandler>>>,
|
||||
on_data_channel_handler: Arc<Mutex<Option<OnDataChannelHandler>>>,
|
||||
on_renegotiation_needed_handler: Arc<Mutex<Option<OnRenegotiationNeededHandler>>>,
|
||||
on_negotiation_needed_event_handler: Arc<Mutex<Option<OnNegotiationNeededEventHandler>>>,
|
||||
on_ice_connection_change_handler: Arc<Mutex<Option<OnIceConnectionChangeHandler>>>,
|
||||
on_standardized_ice_connection_change_handler:
|
||||
Arc<Mutex<Option<OnStandardizedIceConnectionChangeHandler>>>,
|
||||
on_connection_change_handler: Arc<Mutex<Option<OnConnectionChangeHandler>>>,
|
||||
on_ice_gathering_change_handler: Arc<Mutex<Option<OnIceGatheringChangeHandler>>>,
|
||||
on_ice_candidate_handler: Arc<Mutex<Option<OnIceCandidateHandler>>>,
|
||||
on_ice_candidate_error_handler: Arc<Mutex<Option<OnIceCandidateErrorHandler>>>,
|
||||
on_ice_candidates_removed_handler: Arc<Mutex<Option<OnIceCandidatesRemovedHandler>>>,
|
||||
on_ice_connection_receiving_change_handler:
|
||||
Arc<Mutex<Option<OnIceConnectionReceivingChangeHandler>>>,
|
||||
on_ice_selected_candidate_pair_changed_handler:
|
||||
Arc<Mutex<Option<OnIceSelectedCandidatePairChangedHandler>>>,
|
||||
on_add_track_handler: Arc<Mutex<Option<OnAddTrackHandler>>>,
|
||||
on_track_handler: Arc<Mutex<Option<OnTrackHandler>>>,
|
||||
on_remove_track_handler: Arc<Mutex<Option<OnRemoveTrackHandler>>>,
|
||||
on_interesting_usage_handler: Arc<Mutex<Option<OnInterestingUsageHandler>>>,
|
||||
}
|
||||
|
||||
impl Default for InternalObserver {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
on_signaling_change_handler: Arc::new(Default::default()),
|
||||
on_add_stream_handler: Arc::new(Default::default()),
|
||||
on_remove_stream_handler: Arc::new(Default::default()),
|
||||
on_data_channel_handler: Arc::new(Default::default()),
|
||||
on_renegotiation_needed_handler: Arc::new(Default::default()),
|
||||
on_negotiation_needed_event_handler: Arc::new(Default::default()),
|
||||
on_ice_connection_change_handler: Arc::new(Default::default()),
|
||||
on_standardized_ice_connection_change_handler: Arc::new(Default::default()),
|
||||
on_connection_change_handler: Arc::new(Default::default()),
|
||||
on_ice_gathering_change_handler: Arc::new(Default::default()),
|
||||
on_ice_candidate_handler: Arc::new(Default::default()),
|
||||
on_ice_candidate_error_handler: Arc::new(Default::default()),
|
||||
on_ice_candidates_removed_handler: Arc::new(Default::default()),
|
||||
on_ice_connection_receiving_change_handler: Arc::new(Default::default()),
|
||||
on_ice_selected_candidate_pair_changed_handler: Arc::new(Default::default()),
|
||||
on_add_track_handler: Arc::new(Default::default()),
|
||||
on_track_handler: Arc::new(Default::default()),
|
||||
on_remove_track_handler: Arc::new(Default::default()),
|
||||
on_interesting_usage_handler: Arc::new(Default::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Observers are being called on the Signaling Thread
|
||||
impl sys_pc::PeerConnectionObserver for InternalObserver {
|
||||
fn on_signaling_change(&self, new_state: SignalingState) {
|
||||
trace!("on_signaling_change, {:?}", new_state);
|
||||
let mut handler = self.on_signaling_change_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f(new_state);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_add_stream(&self, stream: UniquePtr<webrtc_sys::media_stream::ffi::MediaStream>) {
|
||||
trace!("on_add_stream");
|
||||
let mut handler = self.on_add_stream_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
// TODO(theomonnom)
|
||||
}
|
||||
}
|
||||
|
||||
fn on_remove_stream(&self, stream: UniquePtr<webrtc_sys::media_stream::ffi::MediaStream>) {
|
||||
trace!("on_remove_stream");
|
||||
let mut handler = self.on_remove_stream_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
// TODO(theomonnom)
|
||||
}
|
||||
}
|
||||
|
||||
fn on_data_channel(&self, data_channel: UniquePtr<webrtc_sys::data_channel::ffi::DataChannel>) {
|
||||
trace!("on_data_channel");
|
||||
let mut handler = self.on_data_channel_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f(DataChannel::new(data_channel));
|
||||
}
|
||||
}
|
||||
|
||||
fn on_renegotiation_needed(&self) {
|
||||
trace!("on_renegotiation_needed");
|
||||
let mut handler = self.on_renegotiation_needed_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f();
|
||||
}
|
||||
}
|
||||
|
||||
fn on_negotiation_needed_event(&self, event: u32) {
|
||||
trace!("on_negotiation_needed_event");
|
||||
let mut handler = self.on_negotiation_needed_event_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f(event);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ice_connection_change(&self, new_state: IceConnectionState) {
|
||||
trace!("on_ice_connection_change (new_state: {:?})", new_state);
|
||||
let mut handler = self.on_ice_connection_change_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f(new_state);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_standardized_ice_connection_change(&self, new_state: IceConnectionState) {
|
||||
trace!(
|
||||
"on_standardized_ice_connection_change (new_state: {:?}",
|
||||
new_state
|
||||
);
|
||||
let mut handler = self
|
||||
.on_standardized_ice_connection_change_handler
|
||||
.lock()
|
||||
.unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f(new_state);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_connection_change(&self, new_state: PeerConnectionState) {
|
||||
trace!("on_connection_change (new_state: {:?})", new_state);
|
||||
let mut handler = self.on_connection_change_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f(new_state);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ice_gathering_change(&self, new_state: IceGatheringState) {
|
||||
trace!("on_ice_gathering_change (new_state: {:?}", new_state);
|
||||
let mut handler = self.on_ice_gathering_change_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f(new_state);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ice_candidate(&self, candidate: UniquePtr<webrtc_sys::jsep::ffi::IceCandidate>) {
|
||||
trace!("on_ice_candidate");
|
||||
let mut handler = self.on_ice_candidate_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f(IceCandidate::new(candidate));
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ice_candidate_error(
|
||||
&self,
|
||||
address: String,
|
||||
port: i32,
|
||||
url: String,
|
||||
error_code: i32,
|
||||
error_text: String,
|
||||
) {
|
||||
trace!("on_ice_candidate_error");
|
||||
let mut handler = self.on_ice_candidate_error_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f(address, port, url, error_code, error_text);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ice_candidates_removed(
|
||||
&self,
|
||||
removed: Vec<UniquePtr<webrtc_sys::candidate::ffi::Candidate>>,
|
||||
) {
|
||||
trace!("on_ice_candidates_removed");
|
||||
let mut handler = self.on_ice_candidates_removed_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
// TODO(theomonnom)
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ice_connection_receiving_change(&self, receiving: bool) {
|
||||
trace!("on_ice_connection_receiving_change");
|
||||
let mut handler = self
|
||||
.on_ice_connection_receiving_change_handler
|
||||
.lock()
|
||||
.unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f(receiving);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ice_selected_candidate_pair_changed(
|
||||
&self,
|
||||
event: webrtc_sys::peer_connection::ffi::CandidatePairChangeEvent,
|
||||
) {
|
||||
trace!("on_ice_selected_candidate_pair_changed");
|
||||
let mut handler = self
|
||||
.on_ice_selected_candidate_pair_changed_handler
|
||||
.lock()
|
||||
.unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f(event);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_add_track(
|
||||
&self,
|
||||
receiver: UniquePtr<webrtc_sys::rtp_receiver::ffi::RtpReceiver>,
|
||||
streams: Vec<UniquePtr<webrtc_sys::media_stream::ffi::MediaStream>>,
|
||||
) {
|
||||
trace!("on_add_track");
|
||||
let mut handler = self.on_add_track_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
let streams = streams.into_iter().map(MediaStream::new).collect();
|
||||
f(RtpReceiver::new(receiver), streams)
|
||||
}
|
||||
}
|
||||
|
||||
fn on_track(&self, transceiver: UniquePtr<webrtc_sys::rtp_transceiver::ffi::RtpTransceiver>) {
|
||||
trace!("on_track");
|
||||
let mut handler = self.on_track_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
// TODO(theomonnom)
|
||||
}
|
||||
}
|
||||
|
||||
fn on_remove_track(&self, receiver: UniquePtr<webrtc_sys::rtp_receiver::ffi::RtpReceiver>) {
|
||||
trace!("on_remove_track");
|
||||
let mut handler = self.on_remove_track_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
// TODO(theomonnom)
|
||||
}
|
||||
}
|
||||
|
||||
fn on_interesting_usage(&self, usage_pattern: i32) {
|
||||
trace!("on_interesting_usage");
|
||||
let mut handler = self.on_interesting_usage_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f(usage_pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use log::trace;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use webrtc_sys::peer_connection::ffi::RTCOfferAnswerOptions;
|
||||
use webrtc_sys::peer_connection_factory::ffi::{ContinualGatheringPolicy, IceTransportsType};
|
||||
|
||||
use crate::data_channel::{DataChannel, DataChannelInit};
|
||||
use crate::jsep::IceCandidate;
|
||||
use crate::peer_connection_factory::{ICEServer, PeerConnectionFactory, RTCConfiguration};
|
||||
use crate::webrtc::RTCRuntime;
|
||||
|
||||
fn init_log() {
|
||||
let _ = env_logger::builder().is_test(true).try_init();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn create_pc() {
|
||||
init_log();
|
||||
|
||||
let rtc_runtime = RTCRuntime::new();
|
||||
|
||||
let factory = PeerConnectionFactory::new(rtc_runtime);
|
||||
let config = RTCConfiguration {
|
||||
ice_servers: vec![ICEServer {
|
||||
urls: vec!["stun:stun1.l.google.com:19302".to_string()],
|
||||
username: "".into(),
|
||||
password: "".into(),
|
||||
}],
|
||||
continual_gathering_policy: ContinualGatheringPolicy::GatherOnce,
|
||||
ice_transport_type: IceTransportsType::All,
|
||||
};
|
||||
|
||||
let mut bob = factory.create_peer_connection(config.clone()).unwrap();
|
||||
let mut alice = factory.create_peer_connection(config.clone()).unwrap();
|
||||
|
||||
let (bob_ice_tx, mut bob_ice_rx) = mpsc::channel::<IceCandidate>(16);
|
||||
let (alice_ice_tx, mut alice_ice_rx) = mpsc::channel::<IceCandidate>(16);
|
||||
let (alice_dc_tx, mut alice_dc_rx) = mpsc::channel::<DataChannel>(16);
|
||||
|
||||
bob.on_ice_candidate(Box::new(move |candidate| {
|
||||
bob_ice_tx.blocking_send(candidate).unwrap();
|
||||
}));
|
||||
|
||||
alice.on_ice_candidate(Box::new(move |candidate| {
|
||||
alice_ice_tx.blocking_send(candidate).unwrap();
|
||||
}));
|
||||
|
||||
alice.on_data_channel(Box::new(move |dc| {
|
||||
alice_dc_tx.blocking_send(dc).unwrap();
|
||||
}));
|
||||
|
||||
let mut bob_dc = bob
|
||||
.create_data_channel("test_dc", DataChannelInit::default())
|
||||
.unwrap();
|
||||
|
||||
let offer = bob
|
||||
.create_offer(RTCOfferAnswerOptions::default())
|
||||
.await
|
||||
.unwrap();
|
||||
trace!("Bob offer: {:?}", offer);
|
||||
bob.set_local_description(offer.clone()).await.unwrap();
|
||||
alice.set_remote_description(offer).await.unwrap();
|
||||
|
||||
let answer = alice
|
||||
.create_answer(RTCOfferAnswerOptions::default())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
trace!("Alice answer: {:?}", answer);
|
||||
alice.set_local_description(answer.clone()).await.unwrap();
|
||||
bob.set_remote_description(answer).await.unwrap();
|
||||
|
||||
let bob_ice = bob_ice_rx.recv().await.unwrap();
|
||||
let alice_ice = alice_ice_rx.recv().await.unwrap();
|
||||
|
||||
bob.add_ice_candidate(alice_ice).await.unwrap();
|
||||
alice.add_ice_candidate(bob_ice).await.unwrap();
|
||||
|
||||
let (data_tx, mut data_rx) = mpsc::channel::<String>(1);
|
||||
let mut alice_dc = alice_dc_rx.recv().await.unwrap();
|
||||
alice_dc.on_message(Box::new(move |data, _| {
|
||||
data_tx
|
||||
.blocking_send(String::from_utf8_lossy(data).to_string())
|
||||
.unwrap();
|
||||
}));
|
||||
|
||||
bob_dc.send(b"This is a test", true).unwrap();
|
||||
assert_eq!(data_rx.recv().await.unwrap(), "This is a test");
|
||||
|
||||
alice.close();
|
||||
bob.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
use cxx::UniquePtr;
|
||||
|
||||
pub use sys_factory::ffi::{
|
||||
ContinualGatheringPolicy, ICEServer, IceTransportsType, RTCConfiguration,
|
||||
};
|
||||
use webrtc_sys::peer_connection as sys_pc;
|
||||
use webrtc_sys::peer_connection_factory as sys_factory;
|
||||
|
||||
use crate::peer_connection::{InternalObserver, PeerConnection};
|
||||
use crate::rtc_error::RTCError;
|
||||
use crate::webrtc::RTCRuntime;
|
||||
|
||||
pub struct PeerConnectionFactory {
|
||||
cxx_handle: UniquePtr<sys_factory::ffi::PeerConnectionFactory>,
|
||||
rtc_runtime: RTCRuntime,
|
||||
}
|
||||
|
||||
impl PeerConnectionFactory {
|
||||
pub fn new(rtc_runtime: RTCRuntime) -> Self {
|
||||
Self {
|
||||
cxx_handle: sys_factory::ffi::create_peer_connection_factory(
|
||||
rtc_runtime.clone().release(),
|
||||
),
|
||||
rtc_runtime,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_peer_connection(
|
||||
&self,
|
||||
config: RTCConfiguration,
|
||||
) -> Result<PeerConnection, RTCError> {
|
||||
let native_config = sys_factory::ffi::create_rtc_configuration(config);
|
||||
|
||||
unsafe {
|
||||
let mut observer = Box::new(InternalObserver::default());
|
||||
let mut native_observer = sys_pc::ffi::create_native_peer_connection_observer(
|
||||
self.rtc_runtime.clone().release(),
|
||||
Box::new(sys_pc::PeerConnectionObserverWrapper::new(&mut *observer)),
|
||||
);
|
||||
|
||||
let res = self
|
||||
.cxx_handle
|
||||
.create_peer_connection(native_config, native_observer.pin_mut());
|
||||
|
||||
match res {
|
||||
Ok(cxx_handle) => Ok(PeerConnection::new(cxx_handle, observer, native_observer)),
|
||||
Err(e) => Err(RTCError::from(e.what())),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// TODO(theomonnom) Wrap the RTCError ffi so we can use Option(u16)
|
||||
pub use webrtc_sys::rtc_error::ffi::RTCError;
|
||||
@@ -0,0 +1,26 @@
|
||||
use crate::media_stream::{MediaStreamTrackHandle, MediaStreamTrackTrait};
|
||||
use cxx::UniquePtr;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use webrtc_sys::rtp_receiver as sys_rec;
|
||||
|
||||
pub struct RtpReceiver {
|
||||
cxx_handle: UniquePtr<sys_rec::ffi::RtpReceiver>,
|
||||
}
|
||||
|
||||
impl Debug for RtpReceiver {
|
||||
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
|
||||
f.debug_struct("RtpReceiver")
|
||||
.field("track", &self.track())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl RtpReceiver {
|
||||
pub(crate) fn new(cxx_handle: UniquePtr<sys_rec::ffi::RtpReceiver>) -> Self {
|
||||
Self { cxx_handle }
|
||||
}
|
||||
|
||||
pub fn track(&self) -> MediaStreamTrackHandle {
|
||||
MediaStreamTrackHandle::new(self.cxx_handle.track())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
#[derive(Debug)]
|
||||
pub struct RtpTransceiver {}
|
||||
@@ -0,0 +1,60 @@
|
||||
use cxx::UniquePtr;
|
||||
use webrtc_sys::video_frame as vf_sys;
|
||||
|
||||
pub use vf_sys::ffi::VideoRotation;
|
||||
|
||||
use crate::video_frame_buffer::VideoFrameBuffer;
|
||||
|
||||
pub struct VideoFrame {
|
||||
cxx_handle: UniquePtr<vf_sys::ffi::VideoFrame>,
|
||||
}
|
||||
|
||||
impl VideoFrame {
|
||||
pub(crate) fn new(cxx_handle: UniquePtr<vf_sys::ffi::VideoFrame>) -> Self {
|
||||
Self { cxx_handle }
|
||||
}
|
||||
|
||||
pub fn width(&self) -> i32 {
|
||||
self.cxx_handle.width()
|
||||
}
|
||||
|
||||
pub fn height(&self) -> i32 {
|
||||
self.cxx_handle.height()
|
||||
}
|
||||
|
||||
pub fn size(&self) -> u32 {
|
||||
self.cxx_handle.size()
|
||||
}
|
||||
|
||||
pub fn id(&self) -> u16 {
|
||||
self.cxx_handle.id()
|
||||
}
|
||||
|
||||
pub fn timestamp_us(&self) -> i64 {
|
||||
self.cxx_handle.timestamp_us()
|
||||
}
|
||||
|
||||
pub fn ntp_time_ms(&self) -> i64 {
|
||||
self.cxx_handle.ntp_time_ms()
|
||||
}
|
||||
|
||||
pub fn transport_frame_id(&self) -> u32 {
|
||||
self.cxx_handle.transport_frame_id()
|
||||
}
|
||||
|
||||
pub fn timestamp(&self) -> u32 {
|
||||
self.cxx_handle.timestamp()
|
||||
}
|
||||
|
||||
pub fn rotation(&self) -> VideoRotation {
|
||||
self.cxx_handle.rotation()
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// Must be called only once, this function create the safe Rust
|
||||
/// wrapper around a VideoFrameBuffer.
|
||||
/// Only one wrapper musts exist at a time.
|
||||
pub(crate) unsafe fn video_frame_buffer(&self) -> VideoFrameBuffer {
|
||||
VideoFrameBuffer::new(self.cxx_handle.video_frame_buffer())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
use cxx::UniquePtr;
|
||||
use livekit_utils::enum_dispatch;
|
||||
use std::pin::Pin;
|
||||
use std::slice;
|
||||
use vfb_sys::ffi::VideoFrameBufferType;
|
||||
use webrtc_sys::video_frame_buffer as vfb_sys;
|
||||
|
||||
pub trait VideoFrameBufferTrait {
|
||||
fn width(&self) -> i32;
|
||||
fn height(&self) -> i32;
|
||||
fn to_i420(self) -> I420Buffer;
|
||||
}
|
||||
|
||||
pub trait PlanarYuvBuffer: VideoFrameBufferTrait {
|
||||
fn chroma_width(&self) -> i32;
|
||||
fn chroma_height(&self) -> i32;
|
||||
fn stride_y(&self) -> i32;
|
||||
fn stride_u(&self) -> i32;
|
||||
fn stride_v(&self) -> i32;
|
||||
}
|
||||
|
||||
pub trait PlanarYuv8Buffer: PlanarYuvBuffer {
|
||||
fn data_y(&self) -> &[u8];
|
||||
fn data_u(&self) -> &[u8];
|
||||
fn data_v(&self) -> &[u8];
|
||||
}
|
||||
|
||||
pub enum VideoFrameBuffer {
|
||||
Native(NativeBuffer),
|
||||
I420(I420Buffer),
|
||||
I420A(I420ABuffer),
|
||||
I422(I422Buffer),
|
||||
I444(I444Buffer),
|
||||
I010(I010Buffer),
|
||||
NV12(NV12Buffer),
|
||||
}
|
||||
|
||||
impl VideoFrameBuffer {
|
||||
pub(crate) fn new(mut cxx_handle: UniquePtr<vfb_sys::ffi::VideoFrameBuffer>) -> Self {
|
||||
unsafe {
|
||||
match cxx_handle.buffer_type() {
|
||||
VideoFrameBufferType::Native => Self::Native(NativeBuffer::new(cxx_handle)),
|
||||
VideoFrameBufferType::I420 => {
|
||||
Self::I420(I420Buffer::new(cxx_handle.pin_mut().get_i420()))
|
||||
}
|
||||
VideoFrameBufferType::I420A => Self::I420A(I420ABuffer::new(cxx_handle)),
|
||||
VideoFrameBufferType::I422 => Self::I422(I422Buffer::new(cxx_handle)),
|
||||
VideoFrameBufferType::I444 => Self::I444(I444Buffer::new(cxx_handle)),
|
||||
VideoFrameBufferType::I010 => Self::I010(I010Buffer::new(cxx_handle)),
|
||||
VideoFrameBufferType::NV12 => Self::NV12(NV12Buffer::new(cxx_handle)),
|
||||
_ => unreachable!(), // VideoFrameBufferType is represented as i32
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VideoFrameBufferTrait for VideoFrameBuffer {
|
||||
enum_dispatch!(
|
||||
[Native, I420, I420A, I422, I444, I010, NV12]
|
||||
fnc!(width, &Self, [], i32);
|
||||
fnc!(height, &Self, [], i32);
|
||||
fnc!(to_i420, Self, [], I420Buffer);
|
||||
);
|
||||
}
|
||||
|
||||
macro_rules! recursive_cast {
|
||||
($ptr:expr $(, $fnc:ident)*) => {
|
||||
{
|
||||
let ptr = $ptr;
|
||||
$(
|
||||
let ptr = unsafe { vfb_sys::ffi::$fnc(ptr) };
|
||||
)*
|
||||
ptr
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_video_frame_buffer {
|
||||
($x:ty $(, $cast:ident)*) => {
|
||||
|
||||
// Allow unused_unsafe when we don't do any cast ( e.g. NativeBuffer )
|
||||
#[allow(unused_unsafe)]
|
||||
impl VideoFrameBufferTrait for $x {
|
||||
fn width(&self) -> i32 {
|
||||
let ptr = recursive_cast!(&*self.cxx_handle $(, $cast)*);
|
||||
unsafe {
|
||||
(*ptr).width()
|
||||
}
|
||||
}
|
||||
|
||||
fn height(&self) -> i32 {
|
||||
let ptr = recursive_cast!(&*self.cxx_handle $(, $cast)*);
|
||||
unsafe {
|
||||
(*ptr).height()
|
||||
}
|
||||
}
|
||||
|
||||
// Require ownership because libwebrtc uses the same pointers
|
||||
fn to_i420(self) -> I420Buffer {
|
||||
let ptr = recursive_cast!(&*self.cxx_handle $(, $cast)*)
|
||||
as *const vfb_sys::ffi::VideoFrameBuffer
|
||||
as *mut vfb_sys::ffi::VideoFrameBuffer;
|
||||
|
||||
unsafe {
|
||||
I420Buffer::new(Pin::new_unchecked(&mut *ptr).to_i420())
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_yuv_buffer {
|
||||
($x:ty $(, $cast:ident)*) => {
|
||||
impl PlanarYuvBuffer for $x {
|
||||
fn chroma_width(&self) -> i32 {
|
||||
let ptr = recursive_cast!(&*self.cxx_handle $(, $cast)*);
|
||||
unsafe {
|
||||
(*ptr).chroma_width()
|
||||
}
|
||||
}
|
||||
|
||||
fn chroma_height(&self) -> i32 {
|
||||
let ptr = recursive_cast!(&*self.cxx_handle $(, $cast)*);
|
||||
unsafe {
|
||||
(*ptr).chroma_height()
|
||||
}
|
||||
}
|
||||
|
||||
fn stride_y(&self) -> i32 {
|
||||
let ptr = recursive_cast!(&*self.cxx_handle $(, $cast)*);
|
||||
unsafe {
|
||||
(*ptr).stride_y()
|
||||
}
|
||||
}
|
||||
|
||||
fn stride_u(&self) -> i32 {
|
||||
let ptr = recursive_cast!(&*self.cxx_handle $(, $cast)*);
|
||||
unsafe {
|
||||
(*ptr).stride_u()
|
||||
}
|
||||
}
|
||||
|
||||
fn stride_v(&self) -> i32 {
|
||||
let ptr = recursive_cast!(&*self.cxx_handle $(, $cast)*);
|
||||
unsafe {
|
||||
(*ptr).stride_v()
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_yuv8_buffer {
|
||||
($x:ty $(, $cast:ident)*) => {
|
||||
impl PlanarYuv8Buffer for $x {
|
||||
fn data_y(&self) -> &[u8] {
|
||||
let ptr = recursive_cast!(&*self.cxx_handle $(, $cast)*);
|
||||
unsafe {
|
||||
slice::from_raw_parts((*ptr).data_y(), (self.width() * self.height()) as usize)
|
||||
}
|
||||
}
|
||||
|
||||
fn data_u(&self) -> &[u8] {
|
||||
let ptr = recursive_cast!(&*self.cxx_handle $(, $cast)*);
|
||||
unsafe {
|
||||
let chroma_height = (self.height() + 1) / 2;
|
||||
slice::from_raw_parts((*ptr).data_u(), (self.stride_u() * chroma_height) as usize)
|
||||
}
|
||||
}
|
||||
|
||||
fn data_v(&self) -> &[u8] {
|
||||
let ptr = recursive_cast!(&*self.cxx_handle $(, $cast)*);
|
||||
unsafe {
|
||||
let chroma_height = (self.height() + 1) / 2;
|
||||
slice::from_raw_parts((*ptr).data_v(), (self.stride_v() * chroma_height) as usize)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub struct NativeBuffer {
|
||||
cxx_handle: UniquePtr<vfb_sys::ffi::VideoFrameBuffer>,
|
||||
}
|
||||
|
||||
pub struct I420Buffer {
|
||||
cxx_handle: UniquePtr<vfb_sys::ffi::I420Buffer>,
|
||||
}
|
||||
|
||||
pub struct I420ABuffer {
|
||||
cxx_handle: UniquePtr<vfb_sys::ffi::VideoFrameBuffer>,
|
||||
}
|
||||
|
||||
pub struct I422Buffer {
|
||||
cxx_handle: UniquePtr<vfb_sys::ffi::VideoFrameBuffer>,
|
||||
}
|
||||
|
||||
pub struct I444Buffer {
|
||||
cxx_handle: UniquePtr<vfb_sys::ffi::VideoFrameBuffer>,
|
||||
}
|
||||
|
||||
pub struct I010Buffer {
|
||||
cxx_handle: UniquePtr<vfb_sys::ffi::VideoFrameBuffer>,
|
||||
}
|
||||
|
||||
pub struct NV12Buffer {
|
||||
cxx_handle: UniquePtr<vfb_sys::ffi::VideoFrameBuffer>,
|
||||
}
|
||||
|
||||
impl_video_frame_buffer!(NativeBuffer);
|
||||
impl_video_frame_buffer!(I420Buffer, i420_to_yuv8, yuv8_to_yuv, yuv_to_vfb);
|
||||
impl_video_frame_buffer!(I420ABuffer);
|
||||
impl_video_frame_buffer!(I422Buffer);
|
||||
impl_video_frame_buffer!(I444Buffer);
|
||||
impl_video_frame_buffer!(I010Buffer);
|
||||
impl_video_frame_buffer!(NV12Buffer);
|
||||
|
||||
impl_yuv_buffer!(I420Buffer, i420_to_yuv8, yuv8_to_yuv);
|
||||
|
||||
impl_yuv8_buffer!(I420Buffer, i420_to_yuv8);
|
||||
|
||||
impl NativeBuffer {
|
||||
fn new(cxx_handle: UniquePtr<vfb_sys::ffi::VideoFrameBuffer>) -> Self {
|
||||
Self { cxx_handle }
|
||||
}
|
||||
}
|
||||
|
||||
impl I420Buffer {
|
||||
fn new(cxx_handle: UniquePtr<vfb_sys::ffi::I420Buffer>) -> Self {
|
||||
Self { cxx_handle }
|
||||
}
|
||||
}
|
||||
|
||||
impl I420ABuffer {
|
||||
fn new(cxx_handle: UniquePtr<vfb_sys::ffi::VideoFrameBuffer>) -> Self {
|
||||
Self { cxx_handle }
|
||||
}
|
||||
}
|
||||
|
||||
impl I422Buffer {
|
||||
fn new(cxx_handle: UniquePtr<vfb_sys::ffi::VideoFrameBuffer>) -> Self {
|
||||
Self { cxx_handle }
|
||||
}
|
||||
}
|
||||
|
||||
impl I444Buffer {
|
||||
fn new(cxx_handle: UniquePtr<vfb_sys::ffi::VideoFrameBuffer>) -> Self {
|
||||
Self { cxx_handle }
|
||||
}
|
||||
}
|
||||
|
||||
impl I010Buffer {
|
||||
fn new(cxx_handle: UniquePtr<vfb_sys::ffi::VideoFrameBuffer>) -> Self {
|
||||
Self { cxx_handle }
|
||||
}
|
||||
}
|
||||
|
||||
impl NV12Buffer {
|
||||
fn new(cxx_handle: UniquePtr<vfb_sys::ffi::VideoFrameBuffer>) -> Self {
|
||||
Self { cxx_handle }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
use cxx::SharedPtr;
|
||||
|
||||
use webrtc_sys::webrtc as sys_rtc;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RTCRuntime {
|
||||
cxx_handle: SharedPtr<sys_rtc::ffi::RTCRuntime>,
|
||||
}
|
||||
|
||||
impl RTCRuntime {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
cxx_handle: sys_rtc::ffi::create_rtc_runtime(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn release(self) -> SharedPtr<sys_rtc::ffi::RTCRuntime> {
|
||||
self.cxx_handle
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
use std::convert::TryInto;
|
||||
|
||||
use webrtc_sys::yuv_helper as yuv_sys;
|
||||
|
||||
pub fn i420_to_abgr(
|
||||
src_y: &[u8],
|
||||
src_stride_y: i32,
|
||||
src_u: &[u8],
|
||||
src_stride_u: i32,
|
||||
src_v: &[u8],
|
||||
src_stride_v: i32,
|
||||
dst_abgr: &mut [u8],
|
||||
dst_stride_abgr: i32,
|
||||
width: i32,
|
||||
height: i32,
|
||||
) {
|
||||
// Assert minimum capacity for safety
|
||||
let chroma_height = (height + 1) / 2; // the buffer should be padded?
|
||||
let min_y: usize = (src_stride_y * height).try_into().unwrap();
|
||||
let min_u: usize = (src_stride_u * chroma_height).try_into().unwrap();
|
||||
let min_v: usize = (src_stride_v * chroma_height).try_into().unwrap();
|
||||
let min_abgr: usize = (dst_stride_abgr * height).try_into().unwrap();
|
||||
|
||||
assert!(src_y.len() >= min_y);
|
||||
assert!(src_u.len() >= min_u);
|
||||
assert!(src_v.len() >= min_v);
|
||||
assert!(dst_abgr.len() >= min_abgr);
|
||||
|
||||
unsafe {
|
||||
yuv_sys::ffi::i420_to_abgr(
|
||||
src_y.as_ptr(),
|
||||
src_stride_y,
|
||||
src_u.as_ptr(),
|
||||
src_stride_u,
|
||||
src_v.as_ptr(),
|
||||
src_stride_v,
|
||||
dst_abgr.as_mut_ptr(),
|
||||
dst_stride_abgr,
|
||||
width,
|
||||
height,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user