//! 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 { 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()); } }