From: Kostya Shishkov Date: Sat, 7 Oct 2023 16:51:41 +0000 (+0200) Subject: Bink Audio encoder (version b for now) X-Git-Url: https://git.nihav.org/?a=commitdiff_plain;h=ed91af4b5a98b4be1df4f7e0345e545af87f3175;p=nihav.git Bink Audio encoder (version b for now) --- diff --git a/nihav-rad/Cargo.toml b/nihav-rad/Cargo.toml index 6574300..1bfa712 100644 --- a/nihav-rad/Cargo.toml +++ b/nihav-rad/Cargo.toml @@ -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 index 0000000..b8a03ab --- /dev/null +++ b/nihav-rad/src/codecs/binkaudenc.rs @@ -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, + 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, + 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, + + packets: VecDeque, + 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 { + 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 { + 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 { + 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> { + 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 { + 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 { Box::new(BinkAudioEncoder::new(false)) } +#[allow(dead_code)] +pub fn get_encoder_dct() -> Box { 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]); + } +} diff --git a/nihav-rad/src/codecs/mod.rs b/nihav-rad/src/codecs/mod.rs index e8fbe86..92c547a 100644 --- a/nihav-rad/src/codecs/mod.rs +++ b/nihav-rad/src/codecs/mod.rs @@ -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.