1use std::fmt;
2use std::fs::File;
3use std::io::{self, BufWriter, Seek, SeekFrom, Write};
4use std::path::Path;
5
6use crate::sample::Codec;
7use crate::{
8 Error, Result, Sample, SampleFormat, WavSpec, WavSpecEx, GUID_IEEE_FLOAT, GUID_PCM,
9 TAG_EXTENSIBLE, TAG_IEEE_FLOAT, TAG_PCM,
10};
11
12const BLOCK: usize = 64 * 1024;
14
15pub struct WavWriter<W: Write + Seek> {
37 inner: W,
38 spec: WavSpecEx,
39 codec: Codec,
40 block: Vec<u8>,
41 data_len: u64,
43 max_data: u64,
45 base: u64,
47 fact_pos: Option<u64>,
49 data_size_pos: u64,
51 data_start: u64,
53 finalized: bool,
54}
55
56impl WavWriter<BufWriter<File>> {
57 pub fn create<P: AsRef<Path>>(path: P, spec: WavSpec) -> Result<Self> {
59 let file = File::create(path)?;
60 WavWriter::new(BufWriter::new(file), spec)
61 }
62}
63
64impl<W: Write + Seek> WavWriter<W> {
65 pub fn new(writer: W, spec: WavSpec) -> Result<Self> {
68 if spec.bits_per_sample == 0 {
69 return Err(Error::Format("zero bits per sample"));
70 }
71 let bits = u32::from(spec.bits_per_sample);
72 match spec.sample_format {
73 SampleFormat::Int => {
74 if bits > 32 {
75 return Err(Error::TooWide);
76 }
77 }
78 SampleFormat::Float => {
79 if bits != 32 && bits != 64 {
80 return Err(Error::Unsupported(
84 "float samples other than 32-bit and 64-bit",
85 ));
86 }
87 }
88 }
89 let bytes_per_sample = match spec.sample_format {
90 SampleFormat::Int => ((bits + 7) / 8) as u16,
91 SampleFormat::Float => (bits / 8) as u16,
92 };
93 WavWriter::new_with_spec_ex(
94 writer,
95 WavSpecEx {
96 spec,
97 bytes_per_sample,
98 },
99 )
100 }
101
102 pub fn new_with_spec_ex(mut writer: W, spec: WavSpecEx) -> Result<Self> {
105 let codec = validate(&spec)?;
106 let channels = spec.spec.channels;
107 let container = spec.bytes_per_sample;
108 let bits = spec.spec.bits_per_sample;
109 let container_bits = container * 8;
110 let block_align = u32::from(channels) * u32::from(container);
111 if block_align > u32::from(u16::MAX) {
112 return Err(Error::Unsupported("block align does not fit in 16 bits"));
113 }
114 let byte_rate = match spec.spec.sample_rate.checked_mul(block_align) {
115 Some(rate) => rate,
116 None => return Err(Error::Unsupported("byte rate does not fit in 32 bits")),
117 };
118 let is_float = spec.spec.sample_format == SampleFormat::Float;
119 let extensible = container_bits != bits || channels > 2;
120
121 let mut header = Vec::with_capacity(80);
122 header.extend_from_slice(b"RIFF");
123 header.extend_from_slice(&0u32.to_le_bytes());
124 header.extend_from_slice(b"WAVE");
125 header.extend_from_slice(b"fmt ");
126 let (tag, fmt_len, fmt_bits) = if extensible {
127 (TAG_EXTENSIBLE, 40u32, container_bits)
128 } else if is_float {
129 (TAG_IEEE_FLOAT, 18u32, bits)
130 } else {
131 (TAG_PCM, 16u32, bits)
132 };
133 header.extend_from_slice(&fmt_len.to_le_bytes());
134 header.extend_from_slice(&tag.to_le_bytes());
135 header.extend_from_slice(&channels.to_le_bytes());
136 header.extend_from_slice(&spec.spec.sample_rate.to_le_bytes());
137 header.extend_from_slice(&byte_rate.to_le_bytes());
138 header.extend_from_slice(&(block_align as u16).to_le_bytes());
139 header.extend_from_slice(&fmt_bits.to_le_bytes());
140 if extensible {
141 header.extend_from_slice(&22u16.to_le_bytes());
142 header.extend_from_slice(&bits.to_le_bytes());
143 header.extend_from_slice(&0u32.to_le_bytes());
144 header.extend_from_slice(if is_float {
145 &GUID_IEEE_FLOAT
146 } else {
147 &GUID_PCM
148 });
149 } else if is_float {
150 header.extend_from_slice(&0u16.to_le_bytes());
151 }
152 let fact_pos = if is_float {
153 header.extend_from_slice(b"fact");
154 header.extend_from_slice(&4u32.to_le_bytes());
155 let pos = header.len() as u64;
156 header.extend_from_slice(&0u32.to_le_bytes());
157 Some(pos)
158 } else {
159 None
160 };
161 header.extend_from_slice(b"data");
162 let data_size_pos = header.len() as u64;
163 header.extend_from_slice(&0u32.to_le_bytes());
164 let data_start = header.len() as u64;
165
166 let max_data = (u64::from(u32::MAX) + 7 - data_start).min(u64::from(u32::MAX));
169
170 let base = writer.stream_position()?;
171 writer.write_all(&header)?;
172
173 Ok(WavWriter {
174 inner: writer,
175 spec,
176 codec,
177 block: Vec::with_capacity(BLOCK),
178 data_len: 0,
179 max_data,
180 base,
181 fact_pos,
182 data_size_pos,
183 data_start,
184 finalized: false,
185 })
186 }
187
188 #[inline]
194 pub fn write_sample<S: Sample>(&mut self, sample: S) -> Result<()> {
195 self.check_write::<S>()?;
196 let width = self.codec.width() as u64;
197 if self.data_len + width > self.max_data {
198 return Err(Error::TooLarge);
199 }
200 if self.block.len() + self.codec.width() > BLOCK {
201 self.flush_block()?;
202 }
203 <S as crate::sample::SampleOps>::encode(sample, &self.codec, &mut self.block)?;
204 self.data_len += width;
205 Ok(())
206 }
207
208 pub fn write_samples<S: Sample>(&mut self, samples: &[S]) -> Result<()> {
213 self.check_write::<S>()?;
214 let width = self.codec.width();
215 let total = samples.len() as u64 * width as u64;
216 if self.data_len + total > self.max_data {
217 return Err(Error::TooLarge);
218 }
219 for &sample in samples {
220 if self.block.len() + width > BLOCK {
221 self.flush_block()?;
222 }
223 <S as crate::sample::SampleOps>::encode(sample, &self.codec, &mut self.block)?;
224 self.data_len += width as u64;
225 }
226 Ok(())
227 }
228
229 pub fn flush(&mut self) -> Result<()> {
232 self.flush_block()?;
233 self.inner.flush()?;
234 Ok(())
235 }
236
237 pub fn finalize(mut self) -> Result<()> {
249 self.finalize_inner()
250 }
251
252 pub fn duration(&self) -> u32 {
254 self.len() / u32::from(self.spec.spec.channels)
255 }
256
257 pub fn len(&self) -> u32 {
259 (self.data_len / self.codec.width() as u64) as u32
260 }
261
262 pub fn is_empty(&self) -> bool {
264 self.data_len == 0
265 }
266
267 pub fn spec(&self) -> WavSpec {
269 self.spec.spec
270 }
271
272 pub fn spec_ex(&self) -> WavSpecEx {
274 self.spec
275 }
276
277 fn check_write<S: Sample>(&self) -> Result<()> {
278 if <S as crate::sample::SampleOps>::accepts_write(&self.codec) {
279 Ok(())
280 } else {
281 Err(Error::TooWide)
282 }
283 }
284
285 fn flush_block(&mut self) -> Result<()> {
286 if !self.block.is_empty() {
287 self.inner.write_all(&self.block)?;
288 self.block.clear();
289 }
290 Ok(())
291 }
292
293 fn finalize_inner(&mut self) -> Result<()> {
294 self.finalized = true;
295 self.flush_block()?;
296 let block_align = u64::from(self.block_align());
297 let unfinished = self.data_len % block_align != 0;
298 if unfinished {
299 self.data_len -= self.data_len % block_align;
300 self.inner
301 .seek(SeekFrom::Start(self.base + self.data_start + self.data_len))?;
302 }
303 let pad = self.data_len & 1;
304 if pad == 1 {
305 self.inner.write_all(&[0])?;
306 }
307 let file_len = self.data_start + self.data_len + pad;
308 let riff_size = (file_len - 8) as u32;
309 let data_size = self.data_len as u32;
310 self.patch(4, riff_size)?;
311 self.patch(self.data_size_pos, data_size)?;
312 if let Some(pos) = self.fact_pos {
313 let frames = (self.data_len / block_align) as u32;
314 self.patch(pos, frames)?;
315 }
316 self.inner.seek(SeekFrom::Start(self.base + file_len))?;
317 self.inner.flush()?;
318 if unfinished {
319 return Err(Error::UnfinishedFrame);
320 }
321 Ok(())
322 }
323
324 fn patch(&mut self, offset: u64, value: u32) -> io::Result<()> {
325 self.inner.seek(SeekFrom::Start(self.base + offset))?;
326 self.inner.write_all(&value.to_le_bytes())
327 }
328
329 fn block_align(&self) -> u32 {
330 u32::from(self.spec.spec.channels) * u32::from(self.spec.bytes_per_sample)
331 }
332}
333
334impl<W: Write + Seek> Drop for WavWriter<W> {
335 fn drop(&mut self) {
336 if !self.finalized {
337 let _ = self.finalize_inner();
338 }
339 }
340}
341
342impl<W: Write + Seek> fmt::Debug for WavWriter<W> {
343 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
344 f.debug_struct("WavWriter")
345 .field("spec", &self.spec)
346 .field("data_len", &self.data_len)
347 .field("finalized", &self.finalized)
348 .finish_non_exhaustive()
349 }
350}
351
352fn validate(spec: &WavSpecEx) -> Result<Codec> {
353 let s = spec.spec;
354 let container = spec.bytes_per_sample;
355 if s.channels == 0 {
356 return Err(Error::Format("zero channels"));
357 }
358 if s.sample_rate == 0 {
359 return Err(Error::Format("zero sample rate"));
360 }
361 if s.bits_per_sample == 0 {
362 return Err(Error::Format("zero bits per sample"));
363 }
364 if container == 0 {
365 return Err(Error::Format("zero bytes per sample"));
366 }
367 match s.sample_format {
368 SampleFormat::Int => {
369 if s.bits_per_sample > 32 {
370 return Err(Error::TooWide);
371 }
372 if container > 4 {
373 return Err(Error::Unsupported("PCM containers wider than 4 bytes"));
374 }
375 if s.bits_per_sample > container * 8 {
376 return Err(Error::Format("bits per sample exceed the container width"));
377 }
378 if container == 1 && s.bits_per_sample != 8 {
379 return Err(Error::Format("8-bit PCM must declare 8 valid bits"));
380 }
381 }
382 SampleFormat::Float => {
383 let ok = (s.bits_per_sample == 32 && container == 4)
384 || (s.bits_per_sample == 64 && container == 8);
385 if !ok {
386 return Err(Error::Unsupported(
387 "float samples other than 32-bit and 64-bit",
388 ));
389 }
390 }
391 }
392 Ok(Codec::new(s.sample_format, s.bits_per_sample, container))
393}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398 use std::io::Cursor;
399
400 fn spec16() -> WavSpec {
401 WavSpec {
402 channels: 1,
403 sample_rate: 8000,
404 bits_per_sample: 16,
405 sample_format: SampleFormat::Int,
406 }
407 }
408
409 #[test]
410 fn too_large_fires_before_the_size_fields_wrap() {
411 let mut w = WavWriter::new(Cursor::new(Vec::new()), spec16()).unwrap();
412 assert_eq!(w.max_data, u64::from(u32::MAX) + 7 - 44);
414 w.data_len = w.max_data - 2;
415 w.write_sample(1i16).unwrap();
416 assert!(matches!(w.write_sample(1i16), Err(Error::TooLarge)));
417 assert!(matches!(w.write_samples(&[1i16]), Err(Error::TooLarge)));
418 assert!(matches!(w.write_samples::<i16>(&[]), Ok(())));
419 w.finalized = true;
421 }
422
423 #[test]
424 fn max_data_accounts_for_the_fact_chunk() {
425 let spec = WavSpec {
426 sample_format: SampleFormat::Float,
427 bits_per_sample: 32,
428 ..spec16()
429 };
430 let w = WavWriter::new(Cursor::new(Vec::new()), spec).unwrap();
431 assert_eq!(w.data_start, 58);
432 assert_eq!(w.max_data, u64::from(u32::MAX) + 7 - 58);
433 }
434}