mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2024-11-10 14:44:22 +00:00
musepack: Support SV4-SV6 property reading
This commit is contained in:
parent
d89250eef7
commit
62190b8f9f
6 changed files with 147 additions and 10 deletions
|
@ -491,7 +491,7 @@ mod tests {
|
|||
use crate::ape::{ApeItem, ApeTag};
|
||||
use crate::{ItemValue, Tag, TagExt, TagType};
|
||||
|
||||
use std::io::{Cursor, Seek, SeekFrom};
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn parse_ape() {
|
||||
|
|
|
@ -12,3 +12,6 @@ pub(super) const FREQUENCY_TABLE: [u32; 8] = [44100, 48000, 37800, 32000, 0, 0,
|
|||
// Taken from mpcdec
|
||||
/// This is the gain reference used in old ReplayGain
|
||||
pub const MPC_OLD_GAIN_REF: f32 = 64.82;
|
||||
|
||||
pub(super) const MPC_DECODER_SYNTH_DELAY: u32 = 481;
|
||||
pub(super) const MPC_FRAME_LENGTH: u32 = 36 * 32; // Samples per mpc frame
|
||||
|
|
|
@ -18,6 +18,7 @@ where
|
|||
let mut version = MpcStreamVersion::Sv4to6;
|
||||
let mut file = MpcFile::default();
|
||||
|
||||
#[allow(unstable_name_collisions)]
|
||||
let mut stream_length = reader.stream_len()?;
|
||||
|
||||
// ID3v2 tags are unsupported in MPC files, but still possible
|
||||
|
@ -33,7 +34,7 @@ where
|
|||
size += 10;
|
||||
}
|
||||
|
||||
stream_length -= size as u64;
|
||||
stream_length -= u64::from(size);
|
||||
}
|
||||
|
||||
// Save the current position, so we can go back and read the properties after the tags
|
||||
|
@ -48,7 +49,7 @@ where
|
|||
}
|
||||
|
||||
let ID3FindResults(_, lyrics3v2_size) = find_lyrics3v2(reader)?;
|
||||
stream_length -= lyrics3v2_size as u64;
|
||||
stream_length -= u64::from(lyrics3v2_size);
|
||||
|
||||
reader.seek(SeekFrom::Current(-32))?;
|
||||
|
||||
|
@ -59,7 +60,7 @@ where
|
|||
let pos = reader.stream_position()?;
|
||||
reader.seek(SeekFrom::Start(pos - u64::from(header.size)))?;
|
||||
|
||||
stream_length -= header.size as u64;
|
||||
stream_length -= u64::from(header.size);
|
||||
}
|
||||
|
||||
// Restore the position of the magic signature
|
||||
|
@ -96,6 +97,7 @@ where
|
|||
file.properties = MpcProperties::Sv4to6(MpcSv4to6Properties::read(
|
||||
reader,
|
||||
parse_options.parsing_mode,
|
||||
stream_length,
|
||||
)?)
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,26 +1,34 @@
|
|||
use crate::error::Result;
|
||||
use crate::macros::{decode_err, parse_mode_choice};
|
||||
use crate::musepack::constants::{MPC_DECODER_SYNTH_DELAY, MPC_FRAME_LENGTH};
|
||||
use crate::probe::ParsingMode;
|
||||
use crate::properties::FileProperties;
|
||||
|
||||
use std::io::Read;
|
||||
use std::time::Duration;
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
|
||||
/// MPC stream versions 4-6 audio properties
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct MpcSv4to6Properties {
|
||||
pub(crate) duration: Duration,
|
||||
pub(crate) overall_bitrate: u32,
|
||||
pub(crate) audio_bitrate: u32,
|
||||
pub(crate) channels: u8, // NOTE: always 2
|
||||
pub(crate) sample_rate: u32, // NOTE: always 44100
|
||||
frame_count: u32,
|
||||
|
||||
// Fields actually contained in the header
|
||||
pub(crate) audio_bitrate: u32,
|
||||
pub(crate) mid_side_stereo: bool,
|
||||
pub(crate) stream_version: u16,
|
||||
pub(crate) max_band: u8,
|
||||
pub(crate) frame_count: u32,
|
||||
}
|
||||
|
||||
impl From<MpcSv4to6Properties> for FileProperties {
|
||||
fn from(input: MpcSv4to6Properties) -> Self {
|
||||
Self {
|
||||
duration: input.duration,
|
||||
overall_bitrate: Some(input.overall_bitrate),
|
||||
overall_bitrate: Some(input.audio_bitrate),
|
||||
audio_bitrate: Some(input.audio_bitrate),
|
||||
sample_rate: Some(input.sample_rate),
|
||||
bit_depth: None,
|
||||
|
@ -31,10 +39,114 @@ impl From<MpcSv4to6Properties> for FileProperties {
|
|||
}
|
||||
|
||||
impl MpcSv4to6Properties {
|
||||
pub(crate) fn read<R>(_reader: &mut R, _parse_mode: ParsingMode) -> Result<Self>
|
||||
/// Duration of the audio
|
||||
pub fn duration(&self) -> Duration {
|
||||
self.duration
|
||||
}
|
||||
|
||||
/// Channel count
|
||||
pub fn channels(&self) -> u8 {
|
||||
self.channels
|
||||
}
|
||||
|
||||
/// Sample rate (Hz)
|
||||
pub fn sample_rate(&self) -> u32 {
|
||||
self.sample_rate
|
||||
}
|
||||
|
||||
/// Audio bitrate (kbps)
|
||||
pub fn audio_bitrate(&self) -> u32 {
|
||||
self.audio_bitrate
|
||||
}
|
||||
|
||||
/// Whether MidSideStereo is used
|
||||
pub fn mid_side_stereo(&self) -> bool {
|
||||
self.mid_side_stereo
|
||||
}
|
||||
|
||||
/// The MPC stream version (4-6)
|
||||
pub fn stream_version(&self) -> u16 {
|
||||
self.stream_version
|
||||
}
|
||||
|
||||
/// Last subband used in the whole file
|
||||
pub fn max_band(&self) -> u8 {
|
||||
self.max_band
|
||||
}
|
||||
|
||||
/// Total number of audio frames
|
||||
pub fn frame_count(&self) -> u32 {
|
||||
self.frame_count
|
||||
}
|
||||
|
||||
pub(crate) fn read<R>(
|
||||
reader: &mut R,
|
||||
parse_mode: ParsingMode,
|
||||
stream_length: u64,
|
||||
) -> Result<Self>
|
||||
where
|
||||
R: Read,
|
||||
{
|
||||
todo!()
|
||||
let mut header_data = [0u32; 8];
|
||||
reader.read_u32_into::<LittleEndian>(&mut header_data)?;
|
||||
|
||||
let mut properties = Self::default();
|
||||
|
||||
properties.audio_bitrate = (header_data[0] >> 23) & 0x1FF;
|
||||
let intensity_stereo = (header_data[0] >> 22) & 0x1 == 1;
|
||||
properties.mid_side_stereo = (header_data[0] >> 21) & 0x1 == 1;
|
||||
|
||||
properties.stream_version = ((header_data[0] >> 11) & 0x03FF) as u16;
|
||||
if !(4..=6).contains(&properties.stream_version) {
|
||||
decode_err!(@BAIL Mpc, "Invalid stream version encountered")
|
||||
}
|
||||
|
||||
properties.max_band = ((header_data[0] >> 6) & 0x1F) as u8;
|
||||
let block_size = header_data[0] & 0x3F;
|
||||
|
||||
if properties.stream_version >= 5 {
|
||||
properties.frame_count = header_data[1]; // 32 bit
|
||||
} else {
|
||||
properties.frame_count = header_data[1] >> 16; // 16 bit
|
||||
}
|
||||
|
||||
parse_mode_choice!(
|
||||
parse_mode,
|
||||
STRICT: {
|
||||
if properties.audio_bitrate != 0 {
|
||||
decode_err!(@BAIL Mpc, "Encountered CBR stream")
|
||||
}
|
||||
|
||||
if intensity_stereo {
|
||||
decode_err!(@BAIL Mpc, "Stream uses intensity stereo coding")
|
||||
}
|
||||
|
||||
if block_size != 1 {
|
||||
decode_err!(@BAIL Mpc, "Stream has an invalid block size (must be 1)")
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if properties.stream_version < 6 {
|
||||
// Versions before 6 had an invalid last frame
|
||||
properties.frame_count = properties.frame_count.saturating_sub(1);
|
||||
}
|
||||
|
||||
properties.sample_rate = 44100;
|
||||
properties.channels = 2;
|
||||
|
||||
if properties.frame_count > 0 {
|
||||
let samples =
|
||||
(properties.frame_count * MPC_FRAME_LENGTH).saturating_sub(MPC_DECODER_SYNTH_DELAY);
|
||||
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;
|
||||
}
|
||||
|
||||
Ok(properties)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -127,6 +127,7 @@ mod tests {
|
|||
use crate::iff::wav::{WavFile, WavFormat, WavProperties};
|
||||
use crate::mp4::{AudioObjectType, Mp4Codec, Mp4File, Mp4Properties};
|
||||
use crate::mpeg::{ChannelMode, Emphasis, Layer, MpegFile, MpegProperties, MpegVersion};
|
||||
use crate::musepack::sv4to6::MpcSv4to6Properties;
|
||||
use crate::musepack::sv7::{Link, MpcSv7Properties, Profile};
|
||||
use crate::musepack::sv8::{EncoderInfo, MpcSv8Properties, ReplayGain, StreamHeader};
|
||||
use crate::musepack::{MpcFile, MpcProperties};
|
||||
|
@ -275,6 +276,17 @@ mod tests {
|
|||
channels: 2,
|
||||
};
|
||||
|
||||
const MPC_SV5_PROPERTIES: MpcSv4to6Properties = MpcSv4to6Properties {
|
||||
duration: Duration::from_millis(27),
|
||||
audio_bitrate: 41,
|
||||
channels: 2,
|
||||
frame_count: 1009,
|
||||
mid_side_stereo: true,
|
||||
stream_version: 5,
|
||||
max_band: 31,
|
||||
sample_rate: 44100,
|
||||
};
|
||||
|
||||
const MPC_SV7_PROPERTIES: MpcSv7Properties = MpcSv7Properties {
|
||||
duration: Duration::from_millis(1428),
|
||||
overall_bitrate: 86,
|
||||
|
@ -484,6 +496,14 @@ mod tests {
|
|||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mpc_sv5_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<MpcFile>("tests/files/assets/minimal/mpc_sv5.mpc"),
|
||||
MpcProperties::Sv4to6(MPC_SV5_PROPERTIES)
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mpc_sv7_properties() {
|
||||
assert_eq!(
|
||||
|
|
BIN
tests/files/assets/minimal/mpc_sv5.mpc
Normal file
BIN
tests/files/assets/minimal/mpc_sv5.mpc
Normal file
Binary file not shown.
Loading…
Reference in a new issue