use std::fs::File;
use std::io::{Seek, Write};
use std::path::Path;
use flacenc::bitsink::ByteSink;
use flacenc::component::{BitRepr, Stream, StreamInfo};
use flacenc::config::Encoder as EncoderConfig;
use flacenc::error::{Verified, Verify};
use flacenc::source::{Context, Fill, FrameBuf};
use crate::error::{EncodeError, Result};
const BLOCK_SIZE: usize = 4096;
const BITS_PER_SAMPLE: usize = 16;
const MAX_SAMPLE_RATE: u32 = 96_000;
const HEADER_LEN: usize = 42;
#[inline]
fn quantize_i16(x: f32) -> i32 {
flexaudio_core::quantize_i16(x) as i32
}
fn enc_err(e: impl std::fmt::Display) -> EncodeError {
EncodeError::Encoder(e.to_string())
}
trait Output: Write + Seek + Send + Sync + std::panic::UnwindSafe + std::panic::RefUnwindSafe {}
impl<T: Write + Seek + Send + Sync + std::panic::UnwindSafe + std::panic::RefUnwindSafe> Output
for T
{
}
#[derive(Debug)]
enum WriterState {
Open,
Finalized,
Failed(FailureCause),
}
#[derive(Debug)]
enum FailureCause {
Io {
code: Option<i32>,
kind: std::io::ErrorKind,
message: String,
},
Unsupported(String),
Encoder(String),
}
impl FailureCause {
fn capture(error: EncodeError) -> Self {
match error {
EncodeError::Io(error) => Self::Io {
code: error.raw_os_error(),
kind: error.kind(),
message: error.to_string(),
},
EncodeError::Unsupported(message) => Self::Unsupported(message),
EncodeError::Encoder(message) => Self::Encoder(message),
}
}
fn error(&self) -> EncodeError {
match self {
Self::Io {
code,
kind,
message,
} => EncodeError::Io(match code {
Some(code) => std::io::Error::from_raw_os_error(*code),
None => std::io::Error::new(*kind, message.clone()),
}),
Self::Unsupported(message) => EncodeError::Unsupported(message.clone()),
Self::Encoder(message) => EncodeError::Encoder(message.clone()),
}
}
}
pub struct FlacWriter {
file: Box<dyn Output>,
config: Verified<EncoderConfig>,
stream_info: StreamInfo,
frame_buf: FrameBuf,
context: Context,
sink: ByteSink,
pending: Vec<i32>,
channels: usize,
frame_number: usize,
state: WriterState,
}
impl FlacWriter {
pub fn create<P: AsRef<Path>>(path: P, sample_rate: u32, channels: u16) -> Result<FlacWriter> {
if !(1..=2).contains(&channels) {
return Err(EncodeError::Unsupported(format!(
"channels must be 1 or 2, got {channels}"
)));
}
if !(1..=MAX_SAMPLE_RATE).contains(&sample_rate) {
return Err(EncodeError::Unsupported(format!(
"sample rate must be 1..={MAX_SAMPLE_RATE} Hz, got {sample_rate}"
)));
}
Self::with_output(sample_rate, channels, Box::new(File::create(path)?))
}
fn with_output(sample_rate: u32, channels: u16, file: Box<dyn Output>) -> Result<Self> {
let config = EncoderConfig::default()
.into_verified()
.map_err(|(_, e)| enc_err(e))?;
let mut stream_info =
StreamInfo::new(sample_rate as usize, channels as usize, BITS_PER_SAMPLE)
.map_err(enc_err)?;
stream_info
.set_block_sizes(BLOCK_SIZE, BLOCK_SIZE)
.map_err(enc_err)?;
let frame_buf = FrameBuf::with_size(channels as usize, BLOCK_SIZE).map_err(enc_err)?;
let context = Context::new(BITS_PER_SAMPLE, channels as usize);
let mut writer = FlacWriter {
file,
config,
stream_info,
frame_buf,
context,
sink: ByteSink::new(),
pending: Vec::new(),
channels: channels as usize,
frame_number: 0,
state: WriterState::Open,
};
let header = writer.header_bytes()?;
let result = writer.file.write_all(&header).map_err(EncodeError::Io);
writer.latch(result)?;
Ok(writer)
}
pub fn write_chunk(&mut self, interleaved: &[f32]) -> Result<()> {
self.check_open()?;
if interleaved.len() % self.channels != 0 {
return Err(EncodeError::Unsupported(format!(
"chunk length {} is not a multiple of channels {}",
interleaved.len(),
self.channels
)));
}
self.pending.reserve(interleaved.len());
self.pending
.extend(interleaved.iter().map(|&x| quantize_i16(x)));
let result = self.drain_full_blocks();
self.latch(result)
}
pub fn finalize(mut self) -> Result<()> {
self.finish()
}
fn check_open(&self) -> Result<()> {
match &self.state {
WriterState::Open => Ok(()),
WriterState::Finalized => Err(EncodeError::Unsupported("writer is finalized".into())),
WriterState::Failed(error) => Err(error.error()),
}
}
fn latch(&mut self, result: Result<()>) -> Result<()> {
if let Err(error) = result {
self.state = WriterState::Failed(FailureCause::capture(error));
return self.check_open();
}
Ok(())
}
fn finish(&mut self) -> Result<()> {
self.check_open()?;
let result = self.finish_inner();
self.latch(result)?;
self.state = WriterState::Finalized;
Ok(())
}
fn drain_full_blocks(&mut self) -> Result<()> {
let block_len = BLOCK_SIZE * self.channels;
let pending = std::mem::take(&mut self.pending);
let mut consumed = 0;
let mut result = Ok(());
while consumed + block_len <= pending.len() {
if let Err(e) = self.encode_block(&pending[consumed..consumed + block_len]) {
result = Err(e);
break;
}
consumed += block_len;
}
self.pending = pending;
self.pending.drain(..consumed);
result
}
fn encode_block(&mut self, block: &[i32]) -> Result<()> {
self.frame_buf.fill_interleaved(block).map_err(enc_err)?;
self.context.fill_interleaved(block).map_err(enc_err)?;
let frame = flacenc::encode_fixed_size_frame(
&self.config,
&self.frame_buf,
self.frame_number,
&self.stream_info,
)
.map_err(enc_err)?;
self.stream_info.update_frame_info(&frame);
self.sink.clear();
frame.write(&mut self.sink).map_err(enc_err)?;
self.file.write_all(self.sink.as_slice())?;
self.frame_number += 1;
Ok(())
}
fn finish_inner(&mut self) -> Result<()> {
if !self.pending.is_empty() {
let tail = std::mem::take(&mut self.pending);
self.encode_block(&tail)?;
}
self.stream_info
.set_block_sizes(BLOCK_SIZE, BLOCK_SIZE)
.map_err(enc_err)?;
self.stream_info.set_md5_digest(&self.context.md5_digest());
self.stream_info
.set_total_samples(self.context.total_samples());
let header = self.header_bytes()?;
self.file.flush()?;
self.file.rewind()?;
self.file.write_all(b"FAIL")?;
self.file.write_all(&header[4..])?;
self.file.flush()?;
self.file.rewind()?;
self.file.write_all(&header[..4])?;
Ok(())
}
fn header_bytes(&self) -> Result<Vec<u8>> {
let mut info = self.stream_info.clone();
if self.frame_number == 0 {
info.set_frame_sizes(0, 0).map_err(enc_err)?;
}
let stream = Stream::with_stream_info(info);
let mut sink = ByteSink::new();
stream.write(&mut sink).map_err(enc_err)?;
let bytes = sink.into_inner();
debug_assert_eq!(bytes.len(), HEADER_LEN);
Ok(bytes)
}
}
impl Drop for FlacWriter {
fn drop(&mut self) {
if matches!(self.state, WriterState::Open) {
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| self.finish()));
}
}
}
impl std::fmt::Debug for FlacWriter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FlacWriter")
.field("sample_rate", &self.stream_info.sample_rate())
.field("channels", &self.channels)
.field("frame_number", &self.frame_number)
.field("pending_samples", &self.pending.len())
.field("state", &self.state)
.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::{Cursor, SeekFrom};
use std::sync::{Arc, Mutex};
struct FaultOutput {
data: Arc<Mutex<Cursor<Vec<u8>>>>,
calls: Arc<Mutex<usize>>,
fail_at: usize,
partial: bool,
fail_flush_at: usize,
flushes: usize,
}
impl Write for FaultOutput {
fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
let mut calls = self.calls.lock().unwrap();
*calls += 1;
if *calls == self.fail_at {
if self.partial {
return self.data.lock().unwrap().write(&bytes[..bytes.len() / 2]);
}
return Err(std::io::Error::other("injected first failure"));
}
if self.partial && *calls == self.fail_at + 1 {
return Err(std::io::Error::other("injected first failure"));
}
self.data.lock().unwrap().write(bytes)
}
fn flush(&mut self) -> std::io::Result<()> {
self.flushes += 1;
if self.flushes == self.fail_flush_at {
Err(std::io::Error::other("injected flush failure"))
} else {
Ok(())
}
}
}
impl Seek for FaultOutput {
fn seek(&mut self, position: SeekFrom) -> std::io::Result<u64> {
self.data.lock().unwrap().seek(position)
}
}
#[test]
fn failed_frame_is_never_retried_or_finalized() {
for partial in [false, true] {
let data = Arc::new(Mutex::new(Cursor::new(Vec::new())));
let calls = Arc::new(Mutex::new(0));
let output = FaultOutput {
data: data.clone(),
calls: calls.clone(),
fail_at: 3,
partial,
fail_flush_at: usize::MAX,
flushes: 0,
};
let mut writer = FlacWriter::with_output(48_000, 1, Box::new(output)).unwrap();
writer.write_chunk(&vec![0.25; BLOCK_SIZE]).unwrap();
let first = writer
.write_chunk(&vec![0.5; BLOCK_SIZE])
.unwrap_err()
.to_string();
let count = writer.context.total_samples();
let digest = writer.context.md5_digest();
let bytes = data.lock().unwrap().get_ref().clone();
let writes = *calls.lock().unwrap();
assert_eq!(writer.write_chunk(&[]).unwrap_err().to_string(), first);
assert_eq!(writer.finish().unwrap_err().to_string(), first);
assert_eq!(writer.context.total_samples(), count);
assert_eq!(writer.context.md5_digest(), digest);
assert_eq!(writer.finalize().unwrap_err().to_string(), first);
assert_eq!(*calls.lock().unwrap(), writes);
assert_eq!(*data.lock().unwrap().get_ref(), bytes);
assert!(bytes[22..42].iter().all(|&byte| byte == 0));
}
}
#[test]
fn failed_drop_and_header_flush_do_not_retry() {
let data = Arc::new(Mutex::new(Cursor::new(Vec::new())));
let calls = Arc::new(Mutex::new(0));
let mut writer = FlacWriter::with_output(
48_000,
1,
Box::new(FaultOutput {
data: data.clone(),
calls: calls.clone(),
fail_at: 2,
partial: false,
fail_flush_at: usize::MAX,
flushes: 0,
}),
)
.unwrap();
assert!(writer.write_chunk(&vec![0.25; BLOCK_SIZE]).is_err());
let before = *calls.lock().unwrap();
drop(writer);
assert_eq!(*calls.lock().unwrap(), before);
let mut writer = FlacWriter::with_output(
48_000,
1,
Box::new(FaultOutput {
data: data.clone(),
calls: calls.clone(),
fail_at: usize::MAX,
partial: false,
fail_flush_at: 1,
flushes: 0,
}),
)
.unwrap();
writer.write_chunk(&[0.25]).unwrap();
let first = writer.finish().unwrap_err().to_string();
assert!(first.contains("flush failure"));
let before = *calls.lock().unwrap();
assert_eq!(writer.finalize().unwrap_err().to_string(), first);
assert_eq!(*calls.lock().unwrap(), before);
}
#[test]
fn header_publication_failure_invalidates_magic_and_latches() {
for (fail_at, partial, fail_flush_at) in [
(4, false, usize::MAX),
(4, true, usize::MAX),
(usize::MAX, false, 2),
(5, false, usize::MAX),
(5, true, usize::MAX),
] {
let data = Arc::new(Mutex::new(Cursor::new(Vec::new())));
let calls = Arc::new(Mutex::new(0));
let mut writer = FlacWriter::with_output(
48_000,
1,
Box::new(FaultOutput {
data: data.clone(),
calls: calls.clone(),
fail_at,
partial,
fail_flush_at,
flushes: 0,
}),
)
.unwrap();
writer.write_chunk(&[0.25]).unwrap();
let first = writer.finish().unwrap_err().to_string();
assert_ne!(&data.lock().unwrap().get_ref()[..4], b"fLaC");
let before = *calls.lock().unwrap();
assert_eq!(writer.finalize().unwrap_err().to_string(), first);
assert_eq!(*calls.lock().unwrap(), before);
}
}
#[test]
fn encoder_failure_and_auto_traits() {
fn assert_send_sync<T: Send + Sync + std::panic::UnwindSafe + std::panic::RefUnwindSafe>() {
}
assert_send_sync::<FlacWriter>();
let data = Arc::new(Mutex::new(Cursor::new(Vec::new())));
let calls = Arc::new(Mutex::new(0));
let mut writer = FlacWriter::with_output(
48_000,
1,
Box::new(FaultOutput {
data,
calls: calls.clone(),
fail_at: usize::MAX,
partial: false,
fail_flush_at: usize::MAX,
flushes: 0,
}),
)
.unwrap();
writer.frame_number = usize::MAX;
let first = writer.write_chunk(&vec![0.25; BLOCK_SIZE]).unwrap_err();
assert!(matches!(first, EncodeError::Encoder(_)));
let count = writer.context.total_samples();
let digest = writer.context.md5_digest();
assert_eq!(
writer.write_chunk(&[]).unwrap_err().to_string(),
first.to_string()
);
assert_eq!(writer.context.total_samples(), count);
assert_eq!(writer.context.md5_digest(), digest);
assert_eq!(
writer.finalize().unwrap_err().to_string(),
first.to_string()
);
assert_eq!(*calls.lock().unwrap(), 1);
}
#[test]
fn invalid_chunk_does_not_fail_an_open_writer() {
let data = Arc::new(Mutex::new(Cursor::new(Vec::new())));
let output = FaultOutput {
data,
calls: Arc::new(Mutex::new(0)),
fail_at: usize::MAX,
partial: false,
fail_flush_at: usize::MAX,
flushes: 0,
};
let mut writer = FlacWriter::with_output(48_000, 2, Box::new(output)).unwrap();
assert!(writer.write_chunk(&[0.5]).is_err());
writer.write_chunk(&[0.5, 0.5]).unwrap();
writer.finalize().unwrap();
}
#[test]
fn quantize_reference_points() {
assert_eq!(quantize_i16(0.0), 0);
assert_eq!(quantize_i16(0.25), 8192);
assert_eq!(quantize_i16(-1.0), -32768);
assert_eq!(quantize_i16(1.0), 32767);
assert_eq!(quantize_i16(2.0), 32767);
assert_eq!(quantize_i16(-2.0), -32768);
assert_eq!(quantize_i16(f32::NAN), 0);
assert_eq!(quantize_i16(f32::INFINITY), 32767);
assert_eq!(quantize_i16(f32::NEG_INFINITY), -32768);
assert_eq!(quantize_i16(1.5 / 32768.0), 2);
assert_eq!(quantize_i16(-1.5 / 32768.0), -2);
}
}