├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── examples ├── client │ ├── examples.html │ ├── package.json │ └── server.js └── simple.rs ├── rustfmt.toml └── src ├── audio_frame.rs ├── audio_track.rs ├── auto_ptr.rs ├── create_description_observer.rs ├── cstr.rs ├── lib.rs ├── media_stream.rs ├── media_stream_track.rs ├── observer.rs ├── promisify.rs ├── rtc_datachannel.rs ├── rtc_icecandidate.rs ├── rtc_peerconnection.rs ├── rtc_peerconnection_configure.rs ├── rtc_session_description.rs ├── set_description_observer.rs ├── sink.rs ├── video_frame.rs └── video_track.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .vs 3 | .vscode 4 | *dist* 5 | *node_modules* 6 | */third_party 7 | build/ 8 | .DS_Store 9 | */doc/* 10 | .idea/ 11 | pkg/ 12 | out*/ 13 | 14 | 15 | # Added by cargo 16 | 17 | /target 18 | *.env 19 | Cargo.lock 20 | package-lock.json 21 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "librtc" 3 | version = "0.2.1" 4 | edition = "2021" 5 | authors = [ "Mr.Panda " ] 6 | description = "Facilitating high-level interactions between Rust and WebRTC" 7 | readme = "./README.md" 8 | homepage = "https://github.com/mycrl/librtc-rs" 9 | repository = "https://github.com/mycrl/librtc-rs" 10 | license = "MIT" 11 | keywords = [ 12 | "webrtc", 13 | "ffi" 14 | ] 15 | 16 | categories = [ 17 | "multimedia", 18 | "api-bindings", 19 | "asynchronous" 20 | ] 21 | 22 | [dependencies] 23 | futures = "0.3" 24 | serde = { version = "1.0", features = ["derive"] } 25 | 26 | [build-dependencies] 27 | dotenv = "0.15.0" 28 | anyhow = "1.0.52" 29 | 30 | [[example]] 31 | name = "simple" 32 | 33 | [dev-dependencies] 34 | tokio = { version = "1.20.0", features = ["full"] } 35 | tokio-tungstenite = "*" 36 | futures-util = "0.3" 37 | serde_json = "1.0.61" 38 | clap = { version = "4.0.27", features = ["derive"] } 39 | minifb = "0.25" 40 | libyuv = "0.1.2" 41 | cpal = "0.15.2" 42 | anyhow = "1.0.52" 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Mr.Panda 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | libRTC-rs 3 |

4 |
5 | Rust ❤️ WebRTC 6 |
7 | Facilitating high-level interactions between Rust and WebRTC 8 |
9 |
10 |
11 | 12 | 13 | 14 | 15 |
16 |
17 |
18 | 19 | The rust high-level abstraction binding of Google WebRTC M110. With WebRTC, you can add real-time communication capabilities to your application that works on top of an open standard. It supports video, voice, and generic data to be sent between peers, allowing developers to build powerful voice- and video-communication solutions. 20 | 21 | --- 22 | 23 | 24 | ### License 25 | [MIT](./LICENSE) Copyright (c) 2022 Mr.Panda. 26 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::{env, fs, path::Path, process::Command}; 2 | 3 | use anyhow::{anyhow, Result}; 4 | use dotenv::dotenv; 5 | 6 | fn join(root: &str, next: &str) -> String { 7 | Path::new(root).join(next).to_str().unwrap().to_string() 8 | } 9 | 10 | fn is_exsit(dir: &str) -> bool { 11 | fs::metadata(dir).is_ok() 12 | } 13 | 14 | #[cfg(target_os = "windows")] 15 | fn exec(cmd: &str, work_dir: &str) -> Result { 16 | let output = Command::new("powershell") 17 | .args(["-command", cmd]) 18 | .current_dir(work_dir) 19 | .output()?; 20 | if !output.status.success() { 21 | return Err(anyhow!("{}", String::from_utf8(output.stderr)?)); 22 | } 23 | 24 | Ok(String::from_utf8(output.stdout)?) 25 | } 26 | 27 | #[cfg(not(target_os = "windows"))] 28 | fn exec(command: &str, work_dir: &str) -> std::io::Result<()> { 29 | let output = Command::new("bash") 30 | .arg("-c") 31 | .arg(command) 32 | .current_dir(work_dir) 33 | .output()?; 34 | 35 | if !output.status.success() { 36 | return Err(anyhow!("{}", String::from_utf8(output.stderr)?)); 37 | } 38 | 39 | Ok(String::from_utf8(output.stdout)?) 40 | } 41 | 42 | fn lib_name(module: &str, is_path: bool) -> String { 43 | if is_path { 44 | format!( 45 | "{}{}-{}-{}-release.{}", 46 | if cfg!(target_os = "windows") { 47 | "" 48 | } else { 49 | "lib" 50 | }, 51 | module, 52 | env::var("CARGO_CFG_TARGET_OS").unwrap(), 53 | env::var("CARGO_CFG_TARGET_ARCH").unwrap(), 54 | if cfg!(target_os = "windows") { 55 | "lib" 56 | } else { 57 | "a" 58 | }, 59 | ) 60 | } else { 61 | format!( 62 | "{}-{}-{}-release", 63 | module, 64 | env::var("CARGO_CFG_TARGET_OS").unwrap(), 65 | env::var("CARGO_CFG_TARGET_ARCH").unwrap(), 66 | ) 67 | } 68 | } 69 | 70 | fn main() { 71 | let _ = dotenv(); 72 | println!("cargo:rerun-if-changed=./src"); 73 | println!("cargo:rerun-if-changed=./build.rs"); 74 | 75 | let version = "v0.1.x"; 76 | let output_dir = env::var("OUT_DIR").unwrap(); 77 | let temp = env::var("TEMP").unwrap(); 78 | 79 | for lib_name in [lib_name("rtc", true), lib_name("webrtc", true)] { 80 | if !is_exsit(&join(&output_dir, &lib_name)) { 81 | exec(&format!( 82 | "Invoke-WebRequest -Uri https://github.com/mycrl/librtc/releases/download/{}/{} -OutFile {}", 83 | version, 84 | &lib_name, 85 | &lib_name, 86 | ), &output_dir).unwrap(); 87 | } 88 | } 89 | 90 | // In addition to windows, other platforms use package management 91 | // to install ffmpeg. 92 | #[allow(unused)] 93 | let mut ffmpeg_prefix = "".to_string(); 94 | 95 | #[cfg(target_os = "macos")] 96 | { 97 | ffmpeg_prefix = exec("brew --prefix ffmpeg", &output_dir)?; 98 | } 99 | 100 | #[cfg(target_os = "windows")] 101 | { 102 | ffmpeg_prefix = join(&output_dir, "./ffmpeg-5.1.2-full_build-shared"); 103 | if !is_exsit(&ffmpeg_prefix) { 104 | exec( 105 | "Invoke-WebRequest \ 106 | -Uri https://www.gyan.dev/ffmpeg/builds/packages/ffmpeg-5.1.2-full_build-shared.7z \ 107 | -OutFile ffmpeg.7z; \ 108 | 7z x ffmpeg.7z -aoa; \ 109 | del ffmpeg.7z", 110 | &output_dir, 111 | ) 112 | .unwrap(); 113 | } 114 | } 115 | 116 | #[cfg(target_os = "linux")] 117 | { 118 | ffmpeg_prefix = env::var("FFMPEG_PREFIX") 119 | .expect("On linux, you need to manually specify the prefix of ffmpeg!"); 120 | } 121 | 122 | println!("cargo:rustc-link-search=all={}", &output_dir); 123 | println!( 124 | "cargo:rustc-link-search=all={}", 125 | join(&ffmpeg_prefix, "./lib") 126 | ); 127 | 128 | println!("cargo:rustc-link-lib=avcodec"); 129 | println!("cargo:rustc-link-lib=avutil"); 130 | println!("cargo:rustc-link-lib=static={}", lib_name("webrtc", false)); 131 | println!("cargo:rustc-link-lib=static={}", lib_name("rtc", false)); 132 | 133 | #[cfg(target_os = "windows")] 134 | { 135 | println!("cargo:rustc-link-lib=winmm"); 136 | println!("cargo:rustc-link-lib=secur32"); 137 | println!("cargo:rustc-link-lib=msdmo"); 138 | println!("cargo:rustc-link-lib=dmoguids"); 139 | println!("cargo:rustc-link-lib=wmcodecdspuuid"); 140 | println!("cargo:rustc-link-lib=iphlpapi"); 141 | } 142 | 143 | #[cfg(target_os = "macos")] 144 | { 145 | println!("cargo:rustc-link-lib=c++"); 146 | println!("cargo:rustc-link-lib=framework=Foundation"); 147 | println!("cargo:rustc-link-lib=framework=AudioToolbox"); 148 | println!("cargo:rustc-link-lib=framework=AudioUnit"); 149 | println!("cargo:rustc-link-lib=framework=CoreServices"); 150 | println!("cargo:rustc-link-lib=framework=CoreFoundation"); 151 | println!("cargo:rustc-link-lib=framework=CoreAudio"); 152 | println!("cargo:rustc-link-lib=framework=CoreGraphics"); 153 | } 154 | 155 | #[cfg(target_os = "linux")] 156 | { 157 | println!("cargo:rustc-link-lib=stdc++"); 158 | } 159 | 160 | #[cfg(target_os = "windows")] 161 | { 162 | let temp_dir = join(&temp, &format!("librtc-{}", &version)); 163 | if !is_exsit(&temp_dir) { 164 | fs::create_dir(&temp_dir).unwrap(); 165 | exec( 166 | &format!("cp -r {}/bin/*.dll {}", &ffmpeg_prefix, &temp_dir), 167 | &output_dir, 168 | ) 169 | .unwrap(); 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /examples/client/examples.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | WebRTC Demo 4 | 5 | 6 | 14 | 15 | 146 | 147 | -------------------------------------------------------------------------------- /examples/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "private": true, 4 | "dependencies": { 5 | "express": "^4.18.2", 6 | "ws": "^8.11.0" 7 | }, 8 | "scripts": { 9 | "serve": "node ./server.js" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/client/server.js: -------------------------------------------------------------------------------- 1 | const { WebSocketServer } = require('ws'); 2 | const { createServer } = require('http'); 3 | const express = require('express'); 4 | const url = require('url'); 5 | 6 | const app = express(); 7 | const server = createServer(app); 8 | 9 | // This is used here to serve a sample webpage via an http server. 10 | app.get('/', (_, res) => { 11 | res.sendFile(__dirname + '/examples.html'); 12 | }); 13 | 14 | /** 15 | * The websocket connection pool stores all websocket sessions, 16 | * the key is the remote port number, and the value is the 17 | * `WebSocket` class. 18 | */ 19 | let sockets = {}; 20 | 21 | new WebSocketServer({ server }) 22 | .on('connection', (socket, req) => { 23 | /** 24 | * Triggered when a new websocket is connected to the 25 | * server, the remote port number of the session is 26 | * obtained here and stored in the connection pool. 27 | */ 28 | const id = String(req.socket.remotePort); 29 | console.log('connected for : ' + id); 30 | sockets[id] = socket; 31 | 32 | /** 33 | * Process all messages of the current connection, 34 | * and broadcast the received messages to all 35 | * connections except itself. 36 | */ 37 | socket.on('message', payload => { 38 | Object.keys(sockets) 39 | .filter(key => key != id) 40 | .forEach(key => sockets[key].send(payload.toString())); 41 | }); 42 | 43 | /** 44 | * Removes the current session from the connection 45 | * pool when the current connection is disconnected. 46 | */ 47 | socket.on('close', () => { 48 | delete sockets[id]; 49 | }); 50 | }); 51 | 52 | // Start the http server and listen on port 80. 53 | server.listen(80, () => { 54 | console.log('\r\n\r\n'); 55 | console.log('signaling server starting...'); 56 | console.log('web page: http://localhost'); 57 | console.log('signaling: ws://localhost'); 58 | console.log('\r\n\r\n'); 59 | }); -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | use clap::*; 2 | use minifb::{Window, WindowOptions}; 3 | use serde::{Deserialize, Serialize}; 4 | use std::{ 5 | mem::ManuallyDrop, 6 | ptr::null_mut, 7 | sync::{ 8 | atomic::{AtomicBool, AtomicPtr, Ordering}, 9 | Arc, 10 | }, 11 | time::Duration, 12 | }; 13 | 14 | use futures_util::{stream::*, SinkExt, StreamExt}; 15 | use librtc::*; 16 | use tokio::{net::TcpStream, runtime::Handle, sync::Mutex}; 17 | use tokio_tungstenite::{ 18 | connect_async, tungstenite::protocol::Message, MaybeTlsStream, WebSocketStream, 19 | }; 20 | 21 | #[derive(Debug, Serialize, Deserialize)] 22 | #[serde(tag = "kind", content = "payload", rename_all = "lowercase")] 23 | enum Payload { 24 | Offer(RTCSessionDescription), 25 | Answer(RTCSessionDescription), 26 | Candidate(RTCIceCandidate), 27 | } 28 | 29 | // Remote video track player window. 30 | struct VideoPlayer { 31 | ready: AtomicBool, 32 | label: String, 33 | buf: Arc>>, 34 | } 35 | 36 | impl VideoPlayer { 37 | fn new(label: String) -> Self { 38 | Self { 39 | buf: Arc::new(AtomicPtr::new(null_mut())), 40 | ready: AtomicBool::new(false), 41 | label, 42 | } 43 | } 44 | 45 | // Process video frames decoded from video track. 46 | fn on_frame(&self, frame: &VideoFrame) { 47 | let width = frame.width() as usize; 48 | let height = frame.height() as usize; 49 | 50 | // Check whether the window has been created. If not, create the 51 | // window first. The reason for this is that it must be created 52 | // according to the size of the video frame 53 | if self.ready.load(Ordering::Relaxed) == false { 54 | self.ready.store(true, Ordering::Relaxed); 55 | 56 | let buf = self.buf.clone(); 57 | let label = self.label.clone(); 58 | let delay = Duration::from_millis(1000 / 60); 59 | std::thread::spawn(move || { 60 | let mut window = Window::new( 61 | &format!("video::{}", label), 62 | width, 63 | height, 64 | WindowOptions::default(), 65 | )?; 66 | 67 | // Renders the latest frame from the framebuffer at a 68 | // fixed rate of 60 frames. 69 | window.limit_update_rate(Some(delay)); 70 | loop { 71 | std::thread::sleep(delay); 72 | let frame = unsafe { &*buf.load(Ordering::Relaxed) }; 73 | let (_, shorts, _) = unsafe { frame.align_to::() }; 74 | window.update_with_buffer(shorts, width, height)?; 75 | } 76 | 77 | #[allow(unreachable_code)] 78 | Ok::<(), anyhow::Error>(()) 79 | }); 80 | } 81 | 82 | // The frame format of the video track output is fixed to I420, 83 | // but the window only accepts ARGB, so here you need to 84 | // convert I420 to ARGB. 85 | let mut buf = vec![0u8; width * height * 4]; 86 | unsafe { 87 | libyuv::i420_to_argb( 88 | frame.data_y().as_ptr(), 89 | frame.stride_y() as i32, 90 | frame.data_u().as_ptr(), 91 | frame.stride_u() as i32, 92 | frame.data_v().as_ptr(), 93 | frame.stride_v() as i32, 94 | buf.as_mut_ptr(), 95 | (width * 4) as i32, 96 | width as i32, 97 | height as i32, 98 | ); 99 | } 100 | 101 | // Write the converted video frame into the frame buffer and 102 | // release the memory of the previous frame. 103 | let buf_ptr = Box::into_raw(Box::new(buf)); 104 | let ret = self.buf.swap(buf_ptr, Ordering::Relaxed); 105 | if !ret.is_null() { 106 | drop(unsafe { Box::from_raw(ret) }); 107 | } 108 | } 109 | } 110 | 111 | // Implementation of the video track sink. 112 | impl librtc::SinkExt for VideoPlayer { 113 | type Item = Arc; 114 | 115 | // Triggered when a video frame is received. 116 | fn on_data(&self, frame: Arc) { 117 | self.on_frame(frame.as_ref()); 118 | } 119 | } 120 | 121 | struct AudioPlayer { 122 | track: Arc, 123 | } 124 | 125 | impl AudioPlayer { 126 | fn new(track: Arc) -> Self { 127 | Self { track } 128 | } 129 | } 130 | 131 | // Implementation of the audio track sink. 132 | impl librtc::SinkExt for AudioPlayer { 133 | type Item = Arc; 134 | 135 | // Triggered when an audio frame is received. 136 | fn on_data(&self, frame: Arc) { 137 | // Echo the audio frame to the peer's audio track. 138 | self.track.add_frame(frame.as_ref()); 139 | } 140 | } 141 | 142 | // data channel sink implementation. 143 | struct ChannelSinkImpl; 144 | 145 | impl librtc::SinkExt for ChannelSinkImpl { 146 | type Item = Vec; 147 | 148 | // Triggered when the data channel receives data. 149 | fn on_data(&self, data: Vec) { 150 | println!("on channel data: {:?}", data); 151 | } 152 | } 153 | 154 | // peerconnection event handler, handle peerconnection event callback. 155 | struct ObserverImpl { 156 | ws_writer: Arc>, Message>>>, 157 | audio_track: MediaStreamTrack, 158 | handle: Handle, 159 | } 160 | 161 | impl Observer for ObserverImpl { 162 | // peerconnection emits ice candidate event, when this event occurs, the 163 | // generated ice candidate should be sent to the peer. 164 | fn on_ice_candidate(&self, candidate: RTCIceCandidate) { 165 | let writer = self.ws_writer.clone(); 166 | self.handle.spawn(async move { 167 | writer 168 | .lock() 169 | .await 170 | .send(Message::Text( 171 | serde_json::to_string(&Payload::Candidate(candidate)).unwrap(), 172 | )) 173 | .await 174 | .unwrap(); 175 | }); 176 | } 177 | 178 | // This event is triggered when the peer creates a data channel. 179 | fn on_data_channel(&self, channel: RTCDataChannel) { 180 | // Register a data sink for this data channel. 181 | channel.register_sink(0, Sinker::new(ChannelSinkImpl {})); 182 | 183 | // Next, we will continue to use this container to prevent automatic 184 | // release. This is a bad implementation, and it will not be 185 | // implemented in a normal process, but for a simpler implementation 186 | // example, this bad method will be used here. 187 | let _ = ManuallyDrop::new(channel); 188 | } 189 | 190 | // This event is triggered when the peer creates a video track or audio 191 | // track. 192 | fn on_track(&self, mut track: MediaStreamTrack) { 193 | let audio_track = self.audio_track.clone(); 194 | 195 | // Register sinks for audio and video tracks. 196 | match &mut track { 197 | MediaStreamTrack::Video(track) => { 198 | track.register_sink(0, Sinker::new(VideoPlayer::new(track.label().to_string()))); 199 | } 200 | MediaStreamTrack::Audio(track) => { 201 | if let MediaStreamTrack::Audio(at) = audio_track { 202 | track.register_sink(0, Sinker::new(AudioPlayer::new(at.clone()))); 203 | } 204 | } 205 | } 206 | 207 | // Next, we will continue to use this container to prevent automatic 208 | // release. This is a bad implementation, and it will not be 209 | // implemented in a normal process, but for a simpler implementation 210 | // example, this bad method will be used here. 211 | let _ = ManuallyDrop::new(track); 212 | } 213 | } 214 | 215 | // The command line parameters of the sample program, use clap to parse the 216 | // parameters. 217 | #[derive(Parser, Debug, Clone)] 218 | #[command(author, version, about, long_about = None)] 219 | struct Args { 220 | // Signaling server address, default localhost. 221 | #[arg(long, default_value = "ws://localhost")] 222 | signaling: String, 223 | } 224 | 225 | #[tokio::main] 226 | async fn main() -> Result<(), anyhow::Error> { 227 | // Connect to the signaling server. Since signaling messages need to be 228 | // written from multiple places, the writing end is packaged as a 229 | // thread-safe type. 230 | let args = Args::parse(); 231 | let (ws_stream, _) = connect_async(&args.signaling).await?; 232 | let (writer, mut reader) = ws_stream.split(); 233 | let writer = Arc::new(Mutex::new(writer)); 234 | 235 | // Create an audio track and video track as the output of echo. 236 | let stream = MediaStream::new("media_stream")?; 237 | let audio_track = MediaStreamTrack::create_audio_track("audio_track")?; 238 | 239 | // Create a peer connection with default configuration. 240 | let config = RTCConfiguration::default(); 241 | let pc = RTCPeerConnection::new( 242 | &config, 243 | ObserverImpl { 244 | audio_track: audio_track.clone(), 245 | ws_writer: writer.clone(), 246 | handle: Handle::current(), 247 | }, 248 | )?; 249 | 250 | // Add the created audio track and video track to the peer connection. 251 | pc.add_track(audio_track, stream.clone())?; 252 | 253 | // Read messages from the websocket until unreadable or an error occurs. 254 | while let Some(Ok(msg)) = reader.next().await { 255 | // Only websocket messages of type text are accepted, because 256 | // signaling messages will only be of type text. 257 | if let Message::Text(msg) = msg { 258 | let ret = serde_json::from_str::(&msg); 259 | match ret? { 260 | Payload::Offer(offer) => { 261 | // Receive offer message, set it to peerconnection, and 262 | // create answer. 263 | pc.set_remote_description(&offer).await?; 264 | let answer = pc.create_answer().await?; 265 | pc.set_local_description(&answer).await?; 266 | 267 | // Reply the created answer to the peer via websocket. 268 | writer 269 | .lock() 270 | .await 271 | .send(Message::Text(serde_json::to_string(&Payload::Answer( 272 | answer, 273 | ))?)) 274 | .await?; 275 | } 276 | Payload::Candidate(candidate) => { 277 | // Processing the candidate message will be much simpler, 278 | // just submit it to peerconnection. 279 | pc.add_ice_candidate(&candidate)?; 280 | } 281 | _ => {} 282 | } 283 | } 284 | } 285 | 286 | Ok(()) 287 | } 288 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | wrap_comments = true 2 | format_code_in_doc_comments = true 3 | doc_comment_code_block_width = 80 -------------------------------------------------------------------------------- /src/audio_frame.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::{c_int, c_void}, 3 | slice::from_raw_parts, 4 | sync::Arc, 5 | }; 6 | 7 | use crate::media_stream_track::rtc_free_frame; 8 | 9 | #[repr(C)] 10 | #[derive(Debug)] 11 | pub struct RawAudioFrame { 12 | remote: bool, 13 | size: usize, 14 | frames: usize, 15 | channels: usize, 16 | sample_rate: c_int, 17 | timestamp: i64, 18 | buf: *const i16, 19 | } 20 | 21 | /// A list of audio frames in pcm format, usually 10ms long. 22 | #[derive(Debug)] 23 | pub struct AudioFrame { 24 | raw: *const RawAudioFrame, 25 | } 26 | 27 | unsafe impl Send for AudioFrame {} 28 | unsafe impl Sync for AudioFrame {} 29 | 30 | impl AudioFrame { 31 | /// crate AudiFrame from raw type. 32 | pub(crate) fn from_raw(raw: *const RawAudioFrame) -> Arc { 33 | assert!(!raw.is_null()); 34 | Arc::new(Self { raw }) 35 | } 36 | 37 | pub(crate) fn get_raw(&self) -> *const RawAudioFrame { 38 | self.raw 39 | } 40 | 41 | pub fn new( 42 | sample_rate: usize, 43 | channels: u8, 44 | frames: usize, 45 | timestamp: usize, 46 | buf: &[u8], 47 | ) -> Self { 48 | let buf: &[i16] = unsafe { std::mem::transmute(buf) }; 49 | Self { 50 | raw: Box::into_raw(Box::new(RawAudioFrame { 51 | sample_rate: sample_rate as c_int, 52 | channels: channels as usize, 53 | timestamp: timestamp as i64, 54 | buf: buf.as_ptr(), 55 | size: buf.len(), 56 | remote: false, 57 | frames, 58 | })), 59 | } 60 | } 61 | } 62 | 63 | impl AsRef<[i16]> for AudioFrame { 64 | fn as_ref(&self) -> &[i16] { 65 | let raw = unsafe { &*self.raw }; 66 | unsafe { from_raw_parts(raw.buf, raw.size) } 67 | } 68 | } 69 | 70 | impl Drop for AudioFrame { 71 | fn drop(&mut self) { 72 | let raw = unsafe { &*self.raw }; 73 | unsafe { 74 | // If remote is false, it means the distribution is 75 | // on the rust box. 76 | if raw.remote { 77 | rtc_free_frame(self.raw as *const c_void) 78 | } else { 79 | let _ = Box::from_raw(self.raw.cast_mut()); 80 | }; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/audio_track.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | ffi::c_char, 4 | sync::{Arc, RwLock}, 5 | }; 6 | 7 | use crate::{ 8 | audio_frame::RawAudioFrame, 9 | cstr::{c_str_to_str, free_cstring, to_c_str}, 10 | media_stream::MediaStreamError, 11 | media_stream_track::{ 12 | rtc_free_media_stream_track, rtc_remove_media_stream_track_frame_h, RawMediaStreamTrack, 13 | }, 14 | AudioFrame, Sinker, 15 | }; 16 | 17 | #[allow(improper_ctypes)] 18 | extern "C" { 19 | pub(crate) fn rtc_create_audio_track( 20 | label: *const c_char, 21 | ) -> *const crate::media_stream_track::RawMediaStreamTrack; 22 | 23 | pub(crate) fn rtc_add_audio_track_frame( 24 | track: *const crate::media_stream_track::RawMediaStreamTrack, 25 | frame: *const crate::audio_frame::RawAudioFrame, 26 | ); 27 | 28 | pub(crate) fn rtc_set_audio_track_frame_h( 29 | track: *const crate::media_stream_track::RawMediaStreamTrack, 30 | handler: extern "C" fn( 31 | &crate::audio_track::AudioTrack, 32 | *const crate::audio_frame::RawAudioFrame, 33 | ), 34 | ctx: &crate::audio_track::AudioTrack, 35 | ); 36 | } 37 | 38 | /// The AudioTrack interface represents a single audio track from 39 | /// a MediaStreamTrack. 40 | pub struct AudioTrack { 41 | pub(crate) raw: *const RawMediaStreamTrack, 42 | sinks: RwLock>>>, 43 | } 44 | 45 | unsafe impl Send for AudioTrack {} 46 | unsafe impl Sync for AudioTrack {} 47 | 48 | impl AudioTrack { 49 | pub fn label(&self) -> &str { 50 | c_str_to_str(unsafe { (*self.raw).label }).expect("get video track label string to failed") 51 | } 52 | 53 | /// Create a new audio track, may fail to create, such as 54 | /// insufficient memory. 55 | pub fn new(label: &str) -> Result, MediaStreamError> { 56 | let raw = unsafe { 57 | let c_label = to_c_str(label).map_err(|e| MediaStreamError::StringError(e))?; 58 | let ptr = rtc_create_audio_track(c_label); 59 | free_cstring(c_label); 60 | ptr 61 | }; 62 | 63 | if raw.is_null() { 64 | Err(MediaStreamError::CreateTrackFailed) 65 | } else { 66 | Ok(Self::from_raw(raw)) 67 | } 68 | } 69 | 70 | /// Push audio frames to the current track, currently only 71 | /// supports pushing audio frames in pcm format. 72 | /// 73 | /// Only valid for local audio streams. 74 | pub fn add_frame(&self, frame: &AudioFrame) { 75 | unsafe { 76 | rtc_add_audio_track_frame(self.raw, frame.get_raw()); 77 | } 78 | } 79 | 80 | /// Register audio track frame sink, one track can register multiple sinks. 81 | /// The sink id cannot be repeated, otherwise the sink implementation will 82 | /// be overwritten. 83 | pub fn register_sink(&self, id: u8, sink: Sinker>) { 84 | let mut sinks = self.sinks.write().unwrap(); 85 | 86 | // Register for the first time, register the callback function to 87 | // webrtc native, and then do not need to register again. 88 | if sinks.is_empty() { 89 | unsafe { rtc_set_audio_track_frame_h(self.raw, on_audio_frame, self) } 90 | } 91 | 92 | sinks.insert(id, sink); 93 | } 94 | 95 | /// Delete the registered sink, if it exists, it will return the deleted 96 | /// sink. 97 | pub fn remove_sink(&self, id: u8) -> Option>> { 98 | let mut sinks = self.sinks.write().unwrap(); 99 | let value = sinks.remove(&id); 100 | if sinks.is_empty() { 101 | unsafe { rtc_remove_media_stream_track_frame_h(self.raw) } 102 | } 103 | 104 | value 105 | } 106 | 107 | /// create audio track from raw type ptr. 108 | pub(crate) fn from_raw(raw: *const RawMediaStreamTrack) -> Arc { 109 | assert!(!raw.is_null()); 110 | Arc::new(Self { 111 | sinks: RwLock::new(HashMap::new()), 112 | raw, 113 | }) 114 | } 115 | 116 | fn on_data(this: &Self, frame: Arc) { 117 | for sinker in this.sinks.read().unwrap().values() { 118 | sinker.sink.on_data(frame.clone()); 119 | } 120 | } 121 | } 122 | 123 | impl Drop for AudioTrack { 124 | fn drop(&mut self) { 125 | unsafe { rtc_remove_media_stream_track_frame_h(self.raw) } 126 | unsafe { rtc_free_media_stream_track(self.raw) } 127 | } 128 | } 129 | 130 | #[no_mangle] 131 | extern "C" fn on_audio_frame(ctx: &AudioTrack, frame: *const RawAudioFrame) { 132 | assert!(!frame.is_null()); 133 | let frame = AudioFrame::from_raw(frame); 134 | AudioTrack::on_data(ctx, frame); 135 | } 136 | -------------------------------------------------------------------------------- /src/auto_ptr.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::UnsafeCell, mem::ManuallyDrop}; 2 | 3 | /// The type wrapper for interior mutability in rust. 4 | #[derive(Debug)] 5 | pub struct PointerCell { 6 | data: UnsafeCell, 7 | } 8 | 9 | impl PointerCell { 10 | /// Constructs a new instance of UnsafeCell which will wrap the 11 | /// specified value. 12 | pub fn new(value: T) -> Self { 13 | Self { 14 | data: UnsafeCell::new(value), 15 | } 16 | } 17 | 18 | /// safe get mutability ref for inner value. 19 | pub fn get_mut(&self) -> &mut T { 20 | unsafe { &mut *self.data.get() } 21 | } 22 | 23 | /// safe get ref for inner value. 24 | pub fn get(&self) -> &T { 25 | unsafe { &mut *self.data.get() } 26 | } 27 | } 28 | 29 | /// A wrapper type to construct uninitialized instances of T. 30 | /// inner manage auto drop. 31 | pub struct HeapPointer { 32 | data: PointerCell>, 33 | } 34 | 35 | impl Default for HeapPointer { 36 | fn default() -> Self { 37 | Self::new() 38 | } 39 | } 40 | 41 | impl std::fmt::Debug for HeapPointer { 42 | fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 43 | Ok(()) 44 | } 45 | } 46 | 47 | impl HeapPointer { 48 | /// Creates a new MaybeUninit initialized with the given value. 49 | pub fn new() -> Self { 50 | Self { 51 | data: PointerCell::new(None), 52 | } 53 | } 54 | 55 | /// Sets the value of the HeapPointer. 56 | /// 57 | /// This overwrites any previous value without dropping it, 58 | /// so be careful not to use this twice unless you want to skip 59 | /// running the destructor. For your convenience, this also 60 | /// returns a mutable reference to the (now safely initialized) 61 | /// contents of self. 62 | pub fn set(&self, value: T) -> *mut T { 63 | let value = Box::into_raw(Box::new(value)); 64 | let _ = self.data.get_mut().insert(value); 65 | value 66 | } 67 | 68 | /// get inner type T the raw ptr. 69 | #[allow(unused)] 70 | pub fn get(&self) -> &Option<*mut T> { 71 | self.data.get() 72 | } 73 | } 74 | 75 | impl Drop for HeapPointer { 76 | fn drop(&mut self) { 77 | if let Some(ref_value) = self.data.get() { 78 | let _ = unsafe { Box::from_raw(*ref_value) }; 79 | } 80 | } 81 | } 82 | 83 | pub(crate) trait ArrayExt { 84 | fn into_c_layout(self) -> (*mut T, usize, usize); 85 | } 86 | 87 | impl ArrayExt for Vec { 88 | fn into_c_layout(self) -> (*mut T, usize, usize) { 89 | let mut me = ManuallyDrop::new(self); 90 | (me.as_mut_ptr(), me.len(), me.capacity()) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/create_description_observer.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | error::Error, 3 | ffi::{c_char, c_void}, 4 | fmt, 5 | sync::{ 6 | atomic::{AtomicPtr, Ordering}, 7 | Arc, 8 | }, 9 | }; 10 | 11 | use futures::task::AtomicWaker; 12 | 13 | use crate::{ 14 | cstr::{from_c_str, StringError}, 15 | rtc_peerconnection::RawRTCPeerConnection, 16 | rtc_session_description::RawRTCSessionDescription, 17 | Promisify, PromisifyExt, RTCSessionDescription, 18 | }; 19 | 20 | extern "C" { 21 | pub(crate) fn rtc_create_answer( 22 | pc: *const crate::rtc_peerconnection::RawRTCPeerConnection, 23 | cb: extern "C" fn( 24 | *const c_char, 25 | *const crate::rtc_session_description::RawRTCSessionDescription, 26 | *mut c_void, 27 | ), 28 | ctx: *mut c_void, 29 | ); 30 | 31 | pub(crate) fn rtc_create_offer( 32 | pc: *const crate::rtc_peerconnection::RawRTCPeerConnection, 33 | cb: extern "C" fn( 34 | *const c_char, 35 | *const crate::rtc_session_description::RawRTCSessionDescription, 36 | *mut c_void, 37 | ), 38 | ctx: *mut c_void, 39 | ); 40 | } 41 | 42 | #[derive(Debug)] 43 | pub enum CreateDescriptionError { 44 | StringError(StringError), 45 | CreateFailed(String), 46 | } 47 | 48 | impl Error for CreateDescriptionError {} 49 | 50 | impl fmt::Display for CreateDescriptionError { 51 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 52 | write!(f, "{}", self) 53 | } 54 | } 55 | 56 | #[derive(PartialEq, Eq, PartialOrd)] 57 | pub(crate) enum CreateDescriptionKind { 58 | Offer, 59 | Answer, 60 | } 61 | 62 | struct CreateDescriptionContext { 63 | callback: Box)>, 64 | } 65 | 66 | #[no_mangle] 67 | extern "C" fn create_description_callback( 68 | error: *const c_char, 69 | desc: *const RawRTCSessionDescription, 70 | ctx: *mut c_void, 71 | ) { 72 | let mut ctx = unsafe { Box::from_raw(ctx as *mut CreateDescriptionContext) }; 73 | (ctx.callback)( 74 | unsafe { error.as_ref() } 75 | .map(|_| { 76 | from_c_str(error) 77 | .map_err(|e| CreateDescriptionError::StringError(e)) 78 | .and_then(|s| Err(CreateDescriptionError::CreateFailed(s))) 79 | }) 80 | .unwrap_or_else(|| { 81 | RTCSessionDescription::try_from(unsafe { &*desc }) 82 | .map_err(|e| CreateDescriptionError::StringError(e)) 83 | }), 84 | ); 85 | } 86 | 87 | pub struct CreateDescriptionObserver { 88 | kind: CreateDescriptionKind, 89 | pc: *const RawRTCPeerConnection, 90 | ret: Arc>>, 91 | } 92 | 93 | unsafe impl Send for CreateDescriptionObserver {} 94 | unsafe impl Sync for CreateDescriptionObserver {} 95 | 96 | impl PromisifyExt for CreateDescriptionObserver { 97 | type Output = RTCSessionDescription; 98 | type Err = CreateDescriptionError; 99 | 100 | fn handle(&self, waker: Arc) -> Result<(), Self::Err> { 101 | let ret = self.ret.clone(); 102 | let ctx = Box::into_raw(Box::new(CreateDescriptionContext { 103 | callback: Box::new(move |res| { 104 | ret.store(Box::into_raw(Box::new(res)), Ordering::Relaxed); 105 | waker.wake(); 106 | }), 107 | })) as *mut c_void; 108 | 109 | if self.kind == CreateDescriptionKind::Offer { 110 | unsafe { rtc_create_offer(self.pc, create_description_callback, ctx) }; 111 | } else { 112 | unsafe { rtc_create_answer(self.pc, create_description_callback, ctx) }; 113 | } 114 | 115 | Ok(()) 116 | } 117 | 118 | fn wake(&self) -> Option> { 119 | unsafe { 120 | self.ret 121 | .swap(std::ptr::null_mut(), Ordering::Relaxed) 122 | .as_mut() 123 | } 124 | .map(|ptr| unsafe { *Box::from_raw(ptr) }) 125 | } 126 | } 127 | 128 | pub type CreateDescriptionFuture = Promisify; 129 | impl CreateDescriptionFuture { 130 | pub(crate) fn create(pc: *const RawRTCPeerConnection, kind: CreateDescriptionKind) -> Self { 131 | Promisify::new(CreateDescriptionObserver { 132 | ret: Arc::new(AtomicPtr::new(std::ptr::null_mut())), 133 | kind, 134 | pc, 135 | }) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/cstr.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{c_char, CStr, CString}; 2 | 3 | #[derive(Debug)] 4 | pub enum StringError { 5 | NulError, 6 | Utf8Error, 7 | } 8 | 9 | pub(crate) fn to_c_str(str: &str) -> Result<*const c_char, StringError> { 10 | Ok(CString::new(str) 11 | .map_err(|_| StringError::NulError)? 12 | .into_raw()) 13 | } 14 | 15 | pub(crate) fn from_c_str(str: *const c_char) -> Result { 16 | assert!(!str.is_null()); 17 | Ok(unsafe { 18 | CStr::from_ptr(str) 19 | .to_str() 20 | .map_err(|_| StringError::Utf8Error)? 21 | .to_string() 22 | }) 23 | } 24 | 25 | pub(crate) fn c_str_to_str(str: *const c_char) -> Result<&'static str, StringError> { 26 | Ok(unsafe { 27 | CStr::from_ptr(str) 28 | .to_str() 29 | .map_err(|_| StringError::Utf8Error)? 30 | }) 31 | } 32 | 33 | pub(crate) fn free_cstring(str: *const c_char) { 34 | if !str.is_null() { 35 | drop(unsafe { CString::from_raw(str as *mut c_char) }) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! ##### Facilitating high-level interactions between Rust and WebRTC 2 | //! 3 | //! [M99]: https://groups.google.com/g/discuss-webrtc/c/Yf6c3HW4N3k/m/3SC_Hy15BQAJ 4 | //! 5 | //! The rust high-level abstraction binding of Google WebRTC [M99]. 6 | //! With WebRTC, you can add real-time communication capabilities to 7 | //! your application that works on top of an open standard. It supports 8 | //! video, voice, and generic data to be sent between peers, allowing 9 | //! developers to build powerful voice- and video-communication solutions. 10 | 11 | mod audio_frame; 12 | mod audio_track; 13 | mod auto_ptr; 14 | mod create_description_observer; 15 | mod cstr; 16 | mod media_stream; 17 | mod media_stream_track; 18 | mod observer; 19 | mod promisify; 20 | mod rtc_datachannel; 21 | mod rtc_icecandidate; 22 | mod rtc_peerconnection; 23 | mod rtc_peerconnection_configure; 24 | mod rtc_session_description; 25 | mod set_description_observer; 26 | mod sink; 27 | mod video_frame; 28 | mod video_track; 29 | 30 | pub use audio_frame::AudioFrame; 31 | pub use audio_track::AudioTrack; 32 | pub use create_description_observer::{CreateDescriptionError, CreateDescriptionObserver}; 33 | pub use cstr::StringError; 34 | pub use media_stream::{MediaStream, MediaStreamError}; 35 | pub use media_stream_track::{MediaStreamTrack, MediaStreamTrackKind}; 36 | pub use observer::{ 37 | IceConnectionState, IceGatheringState, Observer, PeerConnectionState, SignalingState, 38 | }; 39 | pub use promisify::{Promisify, PromisifyExt, SpawnBlocking}; 40 | pub use rtc_datachannel::{ 41 | DataChannel, DataChannelOptions, DataChannelPriority, DataChannelState, RTCDataChannel, 42 | }; 43 | pub use rtc_icecandidate::RTCIceCandidate; 44 | pub use rtc_peerconnection::{RTCError, RTCPeerConnection}; 45 | pub use rtc_peerconnection_configure::{ 46 | BundlePolicy, IceTransportPolicy, RTCConfiguration, RTCIceServer, RtcpMuxPolicy, 47 | }; 48 | pub use rtc_session_description::{RTCSessionDescription, RTCSessionDescriptionType}; 49 | pub use set_description_observer::{SetDescriptionError, SetDescriptionObserver}; 50 | pub use sink::{SinkExt, Sinker}; 51 | pub use video_frame::VideoFrame; 52 | pub use video_track::VideoTrack; 53 | -------------------------------------------------------------------------------- /src/media_stream.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, ffi::c_char, fmt, sync::Arc}; 2 | 3 | use crate::{ 4 | cstr::{free_cstring, to_c_str, StringError}, 5 | MediaStreamTrack, 6 | }; 7 | 8 | #[derive(Debug)] 9 | pub enum MediaStreamError { 10 | CreateTrackFailed, 11 | StringError(StringError), 12 | } 13 | 14 | impl Error for MediaStreamError {} 15 | 16 | impl fmt::Display for MediaStreamError { 17 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 18 | write!(f, "{}", self) 19 | } 20 | } 21 | 22 | /// The MediaStream interface represents a stream of media content. 23 | /// 24 | /// A stream consists of several tracks, such as video or audio tracks. Each 25 | /// track is specified as an instance of MediaStreamTrack. 26 | pub struct MediaStream { 27 | pub id: String, 28 | pub tracks: Vec, 29 | pub(crate) raw_id: *const c_char, 30 | } 31 | 32 | unsafe impl Send for MediaStream {} 33 | unsafe impl Sync for MediaStream {} 34 | 35 | impl MediaStream { 36 | /// Creates and returns a new MediaStream object. You can create an empty 37 | /// stream, a stream which is based upon an existing stream, or a stream 38 | /// that contains a specified list of tracks. 39 | pub fn new(id: &str) -> Result, MediaStreamError> { 40 | Ok(Arc::new(Self { 41 | tracks: Vec::with_capacity(10), 42 | raw_id: to_c_str(id).map_err(|e| MediaStreamError::StringError(e))?, 43 | id: id.to_string(), 44 | })) 45 | } 46 | 47 | /// A string containing a 36-character universally unique identifier (UUID) 48 | /// for the object. 49 | pub(crate) fn get_id(&self) -> *const c_char { 50 | self.raw_id 51 | } 52 | } 53 | 54 | impl Drop for MediaStream { 55 | fn drop(&mut self) { 56 | free_cstring(self.raw_id); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/media_stream_track.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::{c_char, c_void}, 3 | sync::Arc, 4 | }; 5 | 6 | use crate::{media_stream::MediaStreamError, AudioTrack, VideoTrack}; 7 | 8 | extern "C" { 9 | pub(crate) fn rtc_free_frame(frame: *const c_void); 10 | pub(crate) fn rtc_remove_media_stream_track_frame_h( 11 | track: *const crate::media_stream_track::RawMediaStreamTrack, 12 | ); 13 | 14 | pub(crate) fn rtc_free_media_stream_track( 15 | track: *const crate::media_stream_track::RawMediaStreamTrack, 16 | ); 17 | } 18 | 19 | #[repr(i32)] 20 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 21 | pub enum MediaStreamTrackKind { 22 | Video, 23 | Audio, 24 | } 25 | 26 | #[repr(C)] 27 | pub(crate) struct RawMediaStreamTrack { 28 | /// Returns a string set to "audio" if the track is an audio track and to 29 | /// "video", if it is a video track. It doesn't change if the track is 30 | /// disassociated from its source. 31 | pub kind: MediaStreamTrackKind, 32 | /// Returns a string containing a user agent-assigned label that identifies 33 | /// the track source, as in "internal microphone". The string may be 34 | /// left empty and is empty as long as no source has been connected. 35 | /// When the track is disassociated from its source, the label is not 36 | /// changed. 37 | pub label: *const c_char, 38 | // video 39 | video_source: *const c_void, 40 | video_sink: *const c_void, 41 | // audio 42 | audio_source: *const c_void, 43 | audio_sink: *const c_void, 44 | 45 | sender: *const c_void, 46 | } 47 | 48 | /// The MediaStreamTrack interface represents a single media track within 49 | /// a stream typically. 50 | /// 51 | /// these are audio or video tracks, but other track types may exist as 52 | /// well. 53 | #[derive(Clone)] 54 | pub enum MediaStreamTrack { 55 | Audio(Arc), 56 | Video(Arc), 57 | } 58 | 59 | impl MediaStreamTrack { 60 | pub fn create_video_track(label: &str) -> Result { 61 | Ok(Self::Video(VideoTrack::new(label)?)) 62 | } 63 | 64 | pub fn create_audio_track(label: &str) -> Result { 65 | Ok(Self::Audio(AudioTrack::new(label)?)) 66 | } 67 | 68 | /// Created through the original media stream track, video and audio 69 | /// are processed separately. 70 | pub(crate) fn from_raw(raw: *const RawMediaStreamTrack) -> Self { 71 | assert!(!raw.is_null()); 72 | match unsafe { (*raw).kind } { 73 | MediaStreamTrackKind::Audio => Self::Audio(AudioTrack::from_raw(raw)), 74 | MediaStreamTrackKind::Video => Self::Video(VideoTrack::from_raw(raw)), 75 | } 76 | } 77 | 78 | /// get raw media stream track ptr. 79 | pub(crate) fn get_raw(&self) -> *const RawMediaStreamTrack { 80 | match self { 81 | Self::Audio(track) => track.raw, 82 | Self::Video(track) => track.raw, 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/observer.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use crate::{ 4 | media_stream_track::RawMediaStreamTrack, rtc_datachannel::RawRTCDataChannel, 5 | rtc_icecandidate::RawRTCIceCandidate, DataChannel, MediaStreamTrack, RTCDataChannel, 6 | RTCIceCandidate, 7 | }; 8 | 9 | /// This state essentially represents the aggregate state of all ICE 10 | /// transports (which are of type RTCIceTransport or RTCDtlsTransport) 11 | /// being used by the connection. 12 | #[repr(i32)] 13 | #[derive(Clone, Copy, Debug)] 14 | pub enum PeerConnectionState { 15 | /// At least one of the connection's ICE transports 16 | /// (RTCIceTransport or RTCDtlsTransport objects) is in the new state, 17 | /// and none of them are in one of the following states: connecting, 18 | /// checking, failed, disconnected, or all of the connection's 19 | /// transports are in the closed state. 20 | New, 21 | /// One or more of the ICE transports are currently in the process of 22 | /// establishing a connection; that is, their iceConnectionState is 23 | /// either checking or connected, and no transports are in the failed 24 | /// state. 25 | Connecting, 26 | /// Every ICE transport used by the connection is either in use 27 | /// (state connected or completed) or is closed (state closed); in 28 | /// addition, at least one transport is either connected or completed. 29 | Connected, 30 | /// At least one of the ICE transports for the connection is in the 31 | /// disconnected state and none of the other transports are in the state 32 | /// failed, connecting, or checking. 33 | Disconnected, 34 | /// One or more of the ICE transports on the connection is in the 35 | /// failed state. 36 | Failed, 37 | /// The RTCPeerConnection is closed. 38 | Close, 39 | } 40 | 41 | /// Describes the state of the signaling process at the local end 42 | /// of the connection when connecting or reconnecting to another peer. 43 | #[repr(i32)] 44 | #[derive(Clone, Copy, Debug)] 45 | pub enum SignalingState { 46 | /// There is no ongoing exchange of offer and answer underway. 47 | Stable, 48 | /// The local peer has called RTCPeerConnection.setLocalDescription(), 49 | /// passing in SDP representing an offer (usually created by calling 50 | /// RTCPeerConnection.createOffer()), and the offer has been applied 51 | /// successfully. 52 | HaveLocalOffer, 53 | /// The offer sent by the remote peer has been applied and an answer 54 | /// has been created (usually by calling RTCPeerConnection.createAnswer()) 55 | /// and applied by calling RTCPeerConnection.setLocalDescription(). 56 | /// This provisional answer describes the supported media formats and 57 | /// so forth, but may not have a complete set of ICE candidates included. 58 | /// Further candidates will be delivered separately later. 59 | HaveLocalPrAnswer, 60 | /// The remote peer has created an offer and used the signaling server 61 | /// to deliver it to the local peer, which has set the offer as the remote 62 | /// description by calling RTCPeerConnection.setRemoteDescription(). 63 | HaveRemoteOffer, 64 | /// A provisional answer has been received and successfully applied 65 | /// in response to an offer previously sent and established by calling 66 | /// setLocalDescription(). 67 | HaveRemotePrAnswer, 68 | /// The RTCPerrConnection has been closed. 69 | Closed, 70 | } 71 | 72 | /// Describes the ICE collection status of the connection. 73 | #[repr(i32)] 74 | #[derive(Clone, Copy, Debug)] 75 | pub enum IceGatheringState { 76 | /// The peer connection was just created and hasn't done any 77 | /// networking yet. 78 | New, 79 | /// The ICE agent is in the process of gathering candidates 80 | /// for the connection. 81 | Gathering, 82 | /// The ICE agent has finished gathering candidates. If 83 | /// something happens that requires collecting new candidates, 84 | /// such as a new interface being added or the addition of a 85 | /// new ICE server, the state will revert to gathering to 86 | /// gather those candidates. 87 | Complete, 88 | } 89 | 90 | /// It describes the current state of the ICE agent and its connection to 91 | /// the ICE server. 92 | #[repr(i32)] 93 | #[derive(Clone, Copy, Debug)] 94 | pub enum IceConnectionState { 95 | /// The ICE agent is gathering addresses or is waiting to be given 96 | /// remote candidates through calls to RTCPeerConnection.addIceCandidate(). 97 | New, 98 | /// The ICE agent has been given one or more remote candidates and is 99 | /// checking pairs of local and remote candidates against one another 100 | /// to try to find a compatible match, but has not yet found a pair 101 | /// which will allow the peer connection to be made. It is possible 102 | /// that gathering of candidates is also still underway. 103 | Checking, 104 | /// A usable pairing of local and remote candidates has been found for 105 | /// all components of the connection, and the connection has been 106 | /// established. It is possible that gathering is still underway, and 107 | /// it is also possible that the ICE agent is still checking candidates 108 | /// against one another looking for a better connection to use. 109 | Connected, 110 | /// The ICE agent has finished gathering candidates, has checked all 111 | /// pairs against one another, and has found a connection for all 112 | /// components. 113 | Completed, 114 | /// The ICE candidate has checked all candidates pairs against one 115 | /// another and has failed to find compatible matches for all components 116 | /// of the connection. It is, however, possible that the ICE agent did 117 | /// find compatible connections for some components. 118 | Failed, 119 | /// Checks to ensure that components are still connected failed for at 120 | /// least one component of the RTCPeerConnection. This is a less stringent 121 | /// test than failed and may trigger intermittently and resolve just as 122 | /// spontaneously on less reliable networks, or during temporary 123 | /// disconnections. When the problem resolves, the connection may return 124 | /// to the connected state. 125 | Disconnected, 126 | /// The ICE agent for this RTCPeerConnection has shut down and is no 127 | /// longer handling requests. 128 | Closed, 129 | Max, 130 | } 131 | 132 | /// PeerConnection callback interface, used for RTCPeerConnection events. 133 | /// Application should implement these methods. 134 | #[allow(unused)] 135 | pub trait Observer { 136 | /// A signalingstatechange event is sent to an RTCPeerConnection to notify 137 | /// it that its signaling state, as indicated by the signalingState 138 | /// property, has changed. 139 | fn on_signaling_change(&self, state: SignalingState) {} 140 | /// The connectionstatechange event is sent to the onconnectionstatechange 141 | /// event handler on an RTCPeerConnection object after a new track has been 142 | /// added to an RTCRtpReceiver which is part of the connection. 143 | fn on_connection_change(&self, state: PeerConnectionState) {} 144 | /// The icegatheringstatechange event is sent to the 145 | /// onicegatheringstatechange event handler on an RTCPeerConnection when 146 | /// the state of the ICE candidate gathering process changes. This 147 | /// signifies that the value of the connection's iceGatheringState property 148 | /// has changed. 149 | /// 150 | /// When ICE first starts to gather connection candidates, the value 151 | /// changes from new to gathering to indicate that the process of 152 | /// collecting candidate configurations for the connection has begun. When 153 | /// the value changes to complete, all of the transports that make up the 154 | /// RTCPeerConnection have finished gathering ICE candidates. 155 | fn on_ice_gathering_change(&self, state: IceGatheringState) {} 156 | /// An icecandidate event is sent to an RTCPeerConnection when an 157 | /// RTCIceCandidate has been identified and added to the local peer by a 158 | /// call to RTCPeerConnection.setLocalDescription(). The event handler 159 | /// should transmit the candidate to the remote peer over the signaling 160 | /// channel so the remote peer can add it to its set of remote candidates. 161 | fn on_ice_candidate(&self, candidate: RTCIceCandidate) {} 162 | /// A negotiationneeded event is sent to the RTCPeerConnection when 163 | /// negotiation of the connection through the signaling channel is 164 | /// required. This occurs both during the initial setup of the connection 165 | /// as well as any time a change to the communication environment requires 166 | /// reconfiguring the connection. 167 | fn on_renegotiation_needed(&self) {} 168 | /// An iceconnectionstatechange event is sent to an RTCPeerConnection 169 | /// object each time the ICE connection state changes during the 170 | /// negotiation process. The new ICE connection state is available in the 171 | /// object's iceConnectionState property. 172 | fn on_ice_connection_change(&self, state: IceConnectionState) {} 173 | /// The track event is sent to the ontrack event handler on 174 | /// RTCPeerConnections after a new track has been added to an 175 | /// RTCRtpReceiver which is part of the connection. 176 | fn on_track(&self, track: MediaStreamTrack) {} 177 | /// A datachannel event is sent to an RTCPeerConnection instance when an 178 | /// RTCDataChannel has been added to the connection, as a result of the 179 | /// remote peer calling RTCPeerConnection.createDataChannel(). 180 | fn on_data_channel(&self, channel: RTCDataChannel) {} 181 | } 182 | 183 | /// wrapper observer trait impl. 184 | pub struct ObserverRef { 185 | data: Box, 186 | } 187 | 188 | impl ObserverRef { 189 | pub fn new(data: T) -> Self { 190 | Self { 191 | data: Box::new(data), 192 | } 193 | } 194 | } 195 | 196 | /// rtc peer connection observer events callback ref. 197 | #[repr(C)] 198 | #[rustfmt::skip] 199 | pub(crate) struct TEvents { 200 | on_signaling_change: extern "C" fn(*mut ObserverRef, SignalingState), 201 | on_datachannel: extern "C" fn(*mut ObserverRef, *const RawRTCDataChannel), 202 | on_ice_gathering_change: extern "C" fn(*mut ObserverRef, IceGatheringState), 203 | on_ice_candidate: extern "C" fn(*mut ObserverRef, *const RawRTCIceCandidate), 204 | on_renegotiation_needed: extern "C" fn(*mut ObserverRef), 205 | on_ice_connection_change: extern "C" fn(*mut ObserverRef, IceConnectionState), 206 | on_track: extern "C" fn(*mut ObserverRef, *const RawMediaStreamTrack), 207 | on_connection_change: extern "C" fn(*mut ObserverRef, PeerConnectionState), 208 | } 209 | 210 | /// events callback const ref. 211 | pub(crate) const EVENTS: TEvents = TEvents { 212 | on_signaling_change, 213 | on_datachannel, 214 | on_ice_gathering_change, 215 | on_ice_candidate, 216 | on_renegotiation_needed, 217 | on_ice_connection_change, 218 | on_track, 219 | on_connection_change, 220 | }; 221 | 222 | extern "C" fn on_signaling_change(ctx: *mut ObserverRef, state: SignalingState) { 223 | assert!(!ctx.is_null()); 224 | (unsafe { &mut *ctx }).data.on_signaling_change(state); 225 | } 226 | 227 | extern "C" fn on_connection_change(ctx: *mut ObserverRef, state: PeerConnectionState) { 228 | assert!(!ctx.is_null()); 229 | (unsafe { &mut *ctx }).data.on_connection_change(state); 230 | } 231 | 232 | extern "C" fn on_ice_gathering_change(ctx: *mut ObserverRef, state: IceGatheringState) { 233 | assert!(!ctx.is_null()); 234 | (unsafe { &mut *ctx }).data.on_ice_gathering_change(state); 235 | } 236 | 237 | extern "C" fn on_ice_candidate(ctx: *mut ObserverRef, candidate: *const RawRTCIceCandidate) { 238 | assert!(!ctx.is_null()); 239 | assert!(!candidate.is_null()); 240 | let candidate = RTCIceCandidate::try_from(unsafe { &*candidate }).unwrap(); 241 | (unsafe { &mut *ctx }).data.on_ice_candidate(candidate); 242 | } 243 | 244 | extern "C" fn on_renegotiation_needed(ctx: *mut ObserverRef) { 245 | assert!(!ctx.is_null()); 246 | (unsafe { &mut *ctx }).data.on_renegotiation_needed(); 247 | } 248 | 249 | extern "C" fn on_ice_connection_change(ctx: *mut ObserverRef, state: IceConnectionState) { 250 | assert!(!ctx.is_null()); 251 | (unsafe { &mut *ctx }).data.on_ice_connection_change(state); 252 | } 253 | 254 | extern "C" fn on_datachannel(ctx: *mut ObserverRef, channel: *const RawRTCDataChannel) { 255 | assert!(!ctx.is_null() && !channel.is_null()); 256 | let channel = DataChannel::from_raw(channel); 257 | (unsafe { &mut *ctx }).data.on_data_channel(channel); 258 | } 259 | 260 | extern "C" fn on_track(ctx: *mut ObserverRef, track: *const RawMediaStreamTrack) { 261 | assert!(!ctx.is_null() && !track.is_null()); 262 | let track = MediaStreamTrack::from_raw(track); 263 | (unsafe { &mut *ctx }).data.on_track(track); 264 | } 265 | -------------------------------------------------------------------------------- /src/promisify.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | future::Future, 3 | pin::Pin, 4 | result::Result, 5 | sync::{ 6 | atomic::{AtomicPtr, Ordering}, 7 | Arc, 8 | }, 9 | task::{Context, Poll}, 10 | thread, 11 | }; 12 | 13 | use futures::task::AtomicWaker; 14 | 15 | pub trait PromisifyExt { 16 | type Err; 17 | type Output; 18 | 19 | fn handle(&self, waker: Arc) -> Result<(), Self::Err>; 20 | fn wake(&self) -> Option>; 21 | } 22 | 23 | pub struct Promisify 24 | where 25 | T: PromisifyExt, 26 | { 27 | pub(crate) waker: Arc, 28 | pub(crate) begin: bool, 29 | pub(crate) ext: T, 30 | } 31 | 32 | impl Promisify 33 | where 34 | T: PromisifyExt, 35 | { 36 | /// A wrapper for asynchronous tasks, used to handle asynchronous tasks that 37 | /// return results in a callback. 38 | /// 39 | /// ```no_run 40 | /// struct SimplePromisify; 41 | /// 42 | /// impl PromisifyExt for SimplePromisify { 43 | /// type Err = (); 44 | /// type Output = (); 45 | /// 46 | /// fn handle(&self, waker: Arc) -> Result<(), Self::Err> { 47 | /// // Handle some asynchronous tasks... 48 | /// waker.wake(); 49 | /// Ok(()) 50 | /// } 51 | /// 52 | /// fn wake(&self) -> Option> { 53 | /// // Handle the return value when woken up... 54 | /// Some(Ok(())) 55 | /// } 56 | /// } 57 | /// 58 | /// assert!(Promisify::new(SimplePromisify).await.is_ok()); 59 | /// ``` 60 | pub(crate) fn new(ext: T) -> Self { 61 | Self { 62 | waker: Arc::new(AtomicWaker::new()), 63 | begin: false, 64 | ext, 65 | } 66 | } 67 | } 68 | 69 | impl Future for Promisify 70 | where 71 | T: PromisifyExt + Unpin, 72 | { 73 | type Output = Result; 74 | 75 | #[rustfmt::skip] 76 | fn poll( 77 | mut self: Pin<&mut Self>, 78 | cx: &mut Context<'_>, 79 | ) -> Poll { 80 | let mut this = self.as_mut(); 81 | this.waker.register(cx.waker()); 82 | if !this.begin { 83 | match this.ext.handle(this.waker.clone()) { 84 | Err(e) => Poll::Ready(Err(e)), 85 | Ok(_) => { 86 | this.begin = true; 87 | Poll::Pending 88 | }, 89 | } 90 | } else { 91 | this.ext 92 | .wake() 93 | .map(Poll::Ready) 94 | .unwrap_or(Poll::Pending) 95 | } 96 | } 97 | } 98 | 99 | pub struct SpawnBlocking { 100 | handle: Option>, 101 | waker: Arc, 102 | process: Option>, 103 | ret: Arc>, 104 | } 105 | 106 | impl SpawnBlocking 107 | where 108 | T: FnOnce() -> R + Send + 'static, 109 | R: Send + 'static, 110 | { 111 | /// Runs the provided function on an executor dedicated to blocking 112 | /// operations. 113 | /// 114 | /// ```no_run 115 | /// // This future never returns. 116 | /// SpawnBlocking::new(|| { 117 | /// loop { 118 | /// // an infinite loop. 119 | /// } 120 | /// }) 121 | /// .await; 122 | /// 123 | /// let ret = SpawnBlocking::new(|| "hello").await; 124 | /// 125 | /// assert_eq!(ret, "hello"); 126 | /// ``` 127 | pub fn new(func: T) -> Self { 128 | Self { 129 | ret: Arc::new(AtomicPtr::new(std::ptr::null_mut())), 130 | waker: Arc::new(AtomicWaker::new()), 131 | process: Some(Box::new(func)), 132 | handle: None, 133 | } 134 | } 135 | } 136 | 137 | impl Future for SpawnBlocking 138 | where 139 | T: FnOnce() -> R + Send + 'static, 140 | R: Send + 'static, 141 | { 142 | type Output = R; 143 | 144 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 145 | let mut this = self.as_mut(); 146 | this.waker.register(cx.waker()); 147 | match &this.handle { 148 | Some(handle) => { 149 | if handle.is_finished() { 150 | let ret = this.ret.swap(std::ptr::null_mut(), Ordering::Relaxed); 151 | return Poll::Ready(unsafe { *Box::from_raw(ret) }); 152 | } 153 | } 154 | None => { 155 | let ret = this.ret.clone(); 156 | let waker = this.waker.clone(); 157 | let process = this.process.take(); 158 | let _ = this.handle.insert(thread::spawn(move || { 159 | if let Some(func) = process { 160 | ret.store(Box::into_raw(Box::new(func())), Ordering::Relaxed); 161 | waker.wake(); 162 | } 163 | })); 164 | } 165 | }; 166 | 167 | Poll::Pending 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/rtc_datachannel.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | ffi::{c_char, c_int, c_void}, 4 | slice::from_raw_parts, 5 | sync::{Arc, RwLock}, 6 | }; 7 | 8 | use crate::{ 9 | cstr::{free_cstring, to_c_str}, 10 | Sinker, 11 | }; 12 | 13 | #[allow(improper_ctypes)] 14 | extern "C" { 15 | pub(crate) fn rtc_get_data_channel_state( 16 | channel: *const crate::rtc_datachannel::RawRTCDataChannel, 17 | ) -> crate::rtc_datachannel::DataChannelState; 18 | 19 | pub(crate) fn rtc_send_data_channel_msg( 20 | channel: *const crate::rtc_datachannel::RawRTCDataChannel, 21 | buf: *const u8, 22 | size: c_int, 23 | ); 24 | 25 | pub(crate) fn rtc_set_data_channel_msg_h( 26 | channel: *const crate::rtc_datachannel::RawRTCDataChannel, 27 | handler: extern "C" fn(&crate::DataChannel, *const u8, u64), 28 | ctx: &crate::DataChannel, 29 | ); 30 | 31 | pub(crate) fn rtc_remove_data_channel_msg_h( 32 | channel: *const crate::rtc_datachannel::RawRTCDataChannel, 33 | ); 34 | 35 | pub(crate) fn rtc_free_data_channel(channel: *const crate::rtc_datachannel::RawRTCDataChannel); 36 | } 37 | 38 | /// Indicates the state of the data channel connection. 39 | #[repr(i32)] 40 | #[derive(Debug, Clone, Copy)] 41 | pub enum DataChannelState { 42 | Connecting, 43 | Open, 44 | Closing, 45 | Closed, 46 | } 47 | 48 | /// Used to process outgoing WebRTC packets and prioritize outgoing WebRTC 49 | /// packets in case of congestion. 50 | #[repr(i32)] 51 | #[derive(Debug, Clone, Copy)] 52 | pub enum DataChannelPriority { 53 | VeryLow = 1, 54 | Low, 55 | Medium, 56 | High, 57 | } 58 | 59 | #[repr(C)] 60 | pub(crate) struct RawDataChannelOptions { 61 | reliable: bool, 62 | ordered: bool, 63 | max_retransmit_time: u64, 64 | max_retransmits: u64, 65 | protocol: *const c_char, 66 | negotiated: bool, 67 | id: c_int, 68 | priority: c_int, 69 | } 70 | 71 | impl Drop for RawDataChannelOptions { 72 | fn drop(&mut self) { 73 | free_cstring(self.protocol) 74 | } 75 | } 76 | 77 | #[repr(C)] 78 | #[derive(Debug)] 79 | pub(crate) struct RawRTCDataChannel { 80 | label: *const c_char, 81 | channel: *const c_void, 82 | remote: bool, 83 | } 84 | 85 | /// An object providing configuration options for the data channel. 86 | pub struct DataChannelOptions { 87 | /// Reliability is assumed, and channel will be unreliable if 88 | /// maxRetransmitTime or MaxRetransmits is set. 89 | pub reliable: bool, // = false 90 | /// True if ordered delivery is required. 91 | pub ordered: bool, // = true 92 | /// The max period of time in milliseconds in which retransmissions will be 93 | /// sent. After this time, no more retransmissions will be sent. 94 | /// 95 | /// Cannot be set along with `maxRetransmits`. 96 | /// This is called `maxPacketLifeTime` in the WebRTC JS API. 97 | /// Negative values are ignored, and positive values are clamped to 98 | /// [0-65535] 99 | pub max_retransmit_time: Option, 100 | /// The max number of retransmissions. 101 | /// 102 | /// Cannot be set along with `maxRetransmitTime`. 103 | /// Negative values are ignored, and positive values are clamped to 104 | /// [0-65535] 105 | pub max_retransmits: Option, 106 | /// This is set by the application and opaque to the WebRTC implementation. 107 | pub protocol: String, // = "" 108 | /// True if the channel has been externally negotiated and we do not send 109 | /// an in-band signalling in the form of an "open" message. If this is 110 | /// true, `id` below must be set; otherwise it should be unset and will 111 | /// be negotiated in-band. 112 | pub negotiated: bool, // = false 113 | /// The stream id, or SID, for SCTP data channels. -1 if unset (see above). 114 | pub id: i8, 115 | pub priority: Option, 116 | } 117 | 118 | impl Default for DataChannelOptions { 119 | fn default() -> Self { 120 | Self { 121 | reliable: false, 122 | ordered: true, 123 | max_retransmit_time: None, 124 | max_retransmits: None, 125 | protocol: "".to_string(), 126 | negotiated: false, 127 | id: 0, 128 | priority: None, 129 | } 130 | } 131 | } 132 | 133 | impl Into for &DataChannelOptions { 134 | fn into(self) -> RawDataChannelOptions { 135 | RawDataChannelOptions { 136 | id: self.id as c_int, 137 | reliable: self.reliable, 138 | ordered: self.ordered, 139 | negotiated: self.negotiated, 140 | protocol: to_c_str(&self.protocol).unwrap(), 141 | max_retransmits: self.max_retransmits.unwrap_or(0), 142 | max_retransmit_time: self.max_retransmit_time.unwrap_or(0), 143 | priority: self.priority.as_ref().map(|x| *x as c_int).unwrap_or(0), 144 | } 145 | } 146 | } 147 | 148 | /// The RTCDataChannel interface represents a network channel which can be used 149 | /// for bidirectional peer-to-peer transfers of arbitrary data. 150 | /// 151 | /// Every data channel is associated with an RTCPeerConnection, and each peer 152 | /// connection can have up to a theoretical maximum of 65,534 data channels. 153 | pub struct DataChannel { 154 | raw: *const RawRTCDataChannel, 155 | sinks: RwLock>>>, 156 | } 157 | 158 | unsafe impl Send for DataChannel {} 159 | unsafe impl Sync for DataChannel {} 160 | 161 | /// Arc DataChannel. 162 | pub type RTCDataChannel = Arc; 163 | 164 | impl DataChannel { 165 | /// Sends data across the data channel to the remote peer. 166 | pub fn send(&self, buf: &[u8]) { 167 | assert!(!unsafe { &*self.raw }.remote); 168 | unsafe { rtc_send_data_channel_msg(self.raw, buf.as_ptr(), buf.len() as c_int) } 169 | } 170 | 171 | /// Returns a string which indicates the state of the data channel's 172 | /// underlying data connection. 173 | pub fn get_state(&self) -> DataChannelState { 174 | unsafe { rtc_get_data_channel_state(self.raw) } 175 | } 176 | 177 | /// Register channel data sink, one channel can register multiple sinks. 178 | /// The sink id cannot be repeated, otherwise the sink implementation will 179 | /// be overwritten. 180 | pub fn register_sink(&self, id: u8, sink: Sinker>) { 181 | assert!(unsafe { &*self.raw }.remote); 182 | let mut sinks = self.sinks.write().unwrap(); 183 | 184 | // Register for the first time, register the callback function to 185 | // webrtc native, and then do not need to register again. 186 | if sinks.is_empty() { 187 | unsafe { rtc_set_data_channel_msg_h(self.raw, on_channal_data, self) } 188 | } 189 | 190 | sinks.insert(id, sink); 191 | } 192 | 193 | /// Delete the registered sink, if it exists, it will return the deleted 194 | /// sink. 195 | pub fn remove_sink(&self, id: u8) -> Option>> { 196 | assert!(unsafe { &*self.raw }.remote); 197 | let mut sinks = self.sinks.write().unwrap(); 198 | let value = sinks.remove(&id); 199 | if sinks.is_empty() { 200 | unsafe { rtc_remove_data_channel_msg_h(self.raw) } 201 | } 202 | 203 | value 204 | } 205 | 206 | /// Create data channel from raw type ptr. 207 | pub(crate) fn from_raw(raw: *const RawRTCDataChannel) -> Arc { 208 | assert!(!raw.is_null()); 209 | Arc::new(Self { 210 | sinks: RwLock::new(HashMap::new()), 211 | raw, 212 | }) 213 | } 214 | 215 | fn on_data(this: &Self, data: Vec) { 216 | for sinker in this.sinks.read().unwrap().values() { 217 | sinker.sink.on_data(data.clone()); 218 | } 219 | } 220 | } 221 | 222 | impl Drop for DataChannel { 223 | fn drop(&mut self) { 224 | unsafe { rtc_remove_data_channel_msg_h(self.raw) } 225 | unsafe { rtc_free_data_channel(self.raw) } 226 | } 227 | } 228 | 229 | #[no_mangle] 230 | extern "C" fn on_channal_data(ctx: &DataChannel, buf: *const u8, size: u64) { 231 | assert!(!buf.is_null()); 232 | let array = unsafe { from_raw_parts(buf, size as usize) }; 233 | DataChannel::on_data(ctx, array.to_vec()); 234 | } 235 | -------------------------------------------------------------------------------- /src/rtc_icecandidate.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{c_char, c_int}; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use crate::cstr::{free_cstring, from_c_str, to_c_str, StringError}; 6 | 7 | #[repr(C)] 8 | pub(crate) struct RawRTCIceCandidate { 9 | pub candidate: *const c_char, 10 | pub sdp_mid: *const c_char, 11 | pub sdp_mline_index: c_int, 12 | } 13 | 14 | impl Drop for RawRTCIceCandidate { 15 | fn drop(&mut self) { 16 | free_cstring(self.sdp_mid.cast_mut()); 17 | free_cstring(self.candidate.cast_mut()); 18 | } 19 | } 20 | 21 | /// Indicates a candidate Interactive Connection Establishment 22 | /// (ICE) configuration. 23 | /// 24 | /// The RTCIceCandidate interface¡ªpart of the WebRTC API¡ªrepresents a 25 | /// candidate Interactive Connectivity Establishment (ICE) configuration which 26 | /// may be used to establish an RTCPeerConnection. 27 | /// 28 | /// An ICE candidate describes the protocols and routing needed for WebRTC to be 29 | /// able to communicate with a remote device. When starting a WebRTC peer 30 | /// connection, typically a number of candidates are proposed by each end of the 31 | /// connection, until they mutually agree upon one which describes the 32 | /// connection they decide will be best. WebRTC then uses that candidate's 33 | /// details to initiate the connection. 34 | /// 35 | /// For details on how the ICE process works, see Lifetime of a WebRTC session. 36 | /// The article WebRTC connectivity provides additional useful details. 37 | #[derive(Clone, Debug, Serialize, Deserialize)] 38 | pub struct RTCIceCandidate { 39 | /// A string describing the properties of the candidate, taken directly 40 | /// from the SDP attribute "candidate". The candidate string specifies 41 | /// the network connectivity information for the candidate. If the 42 | /// candidate is an empty string (""), the end of the candidate list 43 | /// has been reached; this candidate is known as the 44 | /// "end-of-candidates" marker. 45 | pub candidate: String, 46 | /// A string containing the identification tag of the media stream with 47 | /// which the candidate is associated, or null if there is no 48 | /// associated media stream. The default is null. 49 | pub sdp_mid: String, 50 | /// TA number property containing the zero-based index of the m-line with 51 | /// which Tthe candidate is associated, within the SDP of the media 52 | /// description, or Tnull if no such associated exists. The default is 53 | /// null. 54 | pub sdp_mline_index: u8, 55 | } 56 | 57 | impl TryInto for &RTCIceCandidate { 58 | type Error = StringError; 59 | 60 | fn try_into(self) -> Result { 61 | Ok(RawRTCIceCandidate { 62 | sdp_mline_index: self.sdp_mline_index as c_int, 63 | sdp_mid: to_c_str(&self.sdp_mid)?, 64 | candidate: to_c_str(&self.candidate)?, 65 | }) 66 | } 67 | } 68 | 69 | impl TryFrom<&RawRTCIceCandidate> for RTCIceCandidate { 70 | type Error = StringError; 71 | 72 | fn try_from(value: &RawRTCIceCandidate) -> Result { 73 | Ok(RTCIceCandidate { 74 | sdp_mline_index: value.sdp_mline_index as u8, 75 | sdp_mid: from_c_str(value.sdp_mid)?, 76 | candidate: from_c_str(value.candidate)?, 77 | }) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/rtc_peerconnection.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | error::Error, 3 | ffi::{c_char, c_int, c_void}, 4 | fmt, 5 | sync::{Arc, Mutex}, 6 | }; 7 | 8 | use crate::{ 9 | auto_ptr::HeapPointer, 10 | create_description_observer::{CreateDescriptionFuture, CreateDescriptionKind}, 11 | cstr::{free_cstring, to_c_str, StringError}, 12 | observer::{ObserverRef, EVENTS}, 13 | rtc_datachannel::RawDataChannelOptions, 14 | rtc_icecandidate::RawRTCIceCandidate, 15 | rtc_peerconnection_configure::RawRTCPeerConnectionConfigure, 16 | set_description_observer::{SetDescriptionFuture, SetDescriptionKind}, 17 | DataChannel, DataChannelOptions, MediaStream, MediaStreamTrack, Observer, RTCConfiguration, 18 | RTCDataChannel, RTCIceCandidate, RTCSessionDescription, 19 | }; 20 | 21 | #[allow(improper_ctypes)] 22 | extern "C" { 23 | pub(crate) fn rtc_create_peer_connection( 24 | config: *const crate::rtc_peerconnection_configure::RawRTCPeerConnectionConfigure, 25 | events: *const crate::observer::TEvents, 26 | observer: *mut crate::observer::ObserverRef, 27 | ) -> *const crate::rtc_peerconnection::RawRTCPeerConnection; 28 | 29 | pub(crate) fn rtc_add_ice_candidate( 30 | peer: *const crate::rtc_peerconnection::RawRTCPeerConnection, 31 | icecandidate: *const crate::rtc_icecandidate::RawRTCIceCandidate, 32 | ) -> bool; 33 | 34 | pub(crate) fn rtc_add_media_stream_track( 35 | peer: *const crate::rtc_peerconnection::RawRTCPeerConnection, 36 | track: *const crate::media_stream_track::RawMediaStreamTrack, 37 | id: *const c_char, 38 | ) -> c_int; 39 | 40 | pub(crate) fn rtc_remove_media_stream_track( 41 | peer: *const crate::rtc_peerconnection::RawRTCPeerConnection, 42 | track: *const crate::media_stream_track::RawMediaStreamTrack, 43 | ) -> c_int; 44 | 45 | pub(crate) fn rtc_create_data_channel( 46 | peer: *const crate::rtc_peerconnection::RawRTCPeerConnection, 47 | label: *const c_char, 48 | options: *const crate::rtc_datachannel::RawDataChannelOptions, 49 | ) -> *const crate::rtc_datachannel::RawRTCDataChannel; 50 | 51 | pub(crate) fn rtc_close(peer: *const crate::rtc_peerconnection::RawRTCPeerConnection); 52 | } 53 | 54 | pub(crate) type RawRTCPeerConnection = c_void; 55 | 56 | #[derive(Debug)] 57 | pub enum RTCError { 58 | CreateRTCFailed, 59 | AddTrackFailed(i32), 60 | AddIceCandidateFailed, 61 | RemoveTrackFailed(i32), 62 | StringError(StringError), 63 | } 64 | 65 | impl Error for RTCError {} 66 | 67 | impl fmt::Display for RTCError { 68 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 69 | write!(f, "{}", self) 70 | } 71 | } 72 | 73 | /// The RTCPeerConnection interface represents a WebRTC connection between 74 | /// the local computer and a remote peer. 75 | /// 76 | /// It provides methods to connect to a remote peer, maintain and monitor 77 | /// the connection, and close the connection once it's no longer needed. 78 | pub struct RTCPeerConnection { 79 | raw: *const RawRTCPeerConnection, 80 | tracks: Mutex)>>, 81 | #[allow(dead_code)] 82 | observer: HeapPointer, 83 | #[allow(dead_code)] 84 | config: HeapPointer, 85 | } 86 | 87 | unsafe impl Send for RTCPeerConnection {} 88 | unsafe impl Sync for RTCPeerConnection {} 89 | 90 | impl RTCPeerConnection { 91 | /// The RTCPeerConnection constructor returns a newly-created 92 | /// RTCPeerConnection, which represents a connection between the local 93 | /// device and a remote peer. 94 | pub fn new( 95 | config_: &RTCConfiguration, 96 | observer_: T, 97 | ) -> Result, RTCError> { 98 | let observer = HeapPointer::new(); 99 | let config = HeapPointer::new(); 100 | let raw = unsafe { 101 | rtc_create_peer_connection( 102 | config.set(config_.get_raw()), 103 | &EVENTS, 104 | observer.set(ObserverRef::new(observer_)), 105 | ) 106 | }; 107 | 108 | if raw.is_null() { 109 | Err(RTCError::CreateRTCFailed) 110 | } else { 111 | Ok(Arc::new(Self { 112 | tracks: Mutex::new(Vec::with_capacity(10)), 113 | observer, 114 | config, 115 | raw, 116 | })) 117 | } 118 | } 119 | 120 | /// The create_offer() method of the RTCPeerConnection interface initiates 121 | /// the creation of an SDP offer for the purpose of starting a new WebRTC 122 | /// connection to a remote peer. The SDP offer includes information about 123 | /// any MediaStreamTrack objects already attached to the WebRTC session, 124 | /// codec, and options supported by the browser, and any candidates already 125 | /// gathered by the ICE agent, for the purpose of being sent over the 126 | /// signaling channel to a potential peer to request a connection or to 127 | /// update the configuration of an existing connection. 128 | pub fn create_offer(&self) -> CreateDescriptionFuture { 129 | CreateDescriptionFuture::create(self.raw, CreateDescriptionKind::Offer) 130 | } 131 | 132 | /// The create_answer() method on the RTCPeerConnection interface creates an 133 | /// SDP answer to an offer received from a remote peer during the 134 | /// offer/answer negotiation of a WebRTC connection. The answer contains 135 | /// information about any media already attached to the session, codecs and 136 | /// options supported by the browser, and any ICE candidates already 137 | /// gathered. The answer is delivered to the returned Future, and should 138 | /// then be sent to the source of the offer to continue the negotiation 139 | /// process. 140 | pub fn create_answer(&self) -> CreateDescriptionFuture { 141 | CreateDescriptionFuture::create(self.raw, CreateDescriptionKind::Answer) 142 | } 143 | 144 | /// The RTCPeerConnection method setLocalDescription() changes the local 145 | /// description associated with the connection. This description specifies 146 | /// the properties of the local end of the connection, including the media 147 | /// format. 148 | pub fn set_local_description<'b>( 149 | &'b self, 150 | desc: &'b RTCSessionDescription, 151 | ) -> SetDescriptionFuture<'b> { 152 | SetDescriptionFuture::create(self.raw, desc, SetDescriptionKind::Local) 153 | } 154 | 155 | /// The RTCPeerConnection method setRemoteDescription() sets the specified 156 | /// session description as the remote peer's current offer or answer. The 157 | /// description specifies the properties of the remote end of the 158 | /// connection, including the media format. 159 | pub fn set_remote_description<'b>( 160 | &'b self, 161 | desc: &'b RTCSessionDescription, 162 | ) -> SetDescriptionFuture<'b> { 163 | SetDescriptionFuture::create(self.raw, desc, SetDescriptionKind::Remote) 164 | } 165 | 166 | /// When a web site or app using RTCPeerConnection receives a new ICE 167 | /// candidate from the remote peer over its signaling channel, it 168 | /// delivers the newly-received candidate to the browser's ICE agent by 169 | /// calling RTCPeerConnection.addIceCandidate(). This adds this new 170 | /// remote candidate to the RTCPeerConnection's remote description, 171 | /// which describes the state of the remote end of the connection. 172 | /// 173 | /// If the candidate parameter is missing or a value of null is given when 174 | /// calling addIceCandidate(), the added ICE candidate is an 175 | /// "end-of-candidates" indicator. The same is the case if the value of 176 | /// the specified object's candidate is either missing or an empty 177 | /// string (""), it signals that all remote candidates have been 178 | /// delivered. 179 | /// 180 | /// The end-of-candidates notification is transmitted to the remote peer 181 | /// using a candidate with an a-line value of end-of-candidates. 182 | /// 183 | /// During negotiation, your app will likely receive many candidates which 184 | /// you'll deliver to the ICE agent in this way, allowing it to build up 185 | /// a list of potential connection methods. This is covered in more 186 | /// detail in the articles WebRTC connectivity and Signaling and video 187 | /// calling. 188 | pub fn add_ice_candidate<'b>(&'b self, candidate: &'b RTCIceCandidate) -> Result<(), RTCError> { 189 | let raw: RawRTCIceCandidate = candidate.try_into().map_err(|e| RTCError::StringError(e))?; 190 | let ret = unsafe { rtc_add_ice_candidate(self.raw, &raw) }; 191 | if !ret { 192 | return Err(RTCError::AddIceCandidateFailed); 193 | } 194 | 195 | Ok(()) 196 | } 197 | 198 | /// The RTCPeerConnection method addTrack() adds a new media track to the 199 | /// set of tracks which will be transmitted to the other peer. 200 | pub fn add_track( 201 | &self, 202 | track: MediaStreamTrack, 203 | stream: Arc, 204 | ) -> Result<(), RTCError> { 205 | let ret = unsafe { rtc_add_media_stream_track(self.raw, track.get_raw(), stream.get_id()) }; 206 | if ret != 0 { 207 | return Err(RTCError::AddTrackFailed(ret)); 208 | } 209 | 210 | self.tracks.lock().unwrap().push((track, stream)); 211 | Ok(()) 212 | } 213 | 214 | /// The `remove_track` method tells the local end of the connection to stop 215 | /// sending media from the specified track, without actually removing 216 | /// the corresponding RTCRtpSender from the list of senders as reported 217 | /// by `senders`. If the track is already stopped, or is not in the 218 | /// connection's senders list, this method has no effect. 219 | /// 220 | /// If the connection has already been negotiated (signalingState is set to 221 | /// "stable"), it is marked as needing to be negotiated again; the remote 222 | /// peer won't experience the change until this negotiation occurs. A 223 | /// negotiationneeded event is sent to the RTCPeerConnection to let the 224 | /// local end know this negotiation must occur. 225 | pub fn remove_track(&self, track: MediaStreamTrack) -> Result<(), RTCError> { 226 | let ret = unsafe { rtc_remove_media_stream_track(self.raw, track.get_raw()) }; 227 | if ret != 0 { 228 | return Err(RTCError::RemoveTrackFailed(ret)); 229 | } 230 | 231 | Ok(()) 232 | } 233 | 234 | /// The createDataChannel() method on the RTCPeerConnection interface 235 | /// creates a new channel linked with the remote peer, over which any kind 236 | /// of data may be transmitted. 237 | pub fn create_data_channel(&self, label: &str, opt: &DataChannelOptions) -> RTCDataChannel { 238 | let c_label = to_c_str(label).unwrap(); 239 | let opt: RawDataChannelOptions = opt.into(); 240 | let raw = unsafe { rtc_create_data_channel(self.raw, c_label, &opt) }; 241 | free_cstring(c_label); 242 | DataChannel::from_raw(raw) 243 | } 244 | } 245 | 246 | impl Drop for RTCPeerConnection { 247 | fn drop(&mut self) { 248 | unsafe { rtc_close(self.raw) } 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/rtc_peerconnection_configure.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{c_char, c_int}; 2 | 3 | use crate::auto_ptr::ArrayExt; 4 | use crate::cstr::{free_cstring, to_c_str}; 5 | 6 | /// How to handle negotiation of candidates when remote peer is not compatible 7 | /// with standard SDP BUNDLE. 8 | /// 9 | /// Specifies how to handle negotiation of candidates when the remote peer is 10 | /// not compatible with the SDP BUNDLE standard. If the remote endpoint is 11 | /// BUNDLE-aware, all media tracks and data channels are bundled onto a single 12 | /// transport at the completion of negotiation, regardless of policy used, and 13 | /// any superfluous transports that were created initially are closed at that 14 | /// point. 15 | /// 16 | /// In technical terms, a BUNDLE lets all media flow between two peers flow 17 | /// across a single 5-tuple; that is, from a single IP and port on one peer to a 18 | /// single IP and port on the other peer, using the same transport protocol. 19 | #[repr(i32)] 20 | #[derive(Clone, Copy, Debug)] 21 | pub enum BundlePolicy { 22 | /// The ICE agent initially creates one RTCDtlsTransport for each type of 23 | /// content added: audio, video, and data channels. If the remote endpoint 24 | /// is not BUNDLE-aware, then each of these DTLS transports handles all 25 | /// the communication for one type of data. 26 | Balanced = 1, 27 | /// The ICE agent initially creates one RTCDtlsTransport per media track 28 | /// and a separate one for data channels. If the remote endpoint is not 29 | /// BUNDLE-aware, everything is negotiated on these separate DTLS 30 | /// transports. 31 | MaxCompat, 32 | /// The ICE agent initially creates only a single RTCDtlsTransport to carry 33 | /// all of the RTCPeerConnection's data. If the remote endpoint is not 34 | /// BUNDLE-aware, then only a single track will be negotiated and the 35 | /// rest ignored. 36 | MaxBundle, 37 | } 38 | 39 | /// The current ICE transport policy; if the policy isn't specified, all is 40 | /// assumed by default, allowing all candidates to be considered. 41 | #[repr(i32)] 42 | #[derive(Clone, Copy, Debug)] 43 | pub enum IceTransportPolicy { 44 | None = 1, 45 | /// Only ICE candidates whose IP addresses are being relayed, such as those 46 | /// being passed through a STUN or TURN server, will be considered. 47 | Relay, 48 | /// Only ICE candidates with public IP addresses will be considered. 49 | Public, 50 | /// All ICE candidates will be considered. 51 | All, 52 | } 53 | 54 | /// The RTCP mux policy to use when gathering ICE candidates, 55 | /// in order to support non-multiplexed RTCP. 56 | #[repr(i32)] 57 | #[derive(Clone, Copy, Debug)] 58 | pub enum RtcpMuxPolicy { 59 | /// Instructs the ICE agent to gather both RTP and RTCP candidates. 60 | /// If the remote peer can multiplex RTCP, 61 | /// then RTCP candidates are multiplexed atop the corresponding RTP 62 | /// candidates. Otherwise, both the RTP and RTCP candidates are 63 | /// returned, separately. 64 | Negotiate = 1, 65 | /// Tells the ICE agent to gather ICE candidates for only RTP, 66 | /// and to multiplex RTCP atop them. If the remote peer doesn't support 67 | /// RTCP multiplexing, then session negotiation fails. 68 | Require, 69 | } 70 | 71 | #[repr(C)] 72 | #[derive(Debug)] 73 | pub(crate) struct RawRTCIceServer { 74 | credential: *const c_char, 75 | urls: *const *const c_char, 76 | urls_size: c_int, 77 | urls_capacity: c_int, 78 | username: *const c_char, 79 | } 80 | 81 | impl Drop for RawRTCIceServer { 82 | fn drop(&mut self) { 83 | free_cstring(self.credential.cast_mut()); 84 | free_cstring(self.username.cast_mut()); 85 | unsafe { 86 | if !self.urls.is_null() { 87 | for url in Vec::from_raw_parts( 88 | self.urls.cast_mut(), 89 | self.urls_size as usize, 90 | self.urls_capacity as usize, 91 | ) { 92 | free_cstring(url.cast_mut()); 93 | } 94 | } 95 | } 96 | } 97 | } 98 | 99 | #[repr(C)] 100 | #[derive(Debug)] 101 | pub(crate) struct RawRTCPeerConnectionConfigure { 102 | bundle_policy: c_int, // BundlePolicy 103 | ice_transport_policy: c_int, // IceTransportPolicy 104 | peer_identity: *const c_char, 105 | rtcp_mux_policy: c_int, // RtcpMuxPolicy 106 | ice_servers: *const RawRTCIceServer, 107 | ice_servers_size: c_int, 108 | ice_servers_capacity: c_int, 109 | ice_candidate_pool_size: c_int, 110 | } 111 | 112 | impl Drop for RawRTCPeerConnectionConfigure { 113 | fn drop(&mut self) { 114 | unsafe { 115 | free_cstring(self.peer_identity.cast_mut()); 116 | if !self.ice_servers.is_null() { 117 | let _ = Vec::from_raw_parts( 118 | self.ice_servers.cast_mut(), 119 | self.ice_servers_size as usize, 120 | self.ice_servers_capacity as usize, 121 | ); 122 | } 123 | } 124 | } 125 | } 126 | 127 | /// The RTCIceServer dictionary defines how to connect to a single ICE 128 | /// server (such as a STUN or TURN server). 129 | /// 130 | /// An array of RTCIceServer objects, each describing one server which may be 131 | /// used by the ICE agent; these are typically STUN and/or TURN servers. 132 | /// If this isn't specified, the connection attempt will be made with no STUN or 133 | /// TURN server available, which limits the connection to local peers. 134 | #[derive(Default, Clone, Debug)] 135 | pub struct RTCIceServer { 136 | /// The credential to use when logging into the server. 137 | /// This is only used if the RTCIceServer represents a TURN server. 138 | pub credential: Option, 139 | /// If the RTCIceServer is a TURN server, then this is the username to use 140 | /// during the authentication process. 141 | pub username: Option, 142 | /// This required property is either a single string or an array of 143 | /// strings, each specifying a URL which can be used to connect to the 144 | /// server. 145 | pub urls: Option>, 146 | } 147 | 148 | impl Into for &RTCIceServer { 149 | fn into(self) -> RawRTCIceServer { 150 | let (urls, urls_size, urls_capacity) = self 151 | .urls 152 | .as_ref() 153 | .map(|v| { 154 | v.iter() 155 | .map(|s| to_c_str(s).unwrap()) 156 | .collect::>() 157 | .into_c_layout() 158 | }) 159 | .unwrap_or((std::ptr::null_mut(), 0, 0)); 160 | RawRTCIceServer { 161 | credential: self 162 | .credential 163 | .as_ref() 164 | .map(|s| to_c_str(s).unwrap()) 165 | .unwrap_or(std::ptr::null_mut()), 166 | username: self 167 | .username 168 | .as_ref() 169 | .map(|s| to_c_str(s).unwrap()) 170 | .unwrap_or(std::ptr::null_mut()), 171 | urls_capacity: urls_capacity as c_int, 172 | urls_size: urls_size as c_int, 173 | urls, 174 | } 175 | } 176 | } 177 | 178 | /// RTCPeerConnection Configuration. 179 | /// 180 | /// The RTCPeerConnection is a newly-created RTCPeerConnection, 181 | /// which represents a connection between the local device and a remote peer. 182 | #[derive(Default, Debug)] 183 | pub struct RTCConfiguration { 184 | /// Specifies how to handle negotiation of candidates when the remote peer 185 | /// is not compatible with the SDP BUNDLE standard. If the remote endpoint 186 | /// is BUNDLE-aware, all media tracks and data channels are bundled onto a 187 | /// single transport at the completion of negotiation, regardless of policy 188 | /// used, and any superfluous transports that were created initially are 189 | /// closed at that point. 190 | /// 191 | /// In technical terms, a BUNDLE lets all media flow between two peers flow 192 | /// across a single 5-tuple; 193 | /// that is, from a single IP and port on one peer to a single IP and port 194 | /// on the other peer, using the same transport protocol. 195 | pub bundle_policy: Option, 196 | /// The current ICE transport policy; if the policy isn't specified, all is 197 | /// assumed by default, allowing all candidates to be considered 198 | pub ice_transport_policy: Option, 199 | /// A string which specifies the target peer identity for the 200 | /// RTCPeerConnection. 201 | /// If this value is set (it defaults to null), the RTCPeerConnection will 202 | /// not connect to a remote peer unless it can successfully authenticate 203 | /// with the given name. 204 | pub peer_identity: Option, 205 | /// The RTCP mux policy to use when gathering ICE candidates, in order to 206 | /// support non-multiplexed RTCP. 207 | pub rtcp_mux_policy: Option, 208 | /// An array of RTCIceServer objects, each describing one server which may 209 | /// be used by the ICE agent; these are typically STUN and/or TURN servers. 210 | /// If this isn't specified, the connection attempt will be made with no 211 | /// STUN or TURN server available, which limits the connection to local 212 | /// peers. 213 | pub ice_servers: Option>, 214 | /// An unsigned 16-bit integer value which specifies the size of the 215 | /// prefetched ICE candidate pool. 216 | /// The default value is 0 (meaning no candidate prefetching will occur). 217 | /// You may find in some cases that connections can be established more 218 | /// quickly by allowing the ICE agent to start fetching ICE candidates 219 | /// before you start trying to connect, so that they're already available 220 | /// for inspection when RTCPeerConnection.setLocalDescription() is called. 221 | pub ice_candidate_pool_size: Option, 222 | } 223 | 224 | unsafe impl Send for RTCConfiguration {} 225 | unsafe impl Sync for RTCConfiguration {} 226 | 227 | impl Into for &RTCConfiguration { 228 | fn into(self) -> RawRTCPeerConnectionConfigure { 229 | let (ice_servers, ice_servers_size, ice_servers_capacity) = self 230 | .ice_servers 231 | .as_ref() 232 | .map(|i| { 233 | i.iter() 234 | .map(|s| s.into()) 235 | .collect::>() 236 | .into_c_layout() 237 | }) 238 | .unwrap_or((std::ptr::null_mut(), 0, 0)); 239 | RawRTCPeerConnectionConfigure { 240 | bundle_policy: self.bundle_policy.map(|i| i as c_int).unwrap_or(0), 241 | ice_transport_policy: self.ice_transport_policy.map(|i| i as c_int).unwrap_or(0), 242 | peer_identity: self 243 | .peer_identity 244 | .as_ref() 245 | .map(|s| to_c_str(s).unwrap()) 246 | .unwrap_or(std::ptr::null_mut()), 247 | rtcp_mux_policy: self.rtcp_mux_policy.map(|i| i as c_int).unwrap_or(0), 248 | ice_candidate_pool_size: self.ice_candidate_pool_size.unwrap_or(0) as c_int, 249 | ice_servers_capacity: ice_servers_capacity as c_int, 250 | ice_servers_size: ice_servers_size as c_int, 251 | ice_servers, 252 | } 253 | } 254 | } 255 | 256 | impl RTCConfiguration { 257 | pub(crate) fn get_raw(&self) -> RawRTCPeerConnectionConfigure { 258 | self.into() 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /src/rtc_session_description.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | convert::{TryFrom, TryInto}, 3 | ffi::c_char, 4 | }; 5 | 6 | use serde::{Deserialize, Serialize}; 7 | 8 | use crate::cstr::{free_cstring, from_c_str, to_c_str, StringError}; 9 | 10 | /// An enum describing the session description's type. 11 | #[repr(i32)] 12 | #[derive(Clone, Copy, Debug, Serialize, Deserialize)] 13 | #[serde(rename_all = "lowercase")] 14 | pub enum RTCSessionDescriptionType { 15 | /// The session description object describes the initial proposal in an 16 | /// offer/answer exchange. The session negotiation process begins with an 17 | /// offer being sent from the caller to the callee. 18 | Offer, 19 | /// Description must be treated as an SDP answer, but not a final answer. 20 | PrAnswer, 21 | /// The SDP contained in the sdp property is the definitive choice in the 22 | /// exchange. In other words, this session description describes the 23 | /// agreed-upon configuration, and is being sent to finalize 24 | /// negotiation. 25 | Answer, 26 | /// This special type with an empty session description is used to 27 | /// roll back to the previous stable state. 28 | Rollback, 29 | } 30 | 31 | impl Default for RTCSessionDescriptionType { 32 | fn default() -> Self { 33 | Self::Offer 34 | } 35 | } 36 | 37 | #[repr(C)] 38 | #[derive(Debug)] 39 | pub(crate) struct RawRTCSessionDescription { 40 | r#type: RTCSessionDescriptionType, 41 | sdp: *const c_char, 42 | } 43 | 44 | impl Drop for RawRTCSessionDescription { 45 | fn drop(&mut self) { 46 | free_cstring(self.sdp as *mut c_char); 47 | } 48 | } 49 | 50 | /// The RTCSessionDescription interface describes one end of a connection or 51 | /// potential connection and how it's configured. 52 | /// 53 | /// Each RTCSessionDescription consists of a description type indicating which 54 | /// part of the offer/answer negotiation process it describes and of the SDP 55 | /// descriptor of the session. 56 | #[derive(Clone, Debug, Serialize, Deserialize)] 57 | pub struct RTCSessionDescription { 58 | #[serde(rename = "type")] 59 | pub kind: RTCSessionDescriptionType, 60 | /// A string containing the SDP describing the session. 61 | pub sdp: String, 62 | } 63 | 64 | unsafe impl Send for RTCSessionDescription {} 65 | unsafe impl Sync for RTCSessionDescription {} 66 | 67 | impl TryInto for &RTCSessionDescription { 68 | type Error = StringError; 69 | 70 | fn try_into(self) -> Result { 71 | Ok(RawRTCSessionDescription { 72 | sdp: to_c_str(&self.sdp)?, 73 | r#type: self.kind, 74 | }) 75 | } 76 | } 77 | 78 | impl TryFrom<&RawRTCSessionDescription> for RTCSessionDescription { 79 | type Error = StringError; 80 | 81 | fn try_from(value: &RawRTCSessionDescription) -> Result { 82 | Ok(RTCSessionDescription { 83 | sdp: from_c_str(value.sdp)?, 84 | kind: value.r#type, 85 | }) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/set_description_observer.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | error::Error, 3 | ffi::{c_char, c_void}, 4 | fmt, 5 | sync::{ 6 | atomic::{AtomicPtr, Ordering}, 7 | Arc, 8 | }, 9 | }; 10 | 11 | use futures::task::AtomicWaker; 12 | 13 | use crate::{ 14 | cstr::{from_c_str, StringError}, 15 | rtc_peerconnection::RawRTCPeerConnection, 16 | rtc_session_description::RawRTCSessionDescription, 17 | Promisify, PromisifyExt, RTCSessionDescription, 18 | }; 19 | 20 | extern "C" { 21 | pub(crate) fn rtc_set_local_description( 22 | peer: *const crate::rtc_peerconnection::RawRTCPeerConnection, 23 | desc: *const crate::rtc_session_description::RawRTCSessionDescription, 24 | callback: extern "C" fn(*const c_char, *mut c_void), 25 | ctx: *mut c_void, 26 | ); 27 | 28 | pub(crate) fn rtc_set_remote_description( 29 | peer: *const crate::rtc_peerconnection::RawRTCPeerConnection, 30 | desc: *const crate::rtc_session_description::RawRTCSessionDescription, 31 | callback: extern "C" fn(*const c_char, *mut c_void), 32 | ctx: *mut c_void, 33 | ); 34 | } 35 | 36 | #[derive(Debug)] 37 | pub enum SetDescriptionError { 38 | StringError(StringError), 39 | SetFailed(String), 40 | } 41 | 42 | impl Error for SetDescriptionError {} 43 | 44 | impl fmt::Display for SetDescriptionError { 45 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 46 | write!(f, "{}", self) 47 | } 48 | } 49 | 50 | #[derive(PartialEq, Eq, PartialOrd)] 51 | pub(crate) enum SetDescriptionKind { 52 | Local, 53 | Remote, 54 | } 55 | 56 | struct SetDescriptionContext { 57 | callback: Box)>, 58 | } 59 | 60 | #[no_mangle] 61 | extern "C" fn set_description_callback(error: *const c_char, ctx: *mut c_void) { 62 | let mut ctx = unsafe { Box::from_raw(ctx as *mut SetDescriptionContext) }; 63 | (ctx.callback)( 64 | unsafe { error.as_ref() } 65 | .map(|_| { 66 | from_c_str(error) 67 | .map_err(|e| SetDescriptionError::StringError(e)) 68 | .and_then(|s| Err(SetDescriptionError::SetFailed(s))) 69 | }) 70 | .unwrap_or_else(|| Ok(())), 71 | ); 72 | } 73 | 74 | pub struct SetDescriptionObserver<'a> { 75 | kind: SetDescriptionKind, 76 | desc: &'a RTCSessionDescription, 77 | pc: *const RawRTCPeerConnection, 78 | ret: Arc>>, 79 | } 80 | 81 | unsafe impl Send for SetDescriptionObserver<'_> {} 82 | unsafe impl Sync for SetDescriptionObserver<'_> {} 83 | 84 | impl<'a> PromisifyExt for SetDescriptionObserver<'a> { 85 | type Err = SetDescriptionError; 86 | type Output = (); 87 | 88 | fn handle(&self, waker: Arc) -> Result<(), Self::Err> { 89 | let ret = self.ret.clone(); 90 | let ctx = Box::into_raw(Box::new(SetDescriptionContext { 91 | callback: Box::new(move |res| { 92 | ret.store(Box::into_raw(Box::new(res)), Ordering::Relaxed); 93 | waker.wake(); 94 | }), 95 | })) as *mut c_void; 96 | 97 | let desc: RawRTCSessionDescription = self 98 | .desc 99 | .try_into() 100 | .map_err(|e| SetDescriptionError::StringError(e))?; 101 | if self.kind == SetDescriptionKind::Local { 102 | unsafe { rtc_set_local_description(self.pc, &desc, set_description_callback, ctx) }; 103 | } else { 104 | unsafe { rtc_set_remote_description(self.pc, &desc, set_description_callback, ctx) }; 105 | } 106 | 107 | Ok(()) 108 | } 109 | 110 | fn wake(&self) -> Option> { 111 | unsafe { 112 | self.ret 113 | .swap(std::ptr::null_mut(), Ordering::Relaxed) 114 | .as_mut() 115 | } 116 | .map(|ptr| unsafe { *Box::from_raw(ptr) }) 117 | } 118 | } 119 | 120 | pub type SetDescriptionFuture<'a> = Promisify>; 121 | impl<'a> SetDescriptionFuture<'a> { 122 | pub(crate) fn create( 123 | pc: *const RawRTCPeerConnection, 124 | desc: &'a RTCSessionDescription, 125 | kind: SetDescriptionKind, 126 | ) -> Self { 127 | Promisify::new(SetDescriptionObserver { 128 | ret: Arc::new(AtomicPtr::new(std::ptr::null_mut())), 129 | desc, 130 | kind, 131 | pc, 132 | }) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/sink.rs: -------------------------------------------------------------------------------- 1 | /// A Sink is a value into which other values can be sent. 2 | pub trait SinkExt: Send { 3 | type Item; 4 | 5 | /// on data for sink push. 6 | fn on_data(&self, item: Self::Item); 7 | } 8 | 9 | /// A sink trait type wrapper. 10 | pub struct Sinker { 11 | pub(crate) sink: Box>, 12 | } 13 | 14 | impl Sinker { 15 | /// create a sink trait wrapper from T. 16 | pub fn new + 'static>(sink: S) -> Self { 17 | Self { 18 | sink: Box::new(sink), 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/video_frame.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::c_void, slice::from_raw_parts, sync::Arc}; 2 | 3 | use crate::media_stream_track::rtc_free_frame; 4 | 5 | #[repr(C)] 6 | #[derive(Debug)] 7 | pub(crate) struct RawVideoFrame { 8 | remote: bool, 9 | width: u32, 10 | height: u32, 11 | timestamp: i64, 12 | planes: [*const u8; 4], 13 | strides: [u32; 4], 14 | } 15 | 16 | /// VideoFrame represents the frame of the video, 17 | /// and the format is i420 (yu12). 18 | /// 19 | /// Also known as Planar YUV 4:2:0, this format is composed of 20 | /// three distinct planes, one plane of luma and two planes of 21 | /// chroma, denoted Y, U and V, and present in this order. 22 | /// The U an V planes are sub-sampled horizontally and vertically 23 | /// by a factor of 2 compared to the Y plane. Each sample in this 24 | /// format is 8 bits. 25 | #[derive(Debug)] 26 | pub struct VideoFrame { 27 | raw: *const RawVideoFrame, 28 | } 29 | 30 | unsafe impl Send for VideoFrame {} 31 | unsafe impl Sync for VideoFrame {} 32 | 33 | impl VideoFrame { 34 | pub(crate) fn get_raw(&self) -> *const RawVideoFrame { 35 | self.raw 36 | } 37 | 38 | /// create video frame from raw video frame type. 39 | pub(crate) fn from_raw(raw: *const RawVideoFrame) -> Arc { 40 | assert!(!raw.is_null()); 41 | Arc::new(Self { raw }) 42 | } 43 | 44 | /// Create i420 frame structure from memory buffer. 45 | /// 46 | /// The created frame is memory-safe and thread-safe, and can be 47 | /// transferred and copied in threads. 48 | pub fn new( 49 | width: u32, 50 | height: u32, 51 | timestamp: usize, 52 | planes: [&[u8]; 4], 53 | strides: [u32; 4], 54 | ) -> Self { 55 | Self { 56 | raw: Box::into_raw(Box::new(RawVideoFrame { 57 | planes: planes.map(|item| item.as_ptr()), 58 | timestamp: timestamp as i64, 59 | remote: false, 60 | strides, 61 | width, 62 | height, 63 | })), 64 | } 65 | } 66 | 67 | /// get video frame width 68 | pub fn width(&self) -> u32 { 69 | unsafe { &*self.raw }.width 70 | } 71 | 72 | /// get video frame height 73 | pub fn height(&self) -> u32 { 74 | unsafe { &*self.raw }.height 75 | } 76 | 77 | /// get i420 frame y buffer 78 | pub fn data_y(&self) -> &[u8] { 79 | let raw = unsafe { &*self.raw }; 80 | let size = (raw.strides[0] * raw.height) as usize; 81 | unsafe { from_raw_parts(raw.planes[0], size) } 82 | } 83 | 84 | /// get i420 frame y stride 85 | pub fn stride_y(&self) -> usize { 86 | let raw = unsafe { &*self.raw }; 87 | raw.strides[0] as usize 88 | } 89 | 90 | /// get i420 frame u buffer 91 | pub fn data_u(&self) -> &[u8] { 92 | let raw = unsafe { &*self.raw }; 93 | let size = (raw.strides[1] * (raw.height / 2)) as usize; 94 | unsafe { from_raw_parts(raw.planes[1], size) } 95 | } 96 | 97 | /// get i420 frame u stride 98 | pub fn stride_u(&self) -> usize { 99 | let raw = unsafe { &*self.raw }; 100 | raw.strides[1] as usize 101 | } 102 | 103 | /// get i420 frame v buffer 104 | pub fn data_v(&self) -> &[u8] { 105 | let raw = unsafe { &*self.raw }; 106 | let size = (raw.strides[2] * (raw.height / 2)) as usize; 107 | unsafe { from_raw_parts(raw.planes[2], size) } 108 | } 109 | 110 | /// get i420 frame v stride 111 | pub fn stride_v(&self) -> usize { 112 | let raw = unsafe { &*self.raw }; 113 | raw.strides[2] as usize 114 | } 115 | } 116 | 117 | impl Drop for VideoFrame { 118 | fn drop(&mut self) { 119 | let raw = unsafe { &*self.raw }; 120 | unsafe { 121 | // If remote is false, it means the distribution is 122 | // on the rust box. 123 | if raw.remote { 124 | rtc_free_frame(self.raw as *const c_void) 125 | } else { 126 | let _ = Box::from_raw(self.raw.cast_mut()); 127 | }; 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/video_track.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | ffi::c_char, 4 | sync::{Arc, RwLock}, 5 | }; 6 | 7 | use crate::{ 8 | cstr::{c_str_to_str, free_cstring, to_c_str}, 9 | media_stream::MediaStreamError, 10 | media_stream_track::{ 11 | rtc_free_media_stream_track, rtc_remove_media_stream_track_frame_h, RawMediaStreamTrack, 12 | }, 13 | video_frame::RawVideoFrame, 14 | Sinker, VideoFrame, 15 | }; 16 | 17 | #[allow(improper_ctypes)] 18 | extern "C" { 19 | pub(crate) fn rtc_create_video_track( 20 | label: *const c_char, 21 | ) -> *const crate::media_stream_track::RawMediaStreamTrack; 22 | 23 | pub(crate) fn rtc_add_video_track_frame( 24 | track: *const crate::media_stream_track::RawMediaStreamTrack, 25 | frame: *const crate::video_frame::RawVideoFrame, 26 | ); 27 | 28 | pub(crate) fn rtc_set_video_track_frame_h( 29 | track: *const crate::media_stream_track::RawMediaStreamTrack, 30 | handler: extern "C" fn( 31 | &crate::video_track::VideoTrack, 32 | *const crate::video_frame::RawVideoFrame, 33 | ), 34 | ctx: &crate::video_track::VideoTrack, 35 | ); 36 | } 37 | 38 | /// The VideoTrack interface represents a single video track from 39 | /// a MediaStreamTrack. 40 | pub struct VideoTrack { 41 | pub(crate) raw: *const RawMediaStreamTrack, 42 | sinks: RwLock>>>, 43 | } 44 | 45 | unsafe impl Send for VideoTrack {} 46 | unsafe impl Sync for VideoTrack {} 47 | 48 | impl VideoTrack { 49 | pub fn label(&self) -> &str { 50 | c_str_to_str(unsafe { (*self.raw).label }).expect("get video track label string to failed") 51 | } 52 | 53 | /// Create a new video track, may fail to create, such as 54 | /// insufficient memory. 55 | pub fn new(label: &str) -> Result, MediaStreamError> { 56 | let raw = unsafe { 57 | let c_label = to_c_str(label).map_err(|e| MediaStreamError::StringError(e))?; 58 | let ptr = rtc_create_video_track(c_label); 59 | free_cstring(c_label); 60 | ptr 61 | }; 62 | 63 | if raw.is_null() { 64 | Err(MediaStreamError::CreateTrackFailed) 65 | } else { 66 | Ok(Self::from_raw(raw)) 67 | } 68 | } 69 | 70 | /// Push video frames to the current track, currently only 71 | /// supports pushing video frames in i420 format. 72 | /// 73 | /// Only valid for local video streams. 74 | pub fn add_frame(&self, frame: &VideoFrame) { 75 | unsafe { 76 | rtc_add_video_track_frame(self.raw, frame.get_raw()); 77 | } 78 | } 79 | 80 | /// Register video track frame sink, one track can register multiple sinks. 81 | /// The sink id cannot be repeated, otherwise the sink implementation will 82 | /// be overwritten. 83 | pub fn register_sink(&self, id: u8, sink: Sinker>) { 84 | let mut sinks = self.sinks.write().unwrap(); 85 | 86 | // Register for the first time, register the callback function to 87 | // webrtc native, and then do not need to register again. 88 | if sinks.is_empty() { 89 | unsafe { rtc_set_video_track_frame_h(self.raw, on_video_frame, self) } 90 | } 91 | 92 | sinks.insert(id, sink); 93 | } 94 | 95 | /// Delete the registered sink, if it exists, it will return the deleted 96 | /// sink. 97 | pub fn remove_sink(&self, id: u8) -> Option>> { 98 | let mut sinks = self.sinks.write().unwrap(); 99 | let value = sinks.remove(&id); 100 | if sinks.is_empty() { 101 | unsafe { rtc_remove_media_stream_track_frame_h(self.raw) } 102 | } 103 | 104 | value 105 | } 106 | 107 | /// create video track from raw type ptr. 108 | pub(crate) fn from_raw(raw: *const RawMediaStreamTrack) -> Arc { 109 | assert!(!raw.is_null()); 110 | Arc::new(Self { 111 | sinks: RwLock::new(HashMap::new()), 112 | raw, 113 | }) 114 | } 115 | 116 | fn on_data(this: &Self, frame: Arc) { 117 | for sinker in this.sinks.read().unwrap().values() { 118 | sinker.sink.on_data(frame.clone()); 119 | } 120 | } 121 | } 122 | 123 | impl Drop for VideoTrack { 124 | fn drop(&mut self) { 125 | unsafe { rtc_remove_media_stream_track_frame_h(self.raw) } 126 | unsafe { rtc_free_media_stream_track(self.raw) } 127 | } 128 | } 129 | 130 | #[no_mangle] 131 | extern "C" fn on_video_frame(ctx: &VideoTrack, frame: *const RawVideoFrame) { 132 | assert!(!frame.is_null()); 133 | let frame = VideoFrame::from_raw(frame); 134 | VideoTrack::on_data(ctx, frame); 135 | } 136 | --------------------------------------------------------------------------------