use std::{
fmt::{self, Display, Formatter},
str::FromStr,
};
use base64::{display::Base64Display, engine::Engine, prelude::BASE64_STANDARD, DecodeError};
use bytes::Bytes;
use msf_util::h264::{extract_nal_unit, InvalidByteStream};
#[derive(Debug, Copy, Clone)]
pub struct InvalidParameters;
impl Display for InvalidParameters {
#[inline]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str("invalid h264 parameters")
}
}
impl std::error::Error for InvalidParameters {}
impl From<InvalidByteStream> for InvalidParameters {
fn from(_: InvalidByteStream) -> Self {
Self
}
}
impl From<InvalidProfileLevelId> for InvalidParameters {
fn from(_: InvalidProfileLevelId) -> Self {
Self
}
}
#[derive(Clone)]
pub struct H264Parameters {
packetization_mode: u8,
interleaving_depth: Option<u16>,
max_don_diff: Option<u16>,
profile_level_id: Option<ProfileLevelId>,
parameter_sets: Option<Bytes>,
}
impl H264Parameters {
pub fn new(
packetization_mode: u8,
parameter_sets: Option<Bytes>,
) -> Result<Self, InvalidParameters> {
let mut profile_level_id = None;
if let Some(mut parameter_sets) = parameter_sets.clone() {
while let Some(nal_unit) = extract_nal_unit(&mut parameter_sets)? {
if nal_unit.is_empty() || (nal_unit[0] & 0x1f) != 7 {
continue;
}
if nal_unit.len() < 4 {
return Err(InvalidParameters);
}
let profile_idc = nal_unit[1];
let constraints = nal_unit[2];
let level_idc = nal_unit[3];
profile_level_id = Some(ProfileLevelId::new(profile_idc, constraints, level_idc));
}
}
let res = Self {
packetization_mode,
interleaving_depth: None,
max_don_diff: None,
profile_level_id,
parameter_sets,
};
Ok(res)
}
#[inline]
pub fn packetization_mode(&self) -> u8 {
self.packetization_mode
}
#[inline]
pub fn interleaving_depth(&self) -> Option<u16> {
self.interleaving_depth
}
#[inline]
pub fn with_interleaving_depth(mut self, depth: u16) -> Self {
self.interleaving_depth = Some(depth);
self
}
#[inline]
pub fn max_don_diff(&self) -> Option<u16> {
self.max_don_diff
}
#[inline]
pub fn with_max_don_diff(mut self, max_don_diff: u16) -> Self {
self.max_don_diff = Some(max_don_diff);
self
}
#[inline]
pub fn profile_level_id(&self) -> Option<ProfileLevelId> {
self.profile_level_id
}
#[inline]
pub fn parameter_sets(&self) -> Option<&Bytes> {
self.parameter_sets.as_ref()
}
}
impl Display for H264Parameters {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "packetization-mode={}", self.packetization_mode)?;
if let Some(depth) = self.interleaving_depth {
write!(f, ";sprop-interleaving-depth={depth}")?;
}
if let Some(max_don_diff) = self.max_don_diff {
write!(f, ";sprop-max-don-diff={max_don_diff}")?;
}
if let Some(profile_level_id) = self.profile_level_id.as_ref() {
write!(f, ";profile-level-id={profile_level_id}")?;
}
if let Some(mut parameter_sets) = self.parameter_sets.clone() {
f.write_str(";sprop-parameter-sets=")?;
if let Ok(Some(nal_unit)) = extract_nal_unit(&mut parameter_sets) {
write!(f, "{}", Base64Display::new(&nal_unit, &BASE64_STANDARD))?;
}
while let Ok(Some(nal_unit)) = extract_nal_unit(&mut parameter_sets) {
write!(f, ",{}", Base64Display::new(&nal_unit, &BASE64_STANDARD))?;
}
}
Ok(())
}
}
impl FromStr for H264Parameters {
type Err = InvalidParameters;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let params = s
.split(';')
.map(str::trim)
.filter(|param| !param.is_empty());
let mut packetization_mode = None;
let mut interleaving_depth = None;
let mut max_don_diff = None;
let mut profile_level_id = None;
let mut parameter_sets = None;
for param in params {
let (key, value) = param.split_once('=').ok_or(InvalidParameters)?;
let key = key.trim();
let value = value.trim();
match key {
"packetization-mode" => packetization_mode = Some(value),
"sprop-interleaving-depth" => interleaving_depth = Some(value),
"sprop-max-don-diff" => max_don_diff = Some(value),
"profile-level-id" => profile_level_id = Some(value),
"sprop-parameter-sets" => parameter_sets = Some(value),
_ => (),
}
}
let packetization_mode = packetization_mode
.unwrap_or("0")
.parse::<u8>()
.map_err(|_| InvalidParameters)?;
let interleaving_depth = interleaving_depth
.map(u16::from_str)
.transpose()
.map_err(|_| InvalidParameters)?;
let max_don_diff = max_don_diff
.map(u16::from_str)
.transpose()
.map_err(|_| InvalidParameters)?;
let profile_level_id = profile_level_id.map(ProfileLevelId::from_str).transpose()?;
let parameter_sets = parameter_sets
.map(parse_parameter_sets)
.transpose()
.map_err(|_| InvalidParameters)?;
let res = Self {
packetization_mode,
interleaving_depth,
max_don_diff,
profile_level_id,
parameter_sets,
};
Ok(res)
}
}
#[derive(Debug, Copy, Clone)]
pub struct InvalidProfileLevelId;
impl Display for InvalidProfileLevelId {
#[inline]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str("invalid h264 profile-level-id")
}
}
impl std::error::Error for InvalidProfileLevelId {}
#[derive(Copy, Clone)]
pub struct ProfileLevelId {
profile_idc: u8,
constraints: u8,
level_idc: u8,
}
impl ProfileLevelId {
#[inline]
pub const fn new(profile_idc: u8, constraints: u8, level_idc: u8) -> Self {
Self {
profile_idc,
constraints,
level_idc,
}
}
#[inline]
pub const fn profile_idc(&self) -> u8 {
self.profile_idc
}
#[inline]
pub const fn constraints(&self) -> u8 {
self.constraints
}
#[inline]
pub const fn level_idc(&self) -> u8 {
self.level_idc
}
}
impl Display for ProfileLevelId {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,
"{:02X}{:02X}{:02X}",
self.profile_idc, self.constraints, self.level_idc
)
}
}
impl FromStr for ProfileLevelId {
type Err = InvalidProfileLevelId;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() != 6 {
return Err(InvalidProfileLevelId);
}
let n = u32::from_str_radix(s, 16).map_err(|_| InvalidProfileLevelId)?;
let profile_idc = (n >> 16) as u8;
let constraints = (n >> 8) as u8;
let level_idc = n as u8;
let res = Self::new(profile_idc, constraints, level_idc);
Ok(res)
}
}
fn parse_parameter_sets(s: &str) -> Result<Bytes, DecodeError> {
let mut res = Vec::new();
for b64 in s.split(',') {
let input = b64.trim();
if input.is_empty() {
continue;
}
res.extend_from_slice(&[0, 0, 1]);
Engine::decode_vec(&BASE64_STANDARD, input, &mut res)?;
}
Ok(res.into())
}