Using Rust to process wav file
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::str; | |
pub fn read_string_from_buffer<'a>(header: &'a [u8], from: usize, length: usize) -> &'a str { | |
let to = from + length; | |
str::from_utf8(&header[from .. to]).unwrap() | |
} | |
pub fn read_u16_from_buffer(header: &[u8], from: usize) -> u16 { | |
let to = from + 2; | |
let slice = &header[from .. to]; | |
slice.iter().rev().fold(0, |acc, &b| (acc << 8) + b as u16) | |
} | |
pub fn read_u32_from_buffer(header: &[u8], from: usize) -> u32 { | |
let to = from + 4; | |
let slice = &header[from .. to]; | |
slice.iter().rev().fold(0, |acc, &b| (acc << 8) + b as u32) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::fmt; | |
use crate::buffer_utils::{read_string_from_buffer, read_u16_from_buffer, read_u32_from_buffer}; | |
pub struct Header<'a> { | |
pub chunk_id: &'a str, | |
pub chunk_size: u32, | |
pub format: &'a str, | |
pub subchunk1_id: &'a str, | |
pub subchunk1_size: u32, | |
pub audio_format: u16, | |
pub number_of_channels: u16, | |
pub sample_rate: u32, | |
pub byte_rate: u32, | |
pub block_align: u16, | |
pub bits_per_sample: u16, | |
pub subchunk2_id: &'a str, | |
pub subchunk2_size: u32, | |
} | |
impl<'a> Header<'a> { | |
pub fn parse_from(header_buf: &'a [u8]) -> Result<Header, &'static str> { | |
let header = Header { | |
chunk_id: read_string_from_buffer(&header_buf, 0, 4), | |
chunk_size: read_u32_from_buffer(&header_buf, 4), | |
format: read_string_from_buffer(&header_buf, 8, 4), | |
subchunk1_id: read_string_from_buffer(&header_buf, 12, 4), | |
subchunk1_size: read_u32_from_buffer(&header_buf, 16), | |
audio_format: read_u16_from_buffer(&header_buf, 20), | |
number_of_channels: read_u16_from_buffer(&header_buf, 22), | |
sample_rate: read_u32_from_buffer(&header_buf, 24), | |
byte_rate: read_u32_from_buffer(&header_buf, 28), | |
block_align: read_u16_from_buffer(&header_buf, 32), | |
bits_per_sample: read_u16_from_buffer(&header_buf, 34), | |
subchunk2_id: read_string_from_buffer(&header_buf, 36, 4), | |
subchunk2_size: read_u32_from_buffer(&header_buf, 40), | |
}; | |
assert!(header.chunk_id == "RIFF"); | |
assert!(header.format == "WAVE"); | |
assert!(header.subchunk1_id == "fmt "); | |
assert!(header.subchunk1_size == 16); | |
assert!(header.audio_format == 1); | |
assert!(header.byte_rate == header.sample_rate * header.block_align as u32); | |
assert!(header.block_align == header.number_of_channels * header.bits_per_sample / 8); | |
assert!(header.subchunk2_id == "data"); | |
assert!(header.subchunk2_size == header.chunk_size - (header_buf.len() as u32 - 8)); | |
assert!(header.bits_per_sample == 16, "For now, assume 16 bits per sample"); | |
Ok(header) | |
} | |
} | |
impl<'a> fmt::Display for Header<'a> { | |
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
write!(f, "{} file of {} bytes (channels: {}, sample rate: {}, bits per sample: {})", | |
self.format, | |
self.chunk_size + 8, | |
self.number_of_channels, | |
self.sample_rate, | |
self.bits_per_sample | |
) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
mod buffer_utils; | |
mod header; | |
use std::fs::File; | |
use std::io; | |
use std::io::prelude::*; | |
use std::str; | |
use buffer_utils::read_u16_from_buffer; | |
use header::Header; | |
fn main() -> io::Result<()> { | |
process("sample.wav") | |
} | |
fn process(file_name: &'static str) -> io::Result<()> { | |
let mut file = File::open(file_name)?; | |
let mut header_buf = [0u8; 44]; | |
let bytes_read = file.read(&mut header_buf)?; | |
if bytes_read != header_buf.len() { | |
return parse_error("WAV file does not contain a valid header"); | |
} | |
let header = parse_header(&header_buf)?; | |
println!("{}", header); | |
let mut buffer = [0u8; 4096]; | |
let mut channel: u16 = 0; | |
while parse_buffer(&mut file, &mut buffer, &mut channel, &header)? {} | |
Ok(()) | |
} | |
fn parse_header<'a>(header_buf: &'a [u8]) -> io::Result<Header<'a>> { | |
match Header::parse_from(&header_buf) { | |
Ok(header) => Ok(header), | |
Err(err) => parse_error(err) | |
} | |
} | |
fn parse_error<T>(msg: &str) -> io::Result<T> { | |
Err(io::Error::new(io::ErrorKind::InvalidData, msg)) | |
} | |
fn parse_buffer(file: &mut File, buffer: &mut [u8], channel: &mut u16, header: &Header) -> io::Result<bool> { | |
let bytes_per_sample = (header.bits_per_sample/8) as usize; | |
let bytes_read = file.read(buffer)?; | |
let mut i = 0; | |
while i < bytes_read { | |
*channel = (*channel + 1) % header.number_of_channels; | |
let sample = read_u16_from_buffer(&buffer, i); | |
println!("{}:{}", channel, sample); | |
i += bytes_per_sample; | |
} | |
Ok(bytes_read == buffer.len()) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment