1use crate::rtp::{RtpError, RtpHeader};
2
3const MOCK_RTP_PAYLOAD_TYPE: u8 = 120;
4const MOCK_RTP_SSRC: u32 = 0x4f49_5043;
5const MOCK_PAYLOAD_MAGIC: &[u8; 4] = b"ORMF";
6const MOCK_PAYLOAD_HEADER_LEN: usize = 24;
7const MOCK_RTP_PAYLOAD_BYTES: usize = 1_100;
8
9#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct MockRtpFrame {
12 pub width: u16,
14 pub height: u16,
16 pub frame_index: u64,
18 pub timestamp: u32,
20 pub rgba: Vec<u8>,
22 pub rtp_packets: usize,
24 pub rtp_bytes: usize,
26}
27
28#[derive(Debug, Clone)]
34pub struct MockRtpPipeline {
35 width: u16,
36 height: u16,
37 fps: u16,
38 frame_index: u64,
39 sequence: u16,
40 timestamp: u32,
41}
42
43impl Default for MockRtpPipeline {
44 fn default() -> Self {
45 Self::new(320, 180, 30)
46 }
47}
48
49impl MockRtpPipeline {
50 pub fn new(width: u16, height: u16, fps: u16) -> Self {
52 Self {
53 width: width.clamp(16, 1_920),
54 height: height.clamp(16, 1_080),
55 fps: fps.clamp(1, 120),
56 frame_index: 0,
57 sequence: 1,
58 timestamp: 0,
59 }
60 }
61
62 pub fn next_frame(&mut self) -> Result<MockRtpFrame, RtpError> {
65 let rgba = render_mock_rgba(self.width, self.height, self.frame_index);
66 let packets = self.packetize(&rgba);
67 let rtp_packets = packets.len();
68 let rtp_bytes = packets.iter().map(Vec::len).sum();
69 let mut assembler = MockRtpAssembler::default();
70 let mut frame = None;
71 for packet in packets {
72 if let Some(recovered) = assembler.push(&packet)? {
73 frame = Some(recovered);
74 }
75 }
76
77 self.frame_index = self.frame_index.wrapping_add(1);
78 self.timestamp = self.timestamp.wrapping_add(90_000u32 / u32::from(self.fps));
79
80 let mut frame = frame.ok_or(RtpError::EmptyPayload)?;
81 frame.rtp_packets = rtp_packets;
82 frame.rtp_bytes = rtp_bytes;
83 Ok(frame)
84 }
85
86 fn packetize(&mut self, rgba: &[u8]) -> Vec<Vec<u8>> {
87 let mut packets = Vec::new();
88 let total_len = rgba.len() as u32;
89 let mut offset = 0usize;
90 while offset < rgba.len() {
91 let remaining = rgba.len() - offset;
92 let chunk_len = remaining.min(MOCK_RTP_PAYLOAD_BYTES);
93 let marker = offset + chunk_len == rgba.len();
94 let mut packet = Vec::with_capacity(12 + MOCK_PAYLOAD_HEADER_LEN + chunk_len);
95 packet.push(0x80);
96 packet.push((if marker { 0x80 } else { 0x00 }) | MOCK_RTP_PAYLOAD_TYPE);
97 packet.extend_from_slice(&self.sequence.to_be_bytes());
98 packet.extend_from_slice(&self.timestamp.to_be_bytes());
99 packet.extend_from_slice(&MOCK_RTP_SSRC.to_be_bytes());
100 packet.extend_from_slice(MOCK_PAYLOAD_MAGIC);
101 packet.extend_from_slice(&self.frame_index.to_be_bytes());
102 packet.extend_from_slice(&self.width.to_be_bytes());
103 packet.extend_from_slice(&self.height.to_be_bytes());
104 packet.extend_from_slice(&total_len.to_be_bytes());
105 packet.extend_from_slice(&(offset as u32).to_be_bytes());
106 packet.extend_from_slice(&rgba[offset..offset + chunk_len]);
107 packets.push(packet);
108 self.sequence = self.sequence.wrapping_add(1);
109 offset += chunk_len;
110 }
111 packets
112 }
113}
114
115#[derive(Debug, Default)]
116struct MockRtpAssembler {
117 frame_index: Option<u64>,
118 timestamp: u32,
119 width: u16,
120 height: u16,
121 total_len: usize,
122 rgba: Vec<u8>,
123 received: Vec<bool>,
124}
125
126impl MockRtpAssembler {
127 fn push(&mut self, packet: &[u8]) -> Result<Option<MockRtpFrame>, RtpError> {
128 let header = RtpHeader::parse(packet)?;
129 if header.payload_type != MOCK_RTP_PAYLOAD_TYPE {
130 return Err(RtpError::UnsupportedPayload);
131 }
132 let payload = header.payload(packet);
133 if payload.len() < MOCK_PAYLOAD_HEADER_LEN || &payload[..4] != MOCK_PAYLOAD_MAGIC {
134 return Err(RtpError::UnsupportedPayload);
135 }
136 let frame_index = u64::from_be_bytes(payload[4..12].try_into().unwrap());
137 let width = u16::from_be_bytes(payload[12..14].try_into().unwrap());
138 let height = u16::from_be_bytes(payload[14..16].try_into().unwrap());
139 let total_len = u32::from_be_bytes(payload[16..20].try_into().unwrap()) as usize;
140 let offset = u32::from_be_bytes(payload[20..24].try_into().unwrap()) as usize;
141 let bytes = &payload[24..];
142 if total_len == 0 || offset + bytes.len() > total_len {
143 return Err(RtpError::InvalidPadding);
144 }
145 if self.frame_index != Some(frame_index) {
146 self.frame_index = Some(frame_index);
147 self.timestamp = header.timestamp;
148 self.width = width;
149 self.height = height;
150 self.total_len = total_len;
151 self.rgba = vec![0; total_len];
152 self.received = vec![false; total_len];
153 }
154 self.rgba[offset..offset + bytes.len()].copy_from_slice(bytes);
155 self.received[offset..offset + bytes.len()].fill(true);
156
157 if header.marker && self.received.iter().all(|received| *received) {
158 Ok(Some(MockRtpFrame {
159 width: self.width,
160 height: self.height,
161 frame_index,
162 timestamp: self.timestamp,
163 rgba: self.rgba.clone(),
164 rtp_packets: 0,
165 rtp_bytes: 0,
166 }))
167 } else {
168 Ok(None)
169 }
170 }
171}
172
173fn render_mock_rgba(width: u16, height: u16, frame_index: u64) -> Vec<u8> {
174 let width = usize::from(width);
175 let height = usize::from(height);
176 let mut rgba = vec![0; width * height * 4];
177 let phase = (frame_index as usize * 3) % width.max(1);
178 for y in 0..height {
179 for x in 0..width {
180 let i = (y * width + x) * 4;
181 let bar = ((x + phase) * 6 / width.max(1)) as u8;
182 let grid = x % 32 == 0 || y % 32 == 0;
183 let pulse = ((frame_index * 5 + y as u64) & 0xff) as u8;
184 let (r, g, b): (u8, u8, u8) = match bar {
185 0 => (236, 72, 85),
186 1 => (245, 158, 11),
187 2 => (34, 197, 94),
188 3 => (20, 184, 166),
189 4 => (59, 130, 246),
190 _ => (168, 85, 247),
191 };
192 rgba[i] = if grid { 245 } else { r };
193 rgba[i + 1] = if grid {
194 245
195 } else {
196 g.saturating_add(pulse / 12)
197 };
198 rgba[i + 2] = if grid { 245 } else { b };
199 rgba[i + 3] = 255;
200 }
201 }
202 rgba
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208
209 #[test]
210 fn mock_pipeline_roundtrips_rgba_through_rtp_packets() {
211 let mut mock = MockRtpPipeline::new(64, 36, 30);
212 let frame = mock.next_frame().unwrap();
213 assert_eq!(frame.width, 64);
214 assert_eq!(frame.height, 36);
215 assert_eq!(frame.frame_index, 0);
216 assert_eq!(frame.rgba.len(), 64 * 36 * 4);
217 assert!(frame.rtp_packets > 1);
218 assert!(frame.rtp_bytes > frame.rgba.len());
219 }
220}