#![allow(clippy::module_name_repetitions)]
use thiserror::Error;
use crate::EncodeInfo;
#[derive(Debug, Error)]
pub enum EncoderError {
#[error("Encoder error")]
Encoder(mp3lame_encoder::EncodeError),
#[error("Encoder error")]
Id3Tag(mp3lame_encoder::Id3TagError),
#[error("Build error")]
Build(mp3lame_encoder::BuildError),
}
impl From<mp3lame_encoder::EncodeError> for EncoderError {
fn from(value: mp3lame_encoder::EncodeError) -> Self {
Self::Encoder(value)
}
}
impl From<mp3lame_encoder::Id3TagError> for EncoderError {
fn from(value: mp3lame_encoder::Id3TagError) -> Self {
Self::Id3Tag(value)
}
}
impl From<mp3lame_encoder::BuildError> for EncoderError {
fn from(value: mp3lame_encoder::BuildError) -> Self {
Self::Build(value)
}
}
pub fn encoder_mp3() -> Result<mp3lame_encoder::Encoder, EncoderError> {
use mp3lame_encoder::{Builder, Id3Tag};
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::Kbps320)
.expect("set brate");
mp3_encoder
.set_quality(mp3lame_encoder::Quality::Best)
.expect("set quality");
mp3_encoder.set_id3_tag(Id3Tag {
album_art: &[],
title: b"My title",
artist: &[],
album: b"My album",
year: b"Current year",
comment: b"Just my comment",
})?;
let mp3_encoder = mp3_encoder.build()?;
Ok(mp3_encoder)
}
pub fn encode_mp3(
encoder: &mut mp3lame_encoder::Encoder,
input: &[i16],
) -> Result<(Vec<u8>, EncodeInfo), EncoderError> {
use mp3lame_encoder::{FlushNoGap, InterleavedPcm};
let interleaved = InterleavedPcm(input);
let mut mp3_out_buffer = Vec::with_capacity(mp3lame_encoder::max_required_buffer_size(
interleaved.0.len(),
));
let encoded_size = encoder.encode(interleaved, mp3_out_buffer.spare_capacity_mut())?;
unsafe {
mp3_out_buffer.set_len(mp3_out_buffer.len().saturating_add(encoded_size));
}
let encoded_size = encoder.flush::<FlushNoGap>(mp3_out_buffer.spare_capacity_mut())?;
unsafe {
mp3_out_buffer.set_len(mp3_out_buffer.len().saturating_add(encoded_size));
}
Ok((
mp3_out_buffer,
EncodeInfo {
output_size: encoded_size,
input_consumed: input.len(),
},
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test_log::test]
fn test_encoder_creation() {
let result = encoder_mp3();
assert!(result.is_ok(), "MP3 encoder should initialize successfully");
}
#[test_log::test]
fn test_encode_mp3_basic() {
let mut encoder = encoder_mp3().expect("Failed to create encoder");
let input: Vec<i16> = vec![0; 2048];
let result = encode_mp3(&mut encoder, &input);
assert!(result.is_ok(), "Encoding should succeed");
let (output, info) = result.unwrap();
assert!(!output.is_empty(), "Should produce output");
assert_eq!(info.input_consumed, input.len(), "Should consume all input");
}
#[test_log::test]
fn test_encode_mp3_non_zero_samples() {
let mut encoder = encoder_mp3().expect("Failed to create encoder");
let mut input: Vec<i16> = vec![0; 2048];
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
for (i, sample) in input.iter_mut().enumerate() {
*sample = (i as i16 % 1000) - 500;
}
let result = encode_mp3(&mut encoder, &input);
assert!(result.is_ok(), "Encoding should succeed");
let (output, info) = result.unwrap();
assert!(!output.is_empty(), "Should produce output");
assert_eq!(info.input_consumed, input.len(), "Should consume all input");
}
#[test_log::test]
fn test_encode_mp3_multiple_calls() {
let mut encoder = encoder_mp3().expect("Failed to create encoder");
let input1: Vec<i16> = vec![100; 2048];
let result1 = encode_mp3(&mut encoder, &input1);
assert!(result1.is_ok());
let (_output1, info1) = result1.unwrap();
assert_eq!(
info1.input_consumed,
input1.len(),
"First encode should consume all input"
);
let input2: Vec<i16> = vec![200; 2048];
let result2 = encode_mp3(&mut encoder, &input2);
assert!(result2.is_ok(), "Multiple encodes should work");
let (_output2, info2) = result2.unwrap();
assert_eq!(
info2.input_consumed,
input2.len(),
"Second encode should consume all input"
);
}
#[test_log::test]
fn test_encode_mp3_empty_input() {
let mut encoder = encoder_mp3().expect("Failed to create encoder");
let input: Vec<i16> = vec![];
let result = encode_mp3(&mut encoder, &input);
assert!(result.is_ok(), "Empty input should be handled");
let (_output, info) = result.unwrap();
assert_eq!(info.input_consumed, 0, "Should consume zero input");
}
#[test_log::test]
#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
fn test_encode_mp3_varying_samples() {
let mut encoder = encoder_mp3().expect("Failed to create encoder");
let sample_count = 4096; let mut input: Vec<i16> = Vec::with_capacity(sample_count);
for i in 0..sample_count / 2 {
let t = i as f32 / 44100.0;
let left = ((t * 440.0 * std::f32::consts::TAU).sin() * 16000.0) as i16;
let right = ((t * 550.0 * std::f32::consts::TAU).sin() * 16000.0) as i16;
input.push(left);
input.push(right);
}
let result = encode_mp3(&mut encoder, &input);
assert!(result.is_ok(), "Encoding varying samples should succeed");
let (output, info) = result.unwrap();
assert!(!output.is_empty(), "Should produce output");
assert_eq!(info.input_consumed, input.len(), "Should consume all input");
}
}