diff --git a/src/muxer/hls/mp4/atoms.rs b/src/muxer/hls/mp4/atoms.rs index c62f1a1..ccc9148 100644 --- a/src/muxer/hls/mp4/atoms.rs +++ b/src/muxer/hls/mp4/atoms.rs @@ -1,3 +1,9 @@ +trait MP4Atom { + fn size(&self) -> u32; + + fn marshall(&self) -> Vec; +} + struct WindowMatrix { a: u32, b: u32, @@ -16,7 +22,7 @@ struct Edits { play_rate: u32, } -struct MOOV { +pub struct MOOV { mvhd: MVHD, traks: [TRAK; 2], mvex: MVEX, @@ -150,7 +156,7 @@ struct URN { } struct STBL { - stsd: Vec, + stsd: STSD, stts: STTS, stsc: STSC, stsz: STSZ, @@ -158,11 +164,47 @@ struct STBL { opts: Vec>, } +pub struct STSD { + pub version: u8, + pub flags: u32, + pub num_entries: u32, + pub entry: SampleEntry, +} + +pub enum SampleEntry { + Visual(VisualSampleEntry), + Sound(SoundSampleEntry), +} + +pub struct VisualSampleEntry { + pub dref_idx: u16, + pub width: u16, + pub height: u16, + pub resolution: u64, + pub sample_per_frame: u16, + pub encoder_name: String, + pub pixel_depth: u16, + pub codec_config: CodecConfig, +} + +pub struct SoundSampleEntry { + pub dref_idx: u16, + pub channels: u16, + pub sample_size: u16, + pub sample_rate: u32, + pub codec_config: CodecConfig, +} + +pub struct CodecConfig { + pub atom_name: [u8; 4], + pub data: Vec, +} + struct STTS { version: u8, flags: u32, - num_entires: u32, - entires: Vec, + num_entries: u32, + entries: Vec, } struct STTSEntry { @@ -211,3 +253,4 @@ struct TREX { default_size: u32, default_flags: u32, } + diff --git a/src/muxer/hls/mp4/mod.rs b/src/muxer/hls/mp4/mod.rs index 1f6dcbe..3cbbad5 100644 --- a/src/muxer/hls/mp4/mod.rs +++ b/src/muxer/hls/mp4/mod.rs @@ -8,9 +8,9 @@ use crate::util; pub struct MP4Muxer { v: mpsc::Receiver, - v_samples: Vec, + v_samples: Vec>, a: mpsc::Receiver, - a_samples: Vec, + a_samples: Vec>, metadata: Arc, err_in: mpsc::Sender>, } diff --git a/src/muxer/hls/mp4/mp4muxer.rs b/src/muxer/hls/mp4/mp4muxer.rs index 63d8a21..92f585d 100644 --- a/src/muxer/hls/mp4/mp4muxer.rs +++ b/src/muxer/hls/mp4/mp4muxer.rs @@ -1,20 +1,95 @@ use std::error::Error; +use std::sync::Arc; use crate::util; use crate::muxer::hls::mp4; impl mp4::MP4Muxer { pub fn gen_init(&mut self) -> Result<(), Box> { - let a_CC = self.handle_a_CC()?; - let v_CC = self.handle_v_CC()?; + let stsd_v = self.handle_a_cc()?; + let stsd_a = self.handle_v_cc()?; + let init_tree = self.construct_moov(stsd_v, stsd_a); Ok(()) } - fn handle_a_CC(&mut self) -> Result> { - todo!(); + fn handle_v_cc(&mut self) -> Result> { + let v_cc = self.v.recv()?; + return match v_cc.packet_type { + util::NALUPacketType::Video(util::VideoCodec::AV1) => { + self.v_samples.push(v_cc.packet_data.clone()); + Ok(get_av1_stsd(v_cc.packet_data, &self.metadata)) + }, + _ => Err(Box::new(util::MuxerError::InvalidCodec)) + } } - fn handle_v_CC(&mut self) -> Result> { + fn handle_a_cc(&mut self) -> Result> { + let a_cc = self.a.recv()?; + return match a_cc.packet_type { + util::NALUPacketType::Audio(util::AudioCodec::OPUS) => { + self.a.recv()?; + Ok(get_opus_stsd(a_cc.packet_data, &self.metadata)) + }, + _ => Err(Box::new(util::MuxerError::InvalidCodec)) + } + } + + fn construct_moov(&self, stsd_v: mp4::atoms::STSD, stsd_a: mp4::atoms::STSD) -> mp4::atoms::MOOV { todo!(); } } + +fn get_av1_stsd(mut sample: Vec, metadata: &Arc) -> mp4::atoms::STSD { + let cc_len = sample[1] as usize; + let mut av1c: Vec = sample.drain(..2 + cc_len).collect(); + // should match encoder config somehow, check iso binding specs for details + // default for svt defaults + 720p@30fps. + // More complicated to parse than is worth doing. + let mut dummy_mp4_cc_binding = vec![0x81u8, 0x05, 0x0d, 0x00]; + dummy_mp4_cc_binding.append(&mut av1c); + let cc = mp4::atoms::CodecConfig {atom_name: "av1C".as_bytes().try_into().unwrap(), data: dummy_mp4_cc_binding}; + let v_sample_entry = mp4::atoms::VisualSampleEntry { + dref_idx: 1, + width: metadata.video.width as u16, + height: metadata.video.height as u16, + resolution: (72u64 << 48) + (72u64 << 16), + sample_per_frame: 1, + encoder_name: String::from("libsvtav1"), + pixel_depth: 24, + codec_config: cc, + }; + return mp4::atoms::STSD { + version: 0, + flags: 0, + num_entries: 1, + entry: mp4::atoms::SampleEntry::Visual(v_sample_entry), + } +} + +fn get_opus_stsd(sample: Vec, metadata: &Arc) -> mp4::atoms::STSD { + let mut opus_binding = vec![0u8; 11]; + opus_binding[1] = metadata.audio.channels; + let pre_skip = u16::from_le_bytes(sample[10..12].try_into().unwrap()); + opus_binding.splice(2..4, pre_skip.to_be_bytes()); + opus_binding.splice(4..8, metadata.audio.samplerate.to_be_bytes()); + let output_gain = u16::from_le_bytes(sample[16..18].try_into().unwrap()); + opus_binding.splice(8..10, output_gain.to_be_bytes()); + opus_binding[10] = sample[18]; + if opus_binding[18] == 1 { + opus_binding.append(&mut sample[19..].to_vec()); + } + + let a_sample_entry = mp4::atoms::SoundSampleEntry { + dref_idx: 1, + channels: metadata.audio.channels as u16, + sample_size: 16, + sample_rate: metadata.audio.samplerate, + codec_config: mp4::atoms::CodecConfig { atom_name: "dOps".as_bytes().try_into().unwrap(), data: opus_binding} + }; + return mp4::atoms::STSD { + version: 0, + flags: 0, + num_entries: 1, + entry: mp4::atoms::SampleEntry::Sound(a_sample_entry), + } +} diff --git a/src/util/mod.rs b/src/util/mod.rs index e024803..3000ffd 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -197,6 +197,7 @@ impl fmt::Display for EncoderError { pub enum MuxerError { InvalidCodec, + CCParseError, } impl Error for MuxerError {} @@ -206,6 +207,7 @@ impl fmt::Debug for MuxerError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { MuxerError::InvalidCodec => write!(f, "Codec not valid, unhandled"), + MuxerError::CCParseError => write!(f, "Generic error while parsing codec config box"), _ => write!(f, "Error not described yet") } }