mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-11-22 11:30:59 +00:00
fmp4mux: Add ONVIF variant with support for additional codecs
This variant supports H264/5, JPEG, alaw, mulaw and G726.
This commit is contained in:
parent
0206178279
commit
e4634ca2fe
3 changed files with 202 additions and 15 deletions
|
@ -335,7 +335,7 @@ fn brands_from_variant_and_caps(
|
||||||
caps: &[&gst::Caps],
|
caps: &[&gst::Caps],
|
||||||
) -> (&'static [u8; 4], Vec<&'static [u8; 4]>) {
|
) -> (&'static [u8; 4], Vec<&'static [u8; 4]>) {
|
||||||
match variant {
|
match variant {
|
||||||
super::Variant::ISO => (b"iso6", vec![b"iso6"]),
|
super::Variant::ISO | super::Variant::ONVIF => (b"iso6", vec![b"iso6"]),
|
||||||
super::Variant::DASH => {
|
super::Variant::DASH => {
|
||||||
// FIXME: `dsms` / `dash` brands, `msix`
|
// FIXME: `dsms` / `dash` brands, `msix`
|
||||||
(b"msdh", vec![b"dums", b"msdh", b"iso6"])
|
(b"msdh", vec![b"dums", b"msdh", b"iso6"])
|
||||||
|
@ -524,7 +524,9 @@ fn write_tkhd(
|
||||||
// Volume
|
// Volume
|
||||||
let s = caps.structure(0).unwrap();
|
let s = caps.structure(0).unwrap();
|
||||||
match s.name() {
|
match s.name() {
|
||||||
"audio/mpeg" => v.extend((1u16 << 8).to_be_bytes()),
|
"audio/mpeg" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||||
|
v.extend((1u16 << 8).to_be_bytes())
|
||||||
|
}
|
||||||
_ => v.extend(0u16.to_be_bytes()),
|
_ => v.extend(0u16.to_be_bytes()),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -550,7 +552,7 @@ fn write_tkhd(
|
||||||
|
|
||||||
// Width/height
|
// Width/height
|
||||||
match s.name() {
|
match s.name() {
|
||||||
"video/x-h264" | "video/x-h265" => {
|
"video/x-h264" | "video/x-h265" | "image/jpeg" => {
|
||||||
let width = s.get::<i32>("width").context("video caps without width")? as u32;
|
let width = s.get::<i32>("width").context("video caps without width")? as u32;
|
||||||
let height = s
|
let height = s
|
||||||
.get::<i32>("height")
|
.get::<i32>("height")
|
||||||
|
@ -642,8 +644,10 @@ fn write_hdlr(
|
||||||
|
|
||||||
let s = caps.structure(0).unwrap();
|
let s = caps.structure(0).unwrap();
|
||||||
let (handler_type, name) = match s.name() {
|
let (handler_type, name) = match s.name() {
|
||||||
"video/x-h264" | "video/x-h265" => (b"vide", b"VideoHandler\0"),
|
"video/x-h264" | "video/x-h265" | "image/jpeg" => (b"vide", b"VideoHandler\0"),
|
||||||
"audio/mpeg" => (b"soun", b"SoundHandler\0"),
|
"audio/mpeg" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||||
|
(b"soun", b"SoundHandler\0")
|
||||||
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -667,13 +671,15 @@ fn write_minf(
|
||||||
let s = caps.structure(0).unwrap();
|
let s = caps.structure(0).unwrap();
|
||||||
|
|
||||||
match s.name() {
|
match s.name() {
|
||||||
"video/x-h264" | "video/x-h265" => {
|
"video/x-h264" | "video/x-h265" | "image/jpeg" => {
|
||||||
// Flags are always 1 for unspecified reasons
|
// Flags are always 1 for unspecified reasons
|
||||||
write_full_box(v, b"vmhd", FULL_BOX_VERSION_0, 1, |v| write_vmhd(v, cfg))?
|
write_full_box(v, b"vmhd", FULL_BOX_VERSION_0, 1, |v| write_vmhd(v, cfg))?
|
||||||
}
|
}
|
||||||
"audio/mpeg" => write_full_box(v, b"smhd", FULL_BOX_VERSION_0, FULL_BOX_FLAGS_NONE, |v| {
|
"audio/mpeg" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||||
write_smhd(v, cfg)
|
write_full_box(v, b"smhd", FULL_BOX_VERSION_0, FULL_BOX_FLAGS_NONE, |v| {
|
||||||
})?,
|
write_smhd(v, cfg)
|
||||||
|
})?
|
||||||
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -775,8 +781,10 @@ fn write_stsd(
|
||||||
|
|
||||||
let s = caps.structure(0).unwrap();
|
let s = caps.structure(0).unwrap();
|
||||||
match s.name() {
|
match s.name() {
|
||||||
"video/x-h264" | "video/x-h265" => write_visual_sample_entry(v, cfg, caps)?,
|
"video/x-h264" | "video/x-h265" | "image/jpeg" => write_visual_sample_entry(v, cfg, caps)?,
|
||||||
"audio/mpeg" => write_audio_sample_entry(v, cfg, caps)?,
|
"audio/mpeg" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||||
|
write_audio_sample_entry(v, cfg, caps)?
|
||||||
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -822,6 +830,7 @@ fn write_visual_sample_entry(
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"image/jpeg" => b"jpeg",
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -890,6 +899,9 @@ fn write_visual_sample_entry(
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
"image/jpeg" => {
|
||||||
|
// Nothing to do here
|
||||||
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -995,6 +1007,37 @@ fn write_visual_sample_entry(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write fiel box for codecs that require it
|
||||||
|
if ["image/jpeg"].contains(&s.name()) {
|
||||||
|
let interlace_mode = s
|
||||||
|
.get::<&str>("interlace-mode")
|
||||||
|
.ok()
|
||||||
|
.map(gst_video::VideoInterlaceMode::from_string)
|
||||||
|
.unwrap_or(gst_video::VideoInterlaceMode::Progressive);
|
||||||
|
let field_order = s
|
||||||
|
.get::<&str>("field-order")
|
||||||
|
.ok()
|
||||||
|
.map(gst_video::VideoFieldOrder::from_string)
|
||||||
|
.unwrap_or(gst_video::VideoFieldOrder::Unknown);
|
||||||
|
|
||||||
|
write_box(v, b"fiel", move |v| {
|
||||||
|
let (interlace, field_order) = match interlace_mode {
|
||||||
|
gst_video::VideoInterlaceMode::Progressive => (1, 0),
|
||||||
|
gst_video::VideoInterlaceMode::Interleaved
|
||||||
|
if field_order == gst_video::VideoFieldOrder::TopFieldFirst =>
|
||||||
|
{
|
||||||
|
(2, 9)
|
||||||
|
}
|
||||||
|
gst_video::VideoInterlaceMode::Interleaved => (2, 14),
|
||||||
|
_ => (0, 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
v.push(interlace);
|
||||||
|
v.push(field_order);
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: write btrt bitrate box based on tags
|
// TODO: write btrt bitrate box based on tags
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1011,9 +1054,27 @@ fn write_audio_sample_entry(
|
||||||
let s = caps.structure(0).unwrap();
|
let s = caps.structure(0).unwrap();
|
||||||
let fourcc = match s.name() {
|
let fourcc = match s.name() {
|
||||||
"audio/mpeg" => b"mp4a",
|
"audio/mpeg" => b"mp4a",
|
||||||
|
"audio/x-alaw" => b"alaw",
|
||||||
|
"audio/x-mulaw" => b"ulaw",
|
||||||
|
"audio/x-adpcm" => {
|
||||||
|
let layout = s.get::<&str>("layout").context("no ADPCM layout field")?;
|
||||||
|
|
||||||
|
match layout {
|
||||||
|
"g726" => b"ms\x00\x45",
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let sample_size = match s.name() {
|
||||||
|
"audio/x-adpcm" => {
|
||||||
|
let bitrate = s.get::<i32>("bitrate").context("no ADPCM bitrate field")?;
|
||||||
|
(bitrate / 8000) as u16
|
||||||
|
}
|
||||||
|
_ => 16u16,
|
||||||
|
};
|
||||||
|
|
||||||
write_sample_entry_box(v, fourcc, move |v| {
|
write_sample_entry_box(v, fourcc, move |v| {
|
||||||
// Reserved
|
// Reserved
|
||||||
v.extend([0u8; 2 * 4]);
|
v.extend([0u8; 2 * 4]);
|
||||||
|
@ -1024,7 +1085,7 @@ fn write_audio_sample_entry(
|
||||||
v.extend(channels.to_be_bytes());
|
v.extend(channels.to_be_bytes());
|
||||||
|
|
||||||
// Sample size
|
// Sample size
|
||||||
v.extend(16u16.to_be_bytes());
|
v.extend(sample_size.to_be_bytes());
|
||||||
|
|
||||||
// Pre-defined
|
// Pre-defined
|
||||||
v.extend([0u8; 2]);
|
v.extend([0u8; 2]);
|
||||||
|
@ -1050,6 +1111,9 @@ fn write_audio_sample_entry(
|
||||||
}
|
}
|
||||||
write_esds_aac(v, &map)?;
|
write_esds_aac(v, &map)?;
|
||||||
}
|
}
|
||||||
|
"audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||||
|
// Nothing to do here
|
||||||
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1615,7 +1679,10 @@ fn write_traf(
|
||||||
let timescale = caps_to_timescale(caps);
|
let timescale = caps_to_timescale(caps);
|
||||||
|
|
||||||
let check_dts = matches!(s.name(), "video/x-h264" | "video/x-h265");
|
let check_dts = matches!(s.name(), "video/x-h264" | "video/x-h265");
|
||||||
let intra_only = matches!(s.name(), "audio/mpeg");
|
let intra_only = matches!(
|
||||||
|
s.name(),
|
||||||
|
"audio/mpeg" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" | "image/jpeg"
|
||||||
|
);
|
||||||
|
|
||||||
// Analyze all buffers to know what values can be put into the tfhd for all samples and what
|
// Analyze all buffers to know what values can be put into the tfhd for all samples and what
|
||||||
// has to be stored for every single sample
|
// has to be stored for every single sample
|
||||||
|
|
|
@ -844,6 +844,9 @@ impl FMP4Mux {
|
||||||
return Err(gst::FlowError::NotNegotiated);
|
return Err(gst::FlowError::NotNegotiated);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"image/jpeg" => {
|
||||||
|
intra_only = true;
|
||||||
|
}
|
||||||
"audio/mpeg" => {
|
"audio/mpeg" => {
|
||||||
if !s.has_field_with_type("codec_data", gst::Buffer::static_type()) {
|
if !s.has_field_with_type("codec_data", gst::Buffer::static_type()) {
|
||||||
gst::error!(CAT, obj: &pad, "Received caps without codec_data");
|
gst::error!(CAT, obj: &pad, "Received caps without codec_data");
|
||||||
|
@ -851,6 +854,12 @@ impl FMP4Mux {
|
||||||
}
|
}
|
||||||
intra_only = true;
|
intra_only = true;
|
||||||
}
|
}
|
||||||
|
"audio/x-alaw" | "audio/x-mulaw" => {
|
||||||
|
intra_only = true;
|
||||||
|
}
|
||||||
|
"audio/x-adpcm" => {
|
||||||
|
intra_only = true;
|
||||||
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -950,7 +959,7 @@ impl FMP4Mux {
|
||||||
state.stream_header = Some(buffer.clone());
|
state.stream_header = Some(buffer.clone());
|
||||||
|
|
||||||
let variant = match variant {
|
let variant = match variant {
|
||||||
super::Variant::ISO | super::Variant::DASH => "iso-fragmented",
|
super::Variant::ISO | super::Variant::DASH | super::Variant::ONVIF => "iso-fragmented",
|
||||||
super::Variant::CMAF => "cmaf",
|
super::Variant::CMAF => "cmaf",
|
||||||
};
|
};
|
||||||
let caps = gst::Caps::builder("video/quicktime")
|
let caps = gst::Caps::builder("video/quicktime")
|
||||||
|
@ -1872,3 +1881,103 @@ impl AggregatorImpl for DASHMP4Mux {}
|
||||||
impl FMP4MuxImpl for DASHMP4Mux {
|
impl FMP4MuxImpl for DASHMP4Mux {
|
||||||
const VARIANT: super::Variant = super::Variant::DASH;
|
const VARIANT: super::Variant = super::Variant::DASH;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub(crate) struct ONVIFFMP4Mux;
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for ONVIFFMP4Mux {
|
||||||
|
const NAME: &'static str = "GstONVIFFMP4Mux";
|
||||||
|
type Type = super::ONVIFFMP4Mux;
|
||||||
|
type ParentType = super::FMP4Mux;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectImpl for ONVIFFMP4Mux {}
|
||||||
|
|
||||||
|
impl GstObjectImpl for ONVIFFMP4Mux {}
|
||||||
|
|
||||||
|
impl ElementImpl for ONVIFFMP4Mux {
|
||||||
|
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
|
||||||
|
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
|
||||||
|
gst::subclass::ElementMetadata::new(
|
||||||
|
"ONVIFFMP4Mux",
|
||||||
|
"Codec/Muxer",
|
||||||
|
"ONVIF fragmented MP4 muxer",
|
||||||
|
"Sebastian Dröge <sebastian@centricular.com>",
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
Some(&*ELEMENT_METADATA)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pad_templates() -> &'static [gst::PadTemplate] {
|
||||||
|
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
|
||||||
|
let src_pad_template = gst::PadTemplate::new(
|
||||||
|
"src",
|
||||||
|
gst::PadDirection::Src,
|
||||||
|
gst::PadPresence::Always,
|
||||||
|
&gst::Caps::builder("video/quicktime")
|
||||||
|
.field("variant", "iso-fragmented")
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let sink_pad_template = gst::PadTemplate::new(
|
||||||
|
"sink_%u",
|
||||||
|
gst::PadDirection::Sink,
|
||||||
|
gst::PadPresence::Request,
|
||||||
|
&[
|
||||||
|
gst::Structure::builder("video/x-h264")
|
||||||
|
.field("stream-format", gst::List::new(&[&"avc", &"avc3"]))
|
||||||
|
.field("alignment", "au")
|
||||||
|
.field("width", gst::IntRange::<i32>::new(1, u16::MAX as i32))
|
||||||
|
.field("height", gst::IntRange::<i32>::new(1, u16::MAX as i32))
|
||||||
|
.build(),
|
||||||
|
gst::Structure::builder("video/x-h265")
|
||||||
|
.field("stream-format", gst::List::new(&[&"hvc1", &"hev1"]))
|
||||||
|
.field("alignment", "au")
|
||||||
|
.field("width", gst::IntRange::<i32>::new(1, u16::MAX as i32))
|
||||||
|
.field("height", gst::IntRange::<i32>::new(1, u16::MAX as i32))
|
||||||
|
.build(),
|
||||||
|
gst::Structure::builder("image/jpeg")
|
||||||
|
.field("width", gst::IntRange::<i32>::new(1, u16::MAX as i32))
|
||||||
|
.field("height", gst::IntRange::<i32>::new(1, u16::MAX as i32))
|
||||||
|
.build(),
|
||||||
|
gst::Structure::builder("audio/mpeg")
|
||||||
|
.field("mpegversion", 4i32)
|
||||||
|
.field("stream-format", "raw")
|
||||||
|
.field("channels", gst::IntRange::<i32>::new(1, u16::MAX as i32))
|
||||||
|
.field("rate", gst::IntRange::<i32>::new(1, i32::MAX))
|
||||||
|
.build(),
|
||||||
|
gst::Structure::builder("audio/x-alaw")
|
||||||
|
.field("channels", gst::IntRange::<i32>::new(1, 2))
|
||||||
|
.field("rate", gst::IntRange::<i32>::new(1, i32::MAX))
|
||||||
|
.build(),
|
||||||
|
gst::Structure::builder("audio/x-mulaw")
|
||||||
|
.field("channels", gst::IntRange::<i32>::new(1, 2))
|
||||||
|
.field("rate", gst::IntRange::<i32>::new(1, i32::MAX))
|
||||||
|
.build(),
|
||||||
|
gst::Structure::builder("audio/x-adpcm")
|
||||||
|
.field("layout", "g726")
|
||||||
|
.field("channels", 1i32)
|
||||||
|
.field("rate", 8000i32)
|
||||||
|
.field("bitrate", gst::List::new([16000i32, 24000, 32000, 40000]))
|
||||||
|
.build(),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect::<gst::Caps>(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
vec![src_pad_template, sink_pad_template]
|
||||||
|
});
|
||||||
|
|
||||||
|
PAD_TEMPLATES.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AggregatorImpl for ONVIFFMP4Mux {}
|
||||||
|
|
||||||
|
impl FMP4MuxImpl for ONVIFFMP4Mux {
|
||||||
|
const VARIANT: super::Variant = super::Variant::ONVIF;
|
||||||
|
}
|
||||||
|
|
|
@ -28,6 +28,10 @@ glib::wrapper! {
|
||||||
pub(crate) struct DASHMP4Mux(ObjectSubclass<imp::DASHMP4Mux>) @extends FMP4Mux, gst_base::Aggregator, gst::Element, gst::Object;
|
pub(crate) struct DASHMP4Mux(ObjectSubclass<imp::DASHMP4Mux>) @extends FMP4Mux, gst_base::Aggregator, gst::Element, gst::Object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub(crate) struct ONVIFFMP4Mux(ObjectSubclass<imp::ONVIFFMP4Mux>) @extends FMP4Mux, gst_base::Aggregator, gst::Element, gst::Object;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
gst::Element::register(
|
gst::Element::register(
|
||||||
Some(plugin),
|
Some(plugin),
|
||||||
|
@ -47,6 +51,12 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
gst::Rank::Primary,
|
gst::Rank::Primary,
|
||||||
DASHMP4Mux::static_type(),
|
DASHMP4Mux::static_type(),
|
||||||
)?;
|
)?;
|
||||||
|
gst::Element::register(
|
||||||
|
Some(plugin),
|
||||||
|
"onviffmp4mux",
|
||||||
|
gst::Rank::Primary,
|
||||||
|
ONVIFFMP4Mux::static_type(),
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -97,12 +107,13 @@ pub(crate) enum Variant {
|
||||||
ISO,
|
ISO,
|
||||||
CMAF,
|
CMAF,
|
||||||
DASH,
|
DASH,
|
||||||
|
ONVIF,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Variant {
|
impl Variant {
|
||||||
pub(crate) fn is_single_stream(self) -> bool {
|
pub(crate) fn is_single_stream(self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Variant::ISO => false,
|
Variant::ISO | Variant::ONVIF => false,
|
||||||
Variant::CMAF | Variant::DASH => true,
|
Variant::CMAF | Variant::DASH => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue