Skip to content

Commit

Permalink
Change to use custom Source for flacenc-bin.
Browse files Browse the repository at this point in the history
This is good for demonstrating how to customize source of encoder, and
also good for performance in multi-thread mode (because, unlike
pre-loaded signals, file I/O is done while other frames are being
encoded.)
  • Loading branch information
yotarok committed Oct 4, 2023
1 parent 0b75de5 commit 5a3be6d
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 47 deletions.
109 changes: 69 additions & 40 deletions flacenc-bin/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@

use std::fmt::Debug;
use std::fs::File;
use std::io::BufReader;
use std::io::BufWriter;
use std::io::Write;
use std::path::Path;
Expand All @@ -62,7 +63,9 @@ use flacenc::config;
use flacenc::constant::ExitCode;
use flacenc::error::SourceError;
use flacenc::error::Verify;
use flacenc::source;
use flacenc::source::Context;
use flacenc::source::FrameBuf;
use flacenc::source::Source;

/// FLAC encoder.
#[derive(Parser, Debug)]
Expand All @@ -88,57 +91,83 @@ struct Args {
/// Serializes `Stream` to a file.
#[allow(clippy::expect_used)]
fn write_stream<F: Write>(stream: &Stream, file: &mut F) {
eprintln!("{} bits to be written", stream.count_bits());
let mut bv = flacenc::bitsink::ByteVec::new();
let bits = stream.count_bits();
eprintln!("{bits} bits to be written");
let mut bv = flacenc::bitsink::ByteVec::with_capacity(bits);
stream.write(&mut bv).expect("Bitstream formatting failed.");
let mut writer = BufWriter::new(file);
writer
.write_all(bv.as_byte_slice())
.expect("Failed to write a bitstream to the file.");
}

/// Collect iterator of `Result`s, and returns values or the first error.
fn collect_results<I, T, E>(iter: I) -> Result<Vec<T>, E>
where
I: Iterator<Item = Result<T, E>>,
E: std::error::Error,
T: Debug,
{
let mut error: Option<E> = None;
let mut samples = vec![];
for r in iter {
if let Ok(v) = r {
samples.push(v);
} else {
error = Some(r.unwrap_err());
break;
}
/// An example of `flacenc::source::Source` based on `hound::WavReader`.
struct HoundSource {
spec: hound::WavSpec,
duration: usize,
samples: hound::WavIntoSamples<BufReader<File>, i32>,
buf: Vec<i32>,
}

impl HoundSource {
/// Constructs `HoundSource` from `path`.
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, Box<dyn std::error::Error>> {
let reader = Box::new(hound::WavReader::open(path).map_err(Box::new)?);
let spec = reader.spec();
let duration = reader.duration() as usize;
Ok(Self {
spec,
duration,
samples: reader.into_samples(),
buf: Vec::new(),
})
}
error.map_or_else(|| Ok(samples), Err)
}

/// Loads wave file and constructs `PreloadedSignal`.
///
/// # Errors
///
/// This function propagates errors emitted by the backend wave parser.
pub fn load_input_wav<P: AsRef<Path>>(
path: P,
) -> Result<source::PreloadedSignal, Box<dyn std::error::Error>> {
let mut reader = hound::WavReader::open(path).map_err(Box::new)?;
let spec = reader.spec();
let samples: Vec<i32> = collect_results(reader.samples())?;
Ok(source::PreloadedSignal::from_samples(
&samples,
spec.channels as usize,
spec.bits_per_sample as usize,
spec.sample_rate as usize,
))
impl Source for HoundSource {
fn channels(&self) -> usize {
self.spec.channels as usize
}

fn bits_per_sample(&self) -> usize {
self.spec.bits_per_sample as usize
}

fn sample_rate(&self) -> usize {
self.spec.sample_rate as usize
}

#[inline]
fn read_samples(
&mut self,
dest: &mut FrameBuf,
context: &mut Context,
) -> Result<usize, SourceError> {
self.buf.clear();
let to_read = dest.size() * self.channels();
for _t in 0..to_read {
if let Some(res) = self.samples.next() {
let v = res.unwrap();
self.buf.push(v);
} else {
break;
}
}
dest.fill_from_interleaved(&self.buf);
if !self.buf.is_empty() {
context.update(&self.buf, dest.size())?;
}
Ok(self.buf.len() / self.channels())
}

fn len_hint(&self) -> Option<usize> {
Some(self.duration)
}
}

fn run_encoder(
fn run_encoder<S: Source>(
encoder_config: &config::Encoder,
source: source::PreloadedSignal,
source: S,
) -> Result<Stream, SourceError> {
let block_size = encoder_config.block_sizes[0];
coding::encode_with_fixed_block_size(encoder_config, source, block_size)
Expand All @@ -155,7 +184,7 @@ fn main_body(args: Args) -> Result<(), i32> {
return Err(ExitCode::InvalidConfig as i32);
}

let source = load_input_wav(&args.source).expect("Failed to load input source.");
let source = HoundSource::from_path(&args.source).expect("Failed to load input source.");

let stream = run_encoder(&encoder_config, source).expect("Encoder error.");

Expand Down
14 changes: 7 additions & 7 deletions report/report.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ Sources used: wikimedia.i_love_you_california, wikimedia.winter_kiss, wikimedia.

### Average compression speed (inverse RTF)
- Reference
- opt8lax: 258.8324501964389
- opt8: 255.37311996195788
- opt5: 497.2015031869035
- opt8lax: 263.0515223440761
- opt8: 259.1675443841502
- opt5: 494.0462089319891

- Ours
- default: 280.49915771421183
- st: 102.84285535481595
- dmse: 183.0220425392949
- mae: 40.46853900438779
- default: 311.5466700534162
- st: 102.46001430873105
- dmse: 192.25265039265963
- mae: 40.35315704283569


0 comments on commit 5a3be6d

Please sign in to comment.