flv: Implement `AudioData` parsing
This commit is contained in:
parent
9cbc371a6a
commit
f998ec20e8
|
@ -47,4 +47,8 @@ impl<'a> FlvReader<'a> {
|
|||
self.read(4)?.try_into().expect("four bytes"),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn position(&self) -> usize {
|
||||
self.position
|
||||
}
|
||||
}
|
||||
|
|
158
flv/src/sound.rs
158
flv/src/sound.rs
|
@ -1,4 +1,7 @@
|
|||
use crate::FlvReader;
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub enum SoundFormat {
|
||||
LinearPCMPlatformEndian = 0,
|
||||
Adpcm = 1,
|
||||
|
@ -15,7 +18,31 @@ pub enum SoundFormat {
|
|||
DeviceSpecific = 15,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for SoundFormat {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0 => Ok(Self::LinearPCMPlatformEndian),
|
||||
1 => Ok(Self::Adpcm),
|
||||
2 => Ok(Self::MP3),
|
||||
3 => Ok(Self::LinearPCMLittleEndian),
|
||||
4 => Ok(Self::Nellymoser16kHz),
|
||||
5 => Ok(Self::Nellymoser8kHz),
|
||||
6 => Ok(Self::Nellymoser),
|
||||
7 => Ok(Self::G711ALawPCM),
|
||||
8 => Ok(Self::G711MuLawPCM),
|
||||
10 => Ok(Self::Aac),
|
||||
11 => Ok(Self::Speex),
|
||||
14 => Ok(Self::MP38kHz),
|
||||
15 => Ok(Self::DeviceSpecific),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub enum SoundRate {
|
||||
R5_500 = 0,
|
||||
R11_000 = 1,
|
||||
|
@ -23,14 +50,145 @@ pub enum SoundRate {
|
|||
R44_000 = 3,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for SoundRate {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0 => Ok(Self::R5_500),
|
||||
1 => Ok(Self::R11_000),
|
||||
2 => Ok(Self::R22_000),
|
||||
3 => Ok(Self::R44_000),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub enum SoundSize {
|
||||
Bits8 = 0,
|
||||
Bits16 = 1,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for SoundSize {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0 => Ok(Self::Bits8),
|
||||
1 => Ok(Self::Bits16),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub enum SoundType {
|
||||
Mono = 0,
|
||||
Stereo = 1,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for SoundType {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0 => Ok(Self::Mono),
|
||||
1 => Ok(Self::Stereo),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub struct AudioData<'a> {
|
||||
format: SoundFormat,
|
||||
rate: SoundRate,
|
||||
size: SoundSize,
|
||||
sound_type: SoundType,
|
||||
data: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a> AudioData<'a> {
|
||||
/// Parse an audio data structure.
|
||||
///
|
||||
/// This does not parse the actual audio data itself, which is instead
|
||||
/// returned as an array that must be provided to your audio decoder.
|
||||
///
|
||||
/// `data_size` is the size of the entire audio data structure, *including*
|
||||
/// the header.
|
||||
///
|
||||
/// If `None` is yielded, the data stream is not a valid audio header.
|
||||
pub fn parse(reader: &mut FlvReader<'a>, data_size: u32) -> Option<Self> {
|
||||
let start = reader.position();
|
||||
let format_spec = reader.read_u8()?;
|
||||
let header_size = reader.position() - start;
|
||||
if (data_size as usize) < header_size {
|
||||
return None;
|
||||
}
|
||||
|
||||
let format = SoundFormat::try_from(format_spec & 0x0F).ok()?;
|
||||
let rate = SoundRate::try_from((format_spec >> 4) & 0x03).ok()?;
|
||||
let size = SoundSize::try_from((format_spec >> 6) & 0x01).ok()?;
|
||||
let sound_type = SoundType::try_from((format_spec >> 7) & 0x01).ok()?;
|
||||
let data = reader.read(data_size as usize - header_size)?;
|
||||
|
||||
Some(AudioData {
|
||||
format,
|
||||
rate,
|
||||
size,
|
||||
sound_type,
|
||||
data,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::reader::FlvReader;
|
||||
use crate::sound::{AudioData, SoundFormat, SoundRate, SoundSize, SoundType};
|
||||
|
||||
#[test]
|
||||
fn read_audiodata() {
|
||||
let data = [0xFA, 0x12, 0x34, 0x56, 0x78]; //AAC 10 | 44khz 3 | 16bit 1 | Stereo 1
|
||||
let mut reader = FlvReader::from_source(&data);
|
||||
|
||||
assert_eq!(
|
||||
AudioData::parse(&mut reader, data.len() as u32),
|
||||
Some(AudioData {
|
||||
format: SoundFormat::Aac,
|
||||
rate: SoundRate::R44_000,
|
||||
size: SoundSize::Bits16,
|
||||
sound_type: SoundType::Stereo,
|
||||
data: &[0x12, 0x34, 0x56, 0x78]
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_audiodata_invalid_len() {
|
||||
let data = [0xFA, 0x12, 0x34, 0x56, 0x78]; //AAC 10 | 44khz 3 | 16bit 1 | Stereo 1
|
||||
let mut reader = FlvReader::from_source(&data);
|
||||
|
||||
assert_eq!(AudioData::parse(&mut reader, 0), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_audiodata_short_len() {
|
||||
let data = [0xFA, 0x12, 0x34, 0x56, 0x78]; //AAC 10 | 44khz 3 | 16bit 1 | Stereo 1
|
||||
let mut reader = FlvReader::from_source(&data);
|
||||
|
||||
assert_eq!(
|
||||
AudioData::parse(&mut reader, 2),
|
||||
Some(AudioData {
|
||||
format: SoundFormat::Aac,
|
||||
rate: SoundRate::R44_000,
|
||||
size: SoundSize::Bits16,
|
||||
sound_type: SoundType::Stereo,
|
||||
data: &[0x12]
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
use crate::sound::{SoundFormat, SoundRate, SoundSize, SoundType};
|
||||
use crate::reader::FlvReader;
|
||||
use crate::sound::AudioData;
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum TagData<'a> {
|
||||
Audio {
|
||||
format: SoundFormat,
|
||||
rate: SoundRate,
|
||||
size: SoundSize,
|
||||
sound_type: SoundType,
|
||||
data: &'a [u8],
|
||||
} = 8,
|
||||
Audio(AudioData<'a>) = 8,
|
||||
Video = 9,
|
||||
Script = 18,
|
||||
}
|
||||
|
@ -18,3 +13,26 @@ pub struct Tag<'a> {
|
|||
stream_id: u32, //24 bits max
|
||||
data: TagData<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Tag<'a> {
|
||||
/// Parse a single FLV tag structure.
|
||||
///
|
||||
/// This assumes the reader is currently pointing at the tag itself, not
|
||||
/// the back-pointers in between the tags.
|
||||
pub fn parse(reader: &mut FlvReader<'a>) -> Option<Self> {
|
||||
let tag_type = reader.read_u8()?;
|
||||
let data_size = reader.read_u24()?;
|
||||
let timestamp = reader.read_u24()?;
|
||||
let timestamp_extended = reader.read_u8()?;
|
||||
let stream_id = reader.read_u24()?;
|
||||
|
||||
match tag_type {
|
||||
8 => Some(Tag {
|
||||
timestamp: ((timestamp_extended as u32) << 24 | timestamp) as i32,
|
||||
stream_id,
|
||||
data: TagData::Audio(AudioData::parse(reader, data_size)?),
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue