fmp4mux: Add support for AC-3 / EAC-3

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/2208>
This commit is contained in:
Sebastian Dröge 2025-04-19 18:00:24 +03:00 committed by GStreamer Marge Bot
parent 8256601d1d
commit 03e9a9f0fe
4 changed files with 638 additions and 8 deletions

View file

@ -3253,7 +3253,7 @@
"long-name": "CMAFMux",
"pad-templates": {
"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-av1:\n stream-format: obu-stream\n alignment: tu\n profile: { (string)main, (string)high, (string)professional }\n chroma-format: { (string)4:0:0, (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 ]\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 ]\naudio/x-opus:\nchannel-mapping-family: [ 0, 255 ]\n channels: [ 1, 8 ]\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-av1:\n stream-format: obu-stream\n alignment: tu\n profile: { (string)main, (string)high, (string)professional }\n chroma-format: { (string)4:0:0, (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 ]\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 ]\naudio/x-opus:\nchannel-mapping-family: [ 0, 255 ]\n channels: [ 1, 8 ]\n rate: [ 1, 2147483647 ]\naudio/x-eac3:\n framed: true\n alignment: iec61937\n channels: [ 1, 65535 ]\n rate: [ 1, 2147483647 ]\n",
"direction": "sink",
"presence": "always",
"type": "GstFMP4MuxPad"
@ -3290,7 +3290,7 @@
"long-name": "DASHMP4Mux",
"pad-templates": {
"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-vp8:\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 ]\nvideo/x-av1:\n stream-format: obu-stream\n alignment: tu\n profile: { (string)main, (string)high, (string)professional }\n chroma-format: { (string)4:0:0, (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",
"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-vp8:\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 ]\nvideo/x-av1:\n stream-format: obu-stream\n alignment: tu\n profile: { (string)main, (string)high, (string)professional }\n chroma-format: { (string)4:0:0, (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 ]\naudio/x-ac3:\n framed: true\n alignment: frame\n channels: [ 1, 65535 ]\n rate: [ 1, 2147483647 ]\naudio/x-eac3:\n framed: true\n alignment: iec61937\n channels: [ 1, 65535 ]\n rate: [ 1, 2147483647 ]\n",
"direction": "sink",
"presence": "always",
"type": "GstFMP4MuxPad"
@ -3319,7 +3319,7 @@
"long-name": "ISOFMP4Mux",
"pad-templates": {
"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-vp8:\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 ]\nvideo/x-av1:\n stream-format: obu-stream\n alignment: tu\n profile: { (string)main, (string)high, (string)professional }\n chroma-format: { (string)4:0:0, (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 ]\naudio/x-flac:\n framed: true\n channels: [ 1, 8 ]\n rate: [ 1, 655350 ]\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-vp8:\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 ]\nvideo/x-av1:\n stream-format: obu-stream\n alignment: tu\n profile: { (string)main, (string)high, (string)professional }\n chroma-format: { (string)4:0:0, (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 ]\naudio/x-flac:\n framed: true\n channels: [ 1, 8 ]\n rate: [ 1, 655350 ]\naudio/x-ac3:\n framed: true\n alignment: frame\n channels: [ 1, 65535 ]\n rate: [ 1, 2147483647 ]\naudio/x-eac3:\n framed: true\n alignment: iec61937\n channels: [ 1, 65535 ]\n rate: [ 1, 2147483647 ]\n",
"direction": "sink",
"presence": "request",
"type": "GstFMP4MuxPad"

View file

@ -164,6 +164,9 @@ fn cmaf_brands_from_caps(caps: &gst::CapsRef, compatible_brands: &mut Vec<&'stat
"audio/mpeg" => {
compatible_brands.push(b"caac");
}
"audio/x-eac3" => {
compatible_brands.push(b"ceac");
}
"audio/x-opus" => {
compatible_brands.push(b"opus");
}
@ -671,7 +674,7 @@ fn write_tkhd(
let s = stream.caps.structure(0).unwrap();
match s.name().as_str() {
"audio/mpeg" | "audio/x-opus" | "audio/x-flac" | "audio/x-alaw" | "audio/x-mulaw"
| "audio/x-adpcm" => v.extend((1u16 << 8).to_be_bytes()),
| "audio/x-adpcm" | "audio/x-ac3" | "audio/x-eac3" => v.extend((1u16 << 8).to_be_bytes()),
_ => v.extend(0u16.to_be_bytes()),
}
@ -798,7 +801,9 @@ fn write_hdlr(
"video/x-h264" | "video/x-h265" | "video/x-vp8" | "video/x-vp9" | "video/x-av1"
| "image/jpeg" => (b"vide", b"VideoHandler\0".as_slice()),
"audio/mpeg" | "audio/x-opus" | "audio/x-flac" | "audio/x-alaw" | "audio/x-mulaw"
| "audio/x-adpcm" => (b"soun", b"SoundHandler\0".as_slice()),
| "audio/x-adpcm" | "audio/x-ac3" | "audio/x-eac3" => {
(b"soun", b"SoundHandler\0".as_slice())
}
"application/x-onvif-metadata" => (b"meta", b"MetadataHandler\0".as_slice()),
_ => unreachable!(),
};
@ -829,7 +834,7 @@ fn write_minf(
write_full_box(v, b"vmhd", FULL_BOX_VERSION_0, 1, |v| write_vmhd(v, cfg))?
}
"audio/mpeg" | "audio/x-opus" | "audio/x-flac" | "audio/x-alaw" | "audio/x-mulaw"
| "audio/x-adpcm" => {
| "audio/x-adpcm" | "audio/x-ac3" | "audio/x-eac3" => {
write_full_box(v, b"smhd", FULL_BOX_VERSION_0, FULL_BOX_FLAGS_NONE, |v| {
write_smhd(v, cfg)
})?
@ -939,7 +944,9 @@ fn write_stsd(
"video/x-h264" | "video/x-h265" | "video/x-vp8" | "video/x-vp9" | "video/x-av1"
| "image/jpeg" => write_visual_sample_entry(v, cfg, stream)?,
"audio/mpeg" | "audio/x-opus" | "audio/x-flac" | "audio/x-alaw" | "audio/x-mulaw"
| "audio/x-adpcm" => write_audio_sample_entry(v, cfg, stream)?,
| "audio/x-adpcm" | "audio/x-ac3" | "audio/x-eac3" => {
write_audio_sample_entry(v, cfg, stream)?
}
"application/x-onvif-metadata" => write_xml_meta_data_sample_entry(v, cfg, stream)?,
_ => unreachable!(),
}
@ -1382,6 +1389,8 @@ fn write_audio_sample_entry(
_ => unreachable!(),
}
}
"audio/x-ac3" => b"ac-3",
"audio/x-eac3" => b"ec-3",
_ => unreachable!(),
};
@ -1442,6 +1451,16 @@ fn write_audio_sample_entry(
"audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
// Nothing to do here
}
"audio/x-ac3" => {
assert!(!stream.codec_specific_boxes.is_empty());
assert!(&stream.codec_specific_boxes[4..8] == b"dac3");
v.extend_from_slice(&stream.codec_specific_boxes);
}
"audio/x-eac3" => {
assert!(!stream.codec_specific_boxes.is_empty());
assert!(&stream.codec_specific_boxes[4..8] == b"dec3");
v.extend_from_slice(&stream.codec_specific_boxes);
}
_ => unreachable!(),
}
@ -2388,3 +2407,532 @@ pub(crate) fn create_mfra(
Ok(gst::Buffer::from_mut_slice(v))
}
/// Create AC-3 `dac3` box.
pub(crate) fn create_dac3(buffer: &gst::BufferRef) -> Result<Vec<u8>, Error> {
use bitstream_io::{BitRead as _, BitWrite as _};
let map = buffer
.map_readable()
.context("Mapping AC-3 buffer readable")?;
let mut reader = bitstream_io::BitReader::endian(
std::io::Cursor::new(map.as_slice()),
bitstream_io::BigEndian,
);
let header = reader
.parse::<ac3::Header>()
.context("Parsing AC-3 header")?;
let mut dac3 = Vec::with_capacity(11);
let mut writer = bitstream_io::BitWriter::endian(&mut dac3, bitstream_io::BigEndian);
writer
.build(&ac3::Dac3 { header })
.context("Writing dac3 box")?;
Ok(dac3)
}
/// Create EAC-3 `dec3` box.
pub(crate) fn create_dec3(buffer: &gst::BufferRef) -> Result<Vec<u8>, Error> {
use bitstream_io::{BitRead as _, BitWrite as _};
let map = buffer
.map_readable()
.context("Mapping EAC-3 buffer readable")?;
let mut slice = map.as_slice();
let mut headers = Vec::new();
while !slice.is_empty() {
let mut reader =
bitstream_io::BitReader::endian(std::io::Cursor::new(slice), bitstream_io::BigEndian);
let header = reader
.parse::<eac3::Header>()
.context("Parsing EAC-3 header")?;
let framesize = (header.bsi.frmsiz as usize + 1) * 2;
if slice.len() < framesize {
bail!("Incomplete EAC-3 frame");
}
headers.push(header);
slice = &slice[framesize..];
}
let mut dec3 = Vec::new();
let mut writer = bitstream_io::BitWriter::endian(&mut dec3, bitstream_io::BigEndian);
writer
.build(&eac3::Dec3 { headers })
.context("Writing dec3 box")?;
Ok(dec3)
}
mod ac3 {
use anyhow::{bail, Context, Error};
use bitstream_io::{FromBitStream, ToBitStream};
#[derive(Debug, Clone, Copy)]
pub struct Header {
syncinfo: SyncInfo,
bsi: Bsi,
}
impl FromBitStream for Header {
type Error = Error;
fn from_reader<R: bitstream_io::BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error>
where
Self: Sized,
{
let syncinfo = r.parse::<SyncInfo>().context("syncinfo")?;
let bsi = r.parse::<Bsi>().context("bsi")?;
Ok(Header { syncinfo, bsi })
}
}
#[derive(Debug, Clone, Copy)]
struct SyncInfo {
// skipping crc1
fscod: u8,
frmsizecod: u8,
}
impl FromBitStream for SyncInfo {
type Error = Error;
fn from_reader<R: bitstream_io::BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error>
where
Self: Sized,
{
let _syncword = r.read_to::<u16>().context("syncword")?;
if _syncword != 0x0b77 {
bail!("Invalid syncword");
}
r.skip(16).context("crc1")?;
let fscod = r.read::<2, u8>().context("fscod")?;
let frmsizecod = r.read::<6, u8>().context("frmsizecod")?;
Ok(SyncInfo { fscod, frmsizecod })
}
}
#[derive(Debug, Clone, Copy)]
struct Bsi {
bsid: u8,
bsmod: u8,
acmod: u8,
// skipping cmixlev, surmixlev, dsurmod
lfeon: bool,
}
impl FromBitStream for Bsi {
type Error = Error;
fn from_reader<R: bitstream_io::BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error>
where
Self: Sized,
{
let bsid = r.read::<5, u8>().context("bsid")?;
let bsmod = r.read::<3, u8>().context("bsmod")?;
let acmod = r.read::<3, u8>().context("acmod")?;
if acmod & 0x01 != 0 && acmod != 0x01 {
r.skip(2).context("cmixlev")?;
}
if acmod & 0x04 != 0 {
r.skip(2).context("surmixlev")?;
}
if acmod == 0x02 {
r.skip(2).context("dsurmod")?;
}
let lfeon = r.read_bit().context("lfeon")?;
Ok(Bsi {
bsid,
bsmod,
acmod,
lfeon,
})
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct Dac3 {
pub header: Header,
}
impl ToBitStream for Dac3 {
type Error = Error;
fn to_writer<W: bitstream_io::BitWrite + ?Sized>(
&self,
w: &mut W,
) -> Result<(), Self::Error>
where
Self: Sized,
{
w.write_from::<u32>(11).context("size")?;
w.write_bytes(b"dac3").context("type")?;
w.write::<2, u8>(self.header.syncinfo.fscod)
.context("fscod")?;
w.write::<5, u8>(self.header.bsi.bsid).context("bsid")?;
w.write::<3, u8>(self.header.bsi.bsmod).context("bsmod")?;
w.write::<3, u8>(self.header.bsi.acmod).context("acmod")?;
w.write_bit(self.header.bsi.lfeon).context("lfeon")?;
w.write::<5, u8>(self.header.syncinfo.frmsizecod >> 1)
.context("bit_rate_code")?;
w.write::<5, u8>(0).context("reserved")?;
assert!(w.byte_aligned());
Ok(())
}
}
}
mod eac3 {
use anyhow::{bail, Context, Error};
use bitstream_io::{FromBitStream, ToBitStream};
const NUM_BLOCKS: [u8; 4] = [1, 2, 3, 6];
const SAMPLE_RATES: [u16; 4] = [48000, 44100, 32000, 0];
#[derive(Debug, Clone, Copy)]
pub struct Header {
#[expect(unused)]
pub syncinfo: SyncInfo,
pub bsi: Bsi,
}
impl FromBitStream for Header {
type Error = Error;
fn from_reader<R: bitstream_io::BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error>
where
Self: Sized,
{
let syncinfo = r.parse::<SyncInfo>().context("syncinfo")?;
let bsi = r.parse::<Bsi>().context("bsi")?;
Ok(Header { syncinfo, bsi })
}
}
#[derive(Debug, Clone, Copy)]
pub struct SyncInfo {
// No fields for EAC-3
}
impl FromBitStream for SyncInfo {
type Error = Error;
fn from_reader<R: bitstream_io::BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error>
where
Self: Sized,
{
let _syncword = r.read_to::<u16>().context("syncword")?;
if _syncword != 0x0b77 {
bail!("Invalid syncword");
}
Ok(SyncInfo {})
}
}
#[derive(Debug, Clone, Copy)]
pub struct Bsi {
#[expect(unused)]
pub strmtyp: u8,
pub substreamid: u8,
pub frmsiz: u16,
pub fscod: u8,
pub fscod2: Option<u8>,
pub numblkscod: u8,
pub acmod: u8,
pub lfeon: bool,
pub bsid: u8,
// skipping dialnorm, compre, compr, dialnorm2, compr2e
pub chanmap: Option<u16>,
// skipping ...
pub bsmod: u8,
}
impl FromBitStream for Bsi {
type Error = Error;
fn from_reader<R: bitstream_io::BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error>
where
Self: Sized,
{
let strmtyp = r.read::<2, u8>().context("strmtyp")?;
let substreamid = r.read::<3, u8>().context("substreamid")?;
let frmsiz = r.read::<11, u16>().context("frmsiz")?;
let fscod = r.read::<2, u8>().context("fscod")?;
let fscod2;
let numblkscod;
if fscod == 0x3 {
fscod2 = Some(r.read::<2, u8>().context("fscod2")?);
numblkscod = 6;
} else {
fscod2 = None;
numblkscod = r.read::<2, u8>().context("numblkscod")?;
}
let number_of_blocks_per_sync_frame = NUM_BLOCKS[numblkscod as usize];
let acmod = r.read::<3, u8>().context("acmod")?;
let lfeon = r.read_bit().context("lfeon")?;
let bsid = r.read::<5, u8>().context("bsid")?;
r.skip(5).context("dialnorm")?;
let compre = r.read_bit().context("compre")?;
if compre {
r.skip(8).context("compr")?;
}
if acmod == 0x00 {
r.skip(5).context("dialnorm2")?;
let compr2e = r.read_bit().context("compr2e")?;
if compr2e {
r.skip(8).context("compr2")?;
}
}
let mut chanmap = None;
if strmtyp == 0x1 {
let chanmape = r.read_bit().context("chanmap2")?;
if chanmape {
chanmap = Some(r.read::<16, u16>().context("chanmap")?);
}
}
let mixmdate = r.read_bit().context("mixmdate")?;
if mixmdate {
if acmod > 0x2 {
r.skip(2).context("dmixmod")?;
}
if acmod & 0x1 != 0 && acmod > 0x2 {
r.skip(3).context("ltrtcmixlev")?;
r.skip(3).context("lorocmixlev")?;
}
if acmod & 0x4 != 0 {
r.skip(3).context("ltrtsurmixlev")?;
r.skip(3).context("lorosurmixlev")?;
}
if lfeon {
let lfemixlevcode = r.read_bit().context("lfemixlevcode")?;
if lfemixlevcode {
r.skip(5).context("lfemixlevcod")?;
}
}
if strmtyp == 0x0 {
let pgmscle = r.read_bit().context("pgmscle")?;
if pgmscle {
r.skip(6).context("pgmscl")?;
}
}
if acmod == 0x0 {
let pgmscl2e = r.read_bit().context("pgmscl2e")?;
if pgmscl2e {
r.skip(6).context("pgmscl2")?;
}
}
let extpgmscle = r.read_bit().context("extpgmscle")?;
if extpgmscle {
r.skip(6).context("extpgmscl")?;
}
let mixdef = r.read::<2, u8>().context("mixdef")?;
match mixdef {
0x0 => {}
0x1 => {
r.skip(1).context("premixcmpsel")?;
r.skip(1).context("drcsrc")?;
r.skip(3).context("premixcmpscl")?;
}
0x2 => {
r.skip(12).context("mixdata")?;
}
0x3 => {
let mixdeflen = r.read::<5, u8>().context("mixdeflen")?;
r.skip((mixdeflen as u32 + 2) * 8).context("mixdata")?;
}
_ => unreachable!(),
}
if acmod < 0x2 {
let paninfoe = r.read_bit().context("paninfoe")?;
if paninfoe {
r.skip(8).context("panmean")?;
r.skip(6).context("paninfo")?;
}
if acmod == 0x00 {
let paninfo2e = r.read_bit().context("paninfo2e")?;
if paninfo2e {
r.skip(8).context("panmean2")?;
r.skip(6).context("paninfo2")?;
}
}
}
let frmmixcfginfoe = r.read_bit().context("frmmixcfginfoe")?;
if frmmixcfginfoe {
if numblkscod == 0 {
r.skip(5).context("blkmixcfginfo")?;
} else {
for _ in 0..number_of_blocks_per_sync_frame {
let blkmixcfginfoe = r.read_bit().context("blkmixcfginfoe")?;
if blkmixcfginfoe {
r.skip(5).context("blkmixcfginfo")?;
}
}
}
}
}
let infomdate = r.read_bit().context("infomdate")?;
let mut bsmod = 0;
if infomdate {
bsmod = r.read::<3, u8>().context("bsmod")?;
}
Ok(Bsi {
strmtyp,
substreamid,
frmsiz,
fscod,
fscod2,
numblkscod,
acmod,
lfeon,
bsid,
chanmap,
bsmod,
})
}
}
#[derive(Debug)]
pub(crate) struct Dec3 {
pub headers: Vec<Header>,
}
impl ToBitStream for Dec3 {
type Error = Error;
fn to_writer<W: bitstream_io::BitWrite + ?Sized>(
&self,
w: &mut W,
) -> Result<(), Self::Error>
where
Self: Sized,
{
struct IndSub {
header: Header,
num_dep_sub: u8,
chan_loc: u16,
}
let mut num_ind_sub = 0;
let mut ind_subs = Vec::new();
// We assume the stream is well-formed and don't validate increasing
// substream ids and that each first substream of an id is an independent
// stream.
for substream in self
.headers
.chunk_by(|h1, h2| h1.bsi.substreamid == h2.bsi.substreamid)
{
num_ind_sub += 1;
let mut num_dep_sub = 0;
let independent_stream = substream[0];
let mut chan_loc = 0;
for dependent_stream in substream.iter().skip(1) {
num_dep_sub += 1;
chan_loc |= dependent_stream
.bsi
.chanmap
.map(|chanmap| (chanmap >> 5) & 0x1f)
.unwrap_or(independent_stream.bsi.acmod as u16);
}
ind_subs.push(IndSub {
header: independent_stream,
num_dep_sub,
chan_loc,
});
}
let len = 4
+ 4
+ 2
+ ind_subs
.iter()
.map(|s| 3 + if s.num_dep_sub > 0 { 1 } else { 0 })
.sum::<u32>();
w.write_from::<u32>(len).context("size")?;
w.write_bytes(b"dec3").context("type")?;
let data_rate = self
.headers
.iter()
.map(|header| {
((header.bsi.frmsiz as u32 + 1)
* if let Some(fscod2) = header.bsi.fscod2 {
SAMPLE_RATES[fscod2 as usize] as u32 / 2
} else {
SAMPLE_RATES[header.bsi.fscod as usize] as u32
})
/ NUM_BLOCKS[header.bsi.numblkscod as usize] as u32
})
.sum::<u32>();
w.write::<13, u16>((data_rate / 1000) as u16)
.context("data_rate")?;
w.write::<3, u8>(num_ind_sub).context("num_ind_sub")?;
for ind_sub in ind_subs {
w.write::<2, u8>(ind_sub.header.bsi.fscod)
.context("fscod")?;
w.write::<5, u8>(ind_sub.header.bsi.bsid).context("bsid")?;
w.write::<1, u8>(0).context("reserved")?;
w.write::<1, u8>(0).context("asvc")?;
w.write::<3, u8>(ind_sub.header.bsi.bsmod)
.context("bsmod")?;
w.write::<3, u8>(ind_sub.header.bsi.acmod)
.context("acmod")?;
w.write_bit(ind_sub.header.bsi.lfeon).context("lfeon")?;
w.write::<3, u8>(0).context("reserved")?;
w.write::<4, u8>(ind_sub.num_dep_sub)
.context("num_dep_sub")?;
if ind_sub.num_dep_sub > 0 {
w.write::<9, u16>(ind_sub.chan_loc).context("chan_loc")?;
} else {
w.write::<1, u8>(0).context("reserved")?;
}
}
w.byte_align().context("reserved")?;
Ok(())
}
}
}

View file

@ -273,8 +273,12 @@ struct Stream {
/// Mapping between running time and UTC time in ONVIF mode.
running_time_utc_time_mapping: Option<(gst::Signed<gst::ClockTime>, gst::ClockTime)>,
/// More data to be included in the fragmented stream header
extra_header_data: Option<Vec<u8>>,
/// Codec-specific boxes to be included in the sample entry
codec_specific_boxes: Vec<u8>,
/// Earliest PTS of the whole stream
earliest_pts: Option<gst::ClockTime>,
/// Current end PTS of the whole stream
@ -969,7 +973,9 @@ impl FMP4Mux {
]
.as_slice(),
"audio/mpeg" | "audio/x-opus" | "audio/x-flac" | "audio/x-alaw" | "audio/x-mulaw"
| "audio/x-adpcm" => ["channels", "rate", "layout", "bitrate", "codec_data"].as_slice(),
| "audio/x-ac3" | "audio/x-eac3" | "audio/x-adpcm" => {
["channels", "rate", "layout", "bitrate", "codec_data"].as_slice()
}
"application/x-onvif-metadata" => [].as_slice(),
_ => unreachable!(),
};
@ -3516,6 +3522,7 @@ impl FMP4Mux {
let mut delta_frames = DeltaFrames::IntraOnly;
let mut discard_header_buffers = false;
let mut codec_specific_boxes = Vec::new();
match s.name().as_str() {
"video/x-h264" | "video/x-h265" => {
if !s.has_field_with_type("codec_data", gst::Buffer::static_type()) {
@ -3571,6 +3578,46 @@ impl FMP4Mux {
return Err(gst::FlowError::NotNegotiated);
};
}
"audio/x-ac3" | "audio/x-eac3" => {
let Some(first_buffer) = pad.peek_buffer() else {
gst::error!(
CAT,
obj = pad,
"Need first buffer for AC-3 / EAC-3 when creating header"
);
return Err(gst::FlowError::NotNegotiated);
};
match s.name().as_str() {
"audio/x-ac3" => {
codec_specific_boxes = match boxes::create_dac3(&first_buffer) {
Ok(boxes) => boxes,
Err(err) => {
gst::error!(
CAT,
obj = pad,
"Failed to create AC-3 codec specific box: {err}"
);
return Err(gst::FlowError::NotNegotiated);
}
};
}
"audio/x-eac3" => {
codec_specific_boxes = match boxes::create_dec3(&first_buffer) {
Ok(boxes) => boxes,
Err(err) => {
gst::error!(
CAT,
obj = pad,
"Failed to create EAC-3 codec specific box: {err}"
);
return Err(gst::FlowError::NotNegotiated);
}
};
}
_ => unreachable!(),
}
}
"audio/x-alaw" | "audio/x-mulaw" => (),
"audio/x-adpcm" => (),
"application/x-onvif-metadata" => (),
@ -3593,6 +3640,7 @@ impl FMP4Mux {
current_position: gst::ClockTime::ZERO,
running_time_utc_time_mapping: None,
extra_header_data: None,
codec_specific_boxes,
earliest_pts: None,
end_pts: None,
language_code,
@ -3685,6 +3733,7 @@ impl FMP4Mux {
delta_frames: s.delta_frames,
caps: s.caps.clone(),
extra_header_data: s.extra_header_data.clone(),
codec_specific_boxes: s.codec_specific_boxes.clone(),
language_code: s.language_code,
orientation: s.orientation(),
max_bitrate: s.max_bitrate,
@ -4900,6 +4949,18 @@ impl ElementImpl for ISOFMP4Mux {
.field("channels", gst::IntRange::<i32>::new(1, 8))
.field("rate", gst::IntRange::<i32>::new(1, 10 * u16::MAX as i32))
.build(),
gst::Structure::builder("audio/x-ac3")
.field("framed", true)
.field("alignment", "frame")
.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-eac3")
.field("framed", true)
.field("alignment", "iec61937")
.field("channels", gst::IntRange::<i32>::new(1, u16::MAX as i32))
.field("rate", gst::IntRange::<i32>::new(1, i32::MAX))
.build(),
]
.into_iter()
.collect::<gst::Caps>(),
@ -5001,6 +5062,12 @@ impl ElementImpl for CMAFMux {
.field("channels", gst::IntRange::new(1i32, 8))
.field("rate", gst::IntRange::new(1, i32::MAX))
.build(),
gst::Structure::builder("audio/x-eac3")
.field("framed", true)
.field("alignment", "iec61937")
.field("channels", gst::IntRange::<i32>::new(1, u16::MAX as i32))
.field("rate", gst::IntRange::<i32>::new(1, i32::MAX))
.build(),
]
.into_iter()
.collect::<gst::Caps>(),
@ -5114,6 +5181,18 @@ impl ElementImpl for DASHMP4Mux {
.field("channels", gst::IntRange::new(1i32, 8))
.field("rate", gst::IntRange::new(1, i32::MAX))
.build(),
gst::Structure::builder("audio/x-ac3")
.field("framed", true)
.field("alignment", "frame")
.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-eac3")
.field("framed", true)
.field("alignment", "iec61937")
.field("channels", gst::IntRange::<i32>::new(1, u16::MAX as i32))
.field("rate", gst::IntRange::<i32>::new(1, i32::MAX))
.build(),
]
.into_iter()
.collect::<gst::Caps>(),

View file

@ -215,6 +215,9 @@ pub(crate) struct HeaderStream {
// More data to be included in the fragmented stream header
extra_header_data: Option<Vec<u8>>,
// Codec-specific boxes to be included in the sample entry
codec_specific_boxes: Vec<u8>,
// Tags meta for audio language and video orientation
language_code: Option<[u8; 3]>,
orientation: &'static TransformMatrix,