mov: introduce an option to print file structure
[nihav.git] / nihav-commonfmt / src / demuxers / mov.rs
index 5a0e4f9c374733a4968c9318cbfc5a3c9022e20d..0f1020a0757aeea59a33f4580374c034143239cf 100644 (file)
@@ -80,6 +80,24 @@ const ROOT_CHUNK_HANDLERS: &[RootChunkHandler] = &[
     RootChunkHandler { ctype: mktag!(b"moov"), parse: read_moov },
 ];
 
+fn print_cname(ctype: u32, size: u64, off: u64, depth: u8) {
+    for _ in 0..depth { print!("    "); }
+    let tag = [(ctype >> 24) as u8, (ctype >> 16) as u8, (ctype >> 8) as u8, ctype as u8];
+    let mut printable = true;
+    for &ch in tag.iter() {
+        if ch < 0x20 || ch > 0x7F {
+            printable = false;
+            break;
+        }
+    }
+    if printable {
+        print!(" '{}{}{}{}'", tag[0] as char, tag[1] as char, tag[2] as char, tag[3] as char);
+    } else {
+        print!(" {:08X}", ctype);
+    }
+    println!(" size {} @ {:X}", size, off);
+}
+
 macro_rules! read_chunk_list {
     (root; $name: expr, $fname: ident, $handlers: ident) => {
         fn $fname(&mut self, strmgr: &mut StreamManager, size: u64) -> DemuxerResult<()> {
@@ -90,6 +108,9 @@ macro_rules! read_chunk_list {
                 let ret = read_chunk_header(&mut self.src);
                 if ret.is_err() { break; }
                 let (ctype, size) = ret.unwrap();
+                if self.print_chunks {
+                    print_cname(ctype, size, self.src.tell(), self.depth as u8);
+                }
                 if self.src.tell() + size > list_end {
                     break;
                 }
@@ -122,6 +143,9 @@ macro_rules! read_chunk_list {
                 let ret = read_chunk_header(br);
                 if ret.is_err() { break; }
                 let (ctype, size) = ret.unwrap();
+                if self.print_chunks {
+                    print_cname(ctype, size, br.tell(), self.depth + 1);
+                }
                 if br.tell() + size > list_end {
                     break;
                 }
@@ -234,6 +258,7 @@ fn read_cmov(dmx: &mut MOVDemuxer, strmgr: &mut StreamManager, size: u64) -> Dem
     let (ctype, csize) = read_chunk_header(&mut br)?;
     validate!(ctype == mktag!(b"moov"));
     let mut ddmx = MOVDemuxer::new(&mut br);
+    ddmx.print_chunks = dmx.print_chunks;
     ddmx.read_moov(strmgr, csize)?;
     std::mem::swap(&mut dmx.tracks, &mut ddmx.tracks);
     dmx.duration = ddmx.duration;
@@ -257,6 +282,7 @@ fn read_meta(dmx: &mut MOVDemuxer, _strmgr: &mut StreamManager, size: u64) -> De
 
 fn read_trak(dmx: &mut MOVDemuxer, strmgr: &mut StreamManager, size: u64) -> DemuxerResult<u64> {
     let mut track = Track::new(dmx.cur_track as u32, dmx.tb_den);
+    track.print_chunks = dmx.print_chunks;
     track.read_trak(&mut dmx.src, size)?;
     validate!(track.tkhd_found && track.stsd_found);
     validate!(strmgr.get_stream_by_id(track.track_id).is_none());
@@ -335,7 +361,7 @@ fn read_hdlr(track: &mut Track, br: &mut ByteReader, size: u64) -> DemuxerResult
     let _comp_flags         = br.read_u32be()?;
     let _comp_flags_mask    = br.read_u32be()?;
 
-    if comp_type == mktag!(b"mhlr") {
+    if comp_type == mktag!(b"mhlr") || comp_type == 0 {
         if comp_subtype == mktag!(b"vide") {
             track.stream_type = StreamType::Video;
         } else if comp_subtype == mktag!(b"soun") {
@@ -584,7 +610,7 @@ fn read_stsd(track: &mut Track, br: &mut ByteReader, size: u64) -> DemuxerResult
             let packet_size     = br.read_u16be()? as usize;
             validate!(packet_size == 0);
             let sample_rate     = br.read_u32be()?;
-            validate!(sample_rate > 0);
+            validate!(sample_rate > (1 << 16));
             let cname = if let Some(name) = find_codec_from_mov_audio_fourcc(&fcc) {
                     name
                 } else if let (true, Some(name)) = ((fcc[0] == b'm' && fcc[1] == b's'),  find_codec_from_wav_twocc(u16::from(fcc[2]) * 256 + u16::from(fcc[3]))) {
@@ -604,9 +630,19 @@ fn read_stsd(track: &mut Track, br: &mut ByteReader, size: u64) -> DemuxerResult
                 let _bytes_per_sample       = br.read_u32be()?;
                 track.bsize = bytes_per_frame as usize;
                 track.frame_samples = samples_per_packet as usize;
+                track.tb_num = samples_per_packet;
             } else {
-                track.bsize = sample_size as usize;
+                track.bsize = (sample_size / 8) as usize;
             }
+            track.tb_den = sample_rate >> 16;
+            track.raw_audio = match &fcc {
+                    b"NONE" | b"raw " | b"twos" | b"sowt" |
+                    b"in24" | b"in32" | b"fl32" | b"fl64" |
+                    b"ima4" | b"ms\x00\x02" | b"ms\x00\x21" |
+                    b"alaw" | b"ulaw" |
+                    b"MAC3" | b"MAC6" => true,
+                    _ => false,
+                };
             let ahdr = NAAudioInfo::new(sample_rate >> 16, nchannels as u8, soniton, block_align);
             let edata = parse_audio_edata(br, start_pos, size)?;
             codec_info = NACodecInfo::new(cname, NACodecTypeInfo::Audio(ahdr), edata);
@@ -624,25 +660,26 @@ fn read_stsd(track: &mut Track, br: &mut ByteReader, size: u64) -> DemuxerResult
     };
     let read_size = br.tell() - start_pos;
     validate!(read_size <= size);
-    track.stream = Some(NAStream::new(track.stream_type, track.track_no, codec_info, 1, track.tb_den, u64::from(track.duration)));
+    track.stream = Some(NAStream::new(track.stream_type, track.track_no, codec_info, track.tb_num, track.tb_den, u64::from(track.duration)));
     track.stsd_found = true;
     Ok(read_size)
 }
 
 fn read_stts(track: &mut Track, br: &mut ByteReader, size: u64) -> DemuxerResult<u64> {
-    validate!(size >= 16);
+    validate!(size >= 8);
     let start_pos = br.tell();
     let version             = br.read_byte()?;
     validate!(version == 0);
     let _flags              = br.read_u24be()?;
     let entries             = br.read_u32be()? as usize;
     validate!(entries as u64 <= (size - 8) / 8);
-    if entries == 1 {
+    if entries == 0 {
+    } else if entries == 1 {
         let _count          = br.read_u32be()?;
         let tb_num          = br.read_u32be()?;
         if let Some(ref mut stream) = track.stream {
             let tb_den = stream.tb_den;
-            let (tb_num, tb_den) = reduce_timebase(tb_num, tb_den);
+            let (tb_num, tb_den) = reduce_timebase(tb_num * stream.tb_num, tb_den);
             stream.duration /= u64::from(stream.tb_den / tb_den);
             stream.tb_num = tb_num;
             stream.tb_den = tb_den;
@@ -748,6 +785,8 @@ struct MOVDemuxer<'a> {
     tb_den:         u32,
     duration:       u32,
     pal:            Option<Arc<[u8; 1024]>>,
+
+    print_chunks:   bool,
 }
 
 struct Track {
@@ -756,6 +795,8 @@ struct Track {
     track_no:       u32,
     tb_num:         u32,
     tb_den:         u32,
+    raw_audio:      bool,
+    raw_apos:       u64,
     duration:       u32,
     depth:          u8,
     tkhd_found:     bool,
@@ -781,6 +822,8 @@ struct Track {
     last_offset:    u64,
     pal:            Option<Arc<[u8; 1024]>>,
     timesearch:     TimeSearcher,
+
+    print_chunks:   bool,
 }
 
 #[derive(Default)]
@@ -834,6 +877,8 @@ impl Track {
             track_no,
             tb_num: 1,
             tb_den,
+            raw_audio:      false,
+            raw_apos:       0,
             duration:       0,
             stream_type:    StreamType::None,
             width:          0,
@@ -857,6 +902,8 @@ impl Track {
             last_offset:    0,
             pal:            None,
             timesearch:     TimeSearcher::new(),
+
+            print_chunks:   false,
         }
     }
     read_chunk_list!(track; "trak", read_trak, TRAK_CHUNK_HANDLERS);
@@ -908,7 +955,7 @@ impl Track {
     }
     fn get_next_chunk(&mut self) -> Option<(NATimeInfo, u64, usize)> {
         let pts_val = self.timesearch.map_time(self.cur_sample as u32, &self.time_to_sample);
-        let pts = NATimeInfo::new(Some(pts_val), None, None, 1, self.tb_den);
+        let mut pts = NATimeInfo::new(Some(pts_val), None, None, self.tb_num, self.tb_den);
 //todo dts decoding
         if self.chunk_offsets.len() == self.chunk_sizes.len() { // simple one-to-one mapping
             if self.cur_sample >= self.chunk_sizes.len() {
@@ -940,14 +987,34 @@ impl Track {
                 self.samples_left -= 1;
             } else if self.frame_samples != 0 && self.bsize != 0 {
                 let nblocks = size / self.bsize;
+                if self.raw_audio {
+                    pts.pts = Some(self.raw_apos);
+                    pts.duration = Some(nblocks as u64);
+                    self.raw_apos += nblocks as u64;
+                }
                 if nblocks > 0 {
                     let consumed = (nblocks * self.frame_samples).min(self.samples_left);
                     self.samples_left -= consumed;
                 } else {
                     self.samples_left = 0;
                 }
+            } else if !self.raw_audio {
+                self.samples_left -= 1;
             } else {
-                self.samples_left = 0;
+                const BLOCK_SAMPLES: usize = 1024 * 6; // should be multiple of 64 and 6 to fit both IMA ADPCM and MACE 6:1 blocks
+                let max_size = self.calculate_chunk_size(BLOCK_SAMPLES);
+                let cur_size = self.calculate_chunk_size(self.samples_left);
+                let add_off = (size - cur_size) as u64;
+                let dsize = cur_size.min(max_size);
+                if self.samples_left >= BLOCK_SAMPLES {
+                    self.cur_sample += BLOCK_SAMPLES;
+                    self.samples_left -= BLOCK_SAMPLES;
+                    self.last_offset -= size as u64;
+                } else {
+                    self.cur_sample += self.samples_left;
+                    self.samples_left = 0;
+                }
+                return Some((pts, offset + add_off, dsize));
             }
             self.cur_sample += 1;
             Some((pts, offset, size))
@@ -970,11 +1037,81 @@ impl Track {
             self.bsize
         }
     }
-    fn seek(&mut self, pts: u64) {
+    fn seek(&mut self, pts: u64, tpoint: NATimePoint) -> DemuxerResult<()> {
         self.cur_sample = pts as usize;
         self.samples_left = 0;
         if self.stream_type == StreamType::Audio {
-            self.cur_chunk = self.cur_sample;
+            if let NATimePoint::Milliseconds(ms) = tpoint {
+                let exp_pts = NATimeInfo::time_to_ts(ms, 1000, self.tb_num, self.tb_den);
+                if self.raw_audio {
+                    if self.frame_samples != 0 {
+                        self.raw_apos = exp_pts / (self.frame_samples as u64);
+                        let mut apos = 0;
+                        self.cur_sample = 0;
+                        self.cur_chunk = 0;
+                        let mut cmap = self.sample_map.iter();
+                        let mut cur_samps = 0;
+                        let (mut next_idx, mut next_samples) = cmap.next().unwrap();
+                        loop {
+                            if self.cur_chunk + 1 == next_idx as usize {
+                                self.samples_left = cur_samps;
+                                cur_samps = next_samples as usize;
+                                if let Some((new_idx, new_samples)) = cmap.next() {
+                                    next_idx = *new_idx;
+                                    next_samples = *new_samples;
+                                }
+                            }
+                            self.raw_apos = apos;
+                            apos += (cur_samps / self.frame_samples) as u64;
+                            if apos > exp_pts {
+                                if cur_samps == self.frame_samples || apos > exp_pts + 1 {
+                                    if self.cur_chunk >= self.chunk_offsets.len() {
+                                        return Err(DemuxerError::SeekError);
+                                    }
+                                    self.last_offset = self.chunk_offsets[self.cur_chunk];
+                                    break;
+                                }
+                            }
+                            self.cur_chunk += 1;
+                        }
+                        self.samples_left = cur_samps;
+                        self.cur_chunk += 1;
+                    } else {
+                        self.raw_apos = exp_pts;
+                        self.cur_sample = exp_pts as usize;
+                        let mut csamp = 0;
+                        self.cur_chunk = 0;
+                        let mut cmap = self.sample_map.iter();
+                        let mut cur_samps = 0;
+                        let (mut next_idx, mut next_samples) = cmap.next().unwrap();
+                        loop {
+                            if self.cur_chunk + 1 == next_idx as usize {
+                                self.samples_left = cur_samps;
+                                cur_samps = next_samples as usize;
+                                if let Some((new_idx, new_samples)) = cmap.next() {
+                                    next_idx = *new_idx;
+                                    next_samples = *new_samples;
+                                }
+                            }
+                            csamp += cur_samps;
+                            if csamp > self.cur_sample {
+                                if self.cur_chunk >= self.chunk_offsets.len() {
+                                    return Err(DemuxerError::SeekError);
+                                }
+                                self.last_offset = self.chunk_offsets[self.cur_chunk];
+                                break;
+                            }
+                            self.cur_chunk += 1;
+                        }
+                        self.samples_left = csamp - self.cur_sample;
+                        self.cur_chunk += 1;
+                    }
+                } else {
+                    self.cur_chunk = self.cur_sample;
+                }
+            } else {
+                self.cur_chunk = self.cur_sample;
+            }
         } else if self.chunk_offsets.len() != self.chunk_sizes.len() && !self.sample_map.is_empty() {
             let mut csamp = 0;
             self.cur_chunk = 0;
@@ -992,6 +1129,9 @@ impl Track {
                 }
                 csamp += cur_samps;
                 if csamp >= self.cur_sample {
+                    if self.cur_chunk >= self.chunk_offsets.len() {
+                        return Err(DemuxerError::SeekError);
+                    }
                     self.last_offset = self.chunk_offsets[self.cur_chunk];
                     break;
                 }
@@ -1004,6 +1144,7 @@ impl Track {
             self.samples_left = csamp + cur_samps - self.cur_sample;
             self.cur_chunk += 1;
         }
+        Ok(())
     }
 }
 
@@ -1054,7 +1195,7 @@ impl<'a> DemuxCore<'a> for MOVDemuxer<'a> {
         }
         let seek_info = ret.unwrap();
         for track in self.tracks.iter_mut() {
-            track.seek(seek_info.pts);
+            track.seek(seek_info.pts, time)?;
         }
         Ok(())
     }
@@ -1067,10 +1208,37 @@ impl<'a> DemuxCore<'a> for MOVDemuxer<'a> {
     }
 }
 
+const PRINT_CHUNKS: &str = "print_chunks";
+
+const DEMUXER_OPTIONS: &[NAOptionDefinition] = &[
+    NAOptionDefinition {
+        name:           PRINT_CHUNKS,
+        description:    "Print parsed file structure",
+        opt_type:       NAOptionDefinitionType::Bool },
+];
+
 impl<'a> NAOptionHandler for MOVDemuxer<'a> {
-    fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] }
-    fn set_options(&mut self, _options: &[NAOption]) { }
-    fn query_option_value(&self, _name: &str) -> Option<NAValue> { None }
+    fn get_supported_options(&self) -> &[NAOptionDefinition] { DEMUXER_OPTIONS }
+    fn set_options(&mut self, options: &[NAOption]) {
+        for option in options.iter() {
+            for opt_def in DEMUXER_OPTIONS.iter() {
+                if opt_def.check(option).is_ok() {
+                    match (option.name, &option.value) {
+                        (PRINT_CHUNKS, NAValue::Bool(val)) => {
+                            self.print_chunks = *val;
+                        },
+                        _ => {},
+                    }
+                }
+            }
+        }
+    }
+    fn query_option_value(&self, name: &str) -> Option<NAValue> {
+        match name {
+            PRINT_CHUNKS    => Some(NAValue::Bool(self.print_chunks)),
+            _ => None,
+        }
+    }
 }
 
 impl<'a> MOVDemuxer<'a> {
@@ -1085,6 +1253,8 @@ impl<'a> MOVDemuxer<'a> {
             tb_den:         0,
             duration:       0,
             pal:            None,
+
+            print_chunks:   false,
         }
     }
     fn read_root(&mut self, strmgr: &mut StreamManager) -> DemuxerResult<()> {