tolerate bad sps/pps, continued

This commit is contained in:
Scott Lamb 2024-05-30 18:17:04 -07:00
parent 1ae61b4c64
commit e6c7b800fe
5 changed files with 165 additions and 10 deletions

View File

@ -8,6 +8,10 @@ upgrades, e.g. `v0.6.x` -> `v0.7.x`. The config file format and
[API](ref/api.md) currently have no stability guarantees, so they may change [API](ref/api.md) currently have no stability guarantees, so they may change
even on minor releases, e.g. `v0.7.5` -> `v0.7.6`. even on minor releases, e.g. `v0.7.5` -> `v0.7.6`.
## v0.7.16 (2024-05-30)
* further changes to improve Reolink camera compatibility.
## v0.7.15 (2024-05-26) ## v0.7.15 (2024-05-26)
* update Retina to 0.4.8, improving compatibility with some Reolink cameras. * update Retina to 0.4.8, improving compatibility with some Reolink cameras.

1
server/Cargo.lock generated
View File

@ -1190,6 +1190,7 @@ dependencies = [
"nom", "nom",
"num-rational", "num-rational",
"password-hash", "password-hash",
"pretty-hex",
"protobuf 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "protobuf 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"reffers", "reffers",
"reqwest", "reqwest",

View File

@ -27,10 +27,11 @@ base64 = "0.21.0"
h264-reader = "0.7.0" h264-reader = "0.7.0"
itertools = "0.12.0" itertools = "0.12.0"
nix = "0.27.0" nix = "0.27.0"
tracing = { version = "0.1", features = ["log"] } pretty-hex = "0.4.0"
tracing-log = "0.2"
ring = "0.17.0" ring = "0.17.0"
rusqlite = "0.30.0" rusqlite = "0.30.0"
tracing = { version = "0.1", features = ["log"] }
tracing-log = "0.2"
[dependencies] [dependencies]
base = { package = "moonfire-base", path = "base" } base = { package = "moonfire-base", path = "base" }
@ -54,6 +55,7 @@ memchr = "2.0.2"
nix = { workspace = true, features = ["time", "user"] } nix = { workspace = true, features = ["time", "user"] }
nom = "7.0.0" nom = "7.0.0"
password-hash = "0.5.0" password-hash = "0.5.0"
pretty-hex = { workspace = true }
protobuf = "3.0" protobuf = "3.0"
reffers = "0.7.0" reffers = "0.7.0"
retina = "0.4.0" retina = "0.4.0"

View File

@ -29,7 +29,7 @@ libc = "0.2"
nix = { workspace = true, features = ["dir", "feature", "fs", "mman"] } nix = { workspace = true, features = ["dir", "feature", "fs", "mman"] }
num-rational = { version = "0.4.0", default-features = false, features = ["std"] } num-rational = { version = "0.4.0", default-features = false, features = ["std"] }
odds = { version = "0.4.0", features = ["std-vec"] } odds = { version = "0.4.0", features = ["std-vec"] }
pretty-hex = "0.4.0" pretty-hex = { workspace = true }
protobuf = "3.0" protobuf = "3.0"
ring = { workspace = true } ring = { workspace = true }
rusqlite = { workspace = true } rusqlite = { workspace = true }

View File

@ -21,6 +21,8 @@
use base::{bail, err, Error}; use base::{bail, err, Error};
use byteorder::{BigEndian, ByteOrder, WriteBytesExt}; use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
use db::VideoSampleEntryToInsert; use db::VideoSampleEntryToInsert;
use h264_reader::nal::Nal;
use pretty_hex::PrettyHex as _;
use std::convert::TryFrom; use std::convert::TryFrom;
// For certain common sub stream anamorphic resolutions, add a pixel aspect ratio box. // For certain common sub stream anamorphic resolutions, add a pixel aspect ratio box.
@ -60,8 +62,84 @@ fn default_pixel_aspect_ratio(width: u16, height: u16) -> (u16, u16) {
} }
} }
/// Parses the `AvcDecoderConfigurationRecord` in the "extra data". /// `h264_reader::rbsp::BitRead` impl that does not care about extra trailing data.
pub fn parse_extra_data(extradata: &[u8]) -> Result<VideoSampleEntryToInsert, Error> { ///
/// Some (Reolink) cameras appear to have a stray extra byte at the end. Follow the lead of most
/// other RTSP implementations in tolerating this.
#[derive(Debug)]
struct TolerantBitReader<R> {
inner: R,
}
impl<R: h264_reader::rbsp::BitRead> h264_reader::rbsp::BitRead for TolerantBitReader<R> {
fn read_ue(&mut self, name: &'static str) -> Result<u32, h264_reader::rbsp::BitReaderError> {
self.inner.read_ue(name)
}
fn read_se(&mut self, name: &'static str) -> Result<i32, h264_reader::rbsp::BitReaderError> {
self.inner.read_se(name)
}
fn read_bool(&mut self, name: &'static str) -> Result<bool, h264_reader::rbsp::BitReaderError> {
self.inner.read_bool(name)
}
fn read_u8(
&mut self,
bit_count: u32,
name: &'static str,
) -> Result<u8, h264_reader::rbsp::BitReaderError> {
self.inner.read_u8(bit_count, name)
}
fn read_u16(
&mut self,
bit_count: u32,
name: &'static str,
) -> Result<u16, h264_reader::rbsp::BitReaderError> {
self.inner.read_u16(bit_count, name)
}
fn read_u32(
&mut self,
bit_count: u32,
name: &'static str,
) -> Result<u32, h264_reader::rbsp::BitReaderError> {
self.inner.read_u32(bit_count, name)
}
fn read_i32(
&mut self,
bit_count: u32,
name: &'static str,
) -> Result<i32, h264_reader::rbsp::BitReaderError> {
self.inner.read_i32(bit_count, name)
}
fn has_more_rbsp_data(
&mut self,
name: &'static str,
) -> Result<bool, h264_reader::rbsp::BitReaderError> {
self.inner.has_more_rbsp_data(name)
}
fn finish_rbsp(self) -> Result<(), h264_reader::rbsp::BitReaderError> {
match self.inner.finish_rbsp() {
Ok(()) => Ok(()),
Err(h264_reader::rbsp::BitReaderError::RemainingData) => {
tracing::debug!("extra data at end of NAL unit");
Ok(())
}
Err(e) => Err(e),
}
}
fn finish_sei_payload(self) -> Result<(), h264_reader::rbsp::BitReaderError> {
self.inner.finish_sei_payload()
}
}
fn parse_extra_data_inner(extradata: &[u8]) -> Result<VideoSampleEntryToInsert, Error> {
let avcc = let avcc =
h264_reader::avcc::AvcDecoderConfigurationRecord::try_from(extradata).map_err(|e| { h264_reader::avcc::AvcDecoderConfigurationRecord::try_from(extradata).map_err(|e| {
err!( err!(
@ -72,9 +150,39 @@ pub fn parse_extra_data(extradata: &[u8]) -> Result<VideoSampleEntryToInsert, Er
if avcc.num_of_sequence_parameter_sets() != 1 { if avcc.num_of_sequence_parameter_sets() != 1 {
bail!(Unimplemented, msg("multiple SPSs!")); bail!(Unimplemented, msg("multiple SPSs!"));
} }
let ctx = avcc
.create_context() // This logic is essentially copied from
.map_err(|e| err!(Unknown, msg("can't load SPS+PPS: {:?}", e)))?; // `h264_reader::avcc::AvcDecoderConfigurationRecord::create_context` but
// using our `TolerantBitReader` wrapper.
let mut ctx = h264_reader::Context::new();
for sps in avcc.sequence_parameter_sets() {
let sps = h264_reader::nal::RefNal::new(
&sps.map_err(|e| err!(InvalidArgument, msg("bad SPS: {e:?}")))?[..],
&[],
true,
);
let sps = h264_reader::nal::sps::SeqParameterSet::from_bits(TolerantBitReader {
inner: sps.rbsp_bits(),
})
.map_err(|e| err!(InvalidArgument, msg("bad SPS: {e:?}")))?;
ctx.put_seq_param_set(sps);
}
for pps in avcc.picture_parameter_sets() {
let pps = h264_reader::nal::RefNal::new(
&pps.map_err(|e| err!(InvalidArgument, msg("bad PPS: {e:?}")))?[..],
&[],
true,
);
let pps = h264_reader::nal::pps::PicParameterSet::from_bits(
&ctx,
TolerantBitReader {
inner: pps.rbsp_bits(),
},
)
.map_err(|e| err!(InvalidArgument, msg("bad PPS: {e:?}")))?;
ctx.put_pic_param_set(pps);
}
let sps = ctx let sps = ctx
.sps_by_id(h264_reader::nal::pps::ParamSetId::from_u32(0).unwrap()) .sps_by_id(h264_reader::nal::pps::ParamSetId::from_u32(0).unwrap())
.ok_or_else(|| err!(Unimplemented, msg("no SPS 0")))?; .ok_or_else(|| err!(Unimplemented, msg("no SPS 0")))?;
@ -175,17 +283,52 @@ pub fn parse_extra_data(extradata: &[u8]) -> Result<VideoSampleEntryToInsert, Er
}) })
} }
/// Parses the `AvcDecoderConfigurationRecord` in the "extra data".
pub fn parse_extra_data(extradata: &[u8]) -> Result<VideoSampleEntryToInsert, Error> {
parse_extra_data_inner(extradata).map_err(|e| {
err!(
e,
msg(
"can't parse AvcDecoderRecord {}",
extradata.hex_conf(pretty_hex::HexConfig {
width: 0,
group: 0,
chunk: 0,
..Default::default()
})
)
)
})
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use db::testutil; use db::testutil;
#[rustfmt::skip] #[rustfmt::skip]
const AVC_DECODER_CONFIG_TEST_INPUT: [u8; 38] = [ const AVC_DECODER_CONFIG_TEST_INPUT: [u8; 38] = [
0x01, 0x4d, 0x00, 0x1f, 0xff, 0xe1, 0x00, 0x17, 0x01, 0x4d, 0x00, 0x1f, 0xff,
0xe1, 0x00, 0x17, // 1 SPS, length 0x17
0x67, 0x4d, 0x00, 0x1f, 0x9a, 0x66, 0x02, 0x80,
0x2d, 0xff, 0x35, 0x01, 0x01, 0x01, 0x40, 0x00,
0x00, 0xfa, 0x00, 0x00, 0x1d, 0x4c, 0x01,
0x01, 0x00, 0x04, // 1 PPS, length 0x04
0x68, 0xee, 0x3c, 0x80,
];
#[rustfmt::skip]
const AVC_DECODER_CONFIG_TEST_INPUT_WITH_TRAILING_GARBAGE: [u8; 40] = [
0x01, 0x4d, 0x00, 0x1f, 0xff,
0xe1, 0x00, 0x18, // 1 SPS, length 0x18
0x67, 0x4d, 0x00, 0x1f, 0x9a, 0x66, 0x02, 0x80, 0x67, 0x4d, 0x00, 0x1f, 0x9a, 0x66, 0x02, 0x80,
0x2d, 0xff, 0x35, 0x01, 0x01, 0x01, 0x40, 0x00, 0x2d, 0xff, 0x35, 0x01, 0x01, 0x01, 0x40, 0x00,
0x00, 0xfa, 0x00, 0x00, 0x1d, 0x4c, 0x01, 0x01, 0x00, 0xfa, 0x00, 0x00, 0x1d, 0x4c, 0x01, 0x01,
0x00, 0x04, 0x68, 0xee, 0x3c, 0x80,
0x01, 0x00, 0x04, // 1 PPS, length 0x05
0x68, 0xee, 0x3c, 0x80, 0x80,
]; ];
#[rustfmt::skip] #[rustfmt::skip]
@ -232,4 +375,9 @@ mod tests {
assert_eq!(Ratio::new(h * h_spacing, w * v_spacing), Ratio::new(9, 16)); assert_eq!(Ratio::new(h * h_spacing, w * v_spacing), Ratio::new(9, 16));
} }
} }
#[test]
fn extra_sps_data() {
super::parse_extra_data(&AVC_DECODER_CONFIG_TEST_INPUT_WITH_TRAILING_GARBAGE).unwrap();
}
} }