Skip to main content

moq_mux/codec/flac/
mod.rs

1//! FLAC.
2//!
3//! Parse and serialize the FLAC `STREAMINFO` metadata block (RFC 9639 §8.2),
4//! the codec's configuration record. [`Config::description`] builds the
5//! WebCodecs `description` (the `fLaC` stream marker followed by the STREAMINFO
6//! block) so a browser can initialize a decoder from the catalog alone.
7//! [`Import`] publishes raw FLAC frames to a moq broadcast.
8
9mod import;
10
11pub use import::*;
12
13use bytes::{Buf, BufMut, Bytes};
14
15/// The four-byte stream marker that opens a native FLAC bitstream and the
16/// WebCodecs `description`.
17const MARKER: [u8; 4] = *b"fLaC";
18
19/// Length of a STREAMINFO metadata block body in bytes (RFC 9639 §8.2).
20const STREAMINFO_LEN: usize = 34;
21
22/// FLAC parsing errors.
23#[derive(Debug, Clone, thiserror::Error)]
24#[non_exhaustive]
25pub enum Error {
26	/// The buffer ended before a full STREAMINFO (or its enclosing header) could be read.
27	#[error("buffer too short for FLAC STREAMINFO")]
28	Short,
29
30	/// A FLAC header did not begin with the `fLaC` stream marker.
31	#[error("invalid FLAC stream marker")]
32	InvalidMarker,
33
34	/// The first metadata block was not STREAMINFO, which RFC 9639 §8.1 requires.
35	#[error("first metadata block is not STREAMINFO")]
36	MissingStreamInfo,
37}
38
39pub type Result<T> = std::result::Result<T, Error>;
40
41/// Typed FLAC configuration mirroring the fields of a STREAMINFO metadata block
42/// (RFC 9639 §8.2).
43#[derive(Debug, Clone, PartialEq, Eq)]
44pub struct Config {
45	/// Minimum block size in samples.
46	pub min_block_size: u16,
47	/// Maximum block size in samples.
48	pub max_block_size: u16,
49	/// Minimum frame size in bytes (0 = unknown).
50	pub min_frame_size: u32,
51	/// Maximum frame size in bytes (0 = unknown).
52	pub max_frame_size: u32,
53	/// Sample rate in Hz (20-bit field, so 1..=1_048_575).
54	pub sample_rate: u32,
55	/// Channel count (1..=8).
56	pub channel_count: u32,
57	/// Bits per sample (4..=32).
58	pub bits_per_sample: u32,
59	/// Total interchannel samples (36-bit field, 0 = unknown).
60	pub total_samples: u64,
61	/// MD5 of the unencoded audio (all-zero = unknown).
62	pub md5: [u8; 16],
63}
64
65impl Config {
66	/// Parse a 34-byte STREAMINFO body (RFC 9639 §8.2), without the `fLaC`
67	/// marker or metadata block header.
68	pub fn parse_stream_info<T: Buf>(buf: &mut T) -> Result<Self> {
69		if buf.remaining() < STREAMINFO_LEN {
70			return Err(Error::Short);
71		}
72
73		let min_block_size = buf.get_u16();
74		let max_block_size = buf.get_u16();
75		let min_frame_size = buf.get_uint(3) as u32;
76		let max_frame_size = buf.get_uint(3) as u32;
77
78		// Sample rate (20), channels-1 (3), bits-1 (5), and total samples (36) are
79		// packed into a single 64-bit field.
80		let packed = buf.get_u64();
81		let sample_rate = (packed >> 44) as u32;
82		let channel_count = (((packed >> 41) & 0x7) as u32) + 1;
83		let bits_per_sample = (((packed >> 36) & 0x1f) as u32) + 1;
84		let total_samples = packed & 0xF_FFFF_FFFF;
85
86		let mut md5 = [0u8; 16];
87		buf.copy_to_slice(&mut md5);
88
89		Ok(Self {
90			min_block_size,
91			max_block_size,
92			min_frame_size,
93			max_frame_size,
94			sample_rate,
95			channel_count,
96			bits_per_sample,
97			total_samples,
98			md5,
99		})
100	}
101
102	/// Parse a FLAC header: the `fLaC` stream marker followed by the STREAMINFO
103	/// metadata block. This is the Matroska `A_FLAC` CodecPrivate / WebCodecs
104	/// `description` layout. Trailing metadata blocks (Vorbis comments, etc.) are
105	/// ignored.
106	pub fn parse<T: Buf>(buf: &mut T) -> Result<Self> {
107		if buf.remaining() < 4 {
108			return Err(Error::Short);
109		}
110		let mut marker = [0u8; 4];
111		buf.copy_to_slice(&mut marker);
112		if marker != MARKER {
113			return Err(Error::InvalidMarker);
114		}
115
116		if buf.remaining() < 4 {
117			return Err(Error::Short);
118		}
119		// Metadata block header: 1-bit last flag, 7-bit block type, 24-bit length.
120		let header = buf.get_u32();
121		let block_type = ((header >> 24) & 0x7f) as u8;
122		if block_type != 0 {
123			return Err(Error::MissingStreamInfo);
124		}
125
126		Self::parse_stream_info(buf)
127	}
128
129	/// Serialize the 34-byte STREAMINFO body.
130	pub fn encode_stream_info(&self) -> Bytes {
131		let mut buf = Vec::with_capacity(STREAMINFO_LEN);
132		buf.put_u16(self.min_block_size);
133		buf.put_u16(self.max_block_size);
134		buf.put_uint(self.min_frame_size as u64, 3);
135		buf.put_uint(self.max_frame_size as u64, 3);
136
137		let packed = ((self.sample_rate as u64 & 0xF_FFFF) << 44)
138			| ((self.channel_count.saturating_sub(1) as u64 & 0x7) << 41)
139			| ((self.bits_per_sample.saturating_sub(1) as u64 & 0x1f) << 36)
140			| (self.total_samples & 0xF_FFFF_FFFF);
141		buf.put_u64(packed);
142		buf.put_slice(&self.md5);
143
144		buf.into()
145	}
146
147	/// Build the WebCodecs `description`: the `fLaC` stream marker, a metadata
148	/// block header flagged as the final block, and the STREAMINFO body. This is
149	/// the `description` a FLAC `AudioDecoder` expects per the WebCodecs FLAC
150	/// registration.
151	pub fn description(&self) -> Bytes {
152		let stream_info = self.encode_stream_info();
153		let mut buf = Vec::with_capacity(MARKER.len() + 4 + stream_info.len());
154		buf.put_slice(&MARKER);
155		// Last-metadata-block flag (0x80) set, block type 0 (STREAMINFO).
156		buf.put_u8(0x80);
157		buf.put_uint(stream_info.len() as u64, 3);
158		buf.put_slice(&stream_info);
159		buf.into()
160	}
161}
162
163#[cfg(test)]
164mod tests {
165	use super::*;
166
167	fn sample() -> Config {
168		Config {
169			min_block_size: 4608,
170			max_block_size: 4608,
171			min_frame_size: 16,
172			max_frame_size: 9102,
173			sample_rate: 44_100,
174			channel_count: 2,
175			bits_per_sample: 24,
176			total_samples: 120_832,
177			md5: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
178		}
179	}
180
181	#[test]
182	fn stream_info_roundtrip() {
183		let cfg = sample();
184		let encoded = cfg.encode_stream_info();
185		assert_eq!(encoded.len(), STREAMINFO_LEN);
186		let parsed = Config::parse_stream_info(&mut encoded.as_ref()).unwrap();
187		assert_eq!(parsed, cfg);
188	}
189
190	#[test]
191	fn description_roundtrip() {
192		let cfg = sample();
193		let desc = cfg.description();
194		// "fLaC" + 4-byte block header + 34-byte STREAMINFO.
195		assert_eq!(desc.len(), 4 + 4 + STREAMINFO_LEN);
196		assert_eq!(&desc[..4], b"fLaC");
197		// Final block flag set, block type 0 (STREAMINFO).
198		assert_eq!(desc[4], 0x80);
199
200		// The description parses back into the same config.
201		let parsed = Config::parse(&mut desc.as_ref()).unwrap();
202		assert_eq!(parsed, cfg);
203	}
204
205	#[test]
206	fn parse_rejects_bad_marker() {
207		let mut desc = sample().description().to_vec();
208		desc[0] = b'X';
209		assert!(matches!(Config::parse(&mut desc.as_slice()), Err(Error::InvalidMarker)));
210	}
211
212	#[test]
213	fn parse_rejects_non_streaminfo_first_block() {
214		let mut desc = sample().description().to_vec();
215		// Flip the block type away from 0 (STREAMINFO) while keeping the marker.
216		desc[4] = 0x80 | 0x04; // last block, type 4 (Vorbis comment)
217		assert!(matches!(
218			Config::parse(&mut desc.as_slice()),
219			Err(Error::MissingStreamInfo)
220		));
221	}
222}