Musepack: Improve audio properties

This commit is contained in:
Serial 2024-05-09 13:08:40 -04:00 committed by Alex
parent da91e43d02
commit d2d4b81fe0
11 changed files with 222 additions and 147 deletions

View file

@ -54,6 +54,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **MPEG**: Durations estimated by bitrate are more accurate ([PR](https://github.com/Serial-ATA/lofty-rs/pull/395)) - **MPEG**: Durations estimated by bitrate are more accurate ([PR](https://github.com/Serial-ATA/lofty-rs/pull/395))
- **MP4**: Bitrate calculation is now more accurate ([PR](https://github.com/Serial-ATA/lofty-rs/pull/398)) - **MP4**: Bitrate calculation is now more accurate ([PR](https://github.com/Serial-ATA/lofty-rs/pull/398))
- **WAV**: Bitrate calculation is now more accurate ([PR](https://github.com/Serial-ATA/lofty-rs/pull/399)) - **WAV**: Bitrate calculation is now more accurate ([PR](https://github.com/Serial-ATA/lofty-rs/pull/399))
- **MusePack**: Overall improved audio properties ([PR](https://github.com/Serial-ATA/lofty-rs/pull/402))
## [0.19.2] - 2024-04-26 ## [0.19.2] - 2024-04-26

View file

@ -44,6 +44,7 @@ pub struct Id3v2TagFlags {
pub(crate) struct Id3v2Header { pub(crate) struct Id3v2Header {
pub version: Id3v2Version, pub version: Id3v2Version,
pub flags: Id3v2TagFlags, pub flags: Id3v2TagFlags,
/// The size of the tag contents (**DOES NOT INCLUDE THE HEADER/FOOTER**)
pub size: u32, pub size: u32,
pub extended_size: u32, pub extended_size: u32,
} }
@ -140,4 +141,9 @@ impl Id3v2Header {
extended_size, extended_size,
}) })
} }
/// The total size of the tag, including the header, footer, and extended header
pub(crate) fn full_tag_size(&self) -> u32 {
self.size + 10 + self.extended_size + if self.flags.footer { 10 } else { 0 }
}
} }

View file

@ -13,5 +13,5 @@ pub(super) const FREQUENCY_TABLE: [u32; 8] = [44100, 48000, 37800, 32000, 0, 0,
/// This is the gain reference used in old ReplayGain /// This is the gain reference used in old ReplayGain
pub const MPC_OLD_GAIN_REF: f32 = 64.82; pub const MPC_OLD_GAIN_REF: f32 = 64.82;
pub(super) const MPC_DECODER_SYNTH_DELAY: u32 = 481; pub(super) const MPC_DECODER_SYNTH_DELAY: u64 = 481;
pub(super) const MPC_FRAME_LENGTH: u32 = 36 * 32; // Samples per mpc frame pub(super) const MPC_FRAME_LENGTH: u64 = 36 * 32; // Samples per mpc frame

View file

@ -32,12 +32,7 @@ where
let id3v2 = parse_id3v2(reader, header, parse_options.parsing_mode)?; let id3v2 = parse_id3v2(reader, header, parse_options.parsing_mode)?;
file.id3v2_tag = Some(id3v2); file.id3v2_tag = Some(id3v2);
let mut size = header.size; stream_length -= u64::from(header.full_tag_size());
if header.flags.footer {
size += 10;
}
stream_length -= u64::from(size);
} }
// Save the current position, so we can go back and read the properties after the tags // Save the current position, so we can go back and read the properties after the tags

View file

@ -1,8 +1,9 @@
use crate::config::ParsingMode; use crate::config::ParsingMode;
use crate::error::Result; use crate::error::Result;
use crate::macros::{decode_err, parse_mode_choice}; use crate::macros::decode_err;
use crate::musepack::constants::{MPC_DECODER_SYNTH_DELAY, MPC_FRAME_LENGTH}; use crate::musepack::constants::{MPC_DECODER_SYNTH_DELAY, MPC_FRAME_LENGTH};
use crate::properties::FileProperties; use crate::properties::FileProperties;
use crate::util::math::RoundedDivision;
use std::io::Read; use std::io::Read;
use std::time::Duration; use std::time::Duration;
@ -17,7 +18,7 @@ pub struct MpcSv4to6Properties {
pub(crate) sample_rate: u32, // NOTE: always 44100 pub(crate) sample_rate: u32, // NOTE: always 44100
// Fields actually contained in the header // Fields actually contained in the header
pub(crate) audio_bitrate: u32, pub(crate) average_bitrate: u32,
pub(crate) mid_side_stereo: bool, pub(crate) mid_side_stereo: bool,
pub(crate) stream_version: u16, pub(crate) stream_version: u16,
pub(crate) max_band: u8, pub(crate) max_band: u8,
@ -28,8 +29,8 @@ impl From<MpcSv4to6Properties> for FileProperties {
fn from(input: MpcSv4to6Properties) -> Self { fn from(input: MpcSv4to6Properties) -> Self {
Self { Self {
duration: input.duration, duration: input.duration,
overall_bitrate: Some(input.audio_bitrate), overall_bitrate: Some(input.average_bitrate),
audio_bitrate: Some(input.audio_bitrate), audio_bitrate: Some(input.average_bitrate),
sample_rate: Some(input.sample_rate), sample_rate: Some(input.sample_rate),
bit_depth: None, bit_depth: None,
channels: Some(input.channels), channels: Some(input.channels),
@ -54,9 +55,9 @@ impl MpcSv4to6Properties {
self.sample_rate self.sample_rate
} }
/// Audio bitrate (kbps) /// Average bitrate (kbps)
pub fn audio_bitrate(&self) -> u32 { pub fn average_bitrate(&self) -> u32 {
self.audio_bitrate self.average_bitrate
} }
/// Whether MidSideStereo is used /// Whether MidSideStereo is used
@ -92,7 +93,7 @@ impl MpcSv4to6Properties {
let mut properties = Self::default(); let mut properties = Self::default();
properties.audio_bitrate = (header_data[0] >> 23) & 0x1FF; properties.average_bitrate = (header_data[0] >> 23) & 0x1FF;
let intensity_stereo = (header_data[0] >> 22) & 0x1 == 1; let intensity_stereo = (header_data[0] >> 22) & 0x1 == 1;
properties.mid_side_stereo = (header_data[0] >> 21) & 0x1 == 1; properties.mid_side_stereo = (header_data[0] >> 21) & 0x1 == 1;
@ -110,22 +111,19 @@ impl MpcSv4to6Properties {
properties.frame_count = header_data[1] >> 16; // 16 bit properties.frame_count = header_data[1] >> 16; // 16 bit
} }
parse_mode_choice!( if parse_mode == ParsingMode::Strict {
parse_mode, if properties.average_bitrate != 0 {
STRICT: { decode_err!(@BAIL Mpc, "Encountered CBR stream")
if properties.audio_bitrate != 0 { }
decode_err!(@BAIL Mpc, "Encountered CBR stream")
}
if intensity_stereo { if intensity_stereo {
decode_err!(@BAIL Mpc, "Stream uses intensity stereo coding") decode_err!(@BAIL Mpc, "Stream uses intensity stereo coding")
} }
if block_size != 1 { if block_size != 1 {
decode_err!(@BAIL Mpc, "Stream has an invalid block size (must be 1)") decode_err!(@BAIL Mpc, "Stream has an invalid block size (must be 1)")
} }
}, }
);
if properties.stream_version < 6 { if properties.stream_version < 6 {
// Versions before 6 had an invalid last frame // Versions before 6 had an invalid last frame
@ -135,18 +133,28 @@ impl MpcSv4to6Properties {
properties.sample_rate = 44100; properties.sample_rate = 44100;
properties.channels = 2; properties.channels = 2;
if properties.frame_count > 0 { // Nothing more we can do
let samples = if properties.frame_count == 0 {
(properties.frame_count * MPC_FRAME_LENGTH).saturating_sub(MPC_DECODER_SYNTH_DELAY); return Ok(properties);
let length = f64::from(samples) / f64::from(properties.sample_rate);
properties.duration = Duration::from_millis(length.ceil() as u64);
let pcm_frames = 1152 * u64::from(properties.frame_count) - 576;
properties.audio_bitrate = ((stream_length as f64
* 8.0 * f64::from(properties.sample_rate))
/ pcm_frames as f64) as u32;
} }
let samples = (u64::from(properties.frame_count) * MPC_FRAME_LENGTH)
.saturating_sub(MPC_DECODER_SYNTH_DELAY);
let length = (samples * 1000).div_round(u64::from(properties.sample_rate));
properties.duration = Duration::from_millis(length);
// 576 is a magic number from the reference decoder
//
// Quote from the reference source (libmpcdec/trunk/src/streaminfo.c:248 @rev 153):
// "estimation, exact value needs too much time"
let pcm_frames = (MPC_FRAME_LENGTH * u64::from(properties.frame_count)).saturating_sub(576);
// Is this accurate? If not, it really doesn't matter.
properties.average_bitrate = ((stream_length as f64
* 8.0 * f64::from(properties.sample_rate))
/ (pcm_frames as f64)
/ (MPC_FRAME_LENGTH as f64)) as u32;
Ok(properties) Ok(properties)
} }
} }

View file

@ -1,12 +1,14 @@
use crate::error::Result; use crate::error::Result;
use crate::macros::decode_err; use crate::macros::decode_err;
use crate::musepack::constants::{FREQUENCY_TABLE, MPC_OLD_GAIN_REF}; use crate::musepack::constants::{
FREQUENCY_TABLE, MPC_DECODER_SYNTH_DELAY, MPC_FRAME_LENGTH, MPC_OLD_GAIN_REF,
};
use crate::properties::FileProperties; use crate::properties::FileProperties;
use std::io::Read; use std::io::Read;
use std::time::Duration; use std::time::Duration;
use byteorder::{BigEndian, LittleEndian, ReadBytesExt}; use byteorder::{LittleEndian, ReadBytesExt};
/// Used profile /// Used profile
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
@ -104,8 +106,7 @@ impl Link {
#[allow(clippy::struct_excessive_bools)] #[allow(clippy::struct_excessive_bools)]
pub struct MpcSv7Properties { pub struct MpcSv7Properties {
pub(crate) duration: Duration, pub(crate) duration: Duration,
pub(crate) overall_bitrate: u32, pub(crate) average_bitrate: u32,
pub(crate) audio_bitrate: u32,
pub(crate) channels: u8, // NOTE: always 2 pub(crate) channels: u8, // NOTE: always 2
// -- Section 1 -- // -- Section 1 --
pub(crate) frame_count: u32, pub(crate) frame_count: u32,
@ -135,8 +136,8 @@ impl From<MpcSv7Properties> for FileProperties {
fn from(input: MpcSv7Properties) -> Self { fn from(input: MpcSv7Properties) -> Self {
Self { Self {
duration: input.duration, duration: input.duration,
overall_bitrate: Some(input.overall_bitrate), overall_bitrate: Some(input.average_bitrate),
audio_bitrate: Some(input.audio_bitrate), audio_bitrate: Some(input.average_bitrate),
sample_rate: Some(input.sample_freq), sample_rate: Some(input.sample_freq),
bit_depth: None, bit_depth: None,
channels: Some(input.channels), channels: Some(input.channels),
@ -151,14 +152,9 @@ impl MpcSv7Properties {
self.duration self.duration
} }
/// Overall bitrate (kbps) /// Average bitrate (kbps)
pub fn overall_bitrate(&self) -> u32 { pub fn average_bitrate(&self) -> u32 {
self.overall_bitrate self.average_bitrate
}
/// Audio bitrate (kbps)
pub fn audio_bitrate(&self) -> u32 {
self.audio_bitrate
} }
/// Sample rate (Hz) /// Sample rate (Hz)
@ -208,7 +204,7 @@ impl MpcSv7Properties {
/// Change in the replay level /// Change in the replay level
/// ///
/// The value is a signed 16 bit integer, with the level being attenuated by that many mB /// The value is a signed 16-bit integer, with the level being attenuated by that many mB
pub fn title_gain(&self) -> i16 { pub fn title_gain(&self) -> i16 {
self.title_gain self.title_gain
} }
@ -224,7 +220,7 @@ impl MpcSv7Properties {
/// Change in the replay level if the whole CD is supposed to be played with the same level change /// Change in the replay level if the whole CD is supposed to be played with the same level change
/// ///
/// The value is a signed 16 bit integer, with the level being attenuated by that many mB /// The value is a signed 16-bit integer, with the level being attenuated by that many mB
pub fn album_gain(&self) -> i16 { pub fn album_gain(&self) -> i16 {
self.album_gain self.album_gain
} }
@ -280,6 +276,9 @@ impl MpcSv7Properties {
..Self::default() ..Self::default()
}; };
// TODO: Make a Bitreader, would be nice crate-wide but especially here
// The SV7 header is split into 6 32-bit sections
// -- Section 1 -- // -- Section 1 --
properties.frame_count = reader.read_u32::<LittleEndian>()?; properties.frame_count = reader.read_u32::<LittleEndian>()?;
@ -294,11 +293,8 @@ impl MpcSv7Properties {
let byte2 = ((chunk & 0xFF_0000) >> 16) as u8; let byte2 = ((chunk & 0xFF_0000) >> 16) as u8;
let profile_index = (byte2 & 0xF0) >> 4; properties.profile = Profile::from_u8((byte2 & 0xF0) >> 4).unwrap(); // Infallible
properties.profile = Profile::from_u8(profile_index).unwrap(); // Infallible properties.link = Link::from_u8((byte2 & 0x0C) >> 2).unwrap(); // Infallible
let link_index = (byte2 & 0x0C) >> 2;
properties.link = Link::from_u8(link_index).unwrap(); // Infallible
let sample_freq_index = byte2 & 0x03; let sample_freq_index = byte2 & 0x03;
properties.sample_freq = FREQUENCY_TABLE[sample_freq_index as usize]; properties.sample_freq = FREQUENCY_TABLE[sample_freq_index as usize];
@ -307,27 +303,24 @@ impl MpcSv7Properties {
properties.max_level = remaining_bytes; properties.max_level = remaining_bytes;
// -- Section 3 -- // -- Section 3 --
let title_gain = reader.read_i16::<BigEndian>()?; let title_peak = reader.read_u16::<LittleEndian>()?;
let title_peak = reader.read_u16::<BigEndian>()?; let title_gain = reader.read_u16::<LittleEndian>()?;
// -- Section 4 -- // -- Section 4 --
let album_gain = reader.read_i16::<BigEndian>()?; let album_peak = reader.read_u16::<LittleEndian>()?;
let album_peak = reader.read_u16::<BigEndian>()?; let album_gain = reader.read_u16::<LittleEndian>()?;
// -- Section 5 -- // -- Section 5 --
let chunk = reader.read_u32::<LittleEndian>()?; let chunk = reader.read_u32::<LittleEndian>()?;
let byte1 = ((chunk & 0xFF00_0000) >> 24) as u8; properties.true_gapless = (chunk >> 31) == 1;
properties.true_gapless = ((byte1 & 0x80) >> 7) == 1;
let byte2 = ((chunk & 0xFF_0000) >> 16) as u8;
if properties.true_gapless { if properties.true_gapless {
properties.last_frame_length = properties.last_frame_length = ((chunk >> 20) & 0x7FF) as u16;
(u16::from(byte1 & 0x7F) << 4) | u16::from((byte2 & 0xF0) >> 4);
} }
properties.fast_seeking_safe = (chunk >> 19) & 1 == 1;
// NOTE: Rest of the chunk is zeroed and unused // NOTE: Rest of the chunk is zeroed and unused
// -- Section 6 -- // -- Section 6 --
@ -336,19 +329,23 @@ impl MpcSv7Properties {
// -- End of parsing -- // -- End of parsing --
// Convert ReplayGain values // Convert ReplayGain values
let set_replay_gain = |gain: i16| -> i16 { let set_replay_gain = |gain: u16| -> i16 {
let mut gain = (MPC_OLD_GAIN_REF - f32::from(gain) / 100.0) * 256.0 + 0.5; if gain == 0 {
if gain >= ((1 << 16) as f32) || gain < 0.0 { return 0;
gain = 0.0
} }
gain as i16
let gain = ((MPC_OLD_GAIN_REF - f32::from(gain) / 100.0) * 256.0 + 0.5) as i16;
if !(0..i16::MAX).contains(&gain) {
return 0;
}
gain
}; };
let set_replay_peak = |peak: u16| -> u16 { let set_replay_peak = |peak: u16| -> u16 {
if peak == 0 { if peak == 0 {
return 0; return 0;
} }
((peak.ilog10() * 20 * 256) as f32 + 0.5) as u16 ((f64::from(peak).log10() * 20.0 * 256.0) + 0.5) as u16
}; };
properties.title_gain = set_replay_gain(title_gain); properties.title_gain = set_replay_gain(title_gain);
@ -356,22 +353,36 @@ impl MpcSv7Properties {
properties.album_gain = set_replay_gain(album_gain); properties.album_gain = set_replay_gain(album_gain);
properties.album_peak = set_replay_peak(album_peak); properties.album_peak = set_replay_peak(album_peak);
let total_samples; if properties.last_frame_length > MPC_FRAME_LENGTH as u16 {
if properties.true_gapless { decode_err!(@BAIL Mpc, "Invalid last frame length");
total_samples =
(properties.frame_count * 1152) - u32::from(properties.last_frame_length);
} else {
total_samples = (properties.frame_count * 1152) - 576;
} }
if total_samples > 0 && properties.sample_freq > 0 { if properties.sample_freq == 0 {
let length = log::warn!("Sample rate is 0, unable to calculate duration and bitrate");
(f64::from(total_samples) * 1000.0 / f64::from(properties.sample_freq)).ceil(); return Ok(properties);
properties.duration = Duration::from_millis(length as u64);
properties.audio_bitrate = (stream_length * 8 / length as u64) as u32;
properties.overall_bitrate = properties.audio_bitrate;
} }
if properties.frame_count == 0 {
log::warn!("Frame count is 0, unable to calculate duration and bitrate");
return Ok(properties);
}
let time_per_frame = (MPC_FRAME_LENGTH as f64) / f64::from(properties.sample_freq);
let length = (f64::from(properties.frame_count) * time_per_frame) * 1000.0;
properties.duration = Duration::from_millis(length as u64);
let total_samples;
if properties.true_gapless {
total_samples = (u64::from(properties.frame_count) * MPC_FRAME_LENGTH)
- (MPC_FRAME_LENGTH - u64::from(properties.last_frame_length));
} else {
total_samples =
(u64::from(properties.frame_count) * MPC_FRAME_LENGTH) - MPC_DECODER_SYNTH_DELAY;
}
properties.average_bitrate = ((stream_length * 8 * u64::from(properties.sample_freq))
/ (total_samples * 1000)) as u32;
Ok(properties) Ok(properties)
} }
} }

View file

@ -1,8 +1,10 @@
use super::read::PacketReader; use super::read::PacketReader;
use crate::config::ParsingMode; use crate::config::ParsingMode;
use crate::error::Result; use crate::error::Result;
use crate::macros::decode_err;
use crate::musepack::constants::FREQUENCY_TABLE; use crate::musepack::constants::FREQUENCY_TABLE;
use crate::properties::FileProperties; use crate::properties::FileProperties;
use crate::util::math::RoundedDivision;
use std::io::Read; use std::io::Read;
use std::time::Duration; use std::time::Duration;
@ -13,8 +15,7 @@ use byteorder::{BigEndian, ReadBytesExt};
#[derive(Debug, Clone, PartialEq, Default)] #[derive(Debug, Clone, PartialEq, Default)]
pub struct MpcSv8Properties { pub struct MpcSv8Properties {
pub(crate) duration: Duration, pub(crate) duration: Duration,
pub(crate) overall_bitrate: u32, pub(crate) average_bitrate: u32,
pub(crate) audio_bitrate: u32,
/// Mandatory Stream Header packet /// Mandatory Stream Header packet
pub stream_header: StreamHeader, pub stream_header: StreamHeader,
/// Mandatory ReplayGain packet /// Mandatory ReplayGain packet
@ -27,8 +28,8 @@ impl From<MpcSv8Properties> for FileProperties {
fn from(input: MpcSv8Properties) -> Self { fn from(input: MpcSv8Properties) -> Self {
Self { Self {
duration: input.duration, duration: input.duration,
overall_bitrate: Some(input.overall_bitrate), overall_bitrate: Some(input.average_bitrate),
audio_bitrate: Some(input.audio_bitrate), audio_bitrate: Some(input.average_bitrate),
sample_rate: Some(input.stream_header.sample_rate), sample_rate: Some(input.stream_header.sample_rate),
bit_depth: None, bit_depth: None,
channels: Some(input.stream_header.channels), channels: Some(input.stream_header.channels),
@ -43,14 +44,9 @@ impl MpcSv8Properties {
self.duration self.duration
} }
/// Overall bitrate (kbps) /// Average bitrate (kbps)
pub fn overall_bitrate(&self) -> u32 { pub fn average_bitrate(&self) -> u32 {
self.overall_bitrate self.average_bitrate
}
/// Audio bitrate (kbps)
pub fn audio_bitrate(&self) -> u32 {
self.audio_bitrate
} }
/// Sample rate (Hz) /// Sample rate (Hz)
@ -248,3 +244,45 @@ impl EncoderInfo {
}) })
} }
} }
pub(super) fn read(
stream_length: u64,
stream_header: StreamHeader,
replay_gain: ReplayGain,
encoder_info: Option<EncoderInfo>,
) -> Result<MpcSv8Properties> {
let mut properties = MpcSv8Properties {
duration: Duration::ZERO,
average_bitrate: 0,
stream_header,
replay_gain,
encoder_info,
};
let sample_count = stream_header.sample_count;
let beginning_silence = stream_header.beginning_silence;
let sample_rate = stream_header.sample_rate;
if beginning_silence > sample_count {
decode_err!(@BAIL Mpc, "Beginning silence is greater than the total sample count");
}
if sample_rate == 0 {
log::warn!("Sample rate is 0, unable to calculate duration and bitrate");
return Ok(properties);
}
if sample_count == 0 {
log::warn!("Sample count is 0, unable to calculate duration and bitrate");
return Ok(properties);
}
let total_samples = sample_count - beginning_silence;
let length = (total_samples * 1000).div_round(u64::from(sample_rate));
properties.duration = Duration::from_millis(length);
properties.average_bitrate =
((stream_length * 8 * u64::from(sample_rate)) / (total_samples * 1000)) as u32;
Ok(properties)
}

View file

@ -4,7 +4,6 @@ use crate::error::{ErrorKind, LoftyError, Result};
use crate::macros::{decode_err, parse_mode_choice}; use crate::macros::{decode_err, parse_mode_choice};
use std::io::Read; use std::io::Read;
use std::time::Duration;
use byteorder::ReadBytesExt; use byteorder::ReadBytesExt;
@ -12,6 +11,7 @@ use byteorder::ReadBytesExt;
const STREAM_HEADER_KEY: [u8; 2] = *b"SH"; const STREAM_HEADER_KEY: [u8; 2] = *b"SH";
const REPLAYGAIN_KEY: [u8; 2] = *b"RG"; const REPLAYGAIN_KEY: [u8; 2] = *b"RG";
const ENCODER_INFO_KEY: [u8; 2] = *b"EI"; const ENCODER_INFO_KEY: [u8; 2] = *b"EI";
#[allow(dead_code)]
const AUDIO_PACKET_KEY: [u8; 2] = *b"AP"; const AUDIO_PACKET_KEY: [u8; 2] = *b"AP";
const STREAM_END_KEY: [u8; 2] = *b"SE"; const STREAM_END_KEY: [u8; 2] = *b"SE";
@ -29,13 +29,12 @@ where
let mut found_stream_end = false; let mut found_stream_end = false;
while let Ok((packet_id, packet_length)) = packet_reader.next() { while let Ok((packet_id, packet_length)) = packet_reader.next() {
stream_length += packet_length;
match packet_id { match packet_id {
STREAM_HEADER_KEY => stream_header = Some(StreamHeader::read(&mut packet_reader)?), STREAM_HEADER_KEY => stream_header = Some(StreamHeader::read(&mut packet_reader)?),
REPLAYGAIN_KEY => replay_gain = Some(ReplayGain::read(&mut packet_reader)?), REPLAYGAIN_KEY => replay_gain = Some(ReplayGain::read(&mut packet_reader)?),
ENCODER_INFO_KEY => encoder_info = Some(EncoderInfo::read(&mut packet_reader)?), ENCODER_INFO_KEY => encoder_info = Some(EncoderInfo::read(&mut packet_reader)?),
AUDIO_PACKET_KEY => {
stream_length += packet_length;
},
STREAM_END_KEY => { STREAM_END_KEY => {
found_stream_end = true; found_stream_end = true;
break; break;
@ -68,41 +67,16 @@ where
}, },
}; };
if stream_length == 0 { if stream_length == 0 && parse_mode == ParsingMode::Strict {
parse_mode_choice!( decode_err!(@BAIL Mpc, "File is missing an Audio packet");
parse_mode,
STRICT: decode_err!(@BAIL Mpc, "File is missing an Audio packet"),
)
} }
if !found_stream_end { if !found_stream_end && parse_mode == ParsingMode::Strict {
parse_mode_choice!( decode_err!(@BAIL Mpc, "File is missing a Stream End packet");
parse_mode,
STRICT: decode_err!(@BAIL Mpc, "File is missing a Stream End packet"),
)
} }
let mut properties = MpcSv8Properties { let properties =
duration: Duration::ZERO, super::properties::read(stream_length, stream_header, replay_gain, encoder_info)?;
overall_bitrate: 0,
audio_bitrate: 0,
stream_header,
replay_gain,
encoder_info,
};
let sample_count = stream_header.sample_count;
let beginning_silence = stream_header.beginning_silence;
let sample_rate = stream_header.sample_rate;
if sample_count > 0 && beginning_silence <= sample_count && sample_rate > 0 {
let total_samples = sample_count - beginning_silence;
let length = (total_samples as f64 * 1000.0) / f64::from(sample_rate);
properties.duration = Duration::from_millis(length as u64);
properties.audio_bitrate = ((stream_length * 8) / length as u64) as u32;
properties.overall_bitrate = properties.audio_bitrate;
}
Ok(properties) Ok(properties)
} }

View file

@ -160,9 +160,10 @@ const MP4_FLAC_PROPERTIES: Mp4Properties = Mp4Properties {
drm_protected: false, drm_protected: false,
}; };
// Properties verified with libmpcdec 1.2.2
const MPC_SV5_PROPERTIES: MpcSv4to6Properties = MpcSv4to6Properties { const MPC_SV5_PROPERTIES: MpcSv4to6Properties = MpcSv4to6Properties {
duration: Duration::from_millis(27), duration: Duration::from_millis(26347),
audio_bitrate: 41, average_bitrate: 119,
channels: 2, channels: 2,
frame_count: 1009, frame_count: 1009,
mid_side_stereo: true, mid_side_stereo: true,
@ -172,9 +173,8 @@ const MPC_SV5_PROPERTIES: MpcSv4to6Properties = MpcSv4to6Properties {
}; };
const MPC_SV7_PROPERTIES: MpcSv7Properties = MpcSv7Properties { const MPC_SV7_PROPERTIES: MpcSv7Properties = MpcSv7Properties {
duration: Duration::from_millis(1428), duration: Duration::from_millis(1440),
overall_bitrate: 86, average_bitrate: 86,
audio_bitrate: 86,
channels: 2, channels: 2,
frame_count: 60, frame_count: 60,
intensity_stereo: false, intensity_stereo: false,
@ -184,9 +184,9 @@ const MPC_SV7_PROPERTIES: MpcSv7Properties = MpcSv7Properties {
link: Link::VeryLowStartOrEnd, link: Link::VeryLowStartOrEnd,
sample_freq: 48000, sample_freq: 48000,
max_level: 0, max_level: 0,
title_gain: 16594, title_gain: 0,
title_peak: 0, title_peak: 0,
album_gain: 16594, album_gain: 0,
album_peak: 0, album_peak: 0,
true_gapless: true, true_gapless: true,
last_frame_length: 578, last_frame_length: 578,
@ -196,8 +196,7 @@ const MPC_SV7_PROPERTIES: MpcSv7Properties = MpcSv7Properties {
const MPC_SV8_PROPERTIES: MpcSv8Properties = MpcSv8Properties { const MPC_SV8_PROPERTIES: MpcSv8Properties = MpcSv8Properties {
duration: Duration::from_millis(1428), duration: Duration::from_millis(1428),
overall_bitrate: 82, average_bitrate: 82,
audio_bitrate: 82,
stream_header: StreamHeader { stream_header: StreamHeader {
crc: 4_252_559_415, crc: 4_252_559_415,
stream_version: 8, stream_version: 8,

View file

@ -1,3 +1,8 @@
/// Perform a rounded division.
///
/// This is implemented for all unsigned integers.
///
/// NOTE: If the result is less than 1, it will be rounded up to 1.
pub(crate) trait RoundedDivision<Rhs = Self> { pub(crate) trait RoundedDivision<Rhs = Self> {
type Output; type Output;
@ -18,4 +23,42 @@ macro_rules! unsigned_rounded_division {
}; };
} }
unsigned_rounded_division!(u8, u16, u32, u64, u128, usize); unsigned_rounded_division!(u8, u16, u32, u64, usize);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_div_round() {
#[derive(Debug)]
struct TestEntry {
lhs: u32,
rhs: u32,
result: u32,
}
#[rustfmt::skip]
let tests = [
TestEntry { lhs: 1, rhs: 1, result: 1 },
TestEntry { lhs: 1, rhs: 2, result: 1 },
TestEntry { lhs: 2, rhs: 2, result: 1 },
TestEntry { lhs: 3, rhs: 2, result: 2 },
TestEntry { lhs: 4, rhs: 2, result: 2 },
TestEntry { lhs: 5, rhs: 2, result: 3 },
// Should be rounded up to 1
TestEntry { lhs: 800, rhs: 1500, result: 1 },
TestEntry { lhs: 1500, rhs: 3000, result: 1 },
// Shouldn't be rounded
TestEntry { lhs: 0, rhs: 4000, result: 0 },
TestEntry { lhs: 1500, rhs: 4000, result: 0 },
];
for test in &tests {
let result = test.lhs.div_round(test.rhs);
assert_eq!(result, test.result, "{}.div_round({})", test.lhs, test.rhs);
}
}
}