flv: Refactor tag parsing to use error codes instead of `None`.
This commit is contained in:
parent
e4b76ac149
commit
0f176484ea
|
@ -1650,6 +1650,7 @@ name = "flv-rs"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags 2.3.2",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -7,3 +7,4 @@ license = "MIT OR Apache-2.0"
|
|||
|
||||
[dependencies]
|
||||
bitflags = "2.0.0"
|
||||
thiserror = "1.0"
|
|
@ -0,0 +1,115 @@
|
|||
use std::io::Error as IoError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("the FLV parser ran out of data")]
|
||||
EndOfData,
|
||||
|
||||
#[error("the FLV cannot be read as its length exceeds the maximum memory size for this architecture")]
|
||||
PointerTooBig,
|
||||
|
||||
#[error("the data stream does not have a valid FLV header signature")]
|
||||
WrongMagic,
|
||||
|
||||
#[error("the FLV contains a script data block with a value of unknown type")]
|
||||
UnknownValueType,
|
||||
|
||||
#[error("the FLV contains an audio data block that is too short")]
|
||||
ShortAudioBlock,
|
||||
|
||||
#[error("the FLV contains an audio data block with unknown format type {0}")]
|
||||
UnknownAudioFormatType(u8),
|
||||
|
||||
#[error("the FLV contains an audio data block with unknown sample rate {0}")]
|
||||
UnknownAudioRate(u8),
|
||||
|
||||
#[error("the FLV contains an audio data block with unknown sample size {0}")]
|
||||
UnknownAudioSampleSize(u8),
|
||||
|
||||
#[error("the FLV contains an audio data block with unknown channel count {0}")]
|
||||
UnknownAudioChannelCount(u8),
|
||||
|
||||
#[error("the FLV contains an audio data block with AAC data that is of unknown type {0}")]
|
||||
UnknownAacPacketType(u8),
|
||||
|
||||
#[error("the FLV contains a video data block that is too short")]
|
||||
ShortVideoBlock,
|
||||
|
||||
#[error("the FLV contains a video data block with unknown frame type {0}")]
|
||||
UnknownVideoFrameType(u8),
|
||||
|
||||
#[error("the FLV contains a video data block with unknown codec {0}")]
|
||||
UnknownVideoCodec(u8),
|
||||
|
||||
#[error("the FLV contains a video data block with a command frame of unknown type {0}")]
|
||||
UnknownVideoCommandType(u8),
|
||||
|
||||
#[error("the FLV contains a video data block with AVC data that is of unknown type {0}")]
|
||||
UnknownAvcPacketType(u8),
|
||||
|
||||
#[error("the FLV contains a tag with unknown type {0}")]
|
||||
UnknownTagType(u8),
|
||||
|
||||
#[error("IO error ({0})")]
|
||||
IoError(IoError),
|
||||
}
|
||||
|
||||
impl From<IoError> for Error {
|
||||
fn from(error: IoError) -> Self {
|
||||
Self::IoError(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Error {
|
||||
fn eq(&self, other: &Error) -> bool {
|
||||
match (self, other) {
|
||||
(Self::EndOfData, Self::EndOfData) => true,
|
||||
(Self::PointerTooBig, Self::PointerTooBig) => true,
|
||||
(Self::WrongMagic, Self::WrongMagic) => true,
|
||||
(Self::UnknownValueType, Self::UnknownValueType) => true,
|
||||
(Self::ShortAudioBlock, Self::ShortAudioBlock) => true,
|
||||
(Self::UnknownAudioFormatType(s), Self::UnknownAudioFormatType(o)) => s == o,
|
||||
(Self::UnknownAudioRate(s), Self::UnknownAudioRate(o)) => s == o,
|
||||
(Self::UnknownAudioSampleSize(s), Self::UnknownAudioSampleSize(o)) => s == o,
|
||||
(Self::UnknownAudioChannelCount(s), Self::UnknownAudioChannelCount(o)) => s == o,
|
||||
(Self::UnknownAacPacketType(s), Self::UnknownAacPacketType(o)) => s == o,
|
||||
(Self::ShortVideoBlock, Self::ShortVideoBlock) => true,
|
||||
(Self::UnknownVideoFrameType(s), Self::UnknownVideoFrameType(o)) => s == o,
|
||||
(Self::UnknownVideoCodec(s), Self::UnknownVideoCodec(o)) => s == o,
|
||||
(Self::UnknownVideoCommandType(s), Self::UnknownVideoCommandType(o)) => s == o,
|
||||
(Self::UnknownAvcPacketType(s), Self::UnknownAvcPacketType(o)) => s == o,
|
||||
(Self::UnknownTagType(s), Self::UnknownTagType(o)) => s == o,
|
||||
(Self::IoError(s), Self::IoError(o)) => s.kind() == o.kind(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Error {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Self::EndOfData => Self::EndOfData,
|
||||
Self::PointerTooBig => Self::PointerTooBig,
|
||||
Self::WrongMagic => Self::WrongMagic,
|
||||
Self::UnknownValueType => Self::UnknownValueType,
|
||||
Self::ShortAudioBlock => Self::ShortAudioBlock,
|
||||
Self::UnknownAudioFormatType(unk) => Self::UnknownAudioFormatType(*unk),
|
||||
Self::UnknownAudioRate(unk) => Self::UnknownAudioRate(*unk),
|
||||
Self::UnknownAudioSampleSize(unk) => Self::UnknownAudioSampleSize(*unk),
|
||||
Self::UnknownAudioChannelCount(unk) => Self::UnknownAudioChannelCount(*unk),
|
||||
Self::UnknownAacPacketType(unk) => Self::UnknownAacPacketType(*unk),
|
||||
Self::ShortVideoBlock => Self::ShortVideoBlock,
|
||||
Self::UnknownVideoFrameType(unk) => Self::UnknownVideoFrameType(*unk),
|
||||
Self::UnknownVideoCodec(unk) => Self::UnknownVideoCodec(*unk),
|
||||
Self::UnknownVideoCommandType(unk) => Self::UnknownVideoCommandType(*unk),
|
||||
Self::UnknownAvcPacketType(unk) => Self::UnknownAvcPacketType(*unk),
|
||||
Self::UnknownTagType(unk) => Self::UnknownTagType(*unk),
|
||||
|
||||
// IOError cannot be cloned, since you can attach arbitrary error
|
||||
// types to it. Instead, cloned FLV errors contain only the error
|
||||
// kind and text, which can be cloned.
|
||||
Self::IoError(ioe) => Self::IoError(IoError::new(ioe.kind(), ioe.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
use crate::error::Error;
|
||||
use crate::reader::FlvReader;
|
||||
use bitflags::bitflags;
|
||||
use std::io::{Seek, SeekFrom};
|
||||
|
@ -20,34 +21,39 @@ pub struct Header {
|
|||
impl Header {
|
||||
/// Parse an FLV header.
|
||||
///
|
||||
/// If this yields `None`, then the given data stream is either not an FLV
|
||||
/// container or too short to parse.
|
||||
pub fn parse(reader: &mut FlvReader<'_>) -> Option<Self> {
|
||||
let old_position = reader.stream_position().ok()?;
|
||||
/// The header must, at a minimum, contain the FLV magic, version number,
|
||||
/// valid type flags, and a valid offset into the data. The reader will
|
||||
/// seek to the start of the data tags if successful or retain it's prior
|
||||
/// position otherwise.
|
||||
pub fn parse(reader: &mut FlvReader<'_>) -> Result<Self, Error> {
|
||||
let old_position = reader.stream_position()?;
|
||||
|
||||
let ret = (|| {
|
||||
let signature = reader.read_u24()?;
|
||||
if signature != 0x464C56 {
|
||||
return None;
|
||||
return Err(Error::WrongMagic);
|
||||
}
|
||||
|
||||
let version = reader.read_u8()?;
|
||||
let type_flags = TypeFlags::from_bits_retain(reader.read_u8()?);
|
||||
let data_offset = reader.read_u32()?;
|
||||
|
||||
Some(Header {
|
||||
Ok(Header {
|
||||
version,
|
||||
type_flags,
|
||||
data_offset,
|
||||
})
|
||||
})();
|
||||
|
||||
if let Some(ret) = ret {
|
||||
reader.seek(SeekFrom::Start(ret.data_offset as u64)).ok()?;
|
||||
Some(ret)
|
||||
} else {
|
||||
reader.seek(SeekFrom::Start(old_position)).ok()?;
|
||||
None
|
||||
match ret {
|
||||
Ok(ret) => {
|
||||
reader.seek(SeekFrom::Start(ret.data_offset as u64))?;
|
||||
Ok(ret)
|
||||
}
|
||||
Err(e) => {
|
||||
reader.seek(SeekFrom::Start(old_position))?;
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +70,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
Header::parse(&mut reader),
|
||||
Some(Header {
|
||||
Ok(Header {
|
||||
version: 1,
|
||||
type_flags: TypeFlags::HAS_AUDIO | TypeFlags::HAS_VIDEO,
|
||||
data_offset: 0x12345678
|
||||
|
|
|
@ -6,6 +6,9 @@ mod video;
|
|||
|
||||
mod reader;
|
||||
|
||||
mod error;
|
||||
|
||||
pub use error::Error;
|
||||
pub use header::Header;
|
||||
pub use reader::FlvReader;
|
||||
pub use script::{ScriptData, Value, Variable};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use std::io::{Error, ErrorKind, Result, Seek, SeekFrom};
|
||||
use crate::error::Error as FlvError;
|
||||
use std::io::{Error as IoError, ErrorKind, Result as IoResult, Seek, SeekFrom};
|
||||
|
||||
/// A reader that allows demuxing an FLV container.
|
||||
pub struct FlvReader<'a> {
|
||||
|
@ -34,22 +35,35 @@ impl<'a> FlvReader<'a> {
|
|||
/// buffer. The buffer position will be advanced so that repeated reads
|
||||
/// yield new data.
|
||||
///
|
||||
/// If the requested number of bytes are not available, `None` is returned.
|
||||
pub fn read(&mut self, count: usize) -> Option<&'a [u8]> {
|
||||
/// If the requested number of bytes are not available, `EndOfData` is
|
||||
/// returned. This error should halt all parsing; callers should take care
|
||||
/// to return the reader to it's prior position and NOT return partial data
|
||||
/// (e.g. headers without body data) so that callers can retry later when
|
||||
/// more data has been downloaded.
|
||||
pub fn read(&mut self, count: usize) -> Result<&'a [u8], FlvError> {
|
||||
let start = self.position;
|
||||
let end = self.position.checked_add(count)?;
|
||||
let end = self
|
||||
.position
|
||||
.checked_add(count)
|
||||
.ok_or(FlvError::PointerTooBig)?;
|
||||
if end > self.source.len() {
|
||||
return None;
|
||||
return Err(FlvError::EndOfData);
|
||||
}
|
||||
|
||||
self.position = end;
|
||||
|
||||
Some(&self.source[start..end])
|
||||
Ok(&self.source[start..end])
|
||||
}
|
||||
|
||||
/// Read a certain number of bytes from the buffer without advancing the
|
||||
/// buffer position.
|
||||
pub fn peek(&mut self, count: usize) -> Option<&'a [u8]> {
|
||||
///
|
||||
/// If the requested number of bytes are not available, `EndOfData` is
|
||||
/// returned. This error should halt all parsing; callers should take care
|
||||
/// to return the reader to it's prior position and NOT return partial data
|
||||
/// (e.g. headers without body data) so that callers can retry later when
|
||||
/// more data has been downloaded.
|
||||
pub fn peek(&mut self, count: usize) -> Result<&'a [u8], FlvError> {
|
||||
let pos = self.position;
|
||||
let ret = self.read(count);
|
||||
|
||||
|
@ -58,57 +72,57 @@ impl<'a> FlvReader<'a> {
|
|||
ret
|
||||
}
|
||||
|
||||
pub fn read_u8(&mut self) -> Option<u8> {
|
||||
Some(self.read(1)?[0])
|
||||
pub fn read_u8(&mut self) -> Result<u8, FlvError> {
|
||||
Ok(self.read(1)?[0])
|
||||
}
|
||||
|
||||
pub fn read_u16(&mut self) -> Option<u16> {
|
||||
Some(u16::from_be_bytes(
|
||||
pub fn read_u16(&mut self) -> Result<u16, FlvError> {
|
||||
Ok(u16::from_be_bytes(
|
||||
self.read(2)?.try_into().expect("two bytes"),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn read_i16(&mut self) -> Option<i16> {
|
||||
Some(i16::from_be_bytes(
|
||||
pub fn read_i16(&mut self) -> Result<i16, FlvError> {
|
||||
Ok(i16::from_be_bytes(
|
||||
self.read(2)?.try_into().expect("two bytes"),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn read_u24(&mut self) -> Option<u32> {
|
||||
pub fn read_u24(&mut self) -> Result<u32, FlvError> {
|
||||
let bytes = self.read(3)?;
|
||||
|
||||
Some(u32::from_be_bytes([0, bytes[0], bytes[1], bytes[2]]))
|
||||
Ok(u32::from_be_bytes([0, bytes[0], bytes[1], bytes[2]]))
|
||||
}
|
||||
|
||||
pub fn peek_u24(&mut self) -> Option<u32> {
|
||||
pub fn peek_u24(&mut self) -> Result<u32, FlvError> {
|
||||
let bytes = self.peek(3)?;
|
||||
|
||||
Some(u32::from_be_bytes([0, bytes[0], bytes[1], bytes[2]]))
|
||||
Ok(u32::from_be_bytes([0, bytes[0], bytes[1], bytes[2]]))
|
||||
}
|
||||
|
||||
pub fn read_u32(&mut self) -> Option<u32> {
|
||||
Some(u32::from_be_bytes(
|
||||
pub fn read_u32(&mut self) -> Result<u32, FlvError> {
|
||||
Ok(u32::from_be_bytes(
|
||||
self.read(4)?.try_into().expect("four bytes"),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn read_f64(&mut self) -> Option<f64> {
|
||||
Some(f64::from_be_bytes(
|
||||
pub fn read_f64(&mut self) -> Result<f64, FlvError> {
|
||||
Ok(f64::from_be_bytes(
|
||||
self.read(8)?.try_into().expect("eight bytes"),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Seek for FlvReader<'a> {
|
||||
fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
|
||||
fn seek(&mut self, pos: SeekFrom) -> IoResult<u64> {
|
||||
let newpos = match pos {
|
||||
SeekFrom::Start(pos) => pos,
|
||||
SeekFrom::Current(pos) => (self.position as i64 + pos)
|
||||
.try_into()
|
||||
.map_err(|e| Error::new(ErrorKind::InvalidInput, e))?,
|
||||
.map_err(|e| IoError::new(ErrorKind::InvalidInput, e))?,
|
||||
SeekFrom::End(pos) => (self.source.len() as i64 - pos)
|
||||
.try_into()
|
||||
.map_err(|e| Error::new(ErrorKind::InvalidInput, e))?,
|
||||
.map_err(|e| IoError::new(ErrorKind::InvalidInput, e))?,
|
||||
};
|
||||
|
||||
self.position = newpos as usize;
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use crate::error::Error;
|
||||
use crate::reader::FlvReader;
|
||||
use std::io::Seek;
|
||||
|
||||
fn parse_string<'a>(reader: &mut FlvReader<'a>, is_long_string: bool) -> Option<&'a [u8]> {
|
||||
fn parse_string<'a>(reader: &mut FlvReader<'a>, is_long_string: bool) -> Result<&'a [u8], Error> {
|
||||
let length = if is_long_string {
|
||||
reader.read_u32()?
|
||||
} else {
|
||||
|
@ -42,29 +43,29 @@ impl<'a> Value<'a> {
|
|||
/// specification as to how they are to be decoded.
|
||||
///
|
||||
/// data_size is the size of the entire script data structure.
|
||||
pub fn parse(reader: &mut FlvReader<'a>) -> Option<Self> {
|
||||
pub fn parse(reader: &mut FlvReader<'a>) -> Result<Self, Error> {
|
||||
let value_type = reader.read_u8()?;
|
||||
|
||||
match value_type {
|
||||
0 => Some(Self::Number(reader.read_f64()?)),
|
||||
1 => Some(Self::Boolean(reader.read_u8()? != 0)),
|
||||
2 => Some(Self::String(parse_string(reader, false)?)),
|
||||
0 => Ok(Self::Number(reader.read_f64()?)),
|
||||
1 => Ok(Self::Boolean(reader.read_u8()? != 0)),
|
||||
2 => Ok(Self::String(parse_string(reader, false)?)),
|
||||
3 => {
|
||||
let mut variables = vec![];
|
||||
loop {
|
||||
let terminator = reader.peek_u24()?;
|
||||
if terminator == 9 {
|
||||
reader.read_u24()?;
|
||||
return Some(Self::Object(variables));
|
||||
return Ok(Self::Object(variables));
|
||||
}
|
||||
|
||||
variables.push(Variable::parse(reader)?);
|
||||
}
|
||||
}
|
||||
4 => Some(Self::MovieClip(parse_string(reader, false)?)),
|
||||
5 => Some(Self::Null),
|
||||
6 => Some(Self::Undefined),
|
||||
7 => Some(Self::Reference(reader.read_u16()?)),
|
||||
4 => Ok(Self::MovieClip(parse_string(reader, false)?)),
|
||||
5 => Ok(Self::Null),
|
||||
6 => Ok(Self::Undefined),
|
||||
7 => Ok(Self::Reference(reader.read_u16()?)),
|
||||
8 => {
|
||||
let length_hint = reader.read_u32()?;
|
||||
let mut variables = Vec::with_capacity(length_hint as usize);
|
||||
|
@ -73,7 +74,7 @@ impl<'a> Value<'a> {
|
|||
let terminator = reader.peek_u24()?;
|
||||
if terminator == 9 {
|
||||
reader.read_u24()?;
|
||||
return Some(Self::EcmaArray(variables));
|
||||
return Ok(Self::EcmaArray(variables));
|
||||
}
|
||||
|
||||
variables.push(Variable::parse(reader)?);
|
||||
|
@ -87,14 +88,14 @@ impl<'a> Value<'a> {
|
|||
variables.push(Variable::parse(reader)?);
|
||||
}
|
||||
|
||||
Some(Self::StrictArray(variables))
|
||||
Ok(Self::StrictArray(variables))
|
||||
}
|
||||
11 => Some(Self::Date {
|
||||
11 => Ok(Self::Date {
|
||||
unix_time: reader.read_f64()?,
|
||||
local_offset: reader.read_i16()?,
|
||||
}),
|
||||
12 => Some(Self::LongString(parse_string(reader, true)?)),
|
||||
_ => None,
|
||||
12 => Ok(Self::LongString(parse_string(reader, true)?)),
|
||||
_ => Err(Error::UnknownValueType),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -111,8 +112,8 @@ pub struct Variable<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Variable<'a> {
|
||||
pub fn parse(reader: &mut FlvReader<'a>) -> Option<Self> {
|
||||
Some(Self {
|
||||
pub fn parse(reader: &mut FlvReader<'a>) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
name: parse_string(reader, false)?,
|
||||
data: Value::parse(reader)?,
|
||||
})
|
||||
|
@ -127,7 +128,7 @@ impl<'a> ScriptData<'a> {
|
|||
///
|
||||
/// No data size parameter is accepted; we parse until we reach an object
|
||||
/// terminator, reach invalid data, or we run out of bytes in the reader.
|
||||
pub fn parse(reader: &mut FlvReader<'a>, data_size: u32) -> Option<Self> {
|
||||
pub fn parse(reader: &mut FlvReader<'a>, data_size: u32) -> Result<Self, Error> {
|
||||
let start = reader.stream_position().expect("valid position");
|
||||
let _trash = reader.read_u8()?;
|
||||
let mut vars = vec![];
|
||||
|
@ -136,13 +137,13 @@ impl<'a> ScriptData<'a> {
|
|||
let cur_length = reader.stream_position().expect("valid position") - start;
|
||||
if cur_length >= data_size as u64 {
|
||||
// Terminators are commonly elided from script data blocks.
|
||||
return Some(Self(vars));
|
||||
return Ok(Self(vars));
|
||||
}
|
||||
|
||||
let is_return = reader.peek_u24()?;
|
||||
if is_return == 9 {
|
||||
reader.read_u24()?;
|
||||
return Some(Self(vars));
|
||||
return Ok(Self(vars));
|
||||
}
|
||||
|
||||
vars.push(Variable::parse(reader)?);
|
||||
|
@ -160,7 +161,7 @@ mod tests {
|
|||
let data = [0x00, 0x03, 0x01, 0x02, 0x03];
|
||||
let mut reader = FlvReader::from_source(&data);
|
||||
|
||||
assert_eq!(parse_string(&mut reader, false), Some(&data[2..]));
|
||||
assert_eq!(parse_string(&mut reader, false), Ok(&data[2..]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -168,7 +169,7 @@ mod tests {
|
|||
let data = [0x00, 0x00, 0x00, 0x03, 0x01, 0x02, 0x03];
|
||||
let mut reader = FlvReader::from_source(&data);
|
||||
|
||||
assert_eq!(parse_string(&mut reader, true), Some(&data[4..]));
|
||||
assert_eq!(parse_string(&mut reader, true), Ok(&data[4..]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -176,7 +177,7 @@ mod tests {
|
|||
let data = [0x00, 0x40, 0x28, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a];
|
||||
let mut reader = FlvReader::from_source(&data);
|
||||
|
||||
assert_eq!(Value::parse(&mut reader), Some(Value::Number(12.3)));
|
||||
assert_eq!(Value::parse(&mut reader), Ok(Value::Number(12.3)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -184,7 +185,7 @@ mod tests {
|
|||
let data = [0x01, 0x01];
|
||||
let mut reader = FlvReader::from_source(&data);
|
||||
|
||||
assert_eq!(Value::parse(&mut reader), Some(Value::Boolean(true)));
|
||||
assert_eq!(Value::parse(&mut reader), Ok(Value::Boolean(true)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -194,7 +195,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
Value::parse(&mut reader),
|
||||
Some(Value::String(&[0x01, 0x02, 0x03]))
|
||||
Ok(Value::String(&[0x01, 0x02, 0x03]))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -205,7 +206,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
Value::parse(&mut reader),
|
||||
Some(Value::MovieClip(&[0x01, 0x02, 0x03]))
|
||||
Ok(Value::MovieClip(&[0x01, 0x02, 0x03]))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -216,7 +217,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
Value::parse(&mut reader),
|
||||
Some(Value::LongString(&[0x01, 0x02, 0x03]))
|
||||
Ok(Value::LongString(&[0x01, 0x02, 0x03]))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -225,7 +226,7 @@ mod tests {
|
|||
let data = [0x05];
|
||||
let mut reader = FlvReader::from_source(&data);
|
||||
|
||||
assert_eq!(Value::parse(&mut reader), Some(Value::Null));
|
||||
assert_eq!(Value::parse(&mut reader), Ok(Value::Null));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -233,7 +234,7 @@ mod tests {
|
|||
let data = [0x06];
|
||||
let mut reader = FlvReader::from_source(&data);
|
||||
|
||||
assert_eq!(Value::parse(&mut reader), Some(Value::Undefined));
|
||||
assert_eq!(Value::parse(&mut reader), Ok(Value::Undefined));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -241,7 +242,7 @@ mod tests {
|
|||
let data = [0x07, 0x24, 0x38];
|
||||
let mut reader = FlvReader::from_source(&data);
|
||||
|
||||
assert_eq!(Value::parse(&mut reader), Some(Value::Reference(0x2438)));
|
||||
assert_eq!(Value::parse(&mut reader), Ok(Value::Reference(0x2438)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -253,7 +254,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
Value::parse(&mut reader),
|
||||
Some(Value::Date {
|
||||
Ok(Value::Date {
|
||||
unix_time: 12.3,
|
||||
local_offset: -2
|
||||
})
|
||||
|
@ -270,7 +271,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
Value::parse(&mut reader),
|
||||
Some(Value::Object(vec![
|
||||
Ok(Value::Object(vec![
|
||||
Variable {
|
||||
name: &[0x01, 0x02, 0x03],
|
||||
data: Value::Undefined
|
||||
|
@ -293,7 +294,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
Value::parse(&mut reader),
|
||||
Some(Value::EcmaArray(vec![
|
||||
Ok(Value::EcmaArray(vec![
|
||||
Variable {
|
||||
name: &[0x01, 0x02, 0x03],
|
||||
data: Value::Undefined
|
||||
|
@ -316,7 +317,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
Value::parse(&mut reader),
|
||||
Some(Value::EcmaArray(vec![
|
||||
Ok(Value::EcmaArray(vec![
|
||||
Variable {
|
||||
name: &[0x01, 0x02, 0x03],
|
||||
data: Value::Undefined
|
||||
|
@ -339,7 +340,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
Value::parse(&mut reader),
|
||||
Some(Value::EcmaArray(vec![
|
||||
Ok(Value::EcmaArray(vec![
|
||||
Variable {
|
||||
name: &[0x01, 0x02, 0x03],
|
||||
data: Value::Undefined
|
||||
|
@ -362,7 +363,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
Value::parse(&mut reader),
|
||||
Some(Value::StrictArray(vec![
|
||||
Ok(Value::StrictArray(vec![
|
||||
Variable {
|
||||
name: &[0x01, 0x02, 0x03],
|
||||
data: Value::Undefined
|
||||
|
@ -385,7 +386,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
ScriptData::parse(&mut reader, 16),
|
||||
Some(ScriptData(vec![
|
||||
Ok(ScriptData(vec![
|
||||
Variable {
|
||||
name: &[0x01, 0x02, 0x03],
|
||||
data: Value::Undefined
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::error::Error;
|
||||
use crate::FlvReader;
|
||||
use std::io::Seek;
|
||||
|
||||
|
@ -20,7 +21,7 @@ pub enum SoundFormat {
|
|||
}
|
||||
|
||||
impl TryFrom<u8> for SoundFormat {
|
||||
type Error = ();
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
|
@ -37,7 +38,7 @@ impl TryFrom<u8> for SoundFormat {
|
|||
11 => Ok(Self::Speex),
|
||||
14 => Ok(Self::MP38kHz),
|
||||
15 => Ok(Self::DeviceSpecific),
|
||||
_ => Err(()),
|
||||
unk => Err(Error::UnknownAudioFormatType(unk)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +53,7 @@ pub enum SoundRate {
|
|||
}
|
||||
|
||||
impl TryFrom<u8> for SoundRate {
|
||||
type Error = ();
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
|
@ -60,7 +61,7 @@ impl TryFrom<u8> for SoundRate {
|
|||
1 => Ok(Self::R11_000),
|
||||
2 => Ok(Self::R22_000),
|
||||
3 => Ok(Self::R44_000),
|
||||
_ => Err(()),
|
||||
unk => Err(Error::UnknownAudioRate(unk)), //probably unreachable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -73,13 +74,13 @@ pub enum SoundSize {
|
|||
}
|
||||
|
||||
impl TryFrom<u8> for SoundSize {
|
||||
type Error = ();
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0 => Ok(Self::Bits8),
|
||||
1 => Ok(Self::Bits16),
|
||||
_ => Err(()),
|
||||
unk => Err(Error::UnknownAudioSampleSize(unk)), //probably unreachable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -92,13 +93,13 @@ pub enum SoundType {
|
|||
}
|
||||
|
||||
impl TryFrom<u8> for SoundType {
|
||||
type Error = ();
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0 => Ok(Self::Mono),
|
||||
1 => Ok(Self::Stereo),
|
||||
_ => Err(()),
|
||||
unk => Err(Error::UnknownAudioChannelCount(unk)), //probably unreachable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -126,38 +127,39 @@ impl<'a> AudioData<'a> {
|
|||
/// 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> {
|
||||
/// the header. Errors are yielded if the `data_size` is too small for the
|
||||
/// audio data present in the tag. This should not be confused for
|
||||
/// `EndOfData` which indicates that we've read past the end of the whole
|
||||
/// data stream.
|
||||
pub fn parse(reader: &mut FlvReader<'a>, data_size: u32) -> Result<Self, Error> {
|
||||
let start = reader.stream_position().expect("current position") as usize;
|
||||
let format_spec = reader.read_u8()?;
|
||||
|
||||
let format = SoundFormat::try_from(format_spec >> 4).ok()?;
|
||||
let rate = SoundRate::try_from((format_spec >> 2) & 0x03).ok()?;
|
||||
let size = SoundSize::try_from((format_spec >> 1) & 0x01).ok()?;
|
||||
let sound_type = SoundType::try_from(format_spec & 0x01).ok()?;
|
||||
let format = SoundFormat::try_from(format_spec >> 4)?;
|
||||
let rate = SoundRate::try_from((format_spec >> 2) & 0x03)?;
|
||||
let size = SoundSize::try_from((format_spec >> 1) & 0x01)?;
|
||||
let sound_type = SoundType::try_from(format_spec & 0x01)?;
|
||||
|
||||
let header_size = reader.stream_position().expect("current position") as usize - start;
|
||||
if (data_size as usize) < header_size {
|
||||
return None;
|
||||
return Err(Error::ShortAudioBlock);
|
||||
}
|
||||
let data = reader.read(data_size as usize - header_size)?;
|
||||
|
||||
let data = match format {
|
||||
SoundFormat::Aac => {
|
||||
let aac_packet_type = data.first()?;
|
||||
let aac_packet_type = data.first().ok_or(Error::ShortAudioBlock)?;
|
||||
match aac_packet_type {
|
||||
//TODO: The FLV spec says this is explained in ISO 14496-3.
|
||||
0 => AudioDataType::AacSequenceHeader(&data[1..]),
|
||||
1 => AudioDataType::AacRaw(&data[1..]),
|
||||
_ => return None,
|
||||
unk => return Err(Error::UnknownAacPacketType(*unk)),
|
||||
}
|
||||
}
|
||||
_ => AudioDataType::Raw(data),
|
||||
};
|
||||
|
||||
Some(AudioData {
|
||||
Ok(AudioData {
|
||||
format,
|
||||
rate,
|
||||
size,
|
||||
|
@ -169,6 +171,7 @@ impl<'a> AudioData<'a> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::error::Error;
|
||||
use crate::reader::FlvReader;
|
||||
use crate::sound::{AudioData, AudioDataType, SoundFormat, SoundRate, SoundSize, SoundType};
|
||||
|
||||
|
@ -179,7 +182,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
AudioData::parse(&mut reader, data.len() as u32),
|
||||
Some(AudioData {
|
||||
Ok(AudioData {
|
||||
format: SoundFormat::Speex,
|
||||
rate: SoundRate::R44_000,
|
||||
size: SoundSize::Bits16,
|
||||
|
@ -194,7 +197,10 @@ mod tests {
|
|||
let data = [0xBF, 0x12, 0x34, 0x56, 0x78];
|
||||
let mut reader = FlvReader::from_source(&data);
|
||||
|
||||
assert_eq!(AudioData::parse(&mut reader, 0), None);
|
||||
assert_eq!(
|
||||
AudioData::parse(&mut reader, 0),
|
||||
Err(Error::ShortAudioBlock)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -204,7 +210,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
AudioData::parse(&mut reader, 2),
|
||||
Some(AudioData {
|
||||
Ok(AudioData {
|
||||
format: SoundFormat::Speex,
|
||||
rate: SoundRate::R44_000,
|
||||
size: SoundSize::Bits16,
|
||||
|
@ -221,7 +227,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
AudioData::parse(&mut reader, data.len() as u32),
|
||||
Some(AudioData {
|
||||
Ok(AudioData {
|
||||
format: SoundFormat::Aac,
|
||||
rate: SoundRate::R44_000,
|
||||
size: SoundSize::Bits8,
|
||||
|
@ -236,6 +242,9 @@ mod tests {
|
|||
let data = [0xAD, 0x02, 0x12, 0x34, 0x56, 0x78];
|
||||
let mut reader = FlvReader::from_source(&data);
|
||||
|
||||
assert_eq!(AudioData::parse(&mut reader, data.len() as u32), None);
|
||||
assert_eq!(
|
||||
AudioData::parse(&mut reader, data.len() as u32),
|
||||
Err(Error::UnknownAacPacketType(2))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
105
flv/src/tag.rs
105
flv/src/tag.rs
|
@ -1,3 +1,4 @@
|
|||
use crate::error::Error;
|
||||
use crate::reader::FlvReader;
|
||||
use crate::script::ScriptData;
|
||||
use crate::sound::AudioData;
|
||||
|
@ -11,6 +12,12 @@ pub enum TagData<'a> {
|
|||
Audio(AudioData<'a>) = 8,
|
||||
Video(VideoData<'a>) = 9,
|
||||
Script(ScriptData<'a>) = 18,
|
||||
|
||||
/// The tag data was recognized but could not be parsed due to an error.
|
||||
///
|
||||
/// The error contained will never be EndOfData; this should only be used
|
||||
/// to flag unparseable data within an otherwise complete tag.
|
||||
Invalid(Error),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
|
@ -30,14 +37,18 @@ impl<'a> Tag<'a> {
|
|||
/// repeated calls to `parse` will yield further tags until the end of the
|
||||
/// file.
|
||||
///
|
||||
/// `None` indicates that either:
|
||||
/// Errors can be reported in one of two ways. If the header cannot be read
|
||||
/// then this function returns the error normally. However, if the header
|
||||
/// can be read, but the data inside the tag is corrupt, then a
|
||||
/// TagData::Invalid will be returned with the inner error. EndOfData will
|
||||
/// always be reported as a normal error and not as an invalid tag.
|
||||
///
|
||||
/// * There is not enough data in the reader to read the next tag
|
||||
/// * The data in the reader is corrupt and not a valid FLV
|
||||
///
|
||||
/// If encountered, the position of the reader will be unchanged.
|
||||
pub fn parse(reader: &mut FlvReader<'a>) -> Option<Self> {
|
||||
let old_position = reader.stream_position().ok()?;
|
||||
/// In the event of an invalid header or end-of-data error, the reader
|
||||
/// position will be unchanged. Valid headers, with or without valid tag
|
||||
/// data, will seek the reader to the start of the next tag. This allows
|
||||
/// skipping past invalid tags.
|
||||
pub fn parse(reader: &mut FlvReader<'a>) -> Result<Self, Error> {
|
||||
let old_position = reader.stream_position()?;
|
||||
|
||||
let ret = (|| {
|
||||
let _previous_tag_size = reader.read_u32()?;
|
||||
|
@ -49,38 +60,57 @@ impl<'a> Tag<'a> {
|
|||
let stream_id = reader.read_u24()?;
|
||||
|
||||
let timestamp = ((timestamp_extended as u32) << 24 | timestamp) as i32;
|
||||
let data_position = reader.stream_position()?;
|
||||
let new_position = data_position + data_size as u64;
|
||||
|
||||
let new_position = reader.stream_position().ok()? + data_size as u64;
|
||||
|
||||
Some((
|
||||
Ok((
|
||||
match tag_type {
|
||||
8 => Tag {
|
||||
timestamp,
|
||||
stream_id,
|
||||
data: TagData::Audio(AudioData::parse(reader, data_size)?),
|
||||
data: match AudioData::parse(reader, data_size) {
|
||||
Ok(data) => TagData::Audio(data),
|
||||
Err(Error::EndOfData) => return Err(Error::EndOfData),
|
||||
Err(e) => TagData::Invalid(e),
|
||||
},
|
||||
},
|
||||
9 => Tag {
|
||||
timestamp,
|
||||
stream_id,
|
||||
data: TagData::Video(VideoData::parse(reader, data_size)?),
|
||||
data: match VideoData::parse(reader, data_size) {
|
||||
Ok(data) => TagData::Video(data),
|
||||
Err(Error::EndOfData) => return Err(Error::EndOfData),
|
||||
Err(e) => TagData::Invalid(e),
|
||||
},
|
||||
},
|
||||
18 => Tag {
|
||||
timestamp,
|
||||
stream_id,
|
||||
data: TagData::Script(ScriptData::parse(reader, data_size)?),
|
||||
data: match ScriptData::parse(reader, data_size) {
|
||||
Ok(data) => TagData::Script(data),
|
||||
Err(Error::EndOfData) => return Err(Error::EndOfData),
|
||||
Err(e) => TagData::Invalid(e),
|
||||
},
|
||||
},
|
||||
unk => Tag {
|
||||
timestamp,
|
||||
stream_id,
|
||||
data: TagData::Invalid(Error::UnknownTagType(unk)),
|
||||
},
|
||||
_ => return None,
|
||||
},
|
||||
new_position,
|
||||
))
|
||||
})();
|
||||
|
||||
if let Some((tag, new_position)) = ret {
|
||||
reader.seek(SeekFrom::Start(new_position)).ok()?;
|
||||
Some(tag)
|
||||
} else {
|
||||
reader.seek(SeekFrom::Start(old_position)).ok()?;
|
||||
None
|
||||
match ret {
|
||||
Ok((tag, new_position)) => {
|
||||
reader.seek(SeekFrom::Start(new_position))?;
|
||||
Ok(tag)
|
||||
}
|
||||
Err(e) => {
|
||||
reader.seek(SeekFrom::Start(old_position))?;
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,18 +119,17 @@ impl<'a> Tag<'a> {
|
|||
/// FLV files are constructed as a list of tags. Back pointers to prior
|
||||
/// tags are provided to allow reverse seeking. This function ignores the
|
||||
/// tag at the current location and skips back to prior data in the file.
|
||||
pub fn skip_back(reader: &mut FlvReader<'a>) -> Option<()> {
|
||||
pub fn skip_back(reader: &mut FlvReader<'a>) -> Result<(), Error> {
|
||||
let previous_tag_size = reader.read_u32()?;
|
||||
reader
|
||||
.seek(SeekFrom::Current(-(previous_tag_size as i64)))
|
||||
.ok()?;
|
||||
reader.seek(SeekFrom::Current(-(previous_tag_size as i64)))?;
|
||||
|
||||
Some(())
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::error::Error;
|
||||
use crate::reader::FlvReader;
|
||||
use crate::script::{ScriptData, Value, Variable};
|
||||
use crate::sound::{AudioData, AudioDataType, SoundFormat, SoundRate, SoundSize, SoundType};
|
||||
|
@ -117,7 +146,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
Tag::parse(&mut reader),
|
||||
Some(Tag {
|
||||
Ok(Tag {
|
||||
timestamp: 0,
|
||||
stream_id: 0x5000,
|
||||
data: TagData::Audio(AudioData {
|
||||
|
@ -131,6 +160,24 @@ mod tests {
|
|||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_tag_sounddata_invalid() {
|
||||
let data = [
|
||||
0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50,
|
||||
0x00, 0xCA, 0x12, 0x34, 0x56, 0x78,
|
||||
];
|
||||
let mut reader = FlvReader::from_source(&data);
|
||||
|
||||
assert_eq!(
|
||||
Tag::parse(&mut reader),
|
||||
Ok(Tag {
|
||||
timestamp: 0,
|
||||
stream_id: 0x5000,
|
||||
data: TagData::Invalid(Error::UnknownAudioFormatType(0x0C))
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_tag_videodata() {
|
||||
let data = [
|
||||
|
@ -141,7 +188,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
Tag::parse(&mut reader),
|
||||
Some(Tag {
|
||||
Ok(Tag {
|
||||
timestamp: 0,
|
||||
stream_id: 0x5000,
|
||||
data: TagData::Video(VideoData {
|
||||
|
@ -164,7 +211,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
Tag::parse(&mut reader),
|
||||
Some(Tag {
|
||||
Ok(Tag {
|
||||
timestamp: 0,
|
||||
stream_id: 0x5000,
|
||||
data: TagData::Script(ScriptData(vec![
|
||||
|
@ -211,7 +258,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
Tag::parse(&mut reader),
|
||||
Some(Tag {
|
||||
Ok(Tag {
|
||||
timestamp: 0,
|
||||
stream_id: 0,
|
||||
data: TagData::Script(ScriptData(vec![Variable {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::error::Error;
|
||||
use crate::reader::FlvReader;
|
||||
use std::io::Seek;
|
||||
|
||||
|
@ -12,7 +13,7 @@ pub enum FrameType {
|
|||
}
|
||||
|
||||
impl TryFrom<u8> for FrameType {
|
||||
type Error = ();
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
|
@ -21,7 +22,7 @@ impl TryFrom<u8> for FrameType {
|
|||
3 => Ok(Self::InterframeDisposable),
|
||||
4 => Ok(Self::Generated),
|
||||
5 => Ok(Self::CommandFrame),
|
||||
_ => Err(()),
|
||||
unk => Err(Error::UnknownVideoFrameType(unk)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +40,7 @@ pub enum CodecId {
|
|||
}
|
||||
|
||||
impl TryFrom<u8> for CodecId {
|
||||
type Error = ();
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
|
@ -50,7 +51,7 @@ impl TryFrom<u8> for CodecId {
|
|||
5 => Ok(Self::On2Vp6Alpha),
|
||||
6 => Ok(Self::ScreenVideo2),
|
||||
7 => Ok(Self::Avc),
|
||||
_ => Err(()),
|
||||
unk => Err(Error::UnknownVideoCodec(unk)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -63,13 +64,13 @@ pub enum CommandFrame {
|
|||
}
|
||||
|
||||
impl TryFrom<u8> for CommandFrame {
|
||||
type Error = ();
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0 => Ok(Self::StartOfClientSideSeek),
|
||||
1 => Ok(Self::EndOfClientSideSeek),
|
||||
_ => Err(()),
|
||||
unk => Err(Error::UnknownVideoCommandType(unk)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -100,28 +101,29 @@ impl<'a> VideoData<'a> {
|
|||
/// returned as an array that must be provided to your video decoder.
|
||||
///
|
||||
/// `data_size` is the size of the entire video data structure, *including*
|
||||
/// the header.
|
||||
///
|
||||
/// If `None` is yielded, the data stream is not a valid video header.
|
||||
pub fn parse(reader: &mut FlvReader<'a>, data_size: u32) -> Option<Self> {
|
||||
/// the header. Errors are yielded if the `data_size` is too small for the
|
||||
/// video data present in the tag. This should not be confused for
|
||||
/// `EndOfData` which indicates that we've read past the end of the whole
|
||||
/// data stream.
|
||||
pub fn parse(reader: &mut FlvReader<'a>, data_size: u32) -> Result<Self, Error> {
|
||||
let start = reader.stream_position().expect("current position") as usize;
|
||||
let format_spec = reader.read_u8()?;
|
||||
|
||||
let frame_type = FrameType::try_from(format_spec >> 4).ok()?;
|
||||
let codec_id = CodecId::try_from(format_spec & 0x0F).ok()?;
|
||||
let frame_type = FrameType::try_from(format_spec >> 4)?;
|
||||
let codec_id = CodecId::try_from(format_spec & 0x0F)?;
|
||||
|
||||
let header_size = reader.stream_position().expect("current position") as usize - start;
|
||||
if (data_size as usize) < header_size {
|
||||
return None;
|
||||
return Err(Error::ShortVideoBlock);
|
||||
}
|
||||
let data = reader.read(data_size as usize - header_size)?;
|
||||
|
||||
let packet = match (frame_type, codec_id) {
|
||||
(FrameType::CommandFrame, _) => {
|
||||
VideoPacket::CommandFrame(CommandFrame::try_from(*data.first()?).ok()?)
|
||||
}
|
||||
(FrameType::CommandFrame, _) => VideoPacket::CommandFrame(CommandFrame::try_from(
|
||||
*data.first().ok_or(Error::ShortVideoBlock)?,
|
||||
)?),
|
||||
(_, CodecId::Avc) => {
|
||||
let bytes = data.get(1..4)?;
|
||||
let bytes = data.get(1..4).ok_or(Error::ShortVideoBlock)?;
|
||||
let is_negative = bytes[0] & 0x80 != 0;
|
||||
let composition_time_offset = i32::from_be_bytes([
|
||||
if is_negative { 0xFF } else { 0x00 },
|
||||
|
@ -130,20 +132,20 @@ impl<'a> VideoData<'a> {
|
|||
bytes[2],
|
||||
]);
|
||||
|
||||
match *data.first()? {
|
||||
match *data.first().ok_or(Error::ShortVideoBlock)? {
|
||||
0 => VideoPacket::AvcSequenceHeader(&data[4..]),
|
||||
1 => VideoPacket::AvcNalu {
|
||||
composition_time_offset,
|
||||
data: &data[4..],
|
||||
},
|
||||
2 => VideoPacket::AvcEndOfSequence,
|
||||
_ => return None,
|
||||
unk => return Err(Error::UnknownAvcPacketType(unk)),
|
||||
}
|
||||
}
|
||||
(_, _) => VideoPacket::Data(data),
|
||||
};
|
||||
|
||||
Some(VideoData {
|
||||
Ok(VideoData {
|
||||
frame_type,
|
||||
codec_id,
|
||||
data: packet,
|
||||
|
@ -153,6 +155,7 @@ impl<'a> VideoData<'a> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::error::Error;
|
||||
use crate::reader::FlvReader;
|
||||
use crate::video::{CodecId, FrameType, VideoData, VideoPacket};
|
||||
|
||||
|
@ -163,7 +166,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
VideoData::parse(&mut reader, data.len() as u32),
|
||||
Some(VideoData {
|
||||
Ok(VideoData {
|
||||
frame_type: FrameType::Keyframe,
|
||||
codec_id: CodecId::SorensonH263,
|
||||
data: VideoPacket::Data(&[0x12, 0x34, 0x56, 0x78])
|
||||
|
@ -176,7 +179,10 @@ mod tests {
|
|||
let data = [0x12, 0x12, 0x34, 0x56, 0x78];
|
||||
let mut reader = FlvReader::from_source(&data);
|
||||
|
||||
assert_eq!(VideoData::parse(&mut reader, 0), None);
|
||||
assert_eq!(
|
||||
VideoData::parse(&mut reader, 0),
|
||||
Err(Error::ShortVideoBlock)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -186,7 +192,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
VideoData::parse(&mut reader, 2),
|
||||
Some(VideoData {
|
||||
Ok(VideoData {
|
||||
frame_type: FrameType::Keyframe,
|
||||
codec_id: CodecId::SorensonH263,
|
||||
data: VideoPacket::Data(&[0x12])
|
||||
|
@ -201,7 +207,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
VideoData::parse(&mut reader, data.len() as u32),
|
||||
Some(VideoData {
|
||||
Ok(VideoData {
|
||||
frame_type: FrameType::Keyframe,
|
||||
codec_id: CodecId::Avc,
|
||||
data: VideoPacket::AvcSequenceHeader(&[0x12, 0x34, 0x56, 0x78])
|
||||
|
@ -216,7 +222,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
VideoData::parse(&mut reader, data.len() as u32),
|
||||
Some(VideoData {
|
||||
Ok(VideoData {
|
||||
frame_type: FrameType::Keyframe,
|
||||
codec_id: CodecId::Avc,
|
||||
data: VideoPacket::AvcNalu {
|
||||
|
@ -234,7 +240,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
VideoData::parse(&mut reader, data.len() as u32),
|
||||
Some(VideoData {
|
||||
Ok(VideoData {
|
||||
frame_type: FrameType::Keyframe,
|
||||
codec_id: CodecId::Avc,
|
||||
data: VideoPacket::AvcNalu {
|
||||
|
@ -252,7 +258,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
VideoData::parse(&mut reader, data.len() as u32),
|
||||
Some(VideoData {
|
||||
Ok(VideoData {
|
||||
frame_type: FrameType::Keyframe,
|
||||
codec_id: CodecId::Avc,
|
||||
data: VideoPacket::AvcEndOfSequence
|
||||
|
@ -265,6 +271,9 @@ mod tests {
|
|||
let data = [0x17, 0xFF, 0xFF, 0xFF, 0xFE, 0x12, 0x34, 0x56, 0x78];
|
||||
let mut reader = FlvReader::from_source(&data);
|
||||
|
||||
assert_eq!(VideoData::parse(&mut reader, data.len() as u32), None);
|
||||
assert_eq!(
|
||||
VideoData::parse(&mut reader, data.len() as u32),
|
||||
Err(Error::UnknownAvcPacketType(0xFF))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue