1use crate::channel::ChannelId;
2use crate::ieee80211::{FrameLayout, WifiFrame};
3use crate::realtek::{
4 parse_rx_aggregate, parse_rx_aggregate_with_kind, AggregateError, RealtekRxPacket,
5 RxDescriptorKind, RxPacketType,
6};
7use crate::routes::{
8 PayloadRouteError, PayloadRouteEvent, PayloadRouteId, PayloadRouteManager, PayloadRuntimeKey,
9};
10use crate::rtp::{
11 DepacketizedFrame, RtpDepacketizer, RtpDepacketizerStatus, RtpHeader, RtpReorderBuffer,
12 RtpReorderStatus,
13};
14use crate::wfb::{FecCounters, WfbKeypair};
15
16#[derive(Debug, Clone)]
23pub struct ReceiverRuntime {
24 routes: PayloadRouteManager,
25 video_runtime: PayloadRuntimeKey,
26 video_route_id: PayloadRouteId,
27 rtp: RtpDepacketizer,
28 rtp_reorder: Option<RtpReorderBuffer>,
29}
30
31#[derive(Debug, Clone)]
33pub struct ReceiverBatchOptions {
34 pub accept_corrupted: bool,
36 pub raw_payload_routes: Vec<PayloadRouteId>,
38 pub rtp_payload_taps: Vec<RtpPayloadTap>,
40 pub depacketize_video: bool,
46}
47
48impl Default for ReceiverBatchOptions {
49 fn default() -> Self {
50 Self {
51 accept_corrupted: false,
52 raw_payload_routes: Vec::new(),
53 rtp_payload_taps: Vec::new(),
54 depacketize_video: true,
55 }
56 }
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub struct RtpPayloadTap {
67 pub route_id: PayloadRouteId,
69 pub payload_type: u8,
71}
72
73#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct RoutePayload {
76 pub route_id: PayloadRouteId,
78 pub channel_id: ChannelId,
80 pub packet_seq: u64,
82 pub data: Vec<u8>,
84}
85
86#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
88pub struct ReceiverBatchCounters {
89 pub packets: usize,
91 pub accepted_packets: usize,
93 pub dropped_packets: usize,
95 pub crc_dropped: usize,
97 pub icv_dropped: usize,
99 pub report_dropped: usize,
101 pub ignored_frames: usize,
103 pub sessions: usize,
105 pub wfb_payloads: usize,
107 pub rtp_packets: usize,
109 pub video_frames: usize,
111 pub raw_payload_count: usize,
113 pub raw_payload_bytes: usize,
115 pub route_errors: usize,
117}
118
119#[derive(Debug, Clone, PartialEq, Eq)]
121pub struct ReceiverBatch {
122 pub frames: Vec<DepacketizedFrame>,
124 pub raw_payloads: Vec<RoutePayload>,
126 pub counters: ReceiverBatchCounters,
128 pub fec_counters: FecCounters,
130 pub rtp_status: RtpDepacketizerStatus,
132 pub rtp_reorder_status: RtpReorderStatus,
134}
135
136impl ReceiverRuntime {
137 pub fn from_routes(
142 routes: PayloadRouteManager,
143 video_runtime: PayloadRuntimeKey,
144 video_route_id: PayloadRouteId,
145 ) -> Self {
146 Self {
147 routes,
148 video_runtime,
149 video_route_id,
150 rtp: RtpDepacketizer::new(),
151 rtp_reorder: None,
152 }
153 }
154
155 pub fn with_plain_video_route(
159 frame_layout: FrameLayout,
160 video_route_id: PayloadRouteId,
161 channel_id: ChannelId,
162 key_slot: u64,
163 fec_k: usize,
164 fec_n: usize,
165 ) -> Result<Self, PayloadRouteError> {
166 let mut routes = PayloadRouteManager::new(frame_layout);
167 let video_runtime =
168 routes.add_plain_route(video_route_id, channel_id, key_slot, fec_k, fec_n)?;
169 Ok(Self::from_routes(routes, video_runtime, video_route_id))
170 }
171
172 pub fn with_keyed_video_route(
174 frame_layout: FrameLayout,
175 video_route_id: PayloadRouteId,
176 channel_id: ChannelId,
177 key_slot: u64,
178 keypair: WfbKeypair,
179 minimum_epoch: u64,
180 ) -> Result<Self, PayloadRouteError> {
181 let mut routes = PayloadRouteManager::new(frame_layout);
182 let video_runtime =
183 routes.add_keyed_route(video_route_id, channel_id, key_slot, keypair, minimum_epoch)?;
184 Ok(Self::from_routes(routes, video_runtime, video_route_id))
185 }
186
187 pub fn with_direct_video_route(
193 frame_layout: FrameLayout,
194 video_route_id: PayloadRouteId,
195 channel_id: ChannelId,
196 key_slot: u64,
197 ) -> Self {
198 let mut routes = PayloadRouteManager::new(frame_layout);
199 let video_runtime = routes.add_direct_route(video_route_id, channel_id, key_slot);
200 Self::from_routes(routes, video_runtime, video_route_id)
201 }
202
203 pub fn with_mock_video_route(
207 frame_layout: FrameLayout,
208 video_route_id: PayloadRouteId,
209 channel_id: ChannelId,
210 key_slot: u64,
211 ) -> Self {
212 Self::with_direct_video_route(frame_layout, video_route_id, channel_id, key_slot)
213 }
214
215 pub const fn video_runtime(&self) -> PayloadRuntimeKey {
217 self.video_runtime
218 }
219
220 pub const fn video_route_id(&self) -> PayloadRouteId {
222 self.video_route_id
223 }
224
225 pub fn routes(&self) -> &PayloadRouteManager {
227 &self.routes
228 }
229
230 pub fn routes_mut(&mut self) -> &mut PayloadRouteManager {
232 &mut self.routes
233 }
234
235 pub fn rtp_mut(&mut self) -> &mut RtpDepacketizer {
237 &mut self.rtp
238 }
239
240 pub fn rtp_status(&self) -> RtpDepacketizerStatus {
242 self.rtp.status()
243 }
244
245 pub fn rtp_reorder_status(&self) -> RtpReorderStatus {
247 self.rtp_reorder
248 .as_ref()
249 .map(RtpReorderBuffer::status)
250 .unwrap_or_default()
251 }
252
253 pub fn set_rtp_reorder_enabled(&mut self, enabled: bool) {
259 if enabled {
260 self.rtp_reorder
261 .get_or_insert_with(RtpReorderBuffer::default);
262 } else {
263 self.rtp_reorder = None;
264 }
265 }
266
267 pub const fn rtp_reorder_enabled(&self) -> bool {
269 self.rtp_reorder.is_some()
270 }
271
272 pub fn push_rtp_packet(
274 &mut self,
275 packet: &[u8],
276 ) -> Result<Vec<DepacketizedFrame>, crate::rtp::RtpError> {
277 let mut frames = Vec::new();
278 self.push_video_payload_into(packet, &mut frames)?;
279 Ok(frames)
280 }
281
282 fn push_video_payload_into(
283 &mut self,
284 payload: &[u8],
285 frames: &mut Vec<DepacketizedFrame>,
286 ) -> Result<usize, crate::rtp::RtpError> {
287 let before = frames.len();
288 if let Some(reorder) = self.rtp_reorder.as_mut() {
289 for ordered in reorder.push(payload)? {
290 if let Some(frame) = self.rtp.push(&ordered)? {
291 frames.push(frame);
292 }
293 }
294 } else if let Some(frame) = self.rtp.push(payload)? {
295 frames.push(frame);
296 }
297 Ok(frames.len() - before)
298 }
299
300 pub fn add_plain_route(
302 &mut self,
303 route_id: PayloadRouteId,
304 channel_id: ChannelId,
305 key_slot: u64,
306 fec_k: usize,
307 fec_n: usize,
308 ) -> Result<PayloadRuntimeKey, PayloadRouteError> {
309 self.routes
310 .add_plain_route(route_id, channel_id, key_slot, fec_k, fec_n)
311 }
312
313 pub fn add_keyed_route(
315 &mut self,
316 route_id: PayloadRouteId,
317 channel_id: ChannelId,
318 key_slot: u64,
319 keypair: WfbKeypair,
320 minimum_epoch: u64,
321 ) -> Result<PayloadRuntimeKey, PayloadRouteError> {
322 self.routes
323 .add_keyed_route(route_id, channel_id, key_slot, keypair, minimum_epoch)
324 }
325
326 pub fn add_direct_route(
328 &mut self,
329 route_id: PayloadRouteId,
330 channel_id: ChannelId,
331 key_slot: u64,
332 ) -> PayloadRuntimeKey {
333 self.routes.add_direct_route(route_id, channel_id, key_slot)
334 }
335
336 pub fn add_mock_route(
340 &mut self,
341 route_id: PayloadRouteId,
342 channel_id: ChannelId,
343 key_slot: u64,
344 ) -> PayloadRuntimeKey {
345 self.add_direct_route(route_id, channel_id, key_slot)
346 }
347
348 pub fn video_fec_counters(&self) -> FecCounters {
350 self.routes
351 .fec_counters(self.video_runtime)
352 .unwrap_or_default()
353 }
354
355 pub fn accepts_video_frame(&self, frame: &[u8]) -> bool {
357 self.routes.accepts_80211_frame(self.video_runtime, frame)
358 }
359
360 pub fn push_rx_transfer(
362 &mut self,
363 transfer: &[u8],
364 options: &ReceiverBatchOptions,
365 ) -> Result<ReceiverBatch, AggregateError> {
366 let packets = parse_rx_aggregate(transfer)?;
367 Ok(self.push_rx_packets(packets, options))
368 }
369
370 pub fn push_rx_transfer_with_kind(
375 &mut self,
376 transfer: &[u8],
377 descriptor_kind: RxDescriptorKind,
378 options: &ReceiverBatchOptions,
379 ) -> Result<ReceiverBatch, AggregateError> {
380 let packets = parse_rx_aggregate_with_kind(transfer, descriptor_kind)?;
381 Ok(self.push_rx_packets(packets, options))
382 }
383
384 pub fn push_rx_packets<'a>(
386 &mut self,
387 packets: impl IntoIterator<Item = RealtekRxPacket<'a>>,
388 options: &ReceiverBatchOptions,
389 ) -> ReceiverBatch {
390 let mut batch = self.empty_batch();
391 let mut route_events = Vec::new();
392
393 for packet in packets {
394 let parsed = if packet.attrib.crc_err
395 || packet.attrib.icv_err
396 || packet.attrib.pkt_rpt_type != RxPacketType::NormalRx
397 {
398 None
399 } else {
400 WifiFrame::parse(packet.data, self.routes.frame_layout()).ok()
401 };
402 self.push_rx_packet(packet, parsed, options, &mut route_events, &mut batch);
403 }
404
405 self.finish_rx_batch(batch)
406 }
407
408 pub fn push_parsed_rx_packets<'a>(
413 &mut self,
414 packets: impl IntoIterator<Item = (RealtekRxPacket<'a>, Option<WifiFrame<'a>>)>,
415 options: &ReceiverBatchOptions,
416 ) -> ReceiverBatch {
417 let mut batch = self.empty_batch();
418 let mut route_events = Vec::new();
419 for (packet, parsed) in packets {
420 self.push_rx_packet(packet, parsed, options, &mut route_events, &mut batch);
421 }
422 self.finish_rx_batch(batch)
423 }
424
425 fn push_rx_packet(
426 &mut self,
427 packet: RealtekRxPacket<'_>,
428 parsed: Option<WifiFrame<'_>>,
429 options: &ReceiverBatchOptions,
430 route_events: &mut Vec<PayloadRouteEvent>,
431 batch: &mut ReceiverBatch,
432 ) {
433 batch.counters.packets += 1;
434 if packet.attrib.crc_err && !options.accept_corrupted {
435 batch.counters.crc_dropped += 1;
436 return;
437 }
438 if packet.attrib.icv_err && !options.accept_corrupted {
439 batch.counters.icv_dropped += 1;
440 return;
441 }
442 if packet.attrib.pkt_rpt_type != RxPacketType::NormalRx {
443 batch.counters.report_dropped += 1;
444 return;
445 }
446
447 batch.counters.accepted_packets += 1;
448 let Some(parsed) = parsed else {
449 batch.counters.ignored_frames += 1;
450 return;
451 };
452 match self.routes.push_wifi_frame_into(parsed, route_events) {
453 Ok(()) => self.apply_route_events(route_events.drain(..), options, batch),
454 Err(_) => {
455 batch.counters.ignored_frames += 1;
456 batch.counters.route_errors += 1;
457 }
458 }
459 }
460
461 fn finish_rx_batch(&self, mut batch: ReceiverBatch) -> ReceiverBatch {
462 batch.counters.dropped_packets =
463 batch.counters.crc_dropped + batch.counters.icv_dropped + batch.counters.report_dropped;
464 batch.fec_counters = self.video_fec_counters();
465 batch
466 }
467
468 pub fn push_80211_frame(
470 &mut self,
471 frame: &[u8],
472 options: &ReceiverBatchOptions,
473 ) -> Result<ReceiverBatch, PayloadRouteError> {
474 let mut batch = self.empty_batch();
475 let events = self.routes.push_80211_frame(frame)?;
476 self.apply_route_events(events, options, &mut batch);
477 batch.fec_counters = self.video_fec_counters();
478 Ok(batch)
479 }
480
481 pub fn push_decrypted_80211_frame(
483 &mut self,
484 runtime: PayloadRuntimeKey,
485 frame: &[u8],
486 decrypted_fragment: &[u8],
487 options: &ReceiverBatchOptions,
488 ) -> Result<ReceiverBatch, PayloadRouteError> {
489 let mut batch = self.empty_batch();
490 let events = self
491 .routes
492 .push_decrypted_80211_frame(runtime, frame, decrypted_fragment)?;
493 self.apply_route_events(events, options, &mut batch);
494 batch.fec_counters = self.video_fec_counters();
495 Ok(batch)
496 }
497
498 pub fn push_decrypted_fragment(
500 &mut self,
501 runtime: PayloadRuntimeKey,
502 data_nonce: u64,
503 decrypted_fragment: &[u8],
504 options: &ReceiverBatchOptions,
505 ) -> Result<ReceiverBatch, PayloadRouteError> {
506 let mut batch = self.empty_batch();
507 let events =
508 self.routes
509 .push_decrypted_fragment(runtime, data_nonce, decrypted_fragment)?;
510 self.apply_route_events(events, options, &mut batch);
511 batch.fec_counters = self.video_fec_counters();
512 Ok(batch)
513 }
514
515 pub fn push_direct_payload(
517 &mut self,
518 runtime: PayloadRuntimeKey,
519 packet_seq: u64,
520 payload: &[u8],
521 options: &ReceiverBatchOptions,
522 ) -> Result<ReceiverBatch, PayloadRouteError> {
523 let mut batch = self.empty_batch();
524 let (route_ids, direct) = self
525 .routes
526 .route_membership(runtime)
527 .ok_or(PayloadRouteError::UnknownRuntime(runtime))?;
528 if direct {
529 self.apply_recovered_payload(
530 &route_ids,
531 runtime.channel_id(),
532 packet_seq,
533 payload,
534 options,
535 &mut batch,
536 );
537 } else {
538 batch.counters.ignored_frames += 1;
539 }
540 batch.fec_counters = self.video_fec_counters();
541 batch.rtp_status = self.rtp_status();
542 batch.rtp_reorder_status = self.rtp_reorder_status();
543 Ok(batch)
544 }
545
546 pub fn push_mock_payload(
550 &mut self,
551 runtime: PayloadRuntimeKey,
552 packet_seq: u64,
553 payload: &[u8],
554 options: &ReceiverBatchOptions,
555 ) -> Result<ReceiverBatch, PayloadRouteError> {
556 self.push_direct_payload(runtime, packet_seq, payload, options)
557 }
558
559 fn empty_batch(&self) -> ReceiverBatch {
560 ReceiverBatch {
561 frames: Vec::new(),
562 raw_payloads: Vec::new(),
563 counters: ReceiverBatchCounters::default(),
564 fec_counters: self.video_fec_counters(),
565 rtp_status: self.rtp_status(),
566 rtp_reorder_status: self.rtp_reorder_status(),
567 }
568 }
569
570 fn apply_route_events(
571 &mut self,
572 events: impl IntoIterator<Item = PayloadRouteEvent>,
573 options: &ReceiverBatchOptions,
574 batch: &mut ReceiverBatch,
575 ) {
576 for event in events {
577 match event {
578 PayloadRouteEvent::IgnoredFrame => batch.counters.ignored_frames += 1,
579 PayloadRouteEvent::SessionEstablished { .. } => batch.counters.sessions += 1,
580 PayloadRouteEvent::Payload {
581 route_ids, payload, ..
582 } => {
583 self.apply_owned_recovered_payload(
584 &route_ids,
585 payload.channel_id,
586 payload.packet_seq,
587 payload.data,
588 options,
589 batch,
590 );
591 }
592 }
593 }
594 batch.rtp_status = self.rtp_status();
595 batch.rtp_reorder_status = self.rtp_reorder_status();
596 }
597
598 fn apply_recovered_payload(
599 &mut self,
600 route_ids: &[PayloadRouteId],
601 channel_id: ChannelId,
602 packet_seq: u64,
603 data: &[u8],
604 options: &ReceiverBatchOptions,
605 batch: &mut ReceiverBatch,
606 ) {
607 self.apply_video_payload(route_ids, data, options, batch);
608
609 for &route_id in route_ids {
610 if options.raw_payload_routes.contains(&route_id) {
611 copy_raw_payload(route_id, channel_id, packet_seq, data, batch);
612 }
613 }
614
615 if options.rtp_payload_taps.is_empty() {
616 return;
617 }
618 let Ok(header) = RtpHeader::parse(data) else {
619 return;
620 };
621 for tap in &options.rtp_payload_taps {
622 if header.payload_type == tap.payload_type && route_ids.contains(&tap.route_id) {
623 copy_raw_payload(tap.route_id, channel_id, packet_seq, data, batch);
624 }
625 }
626 }
627
628 fn apply_owned_recovered_payload(
629 &mut self,
630 route_ids: &[PayloadRouteId],
631 channel_id: ChannelId,
632 packet_seq: u64,
633 data: Vec<u8>,
634 options: &ReceiverBatchOptions,
635 batch: &mut ReceiverBatch,
636 ) {
637 self.apply_video_payload(route_ids, &data, options, batch);
638
639 let payload_type = (!options.rtp_payload_taps.is_empty())
640 .then(|| {
641 RtpHeader::parse(&data)
642 .ok()
643 .map(|header| header.payload_type)
644 })
645 .flatten();
646 let mut remaining = route_ids
647 .iter()
648 .filter(|route_id| options.raw_payload_routes.contains(route_id))
649 .count()
650 + options
651 .rtp_payload_taps
652 .iter()
653 .filter(|tap| {
654 payload_type == Some(tap.payload_type) && route_ids.contains(&tap.route_id)
655 })
656 .count();
657 if remaining == 0 {
658 return;
659 }
660
661 let mut data = Some(data);
662 for &route_id in route_ids {
663 if options.raw_payload_routes.contains(&route_id) {
664 remaining -= 1;
665 push_raw_payload(
666 route_id,
667 channel_id,
668 packet_seq,
669 take_or_clone_payload(&mut data, remaining),
670 batch,
671 );
672 }
673 }
674 for tap in &options.rtp_payload_taps {
675 if payload_type == Some(tap.payload_type) && route_ids.contains(&tap.route_id) {
676 remaining -= 1;
677 push_raw_payload(
678 tap.route_id,
679 channel_id,
680 packet_seq,
681 take_or_clone_payload(&mut data, remaining),
682 batch,
683 );
684 }
685 }
686 }
687
688 fn apply_video_payload(
689 &mut self,
690 route_ids: &[PayloadRouteId],
691 data: &[u8],
692 options: &ReceiverBatchOptions,
693 batch: &mut ReceiverBatch,
694 ) {
695 if route_ids.contains(&self.video_route_id) {
696 batch.counters.wfb_payloads += 1;
697 batch.counters.rtp_packets += 1;
698 if options.depacketize_video {
699 if let Ok(frames) = self.push_video_payload_into(data, &mut batch.frames) {
700 batch.counters.video_frames += frames;
701 }
702 }
703 }
704 }
705}
706
707fn take_or_clone_payload(data: &mut Option<Vec<u8>>, remaining: usize) -> Vec<u8> {
708 if remaining == 0 {
709 data.take()
710 .expect("the final raw route must own the recovered payload")
711 } else {
712 data.as_ref()
713 .expect("raw route payload must remain available")
714 .clone()
715 }
716}
717
718fn copy_raw_payload(
719 route_id: PayloadRouteId,
720 channel_id: ChannelId,
721 packet_seq: u64,
722 data: &[u8],
723 batch: &mut ReceiverBatch,
724) {
725 push_raw_payload(route_id, channel_id, packet_seq, data.to_vec(), batch);
726}
727
728fn push_raw_payload(
729 route_id: PayloadRouteId,
730 channel_id: ChannelId,
731 packet_seq: u64,
732 data: Vec<u8>,
733 batch: &mut ReceiverBatch,
734) {
735 batch.counters.raw_payload_count += 1;
736 batch.counters.raw_payload_bytes += data.len();
737 batch.raw_payloads.push(RoutePayload {
738 route_id,
739 channel_id,
740 packet_seq,
741 data,
742 });
743}
744
745#[cfg(test)]
746mod tests {
747 use super::*;
748 use crate::RadioPort;
749
750 fn plain(payload: &[u8]) -> Vec<u8> {
751 let mut out = Vec::new();
752 out.push(0);
753 out.extend_from_slice(&(payload.len() as u16).to_be_bytes());
754 out.extend_from_slice(payload);
755 out
756 }
757
758 fn rtp(payload_type: u8, payload: &[u8]) -> Vec<u8> {
759 let mut packet = vec![0x80, payload_type & 0x7f];
760 packet.extend_from_slice(&7u16.to_be_bytes());
761 packet.extend_from_slice(&48_000u32.to_be_bytes());
762 packet.extend_from_slice(&0x1122_3344u32.to_be_bytes());
763 packet.extend_from_slice(payload);
764 packet
765 }
766
767 fn h264_stap_a_rtp() -> Vec<u8> {
768 let sps = [0x67, 0x42, 0x00, 0x1e, 0xab];
769 let pps = [0x68, 0xce, 0x06, 0xe2];
770 let idr = [0x65, 0x88, 0x84, 0x21];
771 let mut payload = vec![24];
772 for nalu in [&sps[..], &pps[..], &idr[..]] {
773 payload.extend_from_slice(&(nalu.len() as u16).to_be_bytes());
774 payload.extend_from_slice(nalu);
775 }
776 let mut packet = rtp(crate::rtp::RTP_PAYLOAD_TYPE_H264, &payload);
777 packet[1] |= 0x80;
778 packet
779 }
780
781 #[test]
782 fn decrypted_fragment_can_fan_out_to_raw_route() {
783 let route = PayloadRouteId::new(7);
784 let mut runtime = ReceiverRuntime::with_plain_video_route(
785 FrameLayout::WithFcs,
786 route,
787 ChannelId::default_video(),
788 0,
789 1,
790 1,
791 )
792 .unwrap();
793 let batch = runtime
794 .push_decrypted_fragment(
795 runtime.video_runtime(),
796 0,
797 &plain(b"payload bytes"),
798 &ReceiverBatchOptions {
799 raw_payload_routes: vec![route],
800 ..ReceiverBatchOptions::default()
801 },
802 )
803 .unwrap();
804
805 assert_eq!(batch.counters.wfb_payloads, 1);
806 assert_eq!(batch.counters.raw_payload_count, 1);
807 assert_eq!(batch.raw_payloads[0].data, b"payload bytes");
808 }
809
810 #[test]
811 fn rtp_payload_tap_copies_only_matching_payload_type() {
812 let video_route = PayloadRouteId::new(1);
813 let audio_route = PayloadRouteId::new(3);
814 let mut runtime = ReceiverRuntime::with_plain_video_route(
815 FrameLayout::WithFcs,
816 video_route,
817 ChannelId::default_video(),
818 0,
819 1,
820 1,
821 )
822 .unwrap();
823 runtime
824 .add_plain_route(audio_route, ChannelId::default_video(), 0, 1, 1)
825 .unwrap();
826
827 let ignored = runtime
828 .push_decrypted_fragment(
829 runtime.video_runtime(),
830 0,
831 &plain(&rtp(crate::rtp::RTP_PAYLOAD_TYPE_H264, b"video")),
832 &ReceiverBatchOptions {
833 rtp_payload_taps: vec![RtpPayloadTap {
834 route_id: audio_route,
835 payload_type: crate::rtp::RTP_PAYLOAD_TYPE_OPUS,
836 }],
837 ..ReceiverBatchOptions::default()
838 },
839 )
840 .unwrap();
841 assert_eq!(ignored.counters.raw_payload_count, 0);
842
843 let packet = rtp(crate::rtp::RTP_PAYLOAD_TYPE_OPUS, b"opus");
844 let batch = runtime
845 .push_decrypted_fragment(
846 runtime.video_runtime(),
847 1 << 8,
848 &plain(&packet),
849 &ReceiverBatchOptions {
850 rtp_payload_taps: vec![RtpPayloadTap {
851 route_id: audio_route,
852 payload_type: crate::rtp::RTP_PAYLOAD_TYPE_OPUS,
853 }],
854 ..ReceiverBatchOptions::default()
855 },
856 )
857 .unwrap();
858
859 assert_eq!(batch.counters.raw_payload_count, 1);
860 assert_eq!(batch.raw_payloads[0].route_id, audio_route);
861 assert_eq!(batch.raw_payloads[0].data, packet);
862 }
863
864 #[test]
865 fn rtp_reorder_is_opt_in() {
866 let mut runtime = ReceiverRuntime::with_plain_video_route(
867 FrameLayout::WithFcs,
868 PayloadRouteId::new(1),
869 ChannelId::default_video(),
870 0,
871 1,
872 1,
873 )
874 .unwrap();
875
876 assert!(!runtime.rtp_reorder_enabled());
877 assert_eq!(runtime.rtp_reorder_status(), RtpReorderStatus::default());
878
879 runtime.set_rtp_reorder_enabled(true);
880 assert!(runtime.rtp_reorder_enabled());
881
882 runtime.set_rtp_reorder_enabled(false);
883 assert!(!runtime.rtp_reorder_enabled());
884 assert_eq!(runtime.rtp_reorder_status(), RtpReorderStatus::default());
885 }
886
887 #[test]
888 fn auxiliary_route_does_not_count_as_video_payload() {
889 let video_route = PayloadRouteId::new(1);
890 let data_route = PayloadRouteId::new(2);
891 let mut runtime = ReceiverRuntime::with_plain_video_route(
892 FrameLayout::WithFcs,
893 video_route,
894 ChannelId::default_video(),
895 0,
896 1,
897 1,
898 )
899 .unwrap();
900 let data_runtime = runtime
901 .add_plain_route(
902 data_route,
903 ChannelId::from_link_port(crate::channel::DEFAULT_LINK_ID, RadioPort::TunnelRx),
904 0,
905 1,
906 1,
907 )
908 .unwrap();
909 let batch = runtime
910 .push_decrypted_fragment(
911 data_runtime,
912 0,
913 &plain(b"data bytes"),
914 &ReceiverBatchOptions {
915 raw_payload_routes: vec![data_route],
916 ..ReceiverBatchOptions::default()
917 },
918 )
919 .unwrap();
920
921 assert_eq!(batch.counters.wfb_payloads, 0);
922 assert_eq!(batch.counters.rtp_packets, 0);
923 assert_eq!(batch.counters.raw_payload_count, 1);
924 assert_eq!(batch.raw_payloads[0].data, b"data bytes");
925 }
926
927 #[test]
928 fn direct_payload_runtime_uses_same_video_route_and_rtp_depacketizer() {
929 let video_route = PayloadRouteId::new(1);
930 let mut runtime = ReceiverRuntime::with_direct_video_route(
931 FrameLayout::WithFcs,
932 video_route,
933 ChannelId::default_video(),
934 0,
935 );
936
937 let packet = h264_stap_a_rtp();
938 let batch = runtime
939 .push_direct_payload(
940 runtime.video_runtime(),
941 123,
942 &packet,
943 &ReceiverBatchOptions {
944 raw_payload_routes: vec![video_route],
945 ..ReceiverBatchOptions::default()
946 },
947 )
948 .unwrap();
949
950 assert_eq!(batch.counters.wfb_payloads, 1);
951 assert_eq!(batch.counters.rtp_packets, 1);
952 assert_eq!(batch.counters.video_frames, 1);
953 assert_eq!(batch.frames.len(), 1);
954 assert_eq!(batch.frames[0].codec, crate::rtp::Codec::H264);
955 assert!(batch.frames[0].is_keyframe);
956 assert_eq!(batch.raw_payloads[0].data, packet);
957 assert_eq!(batch.fec_counters, FecCounters::default());
958 }
959
960 #[test]
961 fn video_depacketization_can_be_delegated_without_losing_raw_rtp() {
962 let video_route = PayloadRouteId::new(1);
963 let mut runtime = ReceiverRuntime::with_direct_video_route(
964 FrameLayout::WithFcs,
965 video_route,
966 ChannelId::default_video(),
967 0,
968 );
969 let packet = h264_stap_a_rtp();
970
971 let batch = runtime
972 .push_direct_payload(
973 runtime.video_runtime(),
974 123,
975 &packet,
976 &ReceiverBatchOptions {
977 raw_payload_routes: vec![video_route],
978 depacketize_video: false,
979 ..ReceiverBatchOptions::default()
980 },
981 )
982 .unwrap();
983
984 assert_eq!(batch.counters.wfb_payloads, 1);
985 assert_eq!(batch.counters.rtp_packets, 1);
986 assert_eq!(batch.counters.video_frames, 0);
987 assert!(batch.frames.is_empty());
988 assert_eq!(batch.raw_payloads[0].data, packet);
989 assert_eq!(batch.rtp_status, RtpDepacketizerStatus::default());
990 }
991
992 #[test]
993 fn rx_transfer_accepts_explicit_jaguar3_descriptor_layout() {
994 let mut runtime = ReceiverRuntime::with_plain_video_route(
995 FrameLayout::WithFcs,
996 PayloadRouteId::new(1),
997 ChannelId::default_video(),
998 0,
999 1,
1000 1,
1001 )
1002 .unwrap();
1003 let mut transfer = vec![0u8; 32];
1004 transfer[..4].copy_from_slice(&8u32.to_le_bytes());
1005 transfer[24..32].copy_from_slice(&[0x08, 0, 0, 0, 0, 0, 0, 0]);
1006
1007 let batch = runtime
1008 .push_rx_transfer_with_kind(
1009 &transfer,
1010 RxDescriptorKind::Jaguar3,
1011 &ReceiverBatchOptions::default(),
1012 )
1013 .unwrap();
1014
1015 assert_eq!(batch.counters.packets, 1);
1016 assert_eq!(batch.counters.accepted_packets, 1);
1017 assert_eq!(batch.counters.ignored_frames, 1);
1018 }
1019}