fmp4mux: Create FLAC dfLa box when receiving the caps too

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/2208>
This commit is contained in:
Sebastian Dröge 2025-04-21 11:01:53 +03:00 committed by GStreamer Marge Bot
parent 03e9a9f0fe
commit 3e5da7a783
2 changed files with 171 additions and 42 deletions

View file

@ -1399,10 +1399,11 @@ fn write_audio_sample_entry(
let bitrate = s.get::<i32>("bitrate").context("no ADPCM bitrate field")?;
(bitrate / 8000) as u16
}
"audio/x-flac" => with_flac_metadata(&stream.caps, |streaminfo, _| {
1 + ((u16::from_be_bytes([streaminfo[16], streaminfo[17]]) >> 4) & 0b11111)
})
.context("FLAC metadata error")?,
"audio/x-flac" => {
let (streamheader, _headers) =
flac::parse_stream_header(&stream.caps).context("FLAC streamheader")?;
streamheader.stream_info.bits_per_sample as u16
}
_ => 16u16,
};
@ -1446,7 +1447,9 @@ fn write_audio_sample_entry(
write_dops(v, &stream.caps)?;
}
"audio/x-flac" => {
write_dfla(v, &stream.caps)?;
assert!(!stream.codec_specific_boxes.is_empty());
assert!(&stream.codec_specific_boxes[4..8] == b"dfLa");
v.extend_from_slice(&stream.codec_specific_boxes);
}
"audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
// Nothing to do here
@ -1673,35 +1676,6 @@ fn write_dops(v: &mut Vec<u8>, caps: &gst::Caps) -> Result<(), Error> {
})
}
fn with_flac_metadata<R>(
caps: &gst::Caps,
cb: impl FnOnce(&[u8], &[gst::glib::SendValue]) -> R,
) -> Result<R, Error> {
let caps = caps.structure(0).unwrap();
let header = caps.get::<gst::ArrayRef>("streamheader").unwrap();
let (streaminfo, remainder) = header.as_ref().split_first().unwrap();
let streaminfo = streaminfo.get::<&gst::BufferRef>().unwrap();
let streaminfo = streaminfo.map_readable().unwrap();
// 13 bytes for the Ogg/FLAC prefix and 38 for the streaminfo itself.
match <&[_; 13 + 38]>::try_from(streaminfo.as_slice()) {
Ok(i) if i.starts_with(b"\x7FFLAC\x01\x00") => Ok(cb(&i[13..], remainder)),
Ok(_) | Err(_) => bail!("Unknown streamheader format"),
}
}
fn write_dfla(v: &mut Vec<u8>, caps: &gst::Caps) -> Result<(), Error> {
write_full_box(v, b"dfLa", 0, 0, move |v| {
with_flac_metadata(caps, |streaminfo, remainder| {
v.extend(streaminfo);
for metadata in remainder {
let metadata = metadata.get::<&gst::BufferRef>().unwrap();
let metadata = metadata.map_readable().unwrap();
v.extend(&metadata[..]);
}
})
})
}
fn write_xml_meta_data_sample_entry(
v: &mut Vec<u8>,
_cfg: &super::HeaderConfiguration,
@ -2408,6 +2382,158 @@ pub(crate) fn create_mfra(
Ok(gst::Buffer::from_mut_slice(v))
}
/// Create FLAC `dfLa` box.
pub(crate) fn write_dfla(caps: &gst::CapsRef) -> Result<Vec<u8>, Error> {
let mut dfla = Vec::new();
let (_streamheader, headers) = flac::parse_stream_header(caps).context("FLAC streamheader")?;
write_full_box(&mut dfla, b"dfLa", 0, 0, move |v| {
for header in headers {
let map = header.map_readable().unwrap();
v.extend(&map[..]);
}
Ok(())
})?;
Ok(dfla)
}
mod flac {
use anyhow::{bail, Context as _, Error};
use bitstream_io::FromBitStream;
#[allow(unused)]
#[derive(Debug, Clone)]
pub(crate) struct StreamHeader {
pub mapping_major_version: u8,
pub mapping_minor_version: u8,
pub num_headers: u16,
pub stream_info: StreamInfo,
}
impl FromBitStream for StreamHeader {
type Error = anyhow::Error;
fn from_reader<R: bitstream_io::BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error>
where
Self: Sized,
{
let packet_type = r.read_to::<u8>().context("packet_type")?;
if packet_type != 0x7f {
bail!("Invalid packet type");
}
let signature = r.read_to::<[u8; 4]>().context("signature")?;
if &signature != b"FLAC" {
bail!("Invalid FLAC signature");
}
let mapping_major_version = r.read_to::<u8>().context("mapping_major_version")?;
let mapping_minor_version = r.read_to::<u8>().context("mapping_minor_version")?;
let num_headers = r.read_to::<u16>().context("num_headers")?;
let signature = r.read_to::<[u8; 4]>().context("signature")?;
if &signature != b"fLaC" {
bail!("Invalid fLaC signature");
}
let stream_info = r.parse::<StreamInfo>().context("stream_info")?;
Ok(StreamHeader {
mapping_major_version,
mapping_minor_version,
num_headers,
stream_info,
})
}
}
#[allow(unused)]
#[derive(Debug, Clone)]
pub(crate) struct StreamInfo {
pub min_block_size: u16,
pub max_block_size: u16,
pub min_frame_size: u32,
pub max_frame_size: u32,
pub sample_rate: u32,
pub num_channels: u8,
pub bits_per_sample: u8,
pub num_samples: u64,
pub md5: [u8; 16],
}
impl FromBitStream for StreamInfo {
type Error = anyhow::Error;
fn from_reader<R: bitstream_io::BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error>
where
Self: Sized,
{
let _is_last = r.read_bit().context("is_last")?;
let metadata_block_type = r.read::<7, u8>().context("metadata_block_type")?;
if metadata_block_type != 0 {
bail!("Invalid metadata block type {metadata_block_type}");
}
let _metadata_block_size = r.read::<24, u32>().context("metadata_block_size")?;
let min_block_size = r.read_to::<u16>().context("min_block_size")?;
let max_block_size = r.read_to::<u16>().context("max_block_size")?;
let min_frame_size = r.read::<24, u32>().context("min_frame_size")?;
let max_frame_size = r.read::<24, u32>().context("max_frame_size")?;
let sample_rate = r.read::<20, u32>().context("sample_rate")?;
let num_channels = r.read::<3, u8>().context("num_channels")? + 1;
let bits_per_sample = r.read::<5, u8>().context("bits_per_sample")? + 1;
let num_samples = r.read::<36, u64>().context("num_samples")?;
let md5 = r.read_to::<[u8; 16]>().context("md5")?;
Ok(StreamInfo {
min_block_size,
max_block_size,
min_frame_size,
max_frame_size,
sample_rate,
num_channels,
bits_per_sample,
num_samples,
md5,
})
}
}
pub(crate) fn parse_stream_header(
caps: &gst::CapsRef,
) -> Result<(StreamHeader, Vec<gst::Buffer>), Error> {
use bitstream_io::BitRead as _;
let s = caps.structure(0).unwrap();
let Ok(streamheader) = s.get::<gst::ArrayRef>("streamheader") else {
bail!("Need streamheader in caps for FLAC");
};
let Some((streaminfo, remainder)) = streamheader.as_ref().split_first() else {
bail!("Empty FLAC streamheader");
};
let streaminfo = streaminfo.get::<&gst::Buffer>().unwrap();
let map = streaminfo.map_readable().unwrap();
let mut reader = bitstream_io::BitReader::endian(
std::io::Cursor::new(map.as_slice()),
bitstream_io::BigEndian,
);
let header = reader
.parse::<StreamHeader>()
.context("Parsing FLAC streamheader")?;
Ok((
header,
std::iter::once(gst::Buffer::from_mut_slice(Vec::from(&map[13..])))
.chain(remainder.iter().map(|v| v.get::<gst::Buffer>().unwrap()))
.collect::<Vec<_>>(),
))
}
}
/// Create AC-3 `dac3` box.
pub(crate) fn create_dac3(buffer: &gst::BufferRef) -> Result<Vec<u8>, Error> {
use bitstream_io::{BitRead as _, BitWrite as _};

View file

@ -3568,14 +3568,17 @@ impl FMP4Mux {
}
"audio/x-flac" => {
discard_header_buffers = true;
if let Err(e) = s.get::<gst::ArrayRef>("streamheader") {
gst::error!(
CAT,
obj = pad,
"Muxing FLAC into MP4 needs streamheader: {}",
e
);
return Err(gst::FlowError::NotNegotiated);
codec_specific_boxes = match boxes::write_dfla(&caps) {
Ok(boxes) => boxes,
Err(err) => {
gst::error!(
CAT,
obj = pad,
"Failed to create FLAC codec specific box: {err}"
);
return Err(gst::FlowError::NotNegotiated);
}
};
}
"audio/x-ac3" | "audio/x-eac3" => {