diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 8006218..a9812d6 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -31,7 +31,8 @@ jobs: with: runs-on: ${{ matrix.os }} min-rustc-version: "1.65.0" - valgrind_flags: "--suppressions=valgrind.supp" + valgrind: true + valgrind_flags: "--suppressions=valgrind.supp --gen-suppressions=all --leak-check=full" cross-compilation-test: needs: test diff --git a/README.md b/README.md index fabb693..e4fcf45 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,30 @@ unsafe { } //At this point your mp3_out_buffer should have full MP3 data, ready to be written on file system or whatever +if mp3_encoder.lame_tag_size() > 0 { + let id3v2_tag_boundary = mp3_encoder.id3v2_tag_size(); + assert_eq!(id3v2_tag_boundary, 158); + let mut lame_tag = [core::mem::MaybeUninit::uninit(); 1024]; + assert!(lame_tag.len() >= mp3_encoder.lame_tag_size(), "Increase buffer size"); + let lame_tag_size = mp3_encoder.lame_tag_encode(&mut lame_tag).expect("to write lame tag"); + assert_eq!(mp3_encoder.lame_tag_size(), lame_tag_size.get()); + + //If you need VBR tag then you need to write mp3 file in following order + //- id3v2 tag + //- VBR tag + //- actual mp3 content + let chunks_to_write = [ + &mp3_out_buffer[..id3v2_tag_boundary], + unsafe { + core::slice::from_raw_parts(lame_tag.as_ptr() as *const u8, lame_tag_size.get()) + }, + &mp3_out_buffer[id3v2_tag_boundary..], + ]; +} else { + let chunks_to_write = [ + &mp3_out_buffer[..] + ]; +} ``` ## License diff --git a/src/lib.rs b/src/lib.rs index dbd5f2a..b5df8ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,28 +14,22 @@ //! comment: b"Just my comment", //!}; //! -//!//Create codec using builder -//!let mut mp3_encoder = Builder::new().expect("Create LAME builder"); -//!mp3_encoder.set_num_channels(2).expect("set channels"); -//!mp3_encoder.set_sample_rate(44_100).expect("set sample rate"); -//!mp3_encoder.set_brate(mp3lame_encoder::Bitrate::Kbps192).expect("set brate"); -//!mp3_encoder.set_quality(mp3lame_encoder::Quality::Best).expect("set quality"); -//!mp3_encoder.set_id3_tag(id3tag); -//!let mut mp3_encoder = mp3_encoder.build().expect("To initialize LAME encoder"); -//! //!//Methods prefixed with `with_*` return Self for convenience //!let mut mp3_encoder = Builder::new().expect("Create LAME builder") //! .with_num_channels(2).expect("set channels") //! .with_sample_rate(44_100).expect("set sample rate") //! .with_brate(mp3lame_encoder::Bitrate::Kbps192).expect("set brate") //! .with_quality(mp3lame_encoder::Quality::Best).expect("set quality") +//! .with_vbr_mode(mp3lame_encoder::VbrMode::Mtrh).expect("set VBR") +//! .with_vbr_quality(mp3lame_encoder::Quality::Best).expect("set VBR quality") +//! .with_to_write_vbr_tag(true).expect("set to write VBR tag") //! .with_id3_tag(id3tag).expect("set tags") //! .build().expect("To initialize LAME encoder"); //! //!//use actual PCM data //!let input = DualPcm { -//! left: &[0u16, 0], -//! right: &[0u16, 0], +//! left: &[0u16, 1024], +//! right: &[0u16, 1024], //!}; //! //!let mut mp3_out_buffer = Vec::new(); @@ -51,6 +45,30 @@ //!} //!//At this point your mp3_out_buffer should have full MP3 data, ready to be written on file system or whatever //! +//!if mp3_encoder.lame_tag_size() > 0 { +//! let id3v2_tag_boundary = mp3_encoder.id3v2_tag_size(); +//! assert_eq!(id3v2_tag_boundary, 158); +//! let mut lame_tag = [core::mem::MaybeUninit::uninit(); 1024]; +//! assert!(lame_tag.len() >= mp3_encoder.lame_tag_size(), "Increase buffer size"); +//! let lame_tag_size = mp3_encoder.lame_tag_encode(&mut lame_tag).expect("to write lame tag"); +//! assert_eq!(mp3_encoder.lame_tag_size(), lame_tag_size.get()); +//! +//! //If you need VBR tag then you need to write mp3 file in following order +//! //- id3v2 tag +//! //- VBR tag +//! //- actual mp3 content +//! let chunks_to_write = [ +//! &mp3_out_buffer[..id3v2_tag_boundary], +//! unsafe { +//! core::slice::from_raw_parts(lame_tag.as_ptr() as *const u8, lame_tag_size.get()) +//! }, +//! &mp3_out_buffer[id3v2_tag_boundary..], +//! ]; +//!} else { +//! let chunks_to_write = [ +//! &mp3_out_buffer[..] +//! ]; +//!} //!``` #![no_std] @@ -68,7 +86,7 @@ pub use mp3lame_sys as ffi; use alloc::vec::Vec; use core::mem::{self, MaybeUninit}; -use core::num::NonZeroU32; +use core::num::{NonZeroU32, NonZeroUsize}; use core::ptr::{self, NonNull}; use core::{cmp, fmt}; use core::ffi::c_int; @@ -157,7 +175,7 @@ impl fmt::Display for BuildError { pub enum EncodeError { ///Indicates output buffer is insufficient. /// - ///Consider using [max_required_buffer_size](max_required_buffer_size) to determine required + ///Consider using [max_required_buffer_size] to determine required ///space to alloc. BufferTooSmall, ///Failed to allocate memory @@ -619,7 +637,7 @@ impl Builder { #[inline] ///Sets id3tag tag. /// - ///If [FlushGap](FlushGap) is used, then `v1` will not be added. + ///If [FlushGap] is used, then `v1` will not be added. ///But `v2` is always added at the beginning. /// ///Returns whether it is supported or not. @@ -686,7 +704,7 @@ impl Builder { #[inline] ///Sets id3tag tag using the builder pattern. /// - ///If [FlushGap](FlushGap) is used, then `v1` will not be added. + ///If [FlushGap] is used, then `v1` will not be added. /// ///Returns an error if it is not supported. pub fn with_id3_tag(mut self, value: Id3Tag<'_>) -> Result { @@ -750,12 +768,74 @@ impl Encoder { } } + #[inline] + ///Returns indication whether encoder is configured to write LAME tag + pub fn is_lame_tag_written(&self) -> bool { + unsafe { + ffi::lame_get_bWriteVbrTag(self.ptr()) != 0 + } + } + + #[inline] + ///Returns size of ths [Id3Tag] written, if any + pub fn id3v2_tag_size(&self) -> usize { + unsafe { + ffi::lame_get_id3v2_tag(self.ptr(), ptr::null_mut(), 0) + } + } + + #[inline] + ///Retrieves size of the lame tag + pub fn lame_tag_size(&self) -> usize { + unsafe { + ffi::lame_get_lametag_frame(self.ptr(), ptr::null_mut(), 0) + } + } + + #[inline] + ///Attempts to write lame tag into `output` returning number of written bytes in case of success + /// + ///Note that if you write [Id3Tag] you must not write this tag at the start. + ///Instead you must insert Lame Tag after [Id3Tag] position. + /// + ///To determine where [Id3Tag] ends in output stream, you can use [Encoder::id3v2_tag_size] + ///which returns full size of [Id3Tag] metadata written (therefore indicating where actual mp3 + ///content is starting) + pub fn lame_tag_encode(&self, output: &mut [MaybeUninit]) -> Option { + //lame_get_lametag_frame() returns full required size in case `output` is not sufficient, + //so first manually check buffer size + if output.len() < self.lame_tag_size() { + None + } else { + NonZeroUsize::new(unsafe { + ffi::lame_get_lametag_frame(self.ptr(), output.as_mut_ptr() as _, output.len()) + }) + } + } + + #[inline] + ///Attempts to write lame tag into `output` returning number of written bytes in case of success + /// + ///Refer to [Encoder::lame_tag_encode] for details + pub fn lame_tag_encode_to_vec(&self, output: &mut Vec) -> Option { + let original_len = output.len(); + match self.lame_tag_encode(output.spare_capacity_mut()) { + Some(written) => { + unsafe { + output.set_len(original_len.saturating_add(written.get())); + } + Some(written) + }, + None => None + } + } + #[inline] ///Attempts to encode PCM data, writing whatever available onto `output` buffer /// ///### Arguments: /// - /// - `input` - Data input. Can be [MonoPcm](MonoPcm), [DualPcm](DualPcm) or [InterleavedPcm](InterleavedPcm) + /// - `input` - Data input. Can be [MonoPcm], [DualPcm] or [InterleavedPcm] /// - `output` - Output buffer to write into. /// ///### Result: @@ -795,8 +875,8 @@ impl Encoder { /// ///### Type: /// - ///- [FlushNoGap](FlushNoGap) - performs flush, using ancillary data to fill gaps; - ///- [FlushGap](FlushGap) - performs flush, padding with 0; + ///- [FlushNoGap] - performs flush, using ancillary data to fill gaps; + ///- [FlushGap] - performs flush, padding with 0; /// ///### Arguments: /// diff --git a/tests/mp3.rs b/tests/mp3.rs index 610b11f..8e8dc41 100644 --- a/tests/mp3.rs +++ b/tests/mp3.rs @@ -45,128 +45,6 @@ fn should_decode_and_encode() { let spec = *audio_buf.spec(); let spec_channels = spec.channels.count(); - let mut mp3_out_buffer = Vec::new(); - let mut mp3_encoder = Builder::new().expect("Create LAME builder"); - mp3_encoder.set_num_channels(spec_channels as u8).expect("set channels"); - mp3_encoder.set_sample_rate(spec.rate).expect("set sample rate"); - mp3_encoder.set_brate(mp3lame_encoder::Bitrate::Kbps192).expect("set brate"); - mp3_encoder.set_quality(mp3lame_encoder::Quality::Best).expect("set quality"); - mp3_encoder.set_id3_tag(Id3Tag { - title: b"Bell", - artist: &[], - album: b"Test", - album_art: ALBUM_ART, - year: b"2022", - comment: b"Just some test shit", - }).expect("success"); - let mut mp3_encoder = mp3_encoder.build().expect("To initialize LAME encoder"); - - mp3_out_buffer.reserve(MAX_ALBUM_ART_SIZE); - - let mut samples_num = audio_buf.frames(); - match audio_buf { - AudioBufferRef::F32(audio_buf) => { - let planes = audio_buf.planes(); - let planes = planes.planes(); - assert_eq!(planes.len(), 1); - let input = MonoPcm(planes[0]); - assert_eq!(samples_num, input.0.len()); - mp3_out_buffer.reserve(mp3lame_encoder::max_required_buffer_size(samples_num)); - mp3_encoder.encode_to_vec(input, &mut mp3_out_buffer).expect("To encode"); - } - AudioBufferRef::F64(audio_buf) => { - let planes = audio_buf.planes(); - let planes = planes.planes(); - assert_eq!(planes.len(), 1); - let input = MonoPcm(planes[0]); - assert_eq!(samples_num, input.0.len()); - mp3_out_buffer.reserve(mp3lame_encoder::max_required_buffer_size(samples_num)); - mp3_encoder.encode_to_vec(input, &mut mp3_out_buffer).expect("To encode"); - } - _ => panic!("Unexpected"), - } - - loop { - let packet = match format.next_packet() { - Ok(packet) => packet, - Err(SymError::IoError(error)) => match error.kind() { - io::ErrorKind::UnexpectedEof => break, - _ => panic!("Unexpected IO error: {error}"), - }, - Err(error) => panic!("Unexpected error reading packets: {error}"), - }; - - if packet.track_id() != track_id { - continue - } - - let audio_buf = decoder.decode(&packet).expect("To decode first packet"); - - samples_num = audio_buf.frames(); - - match audio_buf { - AudioBufferRef::F32(audio_buf) => { - let planes = audio_buf.planes(); - let planes = planes.planes(); - assert_eq!(planes.len(), 1); - let input = MonoPcm(planes[0]); - assert_eq!(samples_num, input.0.len()); - mp3_out_buffer.reserve(mp3lame_encoder::max_required_buffer_size(samples_num)); - mp3_encoder.encode_to_vec(input, &mut mp3_out_buffer).expect("To encode"); - } - AudioBufferRef::F64(audio_buf) => { - let planes = audio_buf.planes(); - let planes = planes.planes(); - assert_eq!(planes.len(), 2); - let input = MonoPcm(planes[0]); - assert_eq!(samples_num, input.0.len()); - mp3_out_buffer.reserve(mp3lame_encoder::max_required_buffer_size(samples_num)); - mp3_encoder.encode_to_vec(input, &mut mp3_out_buffer).expect("To encode"); - } - _ => panic!("Unexpected"), - } - } - - let _ = mp3_encoder.flush_to_vec::(&mut mp3_out_buffer).expect("to flush"); - fs::write(NEW_FILE, &mp3_out_buffer).expect("write file") -} - -#[test] -fn should_decode_and_encode_using_builder_pattern() { - const FILE: &str = "tests/Bell3.ogg"; - const NEW_FILE: &str = "tests/Bell3_with_builder_encoded.mp3"; - - let file = fs::File::open(FILE).expect("open FILE"); - let file = MediaSourceStream::new(Box::new(file), Default::default()); - let mut hint = Hint::new(); - hint.with_extension("ogg"); - - let format_opts = Default::default(); - let metadata_opts = Default::default(); - let decoder_opts = Default::default(); - - // Probe the media source stream for a format. - let probed = symphonia::default::get_probe().format(&hint, file, &format_opts, &metadata_opts).expect("To probe mp3 file"); - // Get the format reader yielded by the probe operation. - let mut format = probed.format; - let track = format.default_track().expect("Get default track"); - let mut decoder = symphonia::default::get_codecs().make(&track.codec_params, &decoder_opts).unwrap(); - - // Store the track identifier, we'll use it to filter packets. - let track_id = track.id; - - let first_packet = loop { - let packet = format.next_packet().expect("to get packet"); - if packet.track_id() != track_id { - continue - } - break packet; - }; - - let audio_buf = decoder.decode(&first_packet).expect("To decode first packet"); - let spec = *audio_buf.spec(); - let spec_channels = spec.channels.count(); - let mut mp3_out_buffer = Vec::new(); // Build the encoder using builder-like ernomonics @@ -175,6 +53,9 @@ fn should_decode_and_encode_using_builder_pattern() { .with_sample_rate(spec.rate).expect("set sample rate") .with_brate(mp3lame_encoder::Birtate::Kbps192).expect("set brate") .with_quality(mp3lame_encoder::Quality::Best).expect("set quality") + .with_vbr_mode(mp3lame_encoder::VbrMode::Mtrh).expect("set VBR") + .with_vbr_quality(mp3lame_encoder::Quality::Best).expect("set VBR quality") + .with_to_write_vbr_tag(true).expect("set to write VBR tag") .with_id3_tag(Id3Tag { title: b"Bell", artist: &[], @@ -185,7 +66,6 @@ fn should_decode_and_encode_using_builder_pattern() { }).expect("Id3 tag") .build().expect("To initialize LAME encoder"); - mp3_out_buffer.reserve(MAX_ALBUM_ART_SIZE); let mut samples_num = audio_buf.frames(); @@ -252,6 +132,33 @@ fn should_decode_and_encode_using_builder_pattern() { } } + let mut lame_tag = Vec::new(); + assert!(mp3_encoder.is_lame_tag_written()); + assert_eq!(mp3_encoder.lame_tag_size(), 417); + assert_eq!(mp3_encoder.lame_tag_encode_to_vec(&mut lame_tag), None); + lame_tag.reserve(mp3_encoder.lame_tag_size()); + assert_eq!(mp3_encoder.lame_tag_encode_to_vec(&mut lame_tag).map(|len| len.get()), Some(417)); + let _ = mp3_encoder.flush_to_vec::(&mut mp3_out_buffer).expect("to flush"); - fs::write(NEW_FILE, &mp3_out_buffer).expect("write file") + assert_eq!(mp3_encoder.id3v2_tag_size(), 94507); + + let mut output_file = fs::File::create(NEW_FILE).expect("create file"); + //Write Id3Tag first (if any) + io::Write::write_all(&mut output_file, &mp3_out_buffer[..mp3_encoder.id3v2_tag_size()]).expect("write id3v2_tag"); + //Write Lame Tag (only after finishing encoding) + io::Write::write_all(&mut output_file, &lame_tag).expect("write lame tag"); + //Write actual encoded mp3 + io::Write::write_all(&mut output_file, &mp3_out_buffer[mp3_encoder.id3v2_tag_size()..]).expect("write encoded mp3"); + io::Write::flush(&mut output_file).expect("flush mp3"); +} + +#[test] +fn should_verify_vbr_tag_is_not_present_without_encode() { + let mp3_encoder = Builder::new().expect("Create LAME builder").with_vbr_mode(mp3lame_encoder::VbrMode::Off).expect("set vbr").build().expect("finish build"); + assert_eq!(mp3_encoder.lame_tag_size(), 0); + let mp3_encoder = Builder::new().expect("Create LAME builder").with_vbr_mode(mp3lame_encoder::VbrMode::Mtrh).expect("set vbr") + .with_vbr_quality(mp3lame_encoder::Quality::Best).expect("set quality") + .build().expect("finish build"); + assert!(mp3_encoder.is_lame_tag_written()); + assert_eq!(mp3_encoder.lame_tag_size(), 0); } diff --git a/valgrind.supp b/valgrind.supp index 95d231b..1b641ec 100644 --- a/valgrind.supp +++ b/valgrind.supp @@ -53,3 +53,32 @@ fun:*rt*lang_start* fun:*rt*lang_start_internal* } +{ + symphonia_bad_static4 + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:alloc + ... + fun:reserve_rehash_inner* + ... + fun:*hashbrown*find_or_find_insert_index* + ... + fun:find_or_find_insert_index* + ... +} +{ + rust_compiler_leak_test + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + ... + fun:current_or_unnamed + ... + fun:__rust_std_internal_init_fn + ... + fun:run_tests* + ... + fun:test_main_with_exit_callback* + ... +}