diff --git a/src/audio_frame_reader.rs b/src/audio_frame_reader.rs index 89e208c..ddc36d1 100644 --- a/src/audio_frame_reader.rs +++ b/src/audio_frame_reader.rs @@ -4,7 +4,7 @@ use std::io::SeekFrom::{Start,}; use byteorder::LittleEndian; use byteorder::ReadBytesExt; -use super::chunks::WaveFmt; +use super::fmt::WaveFmt; use super::errors::Error; /// Read audio frames diff --git a/src/bext.rs b/src/bext.rs new file mode 100644 index 0000000..4c26744 --- /dev/null +++ b/src/bext.rs @@ -0,0 +1,82 @@ + +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). + */ +#[derive(Debug)] +pub struct Bext { + + /// 256 ASCII character field with free text. + pub description: String, + + /// Originating application. + pub originator: String, + + /// Application-specific UID. + pub originator_reference: String, + + /// Creation date in format `YYYY-MM-DD`. + pub origination_date: String, + + /// Creation time in format `HH:MM:SS`. + pub origination_time: String, + + /// Time of the start of this wave file, expressed as the number of samples + /// since local midnight. + pub time_reference: u64, + + /// Bext chunk version. + /// + /// 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, + + /// Loudness range in LU. + /// + /// This field is `None` if the version is less than 2. + pub loudness_range: Option, + + /// Maximum True Peak Level in decibels True Peak. + /// + /// This field is `None` if the version is less than 2. + pub max_true_peak_level: Option, + + /// Maximum momentary loudness in LUFS. + /// + /// This field is `None` if the version is less than 2. + pub max_momentary_loudness: Option, + + /// Maximum short-term loudness in LUFS. + /// + /// This field is `None` if the version is less than 2. + pub max_short_term_loudness: Option, + // 180 bytes of nothing + + /// Coding History. + pub coding_history: String +} diff --git a/src/chunks.rs b/src/chunks.rs index cb88298..e7402c0 100644 --- a/src/chunks.rs +++ b/src/chunks.rs @@ -9,180 +9,8 @@ use encoding::all::ASCII; use byteorder::LittleEndian; use byteorder::{ReadBytesExt, WriteBytesExt}; -/** - * References: - * - http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/multichaudP.pdf -*/ -#[derive(PartialEq)] -enum FormatTags { - Integer = 0x0001, - Float = 0x0003, - Extensible = 0xFFFE -} - -const PCM_SUBTYPE_UUID: [u8; 16] = [0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x10, - 0x80, 0x00, 0x00, 0xaa, - 0x00, 0x38, 0x9b, 0x71]; - -const FLOAT_SUBTYPE_UUID: [u8; 16] = [0x00, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x00, 0x10, - 0x80, 0x00, 0x00, 0xaa, - 0x00, 0x38, 0x9b, 0x71]; - -/* -https://docs.microsoft.com/en-us/windows-hardware/drivers/audio/subformat-guids-for-compressed-audio-formats -http://dream.cs.bath.ac.uk/researchdev/wave-ex/bformat.html - -These are from http://dream.cs.bath.ac.uk/researchdev/wave-ex/mulchaud.rtf -*/ - -#[derive(Debug)] -pub enum WaveFmtExtendedChannelMask { - 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 -} - - -/** - * Extended Wave Format - * - * https://docs.microsoft.com/en-us/windows/win32/api/mmreg/ns-mmreg-waveformatextensible - */ -#[derive(Debug)] -pub struct WaveFmtExtended { - - /// Valid bits per sample - pub valid_bits_per_sample : u16, - - /// Channel mask - /// - /// Identifies the speaker assignment for each channel in the file - pub channel_mask : WaveFmtExtendedChannelMask, - - /// Codec GUID - /// - /// Identifies the codec of the audio stream - pub type_guid : [u8; 16], -} - -/** - * 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. - * - */ -#[derive(Debug)] -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, - - /// Count of audio channels in each frame - pub channel_count: u16, - - /// Sample rate of the audio data - 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, - - /// Count of bits stored in the file per sample - 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 -} - - -impl WaveFmt { - - /// Create a new integer PCM format `WaveFmt` - pub fn new_pcm(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 { - 0 => panic!("Error"), - 1..=2 => FormatTags::Integer as u16, - _ => FormatTags::Extensible as u16, - }; - - 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: None - } - } -} - -/** - * 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). - */ -#[derive(Debug)] -pub struct Bext { - pub description: String, - pub originator: String, - pub originator_reference: String, - pub origination_date: String, - pub origination_time: String, - pub time_reference: u64, - pub version: u16, - pub umid: Option<[u8; 64]>, - pub loudness_value: Option, - pub loudness_range: Option, - pub max_true_peak_level: Option, - pub max_momentary_loudness: Option, - pub max_short_term_loudness: Option, - // 180 bytes of nothing - pub coding_history: String -} +use super::fmt::WaveFmt; +use super::bext::Bext; pub trait ReadBWaveChunks: Read { fn read_bext(&mut self) -> Result; diff --git a/src/fmt.rs b/src/fmt.rs new file mode 100644 index 0000000..5e29165 --- /dev/null +++ b/src/fmt.rs @@ -0,0 +1,143 @@ + +/** + * References: + * - http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/multichaudP.pdf +*/ +#[derive(PartialEq)] +enum FormatTags { + Integer = 0x0001, + Float = 0x0003, + Extensible = 0xFFFE +} + +const PCM_SUBTYPE_UUID: [u8; 16] = [0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x10, + 0x80, 0x00, 0x00, 0xaa, + 0x00, 0x38, 0x9b, 0x71]; + +const FLOAT_SUBTYPE_UUID: [u8; 16] = [0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x10, + 0x80, 0x00, 0x00, 0xaa, + 0x00, 0x38, 0x9b, 0x71]; + +/* +https://docs.microsoft.com/en-us/windows-hardware/drivers/audio/subformat-guids-for-compressed-audio-formats +http://dream.cs.bath.ac.uk/researchdev/wave-ex/bformat.html + +These are from http://dream.cs.bath.ac.uk/researchdev/wave-ex/mulchaud.rtf +*/ + +#[derive(Debug)] +pub enum WaveFmtExtendedChannelMask { + 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 +} + + +/** + * Extended Wave Format + * + * https://docs.microsoft.com/en-us/windows/win32/api/mmreg/ns-mmreg-waveformatextensible + */ +#[derive(Debug)] +pub struct WaveFmtExtended { + + /// Valid bits per sample + pub valid_bits_per_sample : u16, + + /// Channel mask + /// + /// Identifies the speaker assignment for each channel in the file + pub channel_mask : WaveFmtExtendedChannelMask, + + /// Codec GUID + /// + /// Identifies the codec of the audio stream + pub type_guid : [u8; 16], +} + +/** + * 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. + * + */ +#[derive(Debug)] +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, + + /// Count of audio channels in each frame + pub channel_count: u16, + + /// Sample rate of the audio data + 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, + + /// Count of bits stored in the file per sample + 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 +} + + +impl WaveFmt { + + /// Create a new integer PCM format `WaveFmt` + pub fn new_pcm(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 { + 0 => panic!("Error"), + 1..=2 => FormatTags::Integer as u16, + _ => FormatTags::Extensible as u16, + }; + + 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: None + } + } +} + diff --git a/src/lib.rs b/src/lib.rs index fdae856..da722f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,6 +49,9 @@ Things that are _not_ necessarily in the scope of this package: ### 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" @@ -66,9 +69,21 @@ Things that are _not_ necessarily in the scope of this package: - [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) - IBM Corporation and Microsoft Corporation, (August 1991) + (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 + field of the Broadcast Wave Format" +- [EBU Tech R098](https://tech.ebu.ch/docs/r/r098.pdf) (1999) "Format for the 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 @@ -95,11 +110,14 @@ mod validation; mod raw_chunk_reader; mod audio_frame_reader; mod chunks; +mod bext; +mod fmt; mod wavereader; mod wavewriter; pub use wavereader::{WaveReader}; -pub use chunks::{WaveFmt,Bext}; +pub use bext::Bext; +pub use fmt::{WaveFmt, WaveFmtExtended}; pub use errors::Error; pub use audio_frame_reader::AudioFrameReader; \ No newline at end of file diff --git a/src/wavereader.rs b/src/wavereader.rs index c65909e..904f41b 100644 --- a/src/wavereader.rs +++ b/src/wavereader.rs @@ -5,7 +5,8 @@ use super::parser::Parser; use super::fourcc::{FourCC, FMT__SIG, BEXT_SIG, DATA_SIG}; use super::errors::Error as ParserError; use super::raw_chunk_reader::RawChunkReader; -use super::chunks::{WaveFmt, Bext}; +use super::fmt::WaveFmt; +use super::bext::Bext; use super::audio_frame_reader::AudioFrameReader; use super::chunks::ReadBWaveChunks; //use super::validation; diff --git a/src/wavewriter.rs b/src/wavewriter.rs index 526fd28..0fcee76 100644 --- a/src/wavewriter.rs +++ b/src/wavewriter.rs @@ -3,7 +3,9 @@ use std::fs::File; use std::io::Cursor; use super::errors::Error; -use super::chunks::{WaveFmt, Bext, WriteBWaveChunks}; +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;