use std::fs::File;
use std::io::{BufWriter, 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 {
(x * 32768.0).round().clamp(-32768.0, 32767.0) as i32
}
fn enc_err(e: impl std::fmt::Display) -> EncodeError {
EncodeError::Encoder(e.to_string())
}
pub struct FlacWriter {
file: BufWriter<File>,
config: Verified<EncoderConfig>,
stream_info: StreamInfo,
frame_buf: FrameBuf,
context: Context,
sink: ByteSink,
pending: Vec<i32>,
channels: usize,
frame_number: usize,
finalized: bool,
}
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}"
)));
}
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: BufWriter::new(File::create(path)?),
config,
stream_info,
frame_buf,
context,
sink: ByteSink::new(),
pending: Vec::new(),
channels: channels as usize,
frame_number: 0,
finalized: false,
};
let header = writer.header_bytes()?;
writer.file.write_all(&header)?;
Ok(writer)
}
pub fn write_chunk(&mut self, interleaved: &[f32]) -> Result<()> {
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)));
self.drain_full_blocks()
}
pub fn finalize(mut self) -> Result<()> {
let result = self.finish_inner();
self.finalized = true;
result
}
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.rewind()?;
self.file.write_all(&header)?;
self.file.flush()?;
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 !self.finalized {
let _ = self.finish_inner();
}
}
}
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("finalized", &self.finalized)
.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use super::quantize_i16;
#[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);
}
}