ogg/
ogg.rs

1#![allow(dead_code)]
2
3use std::{io::{self, Cursor, Write, ErrorKind}, mem, fmt::{self, Debug, Formatter}};
4
5#[derive(Debug, Clone, Copy)]
6pub enum OggPacketType {
7	/// * The middle packets
8	Continuation = 0,
9
10	/// * The begin of a stream
11	BeginOfStream = 2,
12
13	/// * The last packet of a stream
14	EndOfStream = 4,
15}
16
17/// * An ogg packet as a stream container
18#[derive(Debug, Clone)]
19pub struct OggPacket {
20	/// Ogg Version must be zero
21	pub version: u8,
22
23	/// * The first packet should be `OggPacketType::BeginOfStream`
24	/// * The last packet should be `OggPacketType::EndOfStream`
25	/// * The others should be `OggPacketType::Continuation`
26	pub packet_type: OggPacketType,
27
28	/// * For vorbis, this field indicates when you had decoded from the first packet to this packet,
29	///   and you had finished decoding this packet, how many of the audio frames you should get.
30	pub granule_position: u64,
31
32	/// * The identifier for the streams. Every Ogg packet belonging to a stream should have the same `stream_id`.
33	pub stream_id: u32,
34
35	/// * The index of the packet, beginning from zero.
36	pub packet_index: u32,
37
38	/// * The checksum of the packet.
39	pub checksum: u32,
40
41	/// * A table indicates each segment's size, the max is 255. And the size of the table also couldn't exceed 255.
42	pub segment_table: Vec<u8>,
43
44	/// * The data encapsulated in the Ogg Stream
45	pub data: Vec<u8>,
46}
47
48impl OggPacket {
49	/// Create a new Ogg packet
50	pub fn new(stream_id: u32, packet_type: OggPacketType, packet_index: u32) -> Self {
51		Self {
52			version: 0,
53			packet_type,
54			granule_position: 0,
55			stream_id,
56			packet_index,
57			checksum: 0,
58			segment_table: Vec::new(),
59			data: Vec::new(),
60		}
61	}
62
63	/// Write some data to the packet, returns the actual written bytes.
64	pub fn write(&mut self, data: &[u8]) -> usize {
65		let mut written = 0usize;
66		let mut to_write = data.len();
67		if to_write == 0 {
68			return 0;
69		}
70		while self.segment_table.len() < 255 {
71			if to_write >= 255 {
72				let new_pos = written + 255;
73				self.segment_table.push(255);
74				self.data.extend(data[written..new_pos].to_vec());
75				written = new_pos;
76				to_write -= 255;
77			} else {
78				if to_write == 0 {
79					break;
80				}
81				let new_pos = written + to_write;
82				self.segment_table.push(to_write as u8);
83				self.data.extend(data[written..new_pos].to_vec());
84				written = new_pos;
85				break;
86			}
87		}
88		written
89	}
90
91	/// Clear all data inside the packet
92	pub fn clear(&mut self) {
93		self.segment_table = Vec::new();
94		self.data = Vec::new();
95	}
96
97	/// Read all of the data as segments from the packet
98	pub fn get_segments(&self) -> Vec<Vec<u8>> {
99		let mut ret = Vec::<Vec<u8>>::with_capacity(self.segment_table.len());
100		let mut pos = 0usize;
101		self.segment_table.iter().for_each(|&size|{
102			let next_pos = pos + size as usize;
103			ret.push(self.data[pos..next_pos].to_vec());
104			pos = next_pos;
105		});
106		ret
107	}
108
109	/// Get inner data size
110	pub fn get_inner_data_size(&self) -> usize {
111		self.segment_table.iter().map(|&s|s as usize).sum()
112	}
113
114	/// Read all of the data as a flattened `Vec<u8>`
115	pub fn get_inner_data(&self) -> Vec<u8> {
116		self.get_segments().into_iter().flatten().collect()
117	}
118
119	/// Calculate the checksum
120	pub fn crc(mut crc: u32, data: &[u8]) -> u32 {
121        type CrcTableType = [u32; 256];
122        fn ogg_generate_crc_table() -> CrcTableType {
123            use std::mem::MaybeUninit;
124            #[allow(invalid_value)]
125            #[allow(clippy::uninit_assumed_init)]
126            let mut crc_lookup: CrcTableType = unsafe{MaybeUninit::uninit().assume_init()};
127            (0..256).for_each(|i|{
128                let mut r: u32 = i << 24;
129                for _ in 0..8 {
130                    r = (r << 1) ^ (-(((r >> 31) & 1) as i32) as u32 & 0x04c11db7);
131                }
132                crc_lookup[i as usize] = r;
133            });
134            crc_lookup
135        }
136
137        use std::sync::OnceLock;
138        static OGG_CRC_TABLE: OnceLock<CrcTableType> = OnceLock::<CrcTableType>::new();
139        let crc_lookup = OGG_CRC_TABLE.get_or_init(ogg_generate_crc_table);
140
141        for b in data {
142            crc = (crc << 8) ^ crc_lookup[(*b as u32 ^ (crc >> 24)) as usize];
143        }
144
145        crc
146	}
147
148	pub fn get_checksum(ogg_packet: &[u8]) -> io::Result<u32> {
149		if ogg_packet.len() < 27 {
150			Err(io::Error::new(ErrorKind::InvalidData, format!("The given packet is too small: {} < 27", ogg_packet.len())))
151		} else {
152			let mut field_cleared = ogg_packet.to_vec();
153			field_cleared[22..26].copy_from_slice(&[0u8; 4]);
154			Ok(Self::crc(0, &field_cleared))
155		}
156	}
157
158	/// Set the checksum for the Ogg packet
159	pub fn fill_checksum_field(ogg_packet: &mut [u8]) -> io::Result<()> {
160		let checksum = Self::get_checksum(ogg_packet)?;
161		ogg_packet[22..26].copy_from_slice(&checksum.to_le_bytes());
162		Ok(())
163	}
164
165	/// Serialize the packet to bytes. Only in the bytes form can calculate the checksum.
166	pub fn into_bytes(self) -> Vec<u8> {
167		let mut ret: Vec<u8> = [
168			b"OggS" as &[u8],
169			&[self.version],
170			&[self.packet_type as u8],
171			&self.granule_position.to_le_bytes() as &[u8],
172			&self.stream_id.to_le_bytes() as &[u8],
173			&self.packet_index.to_le_bytes() as &[u8],
174			&0u32.to_le_bytes() as &[u8],
175			&[self.segment_table.len() as u8],
176			&self.segment_table,
177			&self.data,
178		].into_iter().flatten().copied().collect();
179		Self::fill_checksum_field(&mut ret).unwrap();
180		ret
181	}
182
183	/// Retrieve the packet length in bytes
184	pub fn get_length(ogg_packet: &[u8]) -> io::Result<usize> {
185		if ogg_packet.len() < 27 {
186			Err(io::Error::new(ErrorKind::UnexpectedEof, format!("The given ogg page size is too small: {} < 27", ogg_packet.len())))
187		} else if ogg_packet[0..4] != *b"OggS" {
188			Err(io::Error::new(ErrorKind::InvalidData, format!("While parsing Ogg packet: expected `OggS`, got `{}`", String::from_utf8_lossy(&ogg_packet[0..4]))))
189		} else if ogg_packet[4] != 0 {
190			Err(io::Error::new(ErrorKind::InvalidData, format!("While parsing Ogg packet: invalid `version` = {} (should be zero)", ogg_packet[4])))
191		} else {
192			match ogg_packet[5] {
193				0 | 2 | 4 => (),
194				o => return Err(io::Error::new(ErrorKind::InvalidData, format!("While parsing Ogg packet: invalid `packet_type` = {o} (should be 0, 2, 4)"))),
195			}
196			let num_segments = ogg_packet[26] as usize;
197			let data_start = 27 + num_segments;
198			let segment_table = &ogg_packet[27..data_start];
199			let data_length: usize = segment_table.iter().map(|&s|s as usize).sum();
200			Ok(data_start + data_length)
201		}
202	}
203
204	/// Deserialize the packet
205	pub fn from_bytes(ogg_packet: &[u8], packet_length: &mut usize) -> io::Result<Self> {
206		if ogg_packet.len() < 27 {
207			Err(io::Error::new(ErrorKind::UnexpectedEof, format!("The given ogg page size is too small: {} < 27", ogg_packet.len())))
208		} else if ogg_packet[0..4] != *b"OggS" {
209			Err(io::Error::new(ErrorKind::InvalidData, format!("While parsing Ogg packet: expected `OggS`, got `{}`", String::from_utf8_lossy(&ogg_packet[0..4]))))
210		} else if ogg_packet[4] != 0 {
211			Err(io::Error::new(ErrorKind::InvalidData, format!("While parsing Ogg packet: invalid `version` = {} (should be zero)", ogg_packet[4])))
212		} else {
213			let packet_type = match ogg_packet[5] {
214				0 => OggPacketType::Continuation,
215				2 => OggPacketType::BeginOfStream,
216				4 => OggPacketType::EndOfStream,
217				o => return Err(io::Error::new(ErrorKind::InvalidData, format!("While parsing Ogg packet: invalid `packet_type` = {o} (should be 0, 2, 4)"))),
218			};
219			let num_segments = ogg_packet[26] as usize;
220			let data_start = 27 + num_segments;
221			let segment_table = &ogg_packet[27..data_start];
222			let data_length: usize = segment_table.iter().map(|&s|s as usize).sum();
223			*packet_length = data_start + data_length;
224			if ogg_packet.len() < *packet_length {
225				Err(io::Error::new(ErrorKind::UnexpectedEof, format!("The given ogg page size is too small: {} < {packet_length}", ogg_packet.len())))
226			} else {
227				let ret = Self{
228					version: 0,
229					packet_type,
230					granule_position: u64::from_le_bytes(ogg_packet[6..14].try_into().unwrap()),
231					stream_id: u32::from_le_bytes(ogg_packet[14..18].try_into().unwrap()),
232					packet_index: u32::from_le_bytes(ogg_packet[18..22].try_into().unwrap()),
233					checksum: u32::from_le_bytes(ogg_packet[22..26].try_into().unwrap()),
234					segment_table: segment_table.to_vec(),
235					data: ogg_packet[data_start..*packet_length].to_vec(),
236				};
237				let checksum = Self::get_checksum(&ogg_packet[..*packet_length])?;
238				if ret.checksum != checksum {
239					Err(io::Error::new(ErrorKind::InvalidData, format!("Ogg packet checksum not match: should be 0x{:x}, got 0x{:x}", checksum, ret.checksum)))
240				} else {
241					Ok(ret)
242				}
243			}
244		}
245	}
246
247	/// Deserialize to multiple packets
248	pub fn from_cursor(cursor: &mut Cursor<Vec<u8>>) -> Vec<OggPacket> {
249		let mut data: &[u8] = cursor.get_ref();
250		let mut packet_length = 0usize;
251		let mut bytes_read = 0usize;
252		let mut ret = Vec::<OggPacket>::new();
253		while let Ok(packet) = Self::from_bytes(data, &mut packet_length) {
254			bytes_read += packet_length;
255			ret.push(packet);
256			data = &data[packet_length..];
257			if data.is_empty() {
258				break;
259			}
260		}
261		cursor.set_position(bytes_read as u64);
262		ret
263	}
264}
265
266impl Default for OggPacket {
267	fn default() -> Self {
268		Self {
269			version: 0,
270			packet_type: OggPacketType::BeginOfStream,
271			granule_position: 0,
272			stream_id: 0,
273			packet_index: 0,
274			checksum: 0,
275			segment_table: Vec::new(),
276			data: Vec::new(),
277		}
278	}
279}
280
281/// * An ogg packet as a stream container
282pub struct OggStreamWriter<W>
283where
284	W: Write + Debug {
285	/// * The writer, when a packet is full or you want to seal the packet, the packet is flushed in the writer
286	pub writer: W,
287
288	/// * The unique stream ID for a whole stream. Programs use the stream ID to identify which packet is for which stream.
289	pub stream_id: u32,
290
291	/// * The packet index.
292	pub packet_index: u32,
293
294	/// * The current packet, ready to be written.
295	pub cur_packet: OggPacket,
296
297	/// * The granule position is for the programmers to reference it for some purpose.
298	pub granule_position: u64,
299
300	/// * The `OggStreamWriter<W>` implements `Write`, when the `cur_packet` is full, the `on_seal()` closure will be called for updating the granule position.
301	/// * And then the packet will be flushed into the writer.
302	pub on_seal: Box<dyn FnMut(usize) -> u64>,
303
304	/// * How many bytes were written into this stream.
305	pub bytes_written: u64,
306}
307
308impl<W> OggStreamWriter<W>
309where
310	W: Write + Debug {
311	pub fn new(writer: W, stream_id: u32) -> Self {
312		Self {
313			writer,
314			stream_id,
315			packet_index : 0,
316			cur_packet: OggPacket::new(stream_id, OggPacketType::BeginOfStream, 0),
317			granule_position: 0,
318			bytes_written: 0,
319			on_seal: Box::new(|i|i as u64),
320		}
321	}
322
323	/// * Set the granule position. This field of data is not used by the Ogg stream.
324	/// * The granule position is for the inner things to reference it for some purpose.
325	pub fn set_granule_position(&mut self, position: u64) {
326		self.granule_position = position
327	}
328
329	/// * Get the granule position you had set before
330	pub fn get_granule_position(&self) -> u64 {
331		self.granule_position
332	}
333
334	/// * Mark the current packet as EOS
335	pub fn mark_cur_packet_as_end_of_stream(&mut self) {
336		self.cur_packet.packet_type = OggPacketType::EndOfStream;
337	}
338
339	/// * Get how many bytes written in this stream
340	pub fn get_bytes_written(&self) -> u64 {
341		self.bytes_written
342	}
343
344	/// * Set a callback for the `Write` trait when it seals the packet, the callback helps with updating the granule position
345	pub fn set_on_seal_callback(&mut self, on_seal: Box<dyn FnMut(usize) -> u64>) {
346		self.on_seal = on_seal;
347	}
348
349	/// * Reset the stream state, discard the packet, reinit the packet to a BOS
350	pub fn reset(&mut self) {
351		self.packet_index = 0;
352		self.cur_packet = OggPacket::new(self.stream_id, OggPacketType::BeginOfStream, 0);
353		self.granule_position = 0;
354		self.bytes_written = 0;
355	}
356
357	/// * Save the current packet and write it to the sink, then create a new packet for writing.
358	pub fn seal_packet(&mut self, granule_position: u64, is_end_of_stream: bool) -> io::Result<()> {
359		self.packet_index += 1;
360		self.granule_position = granule_position;
361		self.cur_packet.granule_position = self.granule_position;
362		let packed = if is_end_of_stream {
363			self.cur_packet.packet_type = OggPacketType::EndOfStream;
364			mem::take(&mut self.cur_packet).into_bytes()
365		} else {
366			mem::replace(&mut self.cur_packet, OggPacket::new(self.stream_id, OggPacketType::Continuation, self.packet_index)).into_bytes()
367		};
368		self.writer.write_all(&packed)?;
369		Ok(())
370	}
371}
372
373impl<W> Write for OggStreamWriter<W>
374where
375	W: Write + Debug {
376	fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
377		self.bytes_written = buf.len() as u64;
378		let mut buf = buf;
379		let mut written_total = 0usize;
380		while !buf.is_empty() {
381			let written = self.cur_packet.write(buf);
382			buf = &buf[written..];
383			written_total += written;
384			if !buf.is_empty() {
385				self.granule_position = (self.on_seal)(self.cur_packet.get_inner_data_size());
386				self.seal_packet(self.granule_position, false)?;
387			}
388		}
389		Ok(written_total)
390	}
391
392	fn flush(&mut self) -> io::Result<()> {
393		self.writer.flush()
394	}
395}
396
397impl<W> Debug for OggStreamWriter<W>
398where
399	W: Write + Debug {
400	fn fmt(&self, f: &mut Formatter) -> fmt::Result {
401		f.debug_struct(&format!("OggStreamWriter<{}>", std::any::type_name::<W>()))
402		.field("writer", &self.writer)
403		.field("stream_id", &format_args!("0x{:08x}", self.stream_id))
404		.field("packet_index", &self.packet_index)
405		.field("cur_packet", &self.cur_packet)
406		.field("granule_position", &self.granule_position)
407		.field("on_seal", &format_args!("<closure>"))
408		.field("bytes_written", &self.bytes_written)
409		.finish()
410	}
411}
412
413impl<W> Drop for OggStreamWriter<W>
414where
415	W: Write + Debug {
416	fn drop(&mut self) {
417		self.seal_packet(self.granule_position, true).unwrap();
418	}
419}
420