OGG: Read entire packets instead of pages

This makes the handling of OGG files a lot more spec-compliant, and simpler overall.
This commit is contained in:
Serial 2022-11-24 13:55:21 -05:00 committed by Alex
parent a08088ddf8
commit 8051fafbc8
14 changed files with 433 additions and 214 deletions

View file

@ -24,7 +24,7 @@ lofty_attr = { path = "lofty_attr" }
# Debug logging
log = "0.4.17"
# OGG Vorbis/Opus
ogg_pager = "0.3.2"
ogg_pager = { path = "ogg_pager" }
# Key maps
once_cell = "1.13.0"
paste = "1.0.7"

View file

@ -15,6 +15,8 @@ pub enum PageError {
MissingMagic,
/// The reader contains too much data for a single page
TooMuchData,
/// The reader contains too little data to extract the expected information
NotEnoughData,
/// Any std::io::Error
Io(std::io::Error),
}
@ -28,6 +30,9 @@ impl fmt::Display for PageError {
PageError::BadSegmentCount => write!(f, "Page has a segment count < 1"),
PageError::MissingMagic => write!(f, "Page is missing a magic signature"),
PageError::TooMuchData => write!(f, "Too much data was provided"),
PageError::NotEnoughData => {
write!(f, "Too little data is available for the expected read")
},
PageError::Io(err) => write!(f, "{}", err),
}
}

94
ogg_pager/src/header.rs Normal file
View file

@ -0,0 +1,94 @@
use crate::{PageError, Result};
use std::io::{Read, Seek};
use byteorder::{LittleEndian, ReadBytesExt};
// An OGG page header
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct PageHeader {
/// The position in the stream the page started at
pub start: u64,
pub(crate) header_type_flag: u8,
/// The page's absolute granule position
pub abgp: u64,
/// The page's stream serial number
pub stream_serial: u32,
/// The page's sequence number
pub sequence_number: u32,
pub(crate) checksum: u32,
}
impl PageHeader {
pub fn new(header_type_flag: u8, abgp: u64, stream_serial: u32, sequence_number: u32) -> Self {
Self {
start: 0,
header_type_flag,
abgp,
stream_serial,
sequence_number,
checksum: 0,
}
}
pub fn read<R>(data: &mut R) -> Result<(Self, Vec<u8>)>
where
R: Read + Seek,
{
let start = data.stream_position()?;
let mut sig = [0; 4];
data.read_exact(&mut sig)?;
if &sig != b"OggS" {
return Err(PageError::MissingMagic);
}
// Version, always 0
let version = data.read_u8()?;
if version != 0 {
return Err(PageError::InvalidVersion);
}
let header_type_flag = data.read_u8()?;
let abgp = data.read_u64::<LittleEndian>()?;
let stream_serial = data.read_u32::<LittleEndian>()?;
let sequence_number = data.read_u32::<LittleEndian>()?;
let checksum = data.read_u32::<LittleEndian>()?;
let segments = data.read_u8()?;
if segments < 1 {
return Err(PageError::BadSegmentCount);
}
let mut segment_table = vec![0; segments as usize];
data.read_exact(&mut segment_table)?;
let header = Self {
start,
header_type_flag,
abgp,
stream_serial,
sequence_number,
checksum,
};
Ok((header, segment_table))
}
/// Returns the page's header type flag
pub fn header_type_flag(&self) -> u8 {
self.header_type_flag
}
/// Returns the page's checksum
///
/// NOTE: This will not generate a new CRC. It will return
/// the CRC as-is. Use [`Page::gen_crc`] to generate a new one.
pub fn checksum(&self) -> u32 {
self.checksum
}
}

View file

@ -2,13 +2,13 @@
mod crc;
mod error;
mod header;
use std::io::{Read, Seek, SeekFrom};
use byteorder::{LittleEndian, ReadBytesExt};
pub use crc::crc32;
pub use error::{PageError, Result};
pub use header::PageHeader;
const CONTINUED_PACKET: u8 = 0x01;
@ -23,16 +23,7 @@ pub const CONTAINS_LAST_PAGE_OF_BITSTREAM: u8 = 0x04;
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Page {
content: Vec<u8>,
header_type: u8,
/// The page's absolute granule position
pub abgp: u64,
/// The page's stream serial number
pub serial: u32,
/// The page's sequence number
pub seq_num: u32,
checksum: u32,
/// The position in the stream the page started at
pub start: u64,
header: PageHeader,
/// The position in the stream the page ended
pub end: u64,
}
@ -66,13 +57,7 @@ impl Page {
/// ident_header_packet,
/// );
/// ```
pub fn new(
header_type_flag: u8,
abgp: u64,
stream_serial: u32,
sequence_number: u32,
content: Vec<u8>,
) -> Result<Self> {
pub fn new(header: PageHeader, content: Vec<u8>) -> Result<Self> {
let content_len = content.len();
if content_len > MAX_CONTENT_SIZE {
@ -81,16 +66,19 @@ impl Page {
Ok(Self {
content,
header_type: header_type_flag,
abgp,
serial: stream_serial,
seq_num: sequence_number,
checksum: 0,
start: 0,
header,
end: content_len as u64,
})
}
pub fn header(&self) -> &PageHeader {
&self.header
}
pub fn header_mut(&mut self) -> &mut PageHeader {
&mut self.header
}
/// Convert the Page to Vec<u8> for writing
///
/// NOTE: This will write the checksum as is. It is likely [`Page::gen_crc`] will have
@ -105,11 +93,11 @@ impl Page {
bytes.extend(b"OggS");
bytes.push(0); // Version
bytes.push(self.header_type);
bytes.extend(self.abgp.to_le_bytes());
bytes.extend(self.serial.to_le_bytes());
bytes.extend(self.seq_num.to_le_bytes());
bytes.extend(self.checksum.to_le_bytes());
bytes.push(self.header.header_type_flag);
bytes.extend(self.header.abgp.to_le_bytes());
bytes.extend(self.header.stream_serial.to_le_bytes());
bytes.extend(self.header.sequence_number.to_le_bytes());
bytes.extend(self.header.checksum.to_le_bytes());
bytes.push(segment_table.len() as u8);
bytes.append(&mut segment_table);
bytes.extend(self.content.iter());
@ -129,37 +117,7 @@ impl Page {
where
V: Read + Seek,
{
let start = data.stream_position()?;
let mut sig = [0; 4];
data.read_exact(&mut sig)?;
if &sig != b"OggS" {
return Err(PageError::MissingMagic);
}
// Version, always 0
let version = data.read_u8()?;
if version != 0 {
return Err(PageError::InvalidVersion);
}
let header_type = data.read_u8()?;
let abgp = data.read_u64::<LittleEndian>()?;
let serial = data.read_u32::<LittleEndian>()?;
let seq_num = data.read_u32::<LittleEndian>()?;
let checksum = data.read_u32::<LittleEndian>()?;
let segments = data.read_u8()?;
if segments < 1 {
return Err(PageError::BadSegmentCount);
}
let mut segment_table = vec![0; segments as usize];
data.read_exact(&mut segment_table)?;
let (header, segment_table) = PageHeader::read(data)?;
let mut content: Vec<u8> = Vec::new();
let content_len: u16 = segment_table.iter().map(|&b| u16::from(b)).sum();
@ -175,12 +133,7 @@ impl Page {
Ok(Page {
content,
header_type,
abgp,
serial,
seq_num,
checksum,
start,
header,
end,
})
}
@ -191,7 +144,7 @@ impl Page {
///
/// See [`Page::as_bytes`]
pub fn gen_crc(&mut self) -> Result<()> {
self.checksum = crc::crc32(&self.as_bytes()?);
self.header.checksum = crc::crc32(&self.as_bytes()?);
Ok(())
}
@ -220,19 +173,21 @@ impl Page {
let remaining = 65025 - self_len;
self.content.extend(content[0..remaining].iter());
self.header_type = 0;
self.abgp = 1_u64.wrapping_neg(); // -1 in two's complement indicates that no packets finish on this page
self.header.header_type_flag = 0;
self.header.abgp = 1_u64.wrapping_neg(); // -1 in two's complement indicates that no packets finish on this page
self.end += remaining as u64;
let mut p = Page {
content: content[remaining..].to_vec(),
header_type: 1,
abgp: 0,
serial: self.serial,
seq_num: self.seq_num + 1,
checksum: 0,
start: self.end,
end: self.start + content.len() as u64,
header: PageHeader {
start: self.end,
header_type_flag: 1,
abgp: 0,
stream_serial: self.header.stream_serial,
sequence_number: self.header.sequence_number + 1,
checksum: 0,
},
end: self.header().start + content.len() as u64,
};
p.gen_crc()?;
@ -253,19 +208,6 @@ impl Page {
self.content
}
/// Returns the page's header type flag
pub fn header_type(&self) -> u8 {
self.header_type
}
/// Returns the page's checksum
///
/// NOTE: This will not generate a new CRC. It will return
/// the CRC as-is. Use [`Page::gen_crc`] to generate a new one.
pub fn checksum(&self) -> u32 {
self.checksum
}
/// Returns the page's segment table
///
/// # Errors
@ -299,22 +241,24 @@ pub fn paginate(packet: &[u8], stream_serial: u32, abgp: u64, flags: u8) -> Vec<
for (idx, page) in packet.chunks(MAX_CONTENT_SIZE).enumerate() {
let p = Page {
content: page.to_vec(),
header_type: {
if first_page {
if flags & CONTAINS_FIRST_PAGE_OF_BITSTREAM == 0x02 {
CONTAINS_LAST_PAGE_OF_BITSTREAM
header: PageHeader {
start: pos,
header_type_flag: {
if first_page {
if flags & CONTAINS_FIRST_PAGE_OF_BITSTREAM == 0x02 {
CONTAINS_LAST_PAGE_OF_BITSTREAM
} else {
0
}
} else {
0
CONTINUED_PACKET
}
} else {
CONTINUED_PACKET
}
},
abgp,
stream_serial,
sequence_number: (idx + 1) as u32,
checksum: 0,
},
abgp,
serial: stream_serial,
seq_num: (idx + 1) as u32,
checksum: 0,
start: pos,
end: {
pos += page.len() as u64;
pos
@ -327,7 +271,7 @@ pub fn paginate(packet: &[u8], stream_serial: u32, abgp: u64, flags: u8) -> Vec<
if flags & CONTAINS_LAST_PAGE_OF_BITSTREAM == 0x04 {
if let Some(last) = pages.last_mut() {
last.header_type |= CONTAINS_LAST_PAGE_OF_BITSTREAM;
last.header.header_type_flag |= CONTAINS_LAST_PAGE_OF_BITSTREAM;
}
}
@ -339,7 +283,7 @@ pub fn paginate(packet: &[u8], stream_serial: u32, abgp: u64, flags: u8) -> Vec<
break;
}
p.abgp = 1_u64.wrapping_neg();
p.header.abgp = 1_u64.wrapping_neg();
}
}
@ -379,9 +323,135 @@ pub fn segment_table(length: usize) -> Result<Vec<u8>> {
Ok(segments)
}
pub struct Packets {
content: Vec<u8>,
packet_sizes: Vec<u64>,
}
impl Packets {
pub fn read<R>(data: &mut R) -> Result<Self>
where
R: Read + Seek,
{
Self::read_count(data, -1)
}
pub fn read_count<R>(data: &mut R, count: isize) -> Result<Self>
where
R: Read + Seek,
{
let mut content = Vec::new();
let mut packet_sizes = Vec::new();
if count == 0 || count < -1 {
return Ok(Self {
content,
packet_sizes,
});
}
let mut read = 0;
'outer: loop {
if let Ok(page) = Page::read(data, false) {
let segment_table = page.segment_table()?;
let mut packet_size = 0_u64;
for i in segment_table {
packet_size += i as u64;
if i < 255 {
if count != -1 {
read += 1;
}
content.extend_from_slice(&page.content[0..packet_size as usize]);
packet_sizes.push(packet_size);
packet_size = 0;
if read == count {
break 'outer;
}
}
}
continue;
}
break;
}
if count != -1 && packet_sizes.len() != read as usize {
return Err(PageError::NotEnoughData);
}
Ok(Self {
content,
packet_sizes,
})
}
pub fn get(&self, idx: usize) -> Option<&[u8]> {
if idx >= self.content.len() {
return None;
}
let start_pos = match idx {
// Packet 0 starts at pos 0
0 => 0,
// Anything else we have to get the size of the previous packet
other => self.packet_sizes[other - 1] as usize,
};
if let Some(packet_size) = self.packet_sizes.get(idx) {
return Some(&self.content[start_pos..start_pos + *packet_size as usize]);
}
None
}
}
pub struct PacketsIter<'a> {
content: &'a [u8],
packet_sizes: &'a [u64],
cap: usize,
}
impl<'a> Iterator for PacketsIter<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<Self::Item> {
if self.cap == 0 {
return None;
}
let packet_size = self.packet_sizes[0];
self.cap -= 1;
self.packet_sizes = &self.packet_sizes[1..];
let (ret, remaining) = self.content.split_at(packet_size as usize);
self.content = remaining;
Some(ret)
}
}
impl<'a> IntoIterator for &'a Packets {
type Item = &'a [u8];
type IntoIter = PacketsIter<'a>;
fn into_iter(self) -> Self::IntoIter {
PacketsIter {
content: &self.content,
packet_sizes: &self.packet_sizes,
cap: self.packet_sizes.len(),
}
}
}
#[cfg(test)]
mod tests {
use crate::{paginate, segment_table, Page};
use crate::{paginate, segment_table, Page, PageHeader};
use std::io::Cursor;
#[test]
@ -391,12 +461,14 @@ mod tests {
0x4F, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64, 0x01, 0x02, 0x38, 0x01, 0x80, 0xBB,
0, 0, 0, 0, 0,
],
header_type: 2,
abgp: 0,
serial: 1759377061,
seq_num: 0,
checksum: 3579522525,
start: 0,
header: PageHeader {
start: 0,
header_type_flag: 2,
abgp: 0,
stream_serial: 1759377061,
sequence_number: 0,
checksum: 3579522525,
},
end: 47,
};
@ -427,21 +499,23 @@ mod tests {
);
for (i, page) in pages.into_iter().enumerate() {
assert_eq!(page.serial, 1234);
let header = page.header;
assert_eq!(header.stream_serial, 1234);
if i + 1 == len {
assert_eq!(page.abgp, 0);
assert_eq!(header.abgp, 0);
} else {
// -1
assert_eq!(page.abgp, u64::MAX);
assert_eq!(header.abgp, u64::MAX);
}
assert_eq!(page.seq_num, (i + 1) as u32);
assert_eq!(header.sequence_number, (i + 1) as u32);
if i == 0 {
assert_eq!(page.header_type, 0);
assert_eq!(header.header_type_flag, 0);
} else {
assert_eq!(page.header_type, 1);
assert_eq!(header.header_type_flag, 1);
}
}
}

View file

@ -34,10 +34,10 @@ pub use speex::SpeexFile;
pub use vorbis::properties::VorbisProperties;
pub use vorbis::VorbisFile;
pub(self) fn verify_signature(page: &Page, sig: &[u8]) -> Result<()> {
pub(self) fn verify_signature(content: &[u8], sig: &[u8]) -> Result<()> {
let sig_len = sig.len();
if page.content().len() < sig_len || &page.content()[..sig_len] != sig {
if content.len() < sig_len || &content[..sig_len] != sig {
decode_err!(@BAIL Vorbis, "File missing magic signature");
}

View file

@ -38,7 +38,7 @@ impl AudioFile for OpusFile {
let file_information = super::read::read_from(reader, OPUSHEAD, OPUSTAGS)?;
Ok(Self {
properties: if parse_options.read_properties { properties::read_properties(reader, &file_information.1)? } else { OpusProperties::default() },
properties: if parse_options.read_properties { properties::read_properties(reader, file_information.1, &file_information.2)? } else { OpusProperties::default() },
#[cfg(feature = "vorbis_comments")]
// Safe to unwrap, a metadata packet is mandatory in Opus
vorbis_comments_tag: file_information.0.unwrap(),

View file

@ -7,7 +7,7 @@ use std::io::{Read, Seek, SeekFrom};
use std::time::Duration;
use byteorder::{LittleEndian, ReadBytesExt};
use ogg_pager::Page;
use ogg_pager::{Packets, PageHeader};
/// An Opus file's audio properties
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
@ -66,35 +66,35 @@ impl OpusProperties {
}
}
pub(in crate::ogg) fn read_properties<R>(data: &mut R, first_page: &Page) -> Result<OpusProperties>
pub(in crate::ogg) fn read_properties<R>(
data: &mut R,
first_page_header: PageHeader,
packets: &Packets,
) -> Result<OpusProperties>
where
R: Read + Seek,
{
let (stream_len, file_length) = {
let current = data.stream_position()?;
let end = data.seek(SeekFrom::End(0))?;
data.seek(SeekFrom::Start(current))?;
(end - first_page.start, end)
};
let mut properties = OpusProperties::default();
let first_page_abgp = first_page.abgp;
let first_page_abgp = first_page_header.abgp;
// Safe to unwrap, it is impossible to get this far without
// an identification packet.
let identification_packet = packets.get(0).unwrap();
// Skip identification header
let first_page_content = &mut &first_page.content()[8..];
let identification_packet_reader = &mut &identification_packet[8..];
properties.version = first_page_content.read_u8()?;
properties.channels = first_page_content.read_u8()?;
properties.version = identification_packet_reader.read_u8()?;
properties.channels = identification_packet_reader.read_u8()?;
let pre_skip = first_page_content.read_u16::<LittleEndian>()?;
let pre_skip = identification_packet_reader.read_u16::<LittleEndian>()?;
properties.input_sample_rate = first_page_content.read_u32::<LittleEndian>()?;
properties.input_sample_rate = identification_packet_reader.read_u32::<LittleEndian>()?;
let _output_gain = first_page_content.read_u16::<LittleEndian>()?;
let _output_gain = identification_packet_reader.read_u16::<LittleEndian>()?;
let channel_mapping_family = first_page_content.read_u8()?;
let channel_mapping_family = identification_packet_reader.read_u8()?;
// https://datatracker.ietf.org/doc/html/rfc7845.html#section-5.1.1
if (channel_mapping_family == 0 && properties.channels > 2)
@ -103,18 +103,27 @@ where
decode_err!(@BAIL Opus, "Invalid channel count for mapping family");
}
// Subtract the identification and metadata packet length from the total
let audio_size = stream_len - data.stream_position()?;
// Get the last pages' absolute granule position
let last_page = find_last_page(data)?;
let last_page_abgp = last_page.abgp;
let last_page_abgp = last_page.header().abgp;
// Get the stream length
// Also safe to unwrap, metadata is checked prior
let metadata_packet = packets.get(1).unwrap();
let header_size = identification_packet.len() + metadata_packet.len();
let file_length = data.seek(SeekFrom::End(0))?;
let stream_len = file_length - header_size as u64;
if let Some(frame_count) = last_page_abgp.checked_sub(first_page_abgp + u64::from(pre_skip)) {
let length = (frame_count as f64) * 1000.0 / 48000.0_f64 + 0.5;
properties.duration = Duration::from_millis(length as u64);
properties.overall_bitrate = ((file_length as f64) * 8.0 / length) as u32;
properties.audio_bitrate = ((audio_size as f64) * 8.0 / length) as u32;
properties.audio_bitrate = ((stream_len as f64) * 8.0 / length) as u32;
}
Ok(properties)

View file

@ -10,13 +10,13 @@ use std::io::{Read, Seek, SeekFrom};
#[cfg(feature = "vorbis_comments")]
use byteorder::{LittleEndian, ReadBytesExt};
use ogg_pager::Page;
use ogg_pager::{Packets, PageHeader};
#[cfg(feature = "vorbis_comments")]
pub type OGGTags = (Option<VorbisComments>, Page);
pub type OGGTags = (Option<VorbisComments>, PageHeader, Packets);
#[cfg(not(feature = "vorbis_comments"))]
pub type OGGTags = (Option<()>, Page);
pub type OGGTags = (Option<()>, PageHeader, Packets);
#[cfg(feature = "vorbis_comments")]
pub(crate) fn read_comments<R>(data: &mut R, mut len: u64, tag: &mut VorbisComments) -> Result<()>
@ -99,39 +99,38 @@ pub(crate) fn read_from<T>(data: &mut T, header_sig: &[u8], comment_sig: &[u8])
where
T: Read + Seek,
{
let first_page = Page::read(data, false)?;
verify_signature(&first_page, header_sig)?;
// TODO: Would be nice if we didn't have to read just to seek and reread immediately
let start = data.stream_position()?;
let (first_page_header, _) = PageHeader::read(data)?;
let md_page = Page::read(data, false)?;
verify_signature(&md_page, comment_sig)?;
data.seek(SeekFrom::Start(start))?;
let mut md_pages: Vec<u8> = Vec::new();
// Read the first 3 packets, which are all headers
let packets = Packets::read_count(data, 3)?;
md_pages.extend_from_slice(&md_page.content()[comment_sig.len()..]);
let identification_packet = packets
.get(0)
.ok_or_else(|| decode_err!("OGG: Expected identification packet"))?;
verify_signature(identification_packet, header_sig)?;
while let Ok(page) = Page::read(data, false) {
if md_pages.len() > 125_829_120 {
err!(TooMuchData);
}
let mut metadata_packet = packets
.get(1)
.ok_or_else(|| decode_err!("OGG: Expected comment packet"))?;
verify_signature(metadata_packet, comment_sig)?;
if page.header_type() & 0x01 == 1 {
md_pages.extend_from_slice(page.content());
} else {
data.seek(SeekFrom::Start(page.start))?;
break;
}
}
// Remove the signature from the packet
metadata_packet = &metadata_packet[comment_sig.len()..];
#[cfg(feature = "vorbis_comments")]
{
let mut tag = VorbisComments::default();
let reader = &mut &md_pages[..];
let reader = &mut metadata_packet;
read_comments(reader, reader.len() as u64, &mut tag)?;
Ok((Some(tag), first_page))
Ok((Some(tag), first_page_header, packets))
}
#[cfg(not(feature = "vorbis_comments"))]
Ok((None, first_page))
Ok((None, first_page_header, packets))
}

View file

@ -37,7 +37,7 @@ impl AudioFile for SpeexFile {
let file_information = super::read::read_from(reader, SPEEXHEADER, &[])?;
Ok(Self {
properties: if parse_options.read_properties { properties::read_properties(reader, &file_information.1)? } else { SpeexProperties::default() },
properties: if parse_options.read_properties { properties::read_properties(reader, file_information.1, &file_information.2)? } else { SpeexProperties::default() },
#[cfg(feature = "vorbis_comments")]
// Safe to unwrap, a metadata packet is mandatory in Speex
vorbis_comments_tag: file_information.0.unwrap(),

View file

@ -7,7 +7,7 @@ use std::io::{Read, Seek, SeekFrom};
use std::time::Duration;
use byteorder::{LittleEndian, ReadBytesExt};
use ogg_pager::Page;
use ogg_pager::{Packets, PageHeader};
/// A Speex file's audio properties
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
@ -84,13 +84,19 @@ impl SpeexProperties {
}
}
pub(in crate::ogg) fn read_properties<R>(data: &mut R, first_page: &Page) -> Result<SpeexProperties>
pub(in crate::ogg) fn read_properties<R>(
data: &mut R,
first_page_header: PageHeader,
packets: &Packets,
) -> Result<SpeexProperties>
where
R: Read + Seek,
{
let first_page_abgp = first_page.abgp;
// Safe to unwrap, it is impossible to get to this point without an
// identification header.
let identification_packet = packets.get(0).unwrap();
if first_page.content().len() < 80 {
if identification_packet.len() < 80 {
decode_err!(@BAIL Speex, "Header packet too small");
}
@ -101,49 +107,64 @@ where
// Skipping:
// Speex string ("Speex ", 8)
// Speex version (20)
let first_page_content = &mut &first_page.content()[28..];
let identification_packet_reader = &mut &identification_packet[28..];
properties.version = first_page_content.read_u32::<LittleEndian>()?;
properties.version = identification_packet_reader.read_u32::<LittleEndian>()?;
// Total size of the speex header
let _header_size = first_page_content.read_u32::<LittleEndian>()?;
let _header_size = identification_packet_reader.read_u32::<LittleEndian>()?;
properties.sample_rate = first_page_content.read_u32::<LittleEndian>()?;
properties.mode = first_page_content.read_u32::<LittleEndian>()?;
properties.sample_rate = identification_packet_reader.read_u32::<LittleEndian>()?;
properties.mode = identification_packet_reader.read_u32::<LittleEndian>()?;
// Version ID of the bitstream
let _mode_bitstream_version = first_page_content.read_u32::<LittleEndian>()?;
let _mode_bitstream_version = identification_packet_reader.read_u32::<LittleEndian>()?;
let channels = first_page_content.read_u32::<LittleEndian>()?;
let channels = identification_packet_reader.read_u32::<LittleEndian>()?;
if channels != 1 && channels != 2 {
decode_err!(@BAIL Speex, "Found invalid channel count, must be mono or stereo");
}
properties.channels = channels as u8;
properties.nominal_bitrate = first_page_content.read_i32::<LittleEndian>()?;
properties.nominal_bitrate = identification_packet_reader.read_i32::<LittleEndian>()?;
// The size of the frames in samples
let _frame_size = first_page_content.read_u32::<LittleEndian>()?;
let _frame_size = identification_packet_reader.read_u32::<LittleEndian>()?;
properties.vbr = first_page_content.read_u32::<LittleEndian>()? == 1;
let last_page = find_last_page(data)?;
let last_page_abgp = last_page.abgp;
properties.vbr = identification_packet_reader.read_u32::<LittleEndian>()? == 1;
let last_page = find_last_page(data);
let file_length = data.seek(SeekFrom::End(0))?;
if let Some(frame_count) = last_page_abgp.checked_sub(first_page_abgp) {
// This is used for bitrate calculation, it should be the length in
// milliseconds, but if we can't determine it then we'll just use 1000.
let mut length = 1000;
if let Ok(last_page) = last_page {
let first_page_abgp = first_page_header.abgp;
let last_page_abgp = last_page.header().abgp;
if properties.sample_rate > 0 {
let length = ((frame_count as f64) * 1000.0) / f64::from(properties.sample_rate) + 0.5;
properties.duration = Duration::from_millis(length as u64);
let total_samples = last_page_abgp.saturating_sub(first_page_abgp);
properties.overall_bitrate = ((file_length as f64) * 8.0 / length) as u32;
// TODO: Calculate with the stream length, and make this the fallback
properties.audio_bitrate = (properties.nominal_bitrate as u64 / 1000) as u32;
// Best case scenario
if total_samples > 0 {
length = total_samples * 1000 / u64::from(properties.sample_rate);
properties.duration = Duration::from_millis(length);
} else {
log::debug!(
"Speex: The file contains invalid PCM values, unable to calculate length"
);
}
} else {
log::debug!("Speex: Sample rate = 0, unable to calculate length");
}
}
if properties.nominal_bitrate > 0 {
properties.overall_bitrate = (file_length.saturating_mul(8) / length) as u32;
properties.audio_bitrate = (properties.nominal_bitrate as u64 / 1000) as u32;
}
Ok(properties)
}

View file

@ -41,7 +41,7 @@ impl AudioFile for VorbisFile {
super::read::read_from(reader, VORBIS_IDENT_HEAD, VORBIS_COMMENT_HEAD)?;
Ok(Self {
properties: if parse_options.read_properties { properties::read_properties(reader, &file_information.1)? } else { VorbisProperties::default() },
properties: if parse_options.read_properties { properties::read_properties(reader, file_information.1, &file_information.2)? } else { VorbisProperties::default() },
#[cfg(feature = "vorbis_comments")]
// Safe to unwrap, a metadata packet is mandatory in OGG Vorbis
vorbis_comments_tag: file_information.0.unwrap(),

View file

@ -6,7 +6,7 @@ use std::io::{Read, Seek, SeekFrom};
use std::time::Duration;
use byteorder::{LittleEndian, ReadBytesExt};
use ogg_pager::Page;
use ogg_pager::{Packets, PageHeader};
/// An OGG Vorbis file's audio properties
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
@ -85,17 +85,19 @@ impl VorbisProperties {
pub(in crate::ogg) fn read_properties<R>(
data: &mut R,
first_page: &Page,
first_page_header: PageHeader,
packets: &Packets,
) -> Result<VorbisProperties>
where
R: Read + Seek,
{
let first_page_abgp = first_page.abgp;
let mut properties = VorbisProperties::default();
// It's impossible to get this far without the identification packet, safe to unwrap
let first_packet = packets.get(0).unwrap();
// Skip identification header
let first_page_content = &mut &first_page.content()[7..];
let first_page_content = &mut &first_packet[7..];
properties.version = first_page_content.read_u32::<LittleEndian>()?;
@ -106,22 +108,37 @@ where
properties.bitrate_nominal = first_page_content.read_i32::<LittleEndian>()?;
properties.bitrate_minimum = first_page_content.read_i32::<LittleEndian>()?;
let last_page = find_last_page(data)?;
let last_page_abgp = last_page.abgp;
let last_page = find_last_page(data);
let file_length = data.seek(SeekFrom::End(0))?;
if let Some(frame_count) = last_page_abgp.checked_sub(first_page_abgp) {
// This is used for bitrate calculation, it should be the length in
// milliseconds, but if we can't determine it then we'll just use 1000.
let mut length = 1000;
if let Ok(last_page) = last_page {
let first_page_abgp = first_page_header.abgp;
let last_page_abgp = last_page.header().abgp;
if properties.sample_rate > 0 {
let length = frame_count * 1000 / u64::from(properties.sample_rate);
properties.duration = Duration::from_millis(length);
let total_samples = last_page_abgp.saturating_sub(first_page_abgp);
properties.overall_bitrate = ((file_length * 8) / length) as u32;
// TODO: Calculate with the stream length, and make this the fallback
properties.audio_bitrate = (properties.bitrate_nominal as u64 / 1000) as u32;
// Best case scenario
if total_samples > 0 {
length = total_samples * 1000 / u64::from(properties.sample_rate);
properties.duration = Duration::from_millis(length);
} else {
log::debug!(
"Vorbis: The file contains invalid PCM values, unable to calculate length"
);
}
} else {
log::debug!("Vorbis: Sample rate = 0, unable to calculate length");
}
}
if properties.bitrate_nominal > 0 {
properties.overall_bitrate = (file_length.saturating_mul(8) / length) as u32;
properties.audio_bitrate = (properties.bitrate_nominal as u64 / 1000) as u32;
}
Ok(properties)
}

View file

@ -36,8 +36,8 @@ pub(crate) fn write_to(
loop {
let p = Page::read(data, false)?;
if p.header_type() & 0x01 != 1 {
data.seek(SeekFrom::Start(p.start))?;
if p.header().header_type_flag() & 0x01 != 1 {
data.seek(SeekFrom::Start(p.header().start))?;
data.read_to_end(&mut remaining)?;
reached_md_end = true;

View file

@ -146,7 +146,7 @@ where
{
let first_page = Page::read(data, false)?;
let ser = first_page.serial;
let ser = first_page.header().stream_serial;
let mut writer = Vec::new();
writer.write_all(&first_page.as_bytes()?)?;
@ -155,7 +155,7 @@ where
let comment_signature = format.comment_signature();
if let Some(comment_signature) = comment_signature {
verify_signature(&first_md_page, comment_signature)?;
verify_signature(first_md_page.content(), comment_signature)?;
}
let comment_signature = comment_signature.unwrap_or_default();
@ -211,8 +211,8 @@ fn replace_packet(
loop {
let p = Page::read(data, true)?;
if p.header_type() & 0x01 != 0x01 {
data.seek(SeekFrom::Start(p.start))?;
if p.header().header_type_flag() & 0x01 != 0x01 {
data.seek(SeekFrom::Start(p.header().start))?;
reached_md_end = true;
break;
}