1 use nihav_core::formats;
2 use nihav_core::codecs::*;
3 use nihav_core::io::byteio::*;
6 const FRAME_W: usize = 640;
7 const FRAME_H: usize = 429;
9 const BMV_INTRA: u8 = 0x03;
10 const BMV_SCROLL: u8 = 0x04;
11 const BMV_PAL: u8 = 0x08;
12 const BMV_COMMAND: u8 = 0x10;
13 const BMV_PRINT: u8 = 0x80;
15 struct BMVReader<'a> {
23 impl<'a> BMVReader<'a> {
24 fn new(src: &'a [u8], fwd: bool) -> Self {
25 let pos = if fwd { 0 } else { src.len() - 1 };
26 Self { src, pos, fwd, nibble: 0, saved: false }
28 #[allow(clippy::collapsible_else_if)]
29 fn advance(&mut self) {
31 if self.pos < self.src.len() - 1 { self.pos += 1; }
33 if self.pos > 0 { self.pos -= 1; }
36 fn get_byte(&mut self) -> u8 {
37 let ret = self.src[self.pos];
41 fn get_nibble(&mut self) -> u8 {
46 let val = self.get_byte();
48 self.nibble = val >> 4;
54 struct BMVWriter<'a> {
61 impl<'a> BMVWriter<'a> {
62 fn new(data: &'a mut [u8], fwd: bool, off: isize) -> Self {
63 let pos = if fwd { 0 } else { data.len() - 1 };
64 Self { data, pos, fwd, off }
66 fn is_at_end(&self) -> bool {
68 self.pos == self.data.len() - 1
73 #[allow(clippy::collapsible_else_if)]
74 fn advance(&mut self) {
76 if self.pos < self.data.len() - 1 { self.pos += 1; }
78 if self.pos > 0 { self.pos -= 1; }
81 fn put_byte(&mut self, val: u8) {
82 self.data[self.pos] = val;
85 fn copy(&mut self, len: usize) {
87 let saddr = (self.pos as isize) + self.off;
88 if saddr < 0 { continue; }
89 self.data[self.pos] = self.data[saddr as usize];
93 fn repeat(&mut self, len: usize) {
94 let last = if self.fwd { self.data[self.pos - 1] } else { self.data[self.pos + 1] };
101 struct BMVVideoDecoder {
102 info: NACodecInfoRef,
104 frame: [u8; FRAME_W * FRAME_H],
107 impl BMVVideoDecoder {
110 info: NACodecInfoRef::default(), pal: [0; 768], frame: [0; FRAME_W * FRAME_H],
113 fn decode_frame(&mut self, src: &[u8], bufinfo: &mut NABufferType, line: i16) -> DecoderResult<()> {
114 let bufo = bufinfo.get_vbuf();
115 let mut buf = bufo.unwrap();
116 let paloff = buf.get_offset(1);
117 let stride = buf.get_stride(0);
118 let data = buf.get_data_mut().unwrap();
119 let dst = data.as_mut_slice();
121 let fwd = (line <= -640) || (line >= 0);
123 let mut br = BMVReader::new(src, fwd);
124 let mut bw = BMVWriter::new(&mut self.frame, fwd, line as isize);
126 while !bw.is_at_end() {
130 let nib = br.get_nibble() as usize;
131 if (nib & 0xC) != 0 {
146 let len = (val >> 1) - 1;
150 2 => for _ in 0..len { bw.put_byte(br.get_byte()); },
156 for y in 0..FRAME_H {
157 for x in 0..FRAME_W {
158 dst[y * stride + x] = self.frame[y * FRAME_W + x];
162 let dpal = &mut dst[paloff..][..768];
163 dpal.copy_from_slice(&self.pal[0..]);
169 impl NADecoder for BMVVideoDecoder {
170 fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> {
171 if let NACodecTypeInfo::Video(_vinfo) = info.get_properties() {
172 let fmt = NAPixelFormaton::new(ColorModel::RGB(RGBSubmodel::RGB),
173 Some(NAPixelChromaton::new(0, 0, true, 8, 0, 0, 3)),
174 Some(NAPixelChromaton::new(0, 0, true, 8, 0, 1, 3)),
175 Some(NAPixelChromaton::new(0, 0, true, 8, 0, 2, 3)),
177 FORMATON_FLAG_PALETTE, 3);
178 let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(FRAME_W, FRAME_H, false, fmt));
179 self.info = NACodecInfo::new_ref(info.get_name(), myinfo, info.get_extradata()).into_ref();
183 Err(DecoderError::InvalidData)
186 fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult<NAFrameRef> {
187 let src = pkt.get_buffer();
188 validate!(src.len() > 1);
190 let mut mr = MemoryReader::new_read(&src);
191 let mut br = ByteReader::new(&mut mr);
192 let flags = br.read_byte()?;
194 if (flags & BMV_COMMAND) != 0 {
195 let size = if (flags & BMV_PRINT) != 0 { 8 } else { 10 };
198 if (flags & BMV_PAL) != 0 {
199 br.read_buf(&mut self.pal)?;
202 if (flags & BMV_SCROLL) != 0 {
203 line = br.read_u16le()? as i16;
204 } else if (flags & BMV_INTRA) == BMV_INTRA {
209 let pos = br.tell() as usize;
211 let mut bufinfo = alloc_video_buffer(self.info.get_properties().get_video_info().unwrap(), 0)?;
213 self.decode_frame(&src[pos..], &mut bufinfo, line)?;
215 let is_intra = (flags & BMV_INTRA) == BMV_INTRA;
216 let mut frm = NAFrame::new_from_pkt(pkt, self.info.clone(), bufinfo);
217 frm.set_keyframe(is_intra);
218 frm.set_frame_type(if is_intra { FrameType::I } else { FrameType::P });
221 fn flush(&mut self) {
225 impl NAOptionHandler for BMVVideoDecoder {
226 fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] }
227 fn set_options(&mut self, _options: &[NAOption]) { }
228 fn query_option_value(&self, _name: &str) -> Option<NAValue> { None }
232 pub fn get_decoder_video() -> Box<dyn NADecoder + Send> {
233 Box::new(BMVVideoDecoder::new())
236 struct BMVAudioDecoder {
241 const BMV_AUD_SCALES: [i32; 16] = [ 16512, 8256, 4128, 2064, 1032, 516, 258, 192, 129, 88, 64, 56, 48, 40, 36, 32 ];
243 impl BMVAudioDecoder {
246 ainfo: NAAudioInfo::new(0, 1, formats::SND_S16P_FORMAT, 0),
247 chmap: NAChannelMap::new(),
252 fn scale_sample(samp: u8, scale: i32) -> i16 {
253 let val = (i32::from(samp as i8) * scale) >> 5;
256 } else if val > 32767 {
263 impl NADecoder for BMVAudioDecoder {
264 fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> {
265 if let NACodecTypeInfo::Audio(ainfo) = info.get_properties() {
266 self.ainfo = NAAudioInfo::new(ainfo.get_sample_rate(), ainfo.get_channels(), formats::SND_S16P_FORMAT, 32);
267 self.chmap = NAChannelMap::from_str("L,R").unwrap();
270 Err(DecoderError::InvalidData)
273 fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult<NAFrameRef> {
274 let info = pkt.get_stream().get_info();
275 if let NACodecTypeInfo::Audio(_) = info.get_properties() {
276 let pktbuf = pkt.get_buffer();
277 validate!(pktbuf.len() > 1);
278 let nblocks = pktbuf[0] as usize;
279 validate!(pktbuf.len() == 1 + 65 * nblocks);
280 let samples = nblocks * 32;
281 let abuf = alloc_audio_buffer(self.ainfo, samples, self.chmap.clone())?;
282 let mut adata = abuf.get_abuf_i16().unwrap();
283 let off1 = adata.get_offset(1);
284 let dst = adata.get_data_mut().unwrap();
285 let psrc = &pktbuf[1..];
286 for (n, src) in psrc.chunks_exact(65).enumerate() {
287 let code = src[0].rotate_right(1);
288 let scale0 = BMV_AUD_SCALES[(code & 0xF) as usize];
289 let scale1 = BMV_AUD_SCALES[(code >> 4) as usize];
291 let aoff1 = aoff0 + off1;
292 let data = &src[1..];
293 for (i, samp) in data.chunks_exact(2).enumerate() {
294 dst[aoff0 + i] = scale_sample(samp[0], scale0);
295 dst[aoff1 + i] = scale_sample(samp[1], scale1);
298 let mut frm = NAFrame::new_from_pkt(pkt, info, abuf);
299 frm.set_duration(Some(samples as u64));
300 frm.set_keyframe(false);
303 Err(DecoderError::InvalidData)
306 fn flush(&mut self) {
310 impl NAOptionHandler for BMVAudioDecoder {
311 fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] }
312 fn set_options(&mut self, _options: &[NAOption]) { }
313 fn query_option_value(&self, _name: &str) -> Option<NAValue> { None }
316 pub fn get_decoder_audio() -> Box<dyn NADecoder + Send> {
317 Box::new(BMVAudioDecoder::new())
322 use nihav_core::codecs::RegisteredDecoders;
323 use nihav_core::demuxers::RegisteredDemuxers;
324 use nihav_codec_support::test::dec_video::*;
325 use crate::game_register_all_decoders;
326 use crate::game_register_all_demuxers;
327 // samples from: https://samples.mplayerhq.hu/game-formats/bmv/
329 fn test_bmv_video() {
330 let mut dmx_reg = RegisteredDemuxers::new();
331 game_register_all_demuxers(&mut dmx_reg);
332 let mut dec_reg = RegisteredDecoders::new();
333 game_register_all_decoders(&mut dec_reg);
335 test_decoding("bmv", "bmv-video", "assets/Game/WILDCAT.BMV", Some(40), &dmx_reg, &dec_reg,
336 ExpectedTestResult::MD5([0x9e91bb16, 0xc1edafc9, 0x4ef3171f, 0x0f3f6181]));
339 fn test_bmv_audio() {
340 let mut dmx_reg = RegisteredDemuxers::new();
341 game_register_all_demuxers(&mut dmx_reg);
342 let mut dec_reg = RegisteredDecoders::new();
343 game_register_all_decoders(&mut dec_reg);
345 test_decoding("bmv", "bmv-audio", "assets/Game/PERFECT.BMV", None, &dmx_reg, &dec_reg,
346 ExpectedTestResult::MD5([0x90b9ace4, 0x5fc19938, 0x7f534560, 0x32589cdf]));