mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-11-29 06:50:59 +00:00
fmp4mux: Add initial Opus support
Fixes https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/issues/239
This commit is contained in:
parent
9504e4d540
commit
6706f3a4b4
4 changed files with 101 additions and 7 deletions
|
@ -1587,7 +1587,7 @@
|
||||||
"long-name": "CMAFMux",
|
"long-name": "CMAFMux",
|
||||||
"pad-templates": {
|
"pad-templates": {
|
||||||
"sink": {
|
"sink": {
|
||||||
"caps": "video/x-h264:\n stream-format: { (string)avc, (string)avc3 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-h265:\n stream-format: { (string)hvc1, (string)hev1 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-vp9:\n profile: { (string)0, (string)1, (string)2, (string)3 }\n chroma-format: { (string)4:2:0, (string)4:2:2, (string)4:4:4 }\n bit-depth-luma: { (uint)8, (uint)10, (uint)12 }\nbit-depth-chroma: { (uint)8, (uint)10, (uint)12 }\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\naudio/mpeg:\n mpegversion: 4\n stream-format: raw\n channels: [ 1, 65535 ]\n rate: [ 1, 2147483647 ]\n",
|
"caps": "video/x-h264:\n stream-format: { (string)avc, (string)avc3 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-h265:\n stream-format: { (string)hvc1, (string)hev1 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\naudio/mpeg:\n mpegversion: 4\n stream-format: raw\n channels: [ 1, 65535 ]\n rate: [ 1, 2147483647 ]\n",
|
||||||
"direction": "sink",
|
"direction": "sink",
|
||||||
"presence": "always"
|
"presence": "always"
|
||||||
},
|
},
|
||||||
|
@ -1615,7 +1615,7 @@
|
||||||
"long-name": "DASHMP4Mux",
|
"long-name": "DASHMP4Mux",
|
||||||
"pad-templates": {
|
"pad-templates": {
|
||||||
"sink": {
|
"sink": {
|
||||||
"caps": "video/x-h264:\n stream-format: { (string)avc, (string)avc3 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-h265:\n stream-format: { (string)hvc1, (string)hev1 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-vp9:\n profile: { (string)0, (string)1, (string)2, (string)3 }\n chroma-format: { (string)4:2:0, (string)4:2:2, (string)4:4:4 }\n bit-depth-luma: { (uint)8, (uint)10, (uint)12 }\nbit-depth-chroma: { (uint)8, (uint)10, (uint)12 }\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\naudio/mpeg:\n mpegversion: 4\n stream-format: raw\n channels: [ 1, 65535 ]\n rate: [ 1, 2147483647 ]\n",
|
"caps": "video/x-h264:\n stream-format: { (string)avc, (string)avc3 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-h265:\n stream-format: { (string)hvc1, (string)hev1 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-vp9:\n profile: { (string)0, (string)1, (string)2, (string)3 }\n chroma-format: { (string)4:2:0, (string)4:2:2, (string)4:4:4 }\n bit-depth-luma: { (uint)8, (uint)10, (uint)12 }\nbit-depth-chroma: { (uint)8, (uint)10, (uint)12 }\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\naudio/mpeg:\n mpegversion: 4\n stream-format: raw\n channels: [ 1, 65535 ]\n rate: [ 1, 2147483647 ]\naudio/x-opus:\nchannel-mapping-family: [ 0, 255 ]\n channels: [ 1, 8 ]\n rate: [ 1, 2147483647 ]\n",
|
||||||
"direction": "sink",
|
"direction": "sink",
|
||||||
"presence": "always"
|
"presence": "always"
|
||||||
},
|
},
|
||||||
|
@ -1643,7 +1643,7 @@
|
||||||
"long-name": "ISOFMP4Mux",
|
"long-name": "ISOFMP4Mux",
|
||||||
"pad-templates": {
|
"pad-templates": {
|
||||||
"sink_%%u": {
|
"sink_%%u": {
|
||||||
"caps": "video/x-h264:\n stream-format: { (string)avc, (string)avc3 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-h265:\n stream-format: { (string)hvc1, (string)hev1 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-vp9:\n profile: { (string)0, (string)1, (string)2, (string)3 }\n chroma-format: { (string)4:2:0, (string)4:2:2, (string)4:4:4 }\n bit-depth-luma: { (uint)8, (uint)10, (uint)12 }\nbit-depth-chroma: { (uint)8, (uint)10, (uint)12 }\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\naudio/mpeg:\n mpegversion: 4\n stream-format: raw\n channels: [ 1, 65535 ]\n rate: [ 1, 2147483647 ]\n",
|
"caps": "video/x-h264:\n stream-format: { (string)avc, (string)avc3 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-h265:\n stream-format: { (string)hvc1, (string)hev1 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-vp9:\n profile: { (string)0, (string)1, (string)2, (string)3 }\n chroma-format: { (string)4:2:0, (string)4:2:2, (string)4:4:4 }\n bit-depth-luma: { (uint)8, (uint)10, (uint)12 }\nbit-depth-chroma: { (uint)8, (uint)10, (uint)12 }\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\naudio/mpeg:\n mpegversion: 4\n stream-format: raw\n channels: [ 1, 65535 ]\n rate: [ 1, 2147483647 ]\naudio/x-opus:\nchannel-mapping-family: [ 0, 255 ]\n channels: [ 1, 8 ]\n rate: [ 1, 2147483647 ]\n",
|
||||||
"direction": "sink",
|
"direction": "sink",
|
||||||
"presence": "request"
|
"presence": "request"
|
||||||
},
|
},
|
||||||
|
|
|
@ -14,6 +14,7 @@ gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/g
|
||||||
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
||||||
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
||||||
gst-video = { package = "gstreamer-video", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst-video = { package = "gstreamer-video", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
||||||
|
gst-pbutils = { package = "gstreamer-pbutils", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
||||||
once_cell = "1.0"
|
once_cell = "1.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
|
|
@ -593,7 +593,7 @@ 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" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
"audio/mpeg" | "audio/x-opus" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||||
v.extend((1u16 << 8).to_be_bytes())
|
v.extend((1u16 << 8).to_be_bytes())
|
||||||
}
|
}
|
||||||
_ => v.extend(0u16.to_be_bytes()),
|
_ => v.extend(0u16.to_be_bytes()),
|
||||||
|
@ -734,7 +734,7 @@ fn write_hdlr(
|
||||||
"video/x-h264" | "video/x-h265" | "video/x-vp9" | "image/jpeg" => {
|
"video/x-h264" | "video/x-h265" | "video/x-vp9" | "image/jpeg" => {
|
||||||
(b"vide", b"VideoHandler\0".as_slice())
|
(b"vide", b"VideoHandler\0".as_slice())
|
||||||
}
|
}
|
||||||
"audio/mpeg" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
"audio/mpeg" | "audio/x-opus" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||||
(b"soun", b"SoundHandler\0".as_slice())
|
(b"soun", b"SoundHandler\0".as_slice())
|
||||||
}
|
}
|
||||||
"application/x-onvif-metadata" => (b"meta", b"MetadataHandler\0".as_slice()),
|
"application/x-onvif-metadata" => (b"meta", b"MetadataHandler\0".as_slice()),
|
||||||
|
@ -765,7 +765,7 @@ fn write_minf(
|
||||||
// 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" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
"audio/mpeg" | "audio/x-opus" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||||
write_full_box(v, b"smhd", FULL_BOX_VERSION_0, FULL_BOX_FLAGS_NONE, |v| {
|
write_full_box(v, b"smhd", FULL_BOX_VERSION_0, FULL_BOX_FLAGS_NONE, |v| {
|
||||||
write_smhd(v, cfg)
|
write_smhd(v, cfg)
|
||||||
})?
|
})?
|
||||||
|
@ -879,7 +879,7 @@ fn write_stsd(
|
||||||
"video/x-h264" | "video/x-h265" | "video/x-vp9" | "image/jpeg" => {
|
"video/x-h264" | "video/x-h265" | "video/x-vp9" | "image/jpeg" => {
|
||||||
write_visual_sample_entry(v, cfg, caps)?
|
write_visual_sample_entry(v, cfg, caps)?
|
||||||
}
|
}
|
||||||
"audio/mpeg" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
"audio/mpeg" | "audio/x-opus" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||||
write_audio_sample_entry(v, cfg, caps)?
|
write_audio_sample_entry(v, cfg, caps)?
|
||||||
}
|
}
|
||||||
"application/x-onvif-metadata" => write_xml_meta_data_sample_entry(v, cfg, caps)?,
|
"application/x-onvif-metadata" => write_xml_meta_data_sample_entry(v, cfg, caps)?,
|
||||||
|
@ -1216,6 +1216,7 @@ 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-opus" => b"Opus",
|
||||||
"audio/x-alaw" => b"alaw",
|
"audio/x-alaw" => b"alaw",
|
||||||
"audio/x-mulaw" => b"ulaw",
|
"audio/x-mulaw" => b"ulaw",
|
||||||
"audio/x-adpcm" => {
|
"audio/x-adpcm" => {
|
||||||
|
@ -1273,6 +1274,9 @@ fn write_audio_sample_entry(
|
||||||
}
|
}
|
||||||
write_esds_aac(v, &map)?;
|
write_esds_aac(v, &map)?;
|
||||||
}
|
}
|
||||||
|
"audio/x-opus" => {
|
||||||
|
write_dops(v, caps)?;
|
||||||
|
}
|
||||||
"audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
"audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||||
// Nothing to do here
|
// Nothing to do here
|
||||||
}
|
}
|
||||||
|
@ -1408,6 +1412,70 @@ fn write_esds_aac(v: &mut Vec<u8>, codec_data: &[u8]) -> Result<(), Error> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_dops(v: &mut Vec<u8>, caps: &gst::CapsRef) -> Result<(), Error> {
|
||||||
|
let rate;
|
||||||
|
let channels;
|
||||||
|
let channel_mapping_family;
|
||||||
|
let stream_count;
|
||||||
|
let coupled_count;
|
||||||
|
let pre_skip;
|
||||||
|
let output_gain;
|
||||||
|
let mut channel_mapping = [0; 256];
|
||||||
|
|
||||||
|
// TODO: Use audio clipping meta to calculate pre_skip
|
||||||
|
|
||||||
|
if let Some(header) = caps
|
||||||
|
.structure(0)
|
||||||
|
.unwrap()
|
||||||
|
.get::<gst::ArrayRef>("streamheader")
|
||||||
|
.ok()
|
||||||
|
.and_then(|a| a.get(0).and_then(|v| v.get::<gst::Buffer>().ok()))
|
||||||
|
{
|
||||||
|
(
|
||||||
|
rate,
|
||||||
|
channels,
|
||||||
|
channel_mapping_family,
|
||||||
|
stream_count,
|
||||||
|
coupled_count,
|
||||||
|
pre_skip,
|
||||||
|
output_gain,
|
||||||
|
) = gst_pbutils::codec_utils_opus_parse_header(&header, Some(&mut channel_mapping))
|
||||||
|
.unwrap();
|
||||||
|
} else {
|
||||||
|
// FIXME: Workaround for below function taking a &Caps instead of &CapsRef
|
||||||
|
// SAFETY: This is OK because we only get an immutable reference and don't
|
||||||
|
// clone it, so nobody will be able to get a mutable reference to the caps.
|
||||||
|
let caps = unsafe { &*(&caps as *const &gst::CapsRef as *const gst::Caps) };
|
||||||
|
|
||||||
|
(
|
||||||
|
rate,
|
||||||
|
channels,
|
||||||
|
channel_mapping_family,
|
||||||
|
stream_count,
|
||||||
|
coupled_count,
|
||||||
|
) = gst_pbutils::codec_utils_opus_parse_caps(caps, Some(&mut channel_mapping)).unwrap();
|
||||||
|
output_gain = 0;
|
||||||
|
pre_skip = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
write_box(v, b"dOps", move |v| {
|
||||||
|
// Version number
|
||||||
|
v.push(0);
|
||||||
|
v.push(channels);
|
||||||
|
v.extend(pre_skip.to_le_bytes());
|
||||||
|
v.extend(rate.to_le_bytes());
|
||||||
|
v.extend(output_gain.to_le_bytes());
|
||||||
|
v.push(channel_mapping_family);
|
||||||
|
if channel_mapping_family > 0 {
|
||||||
|
v.push(stream_count);
|
||||||
|
v.push(coupled_count);
|
||||||
|
v.extend(&channel_mapping[..channels as usize]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn write_xml_meta_data_sample_entry(
|
fn write_xml_meta_data_sample_entry(
|
||||||
v: &mut Vec<u8>,
|
v: &mut Vec<u8>,
|
||||||
_cfg: &super::HeaderConfiguration,
|
_cfg: &super::HeaderConfiguration,
|
||||||
|
|
|
@ -1499,6 +1499,21 @@ impl FMP4Mux {
|
||||||
return Err(gst::FlowError::NotNegotiated);
|
return Err(gst::FlowError::NotNegotiated);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"audio/x-opus" => {
|
||||||
|
if let Some(header) = s
|
||||||
|
.get::<gst::ArrayRef>("streamheader")
|
||||||
|
.ok()
|
||||||
|
.and_then(|a| a.get(0).and_then(|v| v.get::<gst::Buffer>().ok()))
|
||||||
|
{
|
||||||
|
if gst_pbutils::codec_utils_opus_parse_header(&header, None).is_err() {
|
||||||
|
gst::error!(CAT, obj: pad, "Received invalid Opus header");
|
||||||
|
return Err(gst::FlowError::NotNegotiated);
|
||||||
|
}
|
||||||
|
} else if gst_pbutils::codec_utils_opus_parse_caps(&caps, None).is_err() {
|
||||||
|
gst::error!(CAT, obj: pad, "Received invalid Opus caps");
|
||||||
|
return Err(gst::FlowError::NotNegotiated);
|
||||||
|
}
|
||||||
|
}
|
||||||
"audio/x-alaw" | "audio/x-mulaw" => (),
|
"audio/x-alaw" | "audio/x-mulaw" => (),
|
||||||
"audio/x-adpcm" => (),
|
"audio/x-adpcm" => (),
|
||||||
"application/x-onvif-metadata" => (),
|
"application/x-onvif-metadata" => (),
|
||||||
|
@ -2362,6 +2377,11 @@ impl ElementImpl for ISOFMP4Mux {
|
||||||
.field("channels", gst::IntRange::new(1, u16::MAX as i32))
|
.field("channels", gst::IntRange::new(1, u16::MAX as i32))
|
||||||
.field("rate", gst::IntRange::new(1, i32::MAX))
|
.field("rate", gst::IntRange::new(1, i32::MAX))
|
||||||
.build(),
|
.build(),
|
||||||
|
gst::Structure::builder("audio/x-opus")
|
||||||
|
.field("channel-mapping-family", gst::IntRange::new(0i32, 255))
|
||||||
|
.field("channels", gst::IntRange::new(1i32, 8))
|
||||||
|
.field("rate", gst::IntRange::new(1, i32::MAX))
|
||||||
|
.build(),
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect::<gst::Caps>(),
|
.collect::<gst::Caps>(),
|
||||||
|
@ -2542,6 +2562,11 @@ impl ElementImpl for DASHMP4Mux {
|
||||||
.field("channels", gst::IntRange::<i32>::new(1, u16::MAX as i32))
|
.field("channels", gst::IntRange::<i32>::new(1, u16::MAX as i32))
|
||||||
.field("rate", gst::IntRange::<i32>::new(1, i32::MAX))
|
.field("rate", gst::IntRange::<i32>::new(1, i32::MAX))
|
||||||
.build(),
|
.build(),
|
||||||
|
gst::Structure::builder("audio/x-opus")
|
||||||
|
.field("channel-mapping-family", gst::IntRange::new(0i32, 255))
|
||||||
|
.field("channels", gst::IntRange::new(1i32, 8))
|
||||||
|
.field("rate", gst::IntRange::new(1, i32::MAX))
|
||||||
|
.build(),
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect::<gst::Caps>(),
|
.collect::<gst::Caps>(),
|
||||||
|
|
Loading…
Reference in a new issue