Merge branch 'master' of https://github.com/Wuelle/bwavfile into Wuelle-master

This commit is contained in:
Jamie Hardt
2021-12-31 13:28:55 -08:00
17 changed files with 1350 additions and 1050 deletions

View File

@@ -1,15 +1,13 @@
pub type LU = f32;
pub type LUFS = f32;
pub type Decibels = f32;
/// Broadcast-WAV metadata record.
///
/// The `bext` record contains information about the original recording of the
/// Wave file, including a longish (256 ASCII chars) description field,
/// originator identification fields, creation calendar date and time, a
/// sample-accurate recording time field, and a SMPTE UMID.
/// The `bext` record contains information about the original recording of the
/// Wave file, including a longish (256 ASCII chars) description field,
/// originator identification fields, creation calendar date and time, a
/// sample-accurate recording time field, and a SMPTE UMID.
///
/// For a Wave file to be a complaint "Broadcast-WAV" file, it must contain
/// a `bext` metadata record.
@@ -17,7 +15,7 @@ pub type Decibels = f32;
/// ## Resources
/// - [EBU Tech 3285](https://tech.ebu.ch/docs/tech/tech3285.pdf).
/// - [EBU Tech R098](https://tech.ebu.ch/docs/r/r098.pdf) (1999) "Format for the <CodingHistory> field in Broadcast Wave Format files, BWF"
/// - [EBU Tech R099](https://tech.ebu.ch/docs/r/r099.pdf) (October 2011) "Unique Source Identifier (USID) for use in the
/// - [EBU Tech R099](https://tech.ebu.ch/docs/r/r099.pdf) (October 2011) "Unique Source Identifier (USID) for use in the
/// <OriginatorReference> field of the Broadcast Wave Format"
// Note for me later:
@@ -26,7 +24,6 @@ pub type Decibels = f32;
#[derive(Debug)]
pub struct Bext {
/// 256 ASCII character field with free text.
pub description: String,
@@ -47,23 +44,23 @@ pub struct Bext {
pub time_reference: u64,
/// Bext chunk version.
///
/// Version 1 contains a UMID, version 2 contains a UMID and
///
/// Version 1 contains a UMID, version 2 contains a UMID and
/// loudness metadata.
pub version: u16,
/// SMPTE 330M UMID
///
///
/// This field is `None` if the version is less than 1.
pub umid: Option<[u8; 64]>,
/// Integrated loudness in LUFS.
///
///
/// This field is `None` if the version is less than 2.
pub loudness_value: Option<LUFS>,
/// Loudness range in LU.
///
///
/// This field is `None` if the version is less than 2.
pub loudness_range: Option<LU>,
@@ -78,11 +75,10 @@ pub struct Bext {
pub max_momentary_loudness: Option<LUFS>,
/// Maximum short-term loudness in LUFS.
///
///
/// This field is `None` if the version is less than 2.
pub max_short_term_loudness: Option<LUFS>,
// 180 bytes of nothing
/// Coding History.
pub coding_history: String
pub coding_history: String,
}

View File

@@ -1,33 +1,40 @@
use std::io::{Read, Write};
use encoding::{DecoderTrap, EncoderTrap};
use encoding::{Encoding};
use encoding::all::ASCII;
use encoding::Encoding;
use encoding::{DecoderTrap, EncoderTrap};
use byteorder::LittleEndian;
use byteorder::{ReadBytesExt, WriteBytesExt};
use uuid::Uuid;
use super::bext::Bext;
use super::errors::Error as ParserError;
use super::fmt::{WaveFmt, WaveFmtExtended};
use super::bext::Bext;
pub trait ReadBWaveChunks: Read {
fn read_bext(&mut self) -> Result<Bext, ParserError>;
fn read_bext_string_field(&mut self, length: usize) -> Result<String,ParserError>;
fn read_bext_string_field(&mut self, length: usize) -> Result<String, ParserError>;
fn read_wave_fmt(&mut self) -> Result<WaveFmt, ParserError>;
}
pub trait WriteBWaveChunks: Write {
fn write_wave_fmt(&mut self, format : &WaveFmt) -> Result<(), ParserError>;
fn write_bext_string_field(&mut self, string: &String, length: usize) -> Result<(),ParserError>;
fn write_bext(&mut self, bext: &Bext) -> Result<(),ParserError>;
fn write_wave_fmt(&mut self, format: &WaveFmt) -> Result<(), ParserError>;
fn write_bext_string_field(
&mut self,
string: &String,
length: usize,
) -> Result<(), ParserError>;
fn write_bext(&mut self, bext: &Bext) -> Result<(), ParserError>;
}
impl<T> WriteBWaveChunks for T where T: Write {
fn write_wave_fmt(&mut self, format : &WaveFmt) -> Result<(), ParserError> {
self.write_u16::<LittleEndian>(format.tag as u16 )?;
impl<T> WriteBWaveChunks for T
where
T: Write,
{
fn write_wave_fmt(&mut self, format: &WaveFmt) -> Result<(), ParserError> {
self.write_u16::<LittleEndian>(format.tag as u16)?;
self.write_u16::<LittleEndian>(format.channel_count)?;
self.write_u32::<LittleEndian>(format.sample_rate)?;
self.write_u32::<LittleEndian>(format.bytes_per_second)?;
@@ -44,12 +51,18 @@ impl<T> WriteBWaveChunks for T where T: Write {
Ok(())
}
fn write_bext_string_field(&mut self, string: &String, length: usize) -> Result<(),ParserError> {
let mut buf = ASCII.encode(&string, EncoderTrap::Ignore).expect("Error encoding text");
fn write_bext_string_field(
&mut self,
string: &String,
length: usize,
) -> Result<(), ParserError> {
let mut buf = ASCII
.encode(&string, EncoderTrap::Ignore)
.expect("Error encoding text");
buf.truncate(length);
let filler_length = length - buf.len();
if filler_length > 0{
let mut filler = vec![0u8; filler_length ];
if filler_length > 0 {
let mut filler = vec![0u8; filler_length];
buf.append(&mut filler);
}
@@ -57,7 +70,7 @@ impl<T> WriteBWaveChunks for T where T: Write {
Ok(())
}
fn write_bext(&mut self, bext: &Bext) -> Result<(),ParserError> {
fn write_bext(&mut self, bext: &Bext) -> Result<(), ParserError> {
self.write_bext_string_field(&bext.description, 256)?;
self.write_bext_string_field(&bext.originator, 32)?;
self.write_bext_string_field(&bext.originator_reference, 32)?;
@@ -69,21 +82,21 @@ impl<T> WriteBWaveChunks for T where T: Write {
let buf = bext.umid.unwrap_or([0u8; 64]);
self.write_all(&buf)?;
self.write_i16::<LittleEndian>(
(bext.loudness_value.unwrap_or(0.0) * 100.0) as i16 )?;
self.write_i16::<LittleEndian>(
(bext.loudness_range.unwrap_or(0.0) * 100.0) as i16 )?;
self.write_i16::<LittleEndian>(
(bext.max_true_peak_level.unwrap_or(0.0) * 100.0) as i16 )?;
self.write_i16::<LittleEndian>(
(bext.max_momentary_loudness.unwrap_or(0.0) * 100.0) as i16 )?;
self.write_i16::<LittleEndian>(
(bext.max_short_term_loudness.unwrap_or(0.0) * 100.0) as i16 )?;
self.write_i16::<LittleEndian>((bext.loudness_value.unwrap_or(0.0) * 100.0) as i16)?;
self.write_i16::<LittleEndian>((bext.loudness_range.unwrap_or(0.0) * 100.0) as i16)?;
self.write_i16::<LittleEndian>((bext.max_true_peak_level.unwrap_or(0.0) * 100.0) as i16)?;
self.write_i16::<LittleEndian>(
(bext.max_momentary_loudness.unwrap_or(0.0) * 100.0) as i16,
)?;
self.write_i16::<LittleEndian>(
(bext.max_short_term_loudness.unwrap_or(0.0) * 100.0) as i16,
)?;
let padding = [0u8; 180];
self.write_all(&padding)?;
let coding = ASCII.encode(&bext.coding_history, EncoderTrap::Ignore)
let coding = ASCII
.encode(&bext.coding_history, EncoderTrap::Ignore)
.expect("Error");
self.write_all(&coding)?;
@@ -91,20 +104,22 @@ impl<T> WriteBWaveChunks for T where T: Write {
}
}
impl<T> ReadBWaveChunks for T where T: Read {
impl<T> ReadBWaveChunks for T
where
T: Read,
{
fn read_wave_fmt(&mut self) -> Result<WaveFmt, ParserError> {
let tag_value : u16;
let tag_value: u16;
Ok(WaveFmt {
tag: {
tag_value = self.read_u16::<LittleEndian>()?;
tag_value
},
channel_count: self.read_u16::<LittleEndian>()?,
sample_rate: self.read_u32::<LittleEndian>()?,
bytes_per_second: self.read_u32::<LittleEndian>()?,
block_alignment: self.read_u16::<LittleEndian>()?,
bits_per_sample: self.read_u16::<LittleEndian>()?,
channel_count: self.read_u16::<LittleEndian>()?,
sample_rate: self.read_u32::<LittleEndian>()?,
bytes_per_second: self.read_u32::<LittleEndian>()?,
block_alignment: self.read_u16::<LittleEndian>()?,
bits_per_sample: self.read_u16::<LittleEndian>()?,
extended_format: {
if tag_value == 0xFFFE {
let cb_size = self.read_u16::<LittleEndian>()?;
@@ -113,77 +128,111 @@ impl<T> ReadBWaveChunks for T where T: Read {
valid_bits_per_sample: self.read_u16::<LittleEndian>()?,
channel_mask: self.read_u32::<LittleEndian>()?,
type_guid: {
let mut buf : [u8; 16] = [0; 16];
let mut buf: [u8; 16] = [0; 16];
self.read_exact(&mut buf)?;
Uuid::from_slice(&buf)?
}
},
})
} else {
None
}
}
},
})
}
fn read_bext_string_field(&mut self, length: usize) -> Result<String,ParserError> {
let mut buffer : Vec<u8> = vec![0; length];
fn read_bext_string_field(&mut self, length: usize) -> Result<String, ParserError> {
let mut buffer: Vec<u8> = vec![0; length];
self.read(&mut buffer)?;
let trimmed : Vec<u8> = buffer.iter().take_while(|c| **c != 0 as u8).cloned().collect();
Ok(ASCII.decode(&trimmed, DecoderTrap::Ignore).expect("Error decoding text"))
let trimmed: Vec<u8> = buffer
.iter()
.take_while(|c| **c != 0 as u8)
.cloned()
.collect();
Ok(ASCII
.decode(&trimmed, DecoderTrap::Ignore)
.expect("Error decoding text"))
}
fn read_bext(&mut self) -> Result<Bext, ParserError> {
let version : u16;
Ok( Bext {
description: self.read_bext_string_field(256)?,
originator: self.read_bext_string_field(32)?,
originator_reference : self.read_bext_string_field(32)?,
origination_date : self.read_bext_string_field(10)?,
origination_time : self.read_bext_string_field(8)?,
time_reference: self.read_u64::<LittleEndian>()?,
version: {
version = self.read_u16::<LittleEndian>()?;
version
},
umid: {
let mut buf = [0u8 ; 64];
self.read(&mut buf)?;
if version > 0 { Some(buf) } else { None }
},
loudness_value: {
let val = (self.read_i16::<LittleEndian>()? as f32) / 100f32;
if version > 1 { Some(val) } else { None }
},
loudness_range: {
let val = self.read_i16::<LittleEndian>()? as f32 / 100f32;
if version > 1 { Some(val) } else { None }
},
max_true_peak_level: {
let val = self.read_i16::<LittleEndian>()? as f32 / 100f32;
if version > 1 { Some(val) } else { None }
},
max_momentary_loudness: {
let val = self.read_i16::<LittleEndian>()? as f32 / 100f32;
if version > 1 { Some(val) } else { None }
},
max_short_term_loudness: {
let val = self.read_i16::<LittleEndian>()? as f32 / 100f32;
if version > 1 { Some(val) } else { None }
},
coding_history: {
for _ in 0..180 { self.read_u8()?; }
let mut buf = vec![];
self.read_to_end(&mut buf)?;
ASCII.decode(&buf, DecoderTrap::Ignore).expect("Error decoding text")
let version: u16;
Ok(Bext {
description: self.read_bext_string_field(256)?,
originator: self.read_bext_string_field(32)?,
originator_reference: self.read_bext_string_field(32)?,
origination_date: self.read_bext_string_field(10)?,
origination_time: self.read_bext_string_field(8)?,
time_reference: self.read_u64::<LittleEndian>()?,
version: {
version = self.read_u16::<LittleEndian>()?;
version
},
umid: {
let mut buf = [0u8; 64];
self.read(&mut buf)?;
if version > 0 {
Some(buf)
} else {
None
}
},
loudness_value: {
let val = (self.read_i16::<LittleEndian>()? as f32) / 100f32;
if version > 1 {
Some(val)
} else {
None
}
},
loudness_range: {
let val = self.read_i16::<LittleEndian>()? as f32 / 100f32;
if version > 1 {
Some(val)
} else {
None
}
},
max_true_peak_level: {
let val = self.read_i16::<LittleEndian>()? as f32 / 100f32;
if version > 1 {
Some(val)
} else {
None
}
},
max_momentary_loudness: {
let val = self.read_i16::<LittleEndian>()? as f32 / 100f32;
if version > 1 {
Some(val)
} else {
None
}
},
max_short_term_loudness: {
let val = self.read_i16::<LittleEndian>()? as f32 / 100f32;
if version > 1 {
Some(val)
} else {
None
}
},
coding_history: {
for _ in 0..180 {
self.read_u8()?;
}
let mut buf = vec![];
self.read_to_end(&mut buf)?;
ASCII
.decode(&buf, DecoderTrap::Ignore)
.expect("Error decoding text")
},
})
}
}
}
#[test]
fn test_read_51_wav() {
use super::fmt::ChannelMask;
use super::common_format::CommonFormat;
use super::fmt::ChannelMask;
let path = "tests/media/pt_24bit_51.wav";
@@ -198,9 +247,17 @@ fn test_read_51_wav() {
let channels = ChannelMask::channels(extended.channel_mask, format.channel_count);
assert_eq!(channels, [ChannelMask::FrontLeft, ChannelMask::FrontRight,
ChannelMask::FrontCenter, ChannelMask::LowFrequency,
ChannelMask::BackLeft, ChannelMask::BackRight]);
assert_eq!(
channels,
[
ChannelMask::FrontLeft,
ChannelMask::FrontRight,
ChannelMask::FrontCenter,
ChannelMask::LowFrequency,
ChannelMask::BackLeft,
ChannelMask::BackRight
]
);
assert_eq!(format.common_format(), CommonFormat::IntegerPCM);
}
}

View File

@@ -1,9 +1,9 @@
use uuid::Uuid;
const BASIC_PCM: u16 = 0x0001;
const BASIC_FLOAT: u16 = 0x0003;
const BASIC_MPEG: u16 = 0x0050;
const BASIC_EXTENDED: u16 = 0xFFFE;
const BASIC_PCM: u16 = 0x0001;
const BASIC_FLOAT: u16 = 0x0003;
const BASIC_MPEG: u16 = 0x0050;
const BASIC_EXTENDED: u16 = 0xFFFE;
/* RC 2361 §4:
@@ -15,50 +15,54 @@ const BASIC_EXTENDED: u16 = 0xFFFE;
*/
pub const UUID_PCM: Uuid = Uuid::from_bytes([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71]);
pub const UUID_PCM: Uuid = Uuid::from_bytes([
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71,
]);
pub const UUID_FLOAT: Uuid = Uuid::from_bytes([0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71]);
pub const UUID_FLOAT: Uuid = Uuid::from_bytes([
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71,
]);
pub const UUID_MPEG: Uuid = Uuid::from_bytes([0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71]);
pub const UUID_MPEG: Uuid = Uuid::from_bytes([
0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71,
]);
pub const UUID_BFORMAT_PCM: Uuid = Uuid::from_bytes([0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11,
0x86, 0x44, 0xc8, 0xc1, 0xca, 0x00, 0x00, 0x00]);
pub const UUID_BFORMAT_FLOAT: Uuid = Uuid::from_bytes([0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11,
0x86, 0x44, 0xc8, 0xc1, 0xca, 0x00, 0x00, 0x00]);
pub const UUID_BFORMAT_PCM: Uuid = Uuid::from_bytes([
0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1, 0xca, 0x00, 0x00, 0x00,
]);
pub const UUID_BFORMAT_FLOAT: Uuid = Uuid::from_bytes([
0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1, 0xca, 0x00, 0x00, 0x00,
]);
fn uuid_from_basic_tag(tag: u16) -> Uuid {
let tail : [u8; 6] = [0x00,0xaa,0x00,0x38,0x9b,0x71];
let tail: [u8; 6] = [0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71];
Uuid::from_fields_le(tag as u32, 0x0000, 0x0010, &tail).unwrap()
}
/// Sample format of the Wave file.
///
///
///
///
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum CommonFormat {
/// Integer linear PCM
IntegerPCM,
/// IEEE Floating-point Linear PCM
IeeeFloatPCM,
/// MPEG
Mpeg,
/// Ambisonic B-Format Linear PCM
AmbisonicBFormatIntegerPCM,
/// Ambisonic B-Format Float PCM
AmbisonicBFormatIeeeFloatPCM,
/// An unknown format identified by a basic format tag.
UnknownBasic(u16),
/// An unknown format identified by an extension UUID.
UnknownExtended(Uuid),
}
@@ -70,18 +74,18 @@ impl CommonFormat {
(BASIC_PCM, _) => Self::IntegerPCM,
(BASIC_FLOAT, _) => Self::IeeeFloatPCM,
(BASIC_MPEG, _) => Self::Mpeg,
(BASIC_EXTENDED, Some(UUID_PCM)) => Self::IntegerPCM,
(BASIC_EXTENDED, Some(UUID_FLOAT))=> Self::IeeeFloatPCM,
(BASIC_EXTENDED, Some(UUID_PCM)) => Self::IntegerPCM,
(BASIC_EXTENDED, Some(UUID_FLOAT)) => Self::IeeeFloatPCM,
(BASIC_EXTENDED, Some(UUID_BFORMAT_PCM)) => Self::AmbisonicBFormatIntegerPCM,
(BASIC_EXTENDED, Some(UUID_BFORMAT_FLOAT)) => Self::AmbisonicBFormatIeeeFloatPCM,
(BASIC_EXTENDED, Some(x)) => CommonFormat::UnknownExtended(x),
(x, _) => CommonFormat::UnknownBasic(x)
(x, _) => CommonFormat::UnknownBasic(x),
}
}
/// Get the appropriate tag and `Uuid` for the callee.
///
/// If there is no appropriate tag for the format of the callee, the
///
/// If there is no appropriate tag for the format of the callee, the
/// returned tag will be 0xFFFE and the `Uuid` will describe the format.
pub fn take(self) -> (u16, Uuid) {
match self {
@@ -90,8 +94,8 @@ impl CommonFormat {
Self::Mpeg => (BASIC_MPEG, UUID_MPEG),
Self::AmbisonicBFormatIntegerPCM => (BASIC_EXTENDED, UUID_BFORMAT_PCM),
Self::AmbisonicBFormatIeeeFloatPCM => (BASIC_EXTENDED, UUID_BFORMAT_FLOAT),
Self::UnknownBasic(x) => ( x, uuid_from_basic_tag(x) ),
Self::UnknownExtended(x) => ( BASIC_EXTENDED, x)
Self::UnknownBasic(x) => (x, uuid_from_basic_tag(x)),
Self::UnknownExtended(x) => (BASIC_EXTENDED, x),
}
}
}

View File

@@ -1,31 +1,31 @@
#![allow(dead_code)]
use super::fourcc::{FourCC,ReadFourCC, WriteFourCC, LABL_SIG, NOTE_SIG,
ADTL_SIG, LTXT_SIG, DATA_SIG};
use super::list_form::collect_list_form;
use byteorder::{WriteBytesExt, ReadBytesExt, LittleEndian};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use encoding::{DecoderTrap,EncoderTrap};
use encoding::{Encoding};
use encoding::all::ASCII;
use encoding::Encoding;
use encoding::{DecoderTrap, EncoderTrap};
use std::io::{Cursor, Error, Read, Write};
#[derive(Copy,Clone, Debug)]
#[derive(Copy, Clone, Debug)]
struct RawCue {
cue_point_id : u32,
frame : u32,
chunk_id : FourCC,
chunk_start : u32,
block_start : u32,
frame_offset : u32
cue_point_id: u32,
frame: u32,
chunk_id: FourCC,
chunk_start: u32,
block_start: u32,
frame_offset: u32,
}
impl RawCue {
fn write_to(cues : Vec<Self>) -> Vec<u8> {
fn write_to(cues: Vec<Self>) -> Vec<u8> {
let mut writer = Cursor::new(vec![0u8; 0]);
writer.write_u32::<LittleEndian>(cues.len() as u32).unwrap();
for cue in cues.iter() {
writer.write_u32::<LittleEndian>(cue.cue_point_id).unwrap();
@@ -39,64 +39,64 @@ impl RawCue {
writer.into_inner()
}
fn read_from(data : &[u8]) -> Result<Vec<Self>,Error> {
fn read_from(data: &[u8]) -> Result<Vec<Self>, Error> {
let mut rdr = Cursor::new(data);
let count = rdr.read_u32::<LittleEndian>()?;
let mut retval : Vec<Self> = vec![];
let mut retval: Vec<Self> = vec![];
for _ in 0..count {
retval.push( Self {
cue_point_id : rdr.read_u32::<LittleEndian>()?,
frame : rdr.read_u32::<LittleEndian>()?,
chunk_id : rdr.read_fourcc()?,
chunk_start : rdr.read_u32::<LittleEndian>()?,
block_start : rdr.read_u32::<LittleEndian>()?,
frame_offset : rdr.read_u32::<LittleEndian>()?
retval.push(Self {
cue_point_id: rdr.read_u32::<LittleEndian>()?,
frame: rdr.read_u32::<LittleEndian>()?,
chunk_id: rdr.read_fourcc()?,
chunk_start: rdr.read_u32::<LittleEndian>()?,
block_start: rdr.read_u32::<LittleEndian>()?,
frame_offset: rdr.read_u32::<LittleEndian>()?,
})
}
Ok( retval )
Ok(retval)
}
}
#[derive(Clone, Debug)]
struct RawLabel {
cue_point_id : u32,
text : Vec<u8>
cue_point_id: u32,
text: Vec<u8>,
}
impl RawLabel {
fn write_to(&self) -> Vec<u8> {
let mut writer = Cursor::new(vec![0u8;0]);
writer.write_u32::<LittleEndian>(self.cue_point_id as u32).unwrap();
let mut writer = Cursor::new(vec![0u8; 0]);
writer
.write_u32::<LittleEndian>(self.cue_point_id as u32)
.unwrap();
writer.write(&self.text).unwrap();
writer.into_inner()
}
fn read_from(data : &[u8]) -> Result<Self, Error> {
fn read_from(data: &[u8]) -> Result<Self, Error> {
let mut rdr = Cursor::new(data);
let length = data.len();
Ok( Self {
cue_point_id : rdr.read_u32::<LittleEndian>()?,
text : {
let mut buf = vec![0u8; (length - 4) as usize ];
Ok(Self {
cue_point_id: rdr.read_u32::<LittleEndian>()?,
text: {
let mut buf = vec![0u8; (length - 4) as usize];
rdr.read_exact(&mut buf)?;
buf
}
},
})
}
}
#[derive(Clone, Debug)]
struct RawNote {
cue_point_id : u32,
text : Vec<u8>
cue_point_id: u32,
text: Vec<u8>,
}
impl RawNote {
fn write_to(&self) -> Vec<u8> {
let mut writer = Cursor::new(vec![0u8; 0]);
writer.write_u32::<LittleEndian>(self.cue_point_id).unwrap();
@@ -104,35 +104,34 @@ impl RawNote {
writer.into_inner()
}
fn read_from(data : &[u8]) -> Result<Self, Error> {
fn read_from(data: &[u8]) -> Result<Self, Error> {
let mut rdr = Cursor::new(data);
let length = data.len();
Ok( Self {
cue_point_id : rdr.read_u32::<LittleEndian>()?,
text : {
let mut buf = vec![0u8; (length - 4) as usize ];
Ok(Self {
cue_point_id: rdr.read_u32::<LittleEndian>()?,
text: {
let mut buf = vec![0u8; (length - 4) as usize];
rdr.read_exact(&mut buf)?;
buf
}
},
})
}
}
#[derive(Clone, Debug)]
struct RawLtxt {
cue_point_id : u32,
frame_length : u32,
purpose : FourCC,
country : u16,
language : u16,
dialect : u16,
code_page : u16,
text: Option<Vec<u8>>
struct RawLtxt {
cue_point_id: u32,
frame_length: u32,
purpose: FourCC,
country: u16,
language: u16,
dialect: u16,
code_page: u16,
text: Option<Vec<u8>>,
}
impl RawLtxt {
fn write_to(&self) -> Vec<u8> {
let mut writer = Cursor::new(vec![0u8; 0]);
writer.write_u32::<LittleEndian>(self.cue_point_id).unwrap();
@@ -148,27 +147,27 @@ impl RawLtxt {
writer.into_inner()
}
fn read_from(data : &[u8]) -> Result<Self, Error> {
fn read_from(data: &[u8]) -> Result<Self, Error> {
let mut rdr = Cursor::new(data);
let length = data.len();
Ok( Self {
cue_point_id : rdr.read_u32::<LittleEndian>()?,
frame_length : rdr.read_u32::<LittleEndian>()?,
purpose : rdr.read_fourcc()?,
country : rdr.read_u16::<LittleEndian>()?,
language : rdr.read_u16::<LittleEndian>()?,
dialect : rdr.read_u16::<LittleEndian>()?,
code_page : rdr.read_u16::<LittleEndian>()?,
text : {
Ok(Self {
cue_point_id: rdr.read_u32::<LittleEndian>()?,
frame_length: rdr.read_u32::<LittleEndian>()?,
purpose: rdr.read_fourcc()?,
country: rdr.read_u16::<LittleEndian>()?,
language: rdr.read_u16::<LittleEndian>()?,
dialect: rdr.read_u16::<LittleEndian>()?,
code_page: rdr.read_u16::<LittleEndian>()?,
text: {
if length - 20 > 0 {
let mut buf = vec![0u8; (length - 20) as usize];
rdr.read_exact(&mut buf)?;
Some( buf )
Some(buf)
} else {
None
}
}
},
})
}
}
@@ -178,223 +177,227 @@ enum RawAdtlMember {
Label(RawLabel),
Note(RawNote),
LabeledText(RawLtxt),
Unrecognized(FourCC)
Unrecognized(FourCC),
}
impl RawAdtlMember {
fn compile_adtl(members : &[Self]) -> Vec<u8> {
fn compile_adtl(members: &[Self]) -> Vec<u8> {
let mut w = Cursor::new(vec![0u8; 0]);
// It seems like all this casing could be done with traits
for member in members.iter() {
let (fcc, buf) = match member {
RawAdtlMember::Label(l) => ( (LABL_SIG, l.write_to()) ),
RawAdtlMember::Note(n) => ( (NOTE_SIG, n.write_to()) ),
RawAdtlMember::LabeledText(t) => ( (LTXT_SIG, t.write_to()) ),
RawAdtlMember::Unrecognized(f) => (*f, vec![0u8;0] ) // <-- this is a dopey case but here for completeness
RawAdtlMember::Label(l) => ((LABL_SIG, l.write_to())),
RawAdtlMember::Note(n) => ((NOTE_SIG, n.write_to())),
RawAdtlMember::LabeledText(t) => ((LTXT_SIG, t.write_to())),
RawAdtlMember::Unrecognized(f) => (*f, vec![0u8; 0]), // <-- this is a dopey case but here for completeness
};
w.write_fourcc(fcc).unwrap();
w.write_u32::<LittleEndian>(buf.len() as u32).unwrap();
w.write(&buf).unwrap();
if buf.len() % 2 == 1 { w.write_u8(0).unwrap(); }
if buf.len() % 2 == 1 {
w.write_u8(0).unwrap();
}
}
let chunk_content = w.into_inner();
let mut writer = Cursor::new(vec![0u8; 0]);
writer.write_fourcc(ADTL_SIG).unwrap();
writer.write_u32::<LittleEndian>(chunk_content.len() as u32).unwrap();
writer
.write_u32::<LittleEndian>(chunk_content.len() as u32)
.unwrap();
writer.write(&chunk_content).unwrap();
writer.into_inner()
}
fn collect_from(chunk : &[u8]) -> Result<Vec<RawAdtlMember>,Error> {
fn collect_from(chunk: &[u8]) -> Result<Vec<RawAdtlMember>, Error> {
let chunks = collect_list_form(chunk)?;
let mut retval : Vec<RawAdtlMember> = vec![];
let mut retval: Vec<RawAdtlMember> = vec![];
for chunk in chunks.iter() {
retval.push(
match chunk.signature {
LABL_SIG => RawAdtlMember::Label( RawLabel::read_from(&chunk.contents)? ),
NOTE_SIG => RawAdtlMember::Note( RawNote::read_from(&chunk.contents)? ),
LTXT_SIG => RawAdtlMember::LabeledText( RawLtxt::read_from(&chunk.contents)? ),
x => RawAdtlMember::Unrecognized(x)
}
)
retval.push(match chunk.signature {
LABL_SIG => RawAdtlMember::Label(RawLabel::read_from(&chunk.contents)?),
NOTE_SIG => RawAdtlMember::Note(RawNote::read_from(&chunk.contents)?),
LTXT_SIG => RawAdtlMember::LabeledText(RawLtxt::read_from(&chunk.contents)?),
x => RawAdtlMember::Unrecognized(x),
})
}
Ok( retval )
Ok(retval)
}
}
trait AdtlMemberSearch {
fn labels_for_cue_point(&self, id: u32) -> Vec<&RawLabel>;
fn notes_for_cue_point(&self, id : u32) -> Vec<&RawNote>;
fn notes_for_cue_point(&self, id: u32) -> Vec<&RawNote>;
fn ltxt_for_cue_point(&self, id: u32) -> Vec<&RawLtxt>;
}
impl AdtlMemberSearch for Vec<RawAdtlMember> {
fn labels_for_cue_point(&self, id: u32) -> Vec<&RawLabel> {
self.iter().filter_map(|item| {
match item {
self.iter()
.filter_map(|item| match item {
RawAdtlMember::Label(x) if x.cue_point_id == id => Some(x),
_ => None
}
})
.collect()
_ => None,
})
.collect()
}
fn notes_for_cue_point(&self, id: u32) -> Vec<&RawNote> {
self.iter().filter_map(|item| {
match item {
fn notes_for_cue_point(&self, id: u32) -> Vec<&RawNote> {
self.iter()
.filter_map(|item| match item {
RawAdtlMember::Note(x) if x.cue_point_id == id => Some(x),
_ => None
}
})
.collect()
_ => None,
})
.collect()
}
fn ltxt_for_cue_point(&self, id: u32) -> Vec<&RawLtxt> {
self.iter().filter_map(|item| {
match item {
self.iter()
.filter_map(|item| match item {
RawAdtlMember::LabeledText(x) if x.cue_point_id == id => Some(x),
_ => None
}
})
.collect()
_ => None,
})
.collect()
}
}
/// A cue point recorded in the `cue` and `adtl` metadata.
///
///
/// ## Resources
/// - [Cue list, label and other metadata](https://sites.google.com/site/musicgapi/technical-documents/wav-file-format#smpl)
///
///
/// ### Not Implemented
/// - [EBU 3285 Supplement 2](https://tech.ebu.ch/docs/tech/tech3285s2.pdf) (July 2001): Quality chunk and cuesheet
pub struct Cue {
/// The time of this marker
pub frame : u32,
pub frame: u32,
/// The length of this marker, if it is a range
pub length : Option<u32>,
pub length: Option<u32>,
/// The text "label"/name of this marker if provided
pub label : Option<String>,
pub label: Option<String>,
/// The text "note"/comment of this marker if provided
pub note : Option<String>,
pub note: Option<String>,
/// The offser of this marker
///
///
/// **Note:** Applications use the `frame` and `offset` fields
/// in different ways. iZotope RX Audio Editor writes the
/// marker position to *both* fields, while a Sound Devices
/// recorder writes the marker position to *only* the `offset`
/// field.
pub offset : u32
pub offset: u32,
}
fn convert_to_cue_string(buffer : &[u8]) -> String {
let trimmed : Vec<u8> = buffer.iter().take_while(|c| **c != 0 as u8).cloned().collect();
ASCII.decode(&trimmed, DecoderTrap::Ignore).expect("Error decoding text")
fn convert_to_cue_string(buffer: &[u8]) -> String {
let trimmed: Vec<u8> = buffer
.iter()
.take_while(|c| **c != 0 as u8)
.cloned()
.collect();
ASCII
.decode(&trimmed, DecoderTrap::Ignore)
.expect("Error decoding text")
}
fn convert_from_cue_string(val : &str) -> Vec<u8> {
ASCII.encode(&val, EncoderTrap::Ignore).expect("Error encoding text")
fn convert_from_cue_string(val: &str) -> Vec<u8> {
ASCII
.encode(&val, EncoderTrap::Ignore)
.expect("Error encoding text")
}
impl Cue {
/// Take a list of `Cue`s and convert it into `RawCue` and `RawAdtlMember`s
fn compile_to(cues : &[Cue]) -> (Vec<RawCue>, Vec<RawAdtlMember>) {
cues.iter().enumerate()
fn compile_to(cues: &[Cue]) -> (Vec<RawCue>, Vec<RawAdtlMember>) {
cues.iter()
.enumerate()
.map(|(n, cue)| {
let raw_cue = RawCue {
cue_point_id: n as u32,
frame : cue.frame,
frame: cue.frame,
chunk_id: DATA_SIG,
chunk_start: 0,
block_start: 0,
frame_offset: cue.offset
frame_offset: cue.offset,
};
let raw_label = cue.label.as_ref().map(|val| {
RawLabel {
cue_point_id : n as u32,
text: convert_from_cue_string(&val)
}
let raw_label = cue.label.as_ref().map(|val| RawLabel {
cue_point_id: n as u32,
text: convert_from_cue_string(&val),
});
let raw_note = cue.note.as_ref().map(|val| {
RawNote {
cue_point_id: n as u32,
text : convert_from_cue_string(&val)
}
let raw_note = cue.note.as_ref().map(|val| RawNote {
cue_point_id: n as u32,
text: convert_from_cue_string(&val),
});
let raw_ltxt = cue.length.map(|val| {
RawLtxt {
cue_point_id : n as u32,
frame_length : val,
purpose : FourCC::make(b"rgn "),
country : 0,
language : 0,
dialect : 0,
code_page : 0,
text : None
}
let raw_ltxt = cue.length.map(|val| RawLtxt {
cue_point_id: n as u32,
frame_length: val,
purpose: FourCC::make(b"rgn "),
country: 0,
language: 0,
dialect: 0,
code_page: 0,
text: None,
});
(raw_cue, raw_label, raw_note, raw_ltxt)
})
.fold((Vec::<RawCue>::new(), Vec::<RawAdtlMember>::new()),
|(mut cues, mut adtls) , (cue, label, note, ltxt)| {
cues.push(cue);
label.map(|l| adtls.push( RawAdtlMember::Label(l)));
note.map(|n| adtls.push(RawAdtlMember::Note(n)));
ltxt.map(|m| adtls.push(RawAdtlMember::LabeledText(m)));
(cues, adtls)
})
.fold(
(Vec::<RawCue>::new(), Vec::<RawAdtlMember>::new()),
|(mut cues, mut adtls), (cue, label, note, ltxt)| {
cues.push(cue);
label.map(|l| adtls.push(RawAdtlMember::Label(l)));
note.map(|n| adtls.push(RawAdtlMember::Note(n)));
ltxt.map(|m| adtls.push(RawAdtlMember::LabeledText(m)));
(cues, adtls)
},
)
}
pub fn collect_from(cue_chunk : &[u8], adtl_chunk : Option<&[u8]>) -> Result<Vec<Cue>, Error> {
pub fn collect_from(cue_chunk: &[u8], adtl_chunk: Option<&[u8]>) -> Result<Vec<Cue>, Error> {
let raw_cues = RawCue::read_from(cue_chunk)?;
let raw_adtl : Vec<RawAdtlMember>;
let raw_adtl: Vec<RawAdtlMember>;
if let Some(adtl) = adtl_chunk {
raw_adtl = RawAdtlMember::collect_from(adtl)?;
} else {
raw_adtl = vec![];
}
Ok(
raw_cues.iter()
Ok(raw_cues
.iter()
.map(|i| {
Cue {
//ident : i.cue_point_id,
frame : i.frame,
frame: i.frame,
length: {
raw_adtl.ltxt_for_cue_point(i.cue_point_id).first()
.filter(|x| x.purpose == FourCC::make(b"rgn "))
.map(|x| x.frame_length)
raw_adtl
.ltxt_for_cue_point(i.cue_point_id)
.first()
.filter(|x| x.purpose == FourCC::make(b"rgn "))
.map(|x| x.frame_length)
},
label: {
raw_adtl.labels_for_cue_point(i.cue_point_id).iter()
raw_adtl
.labels_for_cue_point(i.cue_point_id)
.iter()
.map(|s| convert_to_cue_string(&s.text))
.next()
},
note : {
raw_adtl.notes_for_cue_point(i.cue_point_id).iter()
note: {
raw_adtl
.notes_for_cue_point(i.cue_point_id)
.iter()
//.filter_map(|x| str::from_utf8(&x.text).ok())
.map(|s| convert_to_cue_string(&s.text))
.next()
},
offset: i.frame_offset
offset: i.frame_offset,
}
}).collect()
)
})
.collect())
}
}

View File

@@ -1,13 +1,15 @@
use std::{fmt::{Debug,Display}, io};
use std::error::Error as StdError;
use super::fourcc::FourCC;
use std::error::Error as StdError;
use std::{
fmt::{Debug, Display},
io,
};
use uuid;
/// Errors returned by methods in this crate.
#[derive(Debug)]
pub enum Error {
/// An `io::Error` occurred
IOError(io::Error),
@@ -16,14 +18,14 @@ pub enum Error {
/// The file does not begin with a recognized WAVE header
HeaderNotRecognized,
/// A wave file with a 64-bit header does not contain
/// the required `ds64` metadata element
MissingRequiredDS64,
/// A data chunk required to complete the operation
/// is not present in the file
ChunkMissing { signature : FourCC },
ChunkMissing { signature: FourCC },
/// The file is formatted improperly
FmtChunkAfterData,
@@ -37,11 +39,10 @@ pub enum Error {
/// The file cannot be converted into an RF64 file due
/// to its internal structure
InsufficientDS64Reservation {expected: u64, actual: u64},
InsufficientDS64Reservation { expected: u64, actual: u64 },
/// The file is not optimized for writing new data
DataChunkNotPreparedForAppend,
}
impl StdError for Error {}
@@ -52,15 +53,14 @@ impl Display for Error {
}
}
impl From<io::Error> for Error {
fn from(error: io::Error) -> Error {
Error::IOError(error)
}
}
impl From <uuid::Error> for Error {
impl From<uuid::Error> for Error {
fn from(error: uuid::Error) -> Error {
Error::UuidError(error)
}
}
}
}

View File

@@ -1,32 +1,32 @@
use uuid::Uuid;
use super::common_format::{CommonFormat, UUID_PCM,UUID_BFORMAT_PCM};
use super::common_format::{CommonFormat, UUID_BFORMAT_PCM, UUID_PCM};
use std::io::Cursor;
use uuid::Uuid;
use byteorder::LittleEndian;
use byteorder::{WriteBytesExt, ReadBytesExt};
use byteorder::{ReadBytesExt, WriteBytesExt};
// Need more test cases for ADMAudioID
#[allow(dead_code)]
/// ADM Audio ID record.
///
///
/// This structure relates a channel in the wave file to either a common ADM
/// channel definition or further definition in the WAV file's ADM metadata
/// channel definition or further definition in the WAV file's ADM metadata
/// chunk.
///
/// An individual channel in a WAV file can have multiple Audio IDs in an ADM
///
/// An individual channel in a WAV file can have multiple Audio IDs in an ADM
/// `AudioProgramme`.
///
///
/// See BS.2088-1 § 8, also BS.2094, also blahblahblah...
#[derive(Debug)]
pub struct ADMAudioID {
pub track_uid: [char; 12],
pub channel_format_ref: [char; 14],
pub pack_ref: [char; 11]
pub pack_ref: [char; 11],
}
/// Describes a single channel in a WAV file.
///
///
/// This information is correlated from the Wave format ChannelMap field and
/// the `chna` chunk, if present.
#[derive(Debug)]
@@ -35,7 +35,7 @@ pub struct ChannelDescriptor {
pub index: u16,
/// Channel assignment
///
///
/// This is either implied (in the case of mono or stereo wave files) or
/// explicitly given in `WaveFormatExtentended` for files with more tracks.
pub speaker: ChannelMask,
@@ -44,40 +44,38 @@ pub struct ChannelDescriptor {
pub adm_track_audio_ids: Vec<ADMAudioID>,
}
/// A bitmask indicating which channels are present in
/// A bitmask indicating which channels are present in
/// the file.
///
///
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ChannelMask {
DirectOut = 0x0,
FrontLeft = 0x1,
FrontRight = 0x2,
FrontCenter = 0x4,
LowFrequency = 0x8,
BackLeft = 0x10,
BackRight = 0x20,
FrontCenterLeft = 0x40,
DirectOut = 0x0,
FrontLeft = 0x1,
FrontRight = 0x2,
FrontCenter = 0x4,
LowFrequency = 0x8,
BackLeft = 0x10,
BackRight = 0x20,
FrontCenterLeft = 0x40,
FrontCenterRight = 0x80,
BackCenter = 0x100,
SideLeft = 0x200,
SideRight = 0x400,
TopCenter = 0x800,
TopFrontLeft = 0x1000,
TopFrontCenter = 0x2000,
TopFrontRight = 0x4000,
TopBackLeft = 0x8000,
TopBackCenter = 0x10000,
TopBackRight = 0x20000,
BackCenter = 0x100,
SideLeft = 0x200,
SideRight = 0x400,
TopCenter = 0x800,
TopFrontLeft = 0x1000,
TopFrontCenter = 0x2000,
TopFrontRight = 0x4000,
TopBackLeft = 0x8000,
TopBackCenter = 0x10000,
TopBackRight = 0x20000,
}
impl From<u32> for ChannelMask {
fn from(value: u32) -> Self {
fn from(value: u32) -> Self {
match value {
0x1 => Self::FrontLeft,
0x2 => Self::FrontRight,
0x4 => Self::FrontCenter,
0x4 => Self::FrontCenter,
0x8 => Self::LowFrequency,
0x10 => Self::BackLeft,
0x20 => Self::BackRight,
@@ -87,24 +85,25 @@ impl From<u32> for ChannelMask {
0x200 => Self::SideLeft,
0x400 => Self::SideRight,
0x800 => Self::TopCenter,
0x1000 => Self::TopFrontLeft,
0x1000 => Self::TopFrontLeft,
0x2000 => Self::TopFrontCenter,
0x4000 => Self::TopFrontRight,
0x8000 => Self::TopBackLeft,
0x10000 => Self::TopBackCenter,
0x20000 => Self::TopBackRight,
_ => Self::DirectOut
_ => Self::DirectOut,
}
}
}
impl ChannelMask {
pub fn channels(input_mask : u32, channel_count: u16) -> Vec<ChannelMask> {
pub fn channels(input_mask: u32, channel_count: u16) -> Vec<ChannelMask> {
let reserved_mask = 0xfff2_0000_u32;
if (input_mask & reserved_mask) > 0 {
vec![ ChannelMask::DirectOut ; channel_count as usize ]
vec![ChannelMask::DirectOut; channel_count as usize]
} else {
(0..18).map(|i| 1 << i )
(0..18)
.map(|i| 1 << i)
.filter(|mask| mask & input_mask > 0)
.map(|mask| Into::<ChannelMask>::into(mask))
.collect()
@@ -114,36 +113,35 @@ impl ChannelMask {
/**
* Extended Wave Format
*
*
* https://docs.microsoft.com/en-us/windows/win32/api/mmreg/ns-mmreg-waveformatextensible
*/
#[derive(Debug, Copy, Clone)]
pub struct WaveFmtExtended {
/// Valid bits per sample
pub valid_bits_per_sample : u16,
pub valid_bits_per_sample: u16,
/// Channel mask
///
///
/// Identifies the speaker assignment for each channel in the file
pub channel_mask : u32,
pub channel_mask: u32,
/// Codec GUID
///
///
/// Identifies the codec of the audio stream
pub type_guid : Uuid,
pub type_guid: Uuid,
}
///
/// WAV file data format record.
///
/// The `fmt` record contains essential information describing the binary
/// structure of the data segment of the WAVE file, such as sample
/// structure of the data segment of the WAVE file, such as sample
/// rate, sample binary format, channel count, etc.
///
///
/// ## Resources
///
///
/// ## Resources
///
/// ### Implementation of Wave format `fmt` chunk
/// - [MSDN WAVEFORMATEX](https://docs.microsoft.com/en-us/windows/win32/api/mmeapi/ns-mmeapi-waveformatex)
/// - [MSDN WAVEFORMATEXTENSIBLE](https://docs.microsoft.com/en-us/windows/win32/api/mmreg/ns-mmreg-waveformatextensible)
@@ -152,17 +150,15 @@ pub struct WaveFmtExtended {
/// - [RFC 3261][rfc3261] (June 1998) "WAVE and AVI Codec Registries"
/// - [Sampler Metadata](http://www.piclist.com/techref/io/serial/midi/wave.html)
/// - [Peter Kabal, McGill University](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html)
/// - [Multimedia Programming Interface and Data Specifications 1.0](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf)
/// - [Multimedia Programming Interface and Data Specifications 1.0](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf)
/// (August 1991), IBM Corporation and Microsoft Corporation
///
/// [rfc3261]: https://tools.ietf.org/html/rfc2361
///
/// [rfc3261]: https://tools.ietf.org/html/rfc2361
#[derive(Debug, Copy, Clone)]
pub struct WaveFmt {
/// A tag identifying the codec in use.
///
///
/// If this is 0xFFFE, the codec will be identified by a GUID
/// in `extended_format`
pub tag: u16,
@@ -176,12 +172,12 @@ pub struct WaveFmt {
pub sample_rate: u32,
/// Count of bytes per second
///
///
/// By rule, this is `block_alignment * sample_rate`
pub bytes_per_second: u32,
/// Count of bytes per audio frame
///
///
/// By rule, this is `channel_count * bits_per_sample / 8`
pub block_alignment: u16,
@@ -199,15 +195,13 @@ pub struct WaveFmt {
pub bits_per_sample: u16,
/// Extended format description
///
///
/// Additional format metadata if `channel_count` is greater than 2,
/// or if certain codecs are used.
pub extended_format: Option<WaveFmtExtended>
pub extended_format: Option<WaveFmtExtended>,
}
impl WaveFmt {
pub fn valid_bits_per_sample(&self) -> u16 {
if let Some(ext) = self.extended_format {
ext.valid_bits_per_sample
@@ -221,7 +215,7 @@ impl WaveFmt {
Self::new_pcm_multichannel(sample_rate, bits_per_sample, 0x4)
}
/// Create a new integer PCM format for a standard Left-Right stereo audio
/// Create a new integer PCM format for a standard Left-Right stereo audio
/// stream.
pub fn new_pcm_stereo(sample_rate: u32, bits_per_sample: u16) -> Self {
Self::new_pcm_multichannel(sample_rate, bits_per_sample, 0x3)
@@ -230,45 +224,65 @@ impl WaveFmt {
/// Create a new integer PCM format for ambisonic b-format.
pub fn new_pcm_ambisonic(sample_rate: u32, bits_per_sample: u16, channel_count: u16) -> Self {
let container_bits_per_sample = bits_per_sample + (bits_per_sample % 8);
let container_bytes_per_sample= container_bits_per_sample / 8;
let container_bytes_per_sample = container_bits_per_sample / 8;
WaveFmt {
tag : 0xFFFE,
tag: 0xFFFE,
channel_count,
sample_rate,
bytes_per_second: container_bytes_per_sample as u32 * sample_rate * channel_count as u32,
bytes_per_second: container_bytes_per_sample as u32
* sample_rate
* channel_count as u32,
block_alignment: container_bytes_per_sample * channel_count,
bits_per_sample: container_bits_per_sample,
extended_format: Some(WaveFmtExtended {
valid_bits_per_sample: bits_per_sample,
channel_mask: ChannelMask::DirectOut as u32,
type_guid: UUID_BFORMAT_PCM
})
type_guid: UUID_BFORMAT_PCM,
}),
}
}
/// Create a new integer PCM format `WaveFmt` with a custom channel bitmap.
///
/// The order of `channels` is not important. When reading or writing
/// audio frames you must use the standard multichannel order for Wave
///
/// The order of `channels` is not important. When reading or writing
/// audio frames you must use the standard multichannel order for Wave
/// files, the numerical order of the cases of `ChannelMask`.
pub fn new_pcm_multichannel(sample_rate: u32, bits_per_sample: u16, channel_bitmap: u32) -> Self {
pub fn new_pcm_multichannel(
sample_rate: u32,
bits_per_sample: u16,
channel_bitmap: u32,
) -> Self {
let container_bits_per_sample = bits_per_sample + (bits_per_sample % 8);
let container_bytes_per_sample= container_bits_per_sample / 8;
let channel_count: u16 = (0..=31).fold(0u16, |accum, n| accum + (0x1 & (channel_bitmap >> n) as u16) );
let container_bytes_per_sample = container_bits_per_sample / 8;
let result : (u16, Option<WaveFmtExtended>) = match channel_bitmap {
ch if bits_per_sample != container_bits_per_sample => (
(0xFFFE, Some(WaveFmtExtended { valid_bits_per_sample: bits_per_sample, channel_mask: ch,
type_guid: UUID_PCM }) )
),
let channel_count: u16 = (0..=31).fold(0u16, |accum, n| {
accum + (0x1 & (channel_bitmap >> n) as u16)
});
let result: (u16, Option<WaveFmtExtended>) = match channel_bitmap {
ch if bits_per_sample != container_bits_per_sample => {
(
0xFFFE,
Some(WaveFmtExtended {
valid_bits_per_sample: bits_per_sample,
channel_mask: ch,
type_guid: UUID_PCM,
}),
)
}
0b0100 => (0x0001, None),
0b0011 => (0x0001, None),
ch => (
(0xFFFE, Some( WaveFmtExtended { valid_bits_per_sample: bits_per_sample, channel_mask: ch,
type_guid: UUID_PCM}))
)
ch => {
(
0xFFFE,
Some(WaveFmtExtended {
valid_bits_per_sample: bits_per_sample,
channel_mask: ch,
type_guid: UUID_PCM,
}),
)
}
};
let (tag, extformat) = result;
@@ -277,33 +291,35 @@ impl WaveFmt {
tag,
channel_count,
sample_rate,
bytes_per_second: container_bytes_per_sample as u32 * sample_rate * channel_count as u32,
bytes_per_second: container_bytes_per_sample as u32
* sample_rate
* channel_count as u32,
block_alignment: container_bytes_per_sample * channel_count,
bits_per_sample: container_bits_per_sample,
extended_format: extformat
extended_format: extformat,
}
}
/// Format or codec of the file's audio data.
///
///
/// The `CommonFormat` unifies the format tag and the format extension GUID. Use this
/// method to determine the codec.
pub fn common_format(&self) -> CommonFormat {
CommonFormat::make( self.tag, self.extended_format.map(|ext| ext.type_guid))
CommonFormat::make(self.tag, self.extended_format.map(|ext| ext.type_guid))
}
/// Create a frame buffer sized to hold `length` frames for a reader or
/// Create a frame buffer sized to hold `length` frames for a reader or
/// writer
///
///
/// This is a conveneince method that creates a `Vec<i32>` with
/// as many elements as there are channels in the underlying stream.
pub fn create_frame_buffer(&self, length : usize) -> Vec<i32> {
/// as many elements as there are channels in the underlying stream.
pub fn create_frame_buffer(&self, length: usize) -> Vec<i32> {
vec![0i32; self.channel_count as usize * length]
}
/// Create a raw byte buffer to hold `length` blocks from a reader or
/// Create a raw byte buffer to hold `length` blocks from a reader or
/// writer
pub fn create_raw_buffer(&self, length : usize) -> Vec<u8> {
pub fn create_raw_buffer(&self, length: usize) -> Vec<u8> {
vec![0u8; self.block_alignment as usize * length]
}
@@ -311,11 +327,13 @@ impl WaveFmt {
pub fn pack_frames(&self, from_frames: &[i32], into_bytes: &mut [u8]) -> () {
let mut write_cursor = Cursor::new(into_bytes);
assert!(from_frames.len() % self.channel_count as usize == 0,
"frames buffer does not contain a number of samples % channel_count == 0");
assert!(
from_frames.len() % self.channel_count as usize == 0,
"frames buffer does not contain a number of samples % channel_count == 0"
);
for n in 0..from_frames.len() {
match (self.valid_bits_per_sample(), self.bits_per_sample) {
for n in 0..from_frames.len() {
match (self.valid_bits_per_sample(), self.bits_per_sample) {
(0..=8,8) => write_cursor.write_u8((from_frames[n] + 0x80) as u8 ).unwrap(), // EBU 3285 §A2.2
(9..=16,16) => write_cursor.write_i16::<LittleEndian>(from_frames[n] as i16).unwrap(),
(10..=24,24) => write_cursor.write_i24::<LittleEndian>(from_frames[n]).unwrap(),
@@ -323,7 +341,7 @@ impl WaveFmt {
(b,_)=> panic!("Unrecognized integer format, bits per sample {}, channels {}, block_alignment {}",
b, self.channel_count, self.block_alignment)
}
}
}
()
}
@@ -342,43 +360,107 @@ impl WaveFmt {
}
}
/// Channel descriptors for each channel.
pub fn channels(&self) -> Vec<ChannelDescriptor> {
match self.channel_count {
1 => vec![
ChannelDescriptor {
index: 0,
speaker: ChannelMask::FrontCenter,
adm_track_audio_ids: vec![]
}
],
1 => vec![ChannelDescriptor {
index: 0,
speaker: ChannelMask::FrontCenter,
adm_track_audio_ids: vec![],
}],
2 => vec![
ChannelDescriptor {
index: 0,
speaker: ChannelMask::FrontLeft,
adm_track_audio_ids: vec![]
adm_track_audio_ids: vec![],
},
ChannelDescriptor {
index: 1,
speaker: ChannelMask::FrontRight,
adm_track_audio_ids: vec![]
}
adm_track_audio_ids: vec![],
},
],
x if x > 2 => {
let channel_mask = self.extended_format.map(|x| x.channel_mask).unwrap_or(0);
let channels = ChannelMask::channels(channel_mask, self.channel_count);
let channels_expanded = channels.iter().chain(std::iter::repeat(&ChannelMask::DirectOut));
let channels_expanded = channels
.iter()
.chain(std::iter::repeat(&ChannelMask::DirectOut));
(0..self.channel_count)
.zip(channels_expanded)
.map(|(n,chan)| ChannelDescriptor {
.map(|(n, chan)| ChannelDescriptor {
index: n,
speaker: *chan,
adm_track_audio_ids: vec![]
}).collect()
},
speaker: *chan,
adm_track_audio_ids: vec![],
})
.collect()
}
x => panic!("Channel count ({}) was illegal!", x),
}
}
}
}
trait ReadWavAudioData {
fn read_i32_frames(
&mut self,
format: WaveFmt,
into: &mut [i32],
) -> Result<usize, std::io::Error>;
fn read_f32_frames(
&mut self,
format: WaveFmt,
into: &mut [f32],
) -> Result<usize, std::io::Error>;
}
impl<T> ReadWavAudioData for T
where
T: std::io::Read,
{
fn read_i32_frames(
&mut self,
format: WaveFmt,
into: &mut [i32],
) -> Result<usize, std::io::Error> {
assert!(into.len() % format.channel_count as usize == 0);
for n in 0..(into.len()) {
into[n] = match (format.valid_bits_per_sample(), format.bits_per_sample) {
(0..=8,8) => self.read_u8().unwrap() as i32 - 0x80_i32, // EBU 3285 §A2.2
(9..=16,16) => self.read_i16::<LittleEndian>().unwrap() as i32,
(10..=24,24) => self.read_i24::<LittleEndian>().unwrap(),
(25..=32,32) => self.read_i32::<LittleEndian>().unwrap(),
(b,_)=> panic!("Unrecognized integer format, bits per sample {}, channels {}, block_alignment {}",
b, format.channel_count, format.block_alignment)
}
}
todo!()
}
fn read_f32_frames(
&mut self,
format: WaveFmt,
into: &mut [f32],
) -> Result<usize, std::io::Error> {
assert!(into.len() % format.channel_count as usize == 0);
todo!()
}
}
trait WriteWavAudioData {
fn write_i32_frames(&mut self, format: WaveFmt, from: &[i32]) -> Result<usize, std::io::Error>;
fn write_f32_frames(&mut self, format: WaveFmt, from: &[f32]) -> Result<usize, std::io::Error>;
}
impl<T> WriteWavAudioData for T
where
T: std::io::Write,
{
fn write_i32_frames(&mut self, format: WaveFmt, _: &[i32]) -> Result<usize, std::io::Error> {
todo!()
}
fn write_f32_frames(&mut self, format: WaveFmt, _: &[f32]) -> Result<usize, std::io::Error> {
todo!()
}
}

View File

@@ -2,7 +2,7 @@ use std::fmt::Debug;
use std::io;
/// A Four-character Code
///
///
/// For idetifying chunks, structured contiguous slices or segments
/// within a WAV file.
#[derive(Eq, PartialEq, Hash, Copy, Clone)]
@@ -15,8 +15,13 @@ impl FourCC {
}
impl From<[char; 4]> for FourCC {
fn from(chars : [char; 4]) -> Self {
Self([chars[0] as u8 , chars[1] as u8, chars[2] as u8, chars[3] as u8])
fn from(chars: [char; 4]) -> Self {
Self([
chars[0] as u8,
chars[1] as u8,
chars[2] as u8,
chars[3] as u8,
])
}
}
@@ -32,37 +37,45 @@ impl From<FourCC> for [u8; 4] {
}
}
impl From<&FourCC> for [char;4] {
fn from( f: &FourCC) -> Self {
[f.0[0] as char, f.0[1] as char, f.0[2] as char, f.0[3] as char,]
impl From<&FourCC> for [char; 4] {
fn from(f: &FourCC) -> Self {
[
f.0[0] as char,
f.0[1] as char,
f.0[2] as char,
f.0[3] as char,
]
}
}
impl From<FourCC> for [char;4] {
fn from( f: FourCC) -> Self {
[f.0[0] as char, f.0[1] as char, f.0[2] as char, f.0[3] as char,]
impl From<FourCC> for [char; 4] {
fn from(f: FourCC) -> Self {
[
f.0[0] as char,
f.0[1] as char,
f.0[2] as char,
f.0[3] as char,
]
}
}
impl From<&FourCC> for String {
fn from(f: &FourCC) -> Self {
let chars: [char;4] = f.into();
chars.iter().collect::<String>()
fn from(f: &FourCC) -> Self {
let chars: [char; 4] = f.into();
chars.iter().collect::<String>()
}
}
impl From<FourCC> for String {
fn from(f: FourCC) -> Self {
let chars: [char;4] = f.into();
chars.iter().collect::<String>()
fn from(f: FourCC) -> Self {
let chars: [char; 4] = f.into();
chars.iter().collect::<String>()
}
}
impl Debug for FourCC {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
let s : String = self.into();
let s: String = self.into();
write!(f, "FourCC({})", s)
}
}
@@ -72,30 +85,35 @@ pub trait ReadFourCC: io::Read {
}
pub trait WriteFourCC: io::Write {
fn write_fourcc(&mut self, fourcc :FourCC) -> Result<(), io::Error>;
fn write_fourcc(&mut self, fourcc: FourCC) -> Result<(), io::Error>;
}
impl<T> ReadFourCC for T where T: io::Read {
impl<T> ReadFourCC for T
where
T: io::Read,
{
fn read_fourcc(&mut self) -> Result<FourCC, io::Error> {
let mut buf : [u8; 4] = [0 ; 4];
let mut buf: [u8; 4] = [0; 4];
self.read_exact(&mut buf)?;
Ok( FourCC::from(buf) )
Ok(FourCC::from(buf))
}
}
impl<T> WriteFourCC for T where T: io::Write {
fn write_fourcc(&mut self, fourcc :FourCC) -> Result<(), io::Error> {
let buf : [u8; 4] = fourcc.into();
impl<T> WriteFourCC for T
where
T: io::Write,
{
fn write_fourcc(&mut self, fourcc: FourCC) -> Result<(), io::Error> {
let buf: [u8; 4] = fourcc.into();
self.write_all(&buf)?;
Ok(())
}
}
pub const RIFF_SIG: FourCC = FourCC::make(b"RIFF");
pub const WAVE_SIG: FourCC = FourCC::make(b"WAVE");
pub const RF64_SIG: FourCC = FourCC::make(b"RF64");
pub const DS64_SIG: FourCC = FourCC::make(b"ds64");
pub const RF64_SIG: FourCC = FourCC::make(b"RF64");
pub const DS64_SIG: FourCC = FourCC::make(b"ds64");
pub const BW64_SIG: FourCC = FourCC::make(b"BW64");
pub const DATA_SIG: FourCC = FourCC::make(b"data");
@@ -117,7 +135,6 @@ pub const LABL_SIG: FourCC = FourCC::make(b"labl");
pub const NOTE_SIG: FourCC = FourCC::make(b"note");
pub const LTXT_SIG: FourCC = FourCC::make(b"ltxt");
#[cfg(test)]
mod tests {
use super::*;
@@ -125,7 +142,7 @@ mod tests {
#[test]
fn test_to_string() {
let a = FourCC::make(b"a1b2");
let s : String = a.into();
let s: String = a.into();
assert_eq!(s, "a1b2");
}
}
}

View File

@@ -1,29 +1,29 @@
/*!
/*!
# bwavfile
Rust Wave File Reader/Writer with Broadcast-WAV, MBWF and RF64 Support
## Interfaces
### `WaveReader`
`WaveReader` can open and parse a Wave, Broadcast-Wave, or RF64/BW64 64-bit
`WaveReader` can open and parse a Wave, Broadcast-Wave, or RF64/BW64 64-bit
wave file. Metadata can be accessed and parsed in arbitrary order and audio
samples can be accessed using the `AudioFrameReader` type, created by an
samples can be accessed using the `AudioFrameReader` type, created by an
accessor method of `WaveReader`.
### `WaveWriter`
`WaveWriter` can create a new Wave, Broadcast-Wave, or RF64/BW64 64-bit wave
file. Metadata chunks and audio samples are added sequentially, write-only, to
a Wave file which is automatically promoted from standard Wave to RF64 wave
`WaveWriter` can create a new Wave, Broadcast-Wave, or RF64/BW64 64-bit wave
file. Metadata chunks and audio samples are added sequentially, write-only, to
a Wave file which is automatically promoted from standard Wave to RF64 wave
when the total WAVE form size exceeds 0xFFFFFFFF bytes.
## Objectives and Roadmap
This package aims to support read and writing any kind of WAV file you are likely
to encounter in a professional audio, motion picture production, broadcast, or music
This package aims to support read and writing any kind of WAV file you are likely
to encounter in a professional audio, motion picture production, broadcast, or music
production.
Apps we test against:
@@ -36,29 +36,29 @@ Apps we test against:
[github]: https://github.com/iluvcapra/bwavfile
*/
extern crate encoding;
extern crate byteorder;
extern crate encoding;
extern crate uuid;
mod fourcc;
mod errors;
mod common_format;
mod errors;
mod fourcc;
mod parser;
mod list_form;
mod parser;
mod bext;
mod chunks;
mod cue;
mod bext;
mod fmt;
mod wavereader;
mod wavewriter;
pub use errors::Error;
pub use wavereader::{WaveReader, AudioFrameReader};
pub use wavewriter::{WaveWriter, AudioFrameWriter};
pub use bext::Bext;
pub use fmt::{WaveFmt, WaveFmtExtended, ChannelDescriptor, ChannelMask, ADMAudioID};
pub use common_format::CommonFormat;
pub use cue::Cue;
pub use cue::Cue;
pub use errors::Error;
pub use fmt::{ADMAudioID, ChannelDescriptor, ChannelMask, WaveFmt, WaveFmtExtended};
pub use wavereader::{AudioFrameReader, WaveReader};
pub use wavewriter::{AudioFrameWriter, WaveWriter};

View File

@@ -1,22 +1,22 @@
use super::fourcc::{FourCC, ReadFourCC};
use byteorder::{ReadBytesExt, LittleEndian};
use byteorder::{LittleEndian, ReadBytesExt};
use std::io::{Cursor, Error, Read};
pub struct ListFormItem {
pub signature : FourCC,
pub contents : Vec<u8>
pub signature: FourCC,
pub contents: Vec<u8>,
}
/// A helper that will accept a LIST chunk as a [u8]
/// and give you back each segment
///
pub fn collect_list_form(list_contents :& [u8]) -> Result<Vec<ListFormItem>, Error> {
///
pub fn collect_list_form(list_contents: &[u8]) -> Result<Vec<ListFormItem>, Error> {
let mut cursor = Cursor::new(list_contents);
let mut remain = list_contents.len();
let _ = cursor.read_fourcc()?; // skip signature
remain -= 4;
let mut retval : Vec<ListFormItem> = vec![];
let mut retval: Vec<ListFormItem> = vec![];
while remain > 0 {
let this_sig = cursor.read_fourcc()?;
@@ -25,16 +25,18 @@ pub fn collect_list_form(list_contents :& [u8]) -> Result<Vec<ListFormItem>, Err
let mut content_buf = vec![0u8; this_size];
cursor.read_exact(&mut content_buf)?;
remain -= this_size;
retval.push( ListFormItem { signature : this_sig, contents : content_buf } );
retval.push(ListFormItem {
signature: this_sig,
contents: content_buf,
});
if this_size % 2 == 1 {
cursor.read_u8()?;
//panic!("Got this far!");
remain -= 1;
}
}
Ok( retval )
}
Ok(retval)
}

View File

@@ -1,15 +1,14 @@
use std::collections::HashMap;
use std::io;
use std::io::SeekFrom::{Current, Start};
use std::io::{Seek, Read};
use std::collections::HashMap;
use std::io::{Read, Seek};
use byteorder::LittleEndian;
use byteorder::ReadBytesExt;
use super::errors::Error;
use super::fourcc::{FourCC, ReadFourCC};
use super::fourcc::{RIFF_SIG, RF64_SIG, BW64_SIG, WAVE_SIG, DS64_SIG, DATA_SIG};
use super::fourcc::{BW64_SIG, DATA_SIG, DS64_SIG, RF64_SIG, RIFF_SIG, WAVE_SIG};
// just for your reference...
// RF64 documentation https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.2088-1-201910-I!!PDF-E.pdf
@@ -21,12 +20,26 @@ const RF64_SIZE_MARKER: u32 = 0xFF_FF_FF_FF;
#[derive(Debug)]
pub enum Event {
StartParse,
ReadHeader { signature: FourCC, length_field: u32 },
ReadRF64Header { signature: FourCC },
ReadDS64 {file_size: u64, long_sizes: HashMap<FourCC,u64> },
BeginChunk { signature: FourCC, content_start: u64, content_length: u64 },
Failed { error: Error },
FinishParse
ReadHeader {
signature: FourCC,
length_field: u32,
},
ReadRF64Header {
signature: FourCC,
},
ReadDS64 {
file_size: u64,
long_sizes: HashMap<FourCC, u64>,
},
BeginChunk {
signature: FourCC,
content_start: u64,
content_length: u64,
},
Failed {
error: Error,
},
FinishParse,
}
#[derive(Debug)]
@@ -36,67 +49,80 @@ enum State {
ReadyForDS64,
ReadyForChunk { at: u64, remaining: u64 },
Error,
Complete
Complete,
}
pub struct Parser<R: Read + Seek> {
stream: R,
state: State,
ds64state: HashMap<FourCC,u64>
ds64state: HashMap<FourCC, u64>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct ChunkIteratorItem {
pub signature: FourCC,
pub start: u64,
pub length: u64
pub length: u64,
}
impl<R: Read + Seek> Parser<R> {
// wraps a stream
pub fn make(stream: R) -> Result<Self, Error> {
let newmap: HashMap<FourCC, u64> = HashMap::new();
let mut the_stream = stream;
the_stream.seek(Start(0))?;
return Ok(Parser {
stream: the_stream,
stream: the_stream,
state: State::New,
ds64state: newmap,
})
});
}
// pub fn into_inner(self) -> R {
// self.stream
// }
pub fn into_chunk_iterator(self) -> impl Iterator<Item = Result<ChunkIteratorItem, Error>>{
self.filter_map({|event|
if let Event::BeginChunk {signature , content_start, content_length } = event {
Some(Ok(ChunkIteratorItem {signature, start: content_start, length: content_length }))
} else if let Event::Failed { error } = event {
Some(Err(error))
} else {
None
pub fn into_chunk_iterator(self) -> impl Iterator<Item = Result<ChunkIteratorItem, Error>> {
self.filter_map({
|event| {
if let Event::BeginChunk {
signature,
content_start,
content_length,
} = event
{
Some(Ok(ChunkIteratorItem {
signature,
start: content_start,
length: content_length,
}))
} else if let Event::Failed { error } = event {
Some(Err(error))
} else {
None
}
}
})
}
pub fn into_chunk_list(self) -> Result<Vec<ChunkIteratorItem>,Error> {
pub fn into_chunk_list(self) -> Result<Vec<ChunkIteratorItem>, Error> {
let mut error = Ok(());
let chunks = self.into_chunk_iterator()
let chunks = self
.into_chunk_iterator()
.scan(&mut error, |err, res| match res {
Ok(ok) => Some(ok),
Err(e) => { **err = Err(e); None }
Err(e) => {
**err = Err(e);
None
}
})
.collect();
error?;
Ok( chunks )
Ok(chunks)
}
}
impl<R: Read + Seek> Iterator for Parser<R> {
@@ -110,55 +136,53 @@ impl<R: Read + Seek> Iterator for Parser<R> {
}
impl<R: Read + Seek> Parser<R> {
fn parse_header(&mut self) -> Result<(Event,State),io::Error> {
fn parse_header(&mut self) -> Result<(Event, State), io::Error> {
let file_sig = self.stream.read_fourcc()?;
let length = self.stream.read_u32::<LittleEndian>()?;
let list_sig = self.stream.read_fourcc()?;
let event : Event;
let event: Event;
let next_state: State;
match (file_sig, length, list_sig) {
(RIFF_SIG, size, WAVE_SIG) => {
event = Event::ReadHeader {
signature: file_sig,
length_field: size
length_field: size,
};
next_state = State::ReadyForChunk {
at: 12,
remaining: (length - 4) as u64,
};
},
}
(RF64_SIG, RF64_SIZE_MARKER, WAVE_SIG) | (BW64_SIG, RF64_SIZE_MARKER, WAVE_SIG) => {
event = Event::ReadRF64Header {
signature: file_sig
signature: file_sig,
};
next_state = State::ReadyForDS64;
},
}
_ => {
event = Event::Failed {
error: Error::HeaderNotRecognized
error: Error::HeaderNotRecognized,
};
next_state = State::Error;
}
}
return Ok( (event, next_state) );
return Ok((event, next_state));
}
fn parse_ds64(&mut self) -> Result<(Event, State), Error> {
let at :u64 = 12;
let at: u64 = 12;
let ds64_sig = self.stream.read_fourcc()?;
let ds64_size = self.stream.read_u32::<LittleEndian>()? as u64;
let mut read :u64 = 0;
let mut read: u64 = 0;
if ds64_sig != DS64_SIG {
return Err(Error::MissingRequiredDS64);
} else {
let long_file_size = self.stream.read_u64::<LittleEndian>()?;
let long_data_size = self.stream.read_u64::<LittleEndian>()?;
@@ -176,10 +200,10 @@ impl<R: Read + Seek> Parser<R> {
}
self.ds64state.insert(DATA_SIG, long_data_size);
if read < ds64_size {
/* for some reason the ds64 chunk returned by Pro Tools is longer than
it should be but it's all zeroes so... skip.
it should be but it's all zeroes so... skip.
For the record libsndfile seems to do the same thing...
https://github.com/libsndfile/libsndfile/blob/08d802a3d18fa19c74f38ed910d9e33f80248187/src/rf64.c#L230
@@ -189,7 +213,7 @@ impl<R: Read + Seek> Parser<R> {
let event = Event::ReadDS64 {
file_size: long_file_size,
long_sizes : self.ds64state.clone(),
long_sizes: self.ds64state.clone(),
};
let state = State::ReadyForChunk {
@@ -197,19 +221,17 @@ impl<R: Read + Seek> Parser<R> {
remaining: long_file_size - (4 + 8 + ds64_size),
};
return Ok( (event, state) );
return Ok((event, state));
}
}
fn enter_chunk(&mut self, at :u64, remaining: u64) -> Result<(Event, State), io::Error> {
fn enter_chunk(&mut self, at: u64, remaining: u64) -> Result<(Event, State), io::Error> {
let event;
let state;
if remaining == 0 {
event = Event::FinishParse;
state = State::Complete;
} else {
let this_fourcc = self.stream.read_fourcc()?;
let this_size: u64;
@@ -221,59 +243,67 @@ impl<R: Read + Seek> Parser<R> {
this_size = self.stream.read_u32::<LittleEndian>()? as u64;
}
let this_displacement :u64 = if this_size % 2 == 1 { this_size + 1 } else { this_size };
let this_displacement: u64 = if this_size % 2 == 1 {
this_size + 1
} else {
this_size
};
self.stream.seek(Current(this_displacement as i64))?;
event = Event::BeginChunk {
signature: this_fourcc,
content_start: at + 8,
content_length: this_size
content_length: this_size,
};
state = State::ReadyForChunk {
at: at + 8 + this_displacement,
remaining: remaining - 8 - this_displacement
remaining: remaining - 8 - this_displacement,
}
}
return Ok( (event, state) );
return Ok((event, state));
}
fn handle_state(&mut self) -> Result<(Option<Event>, State), Error> {
match self.state {
State::New => {
return Ok( ( Some(Event::StartParse) , State::ReadyForHeader) );
},
return Ok((Some(Event::StartParse), State::ReadyForHeader));
}
State::ReadyForHeader => {
let (event, state) = self.parse_header()?;
return Ok( ( Some(event), state ) );
},
return Ok((Some(event), state));
}
State::ReadyForDS64 => {
let (event, state) = self.parse_ds64()?;
return Ok( ( Some(event), state ) );
},
return Ok((Some(event), state));
}
State::ReadyForChunk { at, remaining } => {
let (event, state) = self.enter_chunk(at, remaining)?;
return Ok( ( Some(event), state ) );
},
return Ok((Some(event), state));
}
State::Error => {
return Ok( ( Some(Event::FinishParse) , State::Complete ) );
},
return Ok((Some(Event::FinishParse), State::Complete));
}
State::Complete => {
return Ok( ( None, State::Complete ) );
return Ok((None, State::Complete));
}
}
}
fn advance(&mut self) -> (Option<Event>, State) {
match self.handle_state() {
Ok(( event , state) ) => {
Ok((event, state)) => {
return (event, state);
},
}
Err(error) => {
return (Some(Event::Failed { error: error.into() } ), State::Error );
return (
Some(Event::Failed {
error: error.into(),
}),
State::Error,
);
}
}
}
}

View File

@@ -1,60 +1,72 @@
use std::fs::File;
use std::io::SeekFrom;
use std::io::Cursor;
use std::io::{Read, Seek, BufReader};
use std::io::SeekFrom::{Start,Current,};
use std::path::Path;
use std::io::Cursor;
use std::io::SeekFrom;
use std::io::SeekFrom::{Current, Start};
use std::io::{BufReader, Read, Seek};
use super::parser::Parser;
use super::fourcc::{FourCC, ReadFourCC, FMT__SIG, DATA_SIG, BEXT_SIG, LIST_SIG,
JUNK_SIG, FLLR_SIG, CUE__SIG, ADTL_SIG, AXML_SIG, IXML_SIG};
use super::errors::Error as ParserError;
use super::fmt::{WaveFmt, ChannelDescriptor, ChannelMask};
use super::bext::Bext;
use super::chunks::ReadBWaveChunks;
use super::cue::Cue;
use super::errors::Error as ParserError;
use super::errors::Error;
use super::fmt::{ChannelDescriptor, ChannelMask, WaveFmt};
use super::fourcc::{
FourCC, ReadFourCC, ADTL_SIG, AXML_SIG, BEXT_SIG, CUE__SIG, DATA_SIG, FLLR_SIG, FMT__SIG,
IXML_SIG, JUNK_SIG, LIST_SIG,
};
use super::parser::Parser;
use super::CommonFormat;
use byteorder::LittleEndian;
use byteorder::ReadBytesExt;
/// Read audio frames
///
///
/// The inner reader is interpreted as a raw audio data
/// bitstream having a format specified by `format`.
///
///
#[derive(Debug)]
pub struct AudioFrameReader<R: Read + Seek> {
inner : R,
inner: R,
format: WaveFmt,
start: u64,
length: u64
length: u64,
}
impl<R: Read + Seek> AudioFrameReader<R> {
/// Create a new `AudioFrameReader`
///
///
/// ### Panics
///
///
/// This method does a few sanity checks on the provided format
/// parameter to confirm the `block_alignment` law is fulfilled
/// and the format tag is readable by this implementation (only
/// format 0x01 is supported at this time.)
/// format 0x01 is supported at this time.)
pub fn new(mut inner: R, format: WaveFmt, start: u64, length: u64) -> Result<Self, Error> {
assert!(format.block_alignment * 8 == format.bits_per_sample * format.channel_count,
assert!(
format.block_alignment * 8 == format.bits_per_sample * format.channel_count,
"Unable to read audio frames from packed formats: block alignment is {}, should be {}",
format.block_alignment, (format.bits_per_sample / 8 ) * format.channel_count);
assert!(format.common_format() == CommonFormat::IntegerPCM || format.common_format() == CommonFormat::IeeeFloatPCM,
"Unsupported format tag {:?}", format.tag);
format.block_alignment,
(format.bits_per_sample / 8) * format.channel_count
);
assert!(
format.common_format() == CommonFormat::IntegerPCM
|| format.common_format() == CommonFormat::IeeeFloatPCM,
"Unsupported format tag {:?}",
format.tag
);
inner.seek(Start(start))?;
Ok( AudioFrameReader { inner , format , start, length} )
Ok(AudioFrameReader {
inner,
format,
start,
length,
})
}
/// Unwrap the inner reader.
@@ -63,42 +75,44 @@ impl<R: Read + Seek> AudioFrameReader<R> {
}
/// Locate the read position to a different frame
///
///
/// Seeks within the audio stream.
///
///
/// Returns the new location of the read position.
///
///
/// locate() behaves similarly to Read methods in that
/// seeking after the end of the audio data is not an error.
pub fn locate(&mut self, to :u64) -> Result<u64,Error> {
pub fn locate(&mut self, to: u64) -> Result<u64, Error> {
let position = to * self.format.block_alignment as u64;
let seek_result = self.inner.seek(Start(self.start + position))?;
Ok( (seek_result - self.start) / self.format.block_alignment as u64 )
Ok((seek_result - self.start) / self.format.block_alignment as u64)
}
/// Read a frame
///
///
/// A single frame is read from the audio stream and the read location
/// is advanced one frame.
///
///
/// Regardless of the number of bits in the audio sample, this method
/// always writes `i32` samples back to the buffer. These samples are
/// always writes `i32` samples back to the buffer. These samples are
/// written back "right-aligned" so samples that are shorter than i32
/// will leave the MSB bits empty.
///
/// For example: A full-code sample in 16 bit (0xFFFF) will be written
///
/// For example: A full-code sample in 16 bit (0xFFFF) will be written
/// back to the buffer as 0x0000FFFF.
///
///
///
/// ### Panics
///
/// The `buffer` must have a number of elements equal to the number of
///
/// The `buffer` must have a number of elements equal to the number of
/// channels and this method will panic if this is not the case.
pub fn read_integer_frame(&mut self, buffer:&mut [i32]) -> Result<u64,Error> {
assert!(buffer.len() as u16 == self.format.channel_count,
"read_integer_frame was called with a mis-sized buffer, expected {}, was {}",
self.format.channel_count, buffer.len());
pub fn read_integer_frame(&mut self, buffer: &mut [i32]) -> Result<u64, Error> {
assert!(
buffer.len() as u16 == self.format.channel_count,
"read_integer_frame was called with a mis-sized buffer, expected {}, was {}",
self.format.channel_count,
buffer.len()
);
let framed_bits_per_sample = self.format.block_alignment * 8 / self.format.channel_count;
@@ -115,16 +129,19 @@ impl<R: Read + Seek> AudioFrameReader<R> {
b, self.format.channel_count, self.format.block_alignment)
}
}
Ok( 1 )
Ok(1)
} else {
Ok( 0 )
Ok(0)
}
}
pub fn read_float_frame(&mut self, buffer: &mut [f32]) -> Result<u64, Error> {
assert!(buffer.len() as u16 == self.format.channel_count,
assert!(
buffer.len() as u16 == self.format.channel_count,
"read_float_frame was called with a mis-sized buffer, expected {}, was {}",
self.format.channel_count, buffer.len());
self.format.channel_count,
buffer.len()
);
let framed_bits_per_sample = self.format.block_alignment * 8 / self.format.channel_count;
@@ -138,9 +155,9 @@ impl<R: Read + Seek> AudioFrameReader<R> {
b, self.format.channel_count, self.format.block_alignment)
}
}
Ok( 1 )
Ok(1)
} else {
Ok( 0 )
Ok(0)
}
}
}
@@ -148,7 +165,7 @@ impl<R: Read + Seek> AudioFrameReader<R> {
/// Wave, Broadcast-WAV and RF64/BW64 parser/reader.
///
/// ```
/// use bwavfile::WaveReader;
/// use bwavfile::WaveReader;
/// let mut r = WaveReader::open("tests/media/ff_silence.wav").unwrap();
///
/// let format = r.format().unwrap();
@@ -159,17 +176,17 @@ impl<R: Read + Seek> AudioFrameReader<R> {
/// let mut buffer = format.create_frame_buffer(1);
///
/// let read = frame_reader.read_integer_frame(&mut buffer).unwrap();
///
///
/// assert_eq!(buffer, [0i32]);
/// assert_eq!(read, 1);
///
///
/// ```
///
///
/// ## Resources
///
///
/// ### Implementation of Wave Files
/// - [Peter Kabal, McGill University](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html)
/// - [Multimedia Programming Interface and Data Specifications 1.0](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf)
/// - [Multimedia Programming Interface and Data Specifications 1.0](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf)
/// (August 1991), IBM Corporation and Microsoft Corporation
///
/// ### Implementation of Broadcast Wave Files
@@ -182,14 +199,13 @@ impl<R: Read + Seek> AudioFrameReader<R> {
/// - Presently in force, adopted by the EBU in [EBU Tech 3306v2][ebu3306v2] (June 2018).
/// - [EBU Tech 3306v1][ebu3306v1] (July 2009), "MBWF / RF64: An extended File Format for Audio"
/// - No longer in force, however long-established.
///
///
///
/// [ebu3285]: https://tech.ebu.ch/docs/tech/tech3285.pdf
/// [ebu3306v1]: https://tech.ebu.ch/docs/tech/tech3306v1_1.pdf
/// [ebu3306v2]: https://tech.ebu.ch/docs/tech/tech3306.pdf
/// [itu2088]: https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.2088-1-201910-I!!PDF-E.pdf
/// [rfc3261]: https://tools.ietf.org/html/rfc2361
/// [rfc3261]: https://tools.ietf.org/html/rfc2361
#[derive(Debug)]
pub struct WaveReader<R: Read + Seek> {
@@ -197,40 +213,37 @@ pub struct WaveReader<R: Read + Seek> {
}
impl WaveReader<BufReader<File>> {
pub fn open(path: &str) -> Result<Self, ParserError> {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, ParserError> {
let f = File::open(path)?;
let inner = BufReader::new(f);
Ok( Self::new(inner)? )
Ok(Self::new(inner)?)
}
}
impl WaveReader<File> {
/// Open a file for reading with unbuffered IO.
///
/// A convenience that opens `path` and calls `Self::new()`
pub fn open_unbuffered(path: &str) -> Result<Self, ParserError> {
/// Open a file for reading with unbuffered IO.
///
/// A convenience that opens `path` and calls `Self::new()`
pub fn open_unbuffered<P: AsRef<Path>>(path: P) -> Result<Self, ParserError> {
let inner = File::open(path)?;
return Ok( Self::new(inner)? )
return Ok(Self::new(inner)?);
}
}
impl<R: Read + Seek> WaveReader<R> {
/// Wrap a `Read` struct in a new `WaveReader`.
///
///
/// This is the primary entry point into the `WaveReader` interface. The
/// stream passed as `inner` must be at the beginning of the header of the
/// WAVE data. For a .wav file, this means it must be at the start of the
/// WAVE data. For a .wav file, this means it must be at the start of the
/// file.
///
/// This function does a minimal validation on the provided stream and
/// will return an `Err(errors::Error)` immediately if there is a structural
/// inconsistency that makes the stream unreadable or if it's missing
/// will return an `Err(errors::Error)` immediately if there is a structural
/// inconsistency that makes the stream unreadable or if it's missing
/// essential components that make interpreting the audio data impossible.
/// ```rust
/// use std::fs::File;
/// use std::io::{Error,ErrorKind};
@@ -247,15 +260,14 @@ impl<R: Read + Seek> WaveReader<R> {
/// }
/// Err(e) => panic!("Unexpected error was returned {:?}", e)
/// }
///
///
/// ```
pub fn new(inner: R) -> Result<Self,ParserError> {
pub fn new(inner: R) -> Result<Self, ParserError> {
let mut retval = Self { inner };
retval.validate_readable()?;
Ok(retval)
}
/// Unwrap the inner reader.
pub fn into_inner(self) -> R {
return self.inner;
@@ -267,18 +279,21 @@ impl<R: Read + Seek> WaveReader<R> {
pub fn audio_frame_reader(mut self) -> Result<AudioFrameReader<R>, ParserError> {
let format = self.format()?;
let audio_chunk_reader = self.get_chunk_extent_at_index(DATA_SIG, 0)?;
Ok(AudioFrameReader::new(self.inner, format, audio_chunk_reader.0, audio_chunk_reader.1)?)
Ok(AudioFrameReader::new(
self.inner,
format,
audio_chunk_reader.0,
audio_chunk_reader.1,
)?)
}
/// The count of audio frames in the file.
pub fn frame_length(&mut self) -> Result<u64, ParserError> {
let (_, data_length ) = self.get_chunk_extent_at_index(DATA_SIG, 0)?;
let (_, data_length) = self.get_chunk_extent_at_index(DATA_SIG, 0)?;
let format = self.format()?;
Ok( data_length / (format.block_alignment as u64) )
}
Ok(data_length / (format.block_alignment as u64))
}
/// Sample and frame format of this wave file.
///
pub fn format(&mut self) -> Result<WaveFmt, ParserError> {
@@ -288,29 +303,28 @@ impl<R: Read + Seek> WaveReader<R> {
}
/// The Broadcast-WAV metadata record for this file, if present.
///
///
pub fn broadcast_extension(&mut self) -> Result<Option<Bext>, ParserError> {
let mut bext_buff : Vec<u8> = vec![ ];
let mut bext_buff: Vec<u8> = vec![];
let result = self.read_chunk(BEXT_SIG, 0, &mut bext_buff)?;
if result > 0 {
let mut bext_cursor = Cursor::new(bext_buff);
Ok( Some( bext_cursor.read_bext()? ) )
Ok(Some(bext_cursor.read_bext()?))
} else {
Ok( None)
Ok(None)
}
}
/// Describe the channels in this file
///
///
/// Returns a vector of channel descriptors, one for each channel
///
///
/// ```rust
/// use bwavfile::WaveReader;
/// use bwavfile::ChannelMask;
///
/// let mut f = WaveReader::open("tests/media/pt_24bit_51.wav").unwrap();
///
///
/// let chans = f.channels().unwrap();
/// assert_eq!(chans[0].index, 0);
/// assert_eq!(chans[0].speaker, ChannelMask::FrontLeft);
@@ -319,97 +333,100 @@ impl<R: Read + Seek> WaveReader<R> {
/// assert_eq!(chans[4].speaker, ChannelMask::BackLeft);
/// ```
pub fn channels(&mut self) -> Result<Vec<ChannelDescriptor>, ParserError> {
let format = self.format()?;
let channel_masks : Vec<ChannelMask> = match (format.channel_count, format.extended_format) {
(1,_) => vec![ChannelMask::FrontCenter],
(2,_) => vec![ChannelMask::FrontLeft, ChannelMask::FrontRight],
(n,Some(x)) => ChannelMask::channels(x.channel_mask, n),
(n,_) => vec![ChannelMask::DirectOut; n as usize]
let channel_masks: Vec<ChannelMask> = match (format.channel_count, format.extended_format) {
(1, _) => vec![ChannelMask::FrontCenter],
(2, _) => vec![ChannelMask::FrontLeft, ChannelMask::FrontRight],
(n, Some(x)) => ChannelMask::channels(x.channel_mask, n),
(n, _) => vec![ChannelMask::DirectOut; n as usize],
};
Ok( (0..format.channel_count).zip(channel_masks)
.map(|(i,m)| ChannelDescriptor { index: i, speaker:m, adm_track_audio_ids: vec![] } )
.collect() )
Ok((0..format.channel_count)
.zip(channel_masks)
.map(|(i, m)| ChannelDescriptor {
index: i,
speaker: m,
adm_track_audio_ids: vec![],
})
.collect())
}
/// Read cue points.
///
///
/// ```rust
/// use bwavfile::WaveReader;
/// use bwavfile::Cue;
///
///
/// let mut f = WaveReader::open("tests/media/izotope_test.wav").unwrap();
/// let cue_points = f.cue_points().unwrap();
///
///
/// assert_eq!(cue_points.len(), 3);
/// assert_eq!(cue_points[0].frame, 12532);
/// assert_eq!(cue_points[0].length, None);
/// assert_eq!(cue_points[0].label, Some(String::from("Marker 1")));
/// assert_eq!(cue_points[0].note, Some(String::from("Marker 1 Comment")));
///
///
/// assert_eq!(cue_points[1].frame, 20997);
/// assert_eq!(cue_points[1].length, None);
/// assert_eq!(cue_points[1].label, Some(String::from("Marker 2")));
/// assert_eq!(cue_points[1].note, Some(String::from("Marker 2 Comment")));
///
/// assert_eq!(cue_points[1].note, Some(String::from("Marker 2 Comment")));
///
/// assert_eq!(cue_points[2].frame, 26711);
/// assert_eq!(cue_points[2].length, Some(6465));
/// assert_eq!(cue_points[2].label, Some(String::from("Timed Region")));
/// assert_eq!(cue_points[2].note, Some(String::from("Region Comment")));
///
/// assert_eq!(cue_points[2].note, Some(String::from("Region Comment")));
///
/// ```
pub fn cue_points(&mut self) -> Result<Vec<Cue>,ParserError> {
let mut cue_buffer : Vec<u8> = vec![];
let mut adtl_buffer : Vec<u8> = vec![];
pub fn cue_points(&mut self) -> Result<Vec<Cue>, ParserError> {
let mut cue_buffer: Vec<u8> = vec![];
let mut adtl_buffer: Vec<u8> = vec![];
let cue_read = self.read_chunk(CUE__SIG, 0, &mut cue_buffer)?;
let adtl_read = self.read_list(ADTL_SIG, &mut adtl_buffer)?;
match (cue_read, adtl_read) {
(0,_) => Ok( vec![] ),
(_,0) => Ok( Cue::collect_from(&cue_buffer, None)? ),
(_,_) => Ok( Cue::collect_from(&cue_buffer, Some(&adtl_buffer) )? )
(0, _) => Ok(vec![]),
(_, 0) => Ok(Cue::collect_from(&cue_buffer, None)?),
(_, _) => Ok(Cue::collect_from(&cue_buffer, Some(&adtl_buffer))?),
}
}
/// Read iXML data.
///
///
/// The iXML data will be appended to `buffer`.
/// If there are no iXML metadata present in the file,
/// If there are no iXML metadata present in the file,
/// Ok(0) will be returned.
pub fn read_ixml(&mut self, buffer: &mut Vec<u8>) -> Result<usize, ParserError> {
self.read_chunk(IXML_SIG, 0, buffer)
self.read_chunk(IXML_SIG, 0, buffer)
}
/// Read AXML data.
///
/// The axml data will be appended to `buffer`. By convention this will
///
/// The axml data will be appended to `buffer`. By convention this will
/// generally be ADM metadata.
///
/// If there are no axml metadata present in the file,
///
/// If there are no axml metadata present in the file,
/// Ok(0) will be returned
pub fn read_axml(&mut self, buffer: &mut Vec<u8>) -> Result<usize, ParserError> {
self.read_chunk(AXML_SIG, 0, buffer)
}
/**
* Validate file is readable.
*
* `Ok(())` if the source meets the minimum standard of
* readability by a permissive client:
* - `fmt` chunk and `data` chunk are present
* - `fmt` chunk appears before `data` chunk
*/
* Validate file is readable.
*
* `Ok(())` if the source meets the minimum standard of
* readability by a permissive client:
* - `fmt` chunk and `data` chunk are present
* - `fmt` chunk appears before `data` chunk
*/
pub fn validate_readable(&mut self) -> Result<(), ParserError> {
let (fmt_pos, _) = self.get_chunk_extent_at_index(FMT__SIG, 0)?;
let (fmt_pos, _) = self.get_chunk_extent_at_index(FMT__SIG, 0)?;
let (data_pos, _) = self.get_chunk_extent_at_index(DATA_SIG, 0)?;
if fmt_pos < data_pos {
Ok(())
} else {
Err( ParserError::FmtChunkAfterData)
Err(ParserError::FmtChunkAfterData)
}
}
@@ -440,35 +457,38 @@ impl<R: Read + Seek> WaveReader<R> {
/// let mut x = WaveReader::open("tests/media/pt_24bit_51.wav").unwrap();
/// x.validate_minimal().expect_err("Complex WAV validated minimal!");
/// ```
pub fn validate_minimal(&mut self) -> Result<(), ParserError> {
pub fn validate_minimal(&mut self) -> Result<(), ParserError> {
self.validate_readable()?;
let chunk_fourccs : Vec<FourCC> = Parser::make(&mut self.inner)?
.into_chunk_list()?.iter().map(|c| c.signature ).collect();
let chunk_fourccs: Vec<FourCC> = Parser::make(&mut self.inner)?
.into_chunk_list()?
.iter()
.map(|c| c.signature)
.collect();
if chunk_fourccs == vec![FMT__SIG, DATA_SIG] {
Ok(()) /* FIXME: finish implementation */
} else {
Err( ParserError::NotMinimalWaveFile )
Err(ParserError::NotMinimalWaveFile)
}
}
/// Validate Broadcast-WAVE file format
///
/// Returns `Ok(())` if `validate_readable()` and file contains a
///
/// Returns `Ok(())` if `validate_readable()` and file contains a
/// Broadcast-WAV metadata record (a `bext` chunk).
///
///
/// ### Examples
///
///
/// ```
/// # use bwavfile::WaveReader;
///
///
/// let mut w = WaveReader::open("tests/media/ff_bwav_stereo.wav").unwrap();
/// w.validate_broadcast_wave().expect("BWAVE file did not validate BWAVE");
///
///
/// let mut x = WaveReader::open("tests/media/pt_24bit.wav").unwrap();
/// x.validate_broadcast_wave().expect("BWAVE file did not validate BWAVE");
///
///
/// let mut y = WaveReader::open("tests/media/audacity_16bit.wav").unwrap();
/// y.validate_broadcast_wave().expect_err("Plain WAV file DID validate BWAVE");
/// ```
@@ -477,14 +497,14 @@ impl<R: Read + Seek> WaveReader<R> {
self.validate_readable()?;
let (_, _) = self.get_chunk_extent_at_index(BEXT_SIG, 0)?;
Ok(())
}
}
///
/// Verify data is aligned to a block boundary.
///
/// Returns `Ok(())` if `validate_readable()` and the start of the
/// Returns `Ok(())` if `validate_readable()` and the start of the
/// `data` chunk's content begins at 0x4000.
pub fn validate_data_chunk_alignment(&mut self) -> Result<() , ParserError> {
pub fn validate_data_chunk_alignment(&mut self) -> Result<(), ParserError> {
self.validate_readable()?;
let (start, _) = self.get_chunk_extent_at_index(DATA_SIG, 0)?;
if start == 0x4000 {
@@ -495,10 +515,10 @@ impl<R: Read + Seek> WaveReader<R> {
}
/// Verify audio data can be appended immediately to this file.
///
///
/// Returns `Ok(())` if:
/// - `validate_readable()`
/// - there is a `JUNK` or `FLLR` immediately at the beginning of the chunk
/// - there is a `JUNK` or `FLLR` immediately at the beginning of the chunk
/// list adequately large enough to be overwritten by a `ds64` (92 bytes)
/// - `data` is the final chunk
pub fn validate_prepared_for_append(&mut self) -> Result<(), ParserError> {
@@ -507,66 +527,82 @@ impl<R: Read + Seek> WaveReader<R> {
let chunks = Parser::make(&mut self.inner)?.into_chunk_list()?;
let ds64_space_required = 92;
let eligible_filler_chunks = chunks.iter()
let eligible_filler_chunks = chunks
.iter()
.take_while(|c| c.signature == JUNK_SIG || c.signature == FLLR_SIG);
let filler = eligible_filler_chunks
.enumerate()
.fold(0, |accum, (n, item)| if n == 0 { accum + item.length } else {accum + item.length + 8});
.fold(0, |accum, (n, item)| {
if n == 0 {
accum + item.length
} else {
accum + item.length + 8
}
});
if filler < ds64_space_required {
Err(ParserError::InsufficientDS64Reservation {expected: ds64_space_required, actual: filler})
Err(ParserError::InsufficientDS64Reservation {
expected: ds64_space_required,
actual: filler,
})
} else {
let data_pos = chunks.iter().position(|c| c.signature == DATA_SIG);
match data_pos {
Some(p) if p == chunks.len() - 1 => Ok(()),
_ => Err(ParserError::DataChunkNotPreparedForAppend)
_ => Err(ParserError::DataChunkNotPreparedForAppend),
}
}
}
}
impl<R:Read+Seek> WaveReader<R> {
impl<R: Read + Seek> WaveReader<R> {
// Private implementation
//
// As time passes thi get smore obnoxious because I haven't implemented recursive chunk
// As time passes thi get smore obnoxious because I haven't implemented recursive chunk
// parsing in the raw parser and I'm working around it
// fn chunk_reader(&mut self, signature: FourCC, at_index: u32) -> Result<RawChunkReader<R>, ParserError> {
// let (start, length) = self.get_chunk_extent_at_index(signature, at_index)?;
// Ok( RawChunkReader::new(&mut self.inner, start, length) )
// }
// }
fn read_list(&mut self, ident: FourCC, buffer: &mut Vec<u8>) -> Result<usize, ParserError> {
if let Some(index) = self.get_list_form(ident)? {
self.read_chunk(LIST_SIG, index, buffer)
} else {
Ok( 0 )
Ok(0)
}
}
fn read_chunk(&mut self, ident: FourCC, at: u32, mut buffer: &mut Vec<u8>) -> Result<usize, ParserError> {
fn read_chunk(
&mut self,
ident: FourCC,
at: u32,
mut buffer: &mut Vec<u8>,
) -> Result<usize, ParserError> {
match self.get_chunk_extent_at_index(ident, at) {
Ok((start, length)) => {
buffer.resize(length as usize, 0x0);
self.inner.seek(SeekFrom::Start(start))?;
self.inner.read(&mut buffer).map_err(|e| ParserError::IOError(e))
},
Err(ParserError::ChunkMissing { signature : _} ) => Ok(0),
Err( any ) => Err(any.into())
self.inner
.read(&mut buffer)
.map_err(|e| ParserError::IOError(e))
}
Err(ParserError::ChunkMissing { signature: _ }) => Ok(0),
Err(any) => Err(any.into()),
}
}
/// Extent of every chunk with the given fourcc
fn get_chunks_extents(&mut self, fourcc: FourCC) -> Result<Vec<(u64,u64)>, ParserError> {
fn get_chunks_extents(&mut self, fourcc: FourCC) -> Result<Vec<(u64, u64)>, ParserError> {
let p = Parser::make(&mut self.inner)?.into_chunk_list()?;
Ok( p.iter().filter(|item| item.signature == fourcc)
.map(|item| (item.start, item.length)).collect() )
Ok(p.iter()
.filter(|item| item.signature == fourcc)
.map(|item| (item.start, item.length))
.collect())
}
/// Index of first LIST for with the given FORM fourcc
@@ -575,18 +611,22 @@ impl<R:Read+Seek> WaveReader<R> {
self.inner.seek(SeekFrom::Start(*start as u64))?;
let this_fourcc = self.inner.read_fourcc()?;
if this_fourcc == fourcc {
return Ok( Some( n as u32 ) );
return Ok(Some(n as u32));
}
}
Ok( None )
Ok(None)
}
fn get_chunk_extent_at_index(&mut self, fourcc: FourCC, index: u32) -> Result<(u64,u64), ParserError> {
fn get_chunk_extent_at_index(
&mut self,
fourcc: FourCC,
index: u32,
) -> Result<(u64, u64), ParserError> {
if let Some((start, length)) = self.get_chunks_extents(fourcc)?.iter().nth(index as usize) {
Ok ((*start, *length))
Ok((*start, *length))
} else {
Err( ParserError::ChunkMissing { signature : fourcc } )
Err(ParserError::ChunkMissing { signature: fourcc })
}
}
}
@@ -594,10 +634,9 @@ impl<R:Read+Seek> WaveReader<R> {
#[test]
fn test_list_form() {
let mut f = WaveReader::open("tests/media/izotope_test.wav").unwrap();
let mut buf : Vec<u8> = vec![];
let mut buf: Vec<u8> = vec![];
f.read_list(ADTL_SIG, &mut buf).unwrap();
assert_ne!(buf.len(), 0);
}
assert_ne!(buf.len(), 0);
}

View File

@@ -1,46 +1,58 @@
use std::fs::File;
use std::io::{Write,Seek,SeekFrom,Cursor,BufWriter};
use std::io::{BufWriter, Cursor, Seek, SeekFrom, Write};
use std::path::Path;
use super::Error;
use super::fourcc::{FourCC, WriteFourCC, RIFF_SIG, RF64_SIG, DS64_SIG,
WAVE_SIG, FMT__SIG, DATA_SIG, ELM1_SIG, JUNK_SIG, BEXT_SIG,AXML_SIG,
IXML_SIG};
use super::fmt::WaveFmt;
use super::fourcc::{
FourCC, WriteFourCC, AXML_SIG, BEXT_SIG, DATA_SIG, DS64_SIG, ELM1_SIG, FMT__SIG, IXML_SIG,
JUNK_SIG, RF64_SIG, RIFF_SIG, WAVE_SIG,
};
use super::Error;
//use super::common_format::CommonFormat;
use super::chunks::WriteBWaveChunks;
use super::bext::Bext;
use super::chunks::WriteBWaveChunks;
use byteorder::LittleEndian;
use byteorder::WriteBytesExt;
/// Write audio frames to a `WaveWriter`.
///
///
pub struct AudioFrameWriter<W> where W: Write + Seek {
inner : WaveChunkWriter<W>
///
///
pub struct AudioFrameWriter<W>
where
W: Write + Seek,
{
inner: WaveChunkWriter<W>,
}
impl<W> AudioFrameWriter<W> where W: Write + Seek {
impl<W> AudioFrameWriter<W>
where
W: Write + Seek,
{
fn new(inner: WaveChunkWriter<W>) -> Self {
AudioFrameWriter { inner }
}
fn write_integer_frames_to_buffer(&self, from_frames :&[i32], to_buffer : &mut [u8]) -> () {
assert!(from_frames.len() % self.inner.inner.format.channel_count as usize == 0,
"frames buffer does not contain a number of samples % channel_count == 0");
fn write_integer_frames_to_buffer(&self, from_frames: &[i32], to_buffer: &mut [u8]) -> () {
assert!(
from_frames.len() % self.inner.inner.format.channel_count as usize == 0,
"frames buffer does not contain a number of samples % channel_count == 0"
);
self.inner.inner.format.pack_frames(&from_frames, to_buffer);
()
}
/// Write interleaved samples in `buffer`
///
///
/// # Panics
///
///
/// This function will panic if `buffer.len()` modulo the Wave file's channel count
/// is not zero.
pub fn write_integer_frames(&mut self, buffer: &[i32]) -> Result<u64,Error> {
let mut write_buffer = self.inner.inner.format
pub fn write_integer_frames(&mut self, buffer: &[i32]) -> Result<u64, Error> {
let mut write_buffer = self
.inner
.inner
.format
.create_raw_buffer(buffer.len() / self.inner.inner.format.channel_count as usize);
self.write_integer_frames_to_buffer(&buffer, &mut write_buffer);
@@ -51,7 +63,7 @@ impl<W> AudioFrameWriter<W> where W: Write + Seek {
}
/// Finish writing audio frames and unwrap the inner `WaveWriter`.
///
///
/// This method must be called when the client has finished writing audio
/// data. This will finalize the audio data chunk.
pub fn end(self) -> Result<WaveWriter<W>, Error> {
@@ -60,29 +72,39 @@ impl<W> AudioFrameWriter<W> where W: Write + Seek {
}
/// Write a wave data chunk.
///
///
/// `WaveChunkWriter` implements `Write` and as bytes are written to it,
///
/// ### Important!
///
/// When you are done writing to a chunk you must call `end()` in order to
///
/// ### Important!
///
/// When you are done writing to a chunk you must call `end()` in order to
/// finalize the chunk for storage.
pub struct WaveChunkWriter<W> where W: Write + Seek {
ident : FourCC,
inner : WaveWriter<W>,
content_start_pos : u64,
length : u64
pub struct WaveChunkWriter<W>
where
W: Write + Seek,
{
ident: FourCC,
inner: WaveWriter<W>,
content_start_pos: u64,
length: u64,
}
impl<W> WaveChunkWriter<W> where W: Write + Seek {
fn begin(mut inner : WaveWriter<W>, ident : FourCC) -> Result<Self,Error> {
let length : u64 = 0;
impl<W> WaveChunkWriter<W>
where
W: Write + Seek,
{
fn begin(mut inner: WaveWriter<W>, ident: FourCC) -> Result<Self, Error> {
let length: u64 = 0;
inner.inner.write_fourcc(ident)?;
inner.inner.write_u32::<LittleEndian>(length as u32)?;
inner.increment_form_length(8)?;
let content_start_pos = inner.inner.seek(SeekFrom::End(0))?;
Ok( WaveChunkWriter { ident, inner , content_start_pos, length } )
Ok(WaveChunkWriter {
ident,
inner,
content_start_pos,
length,
})
}
fn end(mut self) -> Result<WaveWriter<W>, Error> {
@@ -91,75 +113,84 @@ impl<W> WaveChunkWriter<W> where W: Write + Seek {
self.inner.inner.write(&[0u8])?;
self.inner.increment_form_length(1)?;
}
Ok( self.inner )
Ok(self.inner)
}
fn increment_chunk_length(&mut self, amount: u64) -> Result<(), std::io::Error> {
self.length = self.length + amount;
if !self.inner.is_rf64 {
self.inner.inner.seek(SeekFrom::Start(self.content_start_pos - 4))?;
self.inner.inner.write_u32::<LittleEndian>(self.length as u32)?;
self.inner
.inner
.seek(SeekFrom::Start(self.content_start_pos - 4))?;
self.inner
.inner
.write_u32::<LittleEndian>(self.length as u32)?;
} else {
if self.ident == DATA_SIG {
let data_chunk_64bit_field_offset = 8 + 4 + 8 + 8;
self.inner.inner.seek(SeekFrom::Start(self.content_start_pos - 4))?;
self.inner.inner.write_u32::<LittleEndian>(0xFFFF_FFFF)?;
// this only need to happen once, not every time we increment
self.inner
.inner
.seek(SeekFrom::Start(self.content_start_pos - 4))?;
self.inner.inner.write_u32::<LittleEndian>(0xFFFF_FFFF)?;
// this only need to happen once, not every time we increment
self.inner.inner.seek(SeekFrom::Start(data_chunk_64bit_field_offset))?;
self.inner
.inner
.seek(SeekFrom::Start(data_chunk_64bit_field_offset))?;
self.inner.inner.write_u64::<LittleEndian>(self.length)?;
} else {
todo!("FIXME RF64 wave writing is not yet supported for chunks other than `data`")
}
}
Ok(())
}
}
impl<W> Write for WaveChunkWriter<W> where W: Write + Seek {
fn write(&mut self, buffer: &[u8]) -> Result<usize, std::io::Error> {
impl<W> Write for WaveChunkWriter<W>
where
W: Write + Seek,
{
fn write(&mut self, buffer: &[u8]) -> Result<usize, std::io::Error> {
self.inner.inner.seek(SeekFrom::End(0))?;
let written = self.inner.inner.write(buffer)?;
self.inner.increment_form_length(written as u64)?;
self.increment_chunk_length(written as u64)?;
Ok( written )
Ok(written)
}
fn flush(&mut self) -> Result<(), std::io::Error> {
fn flush(&mut self) -> Result<(), std::io::Error> {
self.inner.inner.flush()
}
}
/// Wave, Broadcast-WAV and RF64/BW64 writer.
///
///
/// A `WaveWriter` creates a new wave file at the given path (with `create()`)
/// or into the given `Write`- and `Seek`-able inner writer.
///
///
/// Audio is added to the wave file by starting the audio data chunk with
/// `WaveWriter::audio_frame_writer()`. All of the functions that add chunks
/// move the WaveWriter and return it to the host when complete.
///
///
/// # Structure of New Wave Files
///
///
/// `WaveWriter` will create a Wave file with two chunks automatically: a 96
/// byte `JUNK` chunk and a standard `fmt ` chunk, which has the extended
/// length if the format your provided requires it. The first `JUNK` chunk is
/// byte `JUNK` chunk and a standard `fmt ` chunk, which has the extended
/// length if the format your provided requires it. The first `JUNK` chunk is
/// a reservation for a `ds64` record which will be written over it if
/// the file needs to be upgraded to RF64 format.
///
/// Chunks are added to the file in the order the client adds them.
///
/// Chunks are added to the file in the order the client adds them.
/// `audio_file_writer()` will add a `data` chunk for the audio data, and will
/// also add an `elm1` filler chunk prior to the data chunk to ensure that the
/// also add an `elm1` filler chunk prior to the data chunk to ensure that the
/// first byte of the data chunk's content is aligned with 0x4000.
///
///
/// ```
/// use bwavfile::{WaveWriter,WaveFmt};
/// # use std::io::Cursor;
///
///
/// // Write a three-sample wave file to a cursor
/// let mut cursor = Cursor::new(vec![0u8;0]);
/// let format = WaveFmt::new_pcm_mono(48000, 24);
@@ -174,10 +205,10 @@ impl<W> Write for WaveChunkWriter<W> where W: Write + Seek {
/// ```
///
/// ## Resources
///
///
/// ### Implementation of Wave Files
/// - [Peter Kabal, McGill University](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html)
/// - [Multimedia Programming Interface and Data Specifications 1.0](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf)
/// - [Multimedia Programming Interface and Data Specifications 1.0](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf)
/// (August 1991), IBM Corporation and Microsoft Corporation
///
/// ### Implementation of Broadcast Wave Files
@@ -190,57 +221,66 @@ impl<W> Write for WaveChunkWriter<W> where W: Write + Seek {
/// - Presently in force, adopted by the EBU in [EBU Tech 3306v2][ebu3306v2] (June 2018).
/// - [EBU Tech 3306v1][ebu3306v1] (July 2009), "MBWF / RF64: An extended File Format for Audio"
/// - No longer in force, however long-established.
///
///
///
/// [ebu3285]: https://tech.ebu.ch/docs/tech/tech3285.pdf
/// [ebu3306v1]: https://tech.ebu.ch/docs/tech/tech3306v1_1.pdf
/// [ebu3306v2]: https://tech.ebu.ch/docs/tech/tech3306.pdf
/// [itu2088]: https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.2088-1-201910-I!!PDF-E.pdf
/// [rfc3261]: https://tools.ietf.org/html/rfc2361
pub struct WaveWriter<W> where W: Write + Seek {
inner : W,
/// [rfc3261]: https://tools.ietf.org/html/rfc2361
pub struct WaveWriter<W>
where
W: Write + Seek,
{
inner: W,
form_length: u64,
/// True if file is RF64
pub is_rf64: bool,
/// Format of the wave file.
pub format: WaveFmt
pub format: WaveFmt,
}
const DS64_RESERVATION_LENGTH : u32 = 96;
const DS64_RESERVATION_LENGTH: u32 = 96;
impl WaveWriter<BufWriter<File>> {
/// Create a new Wave file at `path`.
pub fn create(path : &str, format : WaveFmt) -> Result<Self, Error> {
pub fn create<P: AsRef<Path>>(path: P, format: WaveFmt) -> Result<Self, Error> {
let f = File::create(path)?;
let b = BufWriter::new(f);
Ok( Self::new(b, format)? )
Ok(Self::new(b, format)?)
}
}
impl WaveWriter<File> {
/// Creare a new Wave file with unbuffered IO at `path`
pub fn create_unbuffered(path : &str, format : WaveFmt) -> Result<Self, Error> {
pub fn create_unbuffered<P: AsRef<Path>>(path: P, format: WaveFmt) -> Result<Self, Error> {
let f = File::create(path)?;
Ok( Self::new(f, format)? )
Ok(Self::new(f, format)?)
}
}
impl<W> WaveWriter<W> where W: Write + Seek {
impl<W> WaveWriter<W>
where
W: Write + Seek,
{
/// Wrap a writer in a Wave writer.
///
/// The inner writer will immediately have a RIFF WAVE file header
///
/// The inner writer will immediately have a RIFF WAVE file header
/// written to it along with the format descriptor (and possibly a `fact`
/// chunk if appropriate).
pub fn new(mut inner : W, format: WaveFmt) -> Result<Self, Error> {
pub fn new(mut inner: W, format: WaveFmt) -> Result<Self, Error> {
inner.write_fourcc(RIFF_SIG)?;
inner.write_u32::<LittleEndian>(0)?;
inner.write_fourcc(WAVE_SIG)?;
let mut retval = WaveWriter { inner, form_length: 0, is_rf64: false, format};
let mut retval = WaveWriter {
inner,
form_length: 0,
is_rf64: false,
format,
};
retval.increment_form_length(4)?;
@@ -251,10 +291,10 @@ impl<W> WaveWriter<W> where W: Write + Seek {
chunk.write_wave_fmt(&format)?;
let retval = chunk.end()?;
Ok( retval )
Ok(retval)
}
fn write_chunk(&mut self, ident: FourCC, data : &[u8]) -> Result<(),Error> {
fn write_chunk(&mut self, ident: FourCC, data: &[u8]) -> Result<(), Error> {
self.inner.seek(SeekFrom::End(0))?;
self.inner.write_fourcc(ident)?;
assert!(data.len() < u32::MAX as usize);
@@ -270,21 +310,21 @@ impl<W> WaveWriter<W> where W: Write + Seek {
}
/// Write Broadcast-Wave metadata to the file.
///
/// This function will write the metadata chunk immediately to the end of
/// the file; if you have already written and closed the audio data the
///
/// This function will write the metadata chunk immediately to the end of
/// the file; if you have already written and closed the audio data the
/// bext chunk will be positioned after it.
pub fn write_broadcast_metadata(&mut self, bext: &Bext) -> Result<(),Error> {
pub fn write_broadcast_metadata(&mut self, bext: &Bext) -> Result<(), Error> {
//FIXME Implement re-writing
let mut c = Cursor::new(vec![0u8; 0]);
c.write_bext(&bext)?;
let buf = c.into_inner();
self.write_chunk(BEXT_SIG, &buf )?;
self.write_chunk(BEXT_SIG, &buf)?;
Ok(())
}
/// Write iXML metadata
pub fn write_ixml(&mut self, ixml: &[u8]) -> Result<(),Error> {
pub fn write_ixml(&mut self, ixml: &[u8]) -> Result<(), Error> {
//FIXME Implement re-writing
self.write_chunk(IXML_SIG, &ixml)
}
@@ -301,7 +341,7 @@ impl<W> WaveWriter<W> where W: Write + Seek {
self.write_chunk(JUNK_SIG, &filler)
}
/// Create an audio frame writer, which takes possession of the callee
/// Create an audio frame writer, which takes possession of the callee
/// `WaveWriter`.
///
pub fn audio_frame_writer(mut self) -> Result<AudioFrameWriter<W>, Error> {
@@ -316,11 +356,11 @@ impl<W> WaveWriter<W> where W: Write + Seek {
chunk.write(&buf)?;
let closed = chunk.end()?;
let inner = closed.chunk(DATA_SIG)?;
Ok( AudioFrameWriter::new(inner) )
Ok(AudioFrameWriter::new(inner))
}
/// Open a wave chunk writer here
fn chunk(mut self, ident: FourCC) -> Result<WaveChunkWriter<W>,Error> {
fn chunk(mut self, ident: FourCC) -> Result<WaveChunkWriter<W>, Error> {
self.inner.seek(SeekFrom::End(0))?;
WaveChunkWriter::begin(self, ident)
}
@@ -349,10 +389,10 @@ impl<W> WaveWriter<W> where W: Write + Seek {
self.inner.write_u64::<LittleEndian>(self.form_length)?;
} else if self.form_length < u32::MAX as u64 {
self.inner.seek(SeekFrom::Start(4))?;
self.inner.write_u32::<LittleEndian>(self.form_length as u32)?;
self.inner
.write_u32::<LittleEndian>(self.form_length as u32)?;
} else {
self.promote_to_rf64()?;
}
Ok(())
}
@@ -360,11 +400,11 @@ impl<W> WaveWriter<W> where W: Write + Seek {
#[test]
fn test_new() {
use std::io::Cursor;
use super::fourcc::ReadFourCC;
use byteorder::ReadBytesExt;
use std::io::Cursor;
let mut cursor = Cursor::new(vec![0u8;0]);
let mut cursor = Cursor::new(vec![0u8; 0]);
let format = WaveFmt::new_pcm_mono(4800, 24);
WaveWriter::new(&mut cursor, format).unwrap();
@@ -376,7 +416,7 @@ fn test_new() {
assert_eq!(cursor.read_fourcc().unwrap(), JUNK_SIG);
let junk_size = cursor.read_u32::<LittleEndian>().unwrap();
assert_eq!(junk_size,96);
assert_eq!(junk_size, 96);
cursor.seek(SeekFrom::Current(junk_size as i64)).unwrap();
assert_eq!(cursor.read_fourcc().unwrap(), FMT__SIG);
@@ -386,14 +426,14 @@ fn test_new() {
#[test]
fn test_write_audio() {
use std::io::Cursor;
use super::fourcc::ReadFourCC;
use byteorder::ReadBytesExt;
use std::io::Cursor;
let mut cursor = Cursor::new(vec![0u8;0]);
let mut cursor = Cursor::new(vec![0u8; 0]);
let format = WaveFmt::new_pcm_mono(48000, 24);
let w = WaveWriter::new(&mut cursor, format).unwrap();
let mut frame_writer = w.audio_frame_writer().unwrap();
frame_writer.write_integer_frames(&[0i32]).unwrap();
@@ -429,14 +469,17 @@ fn test_write_audio() {
let tell = cursor.seek(SeekFrom::Current(0)).unwrap();
assert!(tell % 0x4000 == 0);
assert_eq!(form_size, 4 + 8 + junk_size + 8 + fmt_size + 8 + elm1_size + 8 + data_size + data_size % 2)
assert_eq!(
form_size,
4 + 8 + junk_size + 8 + fmt_size + 8 + elm1_size + 8 + data_size + data_size % 2
)
}
#[test]
fn test_write_bext() {
use std::io::Cursor;
let mut cursor = Cursor::new(vec![0u8;0]);
let mut cursor = Cursor::new(vec![0u8; 0]);
let format = WaveFmt::new_pcm_mono(48000, 24);
let mut w = WaveWriter::new(&mut cursor, format).unwrap();
@@ -468,14 +511,13 @@ fn test_write_bext() {
frame_writer.end().unwrap();
}
// NOTE! This test of RF64 writing takes several minutes to complete.
#[test]
fn test_create_rf64() {
use super::fourcc::ReadFourCC;
use byteorder::ReadBytesExt;
let mut cursor = Cursor::new(vec![0u8;0]);
let mut cursor = Cursor::new(vec![0u8; 0]);
let format = WaveFmt::new_pcm_stereo(48000, 24);
let w = WaveWriter::new(&mut cursor, format).unwrap();
@@ -492,7 +534,10 @@ fn test_create_rf64() {
}
af.end().unwrap();
assert!(cursor.seek(SeekFrom::End(0)).unwrap() > 0xFFFF_FFFFu64, "internal test error, Created file is not long enough to be RF64" );
assert!(
cursor.seek(SeekFrom::End(0)).unwrap() > 0xFFFF_FFFFu64,
"internal test error, Created file is not long enough to be RF64"
);
let expected_data_length = four_and_a_half_hours_of_frames * format.block_alignment as u64;
cursor.seek(SeekFrom::Start(0)).unwrap();
@@ -505,20 +550,33 @@ fn test_create_rf64() {
let form_size = cursor.read_u64::<LittleEndian>().unwrap();
let data_size = cursor.read_u64::<LittleEndian>().unwrap();
assert_eq!(data_size, expected_data_length);
cursor.seek(SeekFrom::Current(ds64_size as i64 - 16)).unwrap();
cursor
.seek(SeekFrom::Current(ds64_size as i64 - 16))
.unwrap();
assert_eq!(cursor.read_fourcc().unwrap(), FMT__SIG);
let fmt_size = cursor.read_u32::<LittleEndian>().unwrap();
cursor.seek(SeekFrom::Current((fmt_size + fmt_size % 2) as i64)).unwrap();
cursor
.seek(SeekFrom::Current((fmt_size + fmt_size % 2) as i64))
.unwrap();
assert_eq!(cursor.read_fourcc().unwrap(), ELM1_SIG);
let elm1_size = cursor.read_u32::<LittleEndian>().unwrap();
let data_start = cursor.seek(SeekFrom::Current((elm1_size + elm1_size % 2) as i64)).unwrap();
assert!((data_start + 8) % 0x4000 == 0, "data content start is not aligned, starts at {}", data_start + 8);
let data_start = cursor
.seek(SeekFrom::Current((elm1_size + elm1_size % 2) as i64))
.unwrap();
assert!(
(data_start + 8) % 0x4000 == 0,
"data content start is not aligned, starts at {}",
data_start + 8
);
assert_eq!(cursor.read_fourcc().unwrap(), DATA_SIG);
assert_eq!(cursor.read_u32::<LittleEndian>().unwrap(), 0xFFFF_FFFF);
cursor.seek(SeekFrom::Current(data_size as i64)).unwrap();
assert_eq!(4 + 8 + ds64_size as u64 + 8 + data_size + 8 + fmt_size as u64 + 8 + elm1_size as u64, form_size)
}
assert_eq!(
4 + 8 + ds64_size as u64 + 8 + data_size + 8 + fmt_size as u64 + 8 + elm1_size as u64,
form_size
)
}