use bytes::BytesMut;
use mp4_atom::{Any, AsyncReadFrom, Atom, Decode, Esds, Moof, Moov, Trak};
use std::collections::HashMap;
use tokio::io::AsyncRead;
use super::{util, Error, Result};
use crate::{catalog, media};
pub struct Import {
buffer: BytesMut,
broadcast: media::BroadcastProducer,
tracks: HashMap<u32, media::TrackProducer>,
moov: Option<Moov>,
moof: Option<Moof>,
}
impl Import {
pub fn new(broadcast: moq_transfork::BroadcastProducer) -> Self {
Self {
buffer: BytesMut::new(),
broadcast: media::BroadcastProducer::new(broadcast),
tracks: HashMap::default(),
moov: None,
moof: None,
}
}
pub fn parse(&mut self, data: &[u8]) -> Result<()> {
self.buffer.extend_from_slice(data);
let mut buffer = std::mem::replace(&mut self.buffer, BytesMut::new()).freeze();
while let Some(atom) = Option::<mp4_atom::Any>::decode(&mut buffer)? {
self.process(atom)?;
}
self.buffer = buffer.try_into_mut().unwrap();
Ok(())
}
fn init(&mut self, moov: Moov) -> Result<()> {
for trak in &moov.trak {
let track_id = trak.tkhd.track_id;
let handler = &trak.mdia.hdlr.handler_type;
let track = match handler.as_ref() {
b"vide" => {
let track = Self::init_video(trak)?;
self.broadcast.create_video(track)?
}
b"soun" => {
let track = Self::init_audio(trak)?;
self.broadcast.create_audio(track)?
}
b"sbtl" => return Err(Error::UnsupportedTrack("subtitle")),
_ => return Err(Error::UnsupportedTrack("unknown")),
};
self.tracks.insert(track_id, track);
}
self.broadcast.publish()?;
self.moov = Some(moov);
Ok(())
}
fn init_video(trak: &Trak) -> Result<catalog::Video> {
let name = trak.tkhd.track_id.to_string();
let stsd = &trak.mdia.minf.stbl.stsd;
let timescale = trak.mdia.mdhd.timescale;
let track = if let Some(avc1) = &stsd.avc1 {
let avcc = &avc1.avcc;
let mut description = BytesMut::new();
avcc.encode_body(&mut description)?;
catalog::Video {
track: moq_transfork::Track::build(name).priority(2).into(),
resolution: catalog::Dimensions {
width: avc1.width,
height: avc1.height,
},
codec: catalog::H264 {
profile: avcc.avc_profile_indication,
constraints: avcc.profile_compatibility,
level: avcc.avc_level_indication,
}
.into(),
description: description.freeze(),
timescale,
layers: vec![],
bitrate: None,
}
} else if let Some(hev1) = &stsd.hev1 {
let _hvcc = &hev1.hvcc;
return Err(Error::UnsupportedCodec("HEVC"));
} else if let Some(vp09) = &stsd.vp09 {
let vpcc = &vp09.vpcc;
catalog::Video {
track: moq_transfork::Track::build(name).priority(2).into(),
codec: catalog::VP9 {
profile: vpcc.profile,
level: vpcc.level,
bit_depth: vpcc.bit_depth,
chroma_subsampling: vpcc.chroma_subsampling,
color_primaries: vpcc.color_primaries,
transfer_characteristics: vpcc.transfer_characteristics,
matrix_coefficients: vpcc.matrix_coefficients,
full_range: vpcc.video_full_range_flag,
}
.into(),
timescale,
description: Default::default(),
resolution: catalog::Dimensions {
width: vp09.width,
height: vp09.height,
},
layers: vec![],
bitrate: None,
}
} else {
return Err(Error::UnsupportedCodec("unknown"));
};
Ok(track)
}
fn init_audio(trak: &Trak) -> Result<catalog::Audio> {
let name = trak.tkhd.track_id.to_string();
let stsd = &trak.mdia.minf.stbl.stsd;
let timescale = trak.mdia.mdhd.timescale;
let track = if let Some(mp4a) = &stsd.mp4a {
let desc = &mp4a
.esds
.as_ref()
.ok_or(Error::MissingBox(Esds::KIND))?
.es_desc
.dec_config;
if desc.object_type_indication != 0x40 {
return Err(Error::UnsupportedCodec("MPEG2"));
}
catalog::Audio {
track: moq_transfork::Track::build(name).priority(1).into(),
codec: catalog::AAC {
profile: desc.dec_specific.profile,
}
.into(),
timescale,
sample_rate: mp4a.samplerate.integer(),
channel_count: mp4a.channelcount,
bitrate: Some(std::cmp::max(desc.avg_bitrate, desc.max_bitrate)),
}
} else {
return Err(Error::UnsupportedCodec("unknown"));
};
Ok(track)
}
pub async fn init_from<T: AsyncRead + Unpin>(&mut self, input: &mut T) -> Result<()> {
let _ftyp = mp4_atom::Ftyp::read_from(input).await?;
let moov = Moov::read_from(input).await?;
self.init(moov)
}
pub async fn read_from<T: AsyncRead + Unpin>(&mut self, input: &mut T) -> Result<()> {
while let Some(atom) = Option::<mp4_atom::Any>::read_from(input).await? {
self.process(atom)?;
}
Ok(())
}
fn process(&mut self, atom: mp4_atom::Any) -> Result<()> {
match atom {
Any::Ftyp(_) => {
}
Any::Moov(moov) => {
self.init(moov)?;
}
Any::Moof(moof) => {
let track_id = util::frame_track_id(&moof)?;
let keyframe = util::frame_is_key(&moof);
if keyframe {
let moov = self.moov.as_ref().ok_or(Error::MissingBox(Moov::KIND))?;
let trak = moov
.trak
.iter()
.find(|trak| trak.tkhd.track_id == track_id)
.ok_or(Error::UnknownTrack)?;
if trak.mdia.hdlr.handler_type == b"vide".into() {
for track in self.tracks.values_mut() {
track.keyframe();
}
}
}
if self.moof.is_some() {
return Err(Error::DuplicateBox(Moof::KIND));
}
self.moof = Some(moof);
}
Any::Mdat(mdat) => {
let moof = self.moof.take().ok_or(Error::MissingBox(Moof::KIND))?;
let track_id = util::frame_track_id(&moof)?;
let timestamp = util::frame_timestamp(&moof)?;
let track = self.tracks.get_mut(&track_id).ok_or(Error::UnknownTrack)?;
track.write(timestamp, mdat.data);
}
_ => {
}
};
Ok(())
}
pub fn catalog(&self) -> &catalog::Broadcast {
self.broadcast.catalog()
}
}