]>
Commit | Line | Data |
---|---|---|
9895bd7b KS |
1 | use nihav_core::frame::*; |
2 | use nihav_core::demuxers::*; | |
3 | use std::io::SeekFrom; | |
4 | ||
5563dfce KS |
5 | const OLD_HEADER_SIZE: usize = 0x330; |
6 | const NEW_HEADER_SIZE: usize = 0x34; | |
7 | const HEADER1_SIZE: usize = 28; | |
8 | const HEADER2_OFF: usize = HEADER1_SIZE + 768; | |
9895bd7b KS |
9 | const FRAME_HDR_SIZE: usize = 10; |
10 | ||
11 | const CHTYPE_VIDEO: u8 = 0x02; | |
12 | const CHTYPE_AUDIO: u8 = 0x01; | |
13 | ||
14 | #[derive(Clone,Copy)] | |
15 | struct FrameRec { | |
16 | chtype: u8, | |
17 | size: u32, | |
18 | off: u32, | |
19 | hdr: [u8; FRAME_HDR_SIZE], | |
20 | ts: u32, | |
21 | } | |
22 | ||
23 | struct VMDDemuxer<'a> { | |
24 | src: &'a mut ByteReader<'a>, | |
25 | vid_id: usize, | |
26 | aud_id: usize, | |
27 | fno: usize, | |
28 | is_indeo: bool, | |
92355de1 | 29 | is_lhaud: bool, |
9895bd7b KS |
30 | frames: Vec<FrameRec>, |
31 | } | |
32 | ||
33 | impl<'a> DemuxCore<'a> for VMDDemuxer<'a> { | |
33b5a8f0 | 34 | fn open(&mut self, strmgr: &mut StreamManager, _seek_index: &mut SeekIndex) -> DemuxerResult<()> { |
9895bd7b KS |
35 | let src = &mut self.src; |
36 | ||
5563dfce KS |
37 | let mut header: [u8; OLD_HEADER_SIZE] = [0; OLD_HEADER_SIZE]; |
38 | src.read_buf(&mut header[..HEADER1_SIZE])?; | |
39 | let hdr_size = read_u16le(&header)? as usize + 2; | |
40 | validate!(hdr_size == OLD_HEADER_SIZE || hdr_size == NEW_HEADER_SIZE); | |
41 | if hdr_size == OLD_HEADER_SIZE { | |
42 | src.read_buf(&mut header[HEADER1_SIZE..][..768])?; | |
43 | } | |
44 | src.read_buf(&mut header[HEADER2_OFF..])?; | |
9895bd7b KS |
45 | |
46 | let mut width = read_u16le(&header[12..])? as usize; | |
47 | let mut height = read_u16le(&header[14..])? as usize; | |
1dd5b723 | 48 | self.is_indeo = &header[24..27] == b"iv3" || &header[24..27] == b"IV3"; |
9895bd7b KS |
49 | if self.is_indeo && width > 320 { |
50 | width >>= 1; | |
51 | height >>= 1; | |
52 | } | |
53 | ||
54 | let nframes = read_u16le(&header[6..])? as usize; | |
55 | let fpb = read_u16le(&header[18..])? as usize; | |
56 | validate!(nframes > 0 && fpb > 0); | |
d24468d9 | 57 | |
5563dfce | 58 | let mut edata: Vec<u8> = Vec::with_capacity(OLD_HEADER_SIZE); |
9895bd7b KS |
59 | edata.extend_from_slice(&header); |
60 | let vhdr = NAVideoInfo::new(width, height, false, PAL8_FORMAT); | |
61 | let vci = NACodecTypeInfo::Video(vhdr); | |
62 | let vinfo = NACodecInfo::new(if !self.is_indeo { "vmd-video" } else { "indeo3" }, vci, Some(edata)); | |
a480a0de | 63 | self.vid_id = strmgr.add_stream(NAStream::new(StreamType::Video, 0, vinfo, 1, 12, nframes as u64)).unwrap(); |
9895bd7b | 64 | |
5563dfce KS |
65 | let is_ext_audio = (hdr_size & 0xF) == 4; |
66 | let ext_audio_id = if is_ext_audio { | |
67 | src.read_u16le()? | |
68 | } else { 0 }; | |
69 | if is_ext_audio { | |
cb56d166 | 70 | validate!((3..=6).contains(&ext_audio_id)); |
92355de1 | 71 | self.is_lhaud = true; |
5563dfce | 72 | } |
e69b1148 | 73 | let srate = u32::from(read_u16le(&header[804..])?); |
aa7e65d2 | 74 | let block_size; |
9895bd7b KS |
75 | if srate > 0 { |
76 | let bsize = read_u16le(&header[806..])? as usize; | |
aa7e65d2 | 77 | let channels = if (header[811] & 0x8F) != 0 { 2 } else { 1 }; |
9895bd7b | 78 | let is16bit; |
9895bd7b KS |
79 | if (bsize & 0x8000) != 0 { |
80 | is16bit = true; | |
81 | block_size = 0x10000 - bsize; | |
82 | } else { | |
83 | is16bit = false; | |
84 | block_size = bsize; | |
85 | } | |
86 | ||
f45dfcf7 KS |
87 | let mut aedata: Vec<u8> = Vec::with_capacity(2); |
88 | aedata.extend_from_slice(&header[810..][..2]); | |
9895bd7b | 89 | let ahdr = NAAudioInfo::new(srate, channels, if is16bit { SND_S16P_FORMAT } else { SND_U8_FORMAT }, block_size); |
5563dfce KS |
90 | let ac_name = if !is_ext_audio { |
91 | "vmd-audio" | |
92 | } else { | |
92355de1 KS |
93 | match ext_audio_id { |
94 | 3 => "lhst15f8", | |
95 | 4 => "lhst500f22", | |
96 | 5 => "lhst250f11", | |
97 | 6 => "lhst48", | |
98 | _ => "unknown", | |
99 | } | |
5563dfce KS |
100 | }; |
101 | let ainfo = NACodecInfo::new(ac_name, NACodecTypeInfo::Audio(ahdr), Some(aedata)); | |
a480a0de | 102 | self.aud_id = strmgr.add_stream(NAStream::new(StreamType::Audio, 1, ainfo, 1, srate, 0)).unwrap(); |
aa7e65d2 KS |
103 | } else { |
104 | block_size = 0; | |
9895bd7b KS |
105 | } |
106 | ||
e69b1148 KS |
107 | let adelay = u32::from(read_u16le(&header[808..])?); |
108 | let idx_off = u64::from(read_u32le(&header[812..])?); | |
9895bd7b KS |
109 | src.seek(SeekFrom::Start(idx_off))?; |
110 | let mut offs: Vec<u32> = Vec::with_capacity(nframes); | |
cb56d166 | 111 | for _ in 0..nframes { |
9895bd7b KS |
112 | let _flags = src.read_u16le()?; |
113 | let off = src.read_u32le()?; | |
114 | offs.push(off); | |
115 | } | |
116 | self.frames.reserve(nframes * fpb); | |
117 | let mut ats = adelay; | |
cb56d166 KS |
118 | for (i, &offset) in offs.iter().enumerate() { |
119 | let mut off = offset; | |
9895bd7b KS |
120 | for _ in 0..fpb { |
121 | let chtype = src.read_byte()?; | |
122 | src.read_skip(1)?; | |
aa7e65d2 | 123 | let mut size = src.read_u32le()?; |
9895bd7b KS |
124 | let mut hdr: [u8; FRAME_HDR_SIZE] = [0; FRAME_HDR_SIZE]; |
125 | src.read_buf(&mut hdr)?; | |
aa7e65d2 KS |
126 | if (i == 0) && (chtype == CHTYPE_AUDIO) && (size > 4) && ((size as usize) < block_size/2) { |
127 | size += 0x10000; | |
128 | } | |
9895bd7b KS |
129 | if (chtype == CHTYPE_VIDEO || chtype == CHTYPE_AUDIO) && (size > 0) { |
130 | let ts = if (i == 0) || (chtype != CHTYPE_AUDIO) { | |
131 | i as u32 | |
132 | } else { | |
133 | ats | |
134 | }; | |
135 | self.frames.push(FrameRec { chtype, size, hdr, off, ts }); | |
136 | } | |
137 | if i > 0 && chtype == CHTYPE_AUDIO { | |
138 | ats += 1; | |
139 | } | |
140 | if chtype != 0 { | |
141 | validate!(off.checked_add(size).is_some()); | |
142 | off += size; | |
143 | } | |
144 | } | |
145 | } | |
146 | ||
147 | self.fno = 0; | |
148 | Ok(()) | |
149 | } | |
150 | ||
151 | fn get_frame(&mut self, strmgr: &mut StreamManager) -> DemuxerResult<NAPacket> { | |
152 | if self.fno >= self.frames.len() { return Err(DemuxerError::EOF); } | |
153 | let cur_frame = &self.frames[self.fno]; | |
154 | //println!("fno {} -> type {} size {} @ {:X} ts {}", self.fno, cur_frame.chtype, cur_frame.size, cur_frame.off, cur_frame.ts); | |
e69b1148 | 155 | let next_pos = u64::from(cur_frame.off); |
9895bd7b KS |
156 | if self.src.tell() != next_pos { |
157 | self.src.seek(SeekFrom::Start(next_pos))?; | |
158 | } | |
159 | ||
160 | let is_video = cur_frame.chtype == CHTYPE_VIDEO; | |
161 | let mut buf: Vec<u8> = Vec::with_capacity(FRAME_HDR_SIZE + (cur_frame.size as usize)); | |
7450554d | 162 | if !((is_video && self.is_indeo) || (!is_video && self.is_lhaud)) { |
9895bd7b KS |
163 | buf.extend_from_slice(&cur_frame.hdr); |
164 | buf.resize(FRAME_HDR_SIZE + (cur_frame.size as usize), 0); | |
165 | self.src.read_buf(&mut buf[FRAME_HDR_SIZE..])?; | |
166 | } else { | |
167 | buf.resize(cur_frame.size as usize, 0); | |
168 | self.src.read_buf(&mut buf)?; | |
169 | } | |
170 | ||
171 | self.fno += 1; | |
172 | ||
173 | let str_id = if is_video { self.vid_id } else { self.aud_id }; | |
817e4872 KS |
174 | let stream = strmgr.get_stream(str_id).unwrap(); |
175 | let ts = stream.make_ts(Some(u64::from(cur_frame.ts)), None, None); | |
176 | let pkt = NAPacket::new(stream, ts, false, buf); | |
9895bd7b KS |
177 | |
178 | Ok(pkt) | |
179 | } | |
180 | ||
24d99894 | 181 | fn seek(&mut self, _time: NATimePoint, _seek_index: &SeekIndex) -> DemuxerResult<()> { |
33b5a8f0 | 182 | Err(DemuxerError::NotPossible) |
9895bd7b | 183 | } |
a480a0de KS |
184 | |
185 | fn get_duration(&self) -> u64 { 0 } | |
9895bd7b KS |
186 | } |
187 | ||
787b8d03 KS |
188 | impl<'a> NAOptionHandler for VMDDemuxer<'a> { |
189 | fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] } | |
190 | fn set_options(&mut self, _options: &[NAOption]) { } | |
191 | fn query_option_value(&self, _name: &str) -> Option<NAValue> { None } | |
192 | } | |
193 | ||
9895bd7b KS |
194 | impl<'a> VMDDemuxer<'a> { |
195 | fn new(io: &'a mut ByteReader<'a>) -> Self { | |
196 | Self { | |
197 | src: io, | |
198 | vid_id: 0, | |
199 | aud_id: 0, | |
200 | fno: 0, | |
201 | is_indeo: false, | |
92355de1 | 202 | is_lhaud: false, |
9895bd7b KS |
203 | frames: Vec::new(), |
204 | } | |
205 | } | |
206 | } | |
207 | ||
208 | pub struct VMDDemuxerCreator { } | |
209 | ||
210 | impl DemuxerCreator for VMDDemuxerCreator { | |
6011e201 | 211 | fn new_demuxer<'a>(&self, br: &'a mut ByteReader<'a>) -> Box<dyn DemuxCore<'a> + 'a> { |
9895bd7b KS |
212 | Box::new(VMDDemuxer::new(br)) |
213 | } | |
214 | fn get_name(&self) -> &'static str { "vmd" } | |
215 | } | |
216 | ||
217 | #[cfg(test)] | |
218 | mod test { | |
219 | use super::*; | |
220 | use std::fs::File; | |
221 | ||
222 | #[test] | |
223 | fn test_vmd_demux() { | |
886cde48 | 224 | // sample: https://samples.mplayerhq.hu/game-formats/sierra-vmd/Lighthouse/128.vmd |
3900194b | 225 | let mut file = File::open("assets/Game/sierra/128.vmd").unwrap(); |
1678d59a | 226 | //let mut file = File::open("assets/Game/1491.VMD").unwrap(); |
9895bd7b KS |
227 | let mut fr = FileReader::new_read(&mut file); |
228 | let mut br = ByteReader::new(&mut fr); | |
229 | let mut dmx = VMDDemuxer::new(&mut br); | |
230 | let mut sm = StreamManager::new(); | |
caf0f37e KS |
231 | let mut si = SeekIndex::new(); |
232 | dmx.open(&mut sm, &mut si).unwrap(); | |
9895bd7b KS |
233 | loop { |
234 | let pktres = dmx.get_frame(&mut sm); | |
235 | if let Err(e) = pktres { | |
236 | if (e as i32) == (DemuxerError::EOF as i32) { break; } | |
237 | panic!("error"); | |
238 | } | |
239 | let pkt = pktres.unwrap(); | |
240 | println!("Got {}", pkt); | |
241 | } | |
242 | } | |
243 | } |