Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
747 changes: 598 additions & 149 deletions examples/voip-cli/src/main.rs

Large diffs are not rendered by default.

1,152 changes: 1,152 additions & 0 deletions examples/voip-cli/src/video.rs

Large diffs are not rendered by default.

360 changes: 357 additions & 3 deletions src/handlers/call.rs

Large diffs are not rendered by default.

897 changes: 886 additions & 11 deletions src/voip/facade.rs

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions src/voip/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@ pub mod facade;
pub mod registry;
pub mod session;
pub mod transport;
pub mod video;

pub use audio::{AudioSink, AudioSource};
pub use facade::{AcceptCall, CallHandle, OutgoingCall};
pub use video::{VideoFrame, VideoSink, VideoSource};
// CallHandle::events() yields these, so surface them next to CallHandle (they live in wacore).
pub use wacore::voip::CallEvent;
// `CallEvent::VideoStateChanged` carries this; surface it next to CallEvent (it lives in wacore).
pub use wacore::types::call::VideoState;
6 changes: 5 additions & 1 deletion src/voip/transport.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Relay media transport: a pre-negotiated WebRTC DataChannel over SCTP-over-DTLS-over-UDP
//! to a single WhatsApp relay endpoint. The synthetic-SDP / wrtc dance reduces, at this layer,
//! to: connect a UDP socket to the relay, DTLS-handshake as the client (self-signed cert,
//! server-cert verification skipped, since SRTP keys come from callKey/hbh_key, not DTLS),
//! server-cert verification skipped, since SRTP keys come from callKey, not DTLS),
//! run an SCTP association over it, and open the pre-negotiated id=0 DataChannel that carries
//! STUN/RTP/RTCP as binary messages.

Expand Down Expand Up @@ -573,6 +573,7 @@ mod udp_relay_e2e {
integrity_key: b"relay-key".to_vec(),
warp_mi_tag_len: 4,
enable_media: true,
enable_video: false,
enable_sframe: false,
}
}
Expand Down Expand Up @@ -737,6 +738,9 @@ mod udp_relay_e2e {
speaker: spk_tx,
events: ev_tx,
rekey: None,
video_in: async_channel::bounded(1).1,
video_out: async_channel::bounded(1).0,
video_ctl: async_channel::bounded(1).1,
},
eng,
));
Expand Down
42 changes: 42 additions & 0 deletions src/voip/video.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//! Video endpoints for WhatsApp calls. The library transports pre-encoded H.264 — it never touches
//! pixels — so a source hands us complete Annex-B access units (start codes included) and a sink
//! receives reassembled peer AUs. The codec lives with the consumer (ffmpeg, WebCodecs, a hardware
//! encoder). WhatsApp uses H.264 Constrained Baseline (avc1.42E01F), repeated SPS/PPS, and adapts
//! from a low-bandwidth 15 fps mode up to 1280x720 @ 20 fps / ~2 Mbps. A bare channel keeps the
//! compatibility cadence of 15 fps; custom sources report their RTP stride explicitly.

pub use wacore::voip::VideoFrame;

/// A video source for a call: one complete H.264 Annex-B access unit per item. Channel-factory
/// shaped for the same reasons as [`AudioSource`](crate::voip::audio::AudioSource); a closed
/// channel (encoder gone) does NOT end the call — audio keeps running.
pub trait VideoSource: Send + Sync + 'static {
/// The channel the facade reads encoded AUs from. Called once when video starts.
fn frames(&self) -> async_channel::Receiver<Vec<u8>>;

/// RTP clock increment between access units. It must match the source's pacing
/// (`90_000 / frames_per_second`) and remain non-zero.
fn rtp_timestamp_stride(&self) -> u32 {
wacore::voip::rtp::VIDEO_TS_STRIDE_15FPS
}
}

/// A video sink for a call: reassembled peer access units, with keyframe/orientation metadata.
/// VoIP is loss tolerant, so the facade drops a frame if the sink can't keep up.
pub trait VideoSink: Send + Sync + 'static {
/// The channel the facade writes received AUs to. Called once when video starts.
fn playout(&self) -> async_channel::Sender<VideoFrame>;
}

/// Blanket impls so a bare `async_channel` endpoint is usable directly, like the audio ones.
impl VideoSource for async_channel::Receiver<Vec<u8>> {
fn frames(&self) -> async_channel::Receiver<Vec<u8>> {
self.clone()
}
}

impl VideoSink for async_channel::Sender<VideoFrame> {
fn playout(&self) -> async_channel::Sender<VideoFrame> {
self.clone()
}
}
4 changes: 3 additions & 1 deletion wacore/benches/voip_benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ fn config_for(self_lid: &str, peer_lid: &str, ssrc: u32, direction: CallDirectio
integrity_key: b"relay-key".to_vec(),
warp_mi_tag_len: 4,
enable_media: true,
enable_video: false,
enable_sframe: false,
}
}
Expand All @@ -128,7 +129,7 @@ fn started_engine() -> CallEngine {

fn started_engine_from(cfg: CallConfig) -> CallEngine {
let mut eng = CallEngine::new(cfg, Box::new(SequentialTxIds::new())).unwrap();
eng.start(0);
eng.start(0, 0);
drain(&mut eng);
eng
}
Expand Down Expand Up @@ -393,6 +394,7 @@ mod framing {
timestamp: 0x0009_6000,
ssrc: 0xDEAD_BEEF,
extension_word: Some(0x3001_0000),
video_extension: None,
}
}

Expand Down
Loading
Loading