diff --git a/mux/fmp4/src/fmp4mux/boxes.rs b/mux/fmp4/src/fmp4mux/boxes.rs index c67649543..09eace653 100644 --- a/mux/fmp4/src/fmp4mux/boxes.rs +++ b/mux/fmp4/src/fmp4mux/boxes.rs @@ -1399,10 +1399,11 @@ fn write_audio_sample_entry( let bitrate = s.get::("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, caps: &gst::Caps) -> Result<(), Error> { }) } -fn with_flac_metadata( - caps: &gst::Caps, - cb: impl FnOnce(&[u8], &[gst::glib::SendValue]) -> R, -) -> Result { - let caps = caps.structure(0).unwrap(); - let header = caps.get::("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, 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, _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, 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: &mut R) -> Result + where + Self: Sized, + { + let packet_type = r.read_to::().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::().context("mapping_major_version")?; + let mapping_minor_version = r.read_to::().context("mapping_minor_version")?; + let num_headers = r.read_to::().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::().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: &mut R) -> Result + 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::().context("min_block_size")?; + let max_block_size = r.read_to::().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), Error> { + use bitstream_io::BitRead as _; + + let s = caps.structure(0).unwrap(); + let Ok(streamheader) = s.get::("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::() + .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::().unwrap())) + .collect::>(), + )) + } +} + /// Create AC-3 `dac3` box. pub(crate) fn create_dac3(buffer: &gst::BufferRef) -> Result, Error> { use bitstream_io::{BitRead as _, BitWrite as _}; diff --git a/mux/fmp4/src/fmp4mux/imp.rs b/mux/fmp4/src/fmp4mux/imp.rs index eb2c247bc..759ba80d5 100644 --- a/mux/fmp4/src/fmp4mux/imp.rs +++ b/mux/fmp4/src/fmp4mux/imp.rs @@ -3568,14 +3568,17 @@ impl FMP4Mux { } "audio/x-flac" => { discard_header_buffers = true; - if let Err(e) = s.get::("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" => {