From 715b53059b73d762348b8c1f54d9aa5f65f5262f Mon Sep 17 00:00:00 2001 From: jakka Date: Wed, 25 Jun 2025 00:54:46 +0300 Subject: started working on my own libflac decoder --- Cargo.lock | 1 + Cargo.toml | 1 + src/flac.rs | 281 ----------------------------------------------- src/flac/decoder.rs | 139 +++++++++++++++++++++++ src/flac/mod.rs | 311 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 452 insertions(+), 281 deletions(-) delete mode 100644 src/flac.rs create mode 100644 src/flac/decoder.rs create mode 100644 src/flac/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 1e2b0f3..b996bd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -595,6 +595,7 @@ dependencies = [ "futures-util", "i24", "indicatif", + "libflac-sys", "libsql", "md-5", "metaflac", diff --git a/Cargo.toml b/Cargo.toml index 44cac70..fa7e823 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,3 +40,4 @@ symphonia = { git = "https://github.com/sscobici/Symphonia.git", rev = "2213f274 "flac", ] } console = { version = "0.15.11", features = ["windows-console-colors"] } +libflac-sys = { version = "0.3.3", default-features = false, features = ["build-flac"] } diff --git a/src/flac.rs b/src/flac.rs deleted file mode 100644 index 05d915f..0000000 --- a/src/flac.rs +++ /dev/null @@ -1,281 +0,0 @@ -use anyhow::{Result, anyhow}; -use flac_bound::{FlacEncoder, WriteWrapper}; -use md5::{Digest, Md5}; -use metaflac::{Block, Tag}; -use std::{ - fs::File, - path::{Path, PathBuf}, -}; -use symphonia::core::{ - audio::{Audio, GenericAudioBufferRef}, - codecs::audio::AudioDecoder, - formats::{FormatOptions, FormatReader, TrackType, probe::Hint}, - io::MediaSourceStream, - meta::MetadataOptions, -}; - -pub const CURRENT_VENDOR: &str = "reference libFLAC 1.5.0 20250211"; - -type BoxedFormatReader = Box; -type BoxedAudioDecoder = Box; - -struct StreamConfig { - channels: u32, - bits_per_sample: Bps, - sample_rate: u32, -} - -enum Bps { - _16, - _24, - _32, -} - -impl Bps { - fn new(num: u32) -> Result { - match num { - 16 => Ok(Bps::_16), - 24 => Ok(Bps::_24), - 32 => Ok(Bps::_32), - _ => Err(anyhow!("Invalid BPS")), - } - } - - fn value(&self) -> u32 { - match self { - Bps::_16 => 16, - Bps::_24 => 24, - Bps::_32 => 32, - } - } -} - -struct FileEncoder { - filename: PathBuf, - streamdata: StreamConfig, - format: BoxedFormatReader, - decoder: BoxedAudioDecoder, -} - -impl FileEncoder { - fn new(file: impl AsRef) -> Result { - let (format, decoder, config) = init_decoder(&file)?; - Ok(FileEncoder { - filename: file.as_ref().to_path_buf(), - streamdata: config, - format, - decoder, - }) - } - - fn temp_name(&self) -> PathBuf { - self.filename.clone().with_extension("tmp") - } - - fn encode(&mut self, mut encoder: FlacEncoder) -> Result> { - let mut buffer: Vec = Vec::new(); - let mut hasher = Md5::new(); - let track_id = self.format.default_track(TrackType::Audio).unwrap().id; - let offset = self.streamdata.bits_per_sample.value(); - - loop { - let packet = match self.format.next_packet() { - Ok(Some(packet)) => packet, - Ok(None) => break, - Err(error) => return Err(error.into()), - }; - - if packet.track_id() != track_id { - continue; - } - - if let GenericAudioBufferRef::S32(buf) = self.decoder.decode(&packet)? { - for sample in buf.iter_interleaved() { - let real_sample = sample >> (32 - offset); - hasher.update(real_sample.to_le_bytes()); - buffer.push(real_sample); - } - encoder - .process_interleaved(&buffer, buf.samples_planar() as u32) - .unwrap(); - buffer.clear(); - } else { - return Err(anyhow!("unsupported codec")); - } - } - - if let Err(enc) = encoder.finish() { - return Err(anyhow!("Encoding failed:\t{:?}", enc.state())); - } - - Ok(hasher.finalize().to_vec()) - } - - fn write_tags(&self, hash: Vec) -> Result<()> { - let tags = Tag::read_from_path(&self.filename)?; - let mut output = Tag::read_from_path(self.temp_name())?; - - let mut streaminfo = tags.get_streaminfo().unwrap().clone(); - - streaminfo.md5 = hash; - output.set_streaminfo(streaminfo); - - for block in tags.blocks() { - match block { - Block::VorbisComment(comment) => { - for (key, val) in comment.comments.clone() { - if key != "ENCODER" { - output.set_vorbis(key, val); - } - } - } - Block::StreamInfo(_) | Block::Padding(_) => {} - _ => output.push_block(block.clone()), - } - } - - output.write_to_path(self.temp_name())?; - Ok(()) - } -} - -fn init_decoder( - filename: impl AsRef, -) -> Result<(BoxedFormatReader, BoxedAudioDecoder, StreamConfig)> { - let src = std::fs::File::open(filename)?; - let mss = MediaSourceStream::new(Box::new(src), Default::default()); - let mut hint = Hint::new(); - hint.with_extension("flac"); - - let format_opts: FormatOptions = Default::default(); - let metadata_opts: MetadataOptions = Default::default(); - - let format = symphonia::default::get_probe() - .probe(&hint, mss, format_opts, metadata_opts) - .unwrap(); - - let track = format.default_track(TrackType::Audio).unwrap(); - - let decoder = symphonia::default::get_codecs() - .make_audio_decoder( - track.codec_params.as_ref().unwrap().audio().unwrap(), - &Default::default(), - ) - .unwrap(); - - let params = track.codec_params.as_ref().unwrap().audio().unwrap(); - - let config = StreamConfig { - channels: u32::try_from(params.channels.as_ref().unwrap().count()).unwrap(), - bits_per_sample: Bps::new(params.bits_per_sample.unwrap())?, - sample_rate: params.sample_rate.unwrap(), - }; - - Ok((format, decoder, config)) -} - -pub fn encode_file(filename: impl AsRef) -> Result<()> { - let mut filencoder = FileEncoder::new(filename)?; - let temp_name = filencoder.temp_name(); - - if temp_name.exists() { - std::fs::remove_file(&temp_name)?; - } - - let mut outf = File::create(temp_name)?; - let mut outw = WriteWrapper(&mut outf); - let enc = FlacEncoder::new() - .unwrap() - .channels(filencoder.streamdata.channels) - .bits_per_sample(filencoder.streamdata.bits_per_sample.value()) - .sample_rate(filencoder.streamdata.sample_rate) - .compression_level(8) - .verify(false) - .init_write(&mut outw) - .unwrap(); - - let hash = filencoder.encode(enc)?; - filencoder.write_tags(hash)?; - std::fs::rename(filencoder.temp_name(), filencoder.filename)?; - Ok(()) -} - -pub fn get_vendor(file: impl AsRef) -> Result { - if let Some(vorbis) = Tag::read_from_path(file)?.vorbis_comments() { - Ok(vorbis.vendor_string.to_owned()) - } else { - Err(anyhow!("Vendor string not found")) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use metaflac::Tag; - - #[test] - fn bit16() { - let name = "16bit.flac"; - let tempname = "16bit.flac.temp"; - std::fs::copy(name, tempname).unwrap(); - encode_file(name).unwrap(); - let target_md5 = Tag::read_from_path(tempname) - .unwrap() - .get_streaminfo() - .unwrap() - .md5 - .clone(); - let encoded_md5 = Tag::read_from_path(name) - .unwrap() - .get_streaminfo() - .unwrap() - .md5 - .clone(); - std::fs::remove_file(tempname).unwrap(); - assert_eq!(target_md5, encoded_md5); - } - - #[test] - fn bit24() { - let name = "24bit.flac"; - let tempname = "24bit.flac.temp"; - std::fs::copy(name, tempname).unwrap(); - encode_file(name).unwrap(); - let target_md5 = Tag::read_from_path(tempname) - .unwrap() - .get_streaminfo() - .unwrap() - .md5 - .clone(); - let encoded_md5 = Tag::read_from_path(name) - .unwrap() - .get_streaminfo() - .unwrap() - .md5 - .clone(); - std::fs::remove_file(tempname).unwrap(); - assert_eq!(target_md5, encoded_md5); - } - - #[test] - fn bit32() { - let name = "32bit.flac"; - let tempname = "32bit.flac.temp"; - std::fs::copy(name, tempname).unwrap(); - encode_file(name).unwrap(); - let target_md5 = Tag::read_from_path(tempname) - .unwrap() - .get_streaminfo() - .unwrap() - .md5 - .clone(); - let encoded_md5 = Tag::read_from_path(name) - .unwrap() - .get_streaminfo() - .unwrap() - .md5 - .clone(); - std::fs::remove_file(tempname).unwrap(); - assert_eq!(target_md5, encoded_md5); - } -} diff --git a/src/flac/decoder.rs b/src/flac/decoder.rs new file mode 100644 index 0000000..99ac36e --- /dev/null +++ b/src/flac/decoder.rs @@ -0,0 +1,139 @@ +use std::{ + error::Error, + ffi::{CString, c_void}, + fmt::Display, + path::Path, +}; + +use libflac_sys::{ + FLAC__STREAM_DECODER_INIT_STATUS_ALREADY_INITIALIZED, + FLAC__STREAM_DECODER_INIT_STATUS_ERROR_OPENING_FILE, + FLAC__STREAM_DECODER_INIT_STATUS_INVALID_CALLBACKS, + FLAC__STREAM_DECODER_INIT_STATUS_MEMORY_ALLOCATION_ERROR, FLAC__STREAM_DECODER_INIT_STATUS_OK, + FLAC__STREAM_DECODER_INIT_STATUS_UNSUPPORTED_CONTAINER, FLAC__StreamDecoder, + FLAC__StreamDecoderInitStatus, FLAC__bool, FLAC__stream_decoder_finish, + FLAC__stream_decoder_get_bits_per_sample, FLAC__stream_decoder_get_channels, + FLAC__stream_decoder_get_sample_rate, FLAC__stream_decoder_init_file, FLAC__stream_decoder_new, + FLAC__stream_decoder_process_single, FLAC__stream_decoder_set_md5_checking, +}; + +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[repr(u32)] +pub enum FlacDecoderInitError { + AlreadyInitialized = FLAC__STREAM_DECODER_INIT_STATUS_ALREADY_INITIALIZED, + + ErrorOpeningFile = FLAC__STREAM_DECODER_INIT_STATUS_ERROR_OPENING_FILE, + + InvalidCallbacks = FLAC__STREAM_DECODER_INIT_STATUS_INVALID_CALLBACKS, + + MemoryAllocationError = FLAC__STREAM_DECODER_INIT_STATUS_MEMORY_ALLOCATION_ERROR, + + UnsupportedContainer = FLAC__STREAM_DECODER_INIT_STATUS_UNSUPPORTED_CONTAINER, +} + +impl Error for FlacDecoderInitError {} + +impl Display for FlacDecoderInitError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } +} + +impl From for FLAC__StreamDecoderInitStatus { + fn from(val: FlacDecoderInitError) -> Self { + val as FLAC__StreamDecoderInitStatus + } +} + +impl TryFrom for FlacDecoderInitError { + type Error = (); + + #[allow(non_upper_case_globals)] + fn try_from(raw: FLAC__StreamDecoderInitStatus) -> Result { + Ok(match raw { + FLAC__STREAM_DECODER_INIT_STATUS_ALREADY_INITIALIZED => { + FlacDecoderInitError::AlreadyInitialized + } + FLAC__STREAM_DECODER_INIT_STATUS_ERROR_OPENING_FILE => { + FlacDecoderInitError::ErrorOpeningFile + } + FLAC__STREAM_DECODER_INIT_STATUS_INVALID_CALLBACKS => { + FlacDecoderInitError::InvalidCallbacks + } + FLAC__STREAM_DECODER_INIT_STATUS_MEMORY_ALLOCATION_ERROR => { + FlacDecoderInitError::MemoryAllocationError + } + FLAC__STREAM_DECODER_INIT_STATUS_UNSUPPORTED_CONTAINER => { + FlacDecoderInitError::UnsupportedContainer + } + _ => return Err(()), + }) + } +} + +fn convert_path(path: &Path) -> CString { + CString::new(path.to_str().expect("non-UTF-8 filename")).expect("filename has internal NULs") +} + +pub(crate) struct FlacDecoder(pub *mut FLAC__StreamDecoder); + +impl FlacDecoder { + pub(crate) fn new() -> Self { + FlacDecoder(unsafe { FLAC__stream_decoder_new() }) + } + + pub(crate) fn init_decode_from_file>( + &self, + file: &P, + buf: &mut Vec, + ) -> Result<(), FlacDecoderInitError> { + unsafe { + FLAC__stream_decoder_set_md5_checking(self.0, true as FLAC__bool); + } + + let filename = convert_path(file.as_ref()); + unsafe { + let result: FLAC__StreamDecoderInitStatus = FLAC__stream_decoder_init_file( + self.0, + filename.as_ptr(), + None, + None, + None, + buf.as_mut_ptr() as *mut c_void, + ); + if result != FLAC__STREAM_DECODER_INIT_STATUS_OK { + return Err(FlacDecoderInitError::try_from(result).unwrap()); + } + } + + Ok(()) + } + + pub(crate) fn decode_frame(&self) -> Result<(), ()> { + if unsafe { FLAC__stream_decoder_process_single(self.0) } != 0 { + Ok(()) + } else { + Err(()) + } + } + + pub(crate) fn get_channels(&self) -> u32 { + unsafe { FLAC__stream_decoder_get_channels(self.0) } + } + + pub(crate) fn get_bps(&self) -> u32 { + unsafe { FLAC__stream_decoder_get_bits_per_sample(self.0) } + } + + pub(crate) fn get_samplerate(&self) -> u32 { + unsafe { FLAC__stream_decoder_get_sample_rate(self.0) } + } +} + +impl Drop for FlacDecoder { + fn drop(&mut self) { + if !(self.0.is_null()) { + unsafe { FLAC__stream_decoder_finish(self.0) }; + } + } +} diff --git a/src/flac/mod.rs b/src/flac/mod.rs new file mode 100644 index 0000000..7a79e44 --- /dev/null +++ b/src/flac/mod.rs @@ -0,0 +1,311 @@ +mod decoder; + +use anyhow::{Result, anyhow}; +use flac_bound::{FlacEncoder, WriteWrapper}; +/* use md5::Digest; */ +use metaflac::Tag; +use std::{fs::File, path::Path}; + +use crate::flac::decoder::FlacDecoder; + +pub const CURRENT_VENDOR: &str = "reference libFLAC 1.5.0 20250211"; + +/* type BoxedFormatReader = Box; +type BoxedAudioDecoder = Box; */ + +/* pub struct StreamConfig { + channels: u32, + bits_per_sample: Bps, + sample_rate: u32, +} + +pub enum Bps { + _16, + _24, + _32, +} + +impl Bps { + fn new(num: u32) -> Result { + match num { + 16 => Ok(Bps::_16), + 24 => Ok(Bps::_24), + 32 => Ok(Bps::_32), + _ => Err(anyhow!("Invalid BPS")), + } + } + + fn value(&self) -> u32 { + match self { + Bps::_16 => 16, + Bps::_24 => 24, + Bps::_32 => 32, + } + } +} */ + +/* struct FileEncoder { + filename: PathBuf, + streamdata: StreamConfig, + format: BoxedFormatReader, + decoder: BoxedAudioDecoder, +} + +impl FileEncoder { + fn new(file: impl AsRef) -> Result { + let (format, decoder, config) = init_decoder(&file)?; + Ok(FileEncoder { + filename: file.as_ref().to_path_buf(), + streamdata: config, + format, + decoder, + }) + } + + fn temp_name(&self) -> PathBuf { + self.filename.clone().with_extension("tmp") + } + + fn encode(&mut self, mut encoder: FlacEncoder) -> Result> { + let mut buffer: Vec = Vec::new(); + let mut hasher = Md5::new(); + let track_id = self.format.default_track(TrackType::Audio).unwrap().id; + let offset = self.streamdata.bits_per_sample.value(); + + loop { + let packet = match self.format.next_packet() { + Ok(Some(packet)) => packet, + Ok(None) => break, + Err(error) => return Err(error.into()), + }; + + if packet.track_id() != track_id { + continue; + } + + if let GenericAudioBufferRef::S32(buf) = self.decoder.decode(&packet)? { + for sample in buf.iter_interleaved() { + let mut real_sample = sample; + if offset != 32 { + real_sample = sample >> (32 - offset) + } + match offset { + 16 => hasher.update(i16::try_from(real_sample)?.to_le_bytes()), + 24 => hasher + .update(i24::i24::try_from_i32(real_sample).unwrap().to_le_bytes()), + _ => hasher.update(real_sample.to_le_bytes()), + } + buffer.push(real_sample); + } + encoder + .process_interleaved(&buffer, buf.samples_planar() as u32) + .unwrap(); + buffer.clear(); + } else { + return Err(anyhow!("unsupported codec")); + } + } + + if let Err(enc) = encoder.finish() { + return Err(anyhow!("Encoding failed:\t{:?}", enc.state())); + } + + Ok(hasher.finalize().to_vec()) + } + + fn write_tags(&self, hash: Vec) -> Result<()> { + let tags = Tag::read_from_path(&self.filename)?; + let mut output = Tag::read_from_path(self.temp_name())?; + + let mut streaminfo = tags.get_streaminfo().unwrap().clone(); + + streaminfo.md5 = hash; + output.set_streaminfo(streaminfo); + + for block in tags.blocks() { + match block { + Block::VorbisComment(comment) => { + for (key, val) in comment.comments.clone() { + if key != "ENCODER" { + output.set_vorbis(key, val); + } + } + } + Block::StreamInfo(_) | Block::Padding(_) => {} + _ => output.push_block(block.clone()), + } + } + + output.write_to_path(self.temp_name())?; + Ok(()) + } +} + +fn init_decoder( + filename: impl AsRef, +) -> Result<(BoxedFormatReader, BoxedAudioDecoder, StreamConfig)> { + let src = std::fs::File::open(filename)?; + let mss = MediaSourceStream::new(Box::new(src), Default::default()); + let mut hint = Hint::new(); + hint.with_extension("flac"); + + let format_opts: FormatOptions = Default::default(); + let metadata_opts: MetadataOptions = Default::default(); + + let format = symphonia::default::get_probe() + .probe(&hint, mss, format_opts, metadata_opts) + .unwrap(); + + let track = format.default_track(TrackType::Audio).unwrap(); + + let decoder = symphonia::default::get_codecs() + .make_audio_decoder( + track.codec_params.as_ref().unwrap().audio().unwrap(), + &Default::default(), + ) + .unwrap(); + + let params = track.codec_params.as_ref().unwrap().audio().unwrap(); + + let config = StreamConfig { + channels: u32::try_from(params.channels.as_ref().unwrap().count()).unwrap(), + bits_per_sample: Bps::new(params.bits_per_sample.unwrap())?, + sample_rate: params.sample_rate.unwrap(), + }; + + Ok((format, decoder, config)) +} */ + +pub fn encode_file(filename: impl AsRef) -> Result<()> { + /* let mut filencoder = FileEncoder::new(filename)?; + let temp_name = filencoder.temp_name(); + + if temp_name.exists() { + std::fs::remove_file(&temp_name)?; + } + + let mut outf = File::create(temp_name)?; + let mut outw = WriteWrapper(&mut outf); + let enc = FlacEncoder::new() + .unwrap() + .channels(filencoder.streamdata.channels) + .bits_per_sample(filencoder.streamdata.bits_per_sample.value()) + .sample_rate(filencoder.streamdata.sample_rate) + .compression_level(8) + .verify(false) + .init_write(&mut outw) + .unwrap(); + + let hash = filencoder.encode(enc)?; + filencoder.write_tags(hash)?; + std::fs::rename(filencoder.temp_name(), filencoder.filename)?; + Ok(()) */ + + let decoder = FlacDecoder::new(); + let mut buffer: Vec = Vec::new(); + decoder.init_decode_from_file(&filename, &mut buffer)?; + + let temp_name = filename.as_ref().with_extension("tmp"); + let mut outf = File::create(temp_name)?; + let mut outw = WriteWrapper(&mut outf); + let mut enc = FlacEncoder::new() + .unwrap() + .channels(decoder.get_channels()) + .bits_per_sample(decoder.get_bps()) + .sample_rate(decoder.get_samplerate()) + .compression_level(8) + .verify(false) + .init_write(&mut outw) + .unwrap(); + + while decoder.decode_frame().is_ok() { + enc.process_interleaved(&buffer, buffer.iter().len() as u32 / decoder.get_channels()) + .unwrap(); + buffer.clear(); + } + + todo!() +} + +pub fn get_vendor(file: impl AsRef) -> Result { + if let Some(vorbis) = Tag::read_from_path(file)?.vorbis_comments() { + Ok(vorbis.vendor_string.to_owned()) + } else { + Err(anyhow!("Vendor string not found")) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use metaflac::Tag; + + #[test] + fn bit16() { + let name = "16bit.flac"; + let tempname = "16bit.flac.temp"; + std::fs::copy(name, tempname).unwrap(); + encode_file(name).unwrap(); + let target_md5 = Tag::read_from_path(tempname) + .unwrap() + .get_streaminfo() + .unwrap() + .md5 + .clone(); + let encoded_md5 = Tag::read_from_path(name) + .unwrap() + .get_streaminfo() + .unwrap() + .md5 + .clone(); + std::fs::remove_file(name).unwrap(); + std::fs::rename(tempname, name).unwrap(); + assert_eq!(target_md5, encoded_md5); + } + + #[test] + fn bit24() { + let name = "24bit.flac"; + let tempname = "24bit.flac.temp"; + std::fs::copy(name, tempname).unwrap(); + encode_file(name).unwrap(); + let target_md5 = Tag::read_from_path(tempname) + .unwrap() + .get_streaminfo() + .unwrap() + .md5 + .clone(); + let encoded_md5 = Tag::read_from_path(name) + .unwrap() + .get_streaminfo() + .unwrap() + .md5 + .clone(); + std::fs::remove_file(name).unwrap(); + std::fs::rename(tempname, name).unwrap(); + assert_eq!(target_md5, encoded_md5); + } + + #[test] + fn bit32() { + let name = "32bit.flac"; + let tempname = "32bit.flac.temp"; + std::fs::copy(name, tempname).unwrap(); + encode_file(name).unwrap(); + let target_md5 = Tag::read_from_path(tempname) + .unwrap() + .get_streaminfo() + .unwrap() + .md5 + .clone(); + let encoded_md5 = Tag::read_from_path(name) + .unwrap() + .get_streaminfo() + .unwrap() + .md5 + .clone(); + std::fs::remove_file(name).unwrap(); + std::fs::rename(tempname, name).unwrap(); + assert_eq!(target_md5, encoded_md5); + } +} -- cgit v1.3.1