1#[cfg(any(not(windows), test))]
22use std::collections::HashSet;
23#[cfg(windows)]
24use std::io::ErrorKind;
25use std::net::{IpAddr, Ipv4Addr};
26#[cfg(windows)]
27use std::sync::Arc;
28#[cfg(windows)]
29use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
30#[cfg(windows)]
31use std::thread;
32#[cfg(not(windows))]
33use std::time::SystemTime;
34use std::time::{Duration, Instant};
35
36#[cfg(not(windows))]
37use bytes::Bytes;
38use bytes::BytesMut;
39use tokio::net::UdpSocket;
40use tokio::time::timeout;
44#[cfg(not(windows))]
45use tracing::trace;
46use tracing::{debug, info, warn};
47use viva_pfnc::PixelFormat;
48
49use crate::GenicamError;
50use crate::frame::Frame;
51use crate::time::TimeSync;
52use viva_gige::gvcp::{GigeDevice, StreamParams};
53#[cfg(any(not(windows), test))]
54use viva_gige::gvsp::PacketBitmap;
55use viva_gige::gvsp::{self, GvspPacket, StreamConfig};
56use viva_gige::nic::{self, DEFAULT_RCVBUF_BYTES, Iface, McOptions};
57use viva_gige::stats::{StreamStats, StreamStatsAccumulator};
58
59pub use viva_gige::gvsp::StreamDest;
60
61pub(crate) enum PacketSource {
67 Udp(UdpSocket),
68}
69
70impl PacketSource {
71 #[cfg(not(windows))]
73 async fn recv(&self, buf: &mut [u8]) -> Result<Bytes, GenicamError> {
74 match self {
75 PacketSource::Udp(socket) => {
76 let (len, _) = socket
77 .recv_from(buf)
78 .await
79 .map_err(|e| GenicamError::transport(format!("socket recv failed: {e}")))?;
80 Ok(Bytes::copy_from_slice(&buf[..len]))
81 }
82 }
83 }
84
85 fn as_udp_socket(&self) -> Option<&UdpSocket> {
87 match self {
88 PacketSource::Udp(s) => Some(s),
89 }
90 }
91}
92
93const MIN_PACKET_SIZE: u32 = 576;
96
97const MAX_PACKET_SIZE: u32 = viva_gige::gvcp::STREAM_PACKET_SIZE_MASK;
102
103async fn configure_packet_size(
125 device: &mut GigeDevice,
126 channel: u32,
127 requested: u32,
128) -> Result<u32, GenicamError> {
129 if let Ok(current) = device.get_stream_packet_size(channel).await
130 && current == requested
131 {
132 debug!(
133 channel,
134 packet_size = requested,
135 "camera already holds the requested GVSP packet size; leaving it alone"
136 );
137 return Ok(requested);
138 }
139
140 device
141 .set_stream_packet_size(channel, requested)
142 .await
143 .map_err(|err| GenicamError::transport(err.to_string()))?;
144
145 let effective = match device.get_stream_packet_size(channel).await {
146 Ok(effective) => effective,
147 Err(err) => {
148 warn!(
149 channel,
150 packet_size = requested,
151 error = %err,
152 "could not read GevSCPSPacketSize back; assuming the requested size took effect. \
153 If no frame completes, the camera may have clamped it — pass an explicit packet \
154 size of 1500 to find out"
155 );
156 return Ok(requested);
157 }
158 };
159
160 if effective == requested {
161 return Ok(effective);
162 }
163
164 if effective < MIN_PACKET_SIZE {
165 return Err(GenicamError::transport(format!(
166 "camera reduced the GVSP packet size from {requested} to {effective}, below the \
167 {MIN_PACKET_SIZE}-byte minimum; it cannot stream on this link"
168 )));
169 }
170
171 warn!(
172 channel,
173 requested,
174 effective,
175 "camera clamped the GVSP packet size; the receive path will follow the camera. \
176 The requested size is what the host interface MTU allows, so the camera is the \
177 narrower end of this link"
178 );
179 Ok(effective)
180}
181
182const PROBE_FLOOR: u32 = 1500;
188
189const PROBE_TIMEOUT: Duration = Duration::from_millis(250);
191
192async fn probe_packet_size(
226 device: &mut GigeDevice,
227 channel: u32,
228 socket: &UdpSocket,
229 requested: u32,
230) -> Result<u32, GenicamError> {
231 if requested <= PROBE_FLOOR {
232 return Ok(requested);
235 }
236
237 let negotiated = probe_path_ceiling(device, channel, socket, requested).await?;
238
239 configure_packet_size(device, channel, negotiated).await
248}
249
250async fn probe_path_ceiling(
256 device: &mut GigeDevice,
257 channel: u32,
258 socket: &UdpSocket,
259 requested: u32,
260) -> Result<u32, GenicamError> {
261 if !test_packet_arrives(device, channel, socket, PROBE_FLOOR).await? {
264 debug!(
265 channel,
266 packet_size = requested,
267 "device did not answer a {PROBE_FLOOR}-byte test packet; it likely does not \
268 implement them, so the requested size stands"
269 );
270 return Ok(requested);
271 }
272
273 if test_packet_arrives(device, channel, socket, requested).await? {
274 debug!(
275 channel,
276 packet_size = requested,
277 "path carries the requested GVSP packet size"
278 );
279 return Ok(requested);
280 }
281
282 let (mut lo, mut hi) = (PROBE_FLOOR, requested);
286 while hi - lo > 1 {
287 let mid = lo + (hi - lo) / 2;
288 if test_packet_arrives(device, channel, socket, mid).await? {
289 lo = mid;
290 } else {
291 hi = mid;
292 }
293 }
294
295 warn!(
296 channel,
297 requested,
298 negotiated = lo,
299 "the network path did not carry a {requested}-byte GVSP test packet; negotiated down. \
300 Both the host interface and the camera accept the larger size, so the limit is the \
301 link between them"
302 );
303 Ok(lo)
304}
305
306async fn test_packet_arrives(
308 device: &mut GigeDevice,
309 channel: u32,
310 socket: &UdpSocket,
311 size: u32,
312) -> Result<bool, GenicamError> {
313 let mut scratch = [0u8; 2048];
316 while socket.try_recv_from(&mut scratch).is_ok() {}
317
318 device
319 .request_test_packet(channel, size)
320 .await
321 .map_err(|err| GenicamError::transport(err.to_string()))?;
322
323 let mut buf = vec![0u8; size as usize + 64];
324 match timeout(PROBE_TIMEOUT, socket.recv_from(&mut buf)).await {
325 Ok(Ok(_)) => Ok(true),
326 Ok(Err(_)) | Err(_) => Ok(false),
329 }
330}
331
332const SILENCE_GRACE: Duration = Duration::from_secs(3);
334
335#[cfg(not(windows))]
340const SILENCE_POLL: Duration = Duration::from_millis(500);
341
342#[derive(Debug, Clone, Copy, PartialEq, Eq)]
344enum Silence {
345 Quiet,
348 NoPacket,
350 NoFrame,
352}
353
354struct SilenceWatch {
369 since: Instant,
370 packet_size: u32,
371 saw_packet: bool,
372 saw_frame: bool,
373 warned: bool,
374}
375
376impl SilenceWatch {
377 fn new(packet_size: u32) -> Self {
378 Self {
379 since: Instant::now(),
380 packet_size,
381 saw_packet: false,
382 saw_frame: false,
383 warned: false,
384 }
385 }
386
387 fn record_packet(&mut self) {
389 self.saw_packet = true;
390 }
391
392 fn record_frame(&mut self) {
394 self.saw_frame = true;
395 }
396
397 fn assess(&mut self, elapsed: Duration) -> Silence {
402 if self.warned || self.saw_frame || elapsed < SILENCE_GRACE {
403 return Silence::Quiet;
404 }
405 self.warned = true;
406 if self.saw_packet {
407 Silence::NoFrame
408 } else {
409 Silence::NoPacket
410 }
411 }
412
413 fn tick(&mut self) {
415 let seconds = SILENCE_GRACE.as_secs();
416 match self.assess(self.since.elapsed()) {
417 Silence::Quiet => {}
418 Silence::NoPacket => warn!(
419 packet_size = self.packet_size,
420 seconds,
421 "no GVSP packet has arrived since the stream opened. Check, roughly in order of \
422 how often each is the cause: a host firewall blocking inbound UDP on the stream \
423 port; another application holding control privilege, so AcquisitionStart never \
424 reached the camera; a camera waiting for a trigger; or a network path that \
425 cannot carry packets this large — retry with an explicit packet size of 1500 to \
426 rule that out"
427 ),
428 Silence::NoFrame => warn!(
429 packet_size = self.packet_size,
430 payload_stride = gvsp_payload_size(self.packet_size),
431 seconds,
432 "GVSP packets are arriving but no frame has completed. Reassembly places each \
433 packet at a stride derived from the negotiated packet size, so the usual cause \
434 is the two ends disagreeing about it — retry with an explicit packet size of \
435 1500"
436 ),
437 }
438 }
439}
440
441#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
447enum PacketSizeMode {
448 #[default]
450 Preserve,
451 Auto,
453 Explicit(u32),
455}
456
457pub struct StreamBuilder<'a> {
459 device: &'a mut GigeDevice,
460 iface: Option<Iface>,
461 dest: Option<StreamDest>,
462 rcvbuf_bytes: Option<usize>,
463 target_mtu: Option<u32>,
464 packet_size_mode: PacketSizeMode,
465 packet_delay: Option<u32>,
466 channel: u32,
467 dst_port: u16,
468 probe: bool,
469}
470
471impl<'a> StreamBuilder<'a> {
472 pub fn new(device: &'a mut GigeDevice) -> Self {
479 Self {
480 device,
481 iface: None,
482 dest: None,
483 rcvbuf_bytes: None,
484 target_mtu: None,
485 packet_size_mode: PacketSizeMode::Preserve,
486 packet_delay: None,
487 channel: 0,
488 dst_port: 0,
489 probe: true,
491 }
492 }
493
494 pub fn probe(mut self, enable: bool) -> Self {
511 self.probe = enable;
512 self
513 }
514
515 pub fn iface(mut self, iface: Iface) -> Self {
517 self.iface = Some(iface);
518 self
519 }
520
521 pub fn dest(mut self, dest: StreamDest) -> Self {
523 self.dest = Some(dest);
524 self
525 }
526
527 pub fn target_mtu(mut self, mtu: u32) -> Self {
532 self.target_mtu = Some(mtu);
533 self
534 }
535
536 pub fn auto_packet_size(mut self) -> Self {
542 self.packet_size_mode = PacketSizeMode::Auto;
543 self
544 }
545
546 pub fn packet_size(mut self, size: u32) -> Self {
548 self.packet_size_mode = PacketSizeMode::Explicit(size);
549 self
550 }
551
552 pub fn packet_delay(mut self, delay: u32) -> Self {
554 self.packet_delay = Some(delay);
555 self
556 }
557
558 pub fn destination_port(mut self, port: u16) -> Self {
560 self.dst_port = port;
561 if let Some(dest) = &mut self.dest {
562 *dest = match *dest {
563 StreamDest::Unicast { dst_ip, .. } => StreamDest::Unicast {
564 dst_ip,
565 dst_port: port,
566 },
567 StreamDest::Multicast {
568 group,
569 loopback,
570 ttl,
571 ..
572 } => StreamDest::Multicast {
573 group,
574 port,
575 loopback,
576 ttl,
577 },
578 };
579 }
580 self
581 }
582
583 pub fn multicast(mut self, group: Option<Ipv4Addr>) -> Self {
585 if let Some(group) = group {
586 self.dest = Some(StreamDest::Multicast {
587 group,
588 port: self.dst_port,
589 loopback: false,
590 ttl: 1,
591 });
592 } else {
593 self.dest = None;
594 }
595 self
596 }
597
598 pub fn rcvbuf_bytes(mut self, size: usize) -> Self {
600 self.rcvbuf_bytes = Some(size);
601 self
602 }
603
604 pub fn channel(mut self, channel: u32) -> Self {
606 self.channel = channel;
607 self
608 }
609
610 pub async fn build(self) -> Result<Stream, GenicamError> {
612 let iface = self
613 .iface
614 .ok_or_else(|| GenicamError::transport("stream requires a network interface"))?;
615 let host_ip = iface
616 .ipv4()
617 .ok_or_else(|| GenicamError::transport("interface lacks IPv4 address"))?;
618 let default_port = if self.dst_port == 0 {
619 0x5FFF
620 } else {
621 self.dst_port
622 };
623 let mut dest = self.dest.unwrap_or(StreamDest::Unicast {
624 dst_ip: host_ip,
625 dst_port: default_port,
626 });
627 match &mut dest {
628 StreamDest::Unicast { dst_port, .. } => {
629 if *dst_port == 0 {
630 *dst_port = default_port;
631 }
632 }
633 StreamDest::Multicast { port, .. } => {
634 if *port == 0 {
635 *port = default_port;
636 }
637 }
638 }
639
640 let iface_mtu = nic::mtu(&iface).map_err(|err| GenicamError::transport(err.to_string()))?;
641 let mtu = self
642 .target_mtu
643 .map_or(iface_mtu, |limit| limit.min(iface_mtu));
644
645 let (requested, write_camera) = match self.packet_size_mode {
649 PacketSizeMode::Preserve => {
650 let current = self
651 .device
652 .get_stream_packet_size(self.channel)
653 .await
654 .map_err(|err| GenicamError::transport(err.to_string()))?;
655 debug!(
656 channel = self.channel,
657 packet_size = current,
658 "preserving camera GevSCPSPacketSize; pass auto_packet_size() \
659 or packet_size(n) to negotiate"
660 );
661 (current, false)
662 }
663 PacketSizeMode::Auto => (nic::best_packet_size(mtu), true),
664 PacketSizeMode::Explicit(size) => (size, true),
665 };
666
667 if requested < MIN_PACKET_SIZE {
668 return Err(GenicamError::transport(format!(
669 "GVSP packet size {requested} is below the {MIN_PACKET_SIZE}-byte minimum; \
670 raise the camera's GevSCPSPacketSize, or override with auto_packet_size() \
671 or packet_size(n)"
672 )));
673 }
674 if requested > MAX_PACKET_SIZE {
675 return Err(GenicamError::transport(format!(
676 "GVSP packet size {requested} exceeds the {MAX_PACKET_SIZE}-byte maximum an \
677 IPv4 datagram can carry, which is also the widest value GevSCPSPacketSize can \
678 hold"
679 )));
680 }
681
682 match &dest {
683 StreamDest::Unicast { dst_ip, dst_port } => {
684 info!(%dst_ip, dst_port, channel = self.channel, "configuring unicast stream");
685 self.device
686 .set_stream_destination(self.channel, *dst_ip, *dst_port)
687 .await
688 .map_err(|err| GenicamError::transport(err.to_string()))?;
689 }
690 StreamDest::Multicast { .. } => {
691 info!(
692 channel = self.channel,
693 port = dest.port(),
694 addr = %dest.addr(),
695 "configuring multicast stream parameters"
696 );
697 }
698 }
699
700 let source = PacketSource::Udp(Self::bind_socket(&dest, &iface, self.rcvbuf_bytes).await?);
703
704 let packet_size = if write_camera {
705 configure_packet_size(self.device, self.channel, requested).await?
706 } else {
707 requested
708 };
709 let packet_size = if self.probe {
717 let socket = source
718 .as_udp_socket()
719 .expect("the UDP path always has a socket");
720 probe_packet_size(self.device, self.channel, socket, packet_size).await?
721 } else {
722 packet_size
723 };
724
725 let packet_delay = self.packet_delay.unwrap_or({
731 const DELAY_NS: u32 = 2_000;
732 if packet_size <= 1500 {
733 DELAY_NS / 80
734 } else {
735 0
736 }
737 });
738 self.device
739 .set_stream_packet_delay(self.channel, packet_delay)
740 .await
741 .map_err(|err| GenicamError::transport(err.to_string()))?;
742
743 let source_filter = if dest.is_multicast() {
744 None
745 } else {
746 Some(dest.addr())
747 };
748 let resend_enabled = !dest.is_multicast();
749
750 let params = StreamParams {
751 packet_size,
752 packet_delay,
753 mtu,
754 host: dest.addr(),
755 port: dest.port(),
756 };
757
758 let config = StreamConfig {
759 dest,
760 iface: iface.clone(),
761 packet_size: Some(packet_size),
762 packet_delay: Some(packet_delay),
763 source_filter,
764 resend_enabled,
765 };
766
767 let stats = StreamStatsAccumulator::new();
768 Ok(Stream {
769 source,
770 stats,
771 params,
772 config,
773 })
774 }
775
776 async fn bind_socket(
778 dest: &StreamDest,
779 iface: &Iface,
780 rcvbuf_bytes: Option<usize>,
781 ) -> Result<UdpSocket, GenicamError> {
782 match dest {
783 StreamDest::Unicast { dst_port, .. } => {
784 let bind_ip = IpAddr::V4(
785 iface
786 .ipv4()
787 .ok_or_else(|| GenicamError::transport("interface lacks IPv4 address"))?,
788 );
789 nic::bind_udp(bind_ip, *dst_port, Some(iface.clone()), rcvbuf_bytes)
790 .await
791 .map_err(|err| GenicamError::transport(err.to_string()))
792 }
793 StreamDest::Multicast {
794 group,
795 port,
796 loopback,
797 ttl,
798 } => {
799 let opts = McOptions {
800 loopback: *loopback,
801 ttl: *ttl,
802 rcvbuf_bytes: rcvbuf_bytes.unwrap_or(DEFAULT_RCVBUF_BYTES),
803 ..McOptions::default()
804 };
805 nic::bind_multicast(iface, *group, *port, &opts)
806 .await
807 .map_err(|err| GenicamError::transport(err.to_string()))
808 }
809 }
810 }
811}
812
813pub struct Stream {
816 source: PacketSource,
817 stats: StreamStatsAccumulator,
818 params: StreamParams,
819 config: StreamConfig,
820}
821
822impl Stream {
823 pub fn socket(&self) -> Option<&UdpSocket> {
825 self.source.as_udp_socket()
826 }
827
828 pub(crate) fn into_parts(
830 self,
831 ) -> (
832 PacketSource,
833 StreamStatsAccumulator,
834 StreamParams,
835 StreamConfig,
836 ) {
837 (self.source, self.stats, self.params, self.config)
838 }
839
840 pub fn params(&self) -> StreamParams {
842 self.params
843 }
844
845 pub fn stats_handle(&self) -> StreamStatsAccumulator {
847 self.stats.clone()
848 }
849
850 pub fn stats(&self) -> StreamStats {
852 self.stats.snapshot()
853 }
854
855 pub fn config(&self) -> &StreamConfig {
857 &self.config
858 }
859}
860
861impl<'a> From<&'a mut GigeDevice> for StreamBuilder<'a> {
862 fn from(device: &'a mut GigeDevice) -> Self {
863 StreamBuilder::new(device)
864 }
865}
866
867const DEFAULT_FRAME_TIMEOUT: Duration = Duration::from_millis(100);
873
874const GVSP_HEADER_SIZE: usize = 8;
876const IPV4_HEADER_SIZE: usize = 20;
878const UDP_HEADER_SIZE: usize = 8;
880const GVSP_PACKET_OVERHEAD: usize = IPV4_HEADER_SIZE + UDP_HEADER_SIZE + GVSP_HEADER_SIZE;
882
883fn gvsp_payload_size(packet_size: u32) -> usize {
889 (packet_size as usize).saturating_sub(GVSP_PACKET_OVERHEAD)
890}
891
892#[cfg(any(not(windows), test))]
901#[cfg_attr(windows, allow(dead_code))]
902#[derive(Debug)]
903struct FrameAssemblyState {
904 block_id: u64,
905 width: u32,
906 height: u32,
907 pixel_format: PixelFormat,
908 timestamp: u64,
909 expected_packets: Option<usize>,
910 bitmap: Option<PacketBitmap>,
911 received_packet_ids: HashSet<u32>,
912 payload: BytesMut,
913 packet_payload_size: usize,
914 started: Instant,
915}
916
917#[cfg(any(not(windows), test))]
918impl FrameAssemblyState {
919 fn new(
920 block_id: u64,
921 width: u32,
922 height: u32,
923 pixel_format: PixelFormat,
924 timestamp: u64,
925 packet_payload_size: usize,
926 ) -> Self {
927 Self {
928 block_id,
929 width,
930 height,
931 pixel_format,
932 timestamp,
933 expected_packets: None,
934 bitmap: None,
935 received_packet_ids: HashSet::new(),
936 payload: BytesMut::new(),
937 packet_payload_size,
938 started: Instant::now(),
939 }
940 }
941
942 fn ingest(&mut self, packet_id: u32, data: &[u8]) -> bool {
944 if packet_id == 0 || !self.received_packet_ids.insert(packet_id) {
946 return false;
947 }
948
949 let pid = packet_id as usize;
950
951 if let Some(ref mut bitmap) = self.bitmap
954 && !bitmap.set(pid.saturating_sub(1))
955 {
956 return false; }
958
959 let offset = pid.saturating_sub(1) * self.packet_payload_size;
961 let required = offset + data.len();
962 if self.payload.len() < required {
963 self.payload.resize(required, 0);
964 }
965 self.payload[offset..offset + data.len()].copy_from_slice(data);
966 true
967 }
968
969 fn set_trailer_packet_id(&mut self, trailer_packet_id: u32) {
975 if self.expected_packets.is_none() {
976 let expected_packets = trailer_packet_id.saturating_sub(1) as usize;
977 let mut bitmap = PacketBitmap::new(expected_packets);
978 for packet_id in &self.received_packet_ids {
979 if *packet_id < trailer_packet_id {
980 bitmap.set(packet_id.saturating_sub(1) as usize);
981 }
982 }
983 self.expected_packets = Some(expected_packets);
984 self.bitmap = Some(bitmap);
985 }
986 }
987
988 fn is_complete(&self) -> bool {
990 self.bitmap.as_ref().is_some_and(|b| b.is_complete())
991 }
992
993 fn is_expired(&self, timeout: Duration) -> bool {
995 self.started.elapsed() > timeout
996 }
997
998 #[allow(dead_code)]
1000 fn missing_ranges(&self) -> Vec<std::ops::RangeInclusive<u32>> {
1001 self.bitmap
1002 .as_ref()
1003 .map(|b| b.missing_ranges())
1004 .unwrap_or_default()
1005 }
1006}
1007
1008#[cfg(windows)]
1009struct WindowsFrameAssembly {
1010 block_id: u64,
1011 width: u32,
1012 height: u32,
1013 pixel_format: PixelFormat,
1014 timestamp: u64,
1015 next_packet_id: u32,
1016 payload: BytesMut,
1017 started: Instant,
1018}
1019
1020#[cfg(windows)]
1021struct WindowsReceiver {
1022 frames: tokio::sync::mpsc::Receiver<Result<Frame, GenicamError>>,
1023 stop: Arc<AtomicBool>,
1024 join: Option<thread::JoinHandle<()>>,
1025}
1026
1027#[cfg(windows)]
1028fn windows_frame_receiver(
1029 source: PacketSource,
1030 packet_size: u32,
1031 stats: StreamStatsAccumulator,
1032 frame_timeout_ns: Arc<AtomicU64>,
1033) -> WindowsReceiver {
1034 let (tx, rx) = tokio::sync::mpsc::channel(2);
1035 let stop = Arc::new(AtomicBool::new(false));
1036
1037 let PacketSource::Udp(socket) = source;
1038 let socket = match socket.into_std() {
1039 Ok(socket) => socket,
1040 Err(err) => {
1041 let _ = tx.try_send(Err(GenicamError::transport(format!(
1042 "socket conversion failed: {err}"
1043 ))));
1044 return WindowsReceiver {
1045 frames: rx,
1046 stop,
1047 join: None,
1048 };
1049 }
1050 };
1051 if let Err(err) = socket.set_nonblocking(false) {
1052 let _ = tx.try_send(Err(GenicamError::transport(format!(
1053 "set blocking mode failed: {err}"
1054 ))));
1055 return WindowsReceiver {
1056 frames: rx,
1057 stop,
1058 join: None,
1059 };
1060 }
1061 if let Err(err) = socket.set_read_timeout(Some(Duration::from_millis(100))) {
1062 let _ = tx.try_send(Err(GenicamError::transport(format!(
1063 "set stream socket read timeout failed: {err}"
1064 ))));
1065 return WindowsReceiver {
1066 frames: rx,
1067 stop,
1068 join: None,
1069 };
1070 }
1071
1072 let reader_stop = Arc::clone(&stop);
1073 let reader = thread::spawn(move || {
1074 let mut recv_buffer = vec![0u8; (packet_size as usize + 64).max(4096)];
1075 let mut active: Option<WindowsFrameAssembly> = None;
1076 let mut silence = SilenceWatch::new(packet_size);
1077
1078 while !reader_stop.load(Ordering::Acquire) {
1079 let (len, _) = match socket.recv_from(&mut recv_buffer) {
1080 Ok(result) => result,
1081 Err(err)
1082 if matches!(
1083 err.kind(),
1084 ErrorKind::WouldBlock | ErrorKind::TimedOut | ErrorKind::Interrupted
1085 ) =>
1086 {
1087 if active.as_ref().is_some_and(|frame| {
1088 frame.started.elapsed().as_nanos()
1089 > frame_timeout_ns.load(Ordering::Relaxed) as u128
1090 }) {
1091 active = None;
1092 stats.record_drop();
1093 }
1094 silence.tick();
1095 continue;
1096 }
1097 Err(err) => {
1098 let _ = tx.try_send(Err(GenicamError::transport(format!(
1099 "socket receive failed: {err}"
1100 ))));
1101 break;
1102 }
1103 };
1104
1105 silence.record_packet();
1106
1107 let packet = match gvsp::parse_packet(&recv_buffer[..len]) {
1108 Ok(packet) => packet,
1109 Err(_) => continue,
1110 };
1111
1112 match packet {
1113 GvspPacket::Leader {
1114 block_id,
1115 width,
1116 height,
1117 pixel_format,
1118 timestamp,
1119 ..
1120 } => {
1121 if active.take().is_some() {
1122 stats.record_drop();
1123 }
1124 active = Some(WindowsFrameAssembly {
1125 block_id,
1126 width,
1127 height,
1128 pixel_format: PixelFormat::from_code(pixel_format),
1129 timestamp,
1130 next_packet_id: 1,
1131 payload: BytesMut::new(),
1132 started: Instant::now(),
1133 });
1134 }
1135 GvspPacket::Payload {
1136 block_id,
1137 packet_id,
1138 data,
1139 } => {
1140 let Some(frame) = active.as_mut() else {
1141 continue;
1142 };
1143 if frame.block_id != block_id || frame.next_packet_id != packet_id {
1144 active = None;
1145 stats.record_drop();
1146 continue;
1147 }
1148 frame.payload.extend_from_slice(data.as_ref());
1149 frame.next_packet_id += 1;
1150 stats.record_packet();
1151 }
1152 GvspPacket::Trailer {
1153 block_id,
1154 packet_id,
1155 status,
1156 chunk_data,
1157 ..
1158 } => {
1159 let Some(mut frame) = active.take() else {
1160 continue;
1161 };
1162 let expected_bytes = frame.width as usize
1163 * frame.height as usize
1164 * frame.pixel_format.bytes_per_pixel().unwrap_or(1);
1165 if frame.block_id != block_id
1166 || frame.next_packet_id != packet_id
1167 || status != 0
1168 || frame.payload.len() < expected_bytes
1169 {
1170 stats.record_drop();
1171 continue;
1172 }
1173 frame.payload.truncate(expected_bytes);
1174 let chunks = if chunk_data.is_empty() {
1175 None
1176 } else {
1177 crate::chunks::parse_chunk_bytes(chunk_data.as_ref()).ok()
1178 };
1179 let completed = Frame {
1180 payload: frame.payload.freeze(),
1181 width: frame.width,
1182 height: frame.height,
1183 pixel_format: frame.pixel_format,
1184 chunks,
1185 ts_dev: Some(frame.timestamp),
1186 ts_host: None,
1187 };
1188 stats.record_frame(completed.payload.len(), None);
1189 silence.record_frame();
1190 if tx.try_send(Ok(completed)).is_err() {
1191 stats.record_backpressure_drop();
1192 }
1193 }
1194 }
1195 }
1196 });
1197
1198 WindowsReceiver {
1199 frames: rx,
1200 stop,
1201 join: Some(reader),
1202 }
1203}
1204
1205pub struct FrameStream {
1223 #[cfg(not(windows))]
1224 source: PacketSource,
1225 stats: StreamStatsAccumulator,
1226 params: StreamParams,
1227 config: StreamConfig,
1228 #[cfg(not(windows))]
1229 recv_buffer: Vec<u8>,
1230 #[cfg(not(windows))]
1231 active: Option<FrameAssemblyState>,
1232 #[cfg(not(windows))]
1233 silence: SilenceWatch,
1234 frame_timeout: Duration,
1235 #[cfg(windows)]
1236 frame_timeout_ns: Arc<AtomicU64>,
1237 #[cfg(windows)]
1238 frame_rx: tokio::sync::mpsc::Receiver<Result<Frame, GenicamError>>,
1239 #[cfg(windows)]
1240 reader_stop: Arc<AtomicBool>,
1241 #[cfg(windows)]
1242 reader: Option<thread::JoinHandle<()>>,
1243 time_sync: Option<TimeSync>,
1244}
1245
1246impl FrameStream {
1247 pub fn new(stream: Stream, time_sync: Option<TimeSync>) -> Self {
1251 let (source, stats, params, config) = stream.into_parts();
1252 let frame_timeout = DEFAULT_FRAME_TIMEOUT;
1253 #[cfg(not(windows))]
1254 let buffer_size = (params.packet_size as usize + 64).max(4096);
1255 #[cfg(windows)]
1256 let frame_timeout_ns = Arc::new(AtomicU64::new(
1257 frame_timeout.as_nanos().min(u64::MAX as u128) as u64,
1258 ));
1259 #[cfg(windows)]
1260 let WindowsReceiver {
1261 frames: frame_rx,
1262 stop: reader_stop,
1263 join: reader,
1264 } = windows_frame_receiver(
1265 source,
1266 params.packet_size,
1267 stats.clone(),
1268 Arc::clone(&frame_timeout_ns),
1269 );
1270
1271 Self {
1272 #[cfg(not(windows))]
1273 source,
1274 stats,
1275 params,
1276 config,
1277 #[cfg(not(windows))]
1278 recv_buffer: vec![0u8; buffer_size],
1279 #[cfg(not(windows))]
1280 active: None,
1281 #[cfg(not(windows))]
1282 silence: SilenceWatch::new(params.packet_size),
1283 frame_timeout,
1284 #[cfg(windows)]
1285 frame_timeout_ns,
1286 #[cfg(windows)]
1287 frame_rx,
1288 #[cfg(windows)]
1289 reader_stop,
1290 #[cfg(windows)]
1291 reader,
1292 time_sync,
1293 }
1294 }
1295
1296 pub fn set_frame_timeout(&mut self, timeout: Duration) {
1301 self.frame_timeout = timeout;
1302 #[cfg(windows)]
1303 self.frame_timeout_ns.store(
1304 timeout.as_nanos().min(u64::MAX as u128) as u64,
1305 Ordering::Relaxed,
1306 );
1307 }
1308
1309 pub fn set_time_sync(&mut self, time_sync: TimeSync) {
1311 self.time_sync = Some(time_sync);
1312 }
1313
1314 pub fn stats_handle(&self) -> StreamStatsAccumulator {
1316 self.stats.clone()
1317 }
1318
1319 pub fn stats(&self) -> StreamStats {
1321 self.stats.snapshot()
1322 }
1323
1324 pub fn params(&self) -> StreamParams {
1326 self.params
1327 }
1328
1329 pub fn config(&self) -> &StreamConfig {
1331 &self.config
1332 }
1333
1334 pub fn socket(&self) -> Option<&UdpSocket> {
1336 #[cfg(not(windows))]
1337 {
1338 self.source.as_udp_socket()
1339 }
1340 #[cfg(windows)]
1341 {
1342 None
1343 }
1344 }
1345
1346 pub async fn next_frame(&mut self) -> Result<Option<Frame>, GenicamError> {
1352 #[cfg(windows)]
1353 {
1354 return match self.frame_rx.recv().await {
1355 Some(Ok(mut frame)) => {
1356 if let Some(timestamp) = frame.ts_dev {
1357 frame.ts_host = self
1358 .time_sync
1359 .as_ref()
1360 .map(|time_sync| time_sync.to_host_time(timestamp));
1361 }
1362 Ok(Some(frame))
1363 }
1364 Some(Err(err)) => Err(err),
1365 None => Ok(None),
1366 };
1367 }
1368
1369 #[cfg(not(windows))]
1370 {
1371 loop {
1372 if let Some(ref active) = self.active
1374 && active.is_expired(self.frame_timeout)
1375 {
1376 let block_id = active.block_id;
1377 warn!(
1378 block_id,
1379 "frame assembly timeout, dropping incomplete frame"
1380 );
1381 self.stats.record_drop();
1382 self.active = None;
1383 }
1384
1385 let raw = match timeout(SILENCE_POLL, self.source.recv(&mut self.recv_buffer)).await
1392 {
1393 Ok(Ok(data)) if data.is_empty() => return Ok(None), Ok(Ok(data)) => data,
1395 Ok(Err(e)) => return Err(e),
1396 Err(_elapsed) => {
1397 self.silence.tick();
1398 continue;
1399 }
1400 };
1401 self.silence.record_packet();
1402
1403 let packet = match gvsp::parse_packet(&raw) {
1405 Ok(p) => p,
1406 Err(e) => {
1407 trace!(error = %e, "discarding malformed GVSP packet");
1408 continue;
1409 }
1410 };
1411
1412 match packet {
1414 GvspPacket::Leader {
1415 block_id,
1416 width,
1417 height,
1418 pixel_format,
1419 timestamp,
1420 ..
1421 } => {
1422 if let Some(ref prev) = self.active
1424 && prev.block_id != block_id
1425 {
1426 debug!(
1427 old_block = prev.block_id,
1428 new_block = block_id,
1429 "new leader arrived, dropping incomplete frame"
1430 );
1431 self.stats.record_drop();
1432 }
1433
1434 let pixel_format = PixelFormat::from_code(pixel_format);
1435 let packet_payload = gvsp_payload_size(self.params.packet_size);
1436
1437 self.active = Some(FrameAssemblyState::new(
1438 block_id,
1439 width,
1440 height,
1441 pixel_format,
1442 timestamp,
1443 packet_payload,
1444 ));
1445 trace!(block_id, %pixel_format, width, height, "frame leader received");
1446 }
1447
1448 GvspPacket::Payload {
1449 block_id,
1450 packet_id,
1451 data,
1452 } => {
1453 if let Some(ref mut active) = self.active
1454 && active.block_id == block_id
1455 && active.ingest(packet_id, data.as_ref())
1456 {
1457 self.stats.record_packet();
1458 }
1459 }
1460
1461 GvspPacket::Trailer {
1462 block_id,
1463 packet_id,
1464 status,
1465 chunk_data,
1466 ..
1467 } => {
1468 let Some(mut active) = self.active.take() else {
1469 continue;
1470 };
1471
1472 if active.block_id != block_id {
1473 self.stats.record_drop();
1475 continue;
1476 }
1477
1478 if status != 0 {
1479 warn!(block_id, status, "trailer reported non-zero status");
1480 self.stats.record_drop();
1481 continue;
1482 }
1483
1484 active.set_trailer_packet_id(packet_id);
1485 if !active.is_complete() {
1486 warn!(
1487 block_id,
1488 trailer_packet_id = packet_id,
1489 "dropping incomplete frame"
1490 );
1491 self.stats.record_drop();
1492 continue;
1493 }
1494
1495 let ts_host = self
1497 .time_sync
1498 .as_ref()
1499 .map(|ts| ts.to_host_time(active.timestamp));
1500
1501 let chunks = if chunk_data.is_empty() {
1502 None
1503 } else {
1504 match crate::chunks::parse_chunk_bytes(&chunk_data) {
1505 Ok(map) => Some(map),
1506 Err(e) => {
1507 debug!(error = %e, "failed to parse chunk data");
1508 None
1509 }
1510 }
1511 };
1512
1513 let payload = active.payload.freeze();
1516
1517 let frame = Frame {
1518 payload,
1519 width: active.width,
1520 height: active.height,
1521 pixel_format: active.pixel_format,
1522 chunks,
1523 ts_dev: Some(active.timestamp),
1524 ts_host,
1525 };
1526
1527 let latency = frame
1532 .host_time()
1533 .and_then(|ts| SystemTime::now().duration_since(ts).ok());
1534 self.stats.record_frame(frame.payload.len(), latency);
1535 self.silence.record_frame();
1536
1537 debug!(
1538 block_id,
1539 width = frame.width,
1540 height = frame.height,
1541 bytes = frame.payload.len(),
1542 "frame complete"
1543 );
1544
1545 return Ok(Some(frame));
1546 }
1547 }
1548 }
1549 }
1550 }
1551}
1552
1553#[cfg(windows)]
1554impl Drop for FrameStream {
1555 fn drop(&mut self) {
1556 self.reader_stop.store(true, Ordering::Release);
1557 if let Some(reader) = self.reader.take() {
1558 let _ = reader.join();
1559 }
1560 }
1561}
1562
1563#[cfg(feature = "u3v")]
1585#[cfg_attr(docsrs, doc(cfg(feature = "u3v")))]
1586pub struct U3vFrameStream {
1587 rx: tokio::sync::mpsc::Receiver<Result<Frame, GenicamError>>,
1588 stop_tx: tokio::sync::watch::Sender<bool>,
1589 _reader: tokio::task::JoinHandle<()>,
1590}
1591
1592#[cfg(feature = "u3v")]
1593impl U3vFrameStream {
1594 pub fn start<T: crate::u3v::usb::UsbTransfer + 'static>(
1601 mut stream: crate::u3v::stream::U3vStream<T>,
1602 ) -> Self {
1603 let (tx, rx) = tokio::sync::mpsc::channel(4);
1604 let (stop_tx, stop_rx) = tokio::sync::watch::channel(false);
1605
1606 let reader = tokio::task::spawn_blocking(move || {
1607 loop {
1608 if *stop_rx.borrow() {
1609 break;
1610 }
1611 match stream.next_frame() {
1612 Ok(raw) => {
1613 let pixel_format = PixelFormat::from_code(raw.leader.pixel_format);
1614 let frame = Frame {
1615 payload: raw.payload,
1616 width: raw.leader.width,
1617 height: raw.leader.height,
1618 pixel_format,
1619 chunks: None,
1620 ts_dev: Some(raw.leader.timestamp),
1621 ts_host: None,
1622 };
1623 if tx.blocking_send(Ok(frame)).is_err() {
1624 break; }
1626 }
1627 Err(e) => {
1628 let _ = tx.blocking_send(Err(GenicamError::transport(e.to_string())));
1629 break;
1630 }
1631 }
1632 }
1633 });
1634
1635 Self {
1636 rx,
1637 stop_tx,
1638 _reader: reader,
1639 }
1640 }
1641
1642 pub async fn next_frame(&mut self) -> Result<Option<Frame>, GenicamError> {
1646 match self.rx.recv().await {
1647 Some(Ok(frame)) => Ok(Some(frame)),
1648 Some(Err(e)) => Err(e),
1649 None => Ok(None),
1650 }
1651 }
1652
1653 pub fn stop(&self) {
1655 let _ = self.stop_tx.send(true);
1656 }
1657}
1658
1659#[cfg(feature = "u3v")]
1677#[cfg_attr(docsrs, doc(cfg(feature = "u3v")))]
1678pub struct U3vStreamBuilder<'a, T: crate::u3v::usb::UsbTransfer + 'static> {
1679 camera: &'a mut crate::Camera<crate::U3vRegisterIo<T>>,
1680 payload_size: Option<u64>,
1681}
1682
1683#[cfg(feature = "u3v")]
1684impl<'a, T: crate::u3v::usb::UsbTransfer + 'static> U3vStreamBuilder<'a, T> {
1685 pub fn new(camera: &'a mut crate::Camera<crate::U3vRegisterIo<T>>) -> Self {
1687 Self {
1688 camera,
1689 payload_size: None,
1690 }
1691 }
1692
1693 pub fn payload_size(mut self, size: u64) -> Self {
1698 self.payload_size = Some(size);
1699 self
1700 }
1701
1702 pub fn build(self) -> Result<U3vFrameStream, GenicamError> {
1705 let payload_size = match self.payload_size {
1706 Some(s) => s,
1707 None => {
1708 let width: u64 = self
1709 .camera
1710 .get("Width")?
1711 .parse()
1712 .map_err(|e| GenicamError::parse(format!("Width: {e}")))?;
1713 let height: u64 = self
1714 .camera
1715 .get("Height")?
1716 .parse()
1717 .map_err(|e| GenicamError::parse(format!("Height: {e}")))?;
1718 let pf_str = self.camera.get("PixelFormat")?;
1719 let bpp = PixelFormat::from_name(&pf_str)
1720 .bytes_per_pixel()
1721 .unwrap_or(1) as u64;
1722 width * height * bpp
1723 }
1724 };
1725
1726 let mut device = self.camera.transport().lock_device()?;
1727 let stream = device
1728 .open_stream(payload_size)
1729 .map_err(|e| GenicamError::transport(e.to_string()))?;
1730
1731 Ok(U3vFrameStream::start(stream))
1732 }
1733}
1734
1735#[cfg(test)]
1736mod tests {
1737 use super::*;
1738
1739 #[test]
1740 fn frame_assembly_state_ingest_tracks_packets() {
1741 let mut state = FrameAssemblyState::new(1, 640, 480, PixelFormat::Mono8, 0, 1400);
1742
1743 assert!(state.ingest(1, &[1, 2, 3]));
1745 assert!(state.ingest(2, &[4, 5, 6]));
1746
1747 assert!(!state.ingest(1, &[1, 2, 3]));
1749
1750 state.set_trailer_packet_id(3);
1751 assert!(state.is_complete());
1752 }
1753
1754 #[test]
1755 fn gvsp_payload_size_excludes_ip_udp_and_gvsp_headers() {
1756 assert_eq!(gvsp_payload_size(1458), 1422);
1757 assert_eq!(gvsp_payload_size(9000), 8964);
1758 }
1759
1760 #[test]
1761 fn silence_watch_stays_quiet_inside_the_grace_period() {
1762 let mut watch = SilenceWatch::new(1500);
1763 assert_eq!(
1764 watch.assess(SILENCE_GRACE - Duration::from_millis(1)),
1765 Silence::Quiet
1766 );
1767 assert_eq!(watch.assess(SILENCE_GRACE), Silence::NoPacket);
1769 }
1770
1771 #[test]
1772 fn silence_watch_distinguishes_no_packet_from_no_frame() {
1773 let mut nothing = SilenceWatch::new(1500);
1774 assert_eq!(nothing.assess(SILENCE_GRACE), Silence::NoPacket);
1775
1776 let mut packets = SilenceWatch::new(1500);
1780 packets.record_packet();
1781 assert_eq!(packets.assess(SILENCE_GRACE), Silence::NoFrame);
1782 }
1783
1784 #[test]
1785 fn silence_watch_speaks_once_and_never_after_a_frame() {
1786 let mut watch = SilenceWatch::new(1500);
1787 assert_eq!(watch.assess(SILENCE_GRACE), Silence::NoPacket);
1788 assert_eq!(watch.assess(SILENCE_GRACE * 10), Silence::Quiet);
1790
1791 let mut healthy = SilenceWatch::new(1500);
1792 healthy.record_packet();
1793 healthy.record_frame();
1794 assert_eq!(healthy.assess(SILENCE_GRACE * 10), Silence::Quiet);
1795 }
1796
1797 #[test]
1798 fn frame_assembly_state_rejects_missing_payload_packets() {
1799 let mut state = FrameAssemblyState::new(1, 640, 480, PixelFormat::Mono8, 0, 1400);
1800 assert!(state.ingest(1, &[1, 2, 3]));
1801
1802 state.set_trailer_packet_id(3);
1803
1804 assert!(!state.is_complete());
1805 }
1806
1807 #[test]
1808 fn frame_assembly_state_accepts_out_of_order_payload_packets() {
1809 let mut state = FrameAssemblyState::new(1, 640, 480, PixelFormat::Mono8, 0, 1400);
1810 assert!(state.ingest(2, &[4, 5, 6]));
1811 assert!(state.ingest(1, &[1, 2, 3]));
1812
1813 state.set_trailer_packet_id(3);
1814
1815 assert!(state.is_complete());
1816 }
1817
1818 #[test]
1819 fn frame_assembly_state_timeout() {
1820 let state = FrameAssemblyState::new(1, 640, 480, PixelFormat::Mono8, 0, 1400);
1821 assert!(!state.is_expired(Duration::from_secs(10)));
1822 assert!(state.is_expired(Duration::ZERO));
1823 }
1824}