94 Commits

Author SHA1 Message Date
Jamie Hardt
f0b1c51bd8 Merge branch 'master' into release 2021-01-01 12:26:50 -08:00
Jamie Hardt
e14bcd8c76 Update version 2021-01-01 12:26:16 -08:00
Jamie Hardt
1cb7174861 Comment 2021-01-01 12:14:51 -08:00
Jamie Hardt
a855410d6f Added rf64 test back in
...and will push to github see if the action completes
2021-01-01 12:14:06 -08:00
Jamie Hardt
28b9272456 Updated doucmentation to proper comment style 2021-01-01 12:09:26 -08:00
Jamie Hardt
62c9aa7262 Merge branch 'master' of https://github.com/iluvcapra/bwavfile 2021-01-01 11:59:33 -08:00
Jamie Hardt
1f56e0f380 Documentation 2021-01-01 11:59:28 -08:00
Jamie Hardt
ef8a3adf69 Removed example folders 2020-12-31 15:31:55 -08:00
Jamie Hardt
daeb69c08c added main functions 2020-12-31 14:22:31 -08:00
Jamie Hardt
aca56558bc Added examples 2020-12-31 14:19:57 -08:00
Jamie Hardt
b11e1d7354 Merge branch 'master' of https://github.com/iluvcapra/bwavfile 2020-12-31 14:17:32 -08:00
Jamie Hardt
dfaf55955d Update README.md 2020-12-29 21:10:50 -08:00
Jamie Hardt
7ea7ac5ce7 DS64 constant 2020-12-29 14:42:11 -08:00
Jamie Hardt
41977adb83 Fixed a bug in write_chunk 2020-12-29 14:38:05 -08:00
Jamie Hardt
f63d6279c3 Update README.md
iXML/AXML writing support noted
2020-12-29 14:27:13 -08:00
Jamie Hardt
84942a4186 Implented AXML/iXML writing functions 2020-12-29 14:26:34 -08:00
Jamie Hardt
78ad1ae114 More docs, improved bext writer interface 2020-12-29 13:03:20 -08:00
Jamie Hardt
5e4c2c7da9 Doc 2020-12-29 12:17:31 -08:00
Jamie Hardt
d43ddf6338 Reorganized documentation 2020-12-29 12:09:00 -08:00
Jamie Hardt
84366089ba Implementation 2020-12-27 12:05:21 -08:00
Jamie Hardt
cbfcce235c Frame writing methods to make go faster 2020-12-27 11:34:12 -08:00
Jamie Hardt
087d98b228 Reduced commenting of rf64 test case
...to just the line we want to comment-out
2020-12-26 22:22:59 -08:00
Jamie Hardt
f978eb95ed Update README.md
Twiddles, RF64 writing test case note.
2020-12-26 21:28:47 -08:00
Jamie Hardt
1f8542a7ef Update README.md 2020-12-26 21:25:41 -08:00
Jamie Hardt
25589ea848 Version 0.9.1 2020-12-26 21:16:36 -08:00
Jamie Hardt
d242dff686 Added to README 2020-12-26 21:11:56 -08:00
Jamie Hardt
155a26ace0 RF64 implementation 2020-12-26 19:01:32 -08:00
Jamie Hardt
1d2edcb675 Documentation and RF64 impl 2020-12-26 18:50:16 -08:00
Jamie Hardt
213a856e41 Documentation 2020-12-26 18:29:59 -08:00
Jamie Hardt
70bf402776 Ambisonic format create/write 2020-12-26 13:52:52 -08:00
Jamie Hardt
3ab3a28d0e Format implementation 2020-12-26 13:41:08 -08:00
Jamie Hardt
620ca8a968 Bext writing 2020-12-26 12:12:46 -08:00
Jamie Hardt
bb6390a95c Write ds64 reservation 2020-12-26 11:29:09 -08:00
Jamie Hardt
15b4ccf851 Documentation 2020-12-26 00:24:56 -08:00
Jamie Hardt
ea9a0b6cbe Twiddles 2020-12-25 23:57:09 -08:00
Jamie Hardt
95700b642d Removed confusing emoji 2020-12-25 23:51:36 -08:00
Jamie Hardt
f3b646868f Update to v0.9.0 2020-12-25 22:23:48 -08:00
Jamie Hardt
b303a74d45 Wave writer 2020-12-25 22:21:47 -08:00
Jamie Hardt
73d2cf8cd9 Writer impl 2020-12-25 21:58:22 -08:00
Jamie Hardt
e4fc4732b5 Wavewriter impl 2020-12-25 20:55:38 -08:00
Jamie Hardt
e8b030bd1e WaveWriter impl, a new approach 2020-12-25 20:26:56 -08:00
Jamie Hardt
1219a4162f Wavewriter impl 2020-12-25 18:31:17 -08:00
Jamie Hardt
9a275a69c3 Writer impl in progress 2020-12-25 12:30:08 -08:00
Jamie Hardt
3d7c74fc94 Updated sample code in readme
To reflect changes in create_frame_buffer()
2020-12-24 23:02:55 -08:00
Jamie Hardt
e6e42f1c09 Merge branch 'master' of https://github.com/iluvcapra/bwavfile 2020-12-24 22:58:38 -08:00
Jamie Hardt
465bad40fc Writer implementation
creaet_frame_buffer() interface has been moved to WaveFmt
2020-12-24 22:57:54 -08:00
Jamie Hardt
388c46d0ee Update README.md 2020-12-24 21:59:31 -08:00
Jamie Hardt
5dba30fe23 Update README.md 2020-12-24 21:57:28 -08:00
Jamie Hardt
84e4ed1e50 Update README.md 2020-12-24 21:56:33 -08:00
Jamie Hardt
23dccecedb Update README.md
Changed presentation of features
2020-12-24 21:55:32 -08:00
Jamie Hardt
e2e029d3a3 Wavewriter implementation in progress 2020-12-24 21:41:10 -08:00
Jamie Hardt
1edfa7384d Removed raw_chunk_reader from implementation 2020-12-24 20:56:19 -08:00
Jamie Hardt
c73c9ad81d Added a workspace 2020-12-24 20:08:12 -08:00
Jamie Hardt
1edce82525 Revert "Update wavereader.rs"
This reverts commit f4b27f8545.
2020-12-13 12:40:53 -08:00
Jamie Hardt
f4b27f8545 Update wavereader.rs 2020-12-13 12:30:43 -08:00
Jamie Hardt
4cb6105073 Got rid of old code, will rewrite this 2020-12-11 01:58:01 -08:00
Jamie Hardt
ad0736dcc6 Documentation 2020-12-10 18:46:53 -08:00
Jamie Hardt
45311ad748 Documentation fix
Angle brackets were getting swallowed
2020-12-10 18:35:25 -08:00
Jamie Hardt
e502913f4f Updated readme 2020-12-10 18:11:48 -08:00
Jamie Hardt
4890483dcd Cue lists implemented 2020-12-10 18:09:28 -08:00
Jamie Hardt
8519854596 Cue lists in progress
Need to clean up zero-terminated strings in adtl
2020-12-10 14:48:25 -08:00
Jamie Hardt
c947904d0f Cue point implementation 2020-12-10 12:26:49 -08:00
Jamie Hardt
2323d61ee9 Added test media from izotope RX Editor 2020-12-09 20:35:06 -08:00
Jamie Hardt
60af2f43ff Update README.md 2020-12-06 12:16:49 -08:00
Jamie Hardt
eb431f865f Update README.md 2020-12-06 11:47:53 -08:00
Jamie Hardt
8b049e4245 Chanegd reader interface
broadcast_extension returns an Option<> now
2020-12-04 22:22:40 -08:00
Jamie Hardt
dfe90fba4a Documentation 2020-12-04 22:11:25 -08:00
Jamie Hardt
b2d4bff28b Update lib.rs 2020-12-04 20:39:58 -08:00
Jamie Hardt
79173b0576 Update README.md 2020-12-04 20:39:04 -08:00
Jamie Hardt
e15e36d65d Update README.md 2020-12-04 20:35:39 -08:00
Jamie Hardt
1dc5f153b0 Update README.md 2020-12-04 20:34:08 -08:00
Jamie Hardt
7a9f9c8bb3 Update README.md
Roadmap updates to "Features"
2020-12-04 20:33:30 -08:00
Jamie Hardt
0dd7ee4d18 Merge branch 'master' of https://github.com/iluvcapra/bwavfile 2020-12-03 23:57:43 -08:00
Jamie Hardt
a213e748db Sampler resources 2020-12-03 23:47:41 -08:00
Jamie Hardt
d221629b3e More notes on things to do 2020-12-02 22:39:02 -08:00
Jamie Hardt
2fcb211a67 Docs 2020-12-02 22:28:33 -08:00
Jamie Hardt
dfe513b596 iXML and axml cleanup impl 2020-12-02 22:27:14 -08:00
Jamie Hardt
7e1c368862 Nudged version 2020-12-02 21:45:40 -08:00
Jamie Hardt
8985361029 AudioFrameReader consumes the file of WaveReader 2020-12-02 21:36:50 -08:00
Jamie Hardt
e23529e7b6 Note 2020-12-02 17:05:03 -08:00
Jamie Hardt
538aedd413 Playing with comment 2020-12-02 12:52:58 -08:00
Jamie Hardt
09a9413ff2 Changed audio_frame_reader interface
to hide RawChunkReader
2020-12-02 12:21:20 -08:00
Jamie Hardt
016f5e3e3b Update wavereader.rs
Fixed typo
2020-11-29 14:06:16 -08:00
Jamie Hardt
11b834be76 Update fmt.rs
Documentation
2020-11-29 14:04:52 -08:00
Jamie Hardt
a4ded50112 Merge branch 'master' of https://github.com/iluvcapra/bwavfile 2020-11-28 16:36:59 -08:00
Jamie Hardt
9b2a9783a0 Updated Cargo.toml 2020-11-28 16:35:55 -08:00
Jamie Hardt
1e53436d4b Update README.md 2020-11-28 15:57:34 -08:00
Jamie Hardt
a9d9081fad Update README.md 2020-11-28 15:55:12 -08:00
Jamie Hardt
c43144c9db Update README.md 2020-11-28 15:53:53 -08:00
Jamie Hardt
082a8596af Made changes to eliminate warnings 2020-11-28 11:30:21 -08:00
Jamie Hardt
208aa7f064 Fixed typo 2020-11-28 11:24:05 -08:00
Jamie Hardt
69da33b3dc Renamed axml method 2020-11-28 11:01:50 -08:00
Jamie Hardt
9cbb2664d4 axml and iXML access methods 2020-11-27 22:47:48 -08:00
Jamie Hardt
a4c4936665 Fixed thinko in documentation 2020-11-27 22:32:55 -08:00
22 changed files with 1451 additions and 482 deletions

2
Cargo.lock generated
View File

@@ -2,7 +2,7 @@
# It is not intended for manual editing.
[[package]]
name = "bwavfile"
version = "0.1.5"
version = "0.9.2"
dependencies = [
"byteorder",
"encoding",

View File

@@ -1,6 +1,6 @@
[package]
name = "bwavfile"
version = "0.1.5"
version = "0.9.2"
authors = ["Jamie Hardt <jamiehardt@me.com>"]
edition = "2018"
license = "MIT"
@@ -16,5 +16,9 @@ keywords = ["audio", "broadcast", "multimedia","smpte"]
[dependencies]
byteorder = "1.3.4"
encoding = "0.2.33"
serde_json = "1.0.59"
uuid = "0.8.1"
serde_json = "1.0.59"
[profile.release]
debug = true

View File

@@ -6,11 +6,30 @@
# bwavfile
Rust Wave File Reader/Writer with Broadcast-WAV, MBWF and RF64 Support
This is currently a work-in-progress!
### Features
This is currently a work-in-progress! However many features presently work:
| Feature |Read |Write|
|---------------------------------------|:---:|:-----:|
| Standard .wav files | ☑️ | ☑️ |
| Transparent promotion to RF64/BW64 | ☑️ | ☑️ |
| Unified interface for regular and extended Wave format | ☑️ | ☑️ |
| Channel/speaker map metadata | ☑️ | ☑️ |
| Ambisonic B-format metadata | ☑️ | ☑️ |
| EBU Broadcast-WAVE metadata | ☑️ | ☑️ |
| Basic iXML/ADM metadata | ☑️ | ☑️ |
| Enhanced iXML metadata support | | |
| ADM `chna` channel metadata | | |
| Broadcast-WAVE Level overview `levl` metadata | | |
| Cue list metadata | ☑️ | |
| Sampler and instrument metadata | | |
| Enhanced Wave file form validation | ☑️ | |
## Use Examples
### Reading a File
### Reading Audio Frames From a File
```rust
@@ -21,8 +40,8 @@ This is currently a work-in-progress!
assert_eq!(format.sample_rate, 44100);
assert_eq!(format.channel_count, 1);
let mut buffer = format.create_frame_buffer();
let mut frame_reader = r.audio_frame_reader().unwrap();
let mut buffer = frame_reader.create_frame_buffer();
let read = frame_reader.read_integer_frame(&mut buffer).unwrap();
@@ -30,9 +49,30 @@ This is currently a work-in-progress!
assert_eq!(read, 1);
```
### Accessing Channel Descriptions
```rust
use bwavfile::{WaveReader, 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);
assert_eq!(chans[3].index, 3);
assert_eq!(chans[3].speaker, ChannelMask::LowFrequency);
assert_eq!(chans[4].speaker, ChannelMask::BackLeft);
```
## Note on Testing
All of the media for the integration tests is committed to the respository
in zipped form. Before you can run tests, you need to `cd` into the `tests`
directory and run the `create_test_media.sh` script. Note that one of the
test files (the RF64 test case) is over four gigs in size.
Likewise, [the RF64 _writing_ test case][rf64test] writes an RF64 wave file
to memory and is very time-intensive, so is commented-out in the code but
can be un-commented if you want to run it on your system.
[rf64test]: https://github.com/iluvcapra/bwavfile/blob/1f8542a7efb481da076120bf8107032c5b48889d/src/wavewriter.rs#L399

8
bwavefile.code-workspace Normal file
View File

@@ -0,0 +1,8 @@
{
"folders": [
{
"path": "."
}
],
"settings": {}
}

3
examples/blits.rs Normal file
View File

@@ -0,0 +1,3 @@
fn main() -> () {
}

View File

@@ -0,0 +1,3 @@
fn main() -> () {
}

View File

@@ -1,5 +1,5 @@
use std::io::{Read, Seek};
use std::io::SeekFrom::{Start,};
use std::io::SeekFrom::{Start,Current,};
use byteorder::LittleEndian;
use byteorder::ReadBytesExt;
@@ -16,7 +16,9 @@ use super::CommonFormat;
#[derive(Debug)]
pub struct AudioFrameReader<R: Read + Seek> {
inner : R,
format: WaveFmt
format: WaveFmt,
start: u64,
length: u64
}
impl<R: Read + Seek> AudioFrameReader<R> {
@@ -29,16 +31,21 @@ impl<R: Read + Seek> AudioFrameReader<R> {
/// 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.)
pub fn new(inner: R, format: WaveFmt) -> Self {
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,
"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 ,
"Unsupported format tag {:?}", format.tag);
AudioFrameReader { inner , format }
inner.seek(Start(start))?;
Ok( AudioFrameReader { inner , format , start, length} )
}
/// Unwrap the inner reader.
pub fn into_inner(self) -> R {
self.inner
}
/// Locate the read position to a different frame
@@ -46,19 +53,15 @@ impl<R: Read + Seek> AudioFrameReader<R> {
/// 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> {
let position = to * self.format.block_alignment as u64;
let seek_result = self.inner.seek(Start(position))?;
Ok( seek_result / 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 )
}
/// Create a frame buffer sized to hold frames of the reader
///
/// 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) -> Vec<i32> {
vec![0i32; self.format.channel_count as usize]
}
/// Read a frame
///
@@ -67,8 +70,8 @@ impl<R: Read + Seek> AudioFrameReader<R> {
///
/// Regardless of the number of bits in the audio sample, this method
/// always writes `i32` samples back to the buffer. These samples are
/// written back "left-aligned" so samples that are shorter than i32
/// will leave the MSB bits empty.
/// 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
/// back to the buffer as 0x0000FFFF.
@@ -85,17 +88,22 @@ impl<R: Read + Seek> AudioFrameReader<R> {
let framed_bits_per_sample = self.format.block_alignment * 8 / self.format.channel_count;
for n in 0..(self.format.channel_count as usize) {
buffer[n] = match (self.format.bits_per_sample, framed_bits_per_sample) {
(0..=8,8) => self.inner.read_u8()? as i32 - 0x80_i32, // EBU 3285 §A2.2
(9..=16,16) => self.inner.read_i16::<LittleEndian>()? as i32,
(10..=24,24) => self.inner.read_i24::<LittleEndian>()?,
(25..=32,32) => self.inner.read_i32::<LittleEndian>()?,
(b,_)=> panic!("Unrecognized integer format, bits per sample {}, channels {}, block_alignment {}",
b, self.format.channel_count, self.format.block_alignment)
}
}
let tell = self.inner.seek(Current(0))?;
Ok( 1 )
if (tell - self.start) < self.length {
for n in 0..(self.format.channel_count as usize) {
buffer[n] = match (self.format.bits_per_sample, framed_bits_per_sample) {
(0..=8,8) => self.inner.read_u8()? as i32 - 0x80_i32, // EBU 3285 §A2.2
(9..=16,16) => self.inner.read_i16::<LittleEndian>()? as i32,
(10..=24,24) => self.inner.read_i24::<LittleEndian>()?,
(25..=32,32) => self.inner.read_i32::<LittleEndian>()?,
(b,_)=> panic!("Unrecognized integer format, bits per sample {}, channels {}, block_alignment {}",
b, self.format.channel_count, self.format.block_alignment)
}
}
Ok( 1 )
} else {
Ok( 0 )
}
}
}

View File

@@ -3,20 +3,23 @@ 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.
*
* For a Wave file to be a complaint "Broadcast-WAV" file, it must contain
* a `bext` metadata record.
*
* For reference on the structure and use of the BEXT record
* check out [EBU Tech 3285](https://tech.ebu.ch/docs/tech/tech3285.pdf).
*/
/// 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.
///
/// For a Wave file to be a complaint "Broadcast-WAV" file, it must contain
/// a `bext` metadata record.
///
/// ## 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 &lt;CodingHistory&gt; 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
/// &lt;OriginatorReference&gt; field of the Broadcast Wave Format"
#[derive(Debug)]
pub struct Bext {
@@ -47,7 +50,6 @@ pub struct Bext {
/// SMPTE 330M UMID
///
///
/// This field is `None` if the version is less than 1.
pub umid: Option<[u8; 64]>,

View File

@@ -1,12 +1,5 @@
use uuid::Uuid;
/**
* References:
* - http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/multichaudP.pdf
*/
// http://dream.cs.bath.ac.uk/researchdev/wave-ex/bformat.html
const BASIC_PCM: u16 = 0x0001;
const BASIC_FLOAT: u16 = 0x0003;
const BASIC_MPEG: u16 = 0x0050;
@@ -22,19 +15,19 @@ const BASIC_EXTENDED: u16 = 0xFFFE;
*/
const UUID_PCM: Uuid = Uuid::from_bytes([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
pub const UUID_PCM: Uuid = Uuid::from_bytes([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71]);
const UUID_FLOAT: Uuid = Uuid::from_bytes([0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
pub const UUID_FLOAT: Uuid = Uuid::from_bytes([0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71]);
const UUID_MPEG: Uuid = Uuid::from_bytes([0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
pub const UUID_MPEG: Uuid = Uuid::from_bytes([0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71]);
const UUID_BFORMAT_PCM: Uuid = Uuid::from_bytes([0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11,
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]);
const UUID_BFORMAT_FLOAT: Uuid = Uuid::from_bytes([0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11,
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]);
@@ -45,7 +38,7 @@ fn uuid_from_basic_tag(tag: u16) -> Uuid {
/// Sample format of the Wave file.
///
///
///
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum CommonFormat {
/// Integer linear PCM
@@ -71,6 +64,7 @@ pub enum CommonFormat {
}
impl CommonFormat {
/// Resolve a tag and Uuid to a `CommonFormat`.
pub fn make(basic: u16, uuid: Option<Uuid>) -> Self {
match (basic, uuid) {
(BASIC_PCM, _) => Self::IntegerPCM,
@@ -85,6 +79,10 @@ impl CommonFormat {
}
}
/// Get the appropriate tag and `Uuid` for the callee.
///
/// 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 {
Self::IntegerPCM => (BASIC_PCM, UUID_PCM),

262
src/cue.rs Normal file
View File

@@ -0,0 +1,262 @@
use super::fourcc::{FourCC,ReadFourCC, LABL_SIG, NOTE_SIG, LTXT_SIG};
use super::list_form::collect_list_form;
use byteorder::{ReadBytesExt, LittleEndian};
use encoding::{DecoderTrap};
use encoding::{Encoding};
use encoding::all::ASCII;
use std::io::{Cursor, Error, Read};
#[derive(Copy,Clone, Debug)]
struct RawCue {
cue_point_id : u32,
frame : u32,
chunk_id : FourCC,
chunk_start : u32,
block_start : u32,
frame_offset : u32
}
impl RawCue {
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![];
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>()?
})
}
Ok( retval )
}
}
#[derive(Clone, Debug)]
struct RawLabel {
cue_point_id : u32,
text : Vec<u8>
}
impl RawLabel {
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 ];
rdr.read_exact(&mut buf)?;
buf
}
})
}
}
#[derive(Clone, Debug)]
struct RawNote {
cue_point_id : u32,
text : Vec<u8>
}
impl RawNote {
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 ];
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>>
}
impl RawLtxt {
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 : {
if length - 20 > 0 {
let mut buf = vec![0u8; (length - 20) as usize];
rdr.read_exact(&mut buf)?;
Some( buf )
} else {
None
}
}
})
}
}
#[derive(Clone, Debug)]
enum RawAdtlMember {
Label(RawLabel),
Note(RawNote),
LabeledText(RawLtxt),
Unrecognized(FourCC)
}
impl RawAdtlMember {
fn collect_from(chunk : &[u8]) -> Result<Vec<RawAdtlMember>,Error> {
let chunks = collect_list_form(chunk)?;
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)
}
)
}
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 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 {
RawAdtlMember::Label(x) if x.cue_point_id == id => Some(x),
_ => None
}
})
.collect()
}
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()
}
fn ltxt_for_cue_point(&self, id: u32) -> Vec<&RawLtxt> {
self.iter().filter_map(|item| {
match item {
RawAdtlMember::LabeledText(x) if x.cue_point_id == id => Some(x),
_ => 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 {
/// Unique numeric identifier for this cue
pub ident : u32,
/// The time of this marker
pub frame : u32,
/// The length of this marker, if it is a range
pub length : Option<u32>,
/// The text "label"/name of this marker if provided
pub label : Option<String>,
/// The text "note"/comment of this marker if provided
pub note : Option<String>
}
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")
}
impl Cue {
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>;
if let Some(adtl) = adtl_chunk {
raw_adtl = RawAdtlMember::collect_from(adtl)?;
} else {
raw_adtl = vec![];
}
Ok(
raw_cues.iter()
.map(|i| {
Cue {
ident : i.cue_point_id,
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)
},
label: {
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()
//.filter_map(|x| str::from_utf8(&x.text).ok())
.map(|s| convert_to_cue_string(&s.text))
.next()
}
}
}).collect()
)
}
}

View File

@@ -1,26 +1,33 @@
use std::convert::TryFrom;
use uuid::Uuid;
use super::errors::Error;
use super::common_format::CommonFormat;
use super::common_format::{CommonFormat, UUID_PCM,UUID_BFORMAT_PCM};
use std::io::Cursor;
use byteorder::LittleEndian;
use byteorder::WriteBytesExt;
/// ADM Audio ID record
// 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
/// chunk.
///
/// An individual channel in a WAV file can have multiple Audio IDs in an ADM
/// AudioProgramme.
/// `AudioProgramme`.
///
/// See BS.2088-1 § 8, also BS.2094, also blahblahblah...
pub struct ADMAudioID {
track_uid: [char; 12],
channel_format_ref: [char; 14],
pack_ref: [char; 11]
pub track_uid: [char; 12],
pub channel_format_ref: [char; 14],
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.
pub struct ChannelDescriptor {
/// Index, the offset of this channel's samples in one frame.
pub index: u16,
@@ -36,13 +43,9 @@ pub struct ChannelDescriptor {
}
/*
https://docs.microsoft.com/en-us/windows-hardware/drivers/audio/subformat-guids-for-compressed-audio-formats
These are from http://dream.cs.bath.ac.uk/researchdev/wave-ex/mulchaud.rtf
*/
/// A bitmask indicating which channels are present in
/// the file.
///
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ChannelMask {
DirectOut = 0x0,
@@ -129,14 +132,30 @@ pub struct WaveFmtExtended {
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
* rate, sample binary format, channel count, etc.
*
*/
///
/// 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
/// rate, sample binary format, channel count, etc.
///
///
/// ## 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)
///
/// ### Other resources
/// - [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)
/// (August 1991), IBM Corporation and Microsoft Corporation
///
/// [rfc3261]: https://tools.ietf.org/html/rfc2361
#[derive(Debug, Copy, Clone)]
pub struct WaveFmt {
@@ -149,7 +168,9 @@ pub struct WaveFmt {
/// Count of audio channels in each frame
pub channel_count: u16,
/// Sample rate of the audio data
/// Playback rate of the audio data
///
/// In frames per second.
pub sample_rate: u32,
/// Count of bytes per second
@@ -163,6 +184,16 @@ pub struct WaveFmt {
pub block_alignment: u16,
/// Count of bits stored in the file per sample
///
/// By rule, `bits_per_sample % 8 == 0` for Broadcast-Wave files.
///
/// Modern clients will encode
/// unusual sample sizes in normal byte sizes but will set the valid_bits
/// flag in extended format record.
///
/// Generally speaking this will be true for all modern wave files, though
/// there was an historical "packed" stereo format of 20 bits per sample,
/// 5 bytes per frame, 5 bytes block alignment.
pub bits_per_sample: u16,
/// Extended format description
@@ -175,25 +206,79 @@ pub struct WaveFmt {
impl WaveFmt {
/// Create a new integer PCM format `WaveFmt`
pub fn new_pcm(sample_rate: u32, bits_per_sample: u16, channel_count: u16) -> Self {
pub fn valid_bits_per_sample(&self) -> u16 {
if let Some(ext) = self.extended_format {
ext.valid_bits_per_sample
} else {
self.bits_per_sample
}
}
/// Create a new integer PCM format for a monoaural audio stream.
pub fn new_pcm_mono(sample_rate: u32, bits_per_sample: u16) -> Self {
Self::new_pcm_multichannel(sample_rate, bits_per_sample, 0x4)
}
/// 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)
}
/// 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 tag : u16 = match channel_count {
1..=2 => 0x01,
x if x > 2 => 0xFFFE,
x => panic!("Invalid channel count {}", x)
};
WaveFmt {
tag,
tag : 0xFFFE,
channel_count,
sample_rate,
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: None
extended_format: Some(WaveFmtExtended {
valid_bits_per_sample: bits_per_sample,
channel_mask: ChannelMask::DirectOut as u32,
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
/// 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 {
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 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}))
)
};
let (tag, extformat) = result;
WaveFmt {
tag,
channel_count,
sample_rate,
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
}
}
@@ -205,6 +290,55 @@ impl WaveFmt {
CommonFormat::make( self.tag, self.extended_format.map(|ext| ext.type_guid))
}
/// Create a frame buffer sized to hold 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) -> Vec<i32> {
vec![0i32; self.channel_count as usize]
}
/// Calculate the size of a byte buffer needed to hold audio data of this
/// format for a given number of frames
pub fn buffer_length(&self, frame_count: u64) -> usize {
(self.block_alignment as u64 * frame_count) as usize
}
// Write frames into a byte vector
pub fn pack_frames(&self, from_frames: &[i32], into_bytes: &mut Vec<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");
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(),
(25..=32,32) => write_cursor.write_i32::<LittleEndian>(from_frames[n]).unwrap(),
(b,_)=> panic!("Unrecognized integer format, bits per sample {}, channels {}, block_alignment {}",
b, self.channel_count, self.block_alignment)
}
}
()
}
/// Read bytes into frames
// pub fn unpack_frames(&self, from_bytes: &[u8], into_frames: &mut Vec<i32>) -> () {
// for n in 0..(from_bytes.len()) {
// buffer[n] = match (self.format.bits_per_sample, framed_bits_per_sample) {
// (0..=8,8) => self.inner.read_u8()? as i32 - 0x80_i32, // EBU 3285 §A2.2
// (9..=16,16) => self.inner.read_i16::<LittleEndian>()? as i32,
// (10..=24,24) => self.inner.read_i24::<LittleEndian>()?,
// (25..=32,32) => self.inner.read_i32::<LittleEndian>()?,
// (b,_)=> panic!("Unrecognized integer format, bits per sample {}, channels {}, block_alignment {}",
// b, self.format.channel_count, self.format.block_alignment)
// }
// }
// }
/// Channel descriptors for each channel.
pub fn channels(&self) -> Vec<ChannelDescriptor> {
match self.channel_count {

View File

@@ -102,9 +102,20 @@ pub const DATA_SIG: FourCC = FourCC::make(b"data");
pub const FMT__SIG: FourCC = FourCC::make(b"fmt ");
pub const BEXT_SIG: FourCC = FourCC::make(b"bext");
//pub const FACT_SIG: FourCC = FourCC::make(b"fact");
pub const IXML_SIG: FourCC = FourCC::make(b"iXML");
pub const AXML_SIG: FourCC = FourCC::make(b"axml");
pub const JUNK_SIG: FourCC = FourCC::make(b"JUNK");
pub const FLLR_SIG: FourCC = FourCC::make(b"FLLR");
pub const ELM1_SIG: FourCC = FourCC::make(b"elm1");
pub const LIST_SIG: FourCC = FourCC::make(b"LIST");
pub const CUE__SIG: FourCC = FourCC::make(b"cue ");
pub const ADTL_SIG: FourCC = FourCC::make(b"adtl");
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)]

3
src/levl.rs Normal file
View File

@@ -0,0 +1,3 @@
/// Resources
///
/// [EBU 3285 Supplement 3](https://tech.ebu.ch/docs/tech/tech3285s3.pdf) (July 2001): Peak Metadata

View File

@@ -2,10 +2,23 @@
# bwavfile
Rust Wave File Reader/Writer with Broadcast-WAV, MBWF and RF64 Support
__(Note: This crate is still in an alpha or pre-alpha stage of development. Reading of
files works however the interfaces may change significantly. Stay up-to-date on the
status of this project at [Github][github].)__
## Interfaces
### `WaveReader`
`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
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
when the total WAVE form size exceeds 0xFFFFFFFF bytes.
## Objectives and Roadmap
@@ -15,80 +28,10 @@ production.
Apps we test against:
- Avid Pro Tools
- iZotope RX Audio Editor
- FFMpeg
- Audacity
Wave features we want to support with maximum reliability and ease of use:
- Large file size, RF64 support
- Multichannel audio formats
- Embedded metadata
In addition to reading the audio, we want to support all of the different
metadata planes you are liable to need to use.
- Broadcast-WAV metadata (including the SMPTE UMID and EBU v2 extensions)
- iXML Production recorder metadata
- ADM XML (with associated `chna` mappings)
- Dolby metadata block
Things that are _not_ necessarily in the scope of this package:
- Broad codec support. There are a little more than one-hundred
[registered wave codecs][rfc3261], but because this library is targeting
professional formats being created today, we only plan on supporting
two of them: tag 0x0001 (Integer Linear PCM) and tag 0x0003 (IEEE Float
Linear PCM).
- Music library metadata. There are several packages that can read ID3
metadata and it's not particuarly common in wave files in any case. INFO
metadata is more common though in professional applications it tends not
to be used by many applications.
## Resources
### Implementation of Broadcast Wave Files
- [EBU Tech 3285][ebu3285] (May 2011), "Specification of the Broadcast Wave Format (BWF)"
- [Supplement 1](https://tech.ebu.ch/docs/tech/tech3285s1.pdf) (July 1997): MPEG Audio
- [EBU Rec 68](https://tech.ebu.ch/docs/r/r068.pdf): Signal modulation and format constraints
### Implementation of 64-bit Wave Files
- [ITU-R 2088][itu2088] (October 2019), "Long-form file format for the international exchange of audio programme materials with metadata"
- 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.
### 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)
### Other resources
- [RFC 3261][rfc3261] (June 1998) "WAVE and AVI Codec Registries"
- [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)
(August 1991), IBM Corporation and Microsoft Corporation
### Formatting of Specific Metadatums
- [iXML Metadata Specification](http://www.gallery.co.uk/ixml/) (April 2019)
- EBU 3285 Supplements:
- [Supplement 2](https://tech.ebu.ch/docs/tech/tech3285s2.pdf) (July 2001): Quality chunk and cuesheet
- [Supplement 3](https://tech.ebu.ch/docs/tech/tech3285s3.pdf) (July 2001): Peak Metadata
- [Supplement 4](https://tech.ebu.ch/docs/tech/tech3285s4.pdf) (April 2003): Link Metadata
- [Supplement 5](https://tech.ebu.ch/docs/tech/tech3285s5.pdf) (May 2018): ADM Metadata
- [Supplement 6](https://tech.ebu.ch/docs/tech/tech3285s6.pdf) (October 2009): Dolby Metadata
- [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"
- [EBU Tech R098](https://tech.ebu.ch/docs/r/r098.pdf) (1999) "Format for the <CodingHistory> field in Broadcast Wave Format files, BWF"
[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
[github]: https://github.com/iluvcapra/bwavfile
*/
@@ -108,10 +51,11 @@ mod common_format;
mod parser;
mod raw_chunk_reader;
mod audio_frame_reader;
mod list_form;
mod chunks;
mod cue;
mod bext;
mod fmt;
@@ -119,8 +63,10 @@ mod wavereader;
mod wavewriter;
pub use errors::Error;
pub use wavereader::{WaveReader};
pub use wavereader::WaveReader;
pub use wavewriter::{WaveWriter, AudioFrameWriter};
pub use bext::Bext;
pub use fmt::{WaveFmt, WaveFmtExtended, ChannelDescriptor, ChannelMask};
pub use fmt::{WaveFmt, WaveFmtExtended, ChannelDescriptor, ChannelMask, ADMAudioID};
pub use common_format::CommonFormat;
pub use audio_frame_reader::AudioFrameReader;
pub use audio_frame_reader::AudioFrameReader;
pub use cue::Cue;

40
src/list_form.rs Normal file
View File

@@ -0,0 +1,40 @@
use super::fourcc::{FourCC, ReadFourCC};
use byteorder::{ReadBytesExt, LittleEndian};
use std::io::{Cursor, Error, Read};
pub struct ListFormItem {
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> {
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![];
while remain > 0 {
let this_sig = cursor.read_fourcc()?;
let this_size = cursor.read_u32::<LittleEndian>()? as usize;
remain -= 8;
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 } );
if this_size % 2 == 1 {
cursor.read_u8()?;
//panic!("Got this far!");
remain -= 1;
}
}
Ok( retval )
}

View File

@@ -45,6 +45,7 @@ pub struct Parser<R: Read + Seek> {
ds64state: HashMap<FourCC,u64>
}
#[derive(Debug, PartialEq, Eq)]
pub struct ChunkIteratorItem {
pub signature: FourCC,
pub start: u64,

View File

@@ -1,73 +0,0 @@
use std::cmp::min;
use std::io::SeekFrom;
use std::io::SeekFrom::{Start, Current, End};
use std::io::{Seek,Read,Error,ErrorKind};
// I'm not sure this hasn't already been written somewhere in
// std but I'm just doing this here as an exercise.
#[derive(Debug)]
pub struct RawChunkReader<'a, R: Read + Seek> {
reader: &'a mut R,
start: u64,
length: u64,
position: u64
}
impl<'a,R: Read + Seek> RawChunkReader<'a, R> {
pub fn new(reader: &'a mut R, start: u64, length: u64) -> Self {
return Self {
reader: reader,
start: start,
length: length,
position: 0
}
}
pub fn length(&self) -> u64 {
self.length
}
}
impl<'a, R:Read + Seek> Read for RawChunkReader<'_, R> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
if self.position >= self.length {
Err(Error::new(ErrorKind::UnexpectedEof, "RawChunkReader encountered end-of-file"))
} else {
self.reader.seek(Start(self.start + self.position))?;
let to_read = min(self.length - self.position, buf.len() as u64);
self.reader.take(to_read).read(buf)?;
self.position += to_read;
Ok(to_read as usize)
}
}
}
impl<'a, R:Read + Seek> Seek for RawChunkReader<'_, R> {
fn seek(&mut self, seek: SeekFrom) -> Result<u64, std::io::Error> {
match seek {
Start(s) => {
self.position = s;
Ok(self.position)
},
Current(s) => {
let new_position = s + self.position as i64;
if new_position < 0 {
Err( Error::new(ErrorKind::Other, "Attempted seek before beginning of chunk") )
} else {
self.position = new_position as u64;
Ok(self.position)
}
},
End(s) => {
let new_position = s + self.length as i64;
if new_position < 0 {
Err( Error::new(ErrorKind::Other, "Attempted seek before beginning of chunk") )
} else {
self.position = new_position as u64;
Ok(self.position)
}
}
}
}
}

2
src/sampler.rs Normal file
View File

@@ -0,0 +1,2 @@
/// ## Resources
/// - [Sampler Metadata](http://www.piclist.com/techref/io/serial/midi/wave.html)

View File

@@ -1,52 +1,79 @@
use std::io::SeekFrom;
use std::fs::File;
use super::parser::Parser;
use super::fourcc::{FourCC, FMT__SIG,DATA_SIG, BEXT_SIG, JUNK_SIG, FLLR_SIG};
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::raw_chunk_reader::RawChunkReader;
use super::fmt::{WaveFmt, ChannelDescriptor, ChannelMask};
use super::bext::Bext;
use super::audio_frame_reader::AudioFrameReader;
use super::chunks::ReadBWaveChunks;
use super::cue::Cue;
use std::io::Cursor;
use std::io::{Read, Seek};
/**
* Wave, Broadcast-WAV and RF64/BW64 parser/reader.
*
* ```
* use bwavfile::WaveReader;
* let mut r = WaveReader::open("tests/media/ff_silence.wav").unwrap();
*
* let format = r.format().unwrap();
* assert_eq!(format.sample_rate, 44100);
* assert_eq!(format.channel_count, 1);
*
* let mut frame_reader = r.audio_frame_reader().unwrap();
* let mut buffer = frame_reader.create_frame_buffer();
*
* let read = frame_reader.read_integer_frame(&mut buffer).unwrap();
*
* assert_eq!(buffer, [0i32]);
* assert_eq!(read, 1);
*
* ```
*/
/// Wave, Broadcast-WAV and RF64/BW64 parser/reader.
///
/// ```
/// use bwavfile::WaveReader;
/// let mut r = WaveReader::open("tests/media/ff_silence.wav").unwrap();
///
/// let format = r.format().unwrap();
/// assert_eq!(format.sample_rate, 44100);
/// assert_eq!(format.channel_count, 1);
///
/// let mut frame_reader = r.audio_frame_reader().unwrap();
/// let mut buffer = format.create_frame_buffer();
///
/// 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)
/// (August 1991), IBM Corporation and Microsoft Corporation
///
/// ### Implementation of Broadcast Wave Files
/// - [EBU Tech 3285][ebu3285] (May 2011), "Specification of the Broadcast Wave Format (BWF)"
/// - [Supplement 1](https://tech.ebu.ch/docs/tech/tech3285s1.pdf) (July 1997): MPEG Audio
/// - [EBU Rec 68](https://tech.ebu.ch/docs/r/r068.pdf): Signal modulation and format constraints
///
/// ### Implementation of 64-bit Wave Files
/// - [ITU-R 2088][itu2088] (October 2019), "Long-form file format for the international exchange of audio programme materials with metadata"
/// - 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
#[derive(Debug)]
pub struct WaveReader<R: Read + Seek> {
pub inner: R,
}
impl WaveReader<File> {
/**
* Open a file for reading.
*
* A convenience that opens `path` and calls `Self::new()`
*
*/
/// Open a file for reading.
///
/// A convenience that opens `path` and calls `Self::new()`
pub fn open(path: &str) -> Result<Self, ParserError> {
let inner = File::open(path)?;
return Ok( Self::new(inner)? )
@@ -54,82 +81,87 @@ impl WaveReader<File> {
}
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
* 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
* essential components that make interpreting the audio data impoossible.
*
* ```rust
* use std::fs::File;
* use std::io::{Error,ErrorKind};
* use bwavfile::{WaveReader, Error as WavError};
*
* let f = File::open("tests/media/error.wav").unwrap();
*
* let reader = WaveReader::new(f);
*
* match reader {
* Ok(_) => panic!("error.wav should not be openable"),
* Err( WavError::IOError( e ) ) => {
* assert_eq!(e.kind(), ErrorKind::UnexpectedEof)
* }
* Err(e) => panic!("Unexpected error was returned {:?}", e)
* }
*
* ```
*
*/
/// 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
/// 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
/// essential components that make interpreting the audio data impossible.
/// ```rust
/// use std::fs::File;
/// use std::io::{Error,ErrorKind};
/// use bwavfile::{WaveReader, Error as WavError};
///
/// let f = File::open("tests/media/error.wav").unwrap();
///
/// let reader = WaveReader::new(f);
///
/// match reader {
/// Ok(_) => panic!("error.wav should not be openable"),
/// Err( WavError::IOError( e ) ) => {
/// assert_eq!(e.kind(), ErrorKind::UnexpectedEof)
/// }
/// Err(e) => panic!("Unexpected error was returned {:?}", e)
/// }
///
/// ```
pub fn new(inner: R) -> Result<Self,ParserError> {
let mut retval = Self { inner };
retval.validate_readable()?;
Ok(retval)
}
/**
* Unwrap the inner reader.
*/
/// Unwrap the inner reader.
pub fn into_inner(self) -> R {
return self.inner;
}
/**
* Create an `AudioFrameReader` for reading each audio frame.
*/
pub fn audio_frame_reader(&mut self) -> Result<AudioFrameReader<RawChunkReader<R>>, ParserError> {
///
/// Create an `AudioFrameReader` for reading each audio frame and consume the `WaveReader`.
///
pub fn audio_frame_reader(mut self) -> Result<AudioFrameReader<R>, ParserError> {
let format = self.format()?;
let audio_chunk_reader = self.chunk_reader(DATA_SIG, 0)?;
Ok(AudioFrameReader::new(audio_chunk_reader, 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)?)
}
/**
* The count of audio frames in the file.
*/
/// 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 format = self.format()?;
Ok( data_length / (format.block_alignment as u64) )
}
/**
* Sample and frame format of this wave file.
*/
/// Sample and frame format of this wave file.
///
pub fn format(&mut self) -> Result<WaveFmt, ParserError> {
self.chunk_reader(FMT__SIG, 0)?.read_wave_fmt()
let (start, _) = self.get_chunk_extent_at_index(FMT__SIG, 0)?;
self.inner.seek(SeekFrom::Start(start))?;
self.inner.read_wave_fmt()
}
/**
* The Broadcast-WAV metadata record for this file.
*/
pub fn broadcast_extension(&mut self) -> Result<Bext, ParserError> {
self.chunk_reader(BEXT_SIG, 0)?.read_bext()
/// 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 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()? ) )
} else {
Ok( None)
}
}
/// Describe the channels in this file
@@ -137,8 +169,9 @@ impl<R: Read + Seek> WaveReader<R> {
/// Returns a vector of channel descriptors, one for each channel
///
/// ```rust
/// # use bwavfile::WaveReader;
/// # use bwavfile::ChannelMask;
/// use bwavfile::WaveReader;
/// use bwavfile::ChannelMask;
///
/// let mut f = WaveReader::open("tests/media/pt_24bit_51.wav").unwrap();
///
/// let chans = f.channels().unwrap();
@@ -163,6 +196,70 @@ impl<R: Read + Seek> WaveReader<R> {
.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].ident, 1);
/// 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].ident, 2);
/// 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[2].ident, 3);
/// 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")));
///
/// ```
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) )? )
}
}
/// Read iXML data.
///
/// The iXML data will be appended to `buffer`.
/// 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)
}
/// Read AXML data.
///
/// 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,
/// 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.
*
@@ -182,33 +279,33 @@ impl<R: Read + Seek> WaveReader<R> {
}
}
/**
* Validate minimal WAVE file.
*
* `Ok(())` if the source is `validate_readable()` AND
*
* - Contains _only_ a `fmt` chunk and `data` chunk, with no other chunks present
* - is not an RF64/BW64
*
* Some clients require a WAVE file to only contain format and data without any other
* metadata and this function is provided to validate this condition.
*
* ### Examples
*
* ```
* # use bwavfile::WaveReader;
*
* let mut w = WaveReader::open("tests/media/ff_minimal.wav").unwrap();
* w.validate_minimal().expect("Minimal wav did not validate not minimal!");
* ```
*
* ```
* # use bwavfile::WaveReader;
*
* let mut x = WaveReader::open("tests/media/pt_24bit_51.wav").unwrap();
* x.validate_minimal().expect_err("Complex WAV validated minimal!");
* ```
*/
/// Validate minimal WAVE file.
///
/// `Ok(())` if the source is `validate_readable()` AND
///
/// - Contains _only_ a `fmt` chunk and `data` chunk, with no other chunks present
/// - `fmt` chunk is exactly 16 bytes long and begins _exactly_ at file offset 12
/// - `data` content begins _exactly_ at file offset 36
/// - is not an RF64/BW64
///
/// Some clients require a WAVE file to only contain format and data without any other
/// metadata and this function is provided to validate this condition.
///
/// ### Examples
///
/// ```
/// # use bwavfile::WaveReader;
///
/// let mut w = WaveReader::open("tests/media/ff_minimal.wav").unwrap();
/// w.validate_minimal().expect("Minimal wav did not validate not minimal!");
/// ```
///
/// ```
/// # use bwavfile::WaveReader;
///
/// 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> {
self.validate_readable()?;
@@ -216,45 +313,43 @@ impl<R: Read + Seek> WaveReader<R> {
.into_chunk_list()?.iter().map(|c| c.signature ).collect();
if chunk_fourccs == vec![FMT__SIG, DATA_SIG] {
Ok(())
Ok(()) /* FIXME: finish implementation */
} else {
Err( ParserError::NotMinimalWaveFile )
}
}
/**
* Validate Broadcast-WAVE file format
*
* 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");
* ```
*/
/// Validate Broadcast-WAVE file format
///
/// 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");
/// ```
///
pub fn validate_broadcast_wave(&mut self) -> Result<(), ParserError> {
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
* `data` chunk's content begins at 0x4000.
*/
///
/// Verify data is aligned to a block boundary.
///
/// 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> {
self.validate_readable()?;
let (start, _) = self.get_chunk_extent_at_index(DATA_SIG, 0)?;
@@ -265,15 +360,13 @@ 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
* list adequately large enough to be overwritten by a `ds64` (92 bytes)
* - `data` is the final chunk
*/
/// 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
/// 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> {
self.validate_readable()?;
@@ -300,20 +393,77 @@ impl<R: Read + Seek> WaveReader<R> {
}
}
impl<R:Read+Seek> WaveReader<R> { /* Private Implementation */
impl<R:Read+Seek> WaveReader<R> {
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) )
}
// Private implementation
//
// 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 get_chunk_extent_at_index(&mut self, fourcc: FourCC, index: u32) -> Result<(u64,u64), ParserError> {
// 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 )
}
}
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())
}
}
/// Extent of every chunk with the given fourcc
fn get_chunks_extents(&mut self, fourcc: FourCC) -> Result<Vec<(u64,u64)>, ParserError> {
let p = Parser::make(&mut self.inner)?.into_chunk_list()?;
if let Some(chunk) = p.iter().filter(|item| item.signature == fourcc).nth(index as usize) {
Ok ((chunk.start, chunk.length))
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
fn get_list_form(&mut self, fourcc: FourCC) -> Result<Option<u32>, ParserError> {
for (n, (start, _)) in self.get_chunks_extents(LIST_SIG)?.iter().enumerate() {
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 ) );
}
}
Ok( None )
}
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))
} else {
Err( ParserError::ChunkMissing { signature : fourcc })
Err( ParserError::ChunkMissing { signature : fourcc } )
}
}
}
#[test]
fn test_list_form() {
let mut f = WaveReader::open("tests/media/izotope_test.wav").unwrap();
let mut buf : Vec<u8> = vec![];
f.read_list(ADTL_SIG, &mut buf).unwrap();
assert_ne!(buf.len(), 0);
}

View File

@@ -1,104 +1,511 @@
use std::io::{Write, Seek, SeekFrom};
use std::fs::File;
use std::io::Cursor;
use std::io::{Write,Seek,SeekFrom,Cursor};
use super::errors::Error;
use super::chunks::{WriteBWaveChunks};
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::common_format::CommonFormat;
use super::chunks::WriteBWaveChunks;
use super::bext::Bext;
use super::fmt::{WaveFmt};
use super::fourcc::{FourCC, RIFF_SIG, WAVE_SIG, FMT__SIG, JUNK_SIG, BEXT_SIG, DATA_SIG, WriteFourCC};
use byteorder::LittleEndian;
use byteorder::WriteBytesExt;
struct WaveWriter<W> where W: Write + Seek {
inner : W
/// Write audio frames to a `WaveWriter`.
///
///
pub struct AudioFrameWriter<W> where W: Write + Seek {
inner : WaveChunkWriter<W>
}
impl WaveWriter<File> {
pub fn create(path : &str, format:WaveFmt, broadcast_extension: Option<Bext>) -> Result<Self,Error> {
let inner = File::create(path)?;
Self::make(inner, format, broadcast_extension)
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 Vec<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 = vec![0u8; 0];
self.write_integer_frames_to_buffer(&buffer, &mut write_buffer);
self.inner.write(&write_buffer)?;
self.inner.flush()?;
Ok(write_buffer.len() as u64 / self.inner.inner.format.channel_count as u64)
}
/// 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> {
self.inner.end()
}
}
impl<W:Write + Seek> WaveWriter<W> {
/// 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
/// 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 fn make(inner : W, format: WaveFmt, broadcast_extension: Option<Bext>) -> Result<Self, Error> {
let mut retval = Self { inner };
retval.prepare_created(format, broadcast_extension)?;
Ok(retval)
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 } )
}
fn prepare_created(&mut self, format : WaveFmt, broadcast_extension: Option<Bext>) -> Result<(),Error> {
self.inner.write_fourcc(RIFF_SIG)?;
self.inner.write_u32::<LittleEndian>(4)?;
self.inner.write_fourcc(WAVE_SIG)?;
let mut written : u64 = 4;
let ds64_reservation = [0u8; 92];
written += self.primitive_append_chunk(JUNK_SIG, &ds64_reservation)?;
let fmt_data : Vec<u8> = {
let mut c = Cursor::new(vec![]);
c.write_wave_fmt(&format)?;
c.into_inner()
};
written += self.primitive_append_chunk(FMT__SIG, &fmt_data)?;
if let Some(bext) = broadcast_extension {
let mut b = Cursor::new(vec![]);
b.write_bext(&bext)?;
let data = b.into_inner();
written += self.primitive_append_chunk(BEXT_SIG, &data)?;
fn end(mut self) -> Result<WaveWriter<W>, Error> {
if self.length % 2 == 1 {
self.inner.inner.seek(SeekFrom::End(0))?;
self.inner.inner.write(&[0u8])?;
self.inner.increment_form_length(1)?;
}
// show our work
let desired_data_alignment = 0x4000;
let data_fourcc_start = desired_data_alignment - 8;
let current_position_from_start = written + 8;
let data_pad_length = data_fourcc_start - current_position_from_start;
Ok( self.inner )
}
let data_padding = vec![0u8; data_pad_length as usize];
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)?;
} 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
written += self.primitive_append_chunk(JUNK_SIG, &data_padding)?;
self.inner.write_fourcc(DATA_SIG)?;
self.inner.write_u32::<LittleEndian>(0)?;
written += 8;
self.inner.seek(SeekFrom::Start(4))?;
self.inner.write_u32::<LittleEndian>(written as u32)?;
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(())
}
}
fn primitive_append_chunk(&mut self, signature: FourCC, data: &[u8]) -> Result<u64,Error> {
assert!((data.len() as u32) < u32::MAX,
"primitive_append_chunk called with a long data buffer");
impl<W> Write for WaveChunkWriter<W> where W: Write + Seek {
self.inner.write_fourcc(signature)?;
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 )
}
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
/// 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.
/// `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
/// 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);
/// let w = WaveWriter::new(&mut cursor, format).unwrap();
///
/// let mut frame_writer = w.audio_frame_writer().unwrap();
///
/// frame_writer.write_integer_frames(&[0i32]).unwrap();
/// frame_writer.write_integer_frames(&[0i32]).unwrap();
/// frame_writer.write_integer_frames(&[0i32]).unwrap();
/// frame_writer.end().unwrap();
/// ```
///
/// ## 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)
/// (August 1991), IBM Corporation and Microsoft Corporation
///
/// ### Implementation of Broadcast Wave Files
/// - [EBU Tech 3285][ebu3285] (May 2011), "Specification of the Broadcast Wave Format (BWF)"
/// - [Supplement 1](https://tech.ebu.ch/docs/tech/tech3285s1.pdf) (July 1997): MPEG Audio
/// - [EBU Rec 68](https://tech.ebu.ch/docs/r/r068.pdf): Signal modulation and format constraints
///
/// ### Implementation of 64-bit Wave Files
/// - [ITU-R 2088][itu2088] (October 2019), "Long-form file format for the international exchange of audio programme materials with metadata"
/// - 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,
form_length: u64,
/// True if file is RF64
pub is_rf64: bool,
/// Format of the wave file.
pub format: WaveFmt
}
const DS64_RESERVATION_LENGTH : u32 = 96;
impl WaveWriter<File> {
/// Create a new Wave file at `path`.
pub fn create(path : &str, format : WaveFmt) -> Result<Self, Error> {
let f = File::create(path)?;
Ok( Self::new(f, format)? )
}
}
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
/// 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> {
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};
retval.increment_form_length(4)?;
// write ds64_reservation
retval.write_junk(DS64_RESERVATION_LENGTH)?;
let mut chunk = retval.chunk(FMT__SIG)?;
chunk.write_wave_fmt(&format)?;
let retval = chunk.end()?;
Ok( retval )
}
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);
self.inner.write_u32::<LittleEndian>(data.len() as u32)?;
self.inner.write_all(&data)?;
let padding : u64 = data.len() as u64 % 2;
if padding == 1 {
self.inner.write_u8(0)?;
self.inner.write(data)?;
if data.len() % 2 == 0 {
self.increment_form_length(8 + data.len() as u64)?;
} else {
self.inner.write(&[0u8])?;
self.increment_form_length(8 + data.len() as u64 + 1)?;
}
Ok(())
}
Ok(8 + data.len() as u64 + padding)
/// 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
/// bext chunk will be positioned after it.
pub fn write_broadcast_metadata(&mut self, bext: &Bext) -> Result<(),Error> {
let mut c = Cursor::new(vec![0u8; 0]);
c.write_bext(&bext)?;
let buf = c.into_inner();
self.write_chunk(BEXT_SIG, &buf )?;
Ok(())
}
/// Write iXML metadata
pub fn write_ixml(&mut self, ixml: &[u8]) -> Result<(),Error> {
self.write_chunk(IXML_SIG, &ixml)
}
/// Write axml/ADM metadata
pub fn write_axml(&mut self, axml: &[u8]) -> Result<(), Error> {
self.write_chunk(AXML_SIG, &axml)
}
/// Write a `JUNK` filler chunk
pub fn write_junk(&mut self, length: u32) -> Result<(), Error> {
let filler = vec![0u8; length as usize];
self.write_chunk(JUNK_SIG, &filler)
}
/// Create an audio frame writer, which takes possession of the callee
/// `WaveWriter`.
///
pub fn audio_frame_writer(mut self) -> Result<AudioFrameWriter<W>, Error> {
// append elm1 chunk
let framing = 0x4000;
let lip = self.inner.seek(SeekFrom::End(0))?;
let to_add = framing - (lip % framing) - 16;
let mut chunk = self.chunk(ELM1_SIG)?;
let buf = vec![0u8; to_add as usize];
chunk.write(&buf)?;
let closed = chunk.end()?;
let inner = closed.chunk(DATA_SIG)?;
Ok( AudioFrameWriter::new(inner) )
}
/// Open a wave chunk writer here
fn chunk(mut self, ident: FourCC) -> Result<WaveChunkWriter<W>,Error> {
self.inner.seek(SeekFrom::End(0))?;
WaveChunkWriter::begin(self, ident)
}
/// Upgrade this file to RF64
fn promote_to_rf64(&mut self) -> Result<(), std::io::Error> {
if !self.is_rf64 {
self.inner.seek(SeekFrom::Start(0))?;
self.inner.write_fourcc(RF64_SIG)?;
self.inner.write_u32::<LittleEndian>(0xFFFF_FFFF)?;
self.inner.seek(SeekFrom::Start(12))?;
self.inner.write_fourcc(DS64_SIG)?;
self.inner.seek(SeekFrom::Current(4))?;
self.inner.write_u64::<LittleEndian>(self.form_length)?;
self.is_rf64 = true;
}
Ok(())
}
/// Add `amount` to the RIFF/RF64 form length
fn increment_form_length(&mut self, amount: u64) -> Result<(), std::io::Error> {
self.form_length = self.form_length + amount;
if self.is_rf64 {
self.inner.seek(SeekFrom::Start(8 + 4 + 8))?;
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)?;
} else {
self.promote_to_rf64()?;
}
Ok(())
}
}
#[test]
fn test_chunk_append() -> Result<(), Error> {
let mut test :Vec<u8> = vec![];
let mut cursor = Cursor::new(test);
let f = WaveFmt::new_pcm(48000, 16, 1);
let mut w = WaveWriter::make(cursor, f, None)?;
fn test_new() {
use std::io::Cursor;
use super::fourcc::ReadFourCC;
use byteorder::ReadBytesExt;
let mut cursor = Cursor::new(vec![0u8;0]);
let format = WaveFmt::new_pcm_mono(4800, 24);
WaveWriter::new(&mut cursor, format).unwrap();
Ok(())
cursor.seek(SeekFrom::Start(0)).unwrap();
assert_eq!(cursor.read_fourcc().unwrap(), RIFF_SIG);
let form_size = cursor.read_u32::<LittleEndian>().unwrap();
assert_eq!(cursor.read_fourcc().unwrap(), WAVE_SIG);
assert_eq!(cursor.read_fourcc().unwrap(), JUNK_SIG);
let junk_size = cursor.read_u32::<LittleEndian>().unwrap();
assert_eq!(junk_size,96);
cursor.seek(SeekFrom::Current(junk_size as i64)).unwrap();
assert_eq!(cursor.read_fourcc().unwrap(), FMT__SIG);
let fmt_size = cursor.read_u32::<LittleEndian>().unwrap();
assert_eq!(form_size, 4 + 8 + junk_size + 8 + fmt_size);
}
#[test]
fn test_write_audio() {
use std::io::Cursor;
use super::fourcc::ReadFourCC;
use byteorder::ReadBytesExt;
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();
frame_writer.write_integer_frames(&[0i32]).unwrap();
frame_writer.write_integer_frames(&[0i32]).unwrap();
frame_writer.end().unwrap();
cursor.seek(SeekFrom::Start(0)).unwrap();
cursor.seek(SeekFrom::Start(0)).unwrap();
assert_eq!(cursor.read_fourcc().unwrap(), RIFF_SIG);
let form_size = cursor.read_u32::<LittleEndian>().unwrap();
assert_eq!(cursor.read_fourcc().unwrap(), WAVE_SIG); //4
assert_eq!(cursor.read_fourcc().unwrap(), JUNK_SIG); //4
let junk_size = cursor.read_u32::<LittleEndian>().unwrap(); //4
cursor.seek(SeekFrom::Current(junk_size as i64)).unwrap();
assert_eq!(cursor.read_fourcc().unwrap(), FMT__SIG); //4
let fmt_size = cursor.read_u32::<LittleEndian>().unwrap(); //4
cursor.seek(SeekFrom::Current(fmt_size as i64)).unwrap();
assert_eq!(cursor.read_fourcc().unwrap(), ELM1_SIG); //4
let elm1_size = cursor.read_u32::<LittleEndian>().unwrap(); //4
cursor.seek(SeekFrom::Current(elm1_size as i64)).unwrap();
assert_eq!(cursor.read_fourcc().unwrap(), DATA_SIG); //4
let data_size = cursor.read_u32::<LittleEndian>().unwrap(); //4
assert_eq!(data_size, 9);
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)
}
#[test]
fn test_write_bext() {
use std::io::Cursor;
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();
let bext = Bext {
description: String::from("Test description"),
originator: String::from(""),
originator_reference: String::from(""),
origination_date: String::from("2020-01-01"),
origination_time: String::from("12:34:56"),
time_reference: 0,
version: 0,
umid: None,
loudness_value: None,
loudness_range: None,
max_true_peak_level: None,
max_momentary_loudness: None,
max_short_term_loudness: None,
coding_history: String::from(""),
};
w.write_broadcast_metadata(&bext).unwrap();
let mut frame_writer = w.audio_frame_writer().unwrap();
frame_writer.write_integer_frames(&[0i32]).unwrap();
frame_writer.write_integer_frames(&[0i32]).unwrap();
frame_writer.write_integer_frames(&[0i32]).unwrap();
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 format = WaveFmt::new_pcm_stereo(48000, 24);
let w = WaveWriter::new(&mut cursor, format).unwrap();
let buflen = 16000 as u64;
let buf = vec![0i32; buflen as usize];
let four_and_a_half_hours_of_frames = 48000 * 16_200;
let mut af = w.audio_frame_writer().unwrap();
for _ in 0..(four_and_a_half_hours_of_frames * format.channel_count as u64 / buflen) {
af.write_integer_frames(&buf).unwrap();
}
af.end().unwrap();
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();
assert_eq!(cursor.read_fourcc().unwrap(), RF64_SIG);
assert_eq!(cursor.read_u32::<LittleEndian>().unwrap(), 0xFFFF_FFFF);
assert_eq!(cursor.read_fourcc().unwrap(), WAVE_SIG);
assert_eq!(cursor.read_fourcc().unwrap(), DS64_SIG);
let ds64_size = cursor.read_u32::<LittleEndian>().unwrap();
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();
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();
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);
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)
}

View File

@@ -82,10 +82,11 @@ fn test_read() {
let path = "tests/media/audacity_16bit.wav";
let mut w = WaveReader::open(path).expect("Failure opening test file");
let mut buffer = w.format().unwrap().create_frame_buffer();
let mut reader = w.audio_frame_reader().unwrap();
let mut buffer = reader.create_frame_buffer();
assert_eq!(reader.read_integer_frame(&mut buffer).unwrap(), 1);
assert_eq!(buffer[0], -2823_i32);
@@ -100,11 +101,10 @@ fn test_locate_multichannel_read() {
let path = "tests/media/ff_pink.wav";
let mut w = WaveReader::open(path).expect("Failure opening test file");
let mut buffer = w.format().unwrap().create_frame_buffer();
let mut reader = w.audio_frame_reader().unwrap();
let mut buffer = reader.create_frame_buffer();
assert_eq!(reader.read_integer_frame(&mut buffer).unwrap(), 1);
assert_eq!(buffer[0], 332702_i32);
assert_eq!(buffer[1], 3258791_i32);
@@ -157,3 +157,23 @@ fn test_channels_stereo_no_fmt_extended() {
assert_eq!(channels[0].speaker,ChannelMask::FrontLeft);
assert_eq!(channels[1].speaker,ChannelMask::FrontRight);
}
//See issue 6 and 7
#[test]
fn test_frame_reader_consumes_reader() {
// Issue #6
use bwavfile::WaveFmt;
use bwavfile::AudioFrameReader;
use std::fs::File;
fn from_wav_filename(wav_filename: &str) -> Result<(WaveFmt, AudioFrameReader<File>), ()> {
if let Ok(mut r) = WaveReader::open(&wav_filename) {
let format = r.format().unwrap();
let frame_reader = r.audio_frame_reader().unwrap();
Ok((format, frame_reader))
} else {
Err(())
}
}
let _result = from_wav_filename("tests/media/pt_24bit_stereo.wav").unwrap();
}

Binary file not shown.