lc3_codec/common/
wav.rs

1// TODO: move this to its own crate as it is incomplete and does not belong here
2
3use byteorder::{ByteOrder, LittleEndian};
4use core::str;
5
6// see http://tiny.systems/software/soundProgrammer/WavFormatDocs.pdf
7
8#[derive(Debug)]
9pub enum WavError {
10    WriteHeaderBufferTooSmall,
11    ReadHeaderInvalidHeaderLength,
12    ReadHeaderChunkIdNotRIFF,
13    ReadHeaderFormatNotWAVE,
14    ReadHeaderSubChunk1IdNotFmt,
15    ReadHeaderInvalidPcmHeaderLength,
16    ReadHeaderAudioFormatNotPcm,
17    ReadHeaderMissingDataSection,
18}
19
20pub const RIFF_HEADER_ONLY_LEN: usize = 8;
21pub const FULL_WAV_HEADER_LEN: usize = 44;
22
23const PCM_HEADER_LENGTH: u32 = 16;
24const AUDIO_FORMAT_PCM: u16 = 1;
25
26#[derive(Debug)]
27pub struct WavHeader {
28    // chunk_id is always "RIFF"
29    // chunk_size: u32, //  This is the size of the entire file in bytes minus 8 bytes for the two fields not included in this count
30    // format is always "WAVE"
31    // subchunk1_id is always "fmt "
32    // subchunk1_size: u32, // 16 for PCM
33    // audio_format: u16, // 1 for PCM
34    pub num_channels: usize,    // Mono = 1, Stereo = 2, etc.
35    pub sample_rate: usize,     // e.g. 44100
36    pub byte_rate: usize,       // SampleRate * NumChannels * BitsPerSample/8
37    pub block_align: usize,     // NumChannels * BitsPerSample/8
38    pub bits_per_sample: usize, // 8 bits = 8, 16 bits = 16, etc.
39    // subchunk2_id is always "data"
40    pub data_size: usize,             // NumSamples * NumChannels * BitsPerSample/8
41    pub data_start_position: usize,   // the position of the first byte of data
42    pub data_with_header_size: usize, // num bytes of the entire file excluding the first 8 bytes
43}
44
45pub fn write_header(header: &WavHeader, buf: &mut [u8]) -> Result<usize, WavError> {
46    if buf.len() < FULL_WAV_HEADER_LEN {
47        return Err(WavError::WriteHeaderBufferTooSmall);
48    }
49
50    buf[..4].copy_from_slice(b"RIFF"); // chunk ID
51    LittleEndian::write_u32(&mut buf[4..8], header.data_with_header_size as u32); // length of entire file below this line
52    buf[8..12].copy_from_slice(b"WAVE"); // format
53    buf[12..16].copy_from_slice(b"fmt "); // subchunk1 ID
54    LittleEndian::write_u32(&mut buf[16..20], PCM_HEADER_LENGTH); // pcm header length - subchunk1 size
55    LittleEndian::write_u16(&mut buf[20..22], AUDIO_FORMAT_PCM); // pcm audio format - 1
56    LittleEndian::write_u16(&mut buf[22..24], header.num_channels as u16);
57    LittleEndian::write_u32(&mut buf[24..28], header.sample_rate as u32);
58    LittleEndian::write_u32(&mut buf[28..32], header.byte_rate as u32);
59    LittleEndian::write_u16(&mut buf[32..34], header.block_align as u16);
60    LittleEndian::write_u16(&mut buf[34..36], header.bits_per_sample as u16);
61    buf[36..40].copy_from_slice(b"data"); // subchunk2 ID
62    LittleEndian::write_u32(&mut buf[40..44], header.data_size as u32);
63
64    Ok(FULL_WAV_HEADER_LEN)
65}
66
67pub fn read_header(buf: &[u8]) -> Result<WavHeader, WavError> {
68    if buf.len() < FULL_WAV_HEADER_LEN {
69        return Err(WavError::ReadHeaderInvalidHeaderLength);
70    }
71
72    if str::from_utf8(&buf[..4]).map_err(|_| WavError::ReadHeaderChunkIdNotRIFF)? != "RIFF" {
73        return Err(WavError::ReadHeaderChunkIdNotRIFF);
74    }
75
76    if str::from_utf8(&buf[8..12]).map_err(|_| WavError::ReadHeaderFormatNotWAVE)? != "WAVE" {
77        return Err(WavError::ReadHeaderFormatNotWAVE);
78    }
79
80    if str::from_utf8(&buf[12..16]).map_err(|_| WavError::ReadHeaderSubChunk1IdNotFmt)? != "fmt " {
81        return Err(WavError::ReadHeaderSubChunk1IdNotFmt);
82    }
83
84    if LittleEndian::read_u32(&buf[16..20]) != 16 {
85        return Err(WavError::ReadHeaderInvalidPcmHeaderLength);
86    }
87
88    if LittleEndian::read_u16(&buf[20..22]) != 1 {
89        return Err(WavError::ReadHeaderAudioFormatNotPcm);
90    }
91
92    let data_with_header_size = LittleEndian::read_u32(&buf[4..8]) as usize;
93    let num_channels = LittleEndian::read_u16(&buf[22..24]) as usize;
94    let sample_rate = LittleEndian::read_u32(&buf[24..28]) as usize;
95    let byte_rate = LittleEndian::read_u32(&buf[28..32]) as usize;
96    let block_align = LittleEndian::read_u16(&buf[32..34]) as usize;
97    let bits_per_sample = LittleEndian::read_u16(&buf[34..36]) as usize;
98
99    let section = str::from_utf8(&buf[36..40]).map_err(|_| WavError::ReadHeaderMissingDataSection)?;
100    let (data_size, data_start_position) = match section {
101        "data" => (LittleEndian::read_u32(&buf[40..44]) as usize, FULL_WAV_HEADER_LEN),
102        "LIST" => {
103            let size = LittleEndian::read_u32(&buf[40..44]) as usize;
104            LittleEndian::read_u32(&buf[40..44]); // list type id (ignore)
105            (size, FULL_WAV_HEADER_LEN + 4)
106        }
107        _section => {
108            //  warn!("Unknown section: {}", section);
109            return Err(WavError::ReadHeaderMissingDataSection);
110        }
111    };
112
113    Ok(WavHeader {
114        data_with_header_size,
115        num_channels,
116        sample_rate,
117        byte_rate,
118        block_align,
119        bits_per_sample,
120        data_size,
121        data_start_position,
122    })
123}
124
125#[cfg(test)]
126mod tests {
127    extern crate std;
128    use super::*;
129
130    #[test]
131    fn can_read_pcm_wav_header() {
132        let buffer = [
133            0x52, 0x49, 0x46, 0x46, 0x16, 0x29, 0x0B, 0x00, 0x57, 0x41, 0x56, 0x45, 0x66, 0x6D, 0x74, 0x20, 0x10, 0x00,
134            0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x44, 0xAC, 0x00, 0x00, 0x10, 0xB1, 0x02, 0x00, 0x04, 0x00, 0x10, 0x00,
135            0x64, 0x61, 0x74, 0x61, 0x70, 0x28, 0x0B, 0x00, 0x00,
136        ];
137
138        let header = read_header(&buffer).unwrap();
139
140        assert_eq!(header.num_channels, 2);
141        assert_eq!(header.sample_rate, 44100);
142        assert_eq!(header.byte_rate, 176400);
143        assert_eq!(header.block_align, 4);
144        assert_eq!(header.bits_per_sample, 16);
145        assert_eq!(header.data_size, 731248);
146        assert_eq!(header.data_start_position, 44);
147    }
148}