1#[cfg(test)]
11mod ogg_reader_test;
12
13use std::io::{Cursor, Read};
14
15use byteorder::{LittleEndian, ReadBytesExt};
16use bytes::BytesMut;
17
18use crate::io::ResetFn;
19use shared::error::{Error, Result};
20
21pub const PAGE_HEADER_TYPE_CONTINUATION_OF_STREAM: u8 = 0x00;
23pub const PAGE_HEADER_TYPE_BEGINNING_OF_STREAM: u8 = 0x02;
25pub const PAGE_HEADER_TYPE_END_OF_STREAM: u8 = 0x04;
27pub const DEFAULT_PRE_SKIP: u16 = 3840; pub const PAGE_HEADER_SIGNATURE: &[u8] = b"OggS";
32pub const ID_PAGE_SIGNATURE: &[u8] = b"OpusHead";
34pub const COMMENT_PAGE_SIGNATURE: &[u8] = b"OpusTags";
36pub const PAGE_HEADER_SIZE: usize = 27;
38pub const ID_PAGE_PAYLOAD_SIZE: usize = 19;
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum OggHeaderType {
44 OpusHead,
46 OpusTags,
48}
49
50pub struct OggReader<R: Read> {
52 reader: R,
53 bytes_read: usize,
54 checksum_table: [u32; 256],
55 do_checksum: bool,
56}
57
58#[derive(Debug, Clone)]
62pub struct OggHeader {
63 pub channel_map: u8,
65 pub channels: u8,
67 pub output_gain: u16,
69 pub pre_skip: u16,
71 pub sample_rate: u32,
73 pub version: u8,
75 pub stream_count: u8,
77 pub coupled_count: u8,
79 pub channel_mapping: Vec<u8>,
81}
82
83#[derive(Debug, Clone, Default)]
86pub struct OpusTags {
87 pub vendor: String,
89 pub user_comments: Vec<UserComment>,
91}
92
93#[derive(Debug, Clone)]
95pub struct UserComment {
96 pub comment: String,
98 pub value: String,
100}
101
102#[derive(Debug, Clone)]
106pub struct OggPageHeader {
107 pub granule_position: u64,
109 pub serial: u32,
111 pub header_type: u8,
113
114 sig: [u8; 4],
115 version: u8,
116 index: u32,
117 segments_count: u8,
118}
119
120impl OggPageHeader {
121 pub fn opus_header_type(&self, payload: &[u8]) -> Option<OggHeaderType> {
123 if payload.len() < 8 {
124 return None;
125 }
126
127 let sig = &payload[..8];
128 if sig == ID_PAGE_SIGNATURE {
129 if self.header_type == PAGE_HEADER_TYPE_BEGINNING_OF_STREAM {
131 return Some(OggHeaderType::OpusHead);
132 }
133 return None;
134 }
135 if sig == COMMENT_PAGE_SIGNATURE {
136 return Some(OggHeaderType::OpusTags);
137 }
138
139 None
140 }
141
142 pub fn is_beginning_of_stream(&self) -> bool {
144 self.header_type == PAGE_HEADER_TYPE_BEGINNING_OF_STREAM
145 }
146
147 pub fn is_end_of_stream(&self) -> bool {
149 self.header_type == PAGE_HEADER_TYPE_END_OF_STREAM
150 }
151}
152
153pub fn parse_opus_head(payload: &[u8]) -> Result<OggHeader> {
156 if payload.len() < ID_PAGE_PAYLOAD_SIZE {
157 return Err(Error::ErrBadIDPageLength);
158 }
159
160 if &payload[..8] != ID_PAGE_SIGNATURE {
161 return Err(Error::ErrBadIDPagePayloadSignature);
162 }
163
164 let mut reader = Cursor::new(&payload[8..]);
165 let version = reader.read_u8()?;
166 let channels = reader.read_u8()?;
167 let pre_skip = reader.read_u16::<LittleEndian>()?;
168 let sample_rate = reader.read_u32::<LittleEndian>()?;
169 let output_gain = reader.read_u16::<LittleEndian>()?;
170 let channel_map = reader.read_u8()?;
171
172 let (stream_count, coupled_count, channel_mapping) = match channel_map {
173 0 => {
174 if payload.len() != ID_PAGE_PAYLOAD_SIZE {
176 return Err(Error::ErrBadIDPageLength);
177 }
178 (0, 0, vec![])
179 }
180 1 | 2 | 255 => {
181 let expected_len = 21 + channels as usize;
183 if payload.len() < expected_len {
184 return Err(Error::ErrBadIDPageLength);
185 }
186 let stream_count = payload[19];
187 let coupled_count = payload[20];
188 let channel_mapping = payload[21..expected_len].to_vec();
189 (stream_count, coupled_count, channel_mapping)
190 }
191 3 => {
192 return Err(Error::ErrUnsupportedChannelMappingFamily);
193 }
194 _ => {
195 return Err(Error::ErrUnsupportedChannelMappingFamily);
196 }
197 };
198
199 Ok(OggHeader {
200 channel_map,
201 channels,
202 output_gain,
203 pre_skip,
204 sample_rate,
205 version,
206 stream_count,
207 coupled_count,
208 channel_mapping,
209 })
210}
211
212pub fn parse_opus_tags(payload: &[u8]) -> Result<OpusTags> {
215 const HEADER_MAGIC_LEN: usize = 8;
216 const U32_SIZE: usize = 4;
217 const MIN_HEADER_LEN: usize = HEADER_MAGIC_LEN + U32_SIZE + U32_SIZE;
218
219 if payload.len() < MIN_HEADER_LEN {
220 return Err(Error::ErrBadOpusTagsSignature);
221 }
222
223 if &payload[..8] != COMMENT_PAGE_SIGNATURE {
224 return Err(Error::ErrBadOpusTagsSignature);
225 }
226
227 let vendor_len = u32::from_le_bytes([
229 payload[HEADER_MAGIC_LEN],
230 payload[HEADER_MAGIC_LEN + 1],
231 payload[HEADER_MAGIC_LEN + 2],
232 payload[HEADER_MAGIC_LEN + 3],
233 ]) as usize;
234
235 let vendor_start = HEADER_MAGIC_LEN + U32_SIZE;
236 let vendor_end = vendor_start + vendor_len;
237
238 if vendor_end + U32_SIZE > payload.len() {
239 return Err(Error::ErrBadOpusTagsSignature);
240 }
241
242 let vendor = String::from_utf8_lossy(&payload[vendor_start..vendor_end]).to_string();
243
244 let comment_count = u32::from_le_bytes([
246 payload[vendor_end],
247 payload[vendor_end + 1],
248 payload[vendor_end + 2],
249 payload[vendor_end + 3],
250 ]) as usize;
251
252 let mut pos = vendor_end + U32_SIZE;
253 let mut user_comments = Vec::with_capacity(comment_count);
254
255 for _ in 0..comment_count {
256 if pos + U32_SIZE > payload.len() {
257 return Err(Error::ErrBadOpusTagsSignature);
258 }
259
260 let comment_len = u32::from_le_bytes([
261 payload[pos],
262 payload[pos + 1],
263 payload[pos + 2],
264 payload[pos + 3],
265 ]) as usize;
266 pos += U32_SIZE;
267
268 if pos + comment_len > payload.len() {
269 return Err(Error::ErrBadOpusTagsSignature);
270 }
271
272 let comment_str = String::from_utf8_lossy(&payload[pos..pos + comment_len]).to_string();
273 pos += comment_len;
274
275 if let Some(eq_pos) = comment_str.find('=') {
277 user_comments.push(UserComment {
278 comment: comment_str[..eq_pos].to_string(),
279 value: comment_str[eq_pos + 1..].to_string(),
280 });
281 }
282 }
283
284 Ok(OpusTags {
285 vendor,
286 user_comments,
287 })
288}
289
290impl<R: Read> OggReader<R> {
291 pub fn new(reader: R, do_checksum: bool) -> Result<(OggReader<R>, OggHeader)> {
299 let mut r = OggReader {
300 reader,
301 bytes_read: 0,
302 checksum_table: generate_checksum_table(),
303 do_checksum,
304 };
305
306 let header = r.read_headers()?;
307
308 Ok((r, header))
309 }
310
311 pub fn new_with_options(reader: R, do_checksum: bool) -> OggReader<R> {
318 OggReader {
319 reader,
320 bytes_read: 0,
321 checksum_table: generate_checksum_table(),
322 do_checksum,
323 }
324 }
325
326 fn read_headers(&mut self) -> Result<OggHeader> {
327 let (payload, page_header) = self.parse_next_page()?;
328
329 if page_header.sig != PAGE_HEADER_SIGNATURE {
330 return Err(Error::ErrBadIDPageSignature);
331 }
332
333 if page_header.header_type != PAGE_HEADER_TYPE_BEGINNING_OF_STREAM {
334 return Err(Error::ErrBadIDPageType);
335 }
336
337 parse_opus_head(&payload)
338 }
339
340 pub fn parse_next_page(&mut self) -> Result<(BytesMut, OggPageHeader)> {
348 let mut h = [0u8; PAGE_HEADER_SIZE];
349 self.reader.read_exact(&mut h)?;
350
351 let mut head_reader = Cursor::new(h);
352 let mut sig = [0u8; 4]; head_reader.read_exact(&mut sig)?;
354 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];
363 self.reader.read_exact(&mut size_buffer)?;
364
365 let mut payload_size = 0usize;
366 for s in &size_buffer {
367 payload_size += *s as usize;
368 }
369
370 let mut payload = BytesMut::with_capacity(payload_size);
371 payload.resize(payload_size, 0);
372 self.reader.read_exact(&mut payload)?;
373
374 if self.do_checksum {
375 let mut sum = 0;
376
377 for (index, v) in h.iter().enumerate() {
378 if index > 21 && index < 26 {
380 sum = self.update_checksum(0, sum);
381 continue;
382 }
383 sum = self.update_checksum(*v, sum);
384 }
385
386 for v in &size_buffer {
387 sum = self.update_checksum(*v, sum);
388 }
389 for v in &payload[..] {
390 sum = self.update_checksum(*v, sum);
391 }
392
393 if sum != checksum {
394 return Err(Error::ErrChecksumMismatch);
395 }
396 }
397
398 let page_header = OggPageHeader {
399 granule_position,
400 sig,
401 version,
402 header_type,
403 serial,
404 index,
405 segments_count,
406 };
407
408 Ok((payload, page_header))
409 }
410
411 pub fn reset_reader(&mut self, mut reset: ResetFn<R>) {
415 self.reader = reset(self.bytes_read);
416 }
417
418 fn update_checksum(&self, v: u8, sum: u32) -> u32 {
419 (sum << 8) ^ self.checksum_table[(((sum >> 24) as u8) ^ v) as usize]
420 }
421}
422
423pub(crate) fn generate_checksum_table() -> [u32; 256] {
424 let mut table = [0u32; 256];
425 const POLY: u32 = 0x04c11db7;
426
427 for (i, t) in table.iter_mut().enumerate() {
428 let mut r = (i as u32) << 24;
429 for _ in 0..8 {
430 if (r & 0x80000000) != 0 {
431 r = (r << 1) ^ POLY;
432 } else {
433 r <<= 1;
434 }
435 }
436 *t = r;
437 }
438 table
439}