feat(video+desktop): camera capture, video UI, E2E AEAD wiring, test fixes

Blockers 4 & 5: browser getUserMedia → JPEG IPC → Rust I420 pipeline;
remote video strip renders decoded frames via canvas; EncryptingTransport
wraps QuinnTransport so WZP AEAD is applied to all media (C2 fix).

Test fixes: HandshakeResult.session destructuring across relay/client/crypto
integration tests; video_codecs field added to all CallOffer/CallAnswer
structs; wzp-video pipeline_roundtrip integration tests added.

PRD docs: five Kimi-ready specs for E2E encryption, Android NDK 0.9 migration,
quality upgrade flow, wire-format hardening, and clippy debt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-05-25 15:30:26 +04:00
parent 01f55caa96
commit 06253fdeeb
44 changed files with 3221 additions and 163 deletions

View File

@@ -114,11 +114,7 @@ impl EchoCanceller {
/// Number of delayed samples available to release.
fn delay_available(&self) -> usize {
let buffered = self.delay_write - self.delay_read;
if buffered > self.delay_samples {
buffered - self.delay_samples
} else {
0
}
buffered.saturating_sub(self.delay_samples)
}
/// Process a near-end (microphone) frame, removing the estimated echo.
@@ -161,8 +157,8 @@ impl EchoCanceller {
let mut sum_near_sq: f64 = 0.0;
let mut sum_err_sq: f64 = 0.0;
for i in 0..n {
let near_f = nearend[i] as f32;
for (i, sample) in nearend.iter_mut().enumerate() {
let near_f = *sample as f32;
// Position of far-end "now" for this near-end sample.
let base = (self.far_pos + fl * ((n / fl) + 2) + i - n) % fl;
@@ -190,7 +186,7 @@ impl EchoCanceller {
}
let out = error.clamp(-32768.0, 32767.0);
nearend[i] = out as i16;
*sample = out as i16;
sum_near_sq += (near_f as f64).powi(2);
sum_err_sq += (out as f64).powi(2);

View File

@@ -45,7 +45,7 @@ impl Codec2Decoder {
/// Number of compressed bytes per frame.
fn bytes_per_frame(&self) -> usize {
(self.inner.bits_per_frame() + 7) / 8
self.inner.bits_per_frame().div_ceil(8)
}
}

View File

@@ -45,7 +45,7 @@ impl Codec2Encoder {
/// Number of compressed bytes per frame.
fn bytes_per_frame(&self) -> usize {
(self.inner.bits_per_frame() + 7) / 8
self.inner.bits_per_frame().div_ceil(8)
}
}

View File

@@ -56,7 +56,7 @@ impl NoiseSupressor {
// f32 → i16 with clamping
for (i, &val) in output.iter().enumerate() {
let clamped = val.max(-32768.0).min(32767.0);
let clamped = val.clamp(-32768.0, 32767.0);
pcm[offset + i] = clamped as i16;
}
}

View File

@@ -101,7 +101,7 @@ pub fn dred_duration_for(codec: CodecId) -> u8 {
/// mode; unset or empty leaves DRED enabled.
fn read_legacy_fec_env() -> bool {
match std::env::var(LEGACY_FEC_ENV) {
Ok(v) => !v.is_empty() && v != "0" && v.to_ascii_lowercase() != "false",
Ok(v) => !v.is_empty() && v != "0" && !v.eq_ignore_ascii_case("false"),
Err(_) => false,
}
}
@@ -252,7 +252,7 @@ impl OpusEncoder {
let clamped = if self.legacy_fec_mode {
loss_pct.min(100)
} else {
loss_pct.max(DRED_LOSS_FLOOR_PCT).min(100)
loss_pct.clamp(DRED_LOSS_FLOOR_PCT, 100)
};
let _ = self.inner.set_packet_loss(clamped);
}

View File

@@ -48,7 +48,7 @@ fn build_fir_kernel() -> [f64; FIR_TAPS] {
let fc = CUTOFF_HZ / SAMPLE_RATE; // normalised cutoff (0..0.5)
let beta_denom = bessel_i0(KAISER_BETA);
for i in 0..FIR_TAPS {
for (i, slot) in kernel.iter_mut().enumerate() {
// Sinc
let n = i as f64 - m / 2.0;
let sinc = if n.abs() < 1e-12 {
@@ -61,7 +61,7 @@ fn build_fir_kernel() -> [f64; FIR_TAPS] {
let t = 2.0 * i as f64 / m - 1.0; // range [-1, 1]
let kaiser = bessel_i0(KAISER_BETA * (1.0 - t * t).max(0.0).sqrt()) / beta_denom;
kernel[i] = sinc * kaiser;
*slot = sinc * kaiser;
}
// Normalise to unity DC gain.
@@ -180,9 +180,7 @@ impl Upsampler8to48 {
work.extend_from_slice(&self.history);
for &s in input {
work.push(s as f64);
for _ in 1..RATIO {
work.push(0.0);
}
work.resize(work.len() + (RATIO - 1), 0.0f64);
}
let out_len = stuffed_len;