use alloc::format;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use core::marker::PhantomData;
use broadcast_common::{Package, Parse, Unpackage};
use crate::ac3::{Ac3SpecificBox, Ec3SpecificBox};
use crate::box_types::{BOX_HEADER_MIN_SIZE, parse_box};
use crate::dts::DtsSpecificBox;
use crate::error::{Error, Result};
use crate::flac::FlacSpecificBox;
use crate::hls::{MediaPlaylist, MediaSegment};
use crate::init_segment::{MovieBox, OpaqueBox, SampleEntryVariant, StblChild, TrackBox};
use crate::movie_fragment::MovieFragmentBox;
use crate::mp4esds::EsdsBox;
use crate::mpeg_legacy::{Mpeg2SeqHeader, MpegAudioFrameHeader, MpegAudioLayer};
use crate::mpegh::{MHAC_FOURCC, MHADecoderConfigurationRecord};
use crate::opus::OpusSpecificBox;
use crate::pipeline::{
CodecConfig, FragmentTrackData, Sample, TrackSpec, build_init_segment, build_media_segment,
};
const SAMPLE_FLAG_IS_NON_SYNC: u32 = 0x0001_0000;
const DEFAULT_MOVIE_TIMESCALE: u32 = 1000;
const OTI_MPEG2_AUDIO: u8 = 0x69;
const OTI_MPEG1_AUDIO: u8 = 0x6B;
#[derive(Debug, Clone)]
pub struct Track {
pub spec: TrackSpec,
pub samples: Vec<Sample>,
pub start_decode_time: u64,
pub encryption: Option<TrackEncryption>,
}
impl Track {
pub fn new(spec: TrackSpec, samples: Vec<Sample>) -> Self {
Self {
spec,
samples,
start_decode_time: 0,
encryption: None,
}
}
pub fn new_at(spec: TrackSpec, samples: Vec<Sample>, start_decode_time: u64) -> Self {
Self {
spec,
samples,
start_decode_time,
encryption: None,
}
}
pub fn with_start_decode_time(mut self, start_decode_time: u64) -> Self {
self.start_decode_time = start_decode_time;
self
}
pub fn track_id(&self) -> u32 {
self.spec.track_id
}
pub fn timescale(&self) -> u32 {
self.spec.timescale
}
pub fn config(&self) -> &CodecConfig {
&self.spec.config
}
}
#[derive(Debug, Clone)]
pub struct TrackEncryption {
pub scheme: crate::cenc::CencScheme,
pub tenc: crate::cenc::TrackEncryptionBox,
pub samples: Vec<crate::cenc::SampleEncryptionEntry>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PcrSample {
pub pcr_27mhz: u64,
pub pid: u16,
pub packet_index: u64,
pub discontinuity: bool,
}
#[derive(Debug, Clone)]
pub struct Media {
pub tracks: Vec<Track>,
pub movie_timescale: u32,
pub pcr: Vec<PcrSample>,
}
impl Media {
pub fn new(tracks: Vec<Track>, movie_timescale: u32) -> Self {
Self {
tracks,
movie_timescale,
pcr: Vec::new(),
}
}
pub fn with_pcr(mut self, pcr: Vec<PcrSample>) -> Self {
self.pcr = pcr;
self
}
}
#[derive(Debug, Default, Clone)]
pub struct Fmp4Demux<'a> {
_marker: PhantomData<&'a [u8]>,
}
impl Fmp4Demux<'_> {
pub fn new() -> Self {
Self {
_marker: PhantomData,
}
}
}
struct TrackBuilder {
spec: TrackSpec,
samples: Vec<Sample>,
start_decode_time: Option<u64>,
}
impl<'a> Unpackage for Fmp4Demux<'a> {
type Input = &'a [u8];
type Media = Media;
type Error = Error;
fn unpackage(&mut self, input: &'a [u8]) -> Result<Media> {
let moov_bytes =
find_top_box(input, b"moov").ok_or(Error::UnexpectedBox { expected: "moov" })?;
let moov = MovieBox::parse(moov_bytes)?;
let movie_timescale = moov.mvhd.timescale;
let mut builders: Vec<TrackBuilder> = Vec::with_capacity(moov.tracks.len());
for trak in &moov.tracks {
if let Ok(spec) = track_spec_from_trak(trak) {
builders.push(TrackBuilder {
spec,
samples: Vec::new(),
start_decode_time: None,
});
}
}
let mut offset = 0usize;
let mut pending_moof: Option<(usize, MovieFragmentBox)> = None;
while offset + BOX_HEADER_MIN_SIZE <= input.len() {
let (bx, consumed) = parse_box(&input[offset..])?;
let ty = bx.header.box_type.0;
if &ty == b"moof" {
let moof = MovieFragmentBox::parse_body(bx.body)?;
pending_moof = Some((offset, moof));
} else if &ty == b"mdat" {
if let Some((moof_off, moof)) = pending_moof.take() {
absorb_fragment(input, moof_off, &moof, &mut builders)?;
}
}
if consumed == 0 {
break;
}
offset += consumed;
}
let tracks = builders
.into_iter()
.map(|mut b| {
refine_legacy_config(&mut b.spec.config, &b.samples);
Track {
spec: b.spec,
samples: b.samples,
start_decode_time: b.start_decode_time.unwrap_or(0),
encryption: None,
}
})
.collect();
Ok(Media {
tracks,
movie_timescale,
pcr: Vec::new(),
})
}
}
fn absorb_fragment(
file: &[u8],
moof_off: usize,
moof: &MovieFragmentBox,
builders: &mut [TrackBuilder],
) -> Result<()> {
for traf in &moof.traf {
let tfhd = &traf.tfhd;
let Some(builder) = builders
.iter_mut()
.find(|b| b.spec.track_id == tfhd.track_id)
else {
continue;
};
if builder.start_decode_time.is_none() {
if let Some(tfdt) = &traf.tfdt {
builder.start_decode_time = Some(tfdt.base_media_decode_time());
}
}
for trun in &traf.trun {
let base = moof_off as i64 + trun.data_offset.unwrap_or(0) as i64;
let mut cursor = base;
for (i, ts) in trun.samples.iter().enumerate() {
let size = ts
.sample_size
.or(tfhd.default_sample_size)
.ok_or(Error::InvalidInput(
"trun sample has no size (no trun.sample_size, no tfhd default_sample_size)",
))? as usize;
let duration = ts
.sample_duration
.or(tfhd.default_sample_duration)
.unwrap_or(0);
let flags = ts
.sample_flags
.or(if i == 0 {
trun.first_sample_flags
} else {
None
})
.or(tfhd.default_sample_flags)
.unwrap_or(0);
let is_sync = flags & SAMPLE_FLAG_IS_NON_SYNC == 0;
let composition_offset = ts.sample_composition_time_offset.unwrap_or(0);
let start = usize::try_from(cursor)
.map_err(|_| Error::InvalidInput("negative sample data offset"))?;
let end = start + size;
if end > file.len() {
return Err(Error::BufferTooShort {
need: end,
have: file.len(),
what: "fragment sample data",
});
}
builder.samples.push(Sample {
data: file[start..end].to_vec(),
duration,
is_sync,
composition_offset,
source_timing: None,
});
cursor += size as i64;
}
}
}
Ok(())
}
#[derive(Debug, Clone)]
pub struct CmafMux {
pub sequence_number: u32,
}
impl Default for CmafMux {
fn default() -> Self {
Self { sequence_number: 1 }
}
}
impl CmafMux {
pub fn new(sequence_number: u32) -> Self {
Self { sequence_number }
}
}
impl Package for CmafMux {
type Media = Media;
type Output = Vec<u8>;
type Error = Error;
fn package(&mut self, media: &Media) -> Result<Vec<u8>> {
if media.tracks.is_empty() {
return Err(Error::InvalidInput("cannot package a Media with no tracks"));
}
let filtered;
let media: &Media = if media.tracks.iter().any(|t| t.spec.config.is_opaque_data()) {
filtered = media.select_tracks_by(|t| !t.spec.config.is_opaque_data())?;
&filtered
} else {
media
};
let specs: Vec<TrackSpec> = media.tracks.iter().map(|t| t.spec.clone()).collect();
let movie_timescale = if media.movie_timescale == 0 {
DEFAULT_MOVIE_TIMESCALE
} else {
media.movie_timescale
};
let mut out = build_init_segment(&specs, movie_timescale)?;
let fragments: Vec<FragmentTrackData<'_>> = media
.tracks
.iter()
.map(|t| FragmentTrackData {
track_id: t.spec.track_id,
base_media_decode_time: t.start_decode_time,
samples: &t.samples,
})
.collect();
let media_seg = build_media_segment(self.sequence_number, &fragments)?;
out.extend_from_slice(&media_seg);
Ok(out)
}
}
#[derive(Debug, Clone)]
pub struct HlsPackager {
pub version: u8,
pub media_sequence: u64,
pub uri_prefix: String,
}
impl Default for HlsPackager {
fn default() -> Self {
Self {
version: 7,
media_sequence: 0,
uri_prefix: String::from("seg"),
}
}
}
impl Package for HlsPackager {
type Media = Media;
type Output = String;
type Error = Error;
fn package(&mut self, media: &Media) -> Result<String> {
if media.tracks.is_empty() {
return Err(Error::InvalidInput("cannot package a Media with no tracks"));
}
let mut segments = Vec::with_capacity(media.tracks.len());
let mut target_secs = 0u32;
for t in &media.tracks {
let ticks: u64 = t.samples.iter().map(|s| s.duration as u64).sum();
let ts = if t.spec.timescale == 0 {
1
} else {
t.spec.timescale
} as u64;
let ceil_secs = ticks.div_ceil(ts) as u32;
if ceil_secs > target_secs {
target_secs = ceil_secs;
}
segments.push(MediaSegment {
uri: format!("{}{}.m4s", self.uri_prefix, t.spec.track_id),
duration: ticks as f64 / ts as f64,
discontinuous: false,
parts: vec![],
});
}
let playlist = MediaPlaylist {
version: self.version,
target_duration: target_secs,
media_sequence: self.media_sequence,
discontinuity_sequence: 0,
segments,
endlist: true,
extra_tags: vec![],
low_latency: None,
iframes_only: false,
};
Ok(playlist.to_m3u8())
}
}
pub(crate) fn find_top_box<'a>(data: &'a [u8], fourcc: &[u8; 4]) -> Option<&'a [u8]> {
let mut offset = 0usize;
while offset + BOX_HEADER_MIN_SIZE <= data.len() {
let (bx, consumed) = parse_box(&data[offset..]).ok()?;
if &bx.header.box_type.0 == fourcc {
let end = if bx.header.size == 0 {
data.len()
} else {
offset + bx.header.size as usize
};
return Some(&data[offset..end]);
}
if consumed == 0 {
break;
}
offset += consumed;
}
None
}
pub(crate) fn track_spec_from_trak(trak: &TrackBox) -> Result<TrackSpec> {
let track_id = trak.tkhd.track_id;
let mdia = trak
.mdia
.as_ref()
.ok_or(Error::UnexpectedBox { expected: "mdia" })?;
let timescale = mdia
.mdhd
.as_ref()
.ok_or(Error::UnexpectedBox { expected: "mdhd" })?
.timescale;
let minf = mdia
.minf
.as_ref()
.ok_or(Error::UnexpectedBox { expected: "minf" })?;
let stbl = minf
.stbl
.as_ref()
.ok_or(Error::UnexpectedBox { expected: "stbl" })?;
let stsd = stbl
.children
.iter()
.find_map(|c| match c {
StblChild::Stsd(s) => Some(s),
_ => None,
})
.ok_or(Error::UnexpectedBox { expected: "stsd" })?;
let entry = stsd.entries.first().ok_or(Error::UnexpectedBox {
expected: "stsd entry",
})?;
let config = codec_config_from_entry(entry)?;
Ok(TrackSpec::new(track_id, timescale, config))
}
fn codec_config_from_entry(entry: &SampleEntryVariant) -> Result<CodecConfig> {
match entry {
SampleEntryVariant::Avc1(avc) => Ok(CodecConfig::Avc {
config: avc.config.clone(),
width: avc.visual.width,
height: avc.visual.height,
}),
SampleEntryVariant::Hevc1(hevc) => Ok(CodecConfig::Hevc {
config: hevc.config.clone(),
width: hevc.visual.width,
height: hevc.visual.height,
}),
SampleEntryVariant::Vvc(vvc) => {
let (width, height) = vvc
.config
.config
.dimensions()
.unwrap_or((vvc.visual.width, vvc.visual.height));
Ok(CodecConfig::Vvc {
config: vvc.config.clone(),
width,
height,
})
}
SampleEntryVariant::Av01(av1) => Ok(CodecConfig::Av1 {
config: av1.config.clone(),
width: av1.visual.width,
height: av1.visual.height,
}),
SampleEntryVariant::Vp09(vp9) => Ok(CodecConfig::Vp9 {
config: vp9.config.clone(),
width: vp9.visual.width,
height: vp9.visual.height,
}),
SampleEntryVariant::Mp4v(mp4v) => {
let esds = EsdsBox::parse_body(config_box_body(&mp4v.config_boxes, b"esds")?)?;
Ok(CodecConfig::Mpeg2Video {
esds,
width: mp4v.visual.width,
height: mp4v.visual.height,
})
}
SampleEntryVariant::Mp4a(mp4a) => {
let esds = esds_from_config_boxes(&mp4a.config_boxes)?;
let oti = esds
.es_descriptor
.decoder_config
.as_ref()
.map(|dc| dc.object_type_indication.0);
if oti == Some(OTI_MPEG2_AUDIO) || oti == Some(OTI_MPEG1_AUDIO) {
Ok(CodecConfig::MpegAudio {
esds,
layer: MpegAudioLayer::LayerII,
channel_count: mp4a.channelcount,
sample_rate: mp4a.samplerate >> 16,
sample_size: mp4a.samplesize,
})
} else {
Ok(CodecConfig::Aac {
esds,
channel_count: mp4a.channelcount,
sample_rate: mp4a.samplerate >> 16,
sample_size: mp4a.samplesize,
})
}
}
SampleEntryVariant::Ac3(ac3) => {
let config = Ac3SpecificBox::parse(config_box_body(&ac3.config_boxes, b"dac3")?)?;
Ok(CodecConfig::Ac3 {
config,
channel_count: ac3.channelcount,
sample_rate: ac3.samplerate >> 16,
sample_size: ac3.samplesize,
})
}
SampleEntryVariant::Ec3(ec3) => {
let config = Ec3SpecificBox::parse(config_box_body(&ec3.config_boxes, b"dec3")?)?;
Ok(CodecConfig::Eac3 {
config,
channel_count: ec3.channelcount,
sample_rate: ec3.samplerate >> 16,
sample_size: ec3.samplesize,
})
}
SampleEntryVariant::Opus(opus) => {
let config = OpusSpecificBox::parse(config_box_body(&opus.config_boxes, b"dOps")?)?;
Ok(CodecConfig::Opus {
config,
channel_count: opus.channelcount,
sample_rate: opus.samplerate >> 16,
sample_size: opus.samplesize,
})
}
SampleEntryVariant::Flac(flac) => {
let config = FlacSpecificBox::parse(config_box_body(&flac.config_boxes, b"dfLa")?)?;
Ok(CodecConfig::Flac {
config,
channel_count: flac.channelcount,
sample_rate: flac.samplerate >> 16,
sample_size: flac.samplesize,
})
}
SampleEntryVariant::Dts(dts) => {
let config = DtsSpecificBox::parse(config_box_body(&dts.config_boxes, b"ddts")?)?;
Ok(CodecConfig::Dts {
config,
codec_fourcc: dts.codec_type,
channel_count: dts.channelcount,
sample_rate: dts.samplerate >> 16,
sample_size: dts.samplesize,
})
}
SampleEntryVariant::Mha(mha) => {
let config = MHADecoderConfigurationRecord::parse(config_box_body(
&mha.config_boxes,
&MHAC_FOURCC,
)?)?;
Ok(CodecConfig::MpegH {
config,
channel_count: mha.channelcount,
sample_rate: mha.samplerate >> 16,
sample_size: mha.samplesize,
})
}
SampleEntryVariant::Ac4(_)
| SampleEntryVariant::Stpp(_)
| SampleEntryVariant::Wvtt(_)
| SampleEntryVariant::Unknown(_) => Err(Error::UnexpectedBox {
expected: "a codec sample entry transmux can reconstruct (avc1/hvc1/vvc1/mp4v/av01/vp09/mp4a/ac-3/ec-3/Opus/fLaC/dts*/mha*)",
}),
}
}
pub(crate) fn refine_legacy_config(config: &mut CodecConfig, samples: &[Sample]) {
let Some(first) = samples.first() else {
return;
};
match config {
CodecConfig::Mpeg2Video { width, height, .. } => {
if let Ok(sh) = Mpeg2SeqHeader::find(&first.data) {
*width = sh.width;
*height = sh.height;
}
}
CodecConfig::MpegAudio { layer, .. } => {
if let Ok(hdr) = MpegAudioFrameHeader::parse(&first.data) {
*layer = hdr.layer;
}
}
_ => {}
}
}
fn esds_from_config_boxes(boxes: &[OpaqueBox]) -> Result<EsdsBox> {
EsdsBox::parse_body(config_box_body(boxes, b"esds")?)
}
fn config_box_body<'b>(boxes: &'b [OpaqueBox], fourcc: &[u8; 4]) -> Result<&'b [u8]> {
boxes
.iter()
.find(|b| &b.box_type == fourcc)
.map(|b| b.data.as_slice())
.ok_or(Error::UnexpectedBox {
expected: "config box in audio sample entry",
})
}