Skip to main content

dvb_stream/
section_stream.rs

1//! [`SectionStream`] — async [`futures_core::Stream`] of owned SI section events.
2//!
3//! Wraps [`dvb_si::demux::SiDemux`] over any [`tokio::io::AsyncRead`] source,
4//! yielding one [`dvb_si::demux::SectionEvent`] per changed complete section.
5//! Events are already owned (`bytes::Bytes` internally) and therefore `'static`,
6//! `Clone`, and `Send + Sync` — no yoke wrapping is required.
7//!
8//! # Usage
9//!
10//! ```no_run
11//! use futures_core::Stream;
12//! use std::pin::Pin;
13//!
14//! // Stream from a file:
15//! // let f = tokio::fs::File::open("stream.ts").await?;
16//! // let mut s = dvb_stream::SectionStream::new(f);
17//! // while let Some(event) = futures_util::StreamExt::next(&mut s).await { ... }
18//! ```
19//!
20//! # Cancellation
21//!
22//! Dropping the `SectionStream` cancels cleanly — no internal tasks are
23//! spawned. Any pending I/O is abandoned; partially reassembled sections are
24//! discarded.
25
26use std::collections::VecDeque;
27use std::pin::Pin;
28use std::task::{Context, Poll};
29
30use dvb_si::demux::{SectionEvent, SiDemux, SiDemuxBuilder};
31use futures_core::Stream;
32use tokio::io::AsyncRead;
33use tokio::io::ReadBuf;
34
35use crate::resync::{aligned_packets, resync, TS_PACKET_SIZE};
36
37/// Read buffer size: 7 × 188 bytes = 1316 bytes (one UDP/RTP payload
38/// as used in DVB multicast delivery per ETSI TR 101 290 §B).
39const READ_BUF_SIZE: usize = TS_PACKET_SIZE * 7;
40
41/// Async [`Stream`] of [`SectionEvent`]s from a raw TS byte source.
42///
43/// Feed any [`tokio::io::AsyncRead`] byte source (file, TCP socket, UDP
44/// socket) and receive one `SectionEvent` per changed complete SI section.
45///
46/// Internally the adapter:
47/// 1. Reads bytes from `reader` into a fixed-size buffer.
48/// 2. Resyncs on the first `0x47` sync byte (via [`crate::resync::resync`]).
49/// 3. Feeds each aligned 188-byte packet into the owned [`SiDemux`].
50/// 4. Yields events from the demux's output queue before reading more.
51///
52/// # Owned events
53///
54/// [`SectionEvent`] already owns its section bytes via `bytes::Bytes` and is
55/// `'static`, `Clone`, and `Send + Sync`. No additional wrapping is needed.
56///
57/// # Cancellation
58///
59/// Drop the stream. No internal tasks are spawned.
60pub struct SectionStream<R> {
61    reader: R,
62    demux: SiDemux,
63    queue: VecDeque<SectionEvent>,
64    buf: Vec<u8>,
65    /// Byte offset within `buf` for the next read.
66    filled: usize,
67    /// Whether the reader has reached EOF.
68    eof: bool,
69    /// True once we have found a sync byte and trimmed the leading garbage.
70    synced: bool,
71}
72
73impl<R: AsyncRead + Unpin> SectionStream<R> {
74    /// Create a `SectionStream` with the default [`SiDemux`] configuration
75    /// (all standard DVB/SI PIDs, PAT-follow enabled, version gating).
76    #[must_use]
77    pub fn new(reader: R) -> Self {
78        Self::with_demux(reader, SiDemux::builder().build())
79    }
80
81    /// Create a `SectionStream` with a custom [`SiDemuxBuilder`].
82    #[must_use]
83    pub fn with_builder(reader: R, builder: SiDemuxBuilder) -> Self {
84        Self::with_demux(reader, builder.build())
85    }
86
87    /// Create a `SectionStream` with an already-constructed [`SiDemux`].
88    #[must_use]
89    pub fn with_demux(reader: R, demux: SiDemux) -> Self {
90        Self {
91            reader,
92            demux,
93            queue: VecDeque::new(),
94            buf: vec![0u8; READ_BUF_SIZE],
95            filled: 0,
96            eof: false,
97            synced: false,
98        }
99    }
100
101    /// Access the underlying demux statistics.
102    #[must_use]
103    pub fn stats(&self) -> dvb_si::demux::Stats {
104        self.demux.stats()
105    }
106
107    /// Feed a completed read into the demux and push events into `queue`.
108    fn feed_buf(&mut self, data: &[u8]) {
109        // On first use (or after a large gap), resync to the nearest 0x47.
110        let start = if self.synced {
111            0
112        } else {
113            match resync(data) {
114                Some(off) => {
115                    self.synced = true;
116                    off
117                }
118                None => return, // no sync byte yet — discard this chunk
119            }
120        };
121
122        for pkt in aligned_packets(&data[start..]) {
123            for event in self.demux.feed(pkt) {
124                self.queue.push_back(event);
125            }
126        }
127
128        // If the tail was not a full packet, preserve the partial bytes.
129        let aligned_end = start + (data[start..].len() / TS_PACKET_SIZE) * TS_PACKET_SIZE;
130        let remainder = &data[aligned_end..];
131        if !remainder.is_empty() {
132            // If all bytes were consumed cleanly this will be empty.
133            // Non-empty means a partial TS packet at the tail — keep for next read.
134            self.buf[..remainder.len()].copy_from_slice(remainder);
135            self.filled = remainder.len();
136        } else {
137            self.filled = 0;
138        }
139    }
140}
141
142impl<R: AsyncRead + Unpin> Stream for SectionStream<R> {
143    type Item = SectionEvent;
144
145    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
146        let this = self.get_mut();
147
148        loop {
149            // Drain the event queue first.
150            if let Some(event) = this.queue.pop_front() {
151                return Poll::Ready(Some(event));
152            }
153
154            // If EOF and queue empty, the stream is done.
155            if this.eof {
156                return Poll::Ready(None);
157            }
158
159            // Read more bytes from the reader.
160            let buf_len = this.buf.len();
161            let read_from = this.filled;
162            let mut read_buf = ReadBuf::new(&mut this.buf[read_from..buf_len]);
163
164            match Pin::new(&mut this.reader).poll_read(cx, &mut read_buf) {
165                Poll::Pending => return Poll::Pending,
166                Poll::Ready(Err(_)) => {
167                    this.eof = true;
168                    return Poll::Ready(None);
169                }
170                Poll::Ready(Ok(())) => {
171                    let n = read_buf.filled().len();
172                    if n == 0 {
173                        this.eof = true;
174                        return Poll::Ready(None);
175                    }
176                    let total = read_from + n;
177                    // Feed the entire accumulated data (partial + new).
178                    let data: Vec<u8> = this.buf[..total].to_vec();
179                    this.feed_buf(&data);
180                    // `feed_buf` updates `this.filled`; loop to drain queue.
181                }
182            }
183        }
184    }
185}
186
187/// A thin [`AsyncRead`] adapter over a [`tokio::net::UdpSocket`].
188///
189/// Each `poll_read` call attempts one `recv` from the socket, writing the
190/// received datagram bytes into the provided buffer. This is sufficient for
191/// DVB multicast delivery where each UDP datagram carries exactly 7 aligned
192/// 188-byte TS packets (1316 bytes).
193///
194/// Only constructed by [`SectionStream::bind_multicast`] and
195/// [`crate::T2miEventStream::bind_multicast`].
196#[cfg(feature = "udp")]
197pub struct UdpReader {
198    pub(crate) socket: tokio::net::UdpSocket,
199}
200
201#[cfg(feature = "udp")]
202impl AsyncRead for UdpReader {
203    fn poll_read(
204        self: Pin<&mut Self>,
205        cx: &mut Context<'_>,
206        buf: &mut ReadBuf<'_>,
207    ) -> Poll<std::io::Result<()>> {
208        self.get_mut().socket.poll_recv(cx, buf)
209    }
210}
211
212/// UDP/multicast convenience constructor — enabled by the `udp` feature.
213#[cfg(feature = "udp")]
214impl SectionStream<UdpReader> {
215    /// Bind a UDP socket to `bind_addr` and join `multicast_addr`.
216    ///
217    /// Typical DVB multicast delivery uses addresses like `239.0.0.1:5004`.
218    /// The returned `SectionStream` reads one UDP datagram per `poll_next`
219    /// cycle from the socket (treated as a raw TS byte source).
220    ///
221    /// # Errors
222    ///
223    /// Returns a [`std::io::Error`] if binding or joining the multicast group
224    /// fails.
225    pub async fn bind_multicast(
226        bind_addr: std::net::SocketAddrV4,
227        multicast_addr: std::net::Ipv4Addr,
228    ) -> std::io::Result<Self> {
229        use tokio::net::UdpSocket;
230        let socket = UdpSocket::bind(bind_addr).await?;
231        socket.join_multicast_v4(multicast_addr, *bind_addr.ip())?;
232        Ok(Self::new(UdpReader { socket }))
233    }
234}