flv: Implement `AudioData` parsing

This commit is contained in:
David Wendt 2023-04-08 17:52:33 -04:00 committed by kmeisthax
parent 9cbc371a6a
commit f998ec20e8
3 changed files with 188 additions and 8 deletions

View File

@ -47,4 +47,8 @@ impl<'a> FlvReader<'a> {
self.read(4)?.try_into().expect("four bytes"),
))
}
pub fn position(&self) -> usize {
self.position
}
}

View File

@ -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]
})
);
}
}

View File

@ -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,
}
}
}