Flash support
authorKostya Shishkov <kostya.shishkov@gmail.com>
Tue, 2 Nov 2021 17:20:06 +0000 (18:20 +0100)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Tue, 2 Nov 2021 17:20:06 +0000 (18:20 +0100)
20 files changed:
nihav-allstuff/Cargo.toml
nihav-allstuff/src/lib.rs
nihav-duck/src/codecs/mod.rs
nihav-duck/src/codecs/vp56.rs
nihav-duck/src/codecs/vp6.rs
nihav-duck/src/codecs/vp6enc/mod.rs
nihav-flash/Cargo.toml [new file with mode: 0644]
nihav-flash/src/codecs/adpcm.rs [new file with mode: 0644]
nihav-flash/src/codecs/adpcmenc.rs [new file with mode: 0644]
nihav-flash/src/codecs/asao.rs [new file with mode: 0644]
nihav-flash/src/codecs/flashsv.rs [new file with mode: 0644]
nihav-flash/src/codecs/flv263.rs [new file with mode: 0644]
nihav-flash/src/codecs/mod.rs [new file with mode: 0644]
nihav-flash/src/demuxers/flv.rs [new file with mode: 0644]
nihav-flash/src/demuxers/mod.rs [new file with mode: 0644]
nihav-flash/src/lib.rs [new file with mode: 0644]
nihav-flash/src/muxers/flv.rs [new file with mode: 0644]
nihav-flash/src/muxers/mod.rs [new file with mode: 0644]
nihav-registry/src/detect.rs
nihav-registry/src/register.rs

index 372030bb42c1716ed535f381885cb45b64c41340..6e97f02bb4aa2cbb719133307bb039dd9e4f4f1f 100644 (file)
@@ -8,6 +8,7 @@ edition = "2018"
 nihav_core = { path = "../nihav-core" }
 nihav_commonfmt = { path = "../nihav-commonfmt" }
 nihav_duck = { path = "../nihav-duck" }
+nihav_flash = { path = "../nihav-flash" }
 nihav_game = { path = "../nihav-game" }
 nihav_indeo = { path = "../nihav-indeo" }
 nihav_itu = { path = "../nihav-itu" }
index 99c8ad64ca134a9c44df5ce87c64d14909a816b5..f34a61ba281dff995472c2061dca89fc61b69ed4 100644 (file)
@@ -26,6 +26,11 @@ use nihav_duck::duck_register_all_decoders;
 use nihav_duck::duck_register_all_demuxers;
 use nihav_duck::duck_register_all_encoders;
 
+use nihav_flash::flash_register_all_decoders;
+use nihav_flash::flash_register_all_demuxers;
+use nihav_flash::flash_register_all_encoders;
+use nihav_flash::flash_register_all_muxers;
+
 use nihav_game::game_register_all_decoders;
 use nihav_game::game_register_all_demuxers;
 
@@ -56,6 +61,7 @@ use nihav_vivo::vivo_register_all_demuxers;
 pub fn nihav_register_all_decoders(rd: &mut RegisteredDecoders) {
     generic_register_all_decoders(rd);
     duck_register_all_decoders(rd);
+    flash_register_all_decoders(rd);
     game_register_all_decoders(rd);
     indeo_register_all_decoders(rd);
     itu_register_all_decoders(rd);
@@ -71,6 +77,7 @@ pub fn nihav_register_all_decoders(rd: &mut RegisteredDecoders) {
 pub fn nihav_register_all_demuxers(rd: &mut RegisteredDemuxers) {
     duck_register_all_demuxers(rd);
     generic_register_all_demuxers(rd);
+    flash_register_all_demuxers(rd);
     game_register_all_demuxers(rd);
     llaudio_register_all_demuxers(rd);
     rad_register_all_demuxers(rd);
@@ -80,6 +87,7 @@ pub fn nihav_register_all_demuxers(rd: &mut RegisteredDemuxers) {
 
 /// Registers all known encoders.
 pub fn nihav_register_all_encoders(re: &mut RegisteredEncoders) {
+    flash_register_all_encoders(re);
     generic_register_all_encoders(re);
     duck_register_all_encoders(re);
     llaudio_register_all_encoders(re);
@@ -88,6 +96,7 @@ pub fn nihav_register_all_encoders(re: &mut RegisteredEncoders) {
 
 /// Registers all known demuxers.
 pub fn nihav_register_all_muxers(rm: &mut RegisteredMuxers) {
+    flash_register_all_muxers(rm);
     generic_register_all_muxers(rm);
     llaudio_register_all_muxers(rm);
 }
index 38c25d0834214a11847ce78c2613baff6138d7e7..6086150d85831b39045da8705feec29214122864 100644 (file)
@@ -98,6 +98,8 @@ const DUCK_CODECS: &[DecoderInfo] = &[
     DecoderInfo { name: "vp5", get_decoder: vp5::get_decoder },
 #[cfg(feature="decoder_vp6")]
     DecoderInfo { name: "vp6", get_decoder: vp6::get_decoder_vp6 },
+#[cfg(feature="decoder_vp6")]
+    DecoderInfo { name: "vp6f", get_decoder: vp6::get_decoder_vp6f },
 #[cfg(feature="decoder_vp6")]
     DecoderInfo { name: "vp6a", get_decoder: vp6::get_decoder_vp6_alpha },
 #[cfg(feature="decoder_vp7")]
@@ -129,6 +131,8 @@ mod vp6enc;
 const DUCK_ENCODERS: &[EncoderInfo] = &[
 #[cfg(feature="encoder_vp6")]
     EncoderInfo { name: "vp6", get_encoder: vp6enc::get_encoder },
+#[cfg(feature="encoder_vp6")]
+    EncoderInfo { name: "vp6f", get_encoder: vp6enc::get_encoder_flv },
 ];
 
 /// Registers all available encoders provided by this crate.
index 807756a495687533836950daa513237d528c2c14..01e8171da21853c6760bf98d9e0c0a376e0a34ef 100644 (file)
@@ -486,7 +486,7 @@ impl VP56Decoder {
         let hdr = br.parse_header(&mut bc)?;
         validate!((hdr.offset as usize) < aoffset); //XXX: take alpha 3 byte offset into account?
 
-        if hdr.mb_w != 0 {
+        if hdr.mb_w != 0 && (usize::from(hdr.mb_w) != self.mb_w || usize::from(hdr.mb_h) != self.mb_h) {
             self.set_dimensions((hdr.mb_w as usize) * 16, (hdr.mb_h as usize) * 16);
         }
         let fmt = if !self.has_alpha {
index 15bd832452a40974383391912bc9514fef74c7d0..6d971899b4a85ce8917585c9f95c6d431469a577 100644 (file)
@@ -478,15 +478,17 @@ struct VP6Decoder {
     info:       NACodecInfoRef,
     br:         VP6BR,
     has_alpha:  bool,
+    flipped:    bool,
 }
 
 impl VP6Decoder {
-    fn new(has_alpha: bool) -> Self {
+    fn new(flipped: bool, has_alpha: bool) -> Self {
         Self {
-            dec:        VP56Decoder::new(6, has_alpha, true),
+            dec:        VP56Decoder::new(6, has_alpha, flipped),
             info:       NACodecInfoRef::default(),
             br:         VP6BR::new(),
             has_alpha,
+            flipped,
         }
     }
 }
@@ -499,7 +501,15 @@ impl NADecoder for VP6Decoder {
                 } else {
                     VP_YUVA420_FORMAT
                 };
-            let myvinfo = NAVideoInfo::new(vinfo.get_width(), vinfo.get_height(), false, fmt);
+            let mut width  = vinfo.get_width();
+            let mut height = vinfo.get_height();
+            if let (false, Some(edta)) = (self.flipped, info.get_extradata()) {
+                if (width & 0xF) == 0 && (height & 0xF) == 0 {
+                    width  -= usize::from(edta[0] >> 4);
+                    height -= usize::from(edta[0] & 0xF);
+                }
+            }
+            let myvinfo = NAVideoInfo::new(width, height, self.flipped, fmt);
             let myinfo = NACodecTypeInfo::Video(myvinfo);
             self.info = NACodecInfo::new_ref(info.get_name(), myinfo, info.get_extradata()).into_ref();
             self.dec.init(supp, myvinfo)?;
@@ -530,11 +540,15 @@ impl NAOptionHandler for VP6Decoder {
 }
 
 pub fn get_decoder_vp6() -> Box<dyn NADecoder + Send> {
-    Box::new(VP6Decoder::new(false))
+    Box::new(VP6Decoder::new(true, false))
+}
+
+pub fn get_decoder_vp6f() -> Box<dyn NADecoder + Send> {
+    Box::new(VP6Decoder::new(false, false))
 }
 
 pub fn get_decoder_vp6_alpha() -> Box<dyn NADecoder + Send> {
-    Box::new(VP6Decoder::new(true))
+    Box::new(VP6Decoder::new(false, true))
 }
 
 #[cfg(test)]
@@ -605,6 +619,6 @@ mod test {
                             [0xd3801a96, 0x1b5384ff, 0x422b228c, 0x9c4582c4],
                             [0x8ddb0dfe, 0x302eb1ed, 0x10e64e22, 0x5a5a62b9],
                             [0x79338328, 0x06113781, 0x8b116d18, 0xde56707e],
-                            [0x671fa1b3, 0x0b3e41e9, 0xeb3a494c, 0xcdd24b1b]]));
+                            [0xdb58433b, 0x1de4ce67, 0x15fcbcee, 0x1df9de61]]));
     }
 }
index 7959ae006cadc4da520814011200ee68816e72ed..9388fd2ac604ecd9c8ddfeaa6d136c0ed676283b 100644 (file)
@@ -172,6 +172,7 @@ struct VP6Encoder {
     fenc:       FrameEncoder,
     ratectl:    RateControl,
 
+    flipped:    bool,
     huffman:    bool,
 
     version:    u8,
@@ -191,7 +192,7 @@ struct VP6Encoder {
 }
 
 impl VP6Encoder {
-    fn new() -> Self {
+    fn new(flipped: bool) -> Self {
         let vt = alloc_video_buffer(NAVideoInfo::new(24, 24, false, VP_YUVA420_FORMAT), 4).unwrap();
         let mc_buf = vt.get_vbuf().unwrap();
         Self {
@@ -207,6 +208,7 @@ impl VP6Encoder {
             ratectl:    RateControl::new(),
             mc_buf,
 
+            flipped,
             huffman:    false,
 
             version:    VERSION_VP60,
@@ -633,12 +635,12 @@ impl NAEncoder for VP6Encoder {
         match encinfo.format {
             NACodecTypeInfo::None => {
                 let mut ofmt = EncodeParameters::default();
-                ofmt.format = NACodecTypeInfo::Video(NAVideoInfo::new(0, 0, true, YUV420_FORMAT));
+                ofmt.format = NACodecTypeInfo::Video(NAVideoInfo::new(0, 0, self.flipped, YUV420_FORMAT));
                 Ok(ofmt)
             },
             NACodecTypeInfo::Audio(_) => Err(EncoderError::FormatError),
             NACodecTypeInfo::Video(vinfo) => {
-                let outinfo = NAVideoInfo::new((vinfo.width + 3) & !3, (vinfo.height + 3) & !3, true, YUV420_FORMAT);
+                let outinfo = NAVideoInfo::new((vinfo.width + 3) & !3, (vinfo.height + 3) & !3, self.flipped, YUV420_FORMAT);
                 let mut ofmt = *encinfo;
                 ofmt.format = NACodecTypeInfo::Video(outinfo);
                 Ok(ofmt)
@@ -660,8 +662,8 @@ impl NAEncoder for VP6Encoder {
                     return Err(EncoderError::FormatError);
                 }
 
-                let out_info = NAVideoInfo::new(vinfo.width, vinfo.height, true, vinfo.format);
-                let info = NACodecInfo::new("vp6", NACodecTypeInfo::Video(out_info), None);
+                let out_info = NAVideoInfo::new(vinfo.width, vinfo.height, self.flipped, vinfo.format);
+                let info = NACodecInfo::new(if self.flipped { "vp6" } else { "vp6f" }, NACodecTypeInfo::Video(out_info), None);
                 let mut stream = NAStream::new(StreamType::Video, stream_id, info, encinfo.tb_num, encinfo.tb_den, 0);
                 stream.set_num(stream_id as usize);
                 let stream = stream.into_ref();
@@ -854,7 +856,11 @@ impl NAOptionHandler for VP6Encoder {
 }
 
 pub fn get_encoder() -> Box<dyn NAEncoder + Send> {
-    Box::new(VP6Encoder::new())
+    Box::new(VP6Encoder::new(true))
+}
+
+pub fn get_encoder_flv() -> Box<dyn NAEncoder + Send> {
+    Box::new(VP6Encoder::new(false))
 }
 
 #[cfg(test)]
diff --git a/nihav-flash/Cargo.toml b/nihav-flash/Cargo.toml
new file mode 100644 (file)
index 0000000..d5e79ca
--- /dev/null
@@ -0,0 +1,41 @@
+[package]
+name = "nihav_flash"
+version = "0.1.0"
+authors = ["Kostya Shishkov <kostya.shishkov@gmail.com>"]
+edition = "2018"
+
+[dependencies.nihav_core]
+path = "../nihav-core"
+
+[dependencies.nihav_registry]
+path = "../nihav-registry"
+
+[dependencies.nihav_codec_support]
+path = "../nihav-codec-support"
+features = ["h263", "mdct", "dsp_window"]
+
+[features]
+default = ["all_decoders", "all_demuxers", "all_encoders", "all_muxers"]
+decoders = []
+demuxers = []
+encoders = []
+muxers = []
+all_demuxers = ["demuxer_flv"]
+demuxer_flv = ["demuxers"]
+
+all_decoders = ["all_video_decoders", "all_audio_decoders"]
+
+all_video_decoders = ["decoder_flv263", "decoder_flashsv"]
+decoder_flv263 = ["decoders"]
+decoder_flashsv = ["decoders"]
+
+all_audio_decoders = ["decoder_flv_adpcm", "decoder_asao"]
+decoder_flv_adpcm = ["decoders"]
+decoder_asao = ["decoders"]
+
+all_encoders = ["all_audio_encoders"]
+all_audio_encoders = ["encoder_flv_adpcm"]
+encoder_flv_adpcm = ["encoders"]
+
+all_muxers = ["muxer_flv"]
+muxer_flv = ["muxers"]
\ No newline at end of file
diff --git a/nihav-flash/src/codecs/adpcm.rs b/nihav-flash/src/codecs/adpcm.rs
new file mode 100644 (file)
index 0000000..e6e21cf
--- /dev/null
@@ -0,0 +1,159 @@
+use nihav_core::codecs::*;
+use nihav_core::io::bitreader::*;
+use nihav_codec_support::codecs::imaadpcm::*;
+use std::str::FromStr;
+
+const STEPS: [&[i8]; 4] = [
+    &[ -1, 2],
+    &[ -1, -1, 2, 4],
+    &[ -1, -1, -1, -1, 2, 4, 6, 8],
+    &[ -1, -1, -1, -1, -1, -1, -1, -1, 1, 2, 4, 6, 8, 10, 13, 16 ]
+];
+
+#[derive(Clone,Copy)]
+struct State {
+    predictor:  i32,
+    step:       usize,
+    smask:      u8,
+    steps:      &'static [i8],
+}
+
+impl State {
+    fn new() -> Self {
+        Self {
+            predictor:  0,
+            step:       0,
+            smask:      0,
+            steps:      STEPS[2],
+        }
+    }
+    fn expand_sample(&mut self, nibble: u8) -> i16 {
+        let istep = (self.step as isize) + isize::from(self.steps[(nibble & !self.smask) as usize]);
+        let sign = (nibble & self.smask) != 0;
+        let diff = (i32::from(2 * (nibble & !self.smask) + 1) * IMA_STEP_TABLE[self.step]) >> 3;
+        let sample = if !sign { self.predictor + diff } else { self.predictor - diff };
+        self.predictor = sample.max(i32::from(std::i16::MIN)).min(i32::from(std::i16::MAX));
+        self.step = istep.max(0).min(IMA_MAX_STEP as isize) as usize;
+        self.predictor as i16
+    }
+}
+
+const BLOCK_LEN: usize = 4096;
+
+struct ADPCMDecoder {
+    ainfo:      NAAudioInfo,
+    chmap:      NAChannelMap,
+    ch_state:   [State; 2],
+}
+
+impl ADPCMDecoder {
+    fn new() -> Self {
+        Self {
+            ainfo:      NAAudioInfo::new(0, 1, SND_S16P_FORMAT, BLOCK_LEN),
+            chmap:      NAChannelMap::new(),
+            ch_state:   [State::new(), State::new()],
+        }
+    }
+}
+
+impl NADecoder for ADPCMDecoder {
+    fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> {
+        if let NACodecTypeInfo::Audio(ainfo) = info.get_properties() {
+            let channels = ainfo.get_channels() as usize;
+            validate!(channels == 2 || channels == 1);
+            self.ainfo = NAAudioInfo::new(ainfo.get_sample_rate(), channels as u8, SND_S16P_FORMAT, 0);
+            self.chmap = NAChannelMap::from_str(if channels == 1 { "C" } else { "L,R" }).unwrap();
+            Ok(())
+        } else {
+            Err(DecoderError::InvalidData)
+        }
+    }
+    fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult<NAFrameRef> {
+        let info = pkt.get_stream().get_info();
+        if let NACodecTypeInfo::Audio(_) = info.get_properties() {
+            let src = pkt.get_buffer();
+            let channels = self.chmap.num_channels();
+            let mut br = BitReader::new(&src, BitReaderMode::BE);
+            let step_size                       = br.read(2)? as usize + 2;
+
+            let pkt_size = (16 + 6 + (BLOCK_LEN - 1) * step_size) * channels;
+            let num_pkts = ((br.left() as usize) / pkt_size).max(1);
+            let nsamples = num_pkts * BLOCK_LEN;
+            let mut abuf = alloc_audio_buffer(self.ainfo, nsamples, self.chmap.clone())?;
+            let mut adata = abuf.get_abuf_i16().unwrap();
+            let mut off = [adata.get_offset(0), adata.get_offset(1)];
+            let dst = adata.get_data_mut().unwrap();
+
+            let mut tot_samples = 0;
+            for _pkt in 0..num_pkts {
+                for (ch, state) in self.ch_state[..channels].iter_mut().enumerate() {
+                    state.predictor             = br.read_s(16)?;
+                    state.step                  = br.read(6)? as usize;
+                    state.steps = STEPS[step_size - 2];
+                    state.smask = 1 << (step_size - 1);
+                    dst[off[ch]] = state.predictor as i16;
+                    off[ch] += 1;
+                }
+                tot_samples += 1;
+                let cur_samples = ((br.left() as usize) / step_size / channels).min(BLOCK_LEN - 1);
+                for _ in 0..cur_samples {
+                    for (ch, state) in self.ch_state[..channels].iter_mut().enumerate() {
+                        let idx                 = br.read(step_size as u8)? as u8;
+                        dst[off[ch]] = state.expand_sample(idx);
+                        off[ch] += 1;
+                    }
+                }
+                tot_samples += cur_samples;
+            }
+            abuf.truncate_audio(tot_samples);
+
+            let mut frm = NAFrame::new_from_pkt(pkt, info.replace_info(NACodecTypeInfo::Audio(self.ainfo)), abuf);
+            frm.set_duration(Some(tot_samples as u64));
+            frm.set_keyframe(true);
+            Ok(frm.into_ref())
+        } else {
+            Err(DecoderError::InvalidData)
+        }
+    }
+    fn flush(&mut self) {
+    }
+}
+
+impl NAOptionHandler for ADPCMDecoder {
+    fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] }
+    fn set_options(&mut self, _options: &[NAOption]) { }
+    fn query_option_value(&self, _name: &str) -> Option<NAValue> { None }
+}
+
+pub fn get_decoder() -> Box<dyn NADecoder + Send> {
+    Box::new(ADPCMDecoder::new())
+}
+
+#[cfg(test)]
+mod test {
+    use nihav_core::codecs::RegisteredDecoders;
+    use nihav_core::demuxers::RegisteredDemuxers;
+    use nihav_codec_support::test::dec_video::*;
+    use crate::flash_register_all_decoders;
+    use crate::flash_register_all_demuxers;
+    #[test]
+    fn test_flv_adpcm_mono() {
+        let mut dmx_reg = RegisteredDemuxers::new();
+        flash_register_all_demuxers(&mut dmx_reg);
+        let mut dec_reg = RegisteredDecoders::new();
+        flash_register_all_decoders(&mut dec_reg);
+
+        test_decoding("flv", "flv-adpcm", "assets/Flash/mono_11k.flv", Some(3000), &dmx_reg, &dec_reg,
+                      ExpectedTestResult::MD5([0x4cf30e71, 0x4360c85b, 0x21c52863, 0x1782160e]));
+    }
+    #[test]
+    fn test_flv_adpcm_stereo() {
+        let mut dmx_reg = RegisteredDemuxers::new();
+        flash_register_all_demuxers(&mut dmx_reg);
+        let mut dec_reg = RegisteredDecoders::new();
+        flash_register_all_decoders(&mut dec_reg);
+
+        test_decoding("flv", "flv-adpcm", "assets/Flash/stereo_44k.flv", Some(3000), &dmx_reg, &dec_reg,
+                      ExpectedTestResult::MD5([0xae108d38, 0xb36236f8, 0x2bc18d31, 0xac600424]));
+    }
+}
diff --git a/nihav-flash/src/codecs/adpcmenc.rs b/nihav-flash/src/codecs/adpcmenc.rs
new file mode 100644 (file)
index 0000000..ced3c1b
--- /dev/null
@@ -0,0 +1,210 @@
+use nihav_core::codecs::*;
+use nihav_core::io::bitwriter::*;
+use nihav_codec_support::codecs::imaadpcm::*;
+
+#[derive(Default)]
+struct ADPCMEncoder {
+    stream:     Option<NAStreamRef>,
+    samples:    Vec<i16>,
+    flush:      bool,
+    state:      [IMAState; 2],
+    channels:   usize,
+    srate:      u32,
+    pos:        u64,
+}
+
+const BLOCK_LEN: usize = 4096;
+
+impl ADPCMEncoder {
+    fn new() -> Self { Self::default() }
+    fn encode_packet(&mut self) -> EncoderResult<NAPacket> {
+        if self.samples.is_empty() {
+            return Err(EncoderError::TryAgain);
+        }
+        let cur_len = (self.samples.len() / self.channels).min(BLOCK_LEN);
+        if cur_len < BLOCK_LEN && !self.flush {
+            return Err(EncoderError::TryAgain);
+        }
+
+        let bsize = (2 + (16 + 6 + (cur_len - 1) * 4) * self.channels + 7) / 8;
+
+        let mut bw = BitWriter::new(Vec::with_capacity(bsize), BitWriterMode::BE);
+
+        bw.write(2, 2);
+
+        for ch in 0..self.channels {
+            self.state[ch].predictor = i32::from(self.samples[ch]);
+            self.state[ch].step = 30;
+
+            bw.write_s(i32::from(self.samples[ch]), 16);
+            bw.write(self.state[ch].step as u32, 6);
+        }
+
+        let mut siter = self.samples[self.channels..].iter();
+        for _ in 1..cur_len {
+            for state in self.state[..self.channels].iter_mut() {
+                let samp = *siter.next().unwrap();
+                let nib = state.compress_sample(samp);
+                state.expand_sample(nib);
+                bw.write(u32::from(nib), 4);
+            }
+        }
+
+        let data = bw.end();
+
+        self.samples.drain(..cur_len * self.channels);
+        let ts = NATimeInfo::new(Some(self.pos), None, Some(cur_len as u64), 1, self.srate);
+        self.pos += cur_len as u64;
+        Ok(NAPacket::new(self.stream.clone().unwrap(), ts, true, data))
+    }
+}
+
+impl NAEncoder for ADPCMEncoder {
+    fn negotiate_format(&self, encinfo: &EncodeParameters) -> EncoderResult<EncodeParameters> {
+        match encinfo.format {
+            NACodecTypeInfo::None => {
+                let mut ofmt = EncodeParameters::default();
+                ofmt.format = NACodecTypeInfo::Audio(NAAudioInfo::new(0, 1, SND_S16_FORMAT, BLOCK_LEN));
+                Ok(ofmt)
+            },
+            NACodecTypeInfo::Video(_) => Err(EncoderError::FormatError),
+            NACodecTypeInfo::Audio(ainfo) => {
+                let mut outinfo = ainfo;
+                outinfo.channels = outinfo.channels.max(1).min(2);
+                if outinfo.format != SND_S16P_FORMAT && outinfo.format != SND_S16_FORMAT {
+                    outinfo.format = SND_S16_FORMAT;
+                }
+                outinfo.block_len = BLOCK_LEN;
+                let mut ofmt = *encinfo;
+                ofmt.format = NACodecTypeInfo::Audio(outinfo);
+                Ok(ofmt)
+            }
+        }
+    }
+    fn init(&mut self, stream_id: u32, encinfo: EncodeParameters) -> EncoderResult<NAStreamRef> {
+        match encinfo.format {
+            NACodecTypeInfo::None => Err(EncoderError::FormatError),
+            NACodecTypeInfo::Video(_) => Err(EncoderError::FormatError),
+            NACodecTypeInfo::Audio(ainfo) => {
+                if ainfo.format != SND_S16P_FORMAT && ainfo.format != SND_S16_FORMAT {
+                    return Err(EncoderError::FormatError);
+                }
+                if ainfo.channels != 1 && ainfo.channels != 2 {
+                    return Err(EncoderError::FormatError);
+                }
+                self.channels = ainfo.channels as usize;
+
+                let soniton = NASoniton::new(4, 0);
+                let out_ainfo = NAAudioInfo::new(ainfo.sample_rate, ainfo.channels, soniton, if self.channels == 1 { 2051 } else { 4101 });
+                let info = NACodecInfo::new("flv-adpcm", NACodecTypeInfo::Audio(out_ainfo), None);
+                let mut stream = NAStream::new(StreamType::Audio, stream_id, info, 1, ainfo.sample_rate, 0);
+                stream.set_num(stream_id as usize);
+                let stream = stream.into_ref();
+
+                self.stream = Some(stream.clone());
+                self.samples = Vec::with_capacity(BLOCK_LEN * self.channels);
+                self.srate = ainfo.sample_rate;
+                self.flush = false;
+
+                Ok(stream)
+            },
+        }
+    }
+    fn encode(&mut self, frm: &NAFrame) -> EncoderResult<()> {
+        let buf = frm.get_buffer();
+        if let Some(ref abuf) = buf.get_abuf_i16() {
+            let src = abuf.get_data();
+            let len = abuf.get_length();
+            let ch  = abuf.get_chmap().num_channels();
+            if abuf.get_step() > 1 || ch == 1 {
+                self.samples.extend_from_slice(&src[..len * ch]);
+            } else {
+                let astride = abuf.get_stride();
+                self.samples.reserve(len * ch);
+                for i in 0..len {
+                    for ch in 0..self.channels {
+                        self.samples.push(src[ch * astride + i]);
+                    }
+                }
+            }
+            Ok(())
+        } else {
+            Err(EncoderError::InvalidParameters)
+        }
+    }
+    fn get_packet(&mut self) -> EncoderResult<Option<NAPacket>> {
+        if let Ok(pkt) = self.encode_packet() {
+            Ok(Some(pkt))
+        } else {
+            Ok(None)
+        }
+    }
+    fn flush(&mut self) -> EncoderResult<()> {
+        self.flush = true;
+        Ok(())
+    }
+}
+
+impl NAOptionHandler for ADPCMEncoder {
+    fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] }
+    fn set_options(&mut self, _options: &[NAOption]) {}
+    fn query_option_value(&self, _name: &str) -> Option<NAValue> { None }
+}
+
+pub fn get_encoder() -> Box<dyn NAEncoder + Send> {
+    Box::new(ADPCMEncoder::new())
+}
+
+#[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_flv_adpcm_encoder() {
+
+        let mut dmx_reg = RegisteredDemuxers::new();
+        flash_register_all_demuxers(&mut dmx_reg);
+        let mut dec_reg = RegisteredDecoders::new();
+        flash_register_all_decoders(&mut dec_reg);
+        let mut mux_reg = RegisteredMuxers::new();
+        flash_register_all_muxers(&mut mux_reg);
+        let mut enc_reg = RegisteredEncoders::new();
+        flash_register_all_encoders(&mut enc_reg);
+
+        let dec_config = DecoderTestParams {
+                demuxer:        "flv",
+                in_name:        "assets/Flash/mono_11k.flv",
+                stream_type:    StreamType::Audio,
+                limit:          Some(3700),
+                dmx_reg, dec_reg,
+            };
+        let enc_config = EncoderTestParams {
+                muxer:          "flv",
+                enc_name:       "flv-adpcm",
+                out_name:       "flv_adpcm.flv",
+                mux_reg, enc_reg,
+            };
+        let dst_ainfo = NAAudioInfo {
+                sample_rate:    0,
+                channels:       0,
+                format:         SND_S16_FORMAT,
+                block_len:      128,
+            };
+        let enc_params = EncodeParameters {
+                format:  NACodecTypeInfo::Audio(dst_ainfo),
+                quality: 0,
+                bitrate: 0,
+                tb_num:  0,
+                tb_den:  0,
+                flags:   0,
+            };
+        //test_encoding_to_file(&dec_config, &enc_config, enc_params, &[]);
+
+        test_encoding_md5(&dec_config, &enc_config, enc_params, &[],
+                          &[0x6114b7de, 0xb84c226f, 0x0caab8e5, 0x2c7df63f]);
+    }
+}
diff --git a/nihav-flash/src/codecs/asao.rs b/nihav-flash/src/codecs/asao.rs
new file mode 100644 (file)
index 0000000..e20c729
--- /dev/null
@@ -0,0 +1,373 @@
+use nihav_core::codecs::*;
+use nihav_core::io::bitreader::*;
+use nihav_codec_support::dsp::mdct::*;
+use nihav_codec_support::dsp::window::*;
+
+struct Random { state: u32 }
+
+impl Random {
+    fn new(seed: u32) -> Self { Self { state: seed } }
+    fn next(&mut self) -> u32 {
+        self.state ^= self.state << 13;
+        self.state ^= self.state >> 17;
+        self.state ^= self.state <<  5;
+        self.state
+    }
+}
+
+const NUM_BANDS: usize = 23;
+const BLOCK_LEN: usize = 256;
+const PACKED_BLK_LEN: usize = 64;
+const CODED_LEN: usize = 124;
+
+struct ASAODecoder {
+    ainfo:      NAAudioInfo,
+    chmap:      NAChannelMap,
+    window:     [f32; 128],
+    imdct:      IMDCT,
+    rng:        Random,
+
+    scales:     [f32; 128],
+    iscales:    [i32; 128],
+    bits:       [i8; 128],
+    coeffs:     [f32; 128],
+    prev:       [f32; 128],
+    tmp:        [f32; 128],
+}
+
+const HEADER_BITS:  u32 = 116;
+const SUBPART_BITS: i32 = (((PACKED_BLK_LEN as u32) * 8 - HEADER_BITS) / 2) as i32;
+const MAX_CBITS: i32 = 6;
+const BASE_OFF: i32 = 4228;
+const BASE_SHIFT: i8 = 19;
+
+trait SignedShift {
+    type Output;
+    fn sshift(self, shift: i8) -> Self::Output;
+}
+
+impl SignedShift for i32 {
+    type Output = Self;
+    fn sshift(self, shift: i8) -> Self::Output {
+        if shift >= 0 {
+            self << shift
+        } else {
+            self >> -shift
+        }
+    }
+}
+
+fn norm(val: &mut i32) -> i8 {
+    if *val == 0 {
+        31
+    } else {
+        let shift = val.abs().leading_zeros() - 1;
+        *val <<= shift;
+        shift as i8
+    }
+}
+
+fn sum_bits(src: &[i32; 128], shift: i8, off: i32) -> i32 {
+    let mut sum = 0;
+    for &el in src[..CODED_LEN].iter() {
+        let val = (((el - off) >> (shift - 1)) + 1) >> 1;
+        sum += val.max(0).min(MAX_CBITS);
+    }
+    sum
+}
+
+fn bitalloc(bits: &mut [i8; 128], scales: &[i32; 128]) {
+    let mut max = scales[..CODED_LEN].iter().fold(scales[0], |v, &x| v.max(x));
+    let mut shift = norm(&mut max) - 16;
+
+    let mut tmp = [0; 128];
+    let mut sum = 0;
+    for i in 0..CODED_LEN {
+        tmp[i] = (scales[i].sshift(shift) * 3) >> 2;
+        sum += tmp[i];
+    }
+
+    shift += 11;
+    let ref_shift = shift;
+
+    sum -= SUBPART_BITS << shift;
+    shift += norm(&mut sum);
+    shift = ref_shift - (BASE_SHIFT + shift - 31);
+    let mut offset = ((BASE_OFF * (sum >> 16)) >> 15).sshift(shift);
+
+    let mut bitsum = sum_bits(&tmp, ref_shift, offset);
+
+    if bitsum != SUBPART_BITS {
+        let mut off2 = bitsum - SUBPART_BITS;
+        shift = 0;
+        while off2.abs() <= 0x3FFF {
+            off2 <<= 1;
+            shift += 1;
+        }
+
+        shift = ref_shift - (BASE_SHIFT + shift - 15);
+        off2 = ((BASE_OFF * off2) >> 15).sshift(shift);
+
+        let mut last_off = 0;
+        let mut last_bitsum = 0;
+        let mut iter = 1;
+        while iter < 20 {
+            last_off = offset;
+            offset += off2;
+            last_bitsum = bitsum;
+            bitsum = sum_bits(&tmp, ref_shift, offset);
+            if (bitsum - SUBPART_BITS) * (last_bitsum - SUBPART_BITS) <= 0 {
+                break;
+            }
+            iter += 1;
+        }
+
+        let (mut big_off, mut small_off, mut big_sum, mut small_sum) = if bitsum > SUBPART_BITS {
+                (offset, last_off, bitsum, last_bitsum)
+            } else {
+                (last_off, offset, last_bitsum, bitsum)
+            };
+
+        while bitsum != SUBPART_BITS && iter < 20 {
+            let off = (big_off + small_off) >> 1;
+            bitsum = sum_bits(&tmp, ref_shift, off);
+            if bitsum > SUBPART_BITS {
+                big_off = off;
+                big_sum = bitsum;
+            } else {
+                small_off = off;
+                small_sum = bitsum;
+            }
+            iter += 1;
+        }
+
+        if (big_sum - SUBPART_BITS).abs() >= (small_sum - SUBPART_BITS).abs() {
+            offset = small_off;
+            bitsum = small_sum;
+        } else {
+            offset = big_off;
+            bitsum = big_sum;
+        }
+    }
+
+    for (bits, &val) in bits.iter_mut().zip(tmp.iter()).take(CODED_LEN) {
+        *bits = ((((val - offset) >> (ref_shift - 1)) + 1) >> 1).max(0).min(MAX_CBITS) as i8;
+    }
+    if bitsum > SUBPART_BITS {
+        let mut sum = 0;
+        let mut i = 0;
+        while sum < SUBPART_BITS {
+            sum += i32::from(bits[i]);
+            i += 1;
+        }
+        bits[i - 1] -= (sum - SUBPART_BITS) as i8;
+        while i < CODED_LEN {
+            bits[i] = 0;
+            i += 1;
+        }
+    }
+}
+
+fn overlap_add(dst: &mut [f32], src: &[f32; 128], prev: &[f32; 128], window: &[f32; 128]) {
+    for i in 0..64 {
+        let p = prev[64 + i];
+        let s = src [63 - i];
+        let w0 = window[i];
+        let w1 = window[127 - i];
+        dst[i]       = p * w1 - s * w0;
+        dst[127 - i] = p * w0 + s * w1;
+    }
+}
+
+impl ASAODecoder {
+    fn new() -> Self {
+        let mut window = [0.0; 128];
+        generate_window(WindowType::Sine, 1.0/1024.0, 128, true, &mut window);
+
+        Self {
+            ainfo:      NAAudioInfo::new(0, 1, SND_F32P_FORMAT, BLOCK_LEN),
+            chmap:      NAChannelMap::new(),
+            window,
+            imdct:      IMDCT::new(256, true),
+            rng:        Random::new(42),
+
+            scales:     [0.0; 128],
+            iscales:    [0; 128],
+            bits:       [0; 128],
+            coeffs:     [0.0; 128],
+            prev:       [0.0; 128],
+            tmp:        [0.0; 128],
+        }
+    }
+    fn decode_block(&mut self, br: &mut BitReader, dst: &mut [f32]) -> DecoderResult<()> {
+        let mut scale = i32::from(SCALE[br.read(6)? as usize]);
+        let mut sc_iter = self.scales.iter_mut();
+        let mut isc_iter = self.iscales.iter_mut();
+        for (band, &band_size) in BAND_SIZES.iter().enumerate() {
+            if band > 0 {
+                scale += i32::from(SCALE_DIFF[br.read(5)? as usize]);
+            }
+            let scf = -(2.0f32.powf((scale as f32) / 2048.0));
+            for _ in 0..band_size {
+                *sc_iter.next().unwrap() = scf;
+                *isc_iter.next().unwrap() = scale;
+            }
+        }
+
+        bitalloc(&mut self.bits, &self.iscales);
+
+        let mut coeffs = &mut self.coeffs;
+        let mut prev   = &mut self.prev;
+        for (i, out) in dst.chunks_exact_mut(BLOCK_LEN / 2).take(2).enumerate() {
+            br.seek(HEADER_BITS + (SUBPART_BITS as u32) * (i as u32))?;
+            for j in 0..CODED_LEN {
+                if self.bits[j] <= 0 {
+                    self.tmp[j] = std::f32::consts::FRAC_1_SQRT_2;
+                    if (self.rng.next() & 1) != 0 {
+                        self.tmp[j] = -self.tmp[j];
+                    }
+                } else {
+                    let val = br.read(self.bits[j] as u8)? as usize;
+                    self.tmp[j] = QUANT_VALS[self.bits[j] as usize][val];
+                }
+                self.tmp[j] *= self.scales[j];
+            }
+            for j in CODED_LEN..128 {
+                self.tmp[j] = 0.0;
+            }
+            self.imdct.imdct_half(&self.tmp, coeffs);
+            overlap_add(out, coeffs, prev, &self.window);
+            std::mem::swap(&mut coeffs, &mut prev);
+        }
+
+        Ok(())
+    }
+}
+
+
+impl NADecoder for ASAODecoder {
+    fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> {
+        if let NACodecTypeInfo::Audio(ainfo) = info.get_properties() {
+            self.ainfo = NAAudioInfo::new(ainfo.get_sample_rate(), 1, SND_F32P_FORMAT, BLOCK_LEN);
+            self.chmap = NAChannelMap::new();
+            self.chmap.add_channel(NAChannelType::C);
+            Ok(())
+        } else {
+            Err(DecoderError::InvalidData)
+        }
+    }
+    fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult<NAFrameRef> {
+        let info = pkt.get_stream().get_info();
+        if let NACodecTypeInfo::Audio(_) = info.get_properties() {
+            let src = pkt.get_buffer();
+
+            validate!((src.len() % PACKED_BLK_LEN) == 0);
+            let npkts = src.len() / PACKED_BLK_LEN;
+            let nsamples = npkts * BLOCK_LEN;
+            let abuf = alloc_audio_buffer(self.ainfo, nsamples, self.chmap.clone())?;
+            let mut adata = abuf.get_abuf_f32().unwrap();
+            let dst = adata.get_data_mut().unwrap();
+
+            for (src, dst) in src.chunks_exact(PACKED_BLK_LEN).zip(dst.chunks_mut(BLOCK_LEN)) {
+                let mut br = BitReader::new(&src, BitReaderMode::LE);
+                self.decode_block(&mut br, dst)?;
+            }
+
+            let mut frm = NAFrame::new_from_pkt(pkt, info.replace_info(NACodecTypeInfo::Audio(self.ainfo)), abuf);
+            frm.set_duration(Some(nsamples as u64));
+            frm.set_keyframe(true);
+            Ok(frm.into_ref())
+        } else {
+            Err(DecoderError::InvalidData)
+        }
+    }
+    fn flush(&mut self) {
+    }
+}
+
+impl NAOptionHandler for ASAODecoder {
+    fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] }
+    fn set_options(&mut self, _options: &[NAOption]) { }
+    fn query_option_value(&self, _name: &str) -> Option<NAValue> { None }
+}
+
+pub fn get_decoder() -> Box<dyn NADecoder + Send> {
+    Box::new(ASAODecoder::new())
+}
+
+#[cfg(test)]
+mod test {
+    use nihav_core::codecs::RegisteredDecoders;
+    use nihav_core::demuxers::RegisteredDemuxers;
+    use nihav_codec_support::test::dec_video::*;
+    use crate::flash_register_all_decoders;
+    use crate::flash_register_all_demuxers;
+    #[test]
+    fn test_asao() {
+        let mut dmx_reg = RegisteredDemuxers::new();
+        flash_register_all_demuxers(&mut dmx_reg);
+        let mut dec_reg = RegisteredDecoders::new();
+        flash_register_all_decoders(&mut dec_reg);
+
+        test_decode_audio("flv", "assets/Flash/nellymoser-in-flv.flv", Some(3000), None/*Some("asao")*/, &dmx_reg, &dec_reg);
+    }
+}
+
+#[allow(clippy::excessive_precision)]
+const QUANT_VALS: [&[f32]; 7] = [
+  &[  0.0000000000 ],
+  &[ -0.8472560048,  0.7224709988 ],
+  &[ -1.5247479677, -0.4531480074,  0.3753609955,  1.4717899561 ],
+  &[ -1.9822579622, -1.1929379702, -0.5829370022, -0.0693780035,
+      0.3909569979,  0.9069200158,  1.4862740040,  2.2215409279 ],
+  &[ -2.3887870312, -1.8067539930, -1.4105420113, -1.0773609877,
+     -0.7995010018, -0.5558109879, -0.3334020078, -0.1324490011,
+      0.0568020009,  0.2548770010,  0.4773550034,  0.7386850119,
+      1.0443060398,  1.3954459429,  1.8098750114,  2.3918759823 ],
+  &[ -2.3893830776, -1.9884680510, -1.7514040470, -1.5643119812,
+     -1.3922129869, -1.2164649963, -1.0469499826, -0.8905100226,
+     -0.7645580173, -0.6454579830, -0.5259280205, -0.4059549868,
+     -0.3029719889, -0.2096900046, -0.1239869967, -0.0479229987,
+      0.0257730000,  0.1001340002,  0.1737180054,  0.2585540116,
+      0.3522900045,  0.4569880068,  0.5767750144,  0.7003160119,
+      0.8425520062,  1.0093879700,  1.1821349859,  1.3534560204,
+      1.5320819616,  1.7332619429,  1.9722349644,  2.3978140354 ],
+  &[ -2.5756309032, -2.0573320389, -1.8984919786, -1.7727810144,
+     -1.6662600040, -1.5742180347, -1.4993319511, -1.4316639900,
+     -1.3652280569, -1.3000990152, -1.2280930281, -1.1588579416,
+     -1.0921250582, -1.0135740042, -0.9202849865, -0.8287050128,
+     -0.7374889851, -0.6447759867, -0.5590940118, -0.4857139885,
+     -0.4110319912, -0.3459700048, -0.2851159871, -0.2341620028,
+     -0.1870580018, -0.1442500055, -0.1107169986, -0.0739680007,
+     -0.0365610011, -0.0073290002,  0.0203610007,  0.0479039997,
+      0.0751969963,  0.0980999991,  0.1220389977,  0.1458999962,
+      0.1694349945,  0.1970459968,  0.2252430022,  0.2556869984,
+      0.2870100141,  0.3197099864,  0.3525829911,  0.3889069855,
+      0.4334920049,  0.4769459963,  0.5204820037,  0.5644530058,
+      0.6122040153,  0.6685929894,  0.7341650128,  0.8032159805,
+      0.8784040213,  0.9566209912,  1.0397069454,  1.1293770075,
+      1.2211159468,  1.3080279827,  1.4024800062,  1.5056819916,
+      1.6227730513,  1.7724959850,  1.9430880547,  2.2903931141 ]
+];
+
+const SCALE_DIFF: [i16; 32] = [
+    -11725, -9420, -7910, -6801, -5948, -5233, -4599, -4039,
+     -3507, -3030, -2596, -2170, -1774, -1383, -1016,  -660,
+      -329,    -1,   337,   696,  1085,  1512,  1962,  2433,
+      2968,  3569,  4314,  5279,  6622,  8154, 10076, 12975
+];
+
+const BAND_SIZES: [usize; NUM_BANDS] = [
+    2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 9, 10, 12, 14, 15
+];
+
+const SCALE: [u16; 64] = [
+     3134,  5342,  6870,  7792,  8569,  9185,  9744, 10191,
+    10631, 11061, 11434, 11770, 12116, 12513, 12925, 13300,
+    13674, 14027, 14352, 14716, 15117, 15477, 15824, 16157,
+    16513, 16804, 17090, 17401, 17679, 17948, 18238, 18520,
+    18764, 19078, 19381, 19640, 19921, 20205, 20500, 20813,
+    21162, 21465, 21794, 22137, 22453, 22756, 23067, 23350,
+    23636, 23926, 24227, 24521, 24819, 25107, 25414, 25730,
+    26120, 26497, 26895, 27344, 27877, 28463, 29426, 31355
+];
diff --git a/nihav-flash/src/codecs/flashsv.rs b/nihav-flash/src/codecs/flashsv.rs
new file mode 100644 (file)
index 0000000..26e0eb4
--- /dev/null
@@ -0,0 +1,423 @@
+use nihav_core::frame::*;
+use nihav_core::codecs::*;
+use nihav_core::io::byteio::*;
+use nihav_core::compr::deflate::*;
+
+#[derive(Default)]
+struct FSVShuffler {
+    lastframe: Option<NAVideoBufferRef<u8>>,
+    keyframe:  Option<NAVideoBufferRef<u8>>,
+}
+
+#[allow(dead_code)]
+impl FSVShuffler {
+    fn new() -> Self { Self::default() }
+    fn clear(&mut self) {
+        self.keyframe  = None;
+        self.lastframe = None;
+    }
+    fn add_frame(&mut self, buf: NAVideoBufferRef<u8>) {
+        self.lastframe = Some(buf);
+    }
+    fn add_keyframe(&mut self, buf: NAVideoBufferRef<u8>) {
+        self.keyframe = Some(buf);
+    }
+    fn clone_ref(&mut self) -> Option<NAVideoBufferRef<u8>> {
+        if let Some(ref mut frm) = self.lastframe {
+            let newfrm = frm.copy_buffer();
+            *frm = newfrm.clone().into_ref();
+            Some(newfrm.into_ref())
+        } else {
+            None
+        }
+    }
+    fn has_last_frame(&self) -> bool { self.lastframe.is_some() }
+    fn get_key_frame(&mut self) -> Option<NAVideoBufferRef<u8>> {
+        match self.keyframe {
+            Some(ref frm) => Some(frm.clone()),
+            None => None,
+        }
+    }
+    fn get_last_frame(&mut self) -> Option<NAVideoBufferRef<u8>> {
+        match self.lastframe {
+            Some(ref frm) => Some(frm.clone()),
+            None => None,
+        }
+    }
+}
+
+
+struct FSVDecoder {
+    info:       NACodecInfoRef,
+    shuf:       FSVShuffler,
+    w:          usize,
+    h:          usize,
+    block_w:    usize,
+    block_h:    usize,
+    ver1:       bool,
+    has_pal:    bool,
+    has_ifrm:   bool,
+    tile:       Vec<u8>,
+    cbuf:       [u8; 65536],
+    pal:        [u8; 128 * 3],
+    inflate:    Inflate,
+    kdata:      Vec<u8>,
+    bpos:       Vec<usize>,
+    bsize:      Vec<usize>,
+}
+
+impl FSVDecoder {
+    fn new(ver1: bool) -> Self {
+        Self {
+            info:       NACodecInfo::new_dummy(),
+            shuf:       FSVShuffler::new(),
+            w:          0,
+            h:          0,
+            block_w:    0,
+            block_h:    0,
+            ver1,
+            has_pal:    false,
+            has_ifrm:   false,
+            tile:       Vec::new(),
+            cbuf:       [0; 65536],
+            pal:        DEFAULT_PAL,
+            inflate:    Inflate::new(),
+            kdata:      Vec::new(),
+            bpos:       Vec::new(),
+            bsize:      Vec::new(),
+        }
+    }
+    fn decode_v1(&mut self, br: &mut ByteReader, data: &mut [u8], stride: usize) -> DecoderResult<bool> {
+        let mut is_intra = true;
+        for (yy, row) in data.chunks_mut(stride * self.block_h).enumerate() {
+            let cur_h = (self.h - yy * self.block_h).min(self.block_h);
+            for x in (0..self.w).step_by(self.block_w) {
+                let cur_w = (self.w - x).min(self.block_w);
+
+                let data_size           = br.read_u16be()? as usize;
+                if data_size > 0 {
+                                          br.read_buf(&mut self.cbuf[..data_size])?;
+                    self.inflate = Inflate::new();
+                    if self.inflate.decompress_block(&self.cbuf[..data_size], &mut self.tile[..cur_w * cur_h * 3]).is_err() {
+                        return Err(DecoderError::InvalidData);
+                    }
+                    for (dst, src) in row[x * 3..].chunks_mut(stride).zip(self.tile.chunks(cur_w * 3)) {
+                        dst[..cur_w * 3].copy_from_slice(src);
+                    }
+                } else {
+                    is_intra = false;
+                }
+            }
+        }
+        Ok(is_intra)
+    }
+    fn decode_v2(&mut self, br: &mut ByteReader, data: &mut [u8], stride: usize, keyframe: bool) -> DecoderResult<bool> {
+        let mut is_intra = !self.has_ifrm;
+        let bstride = (self.w + self.block_w - 1) / self.block_w;
+        for y in (0..self.h).step_by(self.block_h) {
+            let cur_h = (self.h - y).min(self.block_h);
+            for x in (0..self.w).step_by(self.block_w) {
+                let cur_w = (self.w - x).min(self.block_w);
+
+                let mut data_size       = br.read_u16be()? as usize;
+                validate!(!keyframe || data_size > 0);
+                if data_size == 0 {
+                    is_intra = false;
+                    continue;
+                }
+                let blk_start = br.tell();
+                let flags               = br.read_byte()?;
+                let depth = (flags >> 3) & 3;
+                validate!(depth == 0 || depth == 2);
+                let has_diff = (flags & 4) != 0;
+                let cpriming = (flags & 2) != 0;
+                let ppriming = (flags & 1) != 0;
+                let (start, height) = if has_diff {
+                        let start       = br.read_byte()? as usize;
+                        let height      = br.read_byte()? as usize;
+                        validate!(start + height <= cur_h);
+                        (start, height)
+                    } else {
+                        (0, cur_h)
+                    };
+                if has_diff {
+                    let ret = self.shuf.get_key_frame();
+                    if ret.is_none() {
+                        return Err(DecoderError::MissingReference);
+                    }
+                    let src = ret.unwrap();
+                    let src = src.get_data();
+                    for (dst, src) in data[x * 3 + y * stride..].chunks_mut(stride).take(cur_h).zip(src[x * 3 + y * stride..].chunks(stride)) {
+                        dst[..cur_w * 3].copy_from_slice(&src[..cur_w * 3]);
+                    }
+                }
+                if height != cur_h {
+                    is_intra = false;
+                }
+                let ppos = if cpriming {
+                        let xpos        = br.read_byte()? as usize;
+                        let ypos        = br.read_byte()? as usize;
+                        xpos + ypos * bstride
+                    } else {
+                        x / self.block_w + y / self.block_h * bstride
+                    };
+                data_size -= (br.tell() - blk_start) as usize;
+                if keyframe {
+                    self.bpos.push(br.tell() as usize);
+                    self.bsize.push(data_size);
+                }
+                if data_size > 0 {
+                                          br.read_buf(&mut self.cbuf[..data_size])?;
+                    self.inflate = Inflate::new();
+                    if cpriming || ppriming {
+                        if self.bpos.is_empty() {
+                            return Err(DecoderError::MissingReference);
+                        }
+                        let ret = self.inflate.decompress_block(&self.kdata[self.bpos[ppos]..][..self.bsize[ppos]], &mut self.tile);
+                        if ret.is_err() {
+                            return Err(DecoderError::InvalidData);
+                        }
+                        let ssize = ret.unwrap();
+                        self.inflate = Inflate::new();
+                        self.inflate.set_dict(&self.tile[..ssize]);
+                    }
+                    let ret = self.inflate.decompress_block(&self.cbuf[..data_size], &mut self.tile[..cur_w * height * 3]);
+                    if ret.is_err() {
+                        return Err(DecoderError::InvalidData);
+                    }
+                    let src_len = ret.unwrap();
+
+                    let dst = &mut data[x * 3 + y * stride..];
+                    match depth {
+                        0 => {
+                            validate!(src_len == cur_w * cur_h * 3);
+                            for (dst, src) in dst.chunks_mut(stride).skip(start).take(height).zip(self.tile.chunks(cur_w * 3)) {
+                                dst[..cur_w * 3].copy_from_slice(src);
+                            }
+                        },
+                        2 => {
+                            let mut mr = MemoryReader::new_read(&self.tile[..src_len]);
+                            let mut br = ByteReader::new(&mut mr);
+                            for line in dst.chunks_mut(stride).skip(start).take(height) {
+                                for rgb in line.chunks_mut(3).take(cur_w) {
+                                    let b           = br.read_byte()?;
+                                    if (b & 0x80) == 0 {
+                                        rgb.copy_from_slice(&self.pal[(b as usize) * 3..][..3]);
+                                    } else {
+                                        let c       = br.read_byte()?;
+                                        let clr = (u16::from(b & 0x7F) << 8) | u16::from(c);
+                                        let r = (clr >> 10) as u8;
+                                        let g = ((clr >> 5) & 0x1F) as u8;
+                                        let b = (clr & 0x1F) as u8;
+                                        rgb[0] = (r << 3) | (r >> 2);
+                                        rgb[1] = (g << 3) | (g >> 2);
+                                        rgb[2] = (b << 3) | (b >> 2);
+                                    }
+                                }
+                            }
+                        },
+                        _ => unreachable!(),
+                    };
+                } else {
+                    is_intra = false;
+                }
+            }
+        }
+        if self.has_ifrm {
+unimplemented!();
+        }
+        Ok(is_intra)
+    }
+}
+
+impl NADecoder for FSVDecoder {
+    fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> {
+        if let NACodecTypeInfo::Video(vinfo) = info.get_properties() {
+            let w = vinfo.get_width();
+            let h = vinfo.get_height();
+            let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(w, h, true, RGB24_FORMAT));
+            self.info = NACodecInfo::new_ref(info.get_name(), myinfo, info.get_extradata()).into_ref();
+
+            Ok(())
+        } else {
+            Err(DecoderError::InvalidData)
+        }
+    }
+    fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult<NAFrameRef> {
+        let src = pkt.get_buffer();
+
+        validate!(src.len() > 4);
+        let mut mr = MemoryReader::new_read(&src);
+        let mut br = ByteReader::new(&mut mr);
+
+        let hdr0                        = br.read_u16be()? as usize;
+        let hdr1                        = br.read_u16be()? as usize;
+        let w = hdr0 & 0xFFF;
+        let h = hdr1 & 0xFFF;
+        let blk_w = (hdr0 >> 12) * 16 + 16;
+        let blk_h = (hdr1 >> 12) * 16 + 16;
+        validate!(w != 0 && h != 0 && blk_w != 0 && blk_h != 0);
+
+        if !self.ver1 {
+            let flags                   = br.read_byte()?;
+            self.has_pal  = (flags & 1) != 0;
+            self.has_ifrm = (flags & 2) != 0;
+            if self.has_pal {
+                let pal_sz              = br.read_u16be()? as usize;
+                                          br.read_buf(&mut self.cbuf[..pal_sz])?;
+                self.inflate = Inflate::new();
+                if self.inflate.decompress_block(&self.cbuf[..pal_sz], &mut self.pal).is_err() {
+                    return Err(DecoderError::InvalidData);
+                }
+            }
+            if pkt.keyframe {
+                self.kdata.clear();
+                self.kdata.extend_from_slice(&src);
+                self.bpos.clear();
+                self.bsize.clear();
+            }
+        }
+        if self.w != w || self.h != h || self.block_w != blk_w || self.block_h != blk_h {
+            self.flush();
+            self.tile.resize(blk_w * blk_h * 3, 0);
+            self.w = w;
+            self.h = h;
+            self.block_w = blk_w;
+            self.block_h = blk_h;
+        }
+
+        let mut buf = if let Some(buffer) = self.shuf.clone_ref() {
+                buffer
+            } else {
+                let vinfo = self.info.get_properties().get_video_info().unwrap();
+                let bufinfo = alloc_video_buffer(vinfo, 0)?;
+                bufinfo.get_vbuf().unwrap()
+            };
+        let stride = buf.get_stride(0);
+        let data = buf.get_data_mut().unwrap();
+        let is_intra = if self.ver1 {
+                self.decode_v1(&mut br, data, stride)?
+            } else {
+                self.decode_v2(&mut br, data, stride, pkt.keyframe)?
+            };
+
+        if !is_intra && !self.shuf.has_last_frame() {
+            return Err(DecoderError::MissingReference);
+        }
+
+        if pkt.is_keyframe() {
+            self.shuf.add_keyframe(buf.clone());
+        }
+        self.shuf.add_frame(buf.clone());
+
+        let mut frm = NAFrame::new_from_pkt(pkt, self.info.clone(), NABufferType::VideoPacked(buf));
+        frm.set_frame_type(if is_intra { FrameType::I } else { FrameType::P });
+        Ok(frm.into_ref())
+    }
+    fn flush(&mut self) {
+        self.shuf.clear();
+    }
+}
+
+impl NAOptionHandler for FSVDecoder {
+    fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] }
+    fn set_options(&mut self, _options: &[NAOption]) { }
+    fn query_option_value(&self, _name: &str) -> Option<NAValue> { None }
+}
+
+pub fn get_decoder() -> Box<dyn NADecoder + Send> {
+    Box::new(FSVDecoder::new(true))
+}
+
+pub fn get_decoder_v2() -> Box<dyn NADecoder + Send> {
+    Box::new(FSVDecoder::new(false))
+}
+
+#[cfg(test)]
+mod test {
+    use nihav_core::codecs::RegisteredDecoders;
+    use nihav_core::demuxers::RegisteredDemuxers;
+    use nihav_codec_support::test::dec_video::*;
+    use crate::flash_register_all_decoders;
+    use crate::flash_register_all_demuxers;
+    #[test]
+    fn test_flashsv1() {
+        let mut dmx_reg = RegisteredDemuxers::new();
+        flash_register_all_demuxers(&mut dmx_reg);
+        let mut dec_reg = RegisteredDecoders::new();
+        flash_register_all_decoders(&mut dec_reg);
+
+        test_decoding("flv", "flashsv", "assets/Flash/screen.flv",
+                      Some(3000), &dmx_reg, &dec_reg, ExpectedTestResult::MD5Frames(vec![
+                            [0xb45b899e, 0x417b17d5, 0x7bfe898b, 0x026b289f],
+                            [0xb45b899e, 0x417b17d5, 0x7bfe898b, 0x026b289f],
+                            [0xb45b899e, 0x417b17d5, 0x7bfe898b, 0x026b289f],
+                            [0xb45b899e, 0x417b17d5, 0x7bfe898b, 0x026b289f],
+                            [0xb45b899e, 0x417b17d5, 0x7bfe898b, 0x026b289f],
+                            [0xb45b899e, 0x417b17d5, 0x7bfe898b, 0x026b289f],
+                            [0xb45b899e, 0x417b17d5, 0x7bfe898b, 0x026b289f],
+                            [0xb45b899e, 0x417b17d5, 0x7bfe898b, 0x026b289f],
+                            [0xc04d4d1c, 0xbb1f4b4f, 0xe9f3d85e, 0xa40aff68],
+                            [0x172e5bbe, 0xe44caba3, 0x6cb2a263, 0xcb79a89a]]));
+    }
+    #[test]
+    fn test_flashsv2() {
+        let mut dmx_reg = RegisteredDemuxers::new();
+        flash_register_all_demuxers(&mut dmx_reg);
+        let mut dec_reg = RegisteredDecoders::new();
+        flash_register_all_decoders(&mut dec_reg);
+
+        test_decoding("flv", "flashsv2", "assets/Flash/screen2.flv",
+                      Some(4700), &dmx_reg, &dec_reg, ExpectedTestResult::MD5Frames(vec![
+                            [0x55522afa, 0x9c7dd794, 0xdd67aa2e, 0x8b8c525e],
+                            [0x55522afa, 0x9c7dd794, 0xdd67aa2e, 0x8b8c525e],
+                            [0x55522afa, 0x9c7dd794, 0xdd67aa2e, 0x8b8c525e],
+                            [0x55522afa, 0x9c7dd794, 0xdd67aa2e, 0x8b8c525e],
+                            [0x55522afa, 0x9c7dd794, 0xdd67aa2e, 0x8b8c525e],
+                            [0x55522afa, 0x9c7dd794, 0xdd67aa2e, 0x8b8c525e],
+                            [0x55522afa, 0x9c7dd794, 0xdd67aa2e, 0x8b8c525e],
+                            [0x55522afa, 0x9c7dd794, 0xdd67aa2e, 0x8b8c525e],
+                            [0x9809efc2, 0xec5385aa, 0xb5eb9320, 0x4a47188e],
+                            [0x40c77877, 0x58183722, 0x5700eb17, 0x27a00e33],
+                            [0x802c2c6a, 0x3e08dd62, 0xa6c94df3, 0xc6318a6f],
+                            [0x2aa70255, 0x652f0ca4, 0xe79817f9, 0x4f67e7ba],
+                            [0x5cf34d91, 0xdfc54992, 0x4368180d, 0xfbe747d4],
+                            [0x266d8bc4, 0x2b492ef4, 0xb42401a0, 0x23e530ec],
+                            [0xa0e46b1c, 0x47d0620e, 0x0cbcb15b, 0x243e7f13]]));
+    }
+}
+
+const DEFAULT_PAL: [u8; 128 * 3] = [
+     0x00, 0x00, 0x00, 0x33, 0x33, 0x33, 0x66, 0x66, 0x66, 0x99, 0x99, 0x99,
+    0xCC, 0xCC, 0xCC, 0xFF, 0xFF, 0xFF, 0x33, 0x00, 0x00, 0x66, 0x00, 0x00,
+    0x99, 0x00, 0x00, 0xCC, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x33, 0x00,
+    0x00, 0x66, 0x00, 0x00, 0x99, 0x00, 0x00, 0xCC, 0x00, 0x00, 0xFF, 0x00,
+    0x00, 0x00, 0x33, 0x00, 0x00, 0x66, 0x00, 0x00, 0x99, 0x00, 0x00, 0xCC,
+    0x00, 0x00, 0xFF, 0x33, 0x33, 0x00, 0x66, 0x66, 0x00, 0x99, 0x99, 0x00,
+    0xCC, 0xCC, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x33, 0x33, 0x00, 0x66, 0x66,
+    0x00, 0x99, 0x99, 0x00, 0xCC, 0xCC, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x33,
+    0x66, 0x00, 0x66, 0x99, 0x00, 0x99, 0xCC, 0x00, 0xCC, 0xFF, 0x00, 0xFF,
+    0xFF, 0xFF, 0x33, 0xFF, 0xFF, 0x66, 0xFF, 0xFF, 0x99, 0xFF, 0xFF, 0xCC,
+    0xFF, 0x33, 0xFF, 0xFF, 0x66, 0xFF, 0xFF, 0x99, 0xFF, 0xFF, 0xCC, 0xFF,
+    0x33, 0xFF, 0xFF, 0x66, 0xFF, 0xFF, 0x99, 0xFF, 0xFF, 0xCC, 0xFF, 0xFF,
+    0xCC, 0xCC, 0x33, 0xCC, 0xCC, 0x66, 0xCC, 0xCC, 0x99, 0xCC, 0xCC, 0xFF,
+    0xCC, 0x33, 0xCC, 0xCC, 0x66, 0xCC, 0xCC, 0x99, 0xCC, 0xCC, 0xFF, 0xCC,
+    0x33, 0xCC, 0xCC, 0x66, 0xCC, 0xCC, 0x99, 0xCC, 0xCC, 0xFF, 0xCC, 0xCC,
+    0x99, 0x99, 0x33, 0x99, 0x99, 0x66, 0x99, 0x99, 0xCC, 0x99, 0x99, 0xFF,
+    0x99, 0x33, 0x99, 0x99, 0x66, 0x99, 0x99, 0xCC, 0x99, 0x99, 0xFF, 0x99,
+    0x33, 0x99, 0x99, 0x66, 0x99, 0x99, 0xCC, 0x99, 0x99, 0xFF, 0x99, 0x99,
+    0x66, 0x66, 0x33, 0x66, 0x66, 0x99, 0x66, 0x66, 0xCC, 0x66, 0x66, 0xFF,
+    0x66, 0x33, 0x66, 0x66, 0x99, 0x66, 0x66, 0xCC, 0x66, 0x66, 0xFF, 0x66,
+    0x33, 0x66, 0x66, 0x99, 0x66, 0x66, 0xCC, 0x66, 0x66, 0xFF, 0x66, 0x66,
+    0x33, 0x33, 0x66, 0x33, 0x33, 0x99, 0x33, 0x33, 0xCC, 0x33, 0x33, 0xFF,
+    0x33, 0x66, 0x33, 0x33, 0x99, 0x33, 0x33, 0xCC, 0x33, 0x33, 0xFF, 0x33,
+    0x66, 0x33, 0x33, 0x99, 0x33, 0x33, 0xCC, 0x33, 0x33, 0xFF, 0x33, 0x33,
+    0x00, 0x33, 0x66, 0x33, 0x66, 0x00, 0x66, 0x00, 0x33, 0x00, 0x66, 0x33,
+    0x33, 0x00, 0x66, 0x66, 0x33, 0x00, 0x33, 0x66, 0x99, 0x66, 0x99, 0x33,
+    0x99, 0x33, 0x66, 0x33, 0x99, 0x66, 0x66, 0x33, 0x99, 0x99, 0x66, 0x33,
+    0x66, 0x99, 0xCC, 0x99, 0xCC, 0x66, 0xCC, 0x66, 0x99, 0x66, 0xCC, 0x99,
+    0x99, 0x66, 0xCC, 0xCC, 0x99, 0x66, 0x99, 0xCC, 0xFF, 0xCC, 0xFF, 0x99,
+    0xFF, 0x99, 0xCC, 0x99, 0xFF, 0xCC, 0xCC, 0x99, 0xFF, 0xFF, 0xCC, 0x99,
+    0x11, 0x11, 0x11, 0x22, 0x22, 0x22, 0x44, 0x44, 0x44, 0x55, 0x55, 0x55,
+    0xAA, 0xAA, 0xAA, 0xBB, 0xBB, 0xBB, 0xDD, 0xDD, 0xDD, 0xEE, 0xEE, 0xEE
+];
diff --git a/nihav-flash/src/codecs/flv263.rs b/nihav-flash/src/codecs/flv263.rs
new file mode 100644 (file)
index 0000000..3a8a18a
--- /dev/null
@@ -0,0 +1,369 @@
+use nihav_core::io::bitreader::*;
+use nihav_core::io::codebook::*;
+use nihav_core::formats;
+use nihav_core::frame::*;
+use nihav_core::codecs::*;
+use nihav_codec_support::codecs::{MV, ZIGZAG};
+use nihav_codec_support::codecs::h263::*;
+use nihav_codec_support::codecs::h263::code::*;
+use nihav_codec_support::codecs::h263::decoder::*;
+use nihav_codec_support::codecs::h263::data::*;
+
+#[allow(dead_code)]
+struct Tables {
+    intra_mcbpc_cb: Codebook<u8>,
+    inter_mcbpc_cb: Codebook<u8>,
+    cbpy_cb:        Codebook<u8>,
+    rl_cb:          Codebook<H263RLSym>,
+    aic_rl_cb:      Codebook<H263RLSym>,
+    mv_cb:          Codebook<u8>,
+}
+
+struct SparkDecoder {
+    info:    NACodecInfoRef,
+    dec:     H263BaseDecoder,
+    tables:  Tables,
+    w:       usize,
+    h:       usize,
+    bdsp:    H263BlockDSP,
+}
+
+struct SparkBR<'a> {
+    br:         BitReader<'a>,
+    tables:     &'a Tables,
+    ver1:       bool,
+    mb_w:       usize,
+    mb_h:       usize,
+}
+
+fn decode_mv_component(br: &mut BitReader, mv_cb: &Codebook<u8>) -> DecoderResult<i16> {
+    let code = i16::from(br.read_cb(mv_cb)?);
+    if code == 0 { return Ok(0) }
+    if !br.read_bool()? {
+        Ok(code)
+    } else {
+        Ok(-code)
+    }
+}
+
+fn decode_mv(br: &mut BitReader, mv_cb: &Codebook<u8>) -> DecoderResult<MV> {
+    let xval = decode_mv_component(br, mv_cb)?;
+    let yval = decode_mv_component(br, mv_cb)?;
+    Ok(MV::new(xval, yval))
+}
+
+impl<'a> SparkBR<'a> {
+    fn new(src: &'a [u8], tables: &'a Tables, w: usize, h: usize) -> Self {
+        Self {
+            br:     BitReader::new(src, BitReaderMode::BE),
+            tables,
+            ver1:   false,
+            mb_w:   (w + 15) >> 4,
+            mb_h:   (h + 15) >> 4,
+        }
+    }
+
+    fn decode_block(&mut self, quant: u8, intra: bool, coded: bool, blk: &mut [i16; 64]) -> DecoderResult<()> {
+        let br = &mut self.br;
+        let mut idx = 0;
+        if intra {
+            let mut dc = br.read(8)?;
+            if dc == 255 { dc = 128; }
+            blk[0] = (dc as i16) << 3;
+            idx = 1;
+        }
+        if !coded { return Ok(()); }
+
+        let rl_cb = &self.tables.rl_cb; // could be aic too
+        let q_add = if quant == 0 { 0i16 } else { i16::from((quant - 1) | 1) };
+        let q = i16::from(quant * 2);
+        while idx < 64 {
+            let code = br.read_cb(rl_cb)?;
+            let run;
+            let mut level;
+            let last;
+            if !code.is_escape() {
+                run   = code.get_run();
+                level = code.get_level();
+                last  = code.is_last();
+                if br.read_bool()? { level = -level; }
+                if level > 0 {
+                    level = (level * q) + q_add;
+                } else {
+                    level = (level * q) - q_add;
+                }
+            } else if !self.ver1 {
+                last  = br.read_bool()?;
+                run   = br.read(6)? as u8;
+                level = br.read_s(8)? as i16;
+                if level == -128 {
+                    let low = br.read(5)? as i16;
+                    let top = br.read_s(6)? as i16;
+                    level = (top << 5) | low;
+                }
+                if level > 0 {
+                    level = (level * q) + q_add;
+                } else {
+                    level = (level * q) - q_add;
+                }
+                if level < -2048 { level = -2048; }
+                if level >  2047 { level =  2047; }
+            } else {
+                let fmt_bit = br.read_bool()?;
+                last  = br.read_bool()?;
+                run   = br.read(6)? as u8;
+                level = br.read_s(if !fmt_bit { 7 } else { 11 })? as i16;
+                validate!(level != 0);
+                if !fmt_bit {
+                    validate!(level != 64);
+                } else {
+                    validate!(level != 1024);
+                }
+                if level > 0 {
+                    level = (level * q) + q_add;
+                } else {
+                    level = (level * q) - q_add;
+                }
+                if level < -2048 { level = -2048; }
+                if level >  2047 { level =  2047; }
+            }
+            idx += run;
+            validate!(idx < 64);
+            let oidx = ZIGZAG[idx as usize];
+            blk[oidx] = level;
+            idx += 1;
+            if last { break; }
+        }
+        Ok(())
+    }
+}
+
+
+impl<'a> BlockDecoder for SparkBR<'a> {
+    fn decode_pichdr(&mut self) -> DecoderResult<PicInfo> {
+        let br = &mut self.br;
+        let syncw                   = br.read(17)?;
+        validate!(syncw == 1);
+        let version                 = br.read(5)?;
+        validate!(version == 0 || version == 1);
+        self.ver1 = version == 1;
+        let tr                      = (br.read(8)? << 8) as u16;
+        let sfmt                    = br.read(3)?;
+        validate!(sfmt != 0b111);
+        let (w, h) = match sfmt {
+                0 => {
+                    let w           = br.read(8)? as usize;
+                    let h           = br.read(8)? as usize;
+                    validate!(w != 0 && h != 0);
+                    (w, h)
+                },
+                1 => {
+                    let w           = br.read(16)? as usize;
+                    let h           = br.read(16)? as usize;
+                    validate!(w != 0 && h != 0);
+                    (w, h)
+                },
+                2 => (352, 288),
+                3 => (176, 144),
+                4 => (128, 96),
+                5 => (320, 240),
+                6 => (160, 120),
+                _ => unreachable!(),
+            };
+        let pic_type                = br.read(2)?;
+        let ftype = match pic_type {
+                0 => Type::I,
+                1 => Type::P,
+                2 => Type::Special,
+                _ => return Err(DecoderError::InvalidData),
+            };
+        let deblock                 = br.read_bool()?;
+        let quant                   = br.read(5)?;
+        while br.read_bool()? { // skip PEI
+            br.read(8)?;
+        }
+        self.mb_w = (w + 15) >> 4;
+        self.mb_h = (h + 15) >> 4;
+
+        let plusinfo = if deblock { Some(PlusInfo::new(false, deblock, false, false)) } else { None };
+        let picinfo = PicInfo::new(w, h, ftype, MVMode::Long, true, false, quant as u8, tr, None, plusinfo);
+        Ok(picinfo)
+    }
+    fn decode_slice_header(&mut self, info: &PicInfo) -> DecoderResult<SliceInfo> {
+        Ok(SliceInfo::new(0, 0, self.mb_w * self.mb_h, info.quant))
+    }
+    fn decode_block_header(&mut self, info: &PicInfo, slice: &SliceInfo, _sstate: &SliceState) -> DecoderResult<BlockInfo> {
+        let br = &mut self.br;
+        let mut q = slice.get_quant();
+        match info.get_mode() {
+            Type::I => {
+                    let mut cbpc = br.read_cb(&self.tables.intra_mcbpc_cb)?;
+                    while cbpc == 8 { cbpc = br.read_cb(&self.tables.intra_mcbpc_cb)?; }
+                    let cbpy = br.read_cb(&self.tables.cbpy_cb)?;
+                    let cbp = (cbpy << 2) | (cbpc & 3);
+                    let dquant = (cbpc & 4) != 0;
+                    if dquant {
+                        let idx = br.read(2)? as usize;
+                        q = (i16::from(q) + i16::from(H263_DQUANT_TAB[idx])) as u8;
+                    }
+                    Ok(BlockInfo::new(Type::I, cbp, q))
+                },
+            Type::P | Type::Special => {
+                    if br.read_bool()? {
+                        return Ok(BlockInfo::new(Type::Skip, 0, info.get_quant()));
+                    }
+                    let mut cbpc = br.read_cb(&self.tables.inter_mcbpc_cb)?;
+                    while cbpc == 20 { cbpc = br.read_cb(&self.tables.inter_mcbpc_cb)?; }
+                    let is_intra = (cbpc & 0x04) != 0;
+                    let dquant   = (cbpc & 0x08) != 0;
+                    let is_4x4   = (cbpc & 0x10) != 0;
+                    if is_intra {
+                        let cbpy = br.read_cb(&self.tables.cbpy_cb)?;
+                        let cbp = (cbpy << 2) | (cbpc & 3);
+                        if dquant {
+                            let idx = br.read(2)? as usize;
+                            q = ((q as i16) + (H263_DQUANT_TAB[idx] as i16)) as u8;
+                            validate!(q < 32);
+                        }
+                        let binfo = BlockInfo::new(Type::I, cbp, q);
+                        return Ok(binfo);
+                    }
+
+                    let mut cbpy = br.read_cb(&self.tables.cbpy_cb)?;
+//                    if /* !aiv && */(cbpc & 3) != 3 {
+                        cbpy ^= 0xF;
+//                    }
+                    let cbp = (cbpy << 2) | (cbpc & 3);
+                    if dquant {
+                        let idx = br.read(2)? as usize;
+                        q = ((q as i16) + (H263_DQUANT_TAB[idx] as i16)) as u8;
+                        validate!(q < 32);
+                    }
+                    let mut binfo = BlockInfo::new(Type::P, cbp, q);
+                    if !is_4x4 {
+                        let mvec: [MV; 1] = [decode_mv(br, &self.tables.mv_cb)?];
+                        binfo.set_mv(&mvec);
+                    } else {
+                        let mvec: [MV; 4] = [
+                                decode_mv(br, &self.tables.mv_cb)?,
+                                decode_mv(br, &self.tables.mv_cb)?,
+                                decode_mv(br, &self.tables.mv_cb)?,
+                                decode_mv(br, &self.tables.mv_cb)?
+                            ];
+                        binfo.set_mv(&mvec);
+                    }
+                    Ok(binfo)
+                },
+            _ => { Err(DecoderError::InvalidData) },
+        }
+    }
+    fn decode_block_intra(&mut self, _info: &BlockInfo, _sstate: &SliceState, quant: u8, _no: usize, coded: bool, blk: &mut [i16; 64]) -> DecoderResult<()> {
+        self.decode_block(quant, true, coded, blk)
+    }
+    fn decode_block_inter(&mut self, _info: &BlockInfo, _sstate: &SliceState, quant: u8, _no: usize, coded: bool, blk: &mut [i16; 64]) -> DecoderResult<()> {
+        self.decode_block(quant, false, coded, blk)
+    }
+    fn is_slice_end(&mut self) -> bool { self.br.peek(16) == 0 }
+}
+
+impl SparkDecoder {
+    fn new() -> Self {
+        let mut coderead = H263ShortCodeReader::new(H263_INTRA_MCBPC);
+        let intra_mcbpc_cb = Codebook::new(&mut coderead, CodebookMode::MSB).unwrap();
+        let mut coderead = H263ShortCodeReader::new(H263_INTER_MCBPC);
+        let inter_mcbpc_cb = Codebook::new(&mut coderead, CodebookMode::MSB).unwrap();
+        let mut coderead = H263ShortCodeReader::new(H263_CBPY);
+        let cbpy_cb = Codebook::new(&mut coderead, CodebookMode::MSB).unwrap();
+        let mut coderead = H263RLCodeReader::new(H263_RL_CODES);
+        let rl_cb = Codebook::new(&mut coderead, CodebookMode::MSB).unwrap();
+        let mut coderead = H263RLCodeReader::new(H263_RL_CODES_AIC);
+        let aic_rl_cb = Codebook::new(&mut coderead, CodebookMode::MSB).unwrap();
+        let mut coderead = H263ShortCodeReader::new(H263_MV);
+        let mv_cb = Codebook::new(&mut coderead, CodebookMode::MSB).unwrap();
+        let tables = Tables {
+            intra_mcbpc_cb,
+            inter_mcbpc_cb,
+            cbpy_cb,
+            rl_cb,
+            aic_rl_cb,
+            mv_cb,
+        };
+
+        Self {
+            info:       NACodecInfo::new_dummy(),
+            dec:        H263BaseDecoder::new(true),
+            tables,
+            bdsp:       H263BlockDSP::new(),
+            w:          0,
+            h:          0,
+        }
+    }
+}
+
+impl NADecoder for SparkDecoder {
+    fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> {
+        if let NACodecTypeInfo::Video(vinfo) = info.get_properties() {
+            let w = vinfo.get_width();
+            let h = vinfo.get_height();
+            let fmt = formats::YUV420_FORMAT;
+            let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(w, h, false, fmt));
+            self.info = NACodecInfo::new_ref(info.get_name(), myinfo, info.get_extradata()).into_ref();
+            self.w = w;
+            self.h = h;
+
+            Ok(())
+        } else {
+            Err(DecoderError::InvalidData)
+        }
+    }
+    fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult<NAFrameRef> {
+        let src = pkt.get_buffer();
+
+        let mut ibr = SparkBR::new(&src, &self.tables, self.w, self.h);
+        let bufinfo = self.dec.parse_frame(&mut ibr, &self.bdsp)?;
+
+        let mut frm = NAFrame::new_from_pkt(pkt, self.info.clone(), bufinfo);
+        frm.set_keyframe(self.dec.is_intra());
+        frm.set_frame_type(if self.dec.is_intra() { FrameType::I } else { FrameType::P });
+        Ok(frm.into_ref())
+    }
+    fn flush(&mut self) {
+        self.dec.flush();
+    }
+}
+
+impl NAOptionHandler for SparkDecoder {
+    fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] }
+    fn set_options(&mut self, _options: &[NAOption]) { }
+    fn query_option_value(&self, _name: &str) -> Option<NAValue> { None }
+}
+
+pub fn get_decoder() -> Box<dyn NADecoder + Send> {
+    Box::new(SparkDecoder::new())
+}
+
+#[cfg(test)]
+mod test {
+    use nihav_core::codecs::RegisteredDecoders;
+    use nihav_core::demuxers::RegisteredDemuxers;
+    use nihav_codec_support::test::dec_video::*;
+    use crate::flash_register_all_decoders;
+    use crate::flash_register_all_demuxers;
+    #[test]
+    fn test_flv263() {
+        let mut dmx_reg = RegisteredDemuxers::new();
+        flash_register_all_demuxers(&mut dmx_reg);
+        let mut dec_reg = RegisteredDecoders::new();
+        flash_register_all_decoders(&mut dec_reg);
+
+        test_decoding("flv", "flv263", "assets/Flash/input.flv",
+                      Some(1000), &dmx_reg, &dec_reg, ExpectedTestResult::MD5Frames(vec![
+                            [0x9110718e, 0x794e22ad, 0x3324e552, 0xf58a0449],
+                            [0x2f7cfcd1, 0x1d6bb63b, 0x37dcd87d, 0xb0247d9c],
+                            [0x317e6355, 0xc632f2d5, 0x1d6ae472, 0x45cc1ba6],
+                            [0x7d883ffc, 0xaa8e7c68, 0x8dec683b, 0x0e0dcdea],
+                            [0x79d4cece, 0x98749753, 0xfedb0fb1, 0x5398f6a0],
+                            [0x153f1558, 0xd98a700c, 0xdb166ebe, 0xc347fd61],
+                            [0x0b31c6a9, 0xcb126876, 0xd7dd8626, 0x4a6fea9f]]));
+    }
+}
diff --git a/nihav-flash/src/codecs/mod.rs b/nihav-flash/src/codecs/mod.rs
new file mode 100644 (file)
index 0000000..1789543
--- /dev/null
@@ -0,0 +1,51 @@
+use nihav_core::codecs::*;
+
+macro_rules! validate {
+    ($a:expr) => { if !$a { println!("check failed at {}:{}", file!(), line!()); return Err(DecoderError::InvalidData); } };
+}
+
+#[cfg(feature="decoder_flv263")]
+mod flv263;
+#[cfg(feature="decoder_flashsv")]
+mod flashsv;
+
+#[cfg(feature="decoder_flv_adpcm")]
+mod adpcm;
+#[cfg(feature="decoder_asao")]
+mod asao;
+
+const DECODERS: &[DecoderInfo] = &[
+#[cfg(feature="decoder_flv263")]
+    DecoderInfo { name: "flv263", get_decoder: flv263::get_decoder },
+#[cfg(feature="decoder_flashsv")]
+    DecoderInfo { name: "flashsv", get_decoder: flashsv::get_decoder },
+#[cfg(feature="decoder_flashsv")]
+    DecoderInfo { name: "flashsv2", get_decoder: flashsv::get_decoder_v2 },
+
+#[cfg(feature="decoder_flv_adpcm")]
+    DecoderInfo { name: "flv-adpcm", get_decoder: adpcm::get_decoder },
+#[cfg(feature="decoder_asao")]
+    DecoderInfo { name: "asao", get_decoder: asao::get_decoder },
+];
+
+/// Registers all available codecs provided by this crate.
+pub fn flash_register_all_decoders(rd: &mut RegisteredDecoders) {
+    for decoder in DECODERS.iter() {
+        rd.add_decoder(*decoder);
+    }
+}
+
+#[cfg(feature="encoder_flv_adpcm")]
+mod adpcmenc;
+
+const ENCODERS: &[EncoderInfo] = &[
+#[cfg(feature="encoder_flv_adpcm")]
+    EncoderInfo { name: "flv-adpcm", get_encoder: adpcmenc::get_encoder },
+];
+
+/// Registers all available encoders provided by this crate.
+pub fn flash_register_all_encoders(re: &mut RegisteredEncoders) {
+    for encoder in ENCODERS.iter() {
+        re.add_encoder(*encoder);
+    }
+}
diff --git a/nihav-flash/src/demuxers/flv.rs b/nihav-flash/src/demuxers/flv.rs
new file mode 100644 (file)
index 0000000..52080ab
--- /dev/null
@@ -0,0 +1,518 @@
+use nihav_core::demuxers::*;
+use nihav_core::io::bitreader::*;
+
+const AVC_ID: u8 = 7;
+
+struct FLVDemuxer<'a> {
+    src:        &'a mut ByteReader<'a>,
+    vpkts:      Vec<NAPacket>,
+    vtag:       Option<u8>,
+    apkts:      Vec<NAPacket>,
+    atag:       Option<u8>,
+    vstream:    usize,
+    astream:    usize,
+    duration:   u64,
+    width:      usize,
+    height:     usize,
+}
+
+fn get_vcodec_name(tag: u8) -> DemuxerResult<&'static str> {
+    match tag {
+        2 => Ok("flv263"),
+        3 => Ok("flashsv"),
+        4 => Ok("vp6f"),
+        5 => Ok("vp6a"),
+        6 => Ok("flashsv2"),
+        7 => Ok("h264"),
+        _ => Err(DemuxerError::InvalidData),
+    }
+}
+
+impl<'a> DemuxCore<'a> for FLVDemuxer<'a> {
+    fn open(&mut self, strmgr: &mut StreamManager, seek_index: &mut SeekIndex) -> DemuxerResult<()> {
+        let mut tag = [0; 3];
+                                          self.src.read_buf(&mut tag)?;
+        validate!(&tag == b"FLV");
+        let ver                         = self.src.read_byte()?;
+        validate!(ver == 0 || ver == 1);
+        let hdr                         = self.src.read_byte()?;
+        validate!((hdr & 0xF2) == 0);
+        let has_audio = (hdr & 4) != 0;
+        let has_video = (hdr & 1) != 0;
+        validate!(has_video || has_audio);
+        let hdr_size                    = self.src.read_u32be()?;
+        validate!(hdr_size >= 9);
+
+        let first_prev_tag              = self.src.peek_u32be()?;
+        validate!(first_prev_tag == 0);
+
+        while (self.vtag.is_some() != has_video) || (self.atag.is_some() != has_audio) {
+            self.parse_tag(strmgr)?;
+            if self.apkts.len() > 100 || self.vpkts.len() > 100 {
+                return Err(DemuxerError::InvalidData);
+            }
+        }
+
+        seek_index.mode = SeekIndexMode::Automatic;
+
+        Ok(())
+    }
+
+    fn get_frame(&mut self, strmgr: &mut StreamManager) -> DemuxerResult<NAPacket> {
+        loop {
+            if !self.vpkts.is_empty() && self.vpkts.len() >= self.apkts.len() {
+                return Ok(self.vpkts.remove(0));
+            }
+            if !self.apkts.is_empty() {
+                return Ok(self.apkts.remove(0));
+            }
+            self.parse_tag(strmgr)?;
+        }
+    }
+    fn seek(&mut self, time: NATimePoint, _seek_index: &SeekIndex) -> DemuxerResult<()> {
+        let dst_ms = match time {
+                NATimePoint::PTS(pts) => pts,
+                NATimePoint::Milliseconds(ms) => ms,
+                NATimePoint::None => return Err(DemuxerError::SeekError),
+            };
+        self.apkts.clear();
+        self.vpkts.clear();
+        let mut prev = None;
+        loop {
+            let ppos                    = self.src.read_u32be()?;
+            let ret                     = self.src.read_byte();
+            if let Err(ByteIOError::EOF) = ret {
+                                          self.src.seek(SeekFrom::Current(-8 - i64::from(ppos)))?;
+                continue;
+            }
+            let data_size               = self.src.read_u24be()?;
+            let time                    = self.src.read_u24be()?;
+            let ext_time                = self.src.read_byte()?;
+            let _stream_id              = self.src.read_u24be()?;
+            let ts = (u64::from(ext_time) << 32) | u64::from(time);
+            if dst_ms == ts {
+                                          self.src.seek(SeekFrom::Current(-15))?;
+                return Ok(());
+            }
+            if let Some(p_ts) = prev {
+                if dst_ms > p_ts && dst_ms < ts {
+                                          self.src.seek(SeekFrom::Current(-19 - i64::from(ppos)))?;
+                    return Ok(());
+                }
+            }
+            prev = Some(ts);
+            if dst_ms < ts {
+                                          self.src.seek(SeekFrom::Current(-19 - i64::from(ppos)))?;
+            } else {
+                                          self.src.seek(SeekFrom::Current(i64::from(data_size)))?;
+            }
+        }
+    }
+    fn get_duration(&self) -> u64 { self.duration }
+}
+
+impl<'a> NAOptionHandler for FLVDemuxer<'a> {
+    fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] }
+    fn set_options(&mut self, _options: &[NAOption]) { }
+    fn query_option_value(&self, _name: &str) -> Option<NAValue> { None }
+}
+
+impl<'a> FLVDemuxer<'a> {
+    fn new(io: &'a mut ByteReader<'a>) -> Self {
+        Self {
+            src:        io,
+            vpkts:      Vec::with_capacity(2),
+            apkts:      Vec::with_capacity(2),
+            vtag:       None,
+            atag:       None,
+            vstream:    0,
+            astream:    0,
+            duration:   0,
+            width:      0,
+            height:     0,
+        }
+    }
+    fn parse_tag(&mut self, strmgr: &mut StreamManager) -> DemuxerResult<()> {
+        let _prev_tag_size              = self.src.read_u32be()?;
+        let ret                         = self.src.read_byte();
+        if let Err(ByteIOError::EOF) = ret {
+            return Err(DemuxerError::EOF);
+        }
+
+        let tag = ret?;
+        let mut data_size               = self.src.read_u24be()? as usize;
+        let time                        = self.src.read_u24be()?;
+        let ext_time                    = self.src.read_byte()?;
+        let stream_id                   = self.src.read_u24be()?;
+        validate!(stream_id == 0);
+        if data_size == 0 {
+            return Ok(());
+        }
+        let pkt_start = self.src.tell();
+        match tag {
+            8 => {
+                let hdr                 = self.src.read_byte()?;
+                if let Some(tag) = self.atag {
+                    validate!(tag == (hdr >> 4));
+                } else if data_size > 0 {
+                    let cname = match hdr >> 4 {
+                            0 | 3 => "pcm",
+                            1 => "flv-adpcm",
+                            2 | 14 => "mp3",
+                            4..=6 => "asao",
+                            7 => "alaw",
+                            8 => "ulaw",
+                            10 => "aac",
+                            11 => "speex",
+                            _ => return Err(DemuxerError::InvalidData),
+                        };
+                    let mut srate = match (hdr >> 2) & 0x3 {
+                            0 => 5500,
+                            1 => 11025,
+                            2 => 22050,
+                            _ => 44100,
+                        };
+                    let bits = if (hdr & 2) == 0 { 8 } else { 16 };
+                    let mut channels = if (hdr & 1) == 0 { 1 } else { 2 };
+                    let mut aac_edata = false;
+                    match hdr >> 4 {
+                         4 => { srate = 16000; channels = 1; },
+                         5 => { srate = 8000; channels = 1; },
+                        10 => { aac_edata = self.src.read_byte()? == 0; },
+                        14 => srate = 8000,
+                         _ => {},
+                    };
+                    let edata = if aac_edata {
+                            let pkt_hdr_size = (self.src.tell() - pkt_start) as usize;
+                            validate!(data_size >= pkt_hdr_size);
+                            let mut data = vec![0; data_size - pkt_hdr_size];
+                                              self.src.read_buf(&mut data)?;
+                            Some(data)
+                        } else {
+                            None
+                        };
+                    let soniton = if bits == 16 { SND_S16P_FORMAT } else { SND_U8_FORMAT };
+                    let ahdr = NAAudioInfo::new(srate, channels, soniton, 0);
+                    let ci = NACodecTypeInfo::Audio(ahdr);
+                    let ainfo = NACodecInfo::new(cname, ci, edata);
+                    if let Some(id) = strmgr.add_stream(NAStream::new(StreamType::Audio, 1, ainfo, 1, 1000, 0)) {
+                        self.astream = id;
+                    } else {
+                        return Err(DemuxerError::MemoryError);
+                    }
+                    self.atag = Some(hdr >> 4);
+
+                    if aac_edata {
+                        return Ok(());
+                    }
+                }
+                if (hdr >> 4) == 10 {
+                    let pkt_type            = self.src.read_byte()?;
+                    validate!(pkt_type == 1);
+                }
+                let pkt_hdr_size = (self.src.tell() - pkt_start) as usize;
+                validate!(data_size >= pkt_hdr_size);
+                data_size -= pkt_hdr_size;
+                if data_size > 0 {
+                    let stream = strmgr.get_stream(self.astream).unwrap();
+                    let (tb_num, tb_den) = stream.get_timebase();
+                    let pts = (u64::from(ext_time) << 24) | u64::from(time);
+                    let ts = NATimeInfo::new(Some(pts), None, None, tb_num, tb_den);
+                    self.apkts.push(self.src.read_packet(stream, ts, true, data_size)?);
+                }
+            },
+            9 => {
+                let hdr                 = self.src.read_byte()?;
+                let ftype = match hdr >> 4 {
+                        1 => FrameType::I,
+                        2 => FrameType::P,
+                        3 => FrameType::P, // droppable
+                        4 => FrameType::Other, // generated key frame
+                        5 => FrameType::Other, // video info/command frame
+                        _ => return Err(DemuxerError::InvalidData),
+                    };
+                let codec_tag = hdr & 0xF;
+                if let Some(id) = self.vtag {
+                    validate!(id == codec_tag);
+                } else {
+                    let cname = get_vcodec_name(codec_tag)?;
+                    let is_avc = codec_tag == AVC_ID;
+                    if is_avc {
+                        let pkt_type            = self.src.read_byte()?;
+                        validate!(pkt_type == 0);
+                                                  self.src.read_u24be()?;
+                    }
+                    let mut edata = None;
+                    let (width, height) = match codec_tag {
+                            2 => {
+                                let mut buf = [0; 9];
+                                                  self.src.peek_buf(&mut buf)?;
+                                let mut br = BitReader::new(&buf, BitReaderMode::BE);
+                                                br.skip(30).unwrap_or(());
+                                let sfmt      = br.read(3).unwrap_or(7);
+                                match sfmt {
+                                    0 => {
+                                        let w           = br.read(8).unwrap_or(0) as usize;
+                                        let h           = br.read(8).unwrap_or(0) as usize;
+                                        (w, h)
+                                    },
+                                    1 => {
+                                        let w           = br.read(16).unwrap_or(0) as usize;
+                                        let h           = br.read(16).unwrap_or(0) as usize;
+                                        (w, h)
+                                    },
+                                    2 => (352, 288),
+                                    3 => (176, 144),
+                                    4 => (128, 96),
+                                    5 => (320, 240),
+                                    6 => (160, 120),
+                                    _ => (0, 0),
+                                }
+                            },
+                            3 | 6 => {
+                                let mut buf = [0; 4];
+                                                  self.src.peek_buf(&mut buf)?;
+                                let w = (read_u16be(&buf[0..])? & 0xFFF) as usize;
+                                let h = (read_u16be(&buf[2..])? & 0xFFF) as usize;
+                                (w, h)
+                            },
+                            4 => {
+                                let mut buf = [0; 7];
+                                                  self.src.peek_buf(&mut buf)?;
+                                let off = if (buf[1] & 1) != 0 || (buf[2] & 6) == 0 { 5 } else { 3 };
+                                validate!(buf[off] != 0 && buf[off + 1] != 0);
+                                let w = usize::from(buf[off + 1]) * 16 - usize::from(buf[0] >> 4);
+                                let h = usize::from(buf[off])     * 16 - usize::from(buf[0] & 0xF);
+
+                                edata = Some(vec![buf[0]]);
+
+                                (w, h)
+                            },
+                            5 => {
+                                let mut buf = [0; 10];
+                                                  self.src.peek_buf(&mut buf)?;
+                                let off = if (buf[4] & 1) != 0 || (buf[5] & 6) == 0 { 8 } else { 6 };
+                                validate!(buf[off] != 0 && buf[off + 1] != 0);
+                                let w = usize::from(buf[off + 1]) * 16 - usize::from(buf[0] >> 4);
+                                let h = usize::from(buf[off])     * 16 - usize::from(buf[0] & 0xF);
+
+                                edata = Some(vec![buf[0]]);
+
+                                (w, h)
+                            },
+                            7 => {
+                                let pkt_hdr_size = (self.src.tell() - pkt_start) as usize;
+                                validate!(data_size >= pkt_hdr_size);
+                                data_size -= pkt_hdr_size;
+                                let mut data = vec![0; data_size + 4];
+                                data[..4].copy_from_slice(b"avcC");
+                                                  self.src.read_buf(&mut data[4..])?;
+                                edata = Some(data);
+                                (self.width, self.height)
+                            },
+                            _ => unreachable!(),
+                        };
+
+                    let vhdr = NAVideoInfo::new(width, height, false, YUV420_FORMAT);
+                    let vci = NACodecTypeInfo::Video(vhdr);
+                    let vinfo = NACodecInfo::new(cname, vci, edata);
+                    if let Some(id) = strmgr.add_stream(NAStream::new(StreamType::Video, 0, vinfo, 1, 1000, 0)) {
+                        self.vstream = id;
+                    } else {
+                        return Err(DemuxerError::MemoryError);
+                    }
+                    self.vtag = Some(codec_tag);
+                    if is_avc {
+                        return Ok(());
+                    }
+                }
+                let mut cts = 0;
+                match codec_tag {
+                    4 | 5 => {
+                                          self.src.read_skip(1)?;
+                    },
+                    7 => {
+                        let pkt_type            = self.src.read_byte()?;
+                        if pkt_type == 1 {
+                            cts                 = ((self.src.read_u24be()? << 8) as i32) >> 8;
+                        } else if pkt_type == 2 {
+                            let pkt_hdr_size = (self.src.tell() - pkt_start) as usize;
+                            validate!(data_size >= pkt_hdr_size);
+                            data_size -= pkt_hdr_size;
+                                                  self.src.read_skip(data_size)?;
+                            return Ok(());
+                        }
+                    },
+                    _ => {},
+                };
+
+                let pkt_hdr_size = (self.src.tell() - pkt_start) as usize;
+                validate!(data_size >= pkt_hdr_size);
+                data_size -= pkt_hdr_size;
+
+                if data_size > 0 {
+                    let stream = strmgr.get_stream(self.vstream).unwrap();
+                    let (tb_num, tb_den) = stream.get_timebase();
+                    let pts = (u64::from(ext_time) << 24) | u64::from(time);
+                    let dts = ((pts as i64) + i64::from(cts)).max(0) as u64;
+                    let ts = NATimeInfo::new(Some(pts), Some(dts), None, tb_num, tb_den);
+                    self.vpkts.push(self.src.read_packet(stream, ts, ftype == FrameType::I, data_size)?);
+                }
+            },
+            18 => {
+                let end = self.src.tell() + (data_size as u64);
+                let ntype           = self.src.read_byte()?;
+                validate!(ntype == 2);
+                let nlen            = self.src.read_u16be()? as usize;
+                validate!(nlen > 0);
+                let mut name = vec![0; nlen];
+                                      self.src.read_buf(&mut name)?;
+                if &name == b"onMetaData" {
+                    let otype               = self.src.read_byte()?;
+                    validate!(otype == 8);
+                    let _size               = self.src.read_u32be()?;
+                    while self.src.tell() < end {
+                        let nlen            = self.src.read_u16be()? as usize;
+                        if nlen == 0 {
+                            let emarker     = self.src.peek_byte()?;
+                            if emarker == 9 {
+                                              self.src.read_skip(1)?;
+                                break;
+                            }
+                        }
+                        let mut name = vec![0; nlen];
+                                              self.src.read_buf(&mut name)?;
+                        let vtype           = self.src.read_byte()?;
+                        match vtype {
+                            0 => {
+                                let val     = self.src.read_f64be()?;
+                                match name.as_slice() {
+                                    b"duration" => self.duration = (val * 1000.0) as u64,
+                                    b"width"    => self.width  = val as usize,
+                                    b"height"   => self.height = val as usize,
+                                    b"videocodecid" => {
+                                        let codec_tag = val as u8;
+                                        if self.vtag.is_none() && codec_tag != AVC_ID && self.width != 0 && self.height != 0 {
+                                            let cname = get_vcodec_name(codec_tag)?;
+                                            let edata = if cname.starts_with("vp6") {
+                                                    let ebyte = ((16 - (self.width & 0xF)) & 0xF) * 16 + ((16 - (self.height & 0xF)) & 0xF);
+                                                    Some(vec![ebyte as u8])
+                                                } else { None };
+                                            let vhdr = NAVideoInfo::new(self.width, self.height, false, YUV420_FORMAT);
+                                            let vci = NACodecTypeInfo::Video(vhdr);
+                                            let vinfo = NACodecInfo::new(cname, vci, edata);
+                                            if let Some(id) = strmgr.add_stream(NAStream::new(StreamType::Video, 0, vinfo, 1, 1000, 0)) {
+                                                self.vstream = id;
+                                            } else {
+                                                return Err(DemuxerError::MemoryError);
+                                            }
+                                            self.vtag = Some(codec_tag);
+                                        }
+                                    },
+                                    _ => {},
+                                };
+                            },
+                            1 => {
+                                let _val    = self.src.read_byte()?;
+                            },
+                            2 => {
+                                let len     = self.src.read_u16be()? as usize;
+                                let mut val = vec![0; len];
+                                              self.src.read_buf(&mut val)?;
+                            },
+                            3 => {
+                                break;//unimplemented!();
+                            },
+                            5 => {},
+                            6 => {},
+                            7 => {
+                                              self.src.read_u16be()?;
+                            },
+                            8 => {
+                                unimplemented!();
+                            },
+                            10 => {
+                                unimplemented!();
+                            },
+                            11 => {
+                                              self.src.read_f64be()?;
+                                              self.src.read_u16be()?;
+                            },
+                            12 => {
+                                let len     = self.src.read_u16be()? as usize;
+                                let mut val = vec![0; len];
+                                              self.src.read_buf(&mut val)?;
+                            },
+                            _ => break,
+                        };
+                    }
+                }
+                validate!(self.src.tell() <= end);
+                let to_skip = (end - self.src.tell()) as usize;
+                                          self.src.read_skip(to_skip)?;
+            },
+            _ => {
+                                          self.src.read_skip(data_size)?;
+            },
+        };
+        Ok(())
+    }
+}
+
+pub struct FLVDemuxerCreator { }
+
+impl DemuxerCreator for FLVDemuxerCreator {
+    fn new_demuxer<'a>(&self, br: &'a mut ByteReader<'a>) -> Box<dyn DemuxCore<'a> + 'a> {
+        Box::new(FLVDemuxer::new(br))
+    }
+    fn get_name(&self) -> &'static str { "flv" }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use std::fs::File;
+
+    #[test]
+    fn test_flv_demux() {
+        let mut file = File::open("assets/Flash/input.flv").unwrap();
+        let mut fr = FileReader::new_read(&mut file);
+        let mut br = ByteReader::new(&mut fr);
+        let mut dmx = FLVDemuxer::new(&mut br);
+        let mut sm = StreamManager::new();
+        let mut si = SeekIndex::new();
+        dmx.open(&mut sm, &mut si).unwrap();
+
+        loop {
+            let pktres = dmx.get_frame(&mut sm);
+            if let Err(e) = pktres {
+                if e == DemuxerError::EOF { break; }
+                panic!("error");
+            }
+            let pkt = pktres.unwrap();
+            println!("Got {}", pkt);
+        }
+    }
+    #[test]
+    fn test_flv_demux_back() {
+        let mut file = File::open("assets/Flash/input.flv").unwrap();
+        let mut fr = FileReader::new_read(&mut file);
+        let mut br = ByteReader::new(&mut fr);
+        let mut dmx = FLVDemuxer::new(&mut br);
+        let mut sm = StreamManager::new();
+        let mut si = SeekIndex::new();
+        dmx.open(&mut sm, &mut si).unwrap();
+        dmx.src.seek(SeekFrom::End(-4)).unwrap();
+        dmx.seek(NATimePoint::Milliseconds(7500), &si).unwrap();
+
+        loop {
+            let pktres = dmx.get_frame(&mut sm);
+            if let Err(e) = pktres {
+                if e == DemuxerError::EOF { break; }
+                panic!("error");
+            }
+            let pkt = pktres.unwrap();
+            println!("Got {}", pkt);
+        }
+    }
+}
diff --git a/nihav-flash/src/demuxers/mod.rs b/nihav-flash/src/demuxers/mod.rs
new file mode 100644 (file)
index 0000000..a06fc48
--- /dev/null
@@ -0,0 +1,22 @@
+use nihav_core::demuxers::*;
+
+
+#[allow(unused_macros)]
+macro_rules! validate {
+    ($a:expr) => { if !$a { println!("check failed at {}:{}", file!(), line!()); return Err(DemuxerError::InvalidData); } };
+}
+
+#[cfg(feature="demuxer_flv")]
+mod flv;
+
+const DEMUXERS: &[&dyn DemuxerCreator] = &[
+#[cfg(feature="demuxer_flv")]
+    &flv::FLVDemuxerCreator {},
+];
+
+/// Registers all available demuxers provided by this crate.
+pub fn flash_register_all_demuxers(rd: &mut RegisteredDemuxers) {
+    for demuxer in DEMUXERS.iter() {
+        rd.add_demuxer(*demuxer);
+    }
+}
diff --git a/nihav-flash/src/lib.rs b/nihav-flash/src/lib.rs
new file mode 100644 (file)
index 0000000..bfe1756
--- /dev/null
@@ -0,0 +1,21 @@
+//! Crate for providing support for various Flash-related formats.
+extern crate nihav_core;
+extern crate nihav_codec_support;
+
+mod codecs;
+
+#[cfg(feature="decoders")]
+pub use crate::codecs::flash_register_all_decoders;
+
+#[cfg(feature="demuxers")]
+mod demuxers;
+#[cfg(feature="demuxers")]
+pub use crate::demuxers::flash_register_all_demuxers;
+
+#[cfg(feature="encoders")]
+pub use crate::codecs::flash_register_all_encoders;
+
+#[cfg(feature="muxers")]
+mod muxers;
+#[cfg(feature="muxers")]
+pub use crate::muxers::flash_register_all_muxers;
diff --git a/nihav-flash/src/muxers/flv.rs b/nihav-flash/src/muxers/flv.rs
new file mode 100644 (file)
index 0000000..9f32fbe
--- /dev/null
@@ -0,0 +1,323 @@
+use nihav_core::muxers::*;
+
+const FLV_VCODECS: &[(&str, u8)] = &[
+    ("flv263", 2),
+    ("flashsv", 3),
+    ("vp6f", 4),
+    ("vp6a", 5),
+    ("flashsv2", 6),
+    ("h264", AVC_ID)
+];
+
+const NO_CODEC: u8 = 0;
+const AVC_ID: u8 = 7;
+const AAC_ID: u8 = 10;
+
+fn find_audio_tag(cname: &'static str, rate: u32, channels: u8) -> MuxerResult<u8> {
+    if channels != 1 && channels != 2 {
+        return Err(MuxerError::InvalidArgument);
+    }
+    let tag = match cname {
+            "flv-adpcm" => 1,
+            "pcm" => 3,
+            "mp3" => if rate != 8000 { 2 } else { return Ok(14); },
+            "asao" => {
+                if channels != 1 {
+                    return Err(MuxerError::InvalidArgument);
+                }
+                match rate {
+                    16000 => return Ok(4),
+                    8000 => return Ok(5),
+                    _ => 6,
+                }
+            },
+            "alaw" => 7,
+            "ulaw" => 8,
+            "aac" => AAC_ID,
+            "speex" => 11,
+            _ => return Err(MuxerError::InvalidArgument),
+        };
+    match rate {
+        5500 | 11025 | 22050 | 44100 => {},
+        _ => return Err(MuxerError::InvalidArgument),
+    };
+    Ok(tag)
+}
+
+trait FLVPropertyWriter {
+    fn write_property_num(&mut self, name: &str, val: f64) -> MuxerResult<()>;
+    fn write_property_bool(&mut self, name: &str, val: bool) -> MuxerResult<()>;
+}
+
+impl<'a> FLVPropertyWriter for ByteWriter<'a> {
+    fn write_property_num(&mut self, name: &str, val: f64) -> MuxerResult<()> {
+        self.write_u16be(name.len() as u16)?;
+        self.write_buf(name.as_bytes())?;
+        self.write_byte(0)?;
+        self.write_f64be(val)?;
+        Ok(())
+    }
+    fn write_property_bool(&mut self, name: &str, val: bool) -> MuxerResult<()> {
+        self.write_u16be(name.len() as u16)?;
+        self.write_buf(name.as_bytes())?;
+        self.write_byte(1)?;
+        self.write_byte(val as u8)?;
+        Ok(())
+    }
+}
+
+macro_rules! write_packet {
+    ($self: expr, $pkt_type: expr, $ts: expr, $code: block) => {
+        let start = $self.bw.tell();
+        $self.bw.write_byte($pkt_type)?;
+        $self.bw.write_u24be(0)?;
+        $self.bw.write_u24be($ts)?;
+        $self.bw.write_byte(($ts >> 24) as u8)?;
+        $self.bw.write_u24be(0)?;
+
+        $code
+
+        let end = $self.bw.tell();
+        let size = end - start - 11;
+        $self.bw.seek(SeekFrom::Start(start + 1))?;
+        $self.bw.write_u24be(size as u32)?;
+        $self.bw.seek(SeekFrom::Start(end))?;
+        $self.bw.write_u32be((size + 11) as u32)?;
+    }
+}
+
+struct FLVMuxer<'a> {
+    bw:     &'a mut ByteWriter<'a>,
+    atag:   u8,
+    ahdr:   u8,
+    vtag:   u8,
+    vp6b:   u8,
+    time:   u32,
+    dpos:   u64,
+}
+
+impl<'a> FLVMuxer<'a> {
+    fn new(bw: &'a mut ByteWriter<'a>) -> Self {
+        Self {
+            bw,
+            atag:   NO_CODEC,
+            ahdr:   0,
+            vtag:   NO_CODEC,
+            vp6b:   0,
+            time:   0,
+            dpos:   0,
+        }
+    }
+    fn write_metadata(&mut self, strmgr: &StreamManager) -> MuxerResult<()> {
+        write_packet!(self, 18, 0, {
+            self.bw.write_buf(b"\x02\x00\x0AonMetaData\x08\x00\x00\x00\x00")?;
+            for stream in strmgr.iter() {
+                match stream.get_info().get_properties() {
+                    NACodecTypeInfo::Video(ref vinfo) => {
+                        self.bw.write_property_num("width", vinfo.width as f64)?;
+                        self.bw.write_property_num("height", vinfo.height as f64)?;
+                        self.bw.write_property_num("videocodecid", self.vtag as f64)?;
+                    },
+                    NACodecTypeInfo::Audio(ref ainfo) => {
+                        self.bw.write_property_num("audiosamplerate", ainfo.sample_rate as f64)?;
+                        self.bw.write_property_bool("stereo", ainfo.channels == 2)?;
+                        self.bw.write_property_num("audiocodecid", self.atag as f64)?;
+                    },
+                    _ => {},
+                };
+            }
+            self.bw.write_property_num("duration", 0.0)?;
+            self.dpos = self.bw.tell() - 8;
+            self.bw.write_u16be(0)?;
+            self.bw.write_byte(9)?;
+        });
+
+        Ok(())
+    }
+}
+
+impl<'a> MuxCore<'a> for FLVMuxer<'a> {
+    fn create(&mut self, strmgr: &StreamManager) -> MuxerResult<()> {
+        if strmgr.get_num_streams() == 0 || strmgr.get_num_streams() > 2 {
+            return Err(MuxerError::InvalidArgument);
+        }
+
+        let mut astream = None;
+        let mut vstream = None;
+        for stream in strmgr.iter() {
+            let cname = stream.get_info().get_name();
+            match stream.get_media_type() {
+                StreamType::Video => {
+                    vstream = Some(stream.clone());
+                    if self.vtag != NO_CODEC {
+                        return Err(MuxerError::InvalidArgument);
+                    }
+                    for &(name, tag) in FLV_VCODECS.iter() {
+                        if name == cname {
+                            self.vtag = tag;
+                            break;
+                        }
+                    }
+                    if self.vtag == NO_CODEC {
+                        return Err(MuxerError::UnsupportedFormat);
+                    }
+                },
+                StreamType::Audio => {
+                    astream = Some(stream.clone());
+                    if self.atag != NO_CODEC {
+                        return Err(MuxerError::InvalidArgument);
+                    }
+                    if let Some(ainfo) = stream.get_info().get_properties().get_audio_info() {
+                        self.atag = find_audio_tag(cname, ainfo.sample_rate, ainfo.channels)?;
+                        self.ahdr = (self.atag << 4) |
+                            (match ainfo.sample_rate {
+                                5500 => 0,
+                                11025 => 1,
+                                22050 => 2,
+                                _     => 3,
+                            } << 2) |
+                            if ainfo.format.bits == 8 { 0 } else { 2 } |
+                            if ainfo.channels == 1 { 0 } else { 1 };
+                    } else {
+                        return Err(MuxerError::InvalidArgument);
+                    }
+                },
+                _ => return Err(MuxerError::UnsupportedFormat),
+            };
+        }
+
+        self.bw.write_buf(b"FLV\x01")?;
+        let flags = 0x8 | if self.atag != NO_CODEC { 4 } else { 0 } | if self.vtag != NO_CODEC { 1 } else { 0 };
+        self.bw.write_byte(flags)?;
+        self.bw.write_u32be(9)?;
+        self.bw.write_u32be(0)?;
+
+        self.write_metadata(strmgr)?;
+
+        if let (true, Some(ref stream)) = (self.vtag == 4 || self.vtag == 5, &vstream) {
+            if let Some(edata) = stream.get_info().get_extradata() {
+                if !edata.is_empty() {
+                    self.vp6b = edata[0];
+                }
+            }
+        }
+
+        if let (true, Some(stream)) = (self.vtag == AVC_ID, vstream) {
+            if let Some(edata) = stream.get_info().get_extradata() {
+                validate!(edata.len() > 4);
+                write_packet!(self, 9, 0, {
+                    self.bw.write_byte(0x57)?;
+                    self.bw.write_byte(0x00)?;
+                    self.bw.write_u24be(0)?;
+                    self.bw.write_buf(&edata[4..])?;
+                });
+            }
+        }
+        if let (true, Some(stream)) = (self.atag == AAC_ID, astream) {
+            if let Some(edata) = stream.get_info().get_extradata() {
+                write_packet!(self, 8, 0, {
+                    self.bw.write_byte(self.ahdr)?;
+                    self.bw.write_byte(0x00)?;
+                    self.bw.write_buf(&edata)?;
+                });
+            }
+        }
+
+        Ok(())
+    }
+    fn mux_frame(&mut self, _strmgr: &StreamManager, pkt: NAPacket) -> MuxerResult<()> {
+        let stream = pkt.get_stream();
+        let pts = pkt.get_pts().unwrap_or(0);
+        let ms = NATimeInfo::ts_to_time(pts, 1000, pkt.ts.tb_num, pkt.ts.tb_den) as u32;
+        self.time = self.time.max(ms);
+        match stream.get_media_type() {
+            StreamType::Video => {
+                write_packet!(self, 9, ms, {
+                    let hdr = self.vtag | if pkt.keyframe { 0x10 } else { 0x20 };
+                    self.bw.write_byte(hdr)?;
+                    match self.vtag {
+                        4 | 5 => {
+                            self.bw.write_byte(self.vp6b)?;
+                        },
+                        AVC_ID => {
+                            self.bw.write_byte(1)?;
+                            let cms = NATimeInfo::ts_to_time(pkt.get_pts().unwrap_or(pts), 1000, pkt.ts.tb_num, pkt.ts.tb_den) as u32;
+                            let cts = cms.wrapping_sub(ms) << 8 >> 8;
+                            self.bw.write_u24be(cts)?;
+                        },
+                        _ => {},
+                    };
+                    self.bw.write_buf(&pkt.get_buffer())?;
+                });
+            },
+            StreamType::Audio => {
+                write_packet!(self, 8, ms, {
+                    self.bw.write_byte(self.ahdr)?;
+                    if self.atag == AAC_ID {
+                        self.bw.write_byte(1)?;
+                    }
+                    self.bw.write_buf(&pkt.get_buffer())?;
+                });
+            },
+            _ => return Err(MuxerError::InvalidData),
+        };
+        Ok(())
+    }
+    fn flush(&mut self) -> MuxerResult<()> {
+        Ok(())
+    }
+    fn end(&mut self) -> MuxerResult<()> {
+        self.bw.seek(SeekFrom::Start(self.dpos))?;
+        self.bw.write_f64be((self.time as f64) / 1000.0)?;
+        Ok(())
+    }
+}
+
+impl<'a> NAOptionHandler for FLVMuxer<'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 FLVMuxerCreator {}
+
+impl MuxerCreator for FLVMuxerCreator {
+    fn new_muxer<'a>(&self, bw: &'a mut ByteWriter<'a>) -> Box<dyn MuxCore<'a> + 'a> {
+        Box::new(FLVMuxer::new(bw))
+    }
+    fn get_name(&self) -> &'static str { "flv" }
+    fn get_capabilities(&self) -> MuxerCapabilities { MuxerCapabilities::SingleVideoAndAudio("any", "any") }
+}
+
+#[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_flv_muxer() {
+        let mut dmx_reg = RegisteredDemuxers::new();
+        flash_register_all_demuxers(&mut dmx_reg);
+        let dec_config = DecoderTestParams {
+                demuxer:        "flv",
+                in_name:        "assets/Flash/input.flv",
+                limit:          None,
+                stream_type:    StreamType::None,
+                dmx_reg, dec_reg: RegisteredDecoders::new(),
+            };
+        let mut mux_reg = RegisteredMuxers::new();
+        flash_register_all_muxers(&mut mux_reg);
+        /*let enc_config = EncoderTestParams {
+                muxer:      "flv",
+                enc_name:   "",
+                out_name:   "muxed.flv",
+                mux_reg, enc_reg: RegisteredEncoders::new(),
+            };
+        test_remuxing(&dec_config, &enc_config);*/
+        test_remuxing_md5(&dec_config, "flv", &mux_reg,
+                          [0xc777b605, 0x5777919d, 0x47996fe8, 0xf5e8d64f]);
+    }
+}
diff --git a/nihav-flash/src/muxers/mod.rs b/nihav-flash/src/muxers/mod.rs
new file mode 100644 (file)
index 0000000..692c5ad
--- /dev/null
@@ -0,0 +1,20 @@
+use nihav_core::muxers::*;
+
+#[allow(unused_macros)]
+macro_rules! validate {
+    ($a:expr) => { if !$a { println!("check failed at {}:{}", file!(), line!()); return Err(MuxerError::InvalidData); } };
+}
+
+#[cfg(feature="muxer_flv")]
+mod flv;
+
+const MUXERS: &[&dyn MuxerCreator] = &[
+#[cfg(feature="muxer_flv")]
+    &flv::FLVMuxerCreator {},
+];
+
+pub fn flash_register_all_muxers(rm: &mut RegisteredMuxers) {
+    for muxer in MUXERS.iter() {
+        rm.add_muxer(*muxer);
+    }
+}
index 11fd2ed00be1d6140cf42587071551d9840284be..0f5af8214d9c4badae1bf8fad8039c2f4942c64e 100644 (file)
@@ -235,6 +235,12 @@ const DETECTORS: &[DetectConditions] = &[
         extensions: ".y4m",
         conditions: &[CheckItem{offs: 0, cond: &CC::Str(b"YUV4MPEG2 ") }],
     },
+    DetectConditions {
+        demux_name: "flv",
+        extensions: ".flv",
+        conditions: &[CheckItem{offs: 0, cond: &CC::Str(b"FLV") },
+                      CheckItem{offs: 3, cond: &CC::Le(Arg::Byte(1)) }],
+    },
     DetectConditions {
         demux_name: "ivf",
         extensions: ".ivf",
index 51120a5a389132728551f095007cb95e3edf4775..52da347d3f8a5bd390f40c6d7c8d32c27ae8bfea 100644 (file)
@@ -219,7 +219,8 @@ static CODEC_REGISTER: &[CodecDescription] = &[
     desc!(video;    "vp4",           "VP4"),
     desc!(video;    "vp5",           "VP5"),
     desc!(video;    "vp6",           "VP6"),
-    desc!(video;    "vp6a",          "VP6"),
+    desc!(video;    "vp6f",          "VP6 (in Flash)"),
+    desc!(video;    "vp6a",          "VP6 with alpha"),
     desc!(video;    "vp7",           "VP7"),
     desc!(video;    "vp8",           "VP8"),
     desc!(video;    "vp9",           "VP9"),
@@ -228,6 +229,15 @@ static CODEC_REGISTER: &[CodecDescription] = &[
     desc!(audio;    "on2avc-500",    "On2 AVC"),
     desc!(audio;    "on2avc-501",    "On2 AVC"),
 
+    desc!(video;     "flv263",       "Sorenson H.263"),
+    desc!(video-llp; "flashsv",      "Flash Screen Video"),
+    desc!(video-llp; "flashsv2",     "Flash Screen Video 2"),
+    desc!(audio;     "asao",         "N*llym*s*r ASAO"),
+    desc!(audio;     "flv-adpcm",    "Flash ADPCM"),
+
+    desc!(audio;     "mp3",          "MPEG Audio Layer III"),
+    desc!(audio;     "speex",        "Speex"),
+
     desc!(video;    "gdv-video",     "Gremlin Digital Video - video"),
     desc!(audio;    "gdv-audio",     "Gremlin Digital Video - audio"),
     desc!(video;    "bmv-video",     "BMV video"),