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>
109 lines
3.5 KiB
Rust
109 lines
3.5 KiB
Rust
//! WZP video pipeline — H.264 / H.265 framer and depacketizer.
|
|
//!
|
|
//! This crate lives alongside `wzp-codec` and handles video-specific
|
|
//! packetization (NAL fragmentation / reassembly). Platform encoders and
|
|
//! decoders land in T4.2/T4.3/T5.4.
|
|
|
|
pub mod av1_obu;
|
|
pub mod controller;
|
|
#[cfg(target_os = "macos")]
|
|
pub mod dav1d;
|
|
pub mod decoder;
|
|
pub mod depacketizer;
|
|
pub mod encoder;
|
|
pub mod encoder_mode;
|
|
pub mod factory;
|
|
pub mod framer;
|
|
pub mod mediacodec;
|
|
pub mod nack;
|
|
pub mod transport;
|
|
pub mod simulcast;
|
|
#[cfg(target_os = "macos")]
|
|
pub mod svt_av1;
|
|
pub mod videotoolbox;
|
|
|
|
pub use av1_obu::{Av1Depacketizer, Av1ObuFramer, is_keyframe_obu};
|
|
pub use controller::{VideoQualityController, VideoTarget};
|
|
#[cfg(target_os = "macos")]
|
|
pub use dav1d::Dav1dDecoder;
|
|
pub use decoder::VideoDecoder;
|
|
pub use depacketizer::H264Depacketizer;
|
|
pub use encoder::{VideoEncoder, VideoError, VideoFrame};
|
|
pub use encoder_mode::EncoderMode;
|
|
pub use factory::{create_video_decoder, create_video_encoder};
|
|
pub use framer::{FramedPacket, H264Framer};
|
|
pub use mediacodec::{
|
|
MediaCodecAv1Decoder, MediaCodecAv1Encoder, MediaCodecDecoder, MediaCodecEncoder,
|
|
MediaCodecHevcDecoder, MediaCodecHevcEncoder,
|
|
};
|
|
pub use nack::{CachedPacket, NackAction, NackReceiver, NackSender};
|
|
pub use simulcast::{LayerPacket, LayerTarget, SimulcastEncoder, SimulcastLayer};
|
|
#[cfg(target_os = "macos")]
|
|
pub use svt_av1::SvtAv1Encoder;
|
|
pub use videotoolbox::{
|
|
VideoToolboxAv1Decoder, VideoToolboxDecoder, VideoToolboxEncoder, VideoToolboxHevcDecoder,
|
|
VideoToolboxHevcEncoder,
|
|
};
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::{H264Depacketizer, H264Framer};
|
|
|
|
/// Build a synthetic H.264 access unit (Annex-B, 3-byte start codes):
|
|
/// - NAL 1: IDR slice (type 5) with 100-byte payload
|
|
/// - NAL 2: non-IDR slice (type 1) with 50-byte payload
|
|
fn synthetic_access_unit() -> Vec<u8> {
|
|
let mut au = Vec::new();
|
|
au.extend_from_slice(&[0x00, 0x00, 0x01, 0x65]); // IDR start code
|
|
au.extend_from_slice(&[0xCC; 100]);
|
|
au.extend_from_slice(&[0x00, 0x00, 0x01, 0x41]); // non-IDR start code
|
|
au.extend_from_slice(&[0xDD; 50]);
|
|
au
|
|
}
|
|
|
|
#[test]
|
|
fn roundtrip_single_nal() {
|
|
let au = synthetic_access_unit();
|
|
let framer = H264Framer::new(500);
|
|
let packets = framer.frame(&au);
|
|
|
|
let mut dep = H264Depacketizer::new();
|
|
let mut result = None;
|
|
for pkt in &packets {
|
|
result = dep.push(&pkt.payload, pkt.is_frame_end);
|
|
}
|
|
|
|
assert_eq!(result, Some(au));
|
|
}
|
|
|
|
#[test]
|
|
fn roundtrip_with_fu_a_fragmentation() {
|
|
let au = synthetic_access_unit();
|
|
// Max payload 30 bytes forces the 100-byte NAL into FU-A fragments.
|
|
let framer = H264Framer::new(30);
|
|
let packets = framer.frame(&au);
|
|
|
|
// The 100-byte NAL (1 header + 100 payload = 101 bytes) will be
|
|
// fragmented. 30-byte max means 28 bytes of data per fragment
|
|
// (2 bytes FU-A header). 100 payload bytes → 4 fragments.
|
|
// The 50-byte NAL (1 + 50 = 51) also fragments → 2 fragments.
|
|
// Total packets = 4 + 2 = 6.
|
|
assert_eq!(packets.len(), 6);
|
|
|
|
let mut dep = H264Depacketizer::new();
|
|
let mut result = None;
|
|
for pkt in &packets {
|
|
result = dep.push(&pkt.payload, pkt.is_frame_end);
|
|
}
|
|
|
|
assert_eq!(result, Some(au));
|
|
}
|
|
|
|
#[test]
|
|
fn roundtrip_empty_access_unit() {
|
|
let framer = H264Framer::new(100);
|
|
let packets = framer.frame(&[]);
|
|
assert!(packets.is_empty());
|
|
}
|
|
}
|