Bink Audio encoder (version b for now)
authorKostya Shishkov <kostya.shishkov@gmail.com>
Sat, 7 Oct 2023 16:51:41 +0000 (18:51 +0200)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Sat, 7 Oct 2023 16:52:44 +0000 (18:52 +0200)
nihav-rad/Cargo.toml
nihav-rad/src/codecs/binkaudenc.rs [new file with mode: 0644]
nihav-rad/src/codecs/mod.rs

index 6574300923ef04a101b7a7495b8e3a0745ffad4d..1bfa7121265d0c689d317158fb7eb926b2552e5e 100644 (file)
@@ -12,7 +12,7 @@ path = "../nihav-codec-support"
 features = ["dct", "fft"]
 
 [dev-dependencies]
-nihav_commonfmt = { path = "../nihav-commonfmt", default-features=false, features = ["demuxer_y4m", "decoder_rawvideo"] }
+nihav_commonfmt = { path = "../nihav-commonfmt", default-features=false, features = ["demuxer_wav", "demuxer_y4m", "decoder_pcm", "decoder_rawvideo"] }
 
 [features]
 default = ["all_decoders", "all_demuxers", "all_encoders", "all_muxers"]
diff --git a/nihav-rad/src/codecs/binkaudenc.rs b/nihav-rad/src/codecs/binkaudenc.rs
new file mode 100644 (file)
index 0000000..b8a03ab
--- /dev/null
@@ -0,0 +1,728 @@
+use std::collections::VecDeque;
+
+use nihav_core::codecs::*;
+use nihav_core::io::bitwriter::*;
+use nihav_codec_support::dsp::dct::*;
+use nihav_codec_support::dsp::fft::*;
+use super::binkauddata::*;
+
+trait WriteBinkFloat {
+    fn write_float(&mut self, val: f32);
+}
+
+impl WriteBinkFloat for BitWriter {
+    fn write_float(&mut self, val: f32) {
+        let bits = val.to_bits();
+        let sign = bits >> 31;
+        let nexp = ((bits >> 23).wrapping_sub(0x7E)) & 0x1F;
+        let mant = bits & ((1 << 23) - 1);
+        self.write(nexp, 5);
+        self.write(mant, 23);
+        self.write(sign, 1);
+    }
+}
+
+#[derive(Default)]
+struct RateControl {
+    quality:        u8,
+    bitrate:        u32,
+    bitpool:        u32,
+    spos:           u32,
+    srate:          u32,
+    blk_size:       u32,
+    lambda:         f32,
+}
+
+impl RateControl {
+    fn init(&mut self, quality: u8, bitrate: u32, srate: u32, blk_size: u32) {
+        self.quality  = quality;
+        self.bitrate  = bitrate;
+        self.bitpool  = bitrate;
+        self.srate    = srate;
+        self.spos     = 0;
+        self.blk_size = blk_size;
+        self.lambda   = if self.quality != 0 {
+                ((100 - self.quality) as f32) / 20.0
+            } else {
+                1.0
+            };
+    }
+    fn get_tgt_size(&self) -> usize {
+        if self.bitrate > 0 {
+            (self.bitpool * self.blk_size / (self.srate - self.spos)) as usize
+        } else {
+            0
+        }
+    }
+    fn update(&mut self, real_size: usize) {
+        if self.bitrate == 0 {
+            return;
+        }
+
+        let tgt_size = self.get_tgt_size();
+
+        if real_size < tgt_size - tgt_size / 8 {
+            self.lambda -= 0.2;
+            if self.lambda < 0.0 {
+                self.lambda = 0.0;
+            }
+        }
+        if real_size > tgt_size + tgt_size / 8 {
+            self.lambda += 0.5;
+        }
+
+        self.spos += self.blk_size;
+        while self.spos >= self.srate {
+            self.spos -= self.srate;
+            self.bitpool += self.bitrate;
+        }
+        self.bitpool = self.bitpool.saturating_sub(real_size as u32);
+    }
+}
+
+#[derive(Default)]
+struct SampleBuffer {
+    samples:        Vec<f32>,
+    read_pos:       usize,
+    frame_len:      usize,
+    step_size:      usize,
+}
+
+impl SampleBuffer {
+    fn reset(&mut self) {
+        self.samples.clear();
+        self.read_pos = 0;
+    }
+    fn num_avail(&self) -> usize { self.samples.len() - self.read_pos }
+    fn add_mono(&mut self, src: &[f32]) {
+        self.samples.extend_from_slice(src);
+    }
+    fn add_stereo(&mut self, left: &[f32], right: &[f32]) {
+        for (&l, &r) in left.iter().zip(right.iter()) {
+            self.samples.push(l);
+            self.samples.push(r);
+        }
+    }
+    fn norm(&mut self) {
+        if self.read_pos == self.samples.len() {
+            self.read_pos = 0;
+            self.samples.clear();
+        } else if self.read_pos * 2 >= self.samples.len() {
+            let len = self.num_avail();
+            let (head, tail) = self.samples.split_at_mut(self.read_pos);
+            head[..tail.len()].copy_from_slice(tail);
+            self.read_pos = 0;
+            self.samples.truncate(len);
+        }
+    }
+    fn read_frame(&self, dst: &mut [f32]) {
+        let src = &self.samples[self.read_pos..];
+        let len = dst.len().min(src.len()).min(self.frame_len);
+        dst[..len].copy_from_slice(&src[..len]);
+        let ovl_len = self.frame_len - self.step_size;
+        for (i, sample) in dst[..ovl_len].iter_mut().enumerate() {
+            *sample *= (i as f32) / (ovl_len as f32);
+        }
+        for (i, sample) in dst[self.step_size..][..ovl_len].iter_mut().rev().enumerate() {
+            *sample *= (i as f32) / (ovl_len as f32);
+        }
+    }
+    fn consume_frame(&mut self) {
+        self.read_pos += self.step_size;
+        if self.read_pos > self.samples.len() {
+            self.read_pos = self.samples.len();
+        }
+        self.norm();
+    }
+}
+
+fn find_quant(src: &[f32], quants: &[f32; 96], lambda: f32) -> usize {
+    let maxval = src.iter().fold(0.0f32, |acc, &a| acc.max(a.abs()));
+
+    let mut best = 0;
+    let mut best_dist = maxval * maxval * (src.len() as f32);
+    for (i, &q) in quants.iter().enumerate() {
+        if maxval / q > 32767.0 { continue; }
+        if q > maxval * 2.0 { break; }
+        let mut dist = 0.0;
+        let mut maxqv = 0.0f32;
+        let mut signs = 0u32;
+        for &el in src.iter() {
+            let qval = (el.abs() / q).round();
+            let iqval = qval * q;
+            dist += (el.abs() - iqval) * (el.abs() - iqval);
+            maxqv = maxqv.max(qval);
+            if qval > 0.0 {
+                signs += 1;
+            }
+        }
+        let bits = if maxqv > 0.0 {
+                maxqv.log2().ceil() * (src.len() as f32) + (signs as f32)
+            } else { 0.0 };
+        let metric = (dist + 1.0).log2() * 10.0 + bits * lambda;
+        if metric < best_dist {
+            best_dist = metric;
+            best = i;
+        }
+    }
+    best
+}
+
+enum Transform {
+    None,
+    DCT(DCT),
+    RDFT(RDFT),
+}
+
+struct BinkAudioEncoder {
+    stream:         Option<NAStreamRef>,
+    version:        char,
+    stereo:         bool,
+
+    frm_size:       usize,
+    frame_bits:     u8,
+    overlap_size:   usize,
+
+    apts:           u64,
+    use_dct:        bool,
+    preload:        usize,
+
+    samples:        SampleBuffer,
+    transform:      Transform,
+    scale:          f32,
+    quants:         [f32; 96],
+    num_bands:      usize,
+    bands:          [usize; MAX_BANDS + 1],
+    first_frame:    bool,
+    sample_step:    usize,
+    leftover:       usize,
+
+    tmp:            Vec<f32>,
+
+    packets:        VecDeque<NAPacket>,
+    rc:             RateControl,
+
+    print_stats:    bool,
+    blk_bits:       usize,
+    nblks:          usize,
+    nsamples:       usize,
+    nframes:        usize,
+    qstats:         [usize; 96],
+    bstats:         [usize; 16],
+}
+
+impl BinkAudioEncoder {
+    fn new(use_dct: bool) -> Self {
+        Self {
+            stream:         None,
+            version:        'b',
+            stereo:         false,
+
+            frm_size:       0,
+            frame_bits:     0,
+            overlap_size:   0,
+
+            apts:           0,
+            use_dct,
+            preload:        16,
+
+            samples:        SampleBuffer::default(),
+            transform:      Transform::None,
+            scale:          0.0,
+            quants:         get_quants_table(),
+            num_bands:      0,
+            bands:          [0; MAX_BANDS + 1],
+            first_frame:    true,
+            sample_step:    0,
+            leftover:       0,
+
+            tmp:            Vec::new(),
+
+            packets:        VecDeque::new(),
+            rc:             RateControl::default(),
+
+            print_stats:    false,
+            blk_bits:       0,
+            nblks:          0,
+            nsamples:       0,
+            nframes:        0,
+            qstats:         [0; 96],
+            bstats:         [0; 16],
+        }
+    }
+    #[allow(clippy::transmute_ptr_to_ptr)]
+    fn encode_block(&mut self, bw: &mut BitWriter) {
+        match self.transform {
+            Transform::None => unreachable!(),
+            Transform::DCT(ref mut _dct) => unimplemented!(),
+            Transform::RDFT(ref mut rdft) => {
+                unsafe {
+                    let buf = std::mem::transmute::<&mut [f32], &mut [FFTComplex]>(self.tmp.as_mut_slice());
+                    rdft.do_rdft_inplace(buf);
+                }
+            },
+        };
+        for el in self.tmp.iter_mut() {
+            *el *= self.samples.frame_len as f32;
+        }
+        if self.version == 'b' {
+            bw.write(self.tmp[0].to_bits(), 32);
+            bw.write(self.tmp[1].to_bits(), 32);
+        } else {
+            bw.write_float(self.tmp[0]);
+            bw.write_float(self.tmp[1]);
+        }
+
+        let mut quants = [0; MAX_BANDS];
+        for (range, quant) in self.bands.windows(2).take(self.num_bands).zip(quants.iter_mut()) {
+            let region = &mut self.tmp[range[0]..range[1]];
+            *quant = find_quant(region, &self.quants, self.rc.lambda);
+            for el in region.iter_mut() {
+                *el /= self.quants[*quant];
+            }
+        }
+
+        let mut rec_bits = [0u8; 4096];
+        for (bval, &coef) in rec_bits.iter_mut().zip(self.tmp.iter()).skip(2) {
+            let aval = coef.abs().round() as u32;
+            *bval = if aval > 0 { (32 - aval.leading_zeros()) as u8 } else { 0 };
+        }
+
+        for &quant in quants[..self.num_bands].iter() {
+            self.qstats[quant] += 1;
+        }
+
+        for &quant in quants[..self.num_bands].iter() {
+            bw.write(quant as u32, 8);
+        }
+        if self.version == 'b' {
+            for (coef_reg, bits_reg) in self.tmp[2..].chunks(16).zip(rec_bits[2..].chunks(16)) {
+                let max_bits = bits_reg.iter().fold(0u8, |acc, &a| acc.max(a)).min(15);
+                bw.write(u32::from(max_bits), 4);
+                self.bstats[usize::from(max_bits)] += coef_reg.len();
+                if max_bits != 0 {
+                    for &coef in coef_reg.iter() {
+                        let bval = (coef.abs().round() as u32).min((1 << max_bits) - 1);
+                        bw.write(bval, max_bits);
+                        if bval != 0 {
+                            bw.write_bit(coef < 0.0);
+                        }
+                    }
+                }
+            }
+        } else {
+            unimplemented!();
+        }
+    }
+    fn encode_packet(&mut self, last: bool) -> EncoderResult<bool> {
+        let nsamples = if last {
+                self.samples.num_avail()
+            } else if self.first_frame {
+                self.preload.max(1) * self.frm_size * if self.stereo { 2 } else { 1 }
+            } else {
+                (self.leftover + self.sample_step) * if self.stereo { 2 } else { 1 }
+            };
+
+        let nblocks = if !last {
+                let nblk = (nsamples / self.frm_size).max(1);
+                if self.samples.num_avail() < nblk * self.frm_size + self.overlap_size {
+                    return Ok(false);
+                }
+                self.leftover = (self.leftover + self.sample_step) % self.frm_size;
+                nblk
+            } else {
+                (nsamples + self.frm_size - 1) / self.frm_size
+            };
+        let nsamples = if !last { nblocks * self.frm_size } else { nsamples };
+
+        self.nsamples += nsamples >> (self.stereo as u8);
+        self.nframes += 1;
+
+        let mut bw = BitWriter::new(Vec::new(), BitWriterMode::LE);
+        bw.write((nsamples * 2) as u32, 32); // number of raw audio bytes
+
+        for _nblk in 0..nblocks {
+            let start = bw.tell();
+
+            if last {
+                for el in self.tmp.iter_mut() {
+                    *el = 0.0;
+                }
+            }
+            self.samples.read_frame(&mut self.tmp);
+            self.encode_block(&mut bw);
+            while (bw.tell() & 0x1F) != 0 {
+                bw.write0();
+            }
+            self.samples.consume_frame();
+
+            let blk_bits = bw.tell() - start;
+            self.rc.update(blk_bits);
+            self.blk_bits += blk_bits;
+            self.nblks += 1;
+        }
+
+        if self.first_frame {
+            self.first_frame = false;
+        }
+
+        let dbuf = bw.end();
+        let stream = self.stream.clone().unwrap();
+        let (tb_num, tb_den) = stream.get_timebase();
+        let ts = NATimeInfo::new(Some(self.apts), None, Some(nsamples as u64), tb_num, tb_den);
+        self.apts += (nsamples / if self.stereo { 2 } else { 1 }) as u64;
+        self.packets.push_back(NAPacket::new(self.stream.clone().unwrap(), ts, true, dbuf));
+
+        Ok(true)
+    }
+}
+
+impl NAEncoder for BinkAudioEncoder {
+    fn negotiate_format(&self, encinfo: &EncodeParameters) -> EncoderResult<EncodeParameters> {
+        match encinfo.format {
+            NACodecTypeInfo::None => {
+                Ok(EncodeParameters {
+                    format: NACodecTypeInfo::Audio(NAAudioInfo::new(0, 1, SND_F32P_FORMAT, 512)),
+                    ..Default::default() })
+            },
+            NACodecTypeInfo::Video(_) => Err(EncoderError::FormatError),
+            NACodecTypeInfo::Audio(ainfo) => {
+                let mut outinfo = ainfo;
+                outinfo.channels = if !self.use_dct { 2 } else { ainfo.channels.min(2).max(1) };
+                outinfo.format = SND_F32P_FORMAT;
+                let mut ofmt = *encinfo;
+                ofmt.format = NACodecTypeInfo::Audio(outinfo);
+                Ok(ofmt)
+            }
+        }
+    }
+    fn get_capabilities(&self) -> u64 { ENC_CAPS_CBR }
+    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_F32P_FORMAT {
+                    return Err(EncoderError::FormatError);
+                }
+
+                let srate = ainfo.get_sample_rate();
+                let mut frame_bits = if srate < 22050 { 9 } else if srate < 44100 { 10 } else { 11 };
+
+                if self.version < 'i' && self.use_dct {
+                    println!("DCT is supported starting from version 'i'");
+                    return Err(EncoderError::FormatError);
+                }
+
+                if !self.use_dct && self.version != 'b' {
+                    frame_bits += 1;
+                }
+                self.frame_bits = frame_bits;
+                self.stereo = ainfo.channels == 2;
+                let mut duration = (1 << frame_bits) - (1 << (frame_bits - 4));
+                let single = !self.use_dct && self.stereo; // RDFT codes samples interleaved as single buffer
+                if single {
+                    duration >>= 1;
+                }
+
+                self.transform = if !self.use_dct {
+                        Transform::RDFT(RDFTBuilder::new_rdft(1 << (frame_bits - 1), true, false))
+                    } else {
+                        Transform::DCT(DCT::new(DCTMode::DCT_II, 1 << frame_bits))
+                    };
+                self.scale = if !self.use_dct {
+                        1.0 / (32768.0 * ((1 << frame_bits) as f32).sqrt())
+                    } else {
+                        (2.0 / ((1 << frame_bits) as f32)).sqrt() / 1024.0
+                    };
+                let s_srate = if single { srate } else { srate >> 1 } as usize;
+                init_bands(s_srate, 1 << frame_bits, &mut self.num_bands, &mut self.bands);
+                self.first_frame = true;
+
+                self.samples.reset();
+                self.samples.frame_len = 1 << frame_bits;
+                self.samples.step_size = duration * usize::from(ainfo.channels);
+                self.frm_size = duration * usize::from(ainfo.channels);
+                self.overlap_size = self.frm_size / 15;
+                self.tmp = vec![0.0; 1 << frame_bits];
+
+                let blk_len = ((1 << frame_bits) - (1 << (frame_bits - 4))) >> if self.stereo { 1 } else { 0 };
+                self.rc.init(encinfo.quality, encinfo.bitrate, srate, blk_len as u32);
+
+                let edata = vec![b'B', b'I', b'K', self.version as u8];
+
+                let out_ainfo = NAAudioInfo::new(ainfo.sample_rate, ainfo.channels, SND_F32P_FORMAT, self.frm_size);
+                let name = if !self.use_dct { "bink-audio-rdft" } else { "bink-audio-dct" };
+                let info = NACodecInfo::new(name, NACodecTypeInfo::Audio(out_ainfo), Some(edata));
+                let mut stream = NAStream::new(StreamType::Audio, stream_id, info, self.frm_size as u32, ainfo.sample_rate, 0);
+                stream.set_num(stream_id as usize);
+                let stream = stream.into_ref();
+
+                self.stream = Some(stream.clone());
+                self.sample_step = (encinfo.tb_num as usize) * (ainfo.sample_rate as usize) / (encinfo.tb_den as usize);
+
+                Ok(stream)
+            },
+        }
+    }
+    fn encode(&mut self, frm: &NAFrame) -> EncoderResult<()> {
+        if let Some(ref abuf) = frm.get_buffer().get_abuf_f32() {
+            let src = abuf.get_data();
+            let length = abuf.get_length();
+
+            if abuf.get_info().get_channels() == 2 {
+                let offset = abuf.get_offset(1);
+                self.samples.add_stereo(&src[..length], &src[offset..][..length]);
+            } else {
+                self.samples.add_mono(&src[..length]);
+            }
+
+            while self.encode_packet(false)? {
+            }
+
+            Ok(())
+        } else {
+            Err(EncoderError::InvalidParameters)
+        }
+    }
+    fn get_packet(&mut self) -> EncoderResult<Option<NAPacket>> {
+        Ok(self.packets.pop_front())
+    }
+    fn flush(&mut self) -> EncoderResult<()> {
+        if self.samples.num_avail() > 0 {
+            self.encode_packet(true)?;
+        }
+        Ok(())
+    }
+}
+
+impl Drop for BinkAudioEncoder {
+    fn drop(&mut self) {
+        if self.print_stats && self.nblks > 0 {
+            println!("encoded {} block(s) in {} frame(s), {} samples total ({}s)",
+                    self.nblks, self.nframes, self.nsamples,
+                    (self.nsamples as f32) / (self.rc.srate as f32));
+
+            let bitrate = (self.blk_bits as u64) * u64::from(self.rc.srate) / (self.nsamples as u64);
+            let br_fmt = if bitrate >= 10_000_000 {
+                    format!("{}mbps", bitrate / 1000000)
+                } else if bitrate >= 10_000 {
+                    format!("{}kbps", bitrate / 1000)
+                } else {
+                    format!("{}bps", bitrate)
+                };
+            println!("average bitrate {}", br_fmt);
+
+            let mut end = self.qstats.len();
+            for (i, &cnt) in self.qstats.iter().enumerate().rev() {
+                if cnt != 0 {
+                    break;
+                }
+                end = i;
+            }
+            print!("quants used:");
+            for &cnt in self.qstats.iter().take(end) {
+                print!(" {}", cnt);
+            }
+            println!();
+
+            let mut end = self.bstats.len();
+            for (i, &cnt) in self.bstats.iter().enumerate().rev() {
+                if cnt != 0 {
+                    break;
+                }
+                end = i;
+            }
+            print!("coefficient bits:");
+            for &cnt in self.bstats.iter().take(end) {
+                print!(" {}", cnt);
+            }
+            println!();
+        }
+    }
+}
+
+const ENCODER_OPTS: &[NAOptionDefinition] = &[
+    NAOptionDefinition {
+        name: "version", description: "codec version",
+        opt_type: NAOptionDefinitionType::String(Some(&["b", "f", "g", "h", "i", "k"])) },
+    NAOptionDefinition {
+        name: "preload", description: "number of audio frames to preload",
+        opt_type: NAOptionDefinitionType::Int(Some(1), Some(256)) },
+    NAOptionDefinition {
+        name: "print_stats", description: "print encoder statistics",
+        opt_type: NAOptionDefinitionType::Bool },
+];
+
+impl NAOptionHandler for BinkAudioEncoder {
+    fn get_supported_options(&self) -> &[NAOptionDefinition] { ENCODER_OPTS }
+    fn set_options(&mut self, options: &[NAOption]) {
+        for option in options.iter() {
+            for opt_def in ENCODER_OPTS.iter() {
+                if opt_def.check(option).is_ok() {
+                    match option.name {
+                        "version" => {
+                            if let NAValue::String(ref strval) = option.value {
+                                match strval.as_str() {
+                                    "b" => self.version = 'b',
+                                    _ => {
+                                        println!("versions beside 'b' are not supported");
+                                    },
+                                };
+                            }
+                        },
+                        "preload" => {
+                            if let NAValue::Int(ival) = option.value {
+                                self.preload = ival as usize;
+                            }
+                        },
+                        "print_stats" => {
+                            if let NAValue::Bool(bval) = option.value {
+                                self.print_stats = bval;
+                            }
+                        },
+                        _ => {},
+                    };
+                }
+            }
+        }
+    }
+    fn query_option_value(&self, name: &str) -> Option<NAValue> {
+        match name {
+            "version" => Some(NAValue::String(self.version.to_string())),
+            "preload" => Some(NAValue::Int(self.preload as i64)),
+            "print_stats" => Some(NAValue::Bool(self.print_stats)),
+            _ => None,
+        }
+    }
+}
+
+pub fn get_encoder_rdft() -> Box<dyn NAEncoder + Send> { Box::new(BinkAudioEncoder::new(false)) }
+#[allow(dead_code)]
+pub fn get_encoder_dct() -> Box<dyn NAEncoder + Send> { Box::new(BinkAudioEncoder::new(true)) }
+
+#[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::*;
+    use nihav_commonfmt::*;
+
+    fn test_encoder(name: &'static str, enc_options: &[NAOption],
+                    bitrate: u32, quality: u8, channels: u8, hash: &[u32; 4]) {
+        let mut dmx_reg = RegisteredDemuxers::new();
+        generic_register_all_demuxers(&mut dmx_reg);
+        let mut dec_reg = RegisteredDecoders::new();
+        generic_register_all_decoders(&mut dec_reg);
+        let mut mux_reg = RegisteredMuxers::new();
+        rad_register_all_muxers(&mut mux_reg);
+        let mut enc_reg = RegisteredEncoders::new();
+        rad_register_all_encoders(&mut enc_reg);
+
+        // sample from a private collection
+        let dec_config = DecoderTestParams {
+                demuxer:        "wav",
+                in_name:        "assets/test-stereo.wav",
+                stream_type:    StreamType::Audio,
+                limit:          None,//Some(20),
+                dmx_reg, dec_reg,
+            };
+        let enc_config = EncoderTestParams {
+                muxer:          "bink",
+                enc_name:       "bink-audio-rdft",
+                out_name:       name,
+                mux_reg, enc_reg,
+            };
+        let dst_ainfo = NAAudioInfo {
+                sample_rate:    0,
+                channels,
+                format:         SND_F32P_FORMAT,
+                block_len:      1024,
+            };
+        let enc_params = EncodeParameters {
+                format:  NACodecTypeInfo::Audio(dst_ainfo),
+                quality,
+                bitrate: bitrate * 1000,
+                tb_num:  0,
+                tb_den:  0,
+                flags:   0,
+            };
+        //test_encoding_to_file(&dec_config, &enc_config, enc_params, enc_options);
+
+        test_encoding_md5(&dec_config, &enc_config, enc_params, enc_options, hash);
+    }
+    #[test]
+    fn test_binkaud_encoder_b_q100() {
+        let enc_options = &[
+            NAOption{name: "version", value: NAValue::String("b".to_owned())},
+            //NAOption{name: "print_stats", value: NAValue::Bool(true)},
+        ];
+        test_encoder("bink-aud-b-q80.bik", enc_options, 0, 100, 2,
+            &[0x45c13e44, 0x36ab1efb, 0x84c93f1a, 0x4aa49831]);
+    }
+    #[test]
+    fn test_binkaud_encoder_b_q80() {
+        let enc_options = &[
+            NAOption{name: "version", value: NAValue::String("b".to_owned())},
+            //NAOption{name: "print_stats", value: NAValue::Bool(true)},
+        ];
+        test_encoder("bink-aud-b-q80.bik", enc_options, 0, 80, 2,
+            &[0x3835b6ff, 0xd2da247b, 0xae8ff168, 0x464b4c31]);
+    }
+    #[test]
+    fn test_binkaud_encoder_b_q40() {
+        let enc_options = &[
+            NAOption{name: "version", value: NAValue::String("b".to_owned())},
+            //NAOption{name: "print_stats", value: NAValue::Bool(true)},
+        ];
+        test_encoder("bink-aud-b-q80.bik", enc_options, 0, 40, 2,
+            &[0xe99882b0, 0x12f9be7c, 0xc634c1a7, 0xd88e1c9b]);
+    }
+    #[test]
+    fn test_binkaud_encoder_b_q1() {
+        let enc_options = &[
+            NAOption{name: "version", value: NAValue::String("b".to_owned())},
+            //NAOption{name: "print_stats", value: NAValue::Bool(true)},
+        ];
+        test_encoder("bink-aud-b-q80.bik", enc_options, 0, 1, 2,
+            &[0x783d68e1, 0x9db89348, 0x5348e677, 0x337133fa]);
+    }
+    #[test]
+    fn test_binkaud_encoder_b_br240() {
+        let enc_options = &[
+            NAOption{name: "version", value: NAValue::String("b".to_owned())},
+            //NAOption{name: "print_stats", value: NAValue::Bool(true)},
+        ];
+        test_encoder("bink-aud-b-180k.bik", enc_options, 240, 0, 2,
+            &[0xad33939e, 0x945413f1, 0xf5edc6be, 0xcf8eebd3]);
+    }
+    #[test]
+    fn test_binkaud_encoder_b_br180() {
+        let enc_options = &[
+            NAOption{name: "version", value: NAValue::String("b".to_owned())},
+            //NAOption{name: "print_stats", value: NAValue::Bool(true)},
+        ];
+        test_encoder("bink-aud-b-180k.bik", enc_options, 180, 0, 2,
+            &[0x4d33f1be, 0xb1e662ad, 0x71bd0486, 0x327e053a]);
+    }
+    #[test]
+    fn test_binkaud_encoder_b_br120() {
+        let enc_options = &[
+            NAOption{name: "version", value: NAValue::String("b".to_owned())},
+            //NAOption{name: "print_stats", value: NAValue::Bool(true)},
+        ];
+        test_encoder("bink-aud-b-120k.bik", enc_options, 120, 0, 2,
+            &[0xa1e17945, 0xd837677a, 0x48cd0b3a, 0x3e7c1a03]);
+    }
+    #[test]
+    fn test_binkaud_encoder_b_br60() {
+        let enc_options = &[
+            NAOption{name: "version", value: NAValue::String("b".to_owned())},
+            //NAOption{name: "print_stats", value: NAValue::Bool(true)},
+        ];
+        test_encoder("bink-aud-b-60k.bik", enc_options, 60, 0, 2,
+            &[0xf48cae2e, 0x038ec363, 0x17cb1606, 0x4756f854]);
+    }
+}
index e8fbe86bd41388669ba8c7f9eff83e98d33ba214..92c547a345ef8966a7ac2f527c09ce908d0f6dc6 100644 (file)
@@ -13,7 +13,7 @@ macro_rules! validate {
 mod smacker;
 #[cfg(feature="decoder_binkaud")]
 mod binkaud;
-#[cfg(feature="decoder_binkaud")]
+#[cfg(any(feature="decoder_binkaud", feature="encoder_binkaud"))]
 mod binkauddata;
 #[cfg(feature="decoder_binkvid")]
 mod binkvid;
@@ -46,11 +46,17 @@ pub fn rad_register_all_decoders(rd: &mut RegisteredDecoders) {
 
 #[cfg(feature="encoder_binkvid")]
 mod binkvidenc;
+#[cfg(feature="encoder_binkaud")]
+mod binkaudenc;
 
 #[cfg(feature="encoders")]
 const ENCODERS: &[EncoderInfo] = &[
 #[cfg(feature="encoder_binkvid")]
     EncoderInfo { name: "bink-video", get_encoder: binkvidenc::get_encoder },
+#[cfg(feature="encoder_binkaud")]
+    EncoderInfo { name: "bink-audio-rdft", get_encoder: binkaudenc::get_encoder_rdft },
+//#[cfg(feature="encoder_binkvid")]
+//    EncoderInfo { name: "bink-audio-dct", get_encoder: binkaudenc::get_encoder_dct },
 ];
 
 /// Registers all available encoders provided by this crate.