Skip to main content

sources/
raw.rs

1//! Raw rgb24 frame ingest from any `Read`: stdin fed by `ffmpeg -f rawvideo`,
2//! a socket, a pipe.
3//!
4//! A stream is consecutive frames of `width * height * 3` bytes and nothing
5//! else. Socket clients prefix one [`Header`] with size and rate; stdin
6//! streams carry no header and take the size from the command line.
7
8use wall::Frame;
9use std::io::{self, Read, Write};
10
11/// Reads consecutive rgb24 frames of one size from `r`.
12pub struct RawSource<R> {
13    r: R,
14    width: u32,
15    height: u32,
16    /// Bytes of the current frame already in the caller's buffer, so a read
17    /// that stops early (a timeout) resumes instead of losing the frame.
18    filled: usize,
19}
20
21impl<R: Read> RawSource<R> {
22    pub fn new(r: R, width: u32, height: u32) -> Self {
23        Self {
24            r,
25            width,
26            height,
27            filled: 0,
28        }
29    }
30
31    /// Fill `frame` with the next image, resizing `frame` to the stream's
32    /// size if it differs. `Ok(false)` at end of stream; a short final
33    /// frame is dropped.
34    ///
35    /// # Errors
36    /// Any read error other than an interrupt. On a timeout the bytes read
37    /// so far stay in `frame` and the next call with the same `frame`
38    /// continues the frame.
39    pub fn read_frame(&mut self, frame: &mut Frame) -> io::Result<bool> {
40        if (frame.width, frame.height) != (self.width, self.height) {
41            *frame = Frame::black(self.width, self.height);
42            self.filled = 0;
43        }
44        let buf = frame.as_bytes_mut();
45        while self.filled < buf.len() {
46            match self.r.read(&mut buf[self.filled..]) {
47                Ok(0) => {
48                    self.filled = 0;
49                    return Ok(false);
50                }
51                Ok(n) => self.filled += n,
52                Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
53                Err(e) => return Err(e),
54            }
55        }
56        self.filled = 0;
57        Ok(true)
58    }
59}
60
61impl<R: Read> crate::FrameSource for RawSource<R> {
62    fn next_frame(&mut self, frame: &mut Frame) -> anyhow::Result<bool> {
63        self.read_frame(frame).map_err(Into::into)
64    }
65}
66
67/// The 12-byte header a socket client sends before its first frame.
68///
69/// `RXP` and a zero byte, version 1, one reserved byte, then width, height and fps as
70/// little-endian u16.
71#[derive(Clone, Copy, Debug, PartialEq, Eq)]
72pub struct Header {
73    pub width: u16,
74    pub height: u16,
75    pub fps: u16,
76}
77
78impl Header {
79    pub const LEN: usize = 12;
80    const MAGIC: [u8; 4] = *b"RXP\0";
81    const VERSION: u8 = 1;
82
83    #[must_use]
84    pub fn to_bytes(self) -> [u8; Self::LEN] {
85        let mut b = [0u8; Self::LEN];
86        b[..4].copy_from_slice(&Self::MAGIC);
87        b[4] = Self::VERSION;
88        b[6..8].copy_from_slice(&self.width.to_le_bytes());
89        b[8..10].copy_from_slice(&self.height.to_le_bytes());
90        b[10..12].copy_from_slice(&self.fps.to_le_bytes());
91        b
92    }
93
94    /// # Errors
95    /// `InvalidData` on a wrong magic or version, `InvalidInput` on a zero size.
96    pub fn from_bytes(b: &[u8; Self::LEN]) -> io::Result<Self> {
97        if b[..4] != Self::MAGIC {
98            return Err(io::Error::new(
99                io::ErrorKind::InvalidData,
100                "not a receiverproxy stream header",
101            ));
102        }
103        if b[4] != Self::VERSION {
104            return Err(io::Error::new(
105                io::ErrorKind::InvalidData,
106                format!("stream header version {} (want {})", b[4], Self::VERSION),
107            ));
108        }
109        let h = Self {
110            width: u16::from_le_bytes([b[6], b[7]]),
111            height: u16::from_le_bytes([b[8], b[9]]),
112            fps: u16::from_le_bytes([b[10], b[11]]),
113        };
114        if h.width == 0 || h.height == 0 {
115            return Err(io::Error::new(
116                io::ErrorKind::InvalidInput,
117                "zero-sized stream",
118            ));
119        }
120        Ok(h)
121    }
122
123    /// # Errors
124    /// The write error.
125    pub fn write(self, w: &mut impl Write) -> io::Result<()> {
126        w.write_all(&self.to_bytes())
127    }
128
129    /// # Errors
130    /// The read error, or the errors of [`from_bytes`](Self::from_bytes).
131    pub fn read(r: &mut impl Read) -> io::Result<Self> {
132        let mut b = [0u8; Self::LEN];
133        r.read_exact(&mut b)?;
134        Self::from_bytes(&b)
135    }
136
137    /// Bytes per frame.
138    #[must_use]
139    pub fn frame_len(self) -> usize {
140        usize::from(self.width) * usize::from(self.height) * 3
141    }
142}
143
144/// The client side of `rxp show serve`: writes a [`Header`], then frames.
145///
146/// ```no_run
147/// use std::os::unix::net::UnixStream;
148/// let stream = UnixStream::connect("/tmp/receiverproxy.sock")?;
149/// let mut writer = sources::raw::Writer::new(stream, 128, 64, 30)?;
150/// let rgb = vec![0u8; 128 * 64 * 3];
151/// writer.frame(&rgb)?;
152/// # Ok::<(), std::io::Error>(())
153/// ```
154pub struct Writer<W> {
155    w: W,
156    frame_len: usize,
157}
158
159impl<W: Write> Writer<W> {
160    /// Send the header for `width` x `height` frames at `fps`.
161    ///
162    /// # Errors
163    /// The write error.
164    pub fn new(mut w: W, width: u16, height: u16, fps: u16) -> io::Result<Self> {
165        let header = Header { width, height, fps };
166        header.write(&mut w)?;
167        w.flush()?;
168        Ok(Self {
169            w,
170            frame_len: header.frame_len(),
171        })
172    }
173
174    /// Send one rgb24 frame; `rgb` must be exactly `width * height * 3` bytes.
175    ///
176    /// # Errors
177    /// `InvalidInput` on a wrong length, else the write error.
178    pub fn frame(&mut self, rgb: &[u8]) -> io::Result<()> {
179        if rgb.len() != self.frame_len {
180            return Err(io::Error::new(
181                io::ErrorKind::InvalidInput,
182                format!("frame is {} bytes, want {}", rgb.len(), self.frame_len),
183            ));
184        }
185        self.w.write_all(rgb)?;
186        self.w.flush()
187    }
188
189    pub fn into_inner(self) -> W {
190        self.w
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197    use crate::{pattern, Pattern};
198    use std::io::Cursor;
199
200    #[test]
201    fn frames_are_read_back_to_back_and_a_short_tail_is_dropped() {
202        let (w, h) = (4, 2);
203        let a = pattern(Pattern::Rgb, w, h);
204        let b = pattern(Pattern::Gradient, w, h);
205        let c = pattern(Pattern::White, w, h);
206        let mut bytes = a.as_bytes().to_vec();
207        bytes.extend_from_slice(b.as_bytes());
208        bytes.extend_from_slice(c.as_bytes());
209        bytes.extend_from_slice(&[7; 5]);
210        let mut src = RawSource::new(Cursor::new(bytes), w, h);
211        // Handed a frame of the wrong size, the source resizes it.
212        let mut f = Frame::black(1, 1);
213        for want in [&a, &b, &c] {
214            assert!(src.read_frame(&mut f).unwrap());
215            assert_eq!(&f, want);
216        }
217        assert!(!src.read_frame(&mut f).unwrap());
218        assert!(!src.read_frame(&mut f).unwrap());
219    }
220
221    /// A reader that hands out one byte per call, then times out once.
222    struct Dribble {
223        data: Vec<u8>,
224        at: usize,
225        timeouts: usize,
226    }
227
228    impl Read for Dribble {
229        fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
230            if self.at == 3 && self.timeouts > 0 {
231                self.timeouts -= 1;
232                return Err(io::ErrorKind::TimedOut.into());
233            }
234            if self.at == self.data.len() || buf.is_empty() {
235                return Ok(0);
236            }
237            buf[0] = self.data[self.at];
238            self.at += 1;
239            Ok(1)
240        }
241    }
242
243    #[test]
244    fn a_timeout_mid_frame_resumes_on_the_next_call() {
245        let a = pattern(Pattern::Gradient, 2, 2);
246        let mut src = RawSource::new(
247            Dribble {
248                data: a.as_bytes().to_vec(),
249                at: 0,
250                timeouts: 1,
251            },
252            2,
253            2,
254        );
255        let mut f = Frame::black(2, 2);
256        assert_eq!(
257            src.read_frame(&mut f).unwrap_err().kind(),
258            io::ErrorKind::TimedOut
259        );
260        assert!(src.read_frame(&mut f).unwrap());
261        assert_eq!(f, a);
262    }
263
264    #[test]
265    fn header_round_trips_and_rejects_strangers() {
266        let h = Header {
267            width: 128,
268            height: 64,
269            fps: 30,
270        };
271        let b = h.to_bytes();
272        assert_eq!(&b, b"RXP\0\x01\x00\x80\x00\x40\x00\x1e\x00");
273        assert_eq!(Header::read(&mut Cursor::new(b)).unwrap(), h);
274        assert_eq!(h.frame_len(), 128 * 64 * 3);
275
276        let mut bad = b;
277        bad[0] = b'X';
278        assert_eq!(
279            Header::from_bytes(&bad).unwrap_err().kind(),
280            io::ErrorKind::InvalidData
281        );
282        let mut v2 = b;
283        v2[4] = 2;
284        assert!(Header::from_bytes(&v2).is_err());
285        let mut zero = b;
286        zero[6..8].fill(0);
287        assert_eq!(
288            Header::from_bytes(&zero).unwrap_err().kind(),
289            io::ErrorKind::InvalidInput
290        );
291        assert!(Header::read(&mut Cursor::new(&b[..5])).is_err());
292    }
293
294    #[test]
295    fn writer_output_reads_back_through_a_raw_source() {
296        let (w, h) = (6, 3);
297        let a = pattern(Pattern::Border, w, h);
298        let b = pattern(Pattern::Rows, w, h);
299        let mut writer = Writer::new(Vec::new(), w as u16, h as u16, 25).unwrap();
300        writer.frame(a.as_bytes()).unwrap();
301        writer.frame(b.as_bytes()).unwrap();
302        assert_eq!(
303            writer.frame(&[0; 4]).unwrap_err().kind(),
304            io::ErrorKind::InvalidInput
305        );
306        let bytes = writer.into_inner();
307        assert_eq!(bytes.len(), Header::LEN + 2 * (w * h * 3) as usize);
308
309        let mut r = Cursor::new(bytes);
310        let header = Header::read(&mut r).unwrap();
311        assert_eq!((header.width, header.height, header.fps), (6, 3, 25));
312        let mut src = RawSource::new(r, u32::from(header.width), u32::from(header.height));
313        let mut f = Frame::black(w, h);
314        assert!(src.read_frame(&mut f).unwrap());
315        assert_eq!(f, a);
316        assert!(src.read_frame(&mut f).unwrap());
317        assert_eq!(f, b);
318        assert!(!src.read_frame(&mut f).unwrap());
319    }
320}