use std::borrow::Cow;
use std::{
io::{self, ErrorKind},
mem,
num::{NonZeroU32, NonZeroU8, TryFromIntError}
};
use log::{info, trace};
use thiserror::Error;
use audio_packet_analyze::AudioPacketAnalyze;
use audio_packet_rewrite::AudioPacketRewrite;
use comment_header_copy::CommentHeaderCopy;
use comment_header_parse::CommentHeaderParse;
use comment_header_parse::VorbisCommentData;
use identification_header_copy::IdentificationHeaderCopy;
use setup_header_parse::SetupHeaderParse;
use setup_header_parse::VorbisSetupData;
use setup_header_rewrite::SetupHeaderRewrite;
use super::codebook::VorbisCodebookError;
use super::PacketType;
use super::{TryPacketTypeFromInt, TryResidueTypeFromInt, TryVectorLookupTypeFromInt};
macro_rules! bitpack_packet_read {
($bitpacker:expr, $method:ident, $packet_length:expr, const $width:expr, $type:ty) => {
$crate::vorbis::optimizer::map_eof_err_to_small_packet_err(
$bitpacker.$method(::vorbis_bitpack::bitpacked_integer_width!($width)),
$packet_length
)
.map(|v| v as $type)
};
($bitpacker:expr, $method:ident, $packet_length:expr, mut $width:expr, $type:ty) => {
$crate::vorbis::optimizer::map_eof_err_to_small_packet_err(
$bitpacker.$method(::vorbis_bitpack::BitpackedIntegerWidth::new($width).unwrap()),
$packet_length
)
.map(|v| v as $type)
};
($bitpacker:expr, $method:ident, $packet_length:expr) => {
$crate::vorbis::optimizer::map_eof_err_to_small_packet_err(
$bitpacker.$method(),
$packet_length
)
};
}
macro_rules! eval_on_eop {
($result:expr, $value:expr) => {
match $result {
Ok(inner) => Ok(inner),
Err(VorbisOptimizerError::TooSmallPacket(_)) => $value,
Err(VorbisOptimizerError::Io(err))
if err.kind() == ::std::io::ErrorKind::UnexpectedEof =>
{
$value
}
Err(VorbisOptimizerError::CodebookError(
$crate::vorbis::codebook::VorbisCodebookError::EofWhileDecodingEntry { .. }
)) => $value,
Err(err) => Err(err)
}
};
}
mod audio_packet_analyze;
mod audio_packet_common;
mod audio_packet_rewrite;
mod comment_header_copy;
mod comment_header_parse;
mod identification_header_copy;
mod setup_header_parse;
mod setup_header_rewrite;
#[derive(Default)]
#[non_exhaustive]
pub struct VorbisOptimizerSettings {
pub vendor_string_action: VorbisVendorStringAction,
pub comment_fields_action: VorbisCommentFieldsAction
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum VorbisOptimizerError {
#[error("Invalid Vorbis I packet type: {0}")]
InvalidPacketType(#[from] TryPacketTypeFromInt),
#[error("Unexpected Vorbis I packet type: got {expected_type}, expected {actual_type}")]
UnexpectedPacketType {
expected_type: PacketType,
actual_type: PacketType
},
#[error("Too small Vorbis I packet of {0} bytes")]
TooSmallPacket(usize),
#[error("Invalid Vorbis I packet signature, sync or framing pattern")]
InvalidPattern,
#[error(
"Unexpected Vorbis I {header_type} length: got {actual_length} bytes, expected {expected_length}"
)]
UnexpectedHeaderPacketLength {
header_type: PacketType,
expected_length: usize,
actual_length: usize
},
#[error("Vorbis codec version {0} is not supported")]
IncompatibleVorbisVersion(u32),
#[error("Invalid number of audio channels: {0}")]
InvalidChannelCount(u8),
#[error("Invalid sampling frequency: {0}")]
InvalidSamplingFrequency(u32),
#[error("Invalid Vorbis I block sizes: {0}, {1}")]
InvalidBlocksizes(u16, u16),
#[error("Too big value for this platform: {0}")]
TooBigInteger(#[from] TryFromIntError),
#[error("Invalid value in setup header")]
InvalidSetupValue,
#[error("A codeword length exceeded the 32 bits limit")]
TooBigCodewordLength,
#[error("Reserved codebook lookup type: {0}")]
ReservedLookupType(#[from] TryVectorLookupTypeFromInt),
#[error(
"Codebook {codebook} has a vector dimension of {dimensions} \
(expected greater than zero and multiple of {expected_dimensions_multiple_of})"
)]
InvalidCodebookDimension {
codebook: u8,
dimensions: u16,
expected_dimensions_multiple_of: u32
},
#[error("Unsupported floor type: {0}")]
UnsupportedFloorType(u16),
#[error("Invalid codebook number referenced: {0}")]
InvalidCodebookNumber(u8),
#[error("The floor {0} setup data has repeated X points")]
RepeatedFloor1Point(u8),
#[error("The floor {0} setup data has more than 65 X points")]
TooManyFloor1Points(u8),
#[error("Reserved residue type: {0}")]
ReservedResidueType(#[from] TryResidueTypeFromInt),
#[error("Reserved mapping type: {0}")]
ReservedMappingType(u16),
#[error(
"The channel mapping with magnitude channel {magnitude_channel} \
and angle channel {angle_channel} is invalid for {audio_channels} audio channel(s)"
)]
InvalidChannelMapping {
magnitude_channel: u8,
angle_channel: u8,
audio_channels: u8
},
#[error(
"Invalid channel multiplexing submap: {mux_submap} \
(must be equal or less than {mapping_submap_count})"
)]
InvalidChannelMultiplexing {
mux_submap: u8,
mapping_submap_count: u8
},
#[error("Referenced invalid floor number: {0}")]
InvalidFloorNumber(u8),
#[error("Referenced invalid residue number: {0}")]
InvalidResidueNumber(u8),
#[error("Referenced invalid mapping number: {0}")]
InvalidMappingNumber(u8),
#[error("Codebook {0} is not suitable for vector lookup, but an audio packet tried to do so")]
ScalarCodebookUsedInVectorContext(u8),
#[error("An audio packet referred to the VQ classbook {0}, which was not defined")]
InvalidVectorQuantizationClassbook(usize),
#[error("An audio packet is encoded with mode {0}, which was not defined")]
InvalidModeNumber(u8),
#[error("Codebook error: {0}")]
CodebookError(#[from] VorbisCodebookError),
#[error("I/O error: {0}")]
Io(#[from] io::Error)
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[non_exhaustive]
pub enum VorbisVendorStringAction {
Copy,
Replace,
AppendTag,
AppendShortTag,
Empty
}
impl Default for VorbisVendorStringAction {
fn default() -> Self {
Self::AppendTag
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[non_exhaustive]
pub enum VorbisCommentFieldsAction {
Copy,
Delete
}
impl Default for VorbisCommentFieldsAction {
fn default() -> Self {
Self::Copy
}
}
enum VorbisOptimizerState {
CommentHeaderParse(CommentHeaderParse),
SetupHeaderParse(SetupHeaderParse),
AudioPacketAnalyze(AudioPacketAnalyze),
IdentificationHeaderCopy(IdentificationHeaderCopy),
CommentHeaderCopy(CommentHeaderCopy),
SetupHeaderRewrite(SetupHeaderRewrite),
AudioPacketRewrite(AudioPacketRewrite)
}
macro_rules! optimizer_state_from_inner_impl {
($($inner_state:ident),+) => {
$(impl From<$inner_state> for VorbisOptimizerState {
fn from(state: $inner_state) -> Self {
Self::$inner_state(state)
}
})+
}
}
optimizer_state_from_inner_impl!(
CommentHeaderParse,
SetupHeaderParse,
AudioPacketAnalyze,
IdentificationHeaderCopy,
CommentHeaderCopy,
SetupHeaderRewrite,
AudioPacketRewrite
);
macro_rules! match_and_delegate {
( $self:ident { $( $variant:ident => $method:ident ( $($arg:expr),* ) ),+ } ) => {
match &mut $self.state {
$(VorbisOptimizerState::$variant(state) => {
state.$method($($arg),*).map(|(return_value, new_state)| {
if let Some(new_state) = new_state {
$self.state = new_state.into();
}
return_value
})
}),+
_ => panic!(
"Unexpected optimizer state. Was the optimizer method called according to its contract?"
)
}
};
}
pub struct VorbisOptimizer<'settings> {
settings: &'settings VorbisOptimizerSettings,
pub(crate) identification_data: VorbisIdentificationHeaderData,
state: VorbisOptimizerState
}
pub(crate) struct VorbisIdentificationHeaderData {
channels: NonZeroU8,
pub(crate) sampling_frequency: NonZeroU32,
pub(crate) maximum_bitrate: i32,
pub(crate) nominal_bitrate: i32,
pub(crate) minimum_bitrate: i32,
blocksizes: (u16, u16)
}
impl<'settings> VorbisOptimizer<'settings> {
pub fn new<B: AsRef<[u8]>>(
settings: &'settings VorbisOptimizerSettings,
identification_header: B
) -> Result<Self, VorbisOptimizerError> {
trace!("Decoding identification header Vorbis packet");
let identification_header = common_header_validation(
identification_header.as_ref(),
PacketType::IdentificationHeader
)?;
const IDENTIFICATION_HEADER_LENGTH: usize = 23 + 7;
let header_length = identification_header.len() + 7;
if header_length < IDENTIFICATION_HEADER_LENGTH {
return Err(VorbisOptimizerError::UnexpectedHeaderPacketLength {
header_type: PacketType::IdentificationHeader,
expected_length: IDENTIFICATION_HEADER_LENGTH,
actual_length: header_length
});
}
let vorbis_version = u32::from_le_bytes(identification_header[..4].try_into().unwrap());
if vorbis_version != 0 {
return Err(VorbisOptimizerError::IncompatibleVorbisVersion(
vorbis_version
));
}
let channels = {
let channels = u8::from_le_bytes(identification_header[4..5].try_into().unwrap());
NonZeroU8::new(channels).ok_or(VorbisOptimizerError::InvalidChannelCount(channels))?
};
let sampling_frequency = {
let sampling_frequency =
u32::from_le_bytes(identification_header[5..9].try_into().unwrap());
NonZeroU32::new(sampling_frequency).ok_or(
VorbisOptimizerError::InvalidSamplingFrequency(sampling_frequency)
)?
};
let maximum_bitrate = i32::from_le_bytes(identification_header[9..13].try_into().unwrap());
let nominal_bitrate = i32::from_le_bytes(identification_header[13..17].try_into().unwrap());
let minimum_bitrate = i32::from_le_bytes(identification_header[17..21].try_into().unwrap());
let blocksizes = (
1u16 << (identification_header[21] & 0x0F),
1u16 << (identification_header[21] >> 4)
);
const fn is_blocksize_in_range(blocksize: u16) -> bool {
blocksize >= 64 && blocksize <= 8192
}
if !is_blocksize_in_range(blocksizes.0)
|| !is_blocksize_in_range(blocksizes.1)
|| blocksizes.0 > blocksizes.1
{
return Err(VorbisOptimizerError::InvalidBlocksizes(
blocksizes.0,
blocksizes.1
));
}
info!(
"Vorbis identification header: {} channel(s), {} Hz sampling frequency, \
minimum, nominal and maximum bitrates: {}, {} and {}, blocksizes {} and {}",
channels,
sampling_frequency,
minimum_bitrate,
nominal_bitrate,
maximum_bitrate,
blocksizes.0,
blocksizes.1
);
Ok(VorbisOptimizer {
settings,
identification_data: VorbisIdentificationHeaderData {
channels,
sampling_frequency,
maximum_bitrate,
nominal_bitrate,
minimum_bitrate,
blocksizes
},
state: CommentHeaderParse.into()
})
}
pub fn analyze_packet<B: AsRef<[u8]>>(
&mut self,
packet: B
) -> Result<Option<u16>, VorbisOptimizerError> {
let packet = packet.as_ref();
match_and_delegate!(self {
CommentHeaderParse => analyze_packet(packet, self.settings),
SetupHeaderParse => analyze_packet(packet, &self.identification_data),
AudioPacketAnalyze => analyze_packet(packet, &self.identification_data)
})
}
#[allow(clippy::type_complexity)]
pub fn optimize_packet<'packet, B: Into<Cow<'packet, [u8]>>>(
&mut self,
packet: B
) -> Result<Option<(Cow<'packet, [u8]>, Option<u16>)>, VorbisOptimizerError> {
match &mut self.state {
VorbisOptimizerState::CommentHeaderParse(_) => {
self.state = IdentificationHeaderCopy {
comment_data: None,
codec_setup: None
}
.into();
}
VorbisOptimizerState::SetupHeaderParse(setup_header_parser) => {
self.state = IdentificationHeaderCopy {
comment_data: Some(mem::take(&mut setup_header_parser.comment_data)),
codec_setup: None
}
.into();
}
VorbisOptimizerState::AudioPacketAnalyze(audio_packet_analyzer) => {
self.state = IdentificationHeaderCopy {
comment_data: Some(mem::take(&mut audio_packet_analyzer.comment_data)),
codec_setup: Some(mem::take(&mut audio_packet_analyzer.codec_setup))
}
.into();
}
_ => ()
};
let packet = packet.into();
match_and_delegate!(self {
IdentificationHeaderCopy => optimize_packet(packet, &self.identification_data),
CommentHeaderCopy => optimize_packet(packet),
SetupHeaderRewrite => optimize_packet(packet),
AudioPacketRewrite => optimize_packet(packet, &self.identification_data)
})
}
}
fn common_header_validation(
header_packet: &[u8],
expected_type: PacketType
) -> Result<&[u8], VorbisOptimizerError> {
trace!("Performing common Vorbis header packet validation");
let header_length = header_packet.len();
if header_length < 7 {
return Err(VorbisOptimizerError::TooSmallPacket(header_length));
}
let packet_type = PacketType::try_from(header_packet[0])?;
if packet_type != expected_type {
return Err(VorbisOptimizerError::UnexpectedPacketType {
expected_type,
actual_type: packet_type
});
}
if &header_packet[1..7] != b"vorbis" {
return Err(VorbisOptimizerError::InvalidPattern);
}
Ok(&header_packet[7..])
}
fn map_eof_err_to_small_packet_err<T>(
result: Result<T, io::Error>,
packet_length: usize
) -> Result<T, VorbisOptimizerError> {
result.map_err(|error| match error.kind() {
ErrorKind::UnexpectedEof => VorbisOptimizerError::TooSmallPacket(packet_length),
_ => error.into()
})
}
const fn ilog(n: i32) -> u8 {
if n > 0 {
32 - n.leading_zeros() as u8
} else {
0
}
}
#[cfg(test)]
mod tests {
use super::ilog;
#[test]
fn ilog_works() {
assert_eq!(ilog(0), 0);
assert_eq!(ilog(1), 1);
assert_eq!(ilog(2), 2);
assert_eq!(ilog(3), 2);
assert_eq!(ilog(4), 3);
assert_eq!(ilog(7), 3);
assert_eq!(ilog(i32::MAX), 31);
assert_eq!(ilog(i32::MIN), 0);
}
}