#![cfg_attr(docsrs, feature(doc_cfg))]
use std::num::NonZeroU8;
use std::num::{NonZeroU16, NonZeroU32};
use bytes::Bytes;
use crate::Error;
use crate::error::ErrorInt;
use crate::rtp::ReceivedPacket;
macro_rules! write_mp4_box {
($buf:expr, $fourcc:expr, $b:block) => {{
let _: &mut Vec<u8> = $buf; let pos_start = $buf.len();
let fourcc: [u8; 4] = $fourcc;
$buf.extend_from_slice(&[0, 0, 0, 0, fourcc[0], fourcc[1], fourcc[2], fourcc[3]]);
let r = {
$b;
};
let pos_end = $buf.len();
let len = pos_end
.checked_sub(pos_start)
.expect("buf should not shrink during `write_mp4_box` block");
let len = u32::try_from(len).expect("box size should not exceed u32::MAX");
$buf[pos_start..pos_start + 4].copy_from_slice(&len.to_be_bytes()[..]);
r
}};
}
fn set_iso14496_length(len: usize, data: &mut [u8]) -> usize {
if len < 1 << 7 {
data[0] = len as u8;
1
} else if len < 1 << 14 {
data[0] = ((len & 0x7F) | 0x80) as u8;
data[1] = (len >> 7) as u8;
2
} else if len < 1 << 21 {
data[0] = ((len & 0x7F) | 0x80) as u8;
data[1] = (((len >> 7) & 0x7F) | 0x80) as u8;
data[2] = (len >> 14) as u8;
3
} else if len < 1 << 28 {
data[0] = ((len & 0x7F) | 0x80) as u8;
data[1] = (((len >> 7) & 0x7F) | 0x80) as u8;
data[2] = (((len >> 14) & 0x7F) | 0x80) as u8;
data[3] = (len >> 21) as u8;
4
} else {
panic!("ISO 14496 descriptor should not exceed maximum length of 2**28 - 1")
}
}
macro_rules! write_mpeg4_descriptor {
($buf:expr, $tag:expr, $b:block) => {{
let _: &mut Vec<u8> = $buf; let _: u8 = $tag;
let pos_start = $buf.len();
$buf.extend_from_slice(&[$tag, 0, 0, 0, 0]);
let r = {
$b;
};
let pos_end = $buf.len();
let len = pos_end
.checked_sub(pos_start + 5)
.expect("`buf` should not shrink during `write_iso14496_descriptor` block");
let len_len =
crate::codec::set_iso14496_length(len, &mut $buf[pos_start + 1..pos_start + 4]);
$buf.copy_within(pos_start + 5..pos_end, pos_start + 1 + len_len);
$buf.truncate(pos_end + len_len - 4);
r
}};
}
pub mod aac;
pub(crate) mod g723;
pub mod h26x;
pub(crate) mod jpeg;
pub mod h264;
#[cfg(feature = "h265")]
#[doc(hidden)]
pub mod h265;
pub(crate) mod onvif;
pub(crate) mod simple_audio;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct FrameFormat {
pub h26x_framing: h26x::Framing,
pub parameter_set_insertion: ParameterSetInsertion,
pub aac_framing: aac::Framing,
}
impl FrameFormat {
pub const MP4: Self = Self {
h26x_framing: h26x::Framing::FourByteLength,
parameter_set_insertion: ParameterSetInsertion::Never,
aac_framing: aac::Framing::Raw,
};
pub const SIMPLE: Self = Self {
h26x_framing: h26x::Framing::AnnexB,
parameter_set_insertion: ParameterSetInsertion::EachKeyFrame,
aac_framing: aac::Framing::Adts,
};
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ParameterSetInsertion {
#[default]
EachKeyFrame,
OnChange,
Never,
}
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum CodecItem {
VideoFrame(VideoFrame),
AudioFrame(AudioFrame),
MessageFrame(MessageFrame),
Rtcp(crate::rtcp::ReceivedCompoundPacket),
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum ParametersRef<'a> {
Video(&'a VideoParameters),
Audio(&'a AudioParameters),
Message(&'a MessageParameters),
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct VideoParameters {
all_pixel_dimensions: AllPixelDimensions,
rfc6381_codec: String,
pixel_aspect_ratio: Option<(u32, u32)>,
frame_rate: Option<(u32, u32)>,
extra_data: Bytes,
codec: VideoParametersCodec,
}
#[doc(hidden)]
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct AllPixelDimensions {
pub display: (u16, u16),
pub coded: (u16, u16),
}
impl VideoParameters {
pub fn rfc6381_codec(&self) -> &str {
&self.rfc6381_codec
}
pub fn mp4_sample_entry(&self) -> VideoSampleEntryBuilder<'_> {
VideoSampleEntryBuilder {
params: self,
aspect_ratio_override: None,
}
}
pub fn pixel_dimensions(&self) -> (u32, u32) {
let (width, height) = self.all_pixel_dimensions.display;
(width.into(), height.into())
}
pub fn coded_pixel_dimensions(&self) -> (u32, u32) {
let (width, height) = self.all_pixel_dimensions.coded;
(width.into(), height.into())
}
pub fn pixel_aspect_ratio(&self) -> Option<(u32, u32)> {
self.pixel_aspect_ratio
}
pub fn frame_rate(&self) -> Option<(u32, u32)> {
self.frame_rate
}
pub fn codec_params(&self) -> &VideoParametersCodec {
&self.codec
}
pub fn extra_data(&self) -> &[u8] {
&self.extra_data
}
}
impl std::fmt::Debug for VideoParameters {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("VideoParameters")
.field("rfc6381_codec", &self.rfc6381_codec)
.field("pixel_dimensions", &self.all_pixel_dimensions.display)
.field("coded_pixel_dimensions", &self.all_pixel_dimensions.coded)
.field("pixel_aspect_ratio", &self.pixel_aspect_ratio)
.field("frame_rate", &self.frame_rate)
.field(
"extra_data",
&crate::hex::LimitedHex::new(&self.extra_data, 256),
)
.finish()
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum VideoParametersCodec {
H264 {
sps: Bytes,
pps: Bytes,
},
#[cfg(feature = "h265")]
H265 {
vps: Bytes,
sps: Bytes,
pps: Bytes,
},
Jpeg,
}
impl std::fmt::Debug for VideoParametersCodec {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::H264 { sps, pps } => f
.debug_struct("H264")
.field("sps", &crate::hex::LimitedHex::new(sps, 256))
.field("pps", &crate::hex::LimitedHex::new(pps, 256))
.finish(),
#[cfg(feature = "h265")]
Self::H265 { vps, sps, pps } => f
.debug_struct("H265")
.field("vps", &crate::hex::LimitedHex::new(vps, 256))
.field("sps", &crate::hex::LimitedHex::new(sps, 256))
.field("pps", &crate::hex::LimitedHex::new(pps, 256))
.finish(),
Self::Jpeg => write!(f, "Jpeg"),
}
}
}
impl VideoParametersCodec {
fn visual_sample_entry_box_type(&self) -> [u8; 4] {
match self {
VideoParametersCodec::H264 { .. } => *b"avc1",
#[cfg(feature = "h265")]
VideoParametersCodec::H265 { .. } => *b"hvc1",
VideoParametersCodec::Jpeg => *b"mp4v",
}
}
}
pub struct VideoSampleEntryBuilder<'p> {
params: &'p VideoParameters,
aspect_ratio_override: Option<(u16, u16)>,
}
impl VideoSampleEntryBuilder<'_> {
#[inline]
pub fn with_aspect_ratio(self, aspect_ratio: (u16, u16)) -> Self {
Self {
aspect_ratio_override: Some(aspect_ratio),
..self
}
}
pub fn build(self) -> Result<Vec<u8>, Error> {
let mut buf = Vec::new();
write_mp4_box!(
&mut buf,
self.params.codec.visual_sample_entry_box_type(),
{
buf.extend_from_slice(&0u32.to_be_bytes()[..]); buf.extend_from_slice(&1u32.to_be_bytes()[..]);
buf.extend_from_slice(&[0; 16]);
buf.extend_from_slice(
&self.params.all_pixel_dimensions.display.0.to_be_bytes()[..],
);
buf.extend_from_slice(
&self.params.all_pixel_dimensions.display.1.to_be_bytes()[..],
);
buf.extend_from_slice(&[
0x00, 0x48, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xff, 0xff, ]);
match self.params.codec {
VideoParametersCodec::H264 { .. } => {
write_mp4_box!(&mut buf, *b"avcC", {
buf.extend_from_slice(&self.params.extra_data);
});
}
#[cfg(feature = "h265")]
VideoParametersCodec::H265 { .. } => {
write_mp4_box!(&mut buf, *b"hvcC", {
buf.extend_from_slice(&self.params.extra_data);
});
}
VideoParametersCodec::Jpeg => {
jpeg::append_esds(&mut buf);
}
}
if let Some(aspect_ratio) = self.aspect_ratio_override {
write_mp4_box!(&mut buf, *b"pasp", {
buf.extend_from_slice(&u32::from(aspect_ratio.0).to_be_bytes()[..]);
buf.extend_from_slice(&u32::from(aspect_ratio.1).to_be_bytes()[..]);
});
}
}
);
Ok(buf)
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct AudioParameters {
rfc6381_codec: Option<String>,
frame_length: Option<NonZeroU32>,
clock_rate: u32,
channels: NonZeroU16,
extra_data: Vec<u8>,
codec: AudioParametersCodec,
}
impl std::fmt::Debug for AudioParameters {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AudioParameters")
.field("rfc6381_codec", &self.rfc6381_codec)
.field("frame_length", &self.frame_length)
.field(
"extra_data",
&crate::hex::LimitedHex::new(&self.extra_data, 256),
)
.finish()
}
}
impl AudioParameters {
pub fn rfc6381_codec(&self) -> Option<&str> {
self.rfc6381_codec.as_deref()
}
pub fn frame_length(&self) -> Option<NonZeroU32> {
self.frame_length
}
pub fn clock_rate(&self) -> u32 {
self.clock_rate
}
pub fn channels(&self) -> NonZeroU16 {
self.channels
}
pub fn extra_data(&self) -> &[u8] {
&self.extra_data
}
pub fn mp4_sample_entry(&self) -> AudioSampleEntryBuilder<'_> {
AudioSampleEntryBuilder { params: self }
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
enum AudioParametersCodec {
Aac { channels_config_id: NonZeroU8 },
Other,
}
pub struct AudioSampleEntryBuilder<'p> {
params: &'p AudioParameters,
}
impl AudioSampleEntryBuilder<'_> {
pub fn build(self) -> Result<Vec<u8>, Error> {
match self.params.codec {
AudioParametersCodec::Aac { channels_config_id } => aac::make_sample_entry(
channels_config_id,
self.params.clock_rate,
&self.params.extra_data,
),
AudioParametersCodec::Other => {
bail!(ErrorInt::Unsupported(
"unsupported audio codec for mp4".to_owned()
));
}
}
}
}
#[derive(Eq, PartialEq)]
pub struct AudioFrame {
ctx: crate::PacketContext,
stream_id: usize,
timestamp: crate::Timestamp,
frame_length: NonZeroU32,
loss: u16,
data: Bytes,
}
impl AudioFrame {
#[inline]
pub fn ctx(&self) -> &crate::PacketContext {
&self.ctx
}
#[inline]
pub fn stream_id(&self) -> usize {
self.stream_id
}
#[inline]
pub fn timestamp(&self) -> crate::Timestamp {
self.timestamp
}
#[inline]
pub fn frame_length(&self) -> NonZeroU32 {
self.frame_length
}
#[inline]
pub fn loss(&self) -> u16 {
self.loss
}
#[inline]
pub fn data(&self) -> &[u8] {
&self.data
}
}
impl std::fmt::Debug for AudioFrame {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AudioFrame")
.field("stream_id", &self.stream_id)
.field("ctx", &self.ctx)
.field("loss", &self.loss)
.field("timestamp", &self.timestamp)
.field("frame_length", &self.frame_length)
.field("data", &crate::hex::LimitedHex::new(&self.data, 64))
.finish()
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct MessageParameters(onvif::CompressionType);
#[derive(Eq, PartialEq)]
pub struct MessageFrame {
ctx: crate::PacketContext,
timestamp: crate::Timestamp,
stream_id: usize,
loss: u16,
data: Bytes,
}
impl std::fmt::Debug for MessageFrame {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AudioFrame")
.field("ctx", &self.ctx)
.field("stream_id", &self.stream_id)
.field("loss", &self.loss)
.field("timestamp", &self.timestamp)
.field("data", &crate::hex::LimitedHex::new(&self.data, 64))
.finish()
}
}
impl MessageFrame {
#[inline]
pub fn ctx(&self) -> &crate::PacketContext {
&self.ctx
}
#[inline]
pub fn stream_id(&self) -> usize {
self.stream_id
}
#[inline]
pub fn timestamp(&self) -> crate::Timestamp {
self.timestamp
}
#[inline]
pub fn loss(&self) -> u16 {
self.loss
}
#[inline]
pub fn data(&self) -> &[u8] {
&self.data
}
}
#[derive(Eq, PartialEq)]
pub struct VideoFrame {
start_ctx: crate::PacketContext,
end_ctx: crate::PacketContext,
has_new_parameters: bool,
loss: u16,
timestamp: crate::Timestamp,
stream_id: usize,
is_random_access_point: bool,
is_disposable: bool,
data: Vec<u8>,
}
impl VideoFrame {
#[inline]
pub fn stream_id(&self) -> usize {
self.stream_id
}
#[inline]
pub fn has_new_parameters(&self) -> bool {
self.has_new_parameters
}
#[inline]
pub fn loss(&self) -> u16 {
self.loss
}
#[inline]
pub fn timestamp(&self) -> crate::Timestamp {
self.timestamp
}
#[inline]
pub fn start_ctx(&self) -> &crate::PacketContext {
&self.start_ctx
}
#[inline]
pub fn end_ctx(&self) -> &crate::PacketContext {
&self.end_ctx
}
#[inline]
pub fn is_random_access_point(&self) -> bool {
self.is_random_access_point
}
#[inline]
pub fn is_disposable(&self) -> bool {
self.is_disposable
}
#[inline]
pub fn data(&self) -> &[u8] {
&self.data
}
#[inline]
pub fn into_data(self) -> Vec<u8> {
self.data
}
}
impl std::fmt::Debug for VideoFrame {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("VideoFrame")
.field("timestamp", &self.timestamp)
.field("start_ctx", &self.start_ctx)
.field("end_ctx", &self.end_ctx)
.field("loss", &self.loss)
.field("has_new_parameters", &self.has_new_parameters)
.field("is_random_access_point", &self.is_random_access_point)
.field("is_disposable", &self.is_disposable)
.field("data", &crate::hex::LimitedHex::new(&self.data, 64))
.finish()
}
}
#[doc(hidden)]
#[derive(Debug)]
pub struct Depacketizer(DepacketizerInner);
#[derive(Debug)]
enum DepacketizerInner {
Aac(Box<aac::Depacketizer>),
SimpleAudio(Box<simple_audio::Depacketizer>),
G723(Box<g723::Depacketizer>),
H264(Box<h264::Depacketizer>),
#[cfg(feature = "h265")]
H265(Box<h265::Depacketizer>),
Onvif(Box<onvif::Depacketizer>),
Jpeg(Box<jpeg::Depacketizer>),
}
#[doc(hidden)]
#[derive(Debug, PartialEq, Eq)]
pub struct DepacketizeError {
pub(crate) pkt_ctx: crate::PacketContext,
pub(crate) ssrc: u32,
pub(crate) sequence_number: u16,
pub(crate) description: String,
}
impl Depacketizer {
pub fn new(
media: &str,
encoding_name: &str,
clock_rate: u32,
channels: Option<NonZeroU16>,
format_specific_params: Option<&str>,
) -> Result<Self, String> {
use onvif::CompressionType;
Ok(Depacketizer(match (media, encoding_name) {
("video", "h264") => DepacketizerInner::H264(Box::new(h264::Depacketizer::new(
clock_rate,
format_specific_params,
)?)),
#[cfg(feature = "h265")]
("video", "h265") => DepacketizerInner::H265(Box::new(h265::Depacketizer::new(
clock_rate,
format_specific_params,
)?)),
("image" | "video", "jpeg") => DepacketizerInner::Jpeg(Box::default()),
("audio", "mpeg4-generic") => DepacketizerInner::Aac(Box::new(aac::Depacketizer::new(
clock_rate,
channels,
format_specific_params,
)?)),
("audio", "g726-16") => DepacketizerInner::SimpleAudio(Box::new(
simple_audio::Depacketizer::new(clock_rate, 2, channels),
)),
("audio", "g726-24") => DepacketizerInner::SimpleAudio(Box::new(
simple_audio::Depacketizer::new(clock_rate, 3, channels),
)),
("audio", "dvi4") | ("audio", "g726-32") => DepacketizerInner::SimpleAudio(Box::new(
simple_audio::Depacketizer::new(clock_rate, 4, channels),
)),
("audio", "g726-40") => DepacketizerInner::SimpleAudio(Box::new(
simple_audio::Depacketizer::new(clock_rate, 5, channels),
)),
("audio", "pcma") | ("audio", "pcmu") | ("audio", "u8") | ("audio", "g722") => {
DepacketizerInner::SimpleAudio(Box::new(simple_audio::Depacketizer::new(
clock_rate, 8, channels,
)))
}
("audio", "l16") => DepacketizerInner::SimpleAudio(Box::new(
simple_audio::Depacketizer::new(clock_rate, 16, channels),
)),
("audio", "g723") => {
DepacketizerInner::G723(Box::new(g723::Depacketizer::new(clock_rate)?))
}
("application", "vnd.onvif.metadata") => DepacketizerInner::Onvif(Box::new(
onvif::Depacketizer::new(CompressionType::Uncompressed),
)),
("application", "vnd.onvif.metadata.gzip") => DepacketizerInner::Onvif(Box::new(
onvif::Depacketizer::new(CompressionType::GzipCompressed),
)),
("application", "vnd.onvif.metadata.exi.onvif") => DepacketizerInner::Onvif(Box::new(
onvif::Depacketizer::new(CompressionType::ExiDefault),
)),
("application", "vnd.onvif.metadata.exi.ext") => DepacketizerInner::Onvif(Box::new(
onvif::Depacketizer::new(CompressionType::ExiInBand),
)),
(_, _) => {
log::info!(
"no depacketizer for media/encoding_name {}/{}",
media,
encoding_name
);
return Err(format!(
"no depacketizer for media/encoding_name {media}/{encoding_name}"
));
}
}))
}
pub fn check_invariants(&self) {
match &self.0 {
DepacketizerInner::Aac(_) => {}
DepacketizerInner::G723(_) => {}
DepacketizerInner::H264(d) => d.check_invariants(),
#[cfg(feature = "h265")]
DepacketizerInner::H265(d) => d.check_invariants(),
DepacketizerInner::Onvif(_) => {}
DepacketizerInner::SimpleAudio(_) => {}
DepacketizerInner::Jpeg(_) => {}
}
}
#[doc(hidden)]
pub fn set_frame_format(&mut self, format: FrameFormat) {
match &mut self.0 {
DepacketizerInner::Aac(d) => d.set_frame_format(format),
DepacketizerInner::H264(d) => d.set_frame_format(format),
#[cfg(feature = "h265")]
DepacketizerInner::H265(d) => d.set_frame_format(format),
_ => {}
}
}
pub fn parameters(&self) -> Option<ParametersRef<'_>> {
match &self.0 {
DepacketizerInner::Aac(d) => d.parameters(),
DepacketizerInner::G723(d) => d.parameters(),
DepacketizerInner::H264(d) => d.parameters(),
#[cfg(feature = "h265")]
DepacketizerInner::H265(d) => d.parameters(),
DepacketizerInner::Onvif(d) => d.parameters(),
DepacketizerInner::SimpleAudio(d) => d.parameters(),
DepacketizerInner::Jpeg(d) => d.parameters(),
}
}
pub fn push(&mut self, input: ReceivedPacket) -> Result<(), String> {
match &mut self.0 {
DepacketizerInner::Aac(d) => d.push(input),
DepacketizerInner::G723(d) => d.push(input),
DepacketizerInner::H264(d) => d.push(input),
#[cfg(feature = "h265")]
DepacketizerInner::H265(d) => d.push(input),
DepacketizerInner::Onvif(d) => d.push(input),
DepacketizerInner::SimpleAudio(d) => d.push(input),
DepacketizerInner::Jpeg(d) => d.push(input),
}
}
pub fn pull(&mut self) -> Option<Result<CodecItem, DepacketizeError>> {
match &mut self.0 {
DepacketizerInner::Aac(d) => d.pull(),
DepacketizerInner::G723(d) => d.pull(),
DepacketizerInner::H264(d) => d.pull(),
#[cfg(feature = "h265")]
DepacketizerInner::H265(d) => d.pull(),
DepacketizerInner::Onvif(d) => d.pull(),
DepacketizerInner::SimpleAudio(d) => d.pull(),
DepacketizerInner::Jpeg(d) => d.pull(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn print_sizes() {
crate::testutil::init_logging();
for (name, size) in &[
("Depacketizer", std::mem::size_of::<Depacketizer>()),
(
"aac::Depacketizer",
std::mem::size_of::<aac::Depacketizer>(),
),
(
"g723::Depacketizer",
std::mem::size_of::<g723::Depacketizer>(),
),
(
"h264::Depacketizer",
std::mem::size_of::<h264::Depacketizer>(),
),
#[cfg(feature = "h265")]
(
"h265::Depacketizer",
std::mem::size_of::<h265::Depacketizer>(),
),
(
"onvif::Depacketizer",
std::mem::size_of::<onvif::Depacketizer>(),
),
(
"simple_audio::Depacketizer",
std::mem::size_of::<simple_audio::Depacketizer>(),
),
("CodecItem", std::mem::size_of::<CodecItem>()),
("VideoFrame", std::mem::size_of::<VideoFrame>()),
("AudioFrame", std::mem::size_of::<AudioFrame>()),
("MessageFrame", std::mem::size_of::<MessageFrame>()),
("VideoParameters", std::mem::size_of::<VideoParameters>()),
("AudioParameters", std::mem::size_of::<AudioParameters>()),
(
"MessageParameters",
std::mem::size_of::<MessageParameters>(),
),
] {
log::info!("{name:-40} {size:4}");
}
}
}