Commit 79b01bff authored by Luca Barbato's avatar Luca Barbato Committed by Thomas Daede

Encode decode tests

* Make more structures Debug

Makes easier to println!-debug.

* Fix the cmake flags passed to build the decoding library

* Make sure to not call partially implemented ith

At least the sse4.1 variant is incomplete and triggers an assert.

* Do not use SIMD for TxType::IDTX ith

The implementation of it is incomplete.

* Provide an encode-decode test

Use `cargo test --features=decode_test -- --ignored` to run it.

* Enable decode_test in travis
parent 4f109775
......@@ -3,3 +3,4 @@ target
Cargo.lock
*.y4m
*.ivf
src/lib/aom.rs
......@@ -16,5 +16,6 @@ script:
- |
cargo build --verbose &&
cargo test --verbose &&
cargo test --verbose --release --features=decode_test -- --ignored &&
cargo bench --verbose &&
cargo doc --verbose
......@@ -7,6 +7,7 @@ include = ["/src/**", "/aom_build/**", "/Cargo.toml"]
[features]
repl = ["rustyline"]
decode_test = ["bindgen"]
[dependencies]
bitstream-io = "0.6"
......@@ -21,8 +22,8 @@ backtrace = "0.3"
[build-dependencies]
cc = "1"
cmake = "0.1.29"
bindgen = "0.33"
pkg-config = "0.3.9"
bindgen = { version = "0.33", optional = true }
[dev-dependencies]
bencher = "0.1.5"
......
// build.rs
extern crate pkg_config;
extern crate bindgen;
extern crate cmake;
extern crate cc;
extern crate pkg_config;
#[cfg(feature = "decode_test")]
extern crate bindgen;
use std::env;
use std::path::Path;
use std::fs::{self, File};
use std::io::Write;
fn format_write(builder: bindgen::Builder, output: &str) {
let s = builder
.generate()
.unwrap()
.to_string()
.replace("/**", "/*")
.replace("/*!", "/*");
let mut file = File::create(output).unwrap();
let _ = file.write(s.as_bytes());
}
use std::fs;
fn main() {
let cargo_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
......@@ -38,43 +25,58 @@ fn main() {
let dst = cmake::Config::new(build_path)
.define("CONFIG_DEBUG", debug)
.define("CONFIG_EXPERIMENTAL", "1")
.define("CONFIG_UNIT_TESTS", "0")
.define("CONFIG_EXT_PARTITION_TYPES", "0")
.define("CONFIG_INTRA_EDGE2", "0")
.define("CONFIG_OBU", "1")
.define("CONFIG_FILTER_INTRA", "1")
.define("CONFIG_LV_MAP", "1")
.define("CONFIG_ANALYZER", "0")
.define("CONFIG_MONO_VIDEO", "1")
.define("CONFIG_Q_ADAPT_PROBS", "1")
.define("CONFIG_INTRA_EDGE", "0")
.define("CONFIG_SCALABILITY", "1")
.define("CONFIG_OBU_SIZING", "1")
.define("CONFIG_TIMING_INFO_IN_SEQ_HEADERS", "0")
.define("CONFIG_FILM_GRAIN", "0")
.define("CONFIG_LV_MAP", "1")
.define("CONFIG_ANALYZER", "0")
.define("ENABLE_DOCS", "0")
.define("CONFIG_UNIT_TESTS", "0")
.build();
// Dirty hack to force a rebuild whenever the defaults are changed upstream
let _ = fs::remove_file(dst.join("build/CMakeCache.txt"));
env::set_var("PKG_CONFIG_PATH", dst.join("lib/pkgconfig"));
let _libs = pkg_config::Config::new().statik(true).probe("aom").unwrap();
#[cfg(feature = "decode_test")] {
use std::io::Write;
let libs = pkg_config::Config::new().statik(true).probe("aom").unwrap();
let headers = libs.include_paths.clone();
let headers = _libs.include_paths.clone();
let mut builder = bindgen::builder()
.raw_line("#![allow(dead_code)]")
.raw_line("#![allow(non_camel_case_types)]")
.raw_line("#![allow(non_snake_case)]")
.raw_line("#![allow(non_upper_case_globals)]")
.blacklist_type("max_align_t")
.rustfmt_bindings(false)
.header("data/aom.h");
let mut builder = bindgen::builder()
.raw_line("#![allow(dead_code)]")
.raw_line("#![allow(non_camel_case_types)]")
.raw_line("#![allow(non_snake_case)]")
.raw_line("#![allow(non_upper_case_globals)]")
.blacklist_type("max_align_t")
.rustfmt_bindings(false)
.header("data/aom.h");
for header in headers {
builder = builder.clang_arg("-I").clang_arg(header.to_str().unwrap());
}
for header in headers {
builder = builder.clang_arg("-I").clang_arg(header.to_str().unwrap());
}
// Manually fix the comment so rustdoc won't try to pick them
let s = builder
.generate()
.unwrap()
.to_string()
.replace("/**", "/*")
.replace("/*!", "/*");
// Manually fix the comment so rustdoc won't try to pick them
format_write(builder, "tests/aom.rs");
let mut file = fs::File::create("src/aom.rs").unwrap();
let _ = file.write(s.as_bytes());
}
{
use std::fs;
......
......@@ -53,6 +53,7 @@ extern {
pub fn aom_dsp_rtcd();
}
#[derive(Debug)]
pub struct Frame {
pub planes: [Plane; 3]
}
......@@ -204,6 +205,7 @@ impl Sequence {
}
}
#[derive(Debug)]
pub struct FrameState {
pub input: Frame,
pub rec: Frame,
......@@ -248,6 +250,7 @@ impl Fixed for usize {
// Frame Invariants are invariant inside a frame
#[allow(dead_code)]
#[derive(Debug)]
pub struct FrameInvariants {
pub width: usize,
pub height: usize,
......@@ -345,6 +348,14 @@ impl FrameInvariants {
config,
}
}
pub fn new_frame_state(&self) -> FrameState {
FrameState {
input: Frame::new(self.padded_w, self.padded_h),
rec: Frame::new(self.padded_w, self.padded_h),
qc: Default::default(),
}
}
}
impl fmt::Display for FrameInvariants{
......@@ -427,7 +438,7 @@ impl Default for EncoderConfig {
fn default() -> Self {
EncoderConfig {
limit: 0,
quantizer: 0,
quantizer: 100,
speed: 0,
tune: Tune::Psnr,
}
......@@ -601,8 +612,8 @@ impl<'a> UncompressedHeader for BitWriter<'a, BE> {
for i in 0..seq.operating_points_cnt_minus_1 + 1 {
self.write(OP_POINTS_IDC_BITS as u32, seq.operating_point_idc[i])?;
//let seq_level_idx = 1 as u16; // NOTE: This comes from minor and major
let seq_level_idx =
((seq.level[i][1] - LEVEL_MAJOR_MIN) << LEVEL_MINOR_BITS) + seq.level[i][0];
let seq_level_idx =
((seq.level[i][1] - LEVEL_MAJOR_MIN) << LEVEL_MINOR_BITS) + seq.level[i][0];
self.write(LEVEL_BITS as u32, seq_level_idx as u16)?;
if seq.level[i][1] > 3 {
......@@ -1400,7 +1411,6 @@ pub fn encode_tx_block(fi: &FrameInvariants, fs: &mut FrameState, cw: &mut Conte
tx_size.width(),
tx_size.height());
forward_transform(&residual.array, coeffs, tx_size.width(), tx_size, tx_type);
fs.qc.quantize(coeffs);
......@@ -2212,3 +2222,182 @@ pub fn process_frame(sequence: &mut Sequence, fi: &mut FrameInvariants,
_ => false
}
}
// #[cfg(test)]
#[cfg(feature="decode_test")]
mod aom;
#[cfg(all(test, feature="decode_test"))]
mod test_encode_decode {
use super::*;
use rand::{ChaChaRng, Rng, SeedableRng};
use aom::*;
use std::mem;
fn fill_frame(ra: &mut ChaChaRng, frame: &mut Frame) {
for plane in frame.planes.iter_mut() {
let stride = plane.cfg.stride;
for row in plane.data.chunks_mut(stride) {
for mut pixel in row {
let v: u8 = ra.gen();
*pixel = v as u16;
}
}
}
}
struct AomDecoder {
dec: aom_codec_ctx,
}
fn setup_decoder(w: usize, h: usize) -> AomDecoder {
unsafe {
let interface = aom::aom_codec_av1_dx();
let mut dec: AomDecoder = mem::uninitialized();
let cfg = aom_codec_dec_cfg_t {
threads: 1,
w: w as u32,
h: h as u32,
allow_lowbitdepth: 1,
cfg: cfg_options { ext_partition: 1 }
};
let ret = aom_codec_dec_init_ver(&mut dec.dec, interface, &cfg, 0, AOM_DECODER_ABI_VERSION as i32);
if ret != 0 {
panic!("Cannot instantiate the decoder {}", ret);
}
dec
}
}
impl Drop for AomDecoder {
fn drop(&mut self) {
unsafe { aom_codec_destroy(&mut self.dec) };
}
}
fn setup_encoder(w: usize, h: usize, speed: usize, quantizer: usize) -> (FrameInvariants, Sequence) {
unsafe {
av1_rtcd();
aom_dsp_rtcd();
}
let config = EncoderConfig {
quantizer: quantizer,
speed: speed,
..Default::default()
};
let mut fi = FrameInvariants::new(w, h, config);
fi.use_reduced_tx_set = true;
// fi.min_partition_size =
let seq = Sequence::new(w, h);
(fi, seq)
}
// TODO: support non-multiple-of-16 dimensions
static DIMENSION_OFFSETS: &[(usize, usize)] = &[(0, 0), (16, 16)];
#[test]
#[ignore]
fn speed() {
let quantizer = 100;
let limit = 5;
let w = 64;
let h = 80;
for b in DIMENSION_OFFSETS.iter() {
for s in 0 .. 10 {
encode_decode(w + b.0, h + b.1, s, quantizer, limit);
}
}
}
#[test]
#[ignore]
fn quantizer() {
let limit = 5;
let w = 64;
let h = 80;
let speed = 4;
for b in DIMENSION_OFFSETS.iter() {
for &q in [80, 100, 120].iter() {
encode_decode(w + b.0, h + b.1, speed, q, limit);
}
}
}
fn encode_decode(w:usize, h:usize, speed: usize, quantizer: usize, limit: usize) {
use std::ptr;
let mut ra = ChaChaRng::from_seed([0; 32]);
let mut dec = setup_decoder(w, h);
let (mut fi, mut seq) = setup_encoder(w, h, speed, quantizer);
println!("Encoding {}x{} speed {} quantizer {}", w, h, speed, quantizer);
let mut last_rec: Option<Frame> = None;
let mut iter: aom_codec_iter_t = ptr::null_mut();
for _ in 0 .. limit {
let mut fs = fi.new_frame_state();
fill_frame(&mut ra, &mut fs.input);
fi.frame_type = if fi.number % 30 == 0 { FrameType::KEY } else { FrameType::INTER };
fi.intra_only = fi.frame_type == FrameType::KEY || fi.frame_type == FrameType::INTRA_ONLY;
fi.use_prev_frame_mvs = !(fi.intra_only || fi.error_resilient);
println!("Encoding frame {}", fi.number);
let packet = encode_frame(&mut seq, &mut fi, &mut fs, &last_rec);
println!("Encoded.");
last_rec = Some(fs.rec);
let mut corrupted_count = 0;
unsafe {
println!("Decoding frame {}", fi.number);
let ret = aom_codec_decode(&mut dec.dec, packet.as_ptr(), packet.len() as u32, ptr::null_mut());
println!("Decoded. -> {}", ret);
if ret != 0 {
use std::ffi::CStr;
let error_msg = aom_codec_error(&mut dec.dec);
println!(" Decode codec_decode failed: {}", CStr::from_ptr(error_msg).to_string_lossy());
let detail = aom_codec_error_detail(&mut dec.dec);
if !detail.is_null() {
println!(" Decode codec_decode failed {}", CStr::from_ptr(detail).to_string_lossy());
}
corrupted_count += 1;
}
if ret == 0 {
loop {
println!("Retrieving frame");
let img = aom_codec_get_frame(&mut dec.dec, &mut iter);
println!("Retrieved.");
if img.is_null() {
break;
}
let mut corrupted = 0;
let ret = aom_codec_control_(&mut dec.dec, aom_dec_control_id_AOMD_GET_FRAME_CORRUPTED as i32, &mut corrupted);
if ret != 0 {
use std::ffi::CStr;
let detail = aom_codec_error_detail(&mut dec.dec);
panic!("Decode codec_control failed {}", CStr::from_ptr(detail).to_string_lossy());
}
corrupted_count += corrupted;
}
}
}
assert_eq!(corrupted_count, 0);
fi.number += 1;
}
}
}
......@@ -22,7 +22,7 @@ pub enum PartitionType {
PARTITION_INVALID
}
#[derive(Copy, Clone, PartialEq, PartialOrd)]
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
pub enum BlockSize {
BLOCK_4X4,
BLOCK_4X8,
......
......@@ -10,6 +10,7 @@
#![cfg_attr(feature = "cargo-clippy", allow(cast_lossless))]
/// Plane-specific configuration.
#[derive(Debug)]
pub struct PlaneConfig {
pub stride: usize,
pub xdec: usize,
......@@ -17,11 +18,13 @@ pub struct PlaneConfig {
}
/// Absolute offset in pixels inside a plane
#[derive(Debug)]
pub struct PlaneOffset {
pub x: usize,
pub y: usize
}
#[derive(Debug)]
pub struct Plane {
pub data: Vec<u16>,
pub cfg: PlaneConfig
......
......@@ -60,7 +60,13 @@ extern {
tx_type: libc::c_int,
bd: libc::c_int
);
fn av1_inv_txfm2d_add_4x4_c(
input: *const i32,
output: *mut u16,
stride: libc::c_int,
tx_type: libc::c_int,
bd: libc::c_int
);
static av1_inv_txfm2d_add_8x8: extern fn(
input: *const i32,
output: *mut u16,
......@@ -68,6 +74,13 @@ extern {
tx_type: libc::c_int,
bd: libc::c_int
) -> ();
fn av1_inv_txfm2d_add_8x8_c(
input: *const i32,
output: *mut u16,
stride: libc::c_int,
tx_type: libc::c_int,
bd: libc::c_int
) -> ();
static av1_fht16x16: extern fn(
input: *const i16,
output: *mut i32,
......@@ -98,6 +111,13 @@ extern {
tx_type: libc::c_int,
bd: libc::c_int
);
fn av1_inv_txfm2d_add_32x32_c(
input: *const i32,
output: *mut u16,
stride: libc::c_int,
tx_type: libc::c_int,
bd: libc::c_int
);
}
pub fn forward_transform(
......@@ -140,14 +160,27 @@ fn fht4x4(input: &[i16], output: &mut [i32], stride: usize, tx_type: TxType) {
fn iht4x4_add(
input: &[i32], output: &mut [u16], stride: usize, tx_type: TxType
) {
unsafe {
av1_inv_txfm2d_add_4x4(
input.as_ptr(),
output.as_mut_ptr(),
stride as libc::c_int,
tx_type as libc::c_int,
8
);
// SIMD code may assert for transform types beyond TxType::IDTX.
if tx_type < TxType::IDTX {
unsafe {
av1_inv_txfm2d_add_4x4(
input.as_ptr(),
output.as_mut_ptr(),
stride as libc::c_int,
tx_type as libc::c_int,
8
);
}
} else {
unsafe {
av1_inv_txfm2d_add_4x4_c(
input.as_ptr(),
output.as_mut_ptr(),
stride as libc::c_int,
tx_type as libc::c_int,
8
);
}
}
}
......@@ -165,14 +198,27 @@ fn fht8x8(input: &[i16], output: &mut [i32], stride: usize, tx_type: TxType) {
fn iht8x8_add(
input: &[i32], output: &mut [u16], stride: usize, tx_type: TxType
) {
unsafe {
av1_inv_txfm2d_add_8x8(
input.as_ptr(),
output.as_mut_ptr(),
stride as libc::c_int,
tx_type as libc::c_int,
8
);
// SIMD code may assert for transform types beyond TxType::IDTX.
if tx_type < TxType::IDTX {
unsafe {
av1_inv_txfm2d_add_8x8(
input.as_ptr(),
output.as_mut_ptr(),
stride as libc::c_int,
tx_type as libc::c_int,
8
);
}
} else {
unsafe {
av1_inv_txfm2d_add_8x8_c(
input.as_ptr(),
output.as_mut_ptr(),
stride as libc::c_int,
tx_type as libc::c_int,
8
);
}
}
}
......@@ -231,12 +277,23 @@ fn iht32x32_add(
input: &[i32], output: &mut [u16], stride: usize, tx_type: TxType
) {
unsafe {
av1_inv_txfm2d_add_32x32(
input.as_ptr(),
output.as_mut_ptr(),
stride as libc::c_int,
tx_type as libc::c_int,
8
);
if tx_type < TxType::IDTX {
// SIMDI code may assert for transform types beyond TxType::IDTX.
av1_inv_txfm2d_add_32x32(
input.as_ptr(),
output.as_mut_ptr(),
stride as libc::c_int,
tx_type as libc::c_int,
8
);
} else {
av1_inv_txfm2d_add_32x32_c(
input.as_ptr(),
output.as_mut_ptr(),
stride as libc::c_int,
tx_type as libc::c_int,
8
);
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment