--- /dev/null
+use std::convert::TryInto;
+
+use nihav_core::muxers::*;
+
+mod audiotrack;
+use audiotrack::*;
+mod rawaudiotrack;
+use rawaudiotrack::*;
+mod videotrack;
+use videotrack::*;
+
+const TIMESCALE: u32 = 1000;
+
+trait MovWrite {
+ fn write_pas_str(&mut self, pas_str: &[u8]) -> ByteIOResult<()>;
+}
+
+impl<T:?Sized + ByteIO> MovWrite for T {
+ fn write_pas_str(&mut self, pas_str: &[u8]) -> ByteIOResult<()> {
+ self.write_byte(pas_str.len() as u8)?;
+ self.write_buf(pas_str)?;
+ Ok(())
+ }
+}
+
+fn write_matrix_structure(bw: &mut dyn ByteIO) -> MuxerResult<()> {
+ bw.write_u32be(0x00010000)?;
+ bw.write_u32be(0)?;
+ bw.write_u32be(0)?;
+ bw.write_u32be(0)?;
+ bw.write_u32be(0x00010000)?;
+ bw.write_u32be(0)?;
+ bw.write_u32be(0)?;
+ bw.write_u32be(0)?;
+ bw.write_u32be(0x40000000)?;
+ Ok(())
+}
+
+struct AtomMarker {
+ pos: u64,
+ name: [u8; 4],
+ done: bool,
+}
+
+impl AtomMarker {
+ fn start(bw: &mut dyn ByteIO, tag: &[u8; 4]) -> MuxerResult<Self> {
+ let pos = bw.tell();
+ bw.write_u32be(0)?;
+ bw.write_buf(tag)?;
+ Ok(Self{ pos, name: *tag, done: false })
+ }
+ fn end(mut self, bw: &mut dyn ByteIO) -> MuxerResult<()> {
+ let cur_pos = bw.tell();
+ if cur_pos < self.pos + 8 {
+ return Err(MuxerError::InvalidData);
+ }
+ bw.seek(SeekFrom::Start(self.pos))?;
+ bw.write_u32be((cur_pos - self.pos) as u32)?;
+ bw.seek(SeekFrom::Start(cur_pos))?;
+ self.done = true;
+ Ok(())
+ }
+}
+
+impl Drop for AtomMarker {
+ fn drop(&mut self) {
+ if !self.done {
+ println!("Unclosed marker '{}{}{}{}'", self.name[0] as char, self.name[1] as char, self.name[2] as char, self.name[3] as char);
+ }
+ }
+}
+
+#[derive(Default)]
+struct ChunkAccount {
+ offsets: Vec<u32>,
+ offsets64: Vec<u64>,
+ sizes: Vec<u32>,
+ cmap: Vec<u32>,
+ pts: Vec<u64>,
+ keyframes: Vec<u32>,
+}
+
+impl ChunkAccount {
+ fn new() -> Self { Self::default() }
+ fn is_empty(&self) -> bool { self.offsets.is_empty() && self.offsets64.is_empty() }
+
+ fn add_size(&mut self, size: usize) {
+ self.sizes.push(size as u32);
+ }
+ fn add_keyframe(&mut self, chunk: u32) {
+ self.keyframes.push(chunk);
+ }
+ fn add_pts(&mut self, pts: u64) {
+ self.pts.push(pts);
+ }
+ fn add_offset(&mut self, offset: u64) {
+ if let Ok(off32) = offset.try_into() {
+ self.offsets.push(off32);
+ } else {
+ if !self.offsets.is_empty() {
+ self.offsets64.reserve(self.offsets.len() + 1);
+ for &off32 in self.offsets.iter() {
+ self.offsets64.push(u64::from(off32));
+ }
+ self.offsets = Vec::new();
+ }
+ self.offsets64.push(offset);
+ }
+ }
+ fn add_chunk_samps(&mut self, count: usize) {
+ self.cmap.push(count as u32);
+ }
+
+ fn write_stts(&mut self, bw: &mut dyn ByteIO, raw_audio: bool) -> MuxerResult<()> {
+ if !raw_audio {
+ let mut nentries = 1;
+ let mut prev_delta = self.pts[1] - self.pts[0];
+ for w in self.pts.windows(2).skip(1) {
+ let delta = w[1] - w[0];
+ if delta != prev_delta {
+ nentries += 1;
+ prev_delta = delta;
+ }
+ }
+ bw.write_u32be(nentries as u32)?;
+ let mut prev_delta = self.pts[1] - self.pts[0];
+ let mut run = 1;
+ for w in self.pts.windows(2).skip(1) {
+ let delta = w[1] - w[0];
+ if delta != prev_delta {
+ bw.write_u32be(run)?;
+ bw.write_u32be(prev_delta as u32)?;
+ run = 1;
+ prev_delta = delta;
+ } else {
+ run += 1;
+ }
+ }
+ bw.write_u32be(run + 1)?;
+ bw.write_u32be(prev_delta as u32)?;
+ } else {
+ bw.write_u32be(1)?;
+ bw.write_u32be((*self.pts.last().unwrap_or(&0)) as u32)?;
+ bw.write_u32be(1)?;
+ }
+ Ok(())
+ }
+ fn write_stsc(&mut self, bw: &mut dyn ByteIO) -> MuxerResult<()> {
+ if self.cmap.is_empty() {
+ bw.write_u32be(1)?; // number of entries
+ bw.write_u32be(1)?; // first chunk
+ bw.write_u32be(1)?; // samples per chunk
+ bw.write_u32be(1)?; // sample description ID
+ } else {
+ bw.write_u32be(self.cmap.len() as u32)?;
+ for (i, &entry) in self.cmap.iter().enumerate() {
+ bw.write_u32be(i as u32 + 1)?;
+ bw.write_u32be(entry)?; // samples per chunk
+ bw.write_u32be(1)?; // sample description ID
+ }
+ }
+ Ok(())
+ }
+ fn write_stsz(&mut self, bw: &mut dyn ByteIO, raw_audio: bool) -> MuxerResult<()> {
+ if !raw_audio {
+ bw.write_u32be(0)?; // sample size
+ bw.write_u32be(self.sizes.len() as u32)?;
+ bw.write_u32be_arr(&self.sizes)?;
+ } else {
+ bw.write_u32be(1)?;
+ let tot_size = self.sizes.iter().sum();
+ bw.write_u32be(tot_size)?;
+ }
+ Ok(())
+ }
+ fn write_chunk_offsets(&mut self, bw: &mut dyn ByteIO) -> MuxerResult<()> {
+ if self.offsets64.is_empty() {
+ let stco = AtomMarker::start(bw, b"stco")?;
+ bw.write_byte(0)?; // version
+ bw.write_u24be(0)?; // flags
+ bw.write_u32be(self.offsets.len() as u32)?;
+ bw.write_u32be_arr(&self.offsets)?;
+ stco.end(bw)?;
+ } else {
+ let co64 = AtomMarker::start(bw, b"co64")?;
+ bw.write_byte(0)?; // version
+ bw.write_u24be(0)?; // flags
+ for &off in self.offsets64.iter() {
+ bw.write_u64be(off)?;
+ }
+ co64.end(bw)?;
+ }
+ Ok(())
+ }
+ fn write_stss(&mut self, bw: &mut dyn ByteIO) -> MuxerResult<()> {
+ bw.write_u32be(self.keyframes.len() as u32)?;
+ bw.write_u32be_arr(&self.keyframes)?;
+ Ok(())
+ }
+}
+
+trait TrackHandler {
+ fn write_chunk(&mut self, bw: &mut dyn ByteIO, ca: &mut ChunkAccount, pkt: NAPacket) -> MuxerResult<()>;
+ fn flush_chunks(&mut self, bw: &mut dyn ByteIO, ca: &mut ChunkAccount) -> MuxerResult<()>;
+ fn get_time_info(&self) -> (u64, u32);
+ fn get_dimensions(&self) -> (usize, usize);
+ fn write_media_info(&mut self, bw: &mut dyn ByteIO) -> MuxerResult<()>;
+ fn write_descriptor(&mut self, bw: &mut dyn ByteIO) -> MuxerResult<()>;
+}
+
+struct Track {
+ id: u32,
+ mtype: StreamType,
+ handler: Box<dyn TrackHandler>,
+ ca: ChunkAccount,
+ raw_audio: bool,
+}
+
+impl Track {
+ fn write_trak(&mut self, bw: &mut dyn ByteIO) -> MuxerResult<()> {
+ let trak = AtomMarker::start(bw, b"trak")?;
+
+ let tkhd = AtomMarker::start(bw, b"tkhd")?;
+ bw.write_byte(0)?; // version
+ bw.write_u24be(0xF)?; // flags (from LSB) - enabled, in movie, in preview, in poster
+ bw.write_u32be(0)?; // creation time
+ bw.write_u32be(0)?; // modification time
+ bw.write_u32be(self.id)?;
+ bw.write_u32be(0)?; // reserved
+ let (nduration, tbase) = self.handler.get_time_info();
+ let duration = NATimeInfo::rescale_ts(nduration, 1, tbase, 1, TIMESCALE) as u32;
+ bw.write_u32be(duration)?; // duration
+ bw.write_buf(&[0; 8])?; // reserved
+ bw.write_u16be(0)?; // layer
+ bw.write_u16be(0)?; // alternate group
+ bw.write_u16be(if self.mtype != StreamType::Audio { 0 } else { 0x00FF })?; // volume
+ bw.write_u16be(0)?; // reserved
+ write_matrix_structure(bw)?;
+ let (width, height) = self.handler.get_dimensions();
+ bw.write_u32be(width as u32)?;
+ bw.write_u32be(height as u32)?;
+ tkhd.end(bw)?;
+
+ let mdia = AtomMarker::start(bw, b"mdia")?;
+ let mdhd = AtomMarker::start(bw, b"mdhd")?;
+ bw.write_byte(0)?; // version
+ bw.write_u24be(0)?; // flags
+ bw.write_u32be(0)?; // creation time
+ bw.write_u32be(0)?; // modification time
+ let (duration, time_scale) = self.handler.get_time_info();
+ bw.write_u32be(time_scale)?;
+ bw.write_u32be(duration as u32)?;
+ bw.write_u16be(0)?; // language
+ bw.write_u16be(0)?; // quality
+ mdhd.end(bw)?;
+ let hdlr = AtomMarker::start(bw, b"hdlr")?;
+ bw.write_byte(0)?; // version
+ bw.write_u24be(0)?; // flags
+ bw.write_buf(b"mhlr")?; // component type = media
+ let hd = match self.mtype {
+ StreamType::Video => b"vide",
+ StreamType::Audio => b"soun",
+ _ => unimplemented!(),
+ };
+ bw.write_buf(hd)?;
+ bw.write_buf(b"appl")?; // component manufacturer
+ bw.write_u32be(0)?; // component flags
+ bw.write_u32be(0)?; // component flags mask
+ let comp_string = match self.mtype {
+ StreamType::Video => b"Video handler",
+ StreamType::Audio => b"Audio handler",
+ _ => unimplemented!(),
+ };
+ bw.write_pas_str(comp_string)?;
+ hdlr.end(bw)?;
+
+ let minf = AtomMarker::start(bw, b"minf")?;
+ self.handler.write_media_info(bw)?;
+
+ let hdlr = AtomMarker::start(bw, b"hdlr")?;
+ bw.write_byte(0)?; // version
+ bw.write_u24be(0)?; // flags
+ bw.write_buf(b"dhlr")?; // component type = data handler
+ bw.write_buf(b"alis")?;
+ bw.write_buf(b"appl")?; // component manufacturer
+ bw.write_u32be(0)?; // component flags
+ bw.write_u32be(0)?; // component flags mask
+ bw.write_pas_str(b"Apple Alias Data Handler")?;
+ hdlr.end(bw)?;
+
+ let dinf = AtomMarker::start(bw, b"dinf")?;
+ let dref = AtomMarker::start(bw, b"dref")?;
+ bw.write_byte(0)?; // version
+ bw.write_u24be(0)?; // flags
+ bw.write_u32be(1)?; // number of entries
+ let alis = AtomMarker::start(bw, b"alis")?;
+ bw.write_byte(0)?; // version
+ bw.write_u24be(1)?; // flags
+ alis.end(bw)?;
+ dref.end(bw)?;
+ dinf.end(bw)?;
+
+ let stbl = AtomMarker::start(bw, b"stbl")?;
+
+ let stsd = AtomMarker::start(bw, b"stsd")?;
+ bw.write_byte(0)?; // version
+ bw.write_u24be(0)?; // flags
+ bw.write_u32be(1)?; // number of entries
+ self.handler.write_descriptor(bw)?;
+ stsd.end(bw)?;
+
+ let stts = AtomMarker::start(bw, b"stts")?;
+ bw.write_byte(0)?; // version
+ bw.write_u24be(0)?; // flags
+ self.ca.write_stts(bw, self.raw_audio)?;
+ stts.end(bw)?;
+
+ let stsc = AtomMarker::start(bw, b"stsc")?;
+ bw.write_byte(0)?; // version
+ bw.write_u24be(0)?; // flags
+ self.ca.write_stsc(bw)?;
+ stsc.end(bw)?;
+
+ let stsz = AtomMarker::start(bw, b"stsz")?;
+ bw.write_byte(0)?; // version
+ bw.write_u24be(0)?; // flags
+ self.ca.write_stsz(bw, self.raw_audio)?;
+ stsz.end(bw)?;
+
+ if !self.raw_audio && !self.ca.keyframes.is_empty() {
+ let stss = AtomMarker::start(bw, b"stss")?;
+ bw.write_byte(0)?; // version
+ bw.write_u24be(0)?; // flags
+ self.ca.write_stss(bw)?;
+ stss.end(bw)?;
+ }
+
+ self.ca.write_chunk_offsets(bw)?;
+
+ stbl.end(bw)?;
+ minf.end(bw)?;
+ mdia.end(bw)?;
+ trak.end(bw)?;
+ Ok(())
+ }
+}
+
+struct QTMuxer<'a> {
+ bw: &'a mut dyn ByteIO,
+ tracks: Vec<Track>,
+}
+
+impl<'a> QTMuxer<'a> {
+ fn new(bw: &'a mut dyn ByteIO) -> Self {
+ Self {
+ bw,
+ tracks: Vec::new(),
+ }
+ }
+}
+
+impl<'a> MuxCore<'a> for QTMuxer<'a> {
+ fn create(&mut self, strmgr: &StreamManager) -> MuxerResult<()> {
+ if strmgr.get_num_streams() == 0 {
+ return Err(MuxerError::InvalidArgument);
+ }
+ if strmgr.get_num_streams() > 99 {
+ return Err(MuxerError::UnsupportedFormat);
+ }
+ let mut trk_id = 1;
+ for strm in strmgr.iter() {
+ let mtype = strm.get_media_type();
+ match mtype {
+ StreamType::Video => {
+ self.tracks.push(Track {
+ id: trk_id,
+ mtype,
+ handler: Box::new(VideoTrackHandler::new(strm)?),
+ ca: ChunkAccount::new(),
+ raw_audio: false,
+ });
+ trk_id += 1;
+ },
+ StreamType::Audio => {
+ let raw_audio = is_raw_audio(strm.get_info().get_name());
+ let handler: Box<dyn TrackHandler> = if raw_audio {
+ Box::new(RawAudioTrackHandler::new(strm)?)
+ } else {
+ Box::new(AudioTrackHandler::new(strm)?)
+ };
+ self.tracks.push(Track {
+ id: trk_id,
+ mtype,
+ handler,
+ ca: ChunkAccount::new(),
+ raw_audio,
+ });
+ trk_id += 1;
+ },
+ _ => return Err(MuxerError::UnsupportedFormat),
+ }
+ }
+
+ let marker = AtomMarker::start(self.bw, b"wide")?;
+ marker.end(self.bw)?;
+ let marker = AtomMarker::start(self.bw, b"mdat")?;
+ marker.end(self.bw)?;
+
+ Ok(())
+ }
+ fn mux_frame(&mut self, _strmgr: &StreamManager, pkt: NAPacket) -> MuxerResult<()> {
+ if self.tracks.is_empty() {
+ return Err(MuxerError::NotCreated);
+ }
+ let stream = pkt.get_stream();
+ let str_num = stream.get_num();
+ if str_num >= self.tracks.len() {
+ return Err(MuxerError::UnsupportedFormat);
+ }
+ let trk = &mut self.tracks[str_num];
+
+ trk.handler.write_chunk(self.bw, &mut trk.ca, pkt)?;
+
+ Ok(())
+ }
+ fn flush(&mut self) -> MuxerResult<()> {
+ for trk in self.tracks.iter_mut() {
+ trk.handler.flush_chunks(self.bw, &mut trk.ca)?;
+ }
+ Ok(())
+ }
+ fn end(&mut self) -> MuxerResult<()> {
+ if self.tracks.is_empty() || self.bw.tell() <= 16 {
+ return Err(MuxerError::NotCreated);
+ }
+
+ self.flush()?;
+
+ let mdat_size = self.bw.tell() - 8;
+ if mdat_size < (1 << 32) {
+ self.bw.seek(SeekFrom::Start(8))?;
+ self.bw.write_u32be(mdat_size as u32)?;
+ } else {
+ self.bw.seek(SeekFrom::Start(0))?;
+ self.bw.write_u32be(1)?;
+ self.bw.write_buf(b"mdat")?;
+ self.bw.write_u64be(mdat_size + 8)?;
+ }
+ self.bw.seek(SeekFrom::End(0))?;
+
+ let moov = AtomMarker::start(self.bw, b"moov")?;
+ let mvhd = AtomMarker::start(self.bw, b"mvhd")?;
+ self.bw.write_byte(0)?; // version
+ self.bw.write_u24be(0)?; // flags
+ self.bw.write_u32be(0)?; // creation time
+ self.bw.write_u32be(0)?; // modification time
+ self.bw.write_u32be(TIMESCALE)?;
+ let mut duration = 0;
+ for trk in self.tracks.iter() {
+ let (nduration, tbase) = trk.handler.get_time_info();
+ let dur = NATimeInfo::rescale_ts(nduration, 1, tbase, 1, TIMESCALE) as u32;
+ duration = duration.max(dur);
+ }
+ self.bw.write_u32be(duration)?;
+ self.bw.write_u32be(0x00010000)?; // preferred rate = 1.0
+ self.bw.write_u16be(0x00FF)?; // preferred volume = 1.0
+ self.bw.write_buf(&[0; 10])?; // reserved
+ write_matrix_structure(self.bw)?;
+ self.bw.write_u32be(0)?; // preview time
+ self.bw.write_u32be(0)?; // preview duration
+ self.bw.write_u32be(0)?; // poster time
+ self.bw.write_u32be(0)?; // selection time
+ self.bw.write_u32be(0)?; // selection duration
+ self.bw.write_u32be(0)?; // current time
+ self.bw.write_u32be(self.tracks.len() as u32 + 1)?; // next track ID
+ mvhd.end(self.bw)?;
+
+ for trk in self.tracks.iter_mut() {
+ if !trk.ca.is_empty() {
+ trk.write_trak(self.bw)?;
+ }
+ }
+
+ let udta = AtomMarker::start(self.bw, b"udta")?;
+ let writer = AtomMarker::start(self.bw, b"\xA9wrt")?;
+ let wr_str = b"Generated by NihAV muxer. Sorry about that!";
+ self.bw.write_u16be(wr_str.len() as u16)?;
+ self.bw.write_u16be(0)?; // language code
+ self.bw.write_buf(wr_str)?;
+ writer.end(self.bw)?;
+ self.bw.write_u32be(0)?; // for historical reasons
+ udta.end(self.bw)?;
+
+ moov.end(self.bw)?;
+
+ self.bw.flush()?;
+
+ Ok(())
+ }
+}
+
+impl<'a> NAOptionHandler for QTMuxer<'a> {
+ fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] }
+ fn set_options(&mut self, _options: &[NAOption]) { }
+ fn query_option_value(&self, _name: &str) -> Option<NAValue> { None }
+}
+
+pub struct MovMuxerCreator {}
+
+impl MuxerCreator for MovMuxerCreator {
+ fn new_muxer<'a>(&self, bw: &'a mut dyn ByteIO) -> Box<dyn MuxCore<'a> + 'a> {
+ Box::new(QTMuxer::new(bw))
+ }
+ fn get_name(&self) -> &'static str { "mov" }
+ fn get_capabilities(&self) -> MuxerCapabilities { MuxerCapabilities::Universal }
+ fn get_quirks(&self) -> MuxerQuirks { MuxerQuirks(MUX_QUIRK_UNSYNC) }
+}
+
+#[cfg(test)]
+mod test {
+ use nihav_core::codecs::*;
+ use nihav_core::demuxers::*;
+ use nihav_core::muxers::*;
+ use nihav_codec_support::test::enc_video::*;
+ use crate::*;
+
+ #[test]
+ fn test_mov_muxer_video_only() {
+ let mut dmx_reg = RegisteredDemuxers::new();
+ generic_register_all_demuxers(&mut dmx_reg);
+ // sample from King's Quest VI Macintosh edition
+ let dec_config = DecoderTestParams {
+ demuxer: "mov-macbin",
+ in_name: "assets/QT/Halfdome.bin",
+ limit: None,
+ stream_type: StreamType::None,
+ dmx_reg, dec_reg: RegisteredDecoders::new(),
+ };
+ let mut mux_reg = RegisteredMuxers::new();
+ generic_register_all_muxers(&mut mux_reg);
+ test_remuxing_md5(&dec_config, "mov", &mux_reg,
+ [0x842382db, 0xa6d23d85, 0x4fa746c2, 0x5c8ede5b]);
+ }
+
+ #[test]
+ fn test_mov_muxer_audio_only() {
+ let mut dmx_reg = RegisteredDemuxers::new();
+ generic_register_all_demuxers(&mut dmx_reg);
+ // sample from The Wonders of Electricity: An Adventure in Safety
+ let dec_config = DecoderTestParams {
+ demuxer: "mov-resfork",
+ in_name: "assets/QT/car.mov",
+ limit: None,
+ stream_type: StreamType::None,
+ dmx_reg, dec_reg: RegisteredDecoders::new(),
+ };
+ let mut mux_reg = RegisteredMuxers::new();
+ generic_register_all_muxers(&mut mux_reg);
+ test_remuxing_md5(&dec_config, "mov", &mux_reg,
+ [0xe6bf92a2, 0x6a9871e5, 0x98fc2dcb, 0xe032c119]);
+ }
+
+ #[test]
+ fn test_mov_muxer_video_and_audio() {
+ let mut dmx_reg = RegisteredDemuxers::new();
+ generic_register_all_demuxers(&mut dmx_reg);
+ // samples from https://samples.mplayerhq.hu/V-codecs/QTRLE/tidemo1-24bit-rle.mov
+ let dec_config = DecoderTestParams {
+ demuxer: "mov",
+ in_name: "assets/QT/tidemo1-24bit-rle.mov",
+ limit: None,
+ stream_type: StreamType::None,
+ dmx_reg, dec_reg: RegisteredDecoders::new(),
+ };
+ let mut mux_reg = RegisteredMuxers::new();
+ generic_register_all_muxers(&mut mux_reg);
+ test_remuxing_md5(&dec_config, "mov", &mux_reg,
+ [0xeb99a547, 0xd6c1a4ce, 0xc12d3451, 0x096639c5]);
+ }
+}
--- /dev/null
+use nihav_core::muxers::*;
+
+use super::*;
+
+pub fn is_raw_audio(cname: &str) -> bool {
+ matches!(cname,
+ "pcm" |
+ "mace-3" | "mace-6" |
+ "ima-adpcm-qt" |
+ "ulaw" | "alaw" |
+ "ms\x00\x02" | "ms\x00\x11")
+}
+
+pub struct RawAudioTrackHandler {
+ fcc: [u8; 4],
+ rate: u32,
+ bits: usize,
+ channels: usize,
+ sample: u64,
+}
+
+impl RawAudioTrackHandler {
+ pub fn new(strm: NAStreamRef) -> MuxerResult<Self> {
+ let cname = strm.get_info().get_name();
+ let ainfo = strm.get_info().get_properties().get_audio_info().ok_or(MuxerError::UnsupportedFormat)?;
+ let fcc = match cname {
+ "pcm" => {
+ match ainfo.format {
+ NASoniton { bits: 8, be: _, packed: _, planar: _, float: false, signed: false } => *b"raw ",
+ NASoniton { bits: 8, be: _, packed: _, planar: _, float: false, signed: true } => *b"twos",
+ NASoniton { bits: 16, be: true, packed: _, planar: _, float: false, signed: true } => *b"twos",
+ NASoniton { bits: 16, be: false, packed: _, planar: _, float: false, signed: true } => *b"sowt",
+ NASoniton { bits: 24, be: true, packed: _, planar: _, float: false, signed: true } => *b"in24",
+ NASoniton { bits: 32, be: true, packed: _, planar: _, float: false, signed: true } => *b"in32",
+ NASoniton { bits: 32, be: true, packed: _, planar: _, float: true, signed: _ } => *b"fl32",
+ NASoniton { bits: 64, be: true, packed: _, planar: _, float: true, signed: _ } => *b"fl64",
+ _ => return Err(MuxerError::UnsupportedFormat),
+ }
+ },
+ "mace-3" => *b"MAC3",
+ "mace-6" => *b"MAC6",
+ "ima-adpcm-qt" => *b"ima4",
+ "alaw" => *b"alaw",
+ "ulaw" => *b"ulaw",
+
+ "ms-adpcm" => return Err(MuxerError::UnsupportedFormat), //*b"ms\x00\x02",
+ "ima-adpcm-ms" => *b"ms\x00\x11",
+ _ => return Err(MuxerError::UnsupportedFormat),
+ };
+ let rate = ainfo.sample_rate;
+ let bits = usize::from(ainfo.format.bits);
+ let channels = usize::from(ainfo.channels);
+ Ok(Self { fcc, rate, bits, channels, sample: 0 })
+ }
+
+ fn calculate_chunk_samples(&self, size: usize) -> usize {
+ match &self.fcc {
+ b"NONE" | b"raw " | b"twos" | b"sowt" | &[0, 0, 0, 0] => {
+ size * 8 / (self.bits * self.channels)
+ },
+ b"ima4" => {
+ let nblocks = size / (34 * self.channels);
+ nblocks * 64
+ },
+ b"MAC3" => {
+ size * 3 / self.channels
+ },
+ b"MAC6" => {
+ size * 6 / self.channels
+ },
+ b"in24" => size / (3 * self.channels),
+ b"in32" | b"fl32" => size / (4 * self.channels),
+ b"fl64" => size / (8 * self.channels),
+ b"ulaw" | b"alaw" => size,
+ /*b"ms\x00\x02" => { //MS ADPCM
+ size / block_size * ((block_size / self.channels - 7) * 2 + 2)
+ },*/
+ b"ms\x00\x11" => { //IMA ADPCM
+ (size / self.channels - 4) * 2 + 1
+ },
+ _ => unreachable!(),
+ }
+ }
+}
+
+impl TrackHandler for RawAudioTrackHandler {
+ fn write_chunk(&mut self, bw: &mut dyn ByteIO, ca: &mut ChunkAccount, pkt: NAPacket) -> MuxerResult<()> {
+ let src = pkt.get_buffer();
+
+ let chunk_samples = self.calculate_chunk_samples(src.len());
+
+ let offset = bw.tell();
+ ca.add_offset(offset);
+ ca.add_chunk_samps(chunk_samples);
+ self.sample += chunk_samples as u64;
+ bw.write_buf(&src)?;
+ Ok(())
+ }
+ fn flush_chunks(&mut self, _bw: &mut dyn ByteIO, ca: &mut ChunkAccount) -> MuxerResult<()> {
+ ca.add_pts(self.sample);
+ Ok(())
+ }
+ fn get_time_info(&self) -> (u64, u32) { (self.sample, self.rate) }
+ fn get_dimensions(&self) -> (usize, usize) { (0, 0) }
+ fn write_media_info(&mut self, bw: &mut dyn ByteIO) -> MuxerResult<()> {
+ let smhd = AtomMarker::start(bw, b"smhd")?;
+ bw.write_byte(0)?; // version
+ bw.write_u24be(0)?; // flags
+ bw.write_u16be(0)?; // balance
+ bw.write_u16be(0)?; // reserved
+ smhd.end(bw)?;
+ Ok(())
+ }
+ fn write_descriptor(&mut self, bw: &mut dyn ByteIO) -> MuxerResult<()> {
+ let codec = AtomMarker::start(bw, &self.fcc)?;
+ bw.write_buf(&[0; 6])?;
+ bw.write_u16be(1)?; // data reference index
+ bw.write_u16be(0)?; // version
+ bw.write_u16be(0)?; // revision
+ bw.write_u32be(0)?; // vendor
+ bw.write_u16be(self.channels as u16)?;
+ bw.write_u16be(self.bits as u16)?;
+ bw.write_u16be(0)?; // compression ID
+ bw.write_u16be(0)?; // packet size
+ bw.write_u32be(self.rate << 16)?;
+ codec.end(bw)?;
+
+ Ok(())
+ }
+}
--- /dev/null
+use nihav_core::muxers::*;
+use nihav_registry::register::*;
+
+use super::*;
+
+pub struct VideoTrackHandler {
+ fcc: [u8; 4],
+ vinfo: NAVideoInfo,
+ frameno: u32,
+ tb_num: u32,
+ tb_den: u32,
+ pts: u64,
+ edata: Option<Arc<Vec<u8>>>,
+}
+
+impl VideoTrackHandler {
+ pub fn new(strm: NAStreamRef) -> MuxerResult<Self> {
+ let cname = strm.get_info().get_name();
+ let fcc = find_mov_video_fourcc(cname).ok_or(MuxerError::UnsupportedFormat)?;
+ let vinfo = strm.get_info().get_properties().get_video_info().ok_or(MuxerError::UnsupportedFormat)?;
+ if cname == "rawvideo" {
+ match vinfo.format.to_short_string().ok_or(MuxerError::UnsupportedFormat)?.as_str() {
+ "rgb24" | "rgb555be" => {},
+ other => {
+ println!("Raw format {other} is not supported!");
+ return Err(MuxerError::UnsupportedFormat)?;
+ }
+ }
+ }
+ let (tb_num, tb_den) = strm.get_timebase();
+ let edata = strm.get_info().get_extradata();
+
+ Ok(Self{
+ fcc, vinfo, tb_num, tb_den,
+ frameno: 1,
+ pts: 0,
+ edata,
+ })
+ }
+}
+
+impl TrackHandler for VideoTrackHandler {
+ fn write_chunk(&mut self, bw: &mut dyn ByteIO, ca: &mut ChunkAccount, pkt: NAPacket) -> MuxerResult<()> {
+ let pts = pkt.ts.pts.ok_or(MuxerError::InvalidData)? * u64::from(self.tb_num);
+ let src = pkt.get_buffer();
+ let offset = bw.tell();
+ ca.add_pts(pts);
+ ca.add_offset(offset);
+ ca.add_size(src.len());
+ if pkt.is_keyframe() {
+ ca.add_keyframe(self.frameno);
+ }
+ self.frameno += 1;
+ self.pts = pts + pkt.ts.duration.unwrap_or(1) * u64::from(self.tb_num);
+ bw.write_buf(&src)?;
+ Ok(())
+ }
+ fn flush_chunks(&mut self, _bw: &mut dyn ByteIO, _ca: &mut ChunkAccount) -> MuxerResult<()> { Ok(()) }
+ fn get_time_info(&self) -> (u64, u32) { (self.pts * u64::from(self.tb_num), self.tb_den) }
+ fn get_dimensions(&self) -> (usize, usize) {
+ (self.vinfo.width, self.vinfo.height)
+ }
+ fn write_media_info(&mut self, bw: &mut dyn ByteIO) -> MuxerResult<()> {
+ let vmhd = AtomMarker::start(bw, b"vmhd")?;
+ bw.write_byte(0)?; // version
+ bw.write_u24be(1)?; // flags
+ bw.write_u16be(0x40)?; // graphics mode - dither copy
+ bw.write_u16be_arr(&[0x8000; 3])?; // opcolor
+ vmhd.end(bw)?;
+ Ok(())
+ }
+ fn write_descriptor(&mut self, bw: &mut dyn ByteIO) -> MuxerResult<()> {
+ let codec = AtomMarker::start(bw, &self.fcc)?;
+ bw.write_buf(&[0; 6])?;
+ bw.write_u16be(1)?; // data reference index
+ bw.write_u16be(1)?; // version
+ bw.write_u16be(1)?; // revision
+ bw.write_buf(b"appl")?; // vendor
+ bw.write_u32be(0)?; // temporal quality
+ bw.write_u32be(0)?; // spatial quality
+ bw.write_u16be(self.vinfo.width as u16)?;
+ bw.write_u16be(self.vinfo.height as u16)?;
+ bw.write_u32be(72 << 16)?; // horizontal resolution
+ bw.write_u32be(72 << 16)?; // vertical resolution
+ bw.write_u32be(0)?; // data size
+ bw.write_u16be(1)?; // frame count
+ let name: &[u8] = match &self.fcc {
+ b"raw " | b"j420" => b"Raw video",
+ b"jpeg" => b"Motion JPEG",
+ b"mjpa" => b"Motion JPEG A",
+ b"mjpb" => b"Motion JPEG B",
+ b"cvid" => b"Cinepak",
+ b"smc " => b"Apple Graphics",
+ b"rpza" => b"Apple Video",
+ b"rle " => b"Apple Animation",
+ b"qdrw" => b"QuickDraw",
+ b"SVQ1" => b"Sorenson Video",
+ b"SVQ3" => b"Sorenson Video 3",
+ b"rt21" => b"Indeo 2",
+ b"IV31" | b"IV32" => b"Indeo 3",
+ b"MPAK" => b"MoviePak",
+ b"pgvv" => b"Radius Studio",
+ b"UCOD" => b"Clear Video",
+ b"VP30" | b"VP31" => b"Truemotion VP3",
+//todo more
+ _ => b"Some unknown codec",
+ };
+ bw.write_pas_str(name)?;
+ bw.write_buf(&[0; 32][..32 - name.len() - 1])?;
+ let mut bpp = match self.vinfo.format.get_total_depth() {
+ 15 | 16 => 16,
+ other_depth => other_depth
+ };
+ if &self.fcc == b"j420" {
+ bpp = 12;
+ }
+ bw.write_u16be(u16::from(bpp))?;
+ bw.write_u16be(0xFFFF)?; // color table ID
+ if let Some(edata) = &self.edata {
+ bw.write_u32be(edata.len() as u32 + 4)?;
+ bw.write_buf(edata)?;
+ }
+ codec.end(bw)?;
+ Ok(())
+ }
+}