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
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)
* 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",
"num-rational",
"password-hash",
"pretty-hex",
"protobuf 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"reffers",
"reqwest",

View File

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

View File

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

View File

@ -21,6 +21,8 @@
use base::{bail, err, Error};
use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
use db::VideoSampleEntryToInsert;
use h264_reader::nal::Nal;
use pretty_hex::PrettyHex as _;
use std::convert::TryFrom;
// 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".
pub fn parse_extra_data(extradata: &[u8]) -> Result<VideoSampleEntryToInsert, Error> {
/// `h264_reader::rbsp::BitRead` impl that does not care about extra trailing data.
///
/// 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 =
h264_reader::avcc::AvcDecoderConfigurationRecord::try_from(extradata).map_err(|e| {
err!(
@ -72,9 +150,39 @@ pub fn parse_extra_data(extradata: &[u8]) -> Result<VideoSampleEntryToInsert, Er
if avcc.num_of_sequence_parameter_sets() != 1 {
bail!(Unimplemented, msg("multiple SPSs!"));
}
let ctx = avcc
.create_context()
.map_err(|e| err!(Unknown, msg("can't load SPS+PPS: {:?}", e)))?;
// This logic is essentially copied from
// `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
.sps_by_id(h264_reader::nal::pps::ParamSetId::from_u32(0).unwrap())
.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)]
mod tests {
use db::testutil;
#[rustfmt::skip]
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,
0x2d, 0xff, 0x35, 0x01, 0x01, 0x01, 0x40, 0x00,
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]
@ -232,4 +375,9 @@ mod tests {
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();
}
}