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