Skip to main content

dvb_stream/
t2mi_stream.rs

1//! [`T2miEventStream`] — async [`futures_core::Stream`] of owned T2-MI events.
2//!
3//! Wraps [`dvb_t2mi::pump::T2miPump`] over any [`tokio::io::AsyncRead`] source,
4//! yielding one [`dvb_t2mi::pump::T2miEvent`] per complete, CRC-valid T2-MI packet.
5//! Events own their bytes via `bytes::Bytes` and are `'static`, `Clone`, and
6//! `Send + Sync`.
7//!
8//! # Cancellation
9//!
10//! Dropping the `T2miEventStream` cancels cleanly — no internal tasks are
11//! spawned.
12
13use std::collections::VecDeque;
14use std::pin::Pin;
15use std::task::{Context, Poll};
16
17use dvb_t2mi::pump::{T2miEvent, T2miPump};
18use futures_core::Stream;
19use tokio::io::AsyncRead;
20use tokio::io::ReadBuf;
21
22use crate::ResyncStats;
23use crate::resync::{TS_PACKET_SIZE, TS_SYNC_BYTE, resync};
24
25/// Read buffer size: 7 × 188 bytes (matches typical DVB UDP payload size).
26const READ_BUF_SIZE: usize = TS_PACKET_SIZE * 7;
27
28/// Async [`Stream`] of [`T2miEvent`]s from a raw TS byte source.
29///
30/// Feed any [`tokio::io::AsyncRead`] byte source and receive one `T2miEvent`
31/// per complete, CRC-valid T2-MI packet.
32///
33/// The adapter performs 188-byte TS packet alignment using the same resync
34/// logic as [`SectionStream`](crate::SectionStream) (see [`crate::resync`]).
35///
36/// # Cancellation
37///
38/// Drop the stream. No internal tasks are spawned.
39pub struct T2miEventStream<R> {
40    reader: R,
41    pump: T2miPump,
42    queue: VecDeque<T2miEvent>,
43    buf: Vec<u8>,
44    /// Carry-over bytes from the previous read (partial TS packet).
45    filled: usize,
46    /// Whether the reader has reached EOF.
47    eof: bool,
48    /// True once the stream has found an initial 0x47 sync byte.
49    synced: bool,
50    /// Resync statistics.
51    resync_stats: ResyncStats,
52}
53
54impl<R: AsyncRead + Unpin> T2miEventStream<R> {
55    /// Create a `T2miEventStream` from a TS-encapsulated source on `pid`.
56    ///
57    /// `pid` is the 13-bit T2-MI PID from the PMT (e.g. `0x0006`).
58    #[must_use]
59    pub fn new(reader: R, pid: u16) -> Self {
60        Self::with_pump(reader, T2miPump::new(pid))
61    }
62
63    /// Create a `T2miEventStream` with an already-constructed [`T2miPump`].
64    #[must_use]
65    pub fn with_pump(reader: R, pump: T2miPump) -> Self {
66        Self {
67            reader,
68            pump,
69            queue: VecDeque::new(),
70            buf: vec![0u8; READ_BUF_SIZE],
71            filled: 0,
72            eof: false,
73            synced: false,
74            resync_stats: ResyncStats::default(),
75        }
76    }
77
78    /// Access the underlying pump statistics.
79    #[must_use]
80    pub fn stats(&self) -> dvb_t2mi::pump::Stats {
81        self.pump.stats()
82    }
83
84    /// Access the resync statistics.
85    #[must_use]
86    pub fn resync_stats(&self) -> ResyncStats {
87        self.resync_stats
88    }
89
90    /// Feed a completed read into the pump and push events into `queue`.
91    fn feed_buf(&mut self, data: &[u8]) {
92        let start = if self.synced {
93            0
94        } else {
95            match resync(data) {
96                Some(off) => {
97                    self.synced = true;
98                    self.resync_stats.resyncs += 1;
99                    self.resync_stats.bytes_discarded += off as u64;
100                    off
101                }
102                None => {
103                    self.resync_stats.bytes_discarded += data.len() as u64;
104                    return;
105                }
106            }
107        };
108
109        // Per-packet loop with mid-stream desync detection.
110        let aligned = &data[start..];
111        let n_packets = aligned.len() / TS_PACKET_SIZE;
112        for i in 0..n_packets {
113            let pkt_start = i * TS_PACKET_SIZE;
114            let pkt = &aligned[pkt_start..pkt_start + TS_PACKET_SIZE];
115            if pkt[0] != TS_SYNC_BYTE {
116                // Mid-stream desync: discard rest of this chunk and re-resync.
117                self.resync_stats.desyncs += 1;
118                let discarded = aligned.len() - pkt_start;
119                self.resync_stats.bytes_discarded += discarded as u64;
120                self.synced = false;
121                self.filled = 0;
122                return;
123            }
124            for event in self.pump.feed_ts(pkt) {
125                self.queue.push_back(event);
126            }
127        }
128
129        self.filled = 0;
130    }
131}
132
133impl<R: AsyncRead + Unpin> Stream for T2miEventStream<R> {
134    type Item = T2miEvent;
135
136    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
137        let this = self.get_mut();
138
139        loop {
140            if let Some(event) = this.queue.pop_front() {
141                return Poll::Ready(Some(event));
142            }
143
144            if this.eof {
145                return Poll::Ready(None);
146            }
147
148            let buf_len = this.buf.len();
149            let read_from = this.filled;
150            let mut read_buf = ReadBuf::new(&mut this.buf[read_from..buf_len]);
151
152            match Pin::new(&mut this.reader).poll_read(cx, &mut read_buf) {
153                Poll::Pending => return Poll::Pending,
154                Poll::Ready(Err(_)) => {
155                    this.eof = true;
156                    return Poll::Ready(None);
157                }
158                Poll::Ready(Ok(())) => {
159                    let n = read_buf.filled().len();
160                    if n == 0 {
161                        this.eof = true;
162                        return Poll::Ready(None);
163                    }
164                    let total = read_from + n;
165                    let data: Vec<u8> = this.buf[..total].to_vec();
166                    this.feed_buf(&data);
167                }
168            }
169        }
170    }
171}
172
173/// UDP/multicast convenience constructor — enabled by the `udp` feature.
174#[cfg(feature = "udp")]
175impl T2miEventStream<crate::section_stream::UdpReader> {
176    /// Bind a UDP socket to `bind_addr` and join `multicast_addr`.
177    ///
178    /// `pid` is the 13-bit T2-MI PID from the PMT.
179    ///
180    /// # Errors
181    ///
182    /// Returns a [`std::io::Error`] if binding or joining the multicast group
183    /// fails.
184    pub async fn bind_multicast(
185        bind_addr: std::net::SocketAddrV4,
186        multicast_addr: std::net::Ipv4Addr,
187        pid: u16,
188    ) -> std::io::Result<Self> {
189        use tokio::net::UdpSocket;
190        let socket = UdpSocket::bind(bind_addr).await?;
191        socket.join_multicast_v4(multicast_addr, *bind_addr.ip())?;
192        Ok(Self::new(crate::section_stream::UdpReader { socket }, pid))
193    }
194}