Skip to main content

falcon_ffmpeg/
testing.rs

1//! Test clips for falcon's own tests: AVI files written byte by byte, with
2//! uncompressed BGR pictures and 16-bit PCM sound, so no encoder is needed
3//! (falcon's FFmpeg build has none) and every pixel's value is known.
4//! [`av1`] rewraps an AV1 stream encoded elsewhere in WebM, Matroska and MP4.
5
6pub mod av1;
7pub mod mkv;
8
9/// An AVI clip to write: `frames` pictures of `width` × `height` at `fps`,
10/// with a 440 Hz stereo tone at `audio_rate` samples per second if given.
11/// `width` should be a multiple of 4 (AVI pads rows to 4 bytes).
12#[derive(Clone, Copy, Debug)]
13pub struct Clip {
14    pub width: u32,
15    pub height: u32,
16    pub frames: u32,
17    pub fps: u32,
18    pub audio_rate: Option<u32>,
19}
20
21fn le16(v: &mut Vec<u8>, x: u16) {
22    v.extend_from_slice(&x.to_le_bytes());
23}
24
25fn le32(v: &mut Vec<u8>, x: u32) {
26    v.extend_from_slice(&x.to_le_bytes());
27}
28
29fn chunk(id: &[u8; 4], data: &[u8]) -> Vec<u8> {
30    let mut v = id.to_vec();
31    le32(&mut v, data.len() as u32);
32    v.extend_from_slice(data);
33    if data.len() % 2 == 1 {
34        v.push(0);
35    }
36    v
37}
38
39fn list(kind: &[u8; 4], content: &[u8]) -> Vec<u8> {
40    let mut data = kind.to_vec();
41    data.extend_from_slice(content);
42    chunk(b"LIST", &data)
43}
44
45impl Clip {
46    /// The file's bytes; `pixel(i, y)` is the RGB of row `y` (from the top)
47    /// of frame `i`.
48    pub fn avi(&self, pixel: impl Fn(u32, u32) -> [u8; 3]) -> Vec<u8> {
49        let Clip { width: w, height: h, frames, fps, audio_rate } = *self;
50        let frame_bytes = w * h * 3;
51        let streams = if audio_rate.is_some() { 2 } else { 1 };
52
53        let mut avih = Vec::new();
54        for x in [1_000_000 / fps, 0, 0, 0x10, frames, 0, streams, frame_bytes, w, h, 0, 0, 0, 0] {
55            le32(&mut avih, x);
56        }
57        let mut strh = b"vids".to_vec();
58        strh.extend_from_slice(&[0; 4]);
59        for x in [0u32, 0, 0, 1, fps, 0, frames, frame_bytes, u32::MAX, 0] {
60            le32(&mut strh, x);
61        }
62        for x in [0u16, 0, w as u16, h as u16] {
63            le16(&mut strh, x);
64        }
65        let mut strf = Vec::new();
66        for x in [40, w, h] {
67            le32(&mut strf, x);
68        }
69        le16(&mut strf, 1);
70        le16(&mut strf, 24);
71        for x in [0, frame_bytes, 0, 0, 0, 0] {
72            le32(&mut strf, x);
73        }
74        let mut hdrl = chunk(b"avih", &avih);
75        hdrl.extend(list(b"strl", &[chunk(b"strh", &strh), chunk(b"strf", &strf)].concat()));
76        if let Some(rate) = audio_rate {
77            let per_frame = rate / fps;
78            let mut ah = b"auds".to_vec();
79            ah.extend_from_slice(&[0; 4]);
80            for x in [0u32, 0, 0, 4, rate * 4, 0, per_frame * frames, per_frame * 4, u32::MAX, 4] {
81                le32(&mut ah, x);
82            }
83            ah.extend_from_slice(&[0; 8]);
84            let mut af = Vec::new();
85            le16(&mut af, 1);
86            le16(&mut af, 2);
87            le32(&mut af, rate);
88            le32(&mut af, rate * 4);
89            le16(&mut af, 4);
90            le16(&mut af, 16);
91            le16(&mut af, 0);
92            hdrl.extend(list(b"strl", &[chunk(b"strh", &ah), chunk(b"strf", &af)].concat()));
93        }
94
95        let mut movi = Vec::new();
96        let mut idx = Vec::new();
97        let mut add = |movi: &mut Vec<u8>, id: &[u8; 4], data: &[u8]| {
98            idx.extend_from_slice(id);
99            le32(&mut idx, 0x10);
100            le32(&mut idx, movi.len() as u32 + 4);
101            le32(&mut idx, data.len() as u32);
102            movi.extend(chunk(id, data));
103        };
104        for i in 0..frames {
105            // Bottom-up rows of BGR.
106            let mut px = Vec::with_capacity(frame_bytes as usize);
107            for row in (0..h).rev() {
108                let [r, g, b] = pixel(i, row);
109                for _ in 0..w {
110                    px.extend_from_slice(&[b, g, r]);
111                }
112            }
113            add(&mut movi, b"00db", &px);
114            if let Some(rate) = audio_rate {
115                let per_frame = rate / fps;
116                let mut pcm = Vec::new();
117                for n in 0..per_frame {
118                    let t = f64::from(i * per_frame + n) / f64::from(rate);
119                    let s = ((t * 440.0 * std::f64::consts::TAU).sin() * 8000.0) as i16;
120                    pcm.extend_from_slice(&s.to_le_bytes());
121                    pcm.extend_from_slice(&s.to_le_bytes());
122                }
123                add(&mut movi, b"01wb", &pcm);
124            }
125        }
126        let mut body = b"AVI ".to_vec();
127        body.extend(list(b"hdrl", &hdrl));
128        body.extend(list(b"movi", &movi));
129        body.extend(chunk(b"idx1", &idx));
130        chunk(b"RIFF", &body)
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    #[test]
139    fn avi_files_are_riff_with_an_index() {
140        let clip = Clip { width: 4, height: 2, frames: 3, fps: 10, audio_rate: Some(1000) };
141        let bytes = clip.avi(|_, _| [1, 2, 3]);
142        assert_eq!(&bytes[0..4], b"RIFF");
143        assert_eq!(u32::from_le_bytes(bytes[4..8].try_into().unwrap()) as usize, bytes.len() - 8);
144        assert_eq!(&bytes[8..12], b"AVI ");
145        assert!(bytes.windows(4).any(|w| w == b"idx1"));
146        // 3 pictures of 24 bytes and 3 blocks of 100 samples × 4 bytes.
147        assert_eq!(bytes.windows(4).filter(|w| *w == b"00db").count(), 3 + 3);
148        assert!(bytes.windows(3).any(|w| w == [3, 2, 1]), "pixels are stored BGR");
149    }
150}