1#[cfg(test)]
2mod ogg_reader_test;
3
4use std::io::{Cursor, Read};
5
6use byteorder::{LittleEndian, ReadBytesExt};
7use bytes::BytesMut;
8
9use crate::io::ResetFn;
10use shared::error::{Error, Result};
11
12pub const PAGE_HEADER_TYPE_CONTINUATION_OF_STREAM: u8 = 0x00;
13pub const PAGE_HEADER_TYPE_BEGINNING_OF_STREAM: u8 = 0x02;
14pub const PAGE_HEADER_TYPE_END_OF_STREAM: u8 = 0x04;
15pub const DEFAULT_PRE_SKIP: u16 = 3840; pub const PAGE_HEADER_SIGNATURE: &[u8] = b"OggS";
17pub const ID_PAGE_SIGNATURE: &[u8] = b"OpusHead";
18pub const COMMENT_PAGE_SIGNATURE: &[u8] = b"OpusTags";
19pub const PAGE_HEADER_SIZE: usize = 27;
20pub const ID_PAGE_PAYLOAD_SIZE: usize = 19;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum OggHeaderType {
25 OpusHead,
27 OpusTags,
29}
30
31pub struct OggReader<R: Read> {
33 reader: R,
34 bytes_read: usize,
35 checksum_table: [u32; 256],
36 do_checksum: bool,
37}
38
39#[derive(Debug, Clone)]
43pub struct OggHeader {
44 pub channel_map: u8,
45 pub channels: u8,
46 pub output_gain: u16,
47 pub pre_skip: u16,
48 pub sample_rate: u32,
49 pub version: u8,
50 pub stream_count: u8,
51 pub coupled_count: u8,
52 pub channel_mapping: Vec<u8>,
53}
54
55#[derive(Debug, Clone, Default)]
58pub struct OpusTags {
59 pub vendor: String,
60 pub user_comments: Vec<UserComment>,
61}
62
63#[derive(Debug, Clone)]
65pub struct UserComment {
66 pub comment: String,
67 pub value: String,
68}
69
70#[derive(Debug, Clone)]
74pub struct OggPageHeader {
75 pub granule_position: u64,
76 pub serial: u32,
78 pub header_type: u8,
80
81 sig: [u8; 4],
82 version: u8,
83 index: u32,
84 segments_count: u8,
85}
86
87impl OggPageHeader {
88 pub fn opus_header_type(&self, payload: &[u8]) -> Option<OggHeaderType> {
90 if payload.len() < 8 {
91 return None;
92 }
93
94 let sig = &payload[..8];
95 if sig == ID_PAGE_SIGNATURE {
96 if self.header_type == PAGE_HEADER_TYPE_BEGINNING_OF_STREAM {
98 return Some(OggHeaderType::OpusHead);
99 }
100 return None;
101 }
102 if sig == COMMENT_PAGE_SIGNATURE {
103 return Some(OggHeaderType::OpusTags);
104 }
105
106 None
107 }
108
109 pub fn is_beginning_of_stream(&self) -> bool {
111 self.header_type == PAGE_HEADER_TYPE_BEGINNING_OF_STREAM
112 }
113
114 pub fn is_end_of_stream(&self) -> bool {
116 self.header_type == PAGE_HEADER_TYPE_END_OF_STREAM
117 }
118}
119
120pub fn parse_opus_head(payload: &[u8]) -> Result<OggHeader> {
123 if payload.len() < ID_PAGE_PAYLOAD_SIZE {
124 return Err(Error::ErrBadIDPageLength);
125 }
126
127 if &payload[..8] != ID_PAGE_SIGNATURE {
128 return Err(Error::ErrBadIDPagePayloadSignature);
129 }
130
131 let mut reader = Cursor::new(&payload[8..]);
132 let version = reader.read_u8()?;
133 let channels = reader.read_u8()?;
134 let pre_skip = reader.read_u16::<LittleEndian>()?;
135 let sample_rate = reader.read_u32::<LittleEndian>()?;
136 let output_gain = reader.read_u16::<LittleEndian>()?;
137 let channel_map = reader.read_u8()?;
138
139 let (stream_count, coupled_count, channel_mapping) = match channel_map {
140 0 => {
141 if payload.len() != ID_PAGE_PAYLOAD_SIZE {
143 return Err(Error::ErrBadIDPageLength);
144 }
145 (0, 0, vec![])
146 }
147 1 | 2 | 255 => {
148 let expected_len = 21 + channels as usize;
150 if payload.len() < expected_len {
151 return Err(Error::ErrBadIDPageLength);
152 }
153 let stream_count = payload[19];
154 let coupled_count = payload[20];
155 let channel_mapping = payload[21..expected_len].to_vec();
156 (stream_count, coupled_count, channel_mapping)
157 }
158 3 => {
159 return Err(Error::ErrUnsupportedChannelMappingFamily);
160 }
161 _ => {
162 return Err(Error::ErrUnsupportedChannelMappingFamily);
163 }
164 };
165
166 Ok(OggHeader {
167 channel_map,
168 channels,
169 output_gain,
170 pre_skip,
171 sample_rate,
172 version,
173 stream_count,
174 coupled_count,
175 channel_mapping,
176 })
177}
178
179pub fn parse_opus_tags(payload: &[u8]) -> Result<OpusTags> {
182 const HEADER_MAGIC_LEN: usize = 8;
183 const U32_SIZE: usize = 4;
184 const MIN_HEADER_LEN: usize = HEADER_MAGIC_LEN + U32_SIZE + U32_SIZE;
185
186 if payload.len() < MIN_HEADER_LEN {
187 return Err(Error::ErrBadOpusTagsSignature);
188 }
189
190 if &payload[..8] != COMMENT_PAGE_SIGNATURE {
191 return Err(Error::ErrBadOpusTagsSignature);
192 }
193
194 let vendor_len = u32::from_le_bytes([
196 payload[HEADER_MAGIC_LEN],
197 payload[HEADER_MAGIC_LEN + 1],
198 payload[HEADER_MAGIC_LEN + 2],
199 payload[HEADER_MAGIC_LEN + 3],
200 ]) as usize;
201
202 let vendor_start = HEADER_MAGIC_LEN + U32_SIZE;
203 let vendor_end = vendor_start + vendor_len;
204
205 if vendor_end + U32_SIZE > payload.len() {
206 return Err(Error::ErrBadOpusTagsSignature);
207 }
208
209 let vendor = String::from_utf8_lossy(&payload[vendor_start..vendor_end]).to_string();
210
211 let comment_count = u32::from_le_bytes([
213 payload[vendor_end],
214 payload[vendor_end + 1],
215 payload[vendor_end + 2],
216 payload[vendor_end + 3],
217 ]) as usize;
218
219 let mut pos = vendor_end + U32_SIZE;
220 let mut user_comments = Vec::with_capacity(comment_count);
221
222 for _ in 0..comment_count {
223 if pos + U32_SIZE > payload.len() {
224 return Err(Error::ErrBadOpusTagsSignature);
225 }
226
227 let comment_len = u32::from_le_bytes([
228 payload[pos],
229 payload[pos + 1],
230 payload[pos + 2],
231 payload[pos + 3],
232 ]) as usize;
233 pos += U32_SIZE;
234
235 if pos + comment_len > payload.len() {
236 return Err(Error::ErrBadOpusTagsSignature);
237 }
238
239 let comment_str = String::from_utf8_lossy(&payload[pos..pos + comment_len]).to_string();
240 pos += comment_len;
241
242 if let Some(eq_pos) = comment_str.find('=') {
244 user_comments.push(UserComment {
245 comment: comment_str[..eq_pos].to_string(),
246 value: comment_str[eq_pos + 1..].to_string(),
247 });
248 }
249 }
250
251 Ok(OpusTags {
252 vendor,
253 user_comments,
254 })
255}
256
257impl<R: Read> OggReader<R> {
258 pub fn new(reader: R, do_checksum: bool) -> Result<(OggReader<R>, OggHeader)> {
266 let mut r = OggReader {
267 reader,
268 bytes_read: 0,
269 checksum_table: generate_checksum_table(),
270 do_checksum,
271 };
272
273 let header = r.read_headers()?;
274
275 Ok((r, header))
276 }
277
278 pub fn new_with_options(reader: R, do_checksum: bool) -> OggReader<R> {
285 OggReader {
286 reader,
287 bytes_read: 0,
288 checksum_table: generate_checksum_table(),
289 do_checksum,
290 }
291 }
292
293 fn read_headers(&mut self) -> Result<OggHeader> {
294 let (payload, page_header) = self.parse_next_page()?;
295
296 if page_header.sig != PAGE_HEADER_SIGNATURE {
297 return Err(Error::ErrBadIDPageSignature);
298 }
299
300 if page_header.header_type != PAGE_HEADER_TYPE_BEGINNING_OF_STREAM {
301 return Err(Error::ErrBadIDPageType);
302 }
303
304 parse_opus_head(&payload)
305 }
306
307 pub fn parse_next_page(&mut self) -> Result<(BytesMut, OggPageHeader)> {
310 let mut h = [0u8; PAGE_HEADER_SIZE];
311 self.reader.read_exact(&mut h)?;
312
313 let mut head_reader = Cursor::new(h);
314 let mut sig = [0u8; 4]; head_reader.read_exact(&mut sig)?;
316 let version = head_reader.read_u8()?; let header_type = head_reader.read_u8()?; let granule_position = head_reader.read_u64::<LittleEndian>()?; let serial = head_reader.read_u32::<LittleEndian>()?; let index = head_reader.read_u32::<LittleEndian>()?; let checksum = head_reader.read_u32::<LittleEndian>()?; let segments_count = head_reader.read_u8()?; let mut size_buffer = vec![0u8; segments_count as usize];
325 self.reader.read_exact(&mut size_buffer)?;
326
327 let mut payload_size = 0usize;
328 for s in &size_buffer {
329 payload_size += *s as usize;
330 }
331
332 let mut payload = BytesMut::with_capacity(payload_size);
333 payload.resize(payload_size, 0);
334 self.reader.read_exact(&mut payload)?;
335
336 if self.do_checksum {
337 let mut sum = 0;
338
339 for (index, v) in h.iter().enumerate() {
340 if index > 21 && index < 26 {
342 sum = self.update_checksum(0, sum);
343 continue;
344 }
345 sum = self.update_checksum(*v, sum);
346 }
347
348 for v in &size_buffer {
349 sum = self.update_checksum(*v, sum);
350 }
351 for v in &payload[..] {
352 sum = self.update_checksum(*v, sum);
353 }
354
355 if sum != checksum {
356 return Err(Error::ErrChecksumMismatch);
357 }
358 }
359
360 let page_header = OggPageHeader {
361 granule_position,
362 sig,
363 version,
364 header_type,
365 serial,
366 index,
367 segments_count,
368 };
369
370 Ok((payload, page_header))
371 }
372
373 pub fn reset_reader(&mut self, mut reset: ResetFn<R>) {
377 self.reader = reset(self.bytes_read);
378 }
379
380 fn update_checksum(&self, v: u8, sum: u32) -> u32 {
381 (sum << 8) ^ self.checksum_table[(((sum >> 24) as u8) ^ v) as usize]
382 }
383}
384
385pub(crate) fn generate_checksum_table() -> [u32; 256] {
386 let mut table = [0u32; 256];
387 const POLY: u32 = 0x04c11db7;
388
389 for (i, t) in table.iter_mut().enumerate() {
390 let mut r = (i as u32) << 24;
391 for _ in 0..8 {
392 if (r & 0x80000000) != 0 {
393 r = (r << 1) ^ POLY;
394 } else {
395 r <<= 1;
396 }
397 }
398 *t = r;
399 }
400 table
401}