mcelp 1.0.1

Mitsubishi CELP speech codec: a 3.6 kbit/s speech encoder and decoder
Documentation
//! Encode raw mu-law on stdin to an M-CELP bit stream on stdout.
//!
//! Input is 320 mu-law bytes per frame; output is one hex-encoded frame per
//! line, which is what `mcelp_decode` reads back.  A trailing partial frame is
//! dropped, as the reference `mcelp_codec` drops it.

use std::io::{Read, Write};

use mcelp::{Encoder, FRAME, bitstream};

fn main() -> std::io::Result<()> {
    let stdin = std::io::stdin();
    let stdout = std::io::stdout();
    encode_stream(stdin.lock(), std::io::BufWriter::new(stdout.lock()))
}

/// Encode complete frames as they arrive, without retaining the whole input.
fn encode_stream(mut input: impl Read, mut output: impl Write) -> std::io::Result<()> {
    let mut encoder = Encoder::new();
    let mut block = [0u8; FRAME];

    loop {
        match input.read_exact(&mut block) {
            Ok(()) => {
                let frame = encoder.encode(&block);
                output.write_all(bitstream::to_hex_line(&frame).as_bytes())?;
                output.write_all(b"\n")?;
            }
            Err(error) if error.kind() == std::io::ErrorKind::UnexpectedEof => break,
            Err(error) => return Err(error),
        }
    }
    output.flush()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn stream_matches_encoder_and_drops_a_partial_frame() {
        let first = [0xff; FRAME];
        let second = std::array::from_fn(|i| i as u8);
        let mut input = Vec::from(first);
        input.extend_from_slice(&second);
        input.extend_from_slice(&[0; 17]);

        let mut encoder = Encoder::new();
        let mut expected = String::new();
        for block in [&first, &second] {
            expected.push_str(&bitstream::to_hex_line(&encoder.encode(block)));
            expected.push('\n');
        }

        let mut output = Vec::new();
        encode_stream(input.as_slice(), &mut output).unwrap();

        assert_eq!(output, expected.as_bytes());
    }
}