]> git.nihav.org Git - nihav.git/blame - nihav-rad/src/demuxers/smacker.rs
avimux: do not record palette change chunks in OpenDML index
[nihav.git] / nihav-rad / src / demuxers / smacker.rs
CommitLineData
606c448e 1use std::io::SeekFrom;
3687b8b3 2use std::cmp::Ordering;
606c448e
KS
3use nihav_core::demuxers::*;
4
5const SMK_FLAG_LOOP_FRAME: u32 = 0x01;
6
7const SMK_FRAME_FLAG_PALETTE: u8 = 0x01;
8
9const SMK_AUD_FLAG_PACKED: u8 = 0x80;
10const SMK_AUD_FLAG_PRESENT: u8 = 0x40;
11const SMK_AUD_FLAG_16BIT: u8 = 0x20;
12const SMK_AUD_FLAG_STEREO: u8 = 0x10;
13const SMK_AUD_FLAG_BINKAUD_RDFT: u8 = 0x08;
14const SMK_AUD_FLAG_BINKAUD_DCT: u8 = 0x04;
15
16const NUM_AUDIO_TRACKS: usize = 7;
17
18const PAL_SIZE: usize = 768;
19
20const SMK_DEFAULT_PAL: [u8; 64] = [
21 0x00, 0x04, 0x08, 0x0C, 0x10, 0x14, 0x18, 0x1C,
22 0x20, 0x24, 0x28, 0x2C, 0x30, 0x34, 0x38, 0x3C,
23 0x41, 0x45, 0x49, 0x4D, 0x51, 0x55, 0x59, 0x5D,
24 0x61, 0x65, 0x69, 0x6D, 0x71, 0x75, 0x79, 0x7D,
25 0x82, 0x86, 0x8A, 0x8E, 0x92, 0x96, 0x9A, 0x9E,
26 0xA2, 0xA6, 0xAA, 0xAE, 0xB2, 0xB6, 0xBA, 0xBE,
27 0xC3, 0xC7, 0xCB, 0xCF, 0xD3, 0xD7, 0xDB, 0xDF,
28 0xE3, 0xE7, 0xEB, 0xEF, 0xF3, 0xF7, 0xFB, 0xFF
29];
30
31#[derive(Clone,Copy)]
32struct AudioTrack {
33 size: u32,
34 flags: u8,
35 srate: u32,
36 id: usize,
37}
38
39impl AudioTrack {
40 fn new() -> Self {
41 Self { size: 0, flags: 0, srate: 0, id: 0 }
42 }
43 fn is_present(&self) -> bool {
44 (self.flags & SMK_AUD_FLAG_PRESENT) != 0
45 }
46 fn add_stream(&mut self, strmgr: &mut StreamManager, str_id: usize) -> DemuxerResult<()> {
47 let channels = if (self.flags & SMK_AUD_FLAG_STEREO) != 0 { 2 } else { 1 };
48 let codecname = if (self.flags & SMK_AUD_FLAG_BINKAUD_RDFT) != 0 {
49 "bink-audio-rdft"
50 } else if (self.flags & SMK_AUD_FLAG_BINKAUD_DCT) != 0 {
51 "bink-audio-dct"
52 } else if (self.flags & SMK_AUD_FLAG_PACKED) != 0 {
53 "smacker-audio"
54 } else {
55 "pcm"
56 };
57 let soniton = if (self.flags & SMK_AUD_FLAG_16BIT) != 0 { SND_S16_FORMAT } else { SND_U8_FORMAT };
58 let ahdr = NAAudioInfo::new(self.srate, channels, soniton, 1);
59 let ainfo = NACodecInfo::new(codecname, NACodecTypeInfo::Audio(ahdr), None);
a480a0de 60 let res = strmgr.add_stream(NAStream::new(StreamType::Audio, (str_id + 1) as u32, ainfo, 1, self.srate, 0));
606c448e
KS
61 validate!(res.is_some());
62 self.id = res.unwrap();
63
64 Ok(())
65 }
66}
67
68struct SmackerVideoDemuxer<'a> {
69 src: &'a mut ByteReader<'a>,
70 frames: usize,
71 pts_inc: u64,
72 cur_pts: u64,
73 ainfo: [AudioTrack; NUM_AUDIO_TRACKS],
74 frame_sizes: Vec<u32>,
75 frame_flags: Vec<u8>,
76 video_id: usize,
77 start: u64,
78 cur_frame: usize,
79 queued_packets: Vec<NAPacket>,
80 pal: [u8; PAL_SIZE],
81}
82
83/*macro_rules! mktag {
84 ($a:expr, $b:expr, $c:expr, $d:expr) => ({
85 (($a as u32) << 24) | (($b as u32) << 16) | (($c as u32) << 8) | ($d as u32)
86 });
87 ($arr:expr) => ({
88 (($arr[0] as u32) << 24) | (($arr[1] as u32) << 16) | (($arr[2] as u32) << 8) | ($arr[3] as u32)
89 });
90}*/
91
92fn get_pts_inc(val: i32) -> u64 {
3687b8b3
KS
93 match val.cmp(&0) {
94 Ordering::Greater => (val as u64) * 100,
95 Ordering::Less => -val as u64,
96 Ordering::Equal => 1,
97 }
606c448e
KS
98}
99
100impl<'a> DemuxCore<'a> for SmackerVideoDemuxer<'a> {
33b5a8f0 101 fn open(&mut self, strmgr: &mut StreamManager, _seek_idx: &mut SeekIndex) -> DemuxerResult<()> {
606c448e
KS
102 let src = &mut self.src;
103 let mut magic: [u8; 4] = [0; 4];
104 src.read_buf(&mut magic)?;
105 validate!((&magic == b"SMK2") || (&magic == b"SMK4"));
106 let width = src.read_u32le()? as usize;
107 let height = src.read_u32le()? as usize;
108 self.frames = src.read_u32le()? as usize;
109 let pts_inc = src.read_u32le()? as i32;
110 let flags = src.read_u32le()?;
111 validate!((width > 0) && (height > 0) && (self.frames > 0));
112 self.pts_inc = get_pts_inc(pts_inc);
113
114 if (flags & SMK_FLAG_LOOP_FRAME) != 0 {
115 self.frames += 1;
116 }
117
118 for i in 0..NUM_AUDIO_TRACKS {
119 self.ainfo[i].size = src.read_u32le()?;
120 }
121 let treesize = src.read_u32le()? as usize;
122
123 let mut treedata: Vec<u8> = Vec::with_capacity(treesize + 20);
124 treedata.resize(treesize + 24, 0);
125 let hdr = &mut treedata[0..4];
126 hdr.copy_from_slice(&magic);
127 let flg = &mut treedata[4..8];
128 flg[0] = (flags & 0xFF) as u8;
129 flg[1] = (flags >> 8) as u8;
130 flg[2] = (flags >> 16) as u8;
131 flg[3] = (flags >> 24) as u8;
132 src.read_buf(&mut treedata[8..][..16])?;
133
134 for i in 0..NUM_AUDIO_TRACKS {
135 self.ainfo[i].srate = src.read_u24le()?;
136 self.ainfo[i].flags = src.read_byte()?;
137 }
138
139 src.read_skip(4)?;
140
141 self.frame_sizes.resize(self.frames, 0);
142 self.frame_flags.resize(self.frames, 0);
143 for i in 0..self.frames {
144 self.frame_sizes[i] = src.read_u32le()?;
145 }
146 for i in 0..self.frames {
147 self.frame_flags[i] = src.read_byte()?;
148 }
149
150 src.read_buf(&mut treedata[24..])?;
151
152 let vhdr = NAVideoInfo::new(width, height, false, PAL8_FORMAT);
153 let vinfo = NACodecInfo::new("smacker-video", NACodecTypeInfo::Video(vhdr), Some(treedata));
a480a0de 154 let res = strmgr.add_stream(NAStream::new(StreamType::Video, 0, vinfo, 1, 100000, 0));
606c448e
KS
155 validate!(res.is_some());
156 self.video_id = res.unwrap();
157
158 for i in 0..NUM_AUDIO_TRACKS {
159 if self.ainfo[i].is_present() {
160 self.ainfo[i].add_stream(strmgr, i)?;
161 }
162 }
163
164 self.start = src.tell();
165 self.cur_frame = 0;
166 self.reset_state();
d24468d9 167
606c448e
KS
168 Ok(())
169 }
bf109afe 170 #[allow(clippy::identity_op)]
606c448e
KS
171 fn get_frame(&mut self, strmgr: &mut StreamManager) -> DemuxerResult<NAPacket> {
172 if !self.queued_packets.is_empty() {
173 let pkt = self.queued_packets.pop().unwrap();
174 return Ok(pkt);
175 }
176 if self.cur_frame >= self.frames { return Err(DemuxerError::EOF); }
177
178 let mut payload_size = (self.frame_sizes[self.cur_frame] & !3) as usize;
179 let frame_flags = self.frame_flags[self.cur_frame];
180 if (frame_flags & SMK_FRAME_FLAG_PALETTE) != 0 {
181 let chunk_size = (self.src.read_byte()? as usize) * 4;
182 validate!(chunk_size > 0);
183 validate!(payload_size >= chunk_size);
184 payload_size -= chunk_size;
7e6086e5 185 let oldpal = self.pal;
606c448e
KS
186 let mut idx = 0;
187 let endpos = self.src.tell() + (chunk_size as u64) - 1;
188 while idx < 256 {
189 let op = self.src.read_byte()?;
190 if (op & 0x80) != 0 {
191 idx += ((op & 0x7F) as usize) + 1;
192 } else if (op & 0x40) != 0 {
193 let start = self.src.read_byte()? as usize;
194 let len = ((op & 0x3F) as usize) + 1;
195 validate!(idx + len <= 256);
196 for i in 0..len*3 {
197 self.pal[idx * 3 + i] = oldpal[start * 3 + i];
198 }
199 idx += len;
200 } else {
201 let ix0 = op as usize;
202 let ix1 = (self.src.read_byte()? & 0x3F) as usize;
203 let ix2 = (self.src.read_byte()? & 0x3F) as usize;
204 self.pal[idx * 3 + 0] = SMK_DEFAULT_PAL[ix0];
205 self.pal[idx * 3 + 1] = SMK_DEFAULT_PAL[ix1];
206 self.pal[idx * 3 + 2] = SMK_DEFAULT_PAL[ix2];
207 idx += 1;
208 }
209 }
210 validate!(self.src.tell() <= endpos);
211 if self.src.tell() < endpos {
212 let skip_size = endpos - self.src.tell();
213 self.src.read_skip(skip_size as usize)?;
214 }
215 }
216
217 let ts = NATimeInfo::new(Some(self.cur_pts), None, None, 1, 100000);
218 for i in 0..NUM_AUDIO_TRACKS {
219 if ((frame_flags >> (i + 1)) & 1) == 0 { continue; }
606c448e
KS
220 let size = self.src.read_u32le()? as usize;
221 validate!(size > 4);
222 validate!(payload_size >= size);
223 payload_size -= size;
dc19613e
KS
224 if !self.ainfo[i].is_present() {
225 self.src.read_skip(size - 4)?;
226 continue;
227 }
606c448e
KS
228 let strres = strmgr.get_stream(self.ainfo[i].id);
229 validate!(strres.is_some());
230 let stream = strres.unwrap();
231 let pkt = self.src.read_packet(stream.clone(), ts, true, size - 4)?;
232 self.queued_packets.push(pkt);
233 }
234 self.queued_packets.reverse();
235
236 let mut buf: Vec<u8> = Vec::with_capacity(PAL_SIZE + payload_size);
237 buf.resize(PAL_SIZE, 0);
238 buf.copy_from_slice(&self.pal[0..]);
239 if payload_size > 0 {
240 buf.resize(PAL_SIZE + payload_size, 0);
241 self.src.read_buf(&mut buf[PAL_SIZE..])?;
242 }
243
244 let strres = strmgr.get_stream(self.video_id);
245 validate!(strres.is_some());
246 let stream = strres.unwrap();
247 let keyframe = (self.frame_sizes[self.cur_frame] & 1) != 0;
248 let pkt = NAPacket::new(stream, ts, keyframe, buf);
249
250 self.cur_frame += 1;
251 self.cur_pts += self.pts_inc;
252
253 Ok(pkt)
254 }
24d99894 255 fn seek(&mut self, time: NATimePoint, _seek_idx: &SeekIndex) -> DemuxerResult<()> {
e6aaad5c 256 let seek_to_start = matches!(time, NATimePoint::Milliseconds(0) | NATimePoint::PTS(0));
24d99894 257 if seek_to_start {
606c448e
KS
258 let start = self.start;
259 self.src.seek(SeekFrom::Start(start))?;
260 self.cur_frame = 0;
261 self.cur_pts = 0;
262 self.reset_state();
263 return Ok(());
264 }
265 Err(DemuxerError::NotImplemented)
266 }
a480a0de 267 fn get_duration(&self) -> u64 { self.frames as u64 * self.pts_inc / 100 }
606c448e
KS
268}
269
787b8d03
KS
270impl<'a> NAOptionHandler for SmackerVideoDemuxer<'a> {
271 fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] }
272 fn set_options(&mut self, _options: &[NAOption]) { }
273 fn query_option_value(&self, _name: &str) -> Option<NAValue> { None }
274}
275
606c448e
KS
276impl<'a> SmackerVideoDemuxer<'a> {
277 fn new(io: &'a mut ByteReader<'a>) -> Self {
278 SmackerVideoDemuxer {
279 src: io,
280 frames: 0,
281 pts_inc: 0,
282 cur_pts: 0,
283 ainfo: [AudioTrack::new(); NUM_AUDIO_TRACKS],
284 frame_sizes: Vec::new(),
285 frame_flags: Vec::new(),
286 video_id: 0,
287 start: 0,
288 cur_frame: 0,
289 queued_packets: Vec::new(),
290 pal: [0; PAL_SIZE],
291 }
292 }
293 fn reset_state(&mut self) {
37952415 294 self.queued_packets.clear();
606c448e
KS
295 }
296}
297
298pub struct SMKDemuxerCreator { }
299
300impl DemuxerCreator for SMKDemuxerCreator {
6011e201 301 fn new_demuxer<'a>(&self, br: &'a mut ByteReader<'a>) -> Box<dyn DemuxCore<'a> + 'a> {
606c448e
KS
302 Box::new(SmackerVideoDemuxer::new(br))
303 }
304 fn get_name(&self) -> &'static str { "smacker" }
305}
306
307#[cfg(test)]
308mod test {
309 use super::*;
310 use std::fs::File;
311
312 #[test]
313 fn test_smk_demux() {
886cde48 314 // sample: https://samples.mplayerhq.hu/game-formats/smacker/20130507_audio-distortion.smk
606c448e
KS
315 let mut file = File::open("assets/RAD/20130507_audio-distortion.smk").unwrap();
316// let mut file = File::open("assets/RAD/ajfstr1.smk").unwrap();
317// let mut file = File::open("assets/RAD/credits.smk").unwrap();
318// let mut file = File::open("assets/RAD/wetlogo.smk").unwrap();
319 let mut fr = FileReader::new_read(&mut file);
320 let mut br = ByteReader::new(&mut fr);
321 let mut dmx = SmackerVideoDemuxer::new(&mut br);
322 let mut sm = StreamManager::new();
caf0f37e
KS
323 let mut si = SeekIndex::new();
324 dmx.open(&mut sm, &mut si).unwrap();
606c448e
KS
325 loop {
326 let pktres = dmx.get_frame(&mut sm);
327 if let Err(e) = pktres {
328 if (e as i32) == (DemuxerError::EOF as i32) { break; }
329 panic!("error");
330 }
331 let pkt = pktres.unwrap();
332 println!("Got {}", pkt);
333 }
334 }
335}