diff options
author | Kostya Shishkov <kostya.shishkov@gmail.com> | 2020-02-20 11:00:24 +0100 |
---|---|---|
committer | Kostya Shishkov <kostya.shishkov@gmail.com> | 2020-02-20 11:00:24 +0100 |
commit | b4d5b8515e75383b4fc59ea2813c90c615d59a96 (patch) | |
tree | cf9ea1f458965eea90dff60a607dc90bf42887b3 /nihav-codec-support | |
parent | 2b8bf9a03242bbd6e80091082a50ec13b1a95143 (diff) | |
download | nihav-b4d5b8515e75383b4fc59ea2813c90c615d59a96.tar.gz |
split nihav-codec-support crate from nihav-core
The former is intended just for NihAV decoders, the latter is for both
NihAV crates and for the code using NihAV.
Diffstat (limited to 'nihav-codec-support')
-rw-r--r-- | nihav-codec-support/Cargo.toml | 20 | ||||
-rw-r--r-- | nihav-codec-support/src/codecs/blockdsp.rs | 250 | ||||
-rw-r--r-- | nihav-codec-support/src/codecs/h263/code.rs | 500 | ||||
-rw-r--r-- | nihav-codec-support/src/codecs/h263/data.rs | 203 | ||||
-rw-r--r-- | nihav-codec-support/src/codecs/h263/decoder.rs | 581 | ||||
-rw-r--r-- | nihav-codec-support/src/codecs/h263/mod.rs | 392 | ||||
-rw-r--r-- | nihav-codec-support/src/codecs/mod.rs | 313 | ||||
-rw-r--r-- | nihav-codec-support/src/data/mod.rs | 73 | ||||
-rw-r--r-- | nihav-codec-support/src/dsp/dct.rs | 512 | ||||
-rw-r--r-- | nihav-codec-support/src/dsp/fft.rs | 912 | ||||
-rw-r--r-- | nihav-codec-support/src/dsp/mdct.rs | 81 | ||||
-rw-r--r-- | nihav-codec-support/src/dsp/mod.rs | 11 | ||||
-rw-r--r-- | nihav-codec-support/src/dsp/window.rs | 59 | ||||
-rw-r--r-- | nihav-codec-support/src/lib.rs | 19 | ||||
-rw-r--r-- | nihav-codec-support/src/test/dec_video.rs | 536 | ||||
-rw-r--r-- | nihav-codec-support/src/test/md5.rs | 172 | ||||
-rw-r--r-- | nihav-codec-support/src/test/mod.rs | 21 | ||||
-rw-r--r-- | nihav-codec-support/src/test/wavwriter.rs | 122 |
18 files changed, 4777 insertions, 0 deletions
diff --git a/nihav-codec-support/Cargo.toml b/nihav-codec-support/Cargo.toml new file mode 100644 index 0000000..c302a16 --- /dev/null +++ b/nihav-codec-support/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "nihav_codec_support" +version = "0.1.0" +authors = ["Kostya Shishkov <kostya.shishkov@gmail.com>"] +edition = "2018" + +[dependencies.nihav_core] +path = "../nihav-core" + +[features] +default = [] + +blockdsp = [] +h263 = ["blockdsp"] + +dsp = [] +dct = ["dsp"] +fft = ["dsp"] +mdct = ["fft", "dsp"] +dsp_window = ["dsp"] diff --git a/nihav-codec-support/src/codecs/blockdsp.rs b/nihav-codec-support/src/codecs/blockdsp.rs new file mode 100644 index 0000000..da92742 --- /dev/null +++ b/nihav-codec-support/src/codecs/blockdsp.rs @@ -0,0 +1,250 @@ +//! Various pixel block manipulation functions. +use nihav_core::frame::*; + +/// Puts YUV420 16x16 macroblock data onto picture in the requested place. +pub fn put_blocks(buf: &mut NAVideoBuffer<u8>, xpos: usize, ypos: usize, blk: &[[i16;64]; 6]) { + let stridey = buf.get_stride(0); + let strideu = buf.get_stride(1); + let stridev = buf.get_stride(2); + let mut idxy = buf.get_offset(0) + xpos * 16 + ypos * 16 * stridey; + let mut idxu = buf.get_offset(1) + xpos * 8 + ypos * 8 * strideu; + let mut idxv = buf.get_offset(2) + xpos * 8 + ypos * 8 * stridev; + + let data = buf.get_data_mut().unwrap(); + let framebuf: &mut [u8] = data.as_mut_slice(); + + for j in 0..8 { + for k in 0..8 { + let mut v = blk[0][k + j * 8]; + if v < 0 { v = 0; } else if v > 255 { v = 255; } + framebuf[idxy + k] = v as u8; + } + for k in 0..8 { + let mut v = blk[1][k + j * 8]; + if v < 0 { v = 0; } else if v > 255 { v = 255; } + framebuf[idxy + k + 8] = v as u8; + } + idxy += stridey; + } + for j in 0..8 { + for k in 0..8 { + let mut v = blk[2][k + j * 8]; + if v < 0 { v = 0; } else if v > 255 { v = 255; } + framebuf[idxy + k] = v as u8; + } + for k in 0..8 { + let mut v = blk[3][k + j * 8]; + if v < 0 { v = 0; } else if v > 255 { v = 255; } + framebuf[idxy + k + 8] = v as u8; + } + idxy += stridey; + } + + for j in 0..8 { + for k in 0..8 { + let mut v = blk[4][k + j * 8]; + if v < 0 { v = 0; } else if v > 255 { v = 255; } + framebuf[idxu + k] = v as u8; + } + for k in 0..8 { + let mut v = blk[5][k + j * 8]; + if v < 0 { v = 0; } else if v > 255 { v = 255; } + framebuf[idxv + k] = v as u8; + } + idxu += strideu; + idxv += stridev; + } +} + +/// Adds YUV420 16x16 macroblock coefficients to the picture in the requested place. +pub fn add_blocks(buf: &mut NAVideoBuffer<u8>, xpos: usize, ypos: usize, blk: &[[i16;64]; 6]) { + let stridey = buf.get_stride(0); + let strideu = buf.get_stride(1); + let stridev = buf.get_stride(2); + let mut idxy = buf.get_offset(0) + xpos * 16 + ypos * 16 * stridey; + let mut idxu = buf.get_offset(1) + xpos * 8 + ypos * 8 * strideu; + let mut idxv = buf.get_offset(2) + xpos * 8 + ypos * 8 * stridev; + + let data = buf.get_data_mut().unwrap(); + let framebuf: &mut [u8] = data.as_mut_slice(); + + for j in 0..8 { + for k in 0..8 { + let mut v = blk[0][k + j * 8] + i16::from(framebuf[idxy + k]); + if v < 0 { v = 0; } else if v > 255 { v = 255; } + framebuf[idxy + k] = v as u8; + } + for k in 0..8 { + let mut v = blk[1][k + j * 8] + i16::from(framebuf[idxy + k + 8]); + if v < 0 { v = 0; } else if v > 255 { v = 255; } + framebuf[idxy + k + 8] = v as u8; + } + idxy += stridey; + } + for j in 0..8 { + for k in 0..8 { + let mut v = blk[2][k + j * 8] + i16::from(framebuf[idxy + k]); + if v < 0 { v = 0; } else if v > 255 { v = 255; } + framebuf[idxy + k] = v as u8; + } + for k in 0..8 { + let mut v = blk[3][k + j * 8] + i16::from(framebuf[idxy + k + 8]); + if v < 0 { v = 0; } else if v > 255 { v = 255; } + framebuf[idxy + k + 8] = v as u8; + } + idxy += stridey; + } + + for j in 0..8 { + for k in 0..8 { + let mut v = blk[4][k + j * 8] + i16::from(framebuf[idxu + k]); + if v < 0 { v = 0; } else if v > 255 { v = 255; } + framebuf[idxu + k] = v as u8; + } + for k in 0..8 { + let mut v = blk[5][k + j * 8] + i16::from(framebuf[idxv + k]); + if v < 0 { v = 0; } else if v > 255 { v = 255; } + framebuf[idxv + k] = v as u8; + } + idxu += strideu; + idxv += stridev; + } +} + +/// Copies block from the picture with pixels beyond the picture borders being replaced with replicated edge pixels. +pub fn edge_emu(src: &NAVideoBuffer<u8>, xpos: isize, ypos: isize, bw: usize, bh: usize, dst: &mut [u8], dstride: usize, comp: usize) { + let stride = src.get_stride(comp); + let offs = src.get_offset(comp); + let (w, h) = src.get_dimensions(comp); + let data = src.get_data(); + let framebuf: &[u8] = data.as_slice(); + + for y in 0..bh { + let srcy; + if (y as isize) + ypos < 0 { srcy = 0; } + else if (y as isize) + ypos >= (h as isize) { srcy = h - 1; } + else { srcy = ((y as isize) + ypos) as usize; } + + for x in 0..bw { + let srcx; + if (x as isize) + xpos < 0 { srcx = 0; } + else if (x as isize) + xpos >= (w as isize) { srcx = w - 1; } + else { srcx = ((x as isize) + xpos) as usize; } + dst[x + y * dstride] = framebuf[offs + srcx + srcy * stride]; + } + } +} + +/// A generic type for motion interpolation function used by [`copy_blocks`] +/// +/// The function expects following parameters: +/// * destination buffer +/// * destination buffer stride +/// * source buffer +/// * source buffer stride +/// * block width +/// * block height +/// +/// [`copy_blocks`]: ./fn.copy_blocks.html +pub type BlkInterpFunc = fn(&mut [u8], usize, &[u8], usize, usize, usize); + +/// Performs motion compensation on YUV420 macroblock. +/// +/// Arguments: +/// * `dx` and `dy` - destination coordinates +/// * `sx` and `sy` - source coordinates +/// * `bw` and `bh` - block dimensions +/// * `preborder` and `postborder` - number of pixels before and after interpolated one used by the interpolation filter. +/// * `mode` - interpolation mode (essentially the index for the `interp` array) +pub fn copy_blocks(dst: &mut NAVideoBuffer<u8>, src: &NAVideoBuffer<u8>, + dx: usize, dy: usize, sx: isize, sy: isize, bw: usize, bh: usize, + preborder: usize, postborder: usize, + mode: usize, interp: &[BlkInterpFunc]) +{ + let pre = if mode != 0 { preborder as isize } else { 0 }; + let post = if mode != 0 { postborder as isize } else { 0 }; + let (w, h) = src.get_dimensions(0); + + if (sx - pre < 0) || ((sx >> 1) - pre < 0) || (sx + (bw as isize) + post > (w as isize)) || + (sy - pre < 0) || ((sy >> 1) - pre < 0) || (sy + (bh as isize) + post > (h as isize)) { + let ebuf_stride: usize = 32; + let mut ebuf: Vec<u8> = vec![0; ebuf_stride * (bh + ((pre + post) as usize))]; + + for comp in 0..3 { + let dstride = dst.get_stride(comp); + let doff = dst.get_offset(comp); + let ddta = dst.get_data_mut().unwrap(); + let dbuf: &mut [u8] = ddta.as_mut_slice(); + let x = if comp > 0 { dx/2 } else { dx }; + let y = if comp > 0 { dy/2 } else { dy }; + let sx_ = (if comp > 0 { sx >> 1 } else { sx }) - pre; + let sy_ = (if comp > 0 { sy >> 1 } else { sy }) - pre; + let bw_ = (if comp > 0 { bw/2 } else { bw }) + ((pre + post) as usize); + let bh_ = (if comp > 0 { bh/2 } else { bh }) + ((pre + post) as usize); + edge_emu(src, sx_ - pre, sy_ - pre, bw_, bh_, + ebuf.as_mut_slice(), ebuf_stride, comp); + let bw_ = if comp > 0 { bw/2 } else { bw }; + let bh_ = if comp > 0 { bh/2 } else { bh }; + (interp[mode])(&mut dbuf[doff + x + y * dstride..], dstride, ebuf.as_slice(), ebuf_stride, bw_, bh_); + } + } else { + for comp in 0..3 { + let sstride = src.get_stride(comp); + let soff = src.get_offset(comp); + let sdta = src.get_data(); + let sbuf: &[u8] = sdta.as_slice(); + let dstride = dst.get_stride(comp); + let doff = dst.get_offset(comp); + let ddta = dst.get_data_mut().unwrap(); + let dbuf: &mut [u8] = ddta.as_mut_slice(); + let x = if comp > 0 { dx/2 } else { dx }; + let y = if comp > 0 { dy/2 } else { dy }; + let sx_ = ((if comp > 0 { sx >> 1 } else { sx }) - pre) as usize; + let sy_ = ((if comp > 0 { sy >> 1 } else { sy }) - pre) as usize; + let bw_ = if comp > 0 { bw/2 } else { bw }; + let bh_ = if comp > 0 { bh/2 } else { bh }; + (interp[mode])(&mut dbuf[doff + x + y * dstride..], dstride, &sbuf[(soff + sx_ + sy_ * sstride)..], sstride, bw_, bh_); + } + } +} + +/// Performs motion compensation on arbitrary block on some plane. +/// +/// See [`copy_blocks`] for the arguments explanation. +/// +/// [`copy_blocks`]: ./fn.copy_blocks.html +pub fn copy_block(dst: &mut NASimpleVideoFrame<u8>, src: NAVideoBufferRef<u8>, comp: usize, + dx: usize, dy: usize, mv_x: i16, mv_y: i16, bw: usize, bh: usize, + preborder: usize, postborder: usize, + mode: usize, interp: &[BlkInterpFunc]) +{ + let pre = if mode != 0 { preborder as isize } else { 0 }; + let post = if mode != 0 { postborder as isize } else { 0 }; + let (w, h) = src.get_dimensions(comp); + let sx = (dx as isize) + (mv_x as isize); + let sy = (dy as isize) + (mv_y as isize); + + if (sx - pre < 0) || (sx + (bw as isize) + post > (w as isize)) || + (sy - pre < 0) || (sy + (bh as isize) + post > (h as isize)) { + let ebuf_stride: usize = 32; + let mut ebuf: Vec<u8> = vec![0; ebuf_stride * (bh + ((pre + post) as usize))]; + + let dstride = dst.stride[comp]; + let doff = dst.offset[comp]; + let edge = (pre + post) as usize; + edge_emu(&src, sx - pre, sy - pre, bw + edge, bh + edge, + ebuf.as_mut_slice(), ebuf_stride, comp); + (interp[mode])(&mut dst.data[doff + dx + dy * dstride..], dstride, + ebuf.as_slice(), ebuf_stride, bw, bh); + } else { + let sstride = src.get_stride(comp); + let soff = src.get_offset(comp); + let sdta = src.get_data(); + let sbuf: &[u8] = sdta.as_slice(); + let dstride = dst.stride[comp]; + let doff = dst.offset[comp]; + let saddr = soff + ((sx - pre) as usize) + ((sy - pre) as usize) * sstride; + (interp[mode])(&mut dst.data[doff + dx + dy * dstride..], dstride, + &sbuf[saddr..], sstride, bw, bh); + } +} diff --git a/nihav-codec-support/src/codecs/h263/code.rs b/nihav-codec-support/src/codecs/h263/code.rs new file mode 100644 index 0000000..d794c69 --- /dev/null +++ b/nihav-codec-support/src/codecs/h263/code.rs @@ -0,0 +1,500 @@ +use nihav_core::frame::NAVideoBuffer; +use super::{BlockDSP, CBPInfo, MV}; +use super::super::blockdsp; +//use super::h263data::*; + +/*const W1: i32 = 22725; +const W2: i32 = 21407; +const W3: i32 = 19266; +const W4: i32 = 16383; +const W5: i32 = 12873; +const W6: i32 = 8867; +const W7: i32 = 4520; + +const ROW_SHIFT: u8 = 11; +const COL_SHIFT: u8 = 20; + +fn idct_row(row: &mut [i16]) { + let in0 = row[0] as i32; + let in1 = row[1] as i32; + let in2 = row[2] as i32; + let in3 = row[3] as i32; + let in4 = row[4] as i32; + let in5 = row[5] as i32; + let in6 = row[6] as i32; + let in7 = row[7] as i32; + + let mut a0 = in0 * W1 + (1 << (ROW_SHIFT - 1)); + let mut a1 = a0; + let mut a2 = a0; + let mut a3 = a0; + + a0 += W2 * in2; + a1 += W6 * in2; + a2 -= W6 * in2; + a3 -= W2 * in2; + + let mut b0 = W1 * in1 + W3 * in3; + let mut b1 = W3 * in1 - W7 * in3; + let mut b2 = W5 * in1 - W1 * in3; + let mut b3 = W7 * in1 - W5 * in3; + + a0 += W4 * in4 + W6 * in6; + a1 -= W4 * in4 + W2 * in6; + a2 -= W4 * in4 - W2 * in6; + a3 += W4 * in4 - W6 * in6; + + b0 += W5 * in5 + W7 * in7; + b1 -= W1 * in5 + W5 * in7; + b2 += W7 * in5 + W3 * in7; + b3 += W3 * in5 - W1 * in7; + + row[0] = ((a0 + b0) >> ROW_SHIFT) as i16; + row[7] = ((a0 - b0) >> ROW_SHIFT) as i16; + row[1] = ((a1 + b1) >> ROW_SHIFT) as i16; + row[6] = ((a1 - b1) >> ROW_SHIFT) as i16; + row[2] = ((a2 + b2) >> ROW_SHIFT) as i16; + row[5] = ((a2 - b2) >> ROW_SHIFT) as i16; + row[3] = ((a3 + b3) >> ROW_SHIFT) as i16; + row[4] = ((a3 - b3) >> ROW_SHIFT) as i16; +} + +fn idct_col(blk: &mut [i16; 64], off: usize) { + let in0 = blk[off + 0*8] as i32; + let in1 = blk[off + 1*8] as i32; + let in2 = blk[off + 2*8] as i32; + let in3 = blk[off + 3*8] as i32; + let in4 = blk[off + 4*8] as i32; + let in5 = blk[off + 5*8] as i32; + let in6 = blk[off + 6*8] as i32; + let in7 = blk[off + 7*8] as i32; + + let mut a0 = in0 * W1 + (1 << (COL_SHIFT - 1)); + let mut a1 = a0; + let mut a2 = a0; + let mut a3 = a0; + + a0 += W2 * in2; + a1 += W6 * in2; + a2 -= W6 * in2; + a3 -= W2 * in2; + + let mut b0 = W1 * in1 + W3 * in3; + let mut b1 = W3 * in1 - W7 * in3; + let mut b2 = W5 * in1 - W1 * in3; + let mut b3 = W7 * in1 - W5 * in3; + + a0 += W4 * in4 + W6 * in6; + a1 -= W4 * in4 + W2 * in6; + a2 -= W4 * in4 - W2 * in6; + a3 += W4 * in4 - W6 * in6; + + b0 += W5 * in5 + W7 * in7; + b1 -= W1 * in5 + W5 * in7; + b2 += W7 * in5 + W3 * in7; + b3 += W3 * in5 - W1 * in7; + + blk[off + 0*8] = ((a0 + b0) >> COL_SHIFT) as i16; + blk[off + 7*8] = ((a0 - b0) >> COL_SHIFT) as i16; + blk[off + 1*8] = ((a1 + b1) >> COL_SHIFT) as i16; + blk[off + 6*8] = ((a1 - b1) >> COL_SHIFT) as i16; + blk[off + 2*8] = ((a2 + b2) >> COL_SHIFT) as i16; + blk[off + 5*8] = ((a2 - b2) >> COL_SHIFT) as i16; + blk[off + 3*8] = ((a3 + b3) >> COL_SHIFT) as i16; + blk[off + 4*8] = ((a3 - b3) >> COL_SHIFT) as i16; +} + +#[allow(dead_code)] +pub fn h263_idct(blk: &mut [i16; 64]) { + for i in 0..8 { idct_row(&mut blk[i*8..(i+1)*8]); } + for i in 0..8 { idct_col(blk, i); } +}*/ + +const W1: i32 = 2841; +const W2: i32 = 2676; +const W3: i32 = 2408; +const W5: i32 = 1609; +const W6: i32 = 1108; +const W7: i32 = 565; +const W8: i32 = 181; + +const ROW_SHIFT: u8 = 8; +const COL_SHIFT: u8 = 14; + +#[allow(clippy::erasing_op)] +fn idct_row(row: &mut [i16]) { + let in0 = ((i32::from(row[0])) << 11) + (1 << (ROW_SHIFT - 1)); + let in1 = (i32::from(row[4])) << 11; + let in2 = i32::from(row[6]); + let in3 = i32::from(row[2]); + let in4 = i32::from(row[1]); + let in5 = i32::from(row[7]); + let in6 = i32::from(row[5]); + let in7 = i32::from(row[3]); + + let tmp = W7 * (in4 + in5); + let a4 = tmp + (W1 - W7) * in4; + let a5 = tmp - (W1 + W7) * in5; + + let tmp = W3 * (in6 + in7); + let a6 = tmp - (W3 - W5) * in6; + let a7 = tmp - (W3 + W5) * in7; + + let tmp = in0 + in1; + + let a0 = in0 - in1; + let t1 = W6 * (in2 + in3); + let a2 = t1 - (W2 + W6) * in2; + let a3 = t1 + (W2 - W6) * in3; + let b1 = a4 + a6; + + let b4 = a4 - a6; + let t2 = a5 - a7; + let b6 = a5 + a7; + let b7 = tmp + a3; + let b5 = tmp - a3; + let b3 = a0 + a2; + let b0 = a0 - a2; + let b2 = (W8 * (b4 + t2) + 128) >> 8; + let b4 = (W8 * (b4 - t2) + 128) >> 8; + + row[0] = ((b7 + b1) >> ROW_SHIFT) as i16; + row[7] = ((b7 - b1) >> ROW_SHIFT) as i16; + row[1] = ((b3 + b2) >> ROW_SHIFT) as i16; + row[6] = ((b3 - b2) >> ROW_SHIFT) as i16; + row[2] = ((b0 + b4) >> ROW_SHIFT) as i16; + row[5] = ((b0 - b4) >> ROW_SHIFT) as i16; + row[3] = ((b5 + b6) >> ROW_SHIFT) as i16; + row[4] = ((b5 - b6) >> ROW_SHIFT) as i16; +} + +#[allow(clippy::erasing_op)] +fn idct_col(blk: &mut [i16; 64], off: usize) { + let in0 = ((i32::from(blk[off + 0*8])) << 8) + (1 << (COL_SHIFT - 1)); + let in1 = (i32::from(blk[off + 4*8])) << 8; + let in2 = i32::from(blk[off + 6*8]); + let in3 = i32::from(blk[off + 2*8]); + let in4 = i32::from(blk[off + 1*8]); + let in5 = i32::from(blk[off + 7*8]); + let in6 = i32::from(blk[off + 5*8]); + let in7 = i32::from(blk[off + 3*8]); + + let tmp = W7 * (in4 + in5); + let a4 = (tmp + (W1 - W7) * in4) >> 3; + let a5 = (tmp - (W1 + W7) * in5) >> 3; + + let tmp = W3 * (in6 + in7); + let a6 = (tmp - (W3 - W5) * in6) >> 3; + let a7 = (tmp - (W3 + W5) * in7) >> 3; + + let tmp = in0 + in1; + + let a0 = in0 - in1; + let t1 = W6 * (in2 + in3); + let a2 = (t1 - (W2 + W6) * in2) >> 3; + let a3 = (t1 + (W2 - W6) * in3) >> 3; + let b1 = a4 + a6; + + let b4 = a4 - a6; + let t2 = a5 - a7; + let b6 = a5 + a7; + let b7 = tmp + a3; + let b5 = tmp - a3; + let b3 = a0 + a2; + let b0 = a0 - a2; + let b2 = (W8 * (b4 + t2) + 128) >> 8; + let b4 = (W8 * (b4 - t2) + 128) >> 8; + + blk[off + 0*8] = ((b7 + b1) >> COL_SHIFT) as i16; + blk[off + 7*8] = ((b7 - b1) >> COL_SHIFT) as i16; + blk[off + 1*8] = ((b3 + b2) >> COL_SHIFT) as i16; + blk[off + 6*8] = ((b3 - b2) >> COL_SHIFT) as i16; + blk[off + 2*8] = ((b0 + b4) >> COL_SHIFT) as i16; + blk[off + 5*8] = ((b0 - b4) >> COL_SHIFT) as i16; + blk[off + 3*8] = ((b5 + b6) >> COL_SHIFT) as i16; + blk[off + 4*8] = ((b5 - b6) >> COL_SHIFT) as i16; +} + +#[allow(dead_code)] +pub fn h263_idct(blk: &mut [i16; 64]) { + for i in 0..8 { idct_row(&mut blk[i*8..(i+1)*8]); } + for i in 0..8 { idct_col(blk, i); } +} + +fn h263_interp00(dst: &mut [u8], dstride: usize, src: &[u8], sstride: usize, bw: usize, bh: usize) +{ + let mut didx = 0; + let mut sidx = 0; + for _ in 0..bh { + for x in 0..bw { dst[didx + x] = src[sidx + x]; } + didx += dstride; + sidx += sstride; + } +} + +fn h263_interp01(dst: &mut [u8], dstride: usize, src: &[u8], sstride: usize, bw: usize, bh: usize) +{ + let mut didx = 0; + let mut sidx = 0; + for _ in 0..bh { + for x in 0..bw { dst[didx + x] = (((src[sidx + x] as u16) + (src[sidx + x + 1] as u16) + 1) >> 1) as u8; } + didx += dstride; + sidx += sstride; + } +} + +fn h263_interp10(dst: &mut [u8], dstride: usize, src: &[u8], sstride: usize, bw: usize, bh: usize) +{ + let mut didx = 0; + let mut sidx = 0; + for _ in 0..bh { + for x in 0..bw { dst[didx + x] = (((src[sidx + x] as u16) + (src[sidx + x + sstride] as u16) + 1) >> 1) as u8; } + didx += dstride; + sidx += sstride; + } +} + +fn h263_interp11(dst: &mut [u8], dstride: usize, src: &[u8], sstride: usize, bw: usize, bh: usize) +{ + let mut didx = 0; + let mut sidx = 0; + for _ in 0..bh { + for x in 0..bw { + dst[didx + x] = (((src[sidx + x] as u16) + + (src[sidx + x + 1] as u16) + + (src[sidx + x + sstride] as u16) + + (src[sidx + x + sstride + 1] as u16) + 2) >> 2) as u8; + } + didx += dstride; + sidx += sstride; + } +} + +pub const H263_INTERP_FUNCS: &[blockdsp::BlkInterpFunc] = &[ + h263_interp00, h263_interp01, h263_interp10, h263_interp11 ]; + +fn h263_interp00_avg(dst: &mut [u8], dstride: usize, src: &[u8], sstride: usize, bw: usize, bh: usize) +{ + let mut didx = 0; + let mut sidx = 0; + for _ in 0..bh { + for x in 0..bw { + let a = dst[didx + x] as u16; + let b = src[sidx + x] as u16; + dst[didx + x] = ((a + b + 1) >> 1) as u8; + } + didx += dstride; + sidx += sstride; + } +} + +fn h263_interp01_avg(dst: &mut [u8], dstride: usize, src: &[u8], sstride: usize, bw: usize, bh: usize) +{ + let mut didx = 0; + let mut sidx = 0; + for _ in 0..bh { + for x in 0..bw { + let a = dst[didx + x] as u16; + let b = ((src[sidx + x] as u16) + (src[sidx + x + 1] as u16) + 1) >> 1; + dst[didx + x] = ((a + b + 1) >> 1) as u8; + } + didx += dstride; + sidx += sstride; + } +} + +fn h263_interp10_avg(dst: &mut [u8], dstride: usize, src: &[u8], sstride: usize, bw: usize, bh: usize) +{ + let mut didx = 0; + let mut sidx = 0; + for _ in 0..bh { + for x in 0..bw { + let a = dst[didx + x] as u16; + let b = ((src[sidx + x] as u16) + (src[sidx + x + sstride] as u16) + 1) >> 1; + dst[didx + x] = ((a + b + 1) >> 1) as u8; + } + didx += dstride; + sidx += sstride; + } +} + +fn h263_interp11_avg(dst: &mut [u8], dstride: usize, src: &[u8], sstride: usize, bw: usize, bh: usize) +{ + let mut didx = 0; + let mut sidx = 0; + for _ in 0..bh { + for x in 0..bw { + let a = dst[didx + x] as u16; + let b = ((src[sidx + x] as u16) + + (src[sidx + x + 1] as u16) + + (src[sidx + x + sstride] as u16) + + (src[sidx + x + sstride + 1] as u16) + 2) >> 2; + dst[didx + x] = ((a + b + 1) >> 1) as u8; + } + didx += dstride; + sidx += sstride; + } +} + +pub const H263_INTERP_AVG_FUNCS: &[blockdsp::BlkInterpFunc] = &[ + h263_interp00_avg, h263_interp01_avg, h263_interp10_avg, h263_interp11_avg ]; + +pub struct H263BlockDSP { } + +impl H263BlockDSP { + pub fn new() -> Self { + H263BlockDSP { } + } +} + +#[allow(clippy::erasing_op)] +fn deblock_hor(buf: &mut NAVideoBuffer<u8>, comp: usize, q: u8, off: usize) { + let stride = buf.get_stride(comp); + let dptr = buf.get_data_mut().unwrap(); + let buf = dptr.as_mut_slice(); + for x in 0..8 { + let a = buf[off - 2 * stride + x] as i16; + let b = buf[off - 1 * stride + x] as i16; + let c = buf[off + 0 * stride + x] as i16; + let d = buf[off + 1 * stride + x] as i16; + let diff = ((a - d) * 3 + (c - b) * 8) >> 4; + if (diff != 0) && (diff >= -32) && (diff < 32) { + let d0 = diff.abs() * 2 - (q as i16); + let d1 = if d0 < 0 { 0 } else { d0 }; + let d2 = diff.abs() - d1; + let d3 = if d2 < 0 { 0 } else { d2 }; + + let delta = if diff < 0 { -d3 } else { d3 }; + + let b1 = b + delta; + if b1 < 0 { buf[off - 1 * stride + x] = 0; } + else if b1 > 255 { buf[off - 1 * stride + x] = 0xFF; } + else { buf[off - 1 * stride + x] = b1 as u8; } + let c1 = c - delta; + if c1 < 0 { buf[off + x] = 0; } + else if c1 > 255 { buf[off + x] = 0xFF; } + else { buf[off + x] = c1 as u8; } + } + } +} + +fn deblock_ver(buf: &mut NAVideoBuffer<u8>, comp: usize, q: u8, off: usize) { + let stride = buf.get_stride(comp); + let dptr = buf.get_data_mut().unwrap(); + let buf = dptr.as_mut_slice(); + for y in 0..8 { + let a = buf[off - 2 + y * stride] as i16; + let b = buf[off - 1 + y * stride] as i16; + let c = buf[off + 0 + y * stride] as i16; + let d = buf[off + 1 + y * stride] as i16; + let diff = ((a - d) * 3 + (c - b) * 8) >> 4; + if (diff != 0) && (diff >= -32) && (diff < 32) { + let d0 = diff.abs() * 2 - (q as i16); + let d1 = if d0 < 0 { 0 } else { d0 }; + let d2 = diff.abs() - d1; + let d3 = if d2 < 0 { 0 } else { d2 }; + + let delta = if diff < 0 { -d3 } else { d3 }; + + let b1 = b + delta; + if b1 < 0 { buf[off - 1 + y * stride] = 0; } + else if b1 > 255 { buf[off - 1 + y * stride] = 0xFF; } + else { buf[off - 1 + y * stride] = b1 as u8; } + let c1 = c - delta; + if c1 < 0 { buf[off + y * stride] = 0; } + else if c1 > 255 { buf[off + y * stride] = 0xFF; } + else { buf[off + y * stride] = c1 as u8; } + } + } +} + +pub fn h263_filter_row(buf: &mut NAVideoBuffer<u8>, mb_y: usize, mb_w: usize, cbpi: &CBPInfo) { + let stride = buf.get_stride(0); + let mut off = buf.get_offset(0) + mb_y * 16 * stride; + for mb_x in 0..mb_w { + let coff = off; + let coded0 = cbpi.is_coded(mb_x, 0); + let coded1 = cbpi.is_coded(mb_x, 1); + let q = cbpi.get_q(mb_w + mb_x); + if mb_y != 0 { + if coded0 && cbpi.is_coded_top(mb_x, 0) { deblock_hor(buf, 0, q, coff); } + if coded1 && cbpi.is_coded_top(mb_x, 1) { deblock_hor(buf, 0, q, coff + 8); } + } + let coff = off + 8 * stride; + if cbpi.is_coded(mb_x, 2) && coded0 { deblock_hor(buf, 0, q, coff); } + if cbpi.is_coded(mb_x, 3) && coded1 { deblock_hor(buf, 0, q, coff + 8); } + off += 16; + } + let mut leftt = false; + let mut leftc = false; + let mut off = buf.get_offset(0) + mb_y * 16 * stride; + for mb_x in 0..mb_w { + let ctop0 = cbpi.is_coded_top(mb_x, 0); + let ctop1 = cbpi.is_coded_top(mb_x, 0); + let ccur0 = cbpi.is_coded(mb_x, 0); + let ccur1 = cbpi.is_coded(mb_x, 1); + let q = cbpi.get_q(mb_w + mb_x); + if mb_y != 0 { + let coff = off - 8 * stride; + let qtop = cbpi.get_q(mb_x); + if leftt && ctop0 { deblock_ver(buf, 0, qtop, coff); } + if ctop0 && ctop1 { deblock_ver(buf, 0, qtop, coff + 8); } + } + if leftc && ccur0 { deblock_ver(buf, 0, q, off); } + if ccur0 && ccur1 { deblock_ver(buf, 0, q, off + 8); } + leftt = ctop1; + leftc = ccur1; + off += 16; + } + let strideu = buf.get_stride(1); + let stridev = buf.get_stride(2); + let offu = buf.get_offset(1) + mb_y * 8 * strideu; + let offv = buf.get_offset(2) + mb_y * 8 * stridev; + if mb_y != 0 { + for mb_x in 0..mb_w { + let ctu = cbpi.is_coded_top(mb_x, 4); + let ccu = cbpi.is_coded(mb_x, 4); + let ctv = cbpi.is_coded_top(mb_x, 5); + let ccv = cbpi.is_coded(mb_x, 5); + let q = cbpi.get_q(mb_w + mb_x); + if ctu && ccu { deblock_hor(buf, 1, q, offu + mb_x * 8); } + if ctv && ccv { deblock_hor(buf, 2, q, offv + mb_x * 8); } + } + let mut leftu = false; + let mut leftv = false; + let offu = buf.get_offset(1) + (mb_y - 1) * 8 * strideu; + let offv = buf.get_offset(2) + (mb_y - 1) * 8 * stridev; + for mb_x in 0..mb_w { + let ctu = cbpi.is_coded_top(mb_x, 4); + let ctv = cbpi.is_coded_top(mb_x, 5); + let qt = cbpi.get_q(mb_x); + if leftu && ctu { deblock_ver(buf, 1, qt, offu + mb_x * 8); } + if leftv && ctv { deblock_ver(buf, 2, qt, offv + mb_x * 8); } + leftu = ctu; + leftv = ctv; + } + } +} + +impl BlockDSP for H263BlockDSP { + fn idct(&self, blk: &mut [i16; 64]) { + h263_idct(blk) + } + fn copy_blocks(&self, dst: &mut NAVideoBuffer<u8>, src: &NAVideoBuffer<u8>, xpos: usize, ypos: usize, w: usize, h: usize, mv: MV) { + let srcx = ((mv.x >> 1) as isize) + (xpos as isize); + let srcy = ((mv.y >> 1) as isize) + (ypos as isize); + let mode = ((mv.x & 1) + (mv.y & 1) * 2) as usize; + + blockdsp::copy_blocks(dst, src, xpos, ypos, srcx, srcy, w, h, 0, 1, mode, H263_INTERP_FUNCS); + } + fn avg_blocks(&self, dst: &mut NAVideoBuffer<u8>, src: &NAVideoBuffer<u8>, xpos: usize, ypos: usize, w: usize, h: usize, mv: MV) { + let srcx = ((mv.x >> 1) as isize) + (xpos as isize); + let srcy = ((mv.y >> 1) as isize) + (ypos as isize); + let mode = ((mv.x & 1) + (mv.y & 1) * 2) as usize; + + blockdsp::copy_blocks(dst, src, xpos, ypos, srcx, srcy, w, h, 0, 1, mode, H263_INTERP_AVG_FUNCS); + } + fn filter_row(&self, buf: &mut NAVideoBuffer<u8>, mb_y: usize, mb_w: usize, cbpi: &CBPInfo) { + h263_filter_row(buf, mb_y, mb_w, cbpi) + } +} diff --git a/nihav-codec-support/src/codecs/h263/data.rs b/nihav-codec-support/src/codecs/h263/data.rs new file mode 100644 index 0000000..9a2d4a2 --- /dev/null +++ b/nihav-codec-support/src/codecs/h263/data.rs @@ -0,0 +1,203 @@ +use nihav_core::io::codebook::CodebookDescReader; + +#[allow(dead_code)] +pub const H263_SCALES: &[u8] = &[ + 0, 1, 2, 3, 4, 5, 6, 6, 7, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15 ]; + +#[allow(dead_code)] +pub const H263_DC_SCALES: &[u8] = &[ + 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62 ]; + +pub const H263_SCAN_H: &[usize] = &[ + 0, 1, 2, 3, 8, 9, 16, 17, + 10, 11, 4, 5, 6, 7, 15, 14, + 13, 12, 19, 18, 24, 25, 32, 33, + 26, 27, 20, 21, 22, 23, 28, 29, + 30, 31, 34, 35, 40, 41, 48, 49, + 42, 43, 36, 37, 38, 39, 44, 45, + 46, 47, 50, 51, 56, 57, 58, 59, + 52, 53, 54, 55, 60, 61, 62, 63 +]; + +pub const H263_SCAN_V: &[usize] = &[ + 0, 8, 16, 24, 1, 9, 2, 10, + 17, 25, 32, 40, 48, 56, 57, 49, + 41, 33, 26, 18, 3, 11, 4, 12, + 19, 27, 34, 42, 50, 58, 35, 43, + 51, 59, 20, 28, 5, 13, 6, 14, + 21, 29, 36, 44, 52, 60, 37, 45, + 53, 61, 22, 30, 7, 15, 23, 31, + 38, 46, 54, 62, 39, 47, 55, 63 +]; + +pub const H263_SIZES: &[(usize, usize)] = &[ + (0, 0), (128, 96), (176, 144), (352, 288), (704, 576), (1408, 1152) +]; + +pub const H263_INTRA_MCBPC: &[(u8, u8)] = &[ + (1, 1), (1, 3), (2, 3), (3, 3), (1, 4), (1, 6), (2, 6), (3, 6), (1, 9) +]; + +pub const H263_INTER_MCBPC: &[(u8, u8)] = &[ + (1, 1), (3, 4), (2, 4), (5, 6), (3, 5), (4, 8), (3, 8), (3, 7), + (3, 3), (7, 7), (6, 7), (5, 9), (4, 6), (4, 9), (3, 9), (2, 9), + (2, 3), (5, 7), (4, 7), (5, 8), (1, 9), (0, 0), (0, 0), (0, 0), + (2, 11), (12, 13), (14, 13), (15, 13) +]; + +pub const H263_CBPY: &[(u8, u8)] = &[ + ( 3, 4), ( 5, 5), ( 4, 5), ( 9, 4), ( 3, 5), ( 7, 4), ( 2, 6), (11, 4), + ( 2, 5), ( 3, 6), ( 5, 4), (10, 4), ( 4, 4), ( 8, 4), ( 6, 4), ( 3, 2) +]; + +pub const H263_MV: &[(u8, u8)] = &[ + ( 1, 1), ( 1, 2), ( 1, 3), ( 1, 4), ( 3, 6), ( 5, 7), ( 4, 7), ( 3, 7), + (11, 9), (10, 9), ( 9, 9), (17, 10), (16, 10), (15, 10), (14, 10), (13, 10), + (12, 10), (11, 10), (10, 10), ( 9, 10), ( 8, 10), ( 7, 10), ( 6, 10), ( 5, 10), + ( 4, 10), ( 7, 11), ( 6, 11), ( 5, 11), ( 4, 11), ( 3, 11), ( 2, 11), ( 3, 12), + ( 2, 12) +]; + +pub const H263_MBTYPE_B: &[(u8, u8)] = &[ + (1, 1), (3, 3), (1, 5), (4, 4), (5, 4), (6, 6), (2, 4), (3, 4), + (7, 6), (4, 6), (5, 6), (1, 6), (1, 7), (1, 8), (1, 10) +]; + +// 0x1 - direct, 0x2 - has quant, 0x4 - has CBP, 0x8 - has dquant, 0x10 - has fwd, 0x20 - has bwd, 0x40 - intra + +pub const H263_MBB_CAP_CODED: u8 = 0x2; +pub const H263_MBB_CAP_DQUANT: u8 = 0x4; +pub const H263_MBB_CAP_FORWARD: u8 = 0x10; +pub const H263_MBB_CAP_BACKWARD: u8 = 0x20; +pub const H263_MBB_CAP_INTRA: u8 = 0x80; + +pub const H263_MBTYPE_B_CAPS: &[u8] = &[ + 0x00, 0x02, 0x06, // skipped, direct, direct+dq + 0x10, 0x12, 0x16, // forward, coded forward, forward+dq + 0x20, 0x22, 0x26, // backward, coded backward, backward+dq + 0x30, 0x32, 0x36, // bidir, coded bidir, bidir+dq + 0x82, 0x86 // intra, intra+dq +]; + +pub const H263_CBPC_B: &[(u8, u8)] = &[ + (0, 1), (2, 2), (7, 3), (6, 3) +]; + +pub const H263_DQUANT_TAB: &[i8] = &[-1, -2, 1, 2]; + +pub const H263_MODIFIED_QUANT: [[u8; 32]; 2] = [ + [ + 0, 3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 10, 11, 12, 13, + 14, 15, 16, 17, 18, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28 + ], [ + 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 24, 25, 26, 27, 28, 29, 30, 31, 31, 31, 26 + ] +]; + +pub const H263_CHROMA_QUANT: [u8; 32] = [ + 0, 1, 2, 3, 4, 5, 6, 6, 7, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 12, 13, 13, 13, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15 +]; + +pub struct H263ShortCodeReader { tab: &'static [(u8, u8)] } + +impl H263ShortCodeReader { + pub fn new(tab: &'static [(u8, u8)]) -> Self { H263ShortCodeReader { tab } } +} + +impl CodebookDescReader<u8> for H263ShortCodeReader { + fn bits(&mut self, idx: usize) -> u8 { let (_, bits) = self.tab[idx]; bits } + fn code(&mut self, idx: usize) -> u32 { let (code, _) = self.tab[idx]; code as u32 } + fn sym (&mut self, idx: usize) -> u8 { idx as u8 } + fn len(&mut self) -> usize { self.tab.len() } +} + +#[derive(Clone,Copy)] +pub struct H263RLSym { run: u8, level: i8 } +impl H263RLSym { + pub fn get_run(self) -> u8 { self.run } + pub fn is_last(self) -> bool { self.level < 0 } + pub fn is_escape(self) -> bool { (self.run == 0) && (self.level == 0) } + pub fn get_level(self) -> i16 { if self.level < 0 { -self.level as i16 } else { self.level as i16 } } +} + +pub struct H263RLCodeDesc { code: u8, bits: u8, sym: H263RLSym } + +macro_rules! rlcodes{ + ($(($c:expr, $b:expr, $r:expr, $l:expr)),*) => { + &[$(H263RLCodeDesc{ code: $c, bits: $b, sym: H263RLSym{ run: $r, level: $l }}),*] + } +} + +pub const H263_RL_CODES: &[H263RLCodeDesc] = rlcodes!( + (0x02, 2, 0, 1), (0x0F, 4, 0, 2), (0x15, 6, 0, 3), (0x17, 7, 0, 4), + (0x1F, 8, 0, 5), (0x25, 9, 0, 6), (0x24, 9, 0, 7), (0x21, 10, 0, 8), + (0x20, 10, 0, 9), (0x07, 11, 0, 10), (0x06, 11, 0, 11), (0x20, 11, 0, 12), + (0x06, 3, 1, 1), (0x14, 6, 1, 2), (0x1E, 8, 1, 3), (0x0F, 10, 1, 4), + (0x21, 11, 1, 5), (0x50, 12, 1, 6), (0x0E, 4, 2, 1), (0x1D, 8, 2, 2), + (0x0E, 10, 2, 3), (0x51, 12, 2, 4), (0x0D, 5, 3, 1), (0x23, 9, 3, 2), + (0x0D, 10, 3, 3), (0x0C, 5, 4, 1), (0x22, 9, 4, 2), (0x52, 12, 4, 3), + (0x0B, 5, 5, 1), (0x0C, 10, 5, 2), (0x53, 12, 5, 3), (0x13, 6, 6, 1), + (0x0B, 10, 6, 2), (0x54, 12, 6, 3), (0x12, 6, 7, 1), (0x0A, 10, 7, 2), + (0x11, 6, 8, 1), (0x09, 10, 8, 2), (0x10, 6, 9, 1), (0x08, 10, 9, 2), + (0x16, 7, 10, 1), (0x55, 12, 10, 2), (0x15, 7, 11, 1), (0x14, 7, 12, 1), + (0x1C, 8, 13, 1), (0x1B, 8, 14, 1), (0x21, 9, 15, 1), (0x20, 9, 16, 1), + (0x1F, 9, 17, 1), (0x1E, 9, 18, 1), (0x1D, 9, 19, 1), (0x1C, 9, 20, 1), + (0x1B, 9, 21, 1), (0x1A, 9, 22, 1), (0x22, 11, 23, 1), (0x23, 11, 24, 1), + (0x56, 12, 25, 1), (0x57, 12, 26, 1), (0x07, 4, 0, -1), (0x19, 9, 0, -2), + (0x05, 11, 0, -3), (0x0F, 6, 1, -1), (0x04, 11, 1, -2), (0x0E, 6, 2, -1), + (0x0D, 6, 3, -1), (0x0C, 6, 4, -1), (0x13, 7, 5, -1), (0x12, 7, 6, -1), + (0x11, 7, 7, -1), (0x10, 7, 8, -1), (0x1A, 8, 9, -1), (0x19, 8, 10, -1), + (0x18, 8, 11, -1), (0x17, 8, 12, -1), (0x16, 8, 13, -1), (0x15, 8, 14, -1), + (0x14, 8, 15, -1), (0x13, 8, 16, -1), (0x18, 9, 17, -1), (0x17, 9, 18, -1), + (0x16, 9, 19, -1), (0x15, 9, 20, -1), (0x14, 9, 21, -1), (0x13, 9, 22, -1), + (0x12, 9, 23, -1), (0x11, 9, 24, -1), (0x07, 10, 25, -1), (0x06, 10, 26, -1), + (0x05, 10, 27, -1), (0x04, 10, 28, -1), (0x24, 11, 29, -1), (0x25, 11, 30, -1), + (0x26, 11, 31, -1), (0x27, 11, 32, -1), (0x58, 12, 33, -1), (0x59, 12, 34, -1), + (0x5A, 12, 35, -1), (0x5B, 12, 36, -1), (0x5C, 12, 37, -1), (0x5D, 12, 38, -1), + (0x5E, 12, 39, -1), (0x5F, 12, 40, -1), (0x03, 7, 0, 0) +); + +pub const H263_RL_CODES_AIC: &[H263RLCodeDesc] = rlcodes!( + (0x02, 2, 0, 1), (0x06, 3, 0, 2), (0x0E, 4, 0, 3), (0x0C, 5, 0, 4), + (0x0D, 5, 0, 5), (0x10, 6, 0, 6), (0x11, 6, 0, 7), (0x12, 6, 0, 8), + (0x16, 7, 0, 9), (0x1B, 8, 0, 10), (0x20, 9, 0, 11), (0x21, 9, 0, 12), + (0x1A, 9, 0, 13), (0x1B, 9, 0, 14), (0x1C, 9, 0, 15), (0x1D, 9, 0, 16), + (0x1E, 9, 0, 17), (0x1F, 9, 0, 18), (0x23, 11, 0, 19), (0x22, 11, 0, 20), + (0x57, 12, 0, 21), (0x56, 12, 0, 22), (0x55, 12, 0, 23), (0x54, 12, 0, 24), + (0x53, 12, 0, 25), (0x0F, 4, 1, 1), (0x14, 6, 1, 2), (0x14, 7, 1, 3), + (0x1E, 8, 1, 4), (0x0F, 10, 1, 5), (0x21, 11, 1, 6), (0x50, 12, 1, 7), + (0x0B, 5, 2, 1), (0x15, 7, 2, 2), (0x0E, 10, 2, 3), (0x09, 10, 2, 4), + (0x15, 6, 3, 1), (0x1D, 8, 3, 2), (0x0D, 10, 3, 3), (0x51, 12, 3, 4), + (0x13, 6, 4, 1), (0x23, 9, 4, 2), (0x07, 11, 4, 3), (0x17, 7, 5, 1), + (0x22, 9, 5, 2), (0x52, 12, 5, 3), (0x1C, 8, 6, 1), (0x0C, 10, 6, 2), + (0x1F, 8, 7, 1), (0x0B, 10, 7, 2), (0x25, 9, 8, 1), (0x0A, 10, 8, 2), + (0x24, 9, 9, 1), (0x06, 11, 9, 2), (0x21, 10, 10, 1), (0x20, 10, 11, 1), + (0x08, 10, 12, 1), (0x20, 11, 13, 1), (0x07, 4, 0, -1), (0x0C, 6, 0, -2), + (0x10, 7, 0, -3), (0x13, 8, 0, -4), (0x11, 9, 0, -5), (0x12, 9, 0, -6), + (0x04, 10, 0, -7), (0x27, 11, 0, -8), (0x26, 11, 0, -9), (0x5F, 12, 0,-10), + (0x0F, 6, 1, -1), (0x13, 9, 1, -2), (0x05, 10, 1, -3), (0x25, 11, 1, -4), + (0x0E, 6, 2, -1), (0x14, 9, 2, -2), (0x24, 11, 2, -3), (0x0D, 6, 3, -1), + (0x06, 10, 3, -2), (0x5E, 12, 3, -3), (0x11, 7, 4, -1), (0x07, 10, 4, -2), + (0x13, 7, 5, -1), (0x5D, 12, 5, -2), (0x12, 7, 6, -1), (0x5C, 12, 6, -2), + (0x14, 8, 7, -1), (0x5B, 12, 7, -2), (0x15, 8, 8, -1), (0x1A, 8, 9, -1), + (0x19, 8, 10, -1), (0x18, 8, 11, -1), (0x17, 8, 12, -1), (0x16, 8, 13, -1), + (0x19, 9, 14, -1), (0x15, 9, 15, -1), (0x16, 9, 16, -1), (0x18, 9, 17, -1), + (0x17, 9, 18, -1), (0x04, 11, 19, -1), (0x05, 11, 20, -1), (0x58, 12, 21, -1), + (0x59, 12, 22, -1), (0x5A, 12, 23, -1), (0x03, 7, 0, 0) +); + +pub struct H263RLCodeReader { tab: &'static [H263RLCodeDesc] } + +impl H263RLCodeReader { + pub fn new(tab: &'static [H263RLCodeDesc]) -> Self { H263RLCodeReader { tab } } +} + +impl CodebookDescReader<H263RLSym> for H263RLCodeReader { + fn bits(&mut self, idx: usize) -> u8 { self.tab[idx].bits } + fn code(&mut self, idx: usize) -> u32 { self.tab[idx].code as u32 } + fn sym (&mut self, idx: usize) -> H263RLSym { self.tab[idx].sym } + fn len(&mut self) -> usize { self.tab.len() } +} + diff --git a/nihav-codec-support/src/codecs/h263/decoder.rs b/nihav-codec-support/src/codecs/h263/decoder.rs new file mode 100644 index 0000000..929854a --- /dev/null +++ b/nihav-codec-support/src/codecs/h263/decoder.rs @@ -0,0 +1,581 @@ +//use std::mem; +use nihav_core::codecs::DecoderError; +use nihav_core::frame::*; +use super::super::*; +use super::super::blockdsp; +use super::*; +//use super::code::*; +use nihav_core::formats; + +#[allow(dead_code)] +struct MVInfo { + mv: Vec<MV>, + mb_w: usize, + mb_stride: usize, + mb_start: usize, + top: bool, + mvmode: MVMode, +} + +impl MVInfo { + fn new() -> Self { MVInfo{ mv: Vec::new(), mb_w: 0, mb_stride: 0, mb_start: 0, top: true, mvmode: MVMode::Old } } + fn reset(&mut self, mb_w: usize, mb_start: usize, mvmode: MVMode) { + self.mb_start = mb_start; + self.mb_w = mb_w; + self.mb_stride = mb_w * 2; + self.mv.resize(self.mb_stride * 3, ZERO_MV); + self.mvmode = mvmode; + } + fn update_row(&mut self) { + self.mb_start = self.mb_w + 1; + self.top = false; + for i in 0..self.mb_stride { + self.mv[i] = self.mv[self.mb_stride * 2 + i]; + } + } + #[allow(non_snake_case)] + fn predict(&mut self, mb_x: usize, blk_no: usize, use4: bool, diff: MV, first_line: bool, first_mb: bool) -> MV { + let A; + let B; + let C; + let last = mb_x == self.mb_w - 1; + match blk_no { + 0 => { + if mb_x != self.mb_start { + A = if !first_mb { self.mv[self.mb_stride + mb_x * 2 - 1] } else { ZERO_MV }; + B = if !first_line { self.mv[ mb_x * 2] } else { A }; + C = if !first_line && !last { self.mv[mb_x * 2 + 2] } else { ZERO_MV }; + } else { + A = ZERO_MV; B = ZERO_MV; C = ZERO_MV; + } + }, + 1 => { + A = self.mv[self.mb_stride + mb_x * 2]; + B = if !first_line { self.mv[mb_x * 2 + 1] } else { A }; + C = if !first_line && !last { self.mv[mb_x * 2 + 2] } else { ZERO_MV/*A*/ }; + }, + 2 => { + A = if mb_x != self.mb_start { self.mv[self.mb_stride * 2 + mb_x * 2 - 1] } else { ZERO_MV }; + B = self.mv[self.mb_stride + mb_x * 2]; + C = self.mv[self.mb_stride + mb_x * 2 + 1]; + }, + 3 => { + A = self.mv[self.mb_stride * 2 + mb_x * 2]; + B = self.mv[self.mb_stride * 1 + mb_x * 2 + 1]; + C = self.mv[self.mb_stride * 1 + mb_x * 2]; + }, + _ => { return ZERO_MV; } + } + let pred_mv = MV::pred(A, B, C); + let new_mv = MV::add_umv(pred_mv, diff, self.mvmode); + if !use4 { + self.mv[self.mb_stride * 1 + mb_x * 2 + 0] = new_mv; + self.mv[self.mb_stride * 1 + mb_x * 2 + 1] = new_mv; + self.mv[self.mb_stride * 2 + mb_x * 2 + 0] = new_mv; + self.mv[self.mb_stride * 2 + mb_x * 2 + 1] = new_mv; + } else { + match blk_no { + 0 => { self.mv[self.mb_stride * 1 + mb_x * 2 + 0] = new_mv; }, + 1 => { self.mv[self.mb_stride * 1 + mb_x * 2 + 1] = new_mv; }, + 2 => { self.mv[self.mb_stride * 2 + mb_x * 2 + 0] = new_mv; }, + 3 => { self.mv[self.mb_stride * 2 + mb_x * 2 + 1] = new_mv; }, + _ => {}, + }; + } + + new_mv + } + fn set_zero_mv(&mut self, mb_x: usize) { + self.mv[self.mb_stride * 1 + mb_x * 2 + 0] = ZERO_MV; + self.mv[self.mb_stride * 1 + mb_x * 2 + 1] = ZERO_MV; + self.mv[self.mb_stride * 2 + mb_x * 2 + 0] = ZERO_MV; + self.mv[self.mb_stride * 2 + mb_x * 2 + 1] = ZERO_MV; + } + fn get_mv(&self, mb_x: usize, blk_no: usize) -> MV { + self.mv[self.mb_stride + mb_x * 2 + (blk_no & 1) + (blk_no >> 1) * self.mb_stride] + } +} + +#[allow(dead_code)] +#[derive(Clone,Copy)] +struct BMB { + num_mv: usize, + mv_f: [MV; 4], + mv_b: [MV; 4], + fwd: bool, + blk: [[i16; 64]; 6], + cbp: u8, +} + +impl BMB { + fn new() -> Self { BMB {blk: [[0; 64]; 6], cbp: 0, fwd: false, mv_f: [ZERO_MV; 4], mv_b: [ZERO_MV; 4], num_mv: 0} } +} + +#[derive(Clone,Copy)] +struct PredCoeffs { + hor: [[i16; 8]; 6], + ver: [[i16; 8]; 6], +} + +const ZERO_PRED_COEFFS: PredCoeffs = PredCoeffs { hor: [[0; 8]; 6], ver: [[0; 8]; 6] }; + +pub struct H263BaseDecoder { + w: usize, + h: usize, + mb_w: usize, + mb_h: usize, + num_mb: usize, + ftype: Type, + ipbs: IPBShuffler, + next_ts: u16, + last_ts: u16, + tsdiff: u16, + has_b: bool, + b_data: Vec<BMB>, + pred_coeffs: Vec<PredCoeffs>, + is_gob: bool, + slice_reset: bool, + may_have_b_frames: bool, + mv_data: Vec<BlockMVInfo>, +} + +#[inline] +fn clip_dc(dc: i16) -> i16 { + if dc < 0 { 0 } + else if dc > 2046 { 2046 } + else { (dc + 1) & !1 } +} + +#[inline] +fn clip_ac(ac: i16) -> i16 { + if ac < -2048 { -2048 } + else if ac > 2047 { 2047 } + else { ac } +} + +#[allow(dead_code)] +impl H263BaseDecoder { + pub fn new_with_opts(is_gob: bool, slice_reset: bool, may_have_b_frames: bool) -> Self { + H263BaseDecoder{ + w: 0, h: 0, mb_w: 0, mb_h: 0, num_mb: 0, + ftype: Type::Special, + ipbs: IPBShuffler::new(), + last_ts: 0, next_ts: 0, tsdiff: 0, + has_b: false, b_data: Vec::new(), + pred_coeffs: Vec::new(), + is_gob, slice_reset, + may_have_b_frames, + mv_data: Vec::new(), + } + } + pub fn new(is_gob: bool) -> Self { + Self::new_with_opts(is_gob, true, false) + } + pub fn new_b_frames(is_gob: bool) -> Self { + Self::new_with_opts(is_gob, true, true) + } + + pub fn is_intra(&self) -> bool { self.ftype == Type::I } + pub fn get_frame_type(&self) -> FrameType { + match self.ftype { + Type::I => FrameType::I, + Type::P => FrameType::P, + Type::B => FrameType::B, + Type::PB => FrameType::P, + Type::Skip => FrameType::Skip, + Type::Special => FrameType::Skip, + } + } + pub fn get_dimensions(&self) -> (usize, usize) { (self.w, self.h) } + + pub fn parse_frame(&mut self, bd: &mut BlockDecoder, bdsp: &BlockDSP) -> DecoderResult<NABufferType> { + let pinfo = bd.decode_pichdr()?; + let mut mvi = MVInfo::new(); + let mut mvi2 = MVInfo::new(); + let mut cbpi = CBPInfo::new(); + +//todo handle res change + self.w = pinfo.w; + self.h = pinfo.h; + self.mb_w = (pinfo.w + 15) >> 4; + self.mb_h = (pinfo.h + 15) >> 4; + self.num_mb = self.mb_w * self.mb_h; + self.ftype = pinfo.mode; + self.has_b = pinfo.is_pb(); + + if self.has_b { + self.mv_data.truncate(0); + } + + let save_b_data = pinfo.mode.is_ref() && self.may_have_b_frames; + if save_b_data { + self.mv_data.truncate(0); + } + let is_b = pinfo.mode == Type::B; + + let tsdiff = if pinfo.is_pb() { pinfo.ts.wrapping_sub(self.last_ts) >> 1 } + else { self.last_ts.wrapping_sub(self.next_ts) >> 1 }; + let bsdiff = if pinfo.is_pb() { (pinfo.get_pbinfo().get_trb() as u16) << 7 } + else { pinfo.ts.wrapping_sub(self.next_ts) >> 1 }; + + let fmt = formats::YUV420_FORMAT; + let vinfo = NAVideoInfo::new(self.w, self.h, false, fmt); + let bufinfo = alloc_video_buffer(vinfo, 4)?; + let mut buf = bufinfo.get_vbuf().unwrap(); + + let mut slice = if self.is_gob { + SliceInfo::get_default_slice(&pinfo) + } else { + bd.decode_slice_header(&pinfo)? + }; + mvi.reset(self.mb_w, 0, pinfo.get_mvmode()); + if is_b || pinfo.is_pb() { + mvi2.reset(self.mb_w, 0, pinfo.get_mvmode()); + } + cbpi.reset(self.mb_w); + + let mut blk: [[i16; 64]; 6] = [[0; 64]; 6]; + let mut sstate = SliceState::new(pinfo.mode == Type::I); + let mut mb_pos = 0; + let apply_acpred = (pinfo.mode == Type::I) && pinfo.plusinfo.is_some() && pinfo.plusinfo.unwrap().aic; + if apply_acpred { + self.pred_coeffs.truncate(0); + self.pred_coeffs.resize(self.mb_w * self.mb_h, ZERO_PRED_COEFFS); + } + sstate.quant = slice.quant; + for mb_y in 0..self.mb_h { + for mb_x in 0..self.mb_w { + for i in 0..6 { for j in 0..64 { blk[i][j] = 0; } } + + if slice.is_at_end(mb_pos) || (slice.needs_check() && mb_pos > 0 && bd.is_slice_end()) { + slice = bd.decode_slice_header(&pinfo)?; + if !self.is_gob && self.slice_reset { + mvi.reset(self.mb_w, mb_x, pinfo.get_mvmode()); + if is_b || pinfo.is_pb() { + mvi2.reset(self.mb_w, mb_x, pinfo.get_mvmode()); + } + cbpi.reset(self.mb_w); + sstate.reset_slice(mb_x, mb_y); + sstate.quant = slice.quant; + } + } + + let binfo = bd.decode_block_header(&pinfo, &slice, &sstate)?; + let cbp = binfo.get_cbp(); + cbpi.set_cbp(mb_x, cbp); + cbpi.set_q(mb_x, binfo.get_q()); + sstate.quant = binfo.get_q(); + if binfo.is_intra() { + if save_b_data { + self.mv_data.push(BlockMVInfo::Intra); + } + for i in 0..6 { + bd.decode_block_intra(&binfo, &sstate, binfo.get_q(), i, (cbp & (1 << (5 - i))) != 0, &mut blk[i])?; + if apply_acpred && (binfo.acpred != ACPredMode::None) { + let has_b = (i == 1) || (i == 3) || !sstate.first_mb; + let has_a = (i == 2) || (i == 3) || !sstate.first_line; + let (b_mb, b_blk) = if has_b { + if (i == 1) || (i == 3) { + (mb_pos, i - 1) + } else if i < 4 { + (mb_pos - 1, i + 1) + } else { + (mb_pos - 1, i) + } + } else { (0, 0) }; + let (a_mb, a_blk) = if has_a { + if (i == 2) || (i == 3) { + (mb_pos, i - 2) + } else if i < 4 { + (mb_pos - self.mb_w, i + 2) + } else { + (mb_pos - self.mb_w, i) + } + } else { (0, 0) }; + match binfo.acpred { + ACPredMode::DC => { + let dc; + if has_a && has_b { + dc = (self.pred_coeffs[b_mb].hor[b_blk][0] + self.pred_coeffs[a_mb].ver[a_blk][0]) / 2; + } else if has_a { + dc = self.pred_coeffs[a_mb].ver[a_blk][0]; + } else if has_b { + dc = self.pred_coeffs[b_mb].hor[b_blk][0]; + } else { + dc = 1024; + } + blk[i][0] = clip_dc(blk[i][0] + dc); + }, + ACPredMode::Hor => { + if has_b { + for k in 0..8 { + blk[i][k * 8] += self.pred_coeffs[b_mb].hor[b_blk][k]; + } + for k in 1..8 { + blk[i][k * 8] = clip_ac(blk[i][k * 8]); + } + } else { + blk[i][0] += 1024; + } + blk[i][0] = clip_dc(blk[i][0]); + }, + ACPredMode::Ver => { + if has_a { + for k in 0..8 { + blk[i][k] += self.pred_coeffs[a_mb].ver[a_blk][k]; + } + for k in 1..8 { + blk[i][k] = clip_ac(blk[i][k]); + } + } else { + blk[i][0] += 1024; + } + blk[i][0] = clip_dc(blk[i][0]); + }, + ACPredMode::None => {}, + }; + for t in 0..8 { self.pred_coeffs[mb_pos].hor[i][t] = blk[i][t * 8]; } + for t in 0..8 { self.pred_coeffs[mb_pos].ver[i][t] = blk[i][t]; } + } + bdsp.idct(&mut blk[i]); + } + blockdsp::put_blocks(&mut buf, mb_x, mb_y, &blk); + mvi.set_zero_mv(mb_x); + if is_b { + mvi2.set_zero_mv(mb_x); + } else if pinfo.is_pb() { + mvi2.predict(mb_x, 0, false, binfo.get_mv2(0), sstate.first_line, sstate.first_mb); + } + } else if (binfo.mode != Type::B) && !binfo.is_skipped() { + if binfo.get_num_mvs() == 1 { + let mv = mvi.predict(mb_x, 0, false, binfo.get_mv(0), sstate.first_line, sstate.first_mb); + if save_b_data { + self.mv_data.push(BlockMVInfo::Inter_1MV(mv)); + } + if let Some(ref srcbuf) = self.ipbs.get_lastref() { + bdsp.copy_blocks(&mut buf, srcbuf, mb_x * 16, mb_y * 16, 16, 16, mv); + } + if pinfo.is_pb() { + mvi2.predict(mb_x, 0, false, binfo.get_mv(0), sstate.first_line, sstate.first_mb); + } + } else { + let mut mv: [MV; 4] = [ZERO_MV, ZERO_MV, ZERO_MV, ZERO_MV]; + for blk_no in 0..4 { + mv[blk_no] = mvi.predict(mb_x, blk_no, true, binfo.get_mv(blk_no), sstate.first_line, sstate.first_mb); + if let Some(ref srcbuf) = self.ipbs.get_lastref() { + bdsp.copy_blocks(&mut buf, srcbuf, + mb_x * 16 + (blk_no & 1) * 8, + mb_y * 16 + (blk_no & 2) * 4, 8, 8, mv[blk_no]); + } + } + if pinfo.is_pb() { + for blk_no in 0..4 { + mvi2.predict(mb_x, blk_no, true, binfo.get_mv(blk_no), sstate.first_line, sstate.first_mb); + } + } + if save_b_data { + self.mv_data.push(BlockMVInfo::Inter_4MV(mv)); + } + } + for i in 0..6 { + bd.decode_block_inter(&binfo, &sstate, binfo.get_q(), i, ((cbp >> (5 - i)) & 1) != 0, &mut blk[i])?; + bdsp.idct(&mut blk[i]); + } + blockdsp::add_blocks(&mut buf, mb_x, mb_y, &blk); + if is_b && !pinfo.is_pb() { + mvi2.set_zero_mv(mb_x); + } + } else if binfo.mode != Type::B { + self.mv_data.push(BlockMVInfo::Inter_1MV(ZERO_MV)); + mvi.set_zero_mv(mb_x); + if is_b || pinfo.is_pb() { + mvi2.set_zero_mv(mb_x); + } + if let Some(ref srcbuf) = self.ipbs.get_lastref() { + bdsp.copy_blocks(&mut buf, srcbuf, mb_x * 16, mb_y * 16, 16, 16, ZERO_MV); + } + } else { + let ref_mv_info = self.mv_data[mb_pos]; + let has_fwd = binfo.get_num_mvs() > 0; + let has_bwd = binfo.get_num_mvs2() > 0; +//todo refactor + if has_fwd || has_bwd { + let fwd_mv; + if has_fwd { + fwd_mv = mvi.predict(mb_x, 0, false, binfo.get_mv(0), sstate.first_line, sstate.first_mb); + } else { + fwd_mv = ZERO_MV; + mvi.set_zero_mv(mb_x); + } + let bwd_mv; + if has_bwd { + bwd_mv = mvi2.predict(mb_x, 0, false, binfo.get_mv2(0), sstate.first_line, sstate.first_mb); + } else { + bwd_mv = ZERO_MV; + mvi2.set_zero_mv(mb_x); + } + if let (Some(ref fwd_buf), Some(ref bck_buf)) = (self.ipbs.get_nextref(), self.ipbs.get_lastref()) { + if has_fwd && has_bwd { + bdsp.copy_blocks(&mut buf, fwd_buf, mb_x * 16, mb_y * 16, 16, 16, fwd_mv); + bdsp.avg_blocks (&mut buf, bck_buf, mb_x * 16, mb_y * 16, 16, 16, bwd_mv); + } else if has_fwd { + bdsp.copy_blocks(&mut buf, fwd_buf, mb_x * 16, mb_y * 16, 16, 16, fwd_mv); + } else { + bdsp.copy_blocks(&mut buf, bck_buf, mb_x * 16, mb_y * 16, 16, 16, bwd_mv); + } + } + } else { + if let BlockMVInfo::Inter_4MV(mvs) = ref_mv_info { + for blk_no in 0..4 { + let ref_mv = mvs[blk_no]; + let ref_mv_fwd = ref_mv.scale(bsdiff, tsdiff); + let ref_mv_bwd = ref_mv - ref_mv_fwd; + let xoff = mb_x * 16 + (blk_no & 1) * 8; + let yoff = mb_y * 16 + (blk_no & 2) * 4; + if let (Some(ref fwd_buf), Some(ref bck_buf)) = (self.ipbs.get_nextref(), self.ipbs.get_lastref()) { + bdsp.copy_blocks(&mut buf, fwd_buf, xoff, yoff, 8, 8, ref_mv_fwd); + bdsp.avg_blocks (&mut buf, bck_buf, xoff, yoff, 8, 8, ref_mv_bwd); + } + } + } else { + let ref_mv = if let BlockMVInfo::Inter_1MV(mv_) = ref_mv_info { mv_ } else { ZERO_MV }; + let ref_mv_fwd = ref_mv.scale(bsdiff, tsdiff); + let ref_mv_bwd = MV::b_sub(ref_mv, ref_mv_fwd, ZERO_MV, bsdiff, tsdiff); + + if let (Some(ref fwd_buf), Some(ref bck_buf)) = (self.ipbs.get_nextref(), self.ipbs.get_lastref()) { + bdsp.copy_blocks(&mut buf, fwd_buf, mb_x * 16, mb_y * 16, 16, 16, ref_mv_fwd); + bdsp.avg_blocks (&mut buf, bck_buf, mb_x * 16, mb_y * 16, 16, 16, ref_mv_bwd); + } + } + mvi.set_zero_mv(mb_x); + mvi2.set_zero_mv(mb_x); + } + if cbp != 0 { + for i in 0..6 { + bd.decode_block_inter(&binfo, &sstate, binfo.get_q(), i, ((cbp >> (5 - i)) & 1) != 0, &mut blk[i])?; + bdsp.idct(&mut blk[i]); + } + blockdsp::add_blocks(&mut buf, mb_x, mb_y, &blk); + } + } + if pinfo.is_pb() { + let mut b_mb = BMB::new(); + let cbp = binfo.get_cbp_b(); + let bq = (((pinfo.get_pbinfo().get_dbquant() + 5) as u16) * (binfo.get_q() as u16)) >> 2; + let bquant; + if bq < 1 { bquant = 1; } + else if bq > 31 { bquant = 31; } + else { bquant = bq as u8; } + + b_mb.cbp = cbp; + for i in 0..6 { + bd.decode_block_inter(&binfo, &sstate, bquant, i, (cbp & (1 << (5 - i))) != 0, &mut b_mb.blk[i])?; + bdsp.idct(&mut b_mb.blk[i]); + } + + let is_fwd = binfo.is_b_fwd(); + b_mb.fwd = is_fwd; + if binfo.get_num_mvs() != 4 { + let ref_mv = mvi2.get_mv(mb_x, 0); + let b_mv = if binfo.is_intra() { binfo.get_mv2(1) } else { binfo.get_mv2(0) }; + let src_mv = if is_fwd { ZERO_MV } else { ref_mv.scale(bsdiff, tsdiff) }; + let mv_f = MV::add_umv(src_mv, b_mv, pinfo.get_mvmode()); + let mv_b = MV::b_sub(ref_mv, mv_f, b_mv, bsdiff, tsdiff); + b_mb.mv_f[0] = mv_f; + b_mb.mv_b[0] = mv_b; + b_mb.num_mv = 1; + } else { + for blk_no in 0..4 { + let src_mv = if is_fwd { ZERO_MV } else { mvi2.get_mv(mb_x, blk_no).scale(bsdiff, tsdiff) }; + let mv_f = MV::add_umv(src_mv, binfo.get_mv2(0), pinfo.get_mvmode()); + let mv_b = MV::b_sub(mvi2.get_mv(mb_x, blk_no), mv_f, binfo.get_mv2(0), bsdiff, tsdiff); + b_mb.mv_f[blk_no] = mv_f; + b_mb.mv_b[blk_no] = mv_b; + } + b_mb.num_mv = 4; + } + self.b_data.push(b_mb); + } + sstate.next_mb(); + mb_pos += 1; + } + if let Some(plusinfo) = pinfo.plusinfo { + if plusinfo.deblock { + bdsp.filter_row(&mut buf, mb_y, self.mb_w, &cbpi); + } + } + mvi.update_row(); + if is_b || pinfo.is_pb() { + mvi2.update_row(); + } + cbpi.update_row(); + sstate.new_row(); + } + + if pinfo.mode.is_ref() { + self.ipbs.add_frame(buf); + self.next_ts = self.last_ts; + self.last_ts = pinfo.ts; + self.tsdiff = tsdiff; + } + + Ok(bufinfo) + } + pub fn flush(&mut self) { + self.ipbs.clear(); + } + + pub fn get_bframe(&mut self, bdsp: &BlockDSP) -> DecoderResult<NABufferType> { + if !self.has_b || self.ipbs.get_lastref().is_none() || self.ipbs.get_nextref().is_none() { + return Err(DecoderError::MissingReference); + } + self.has_b = false; + + let fmt = formats::YUV420_FORMAT; + let vinfo = NAVideoInfo::new(self.w, self.h, false, fmt); + let bufinfo = alloc_video_buffer(vinfo, 4)?; + let mut b_buf = bufinfo.get_vbuf().unwrap(); + + if let (Some(ref bck_buf), Some(ref fwd_buf)) = (self.ipbs.get_nextref(), self.ipbs.get_lastref()) { + recon_b_frame(&mut b_buf, fwd_buf, bck_buf, self.mb_w, self.mb_h, self.b_data.as_slice(), bdsp); + } + + self.b_data.truncate(0); + Ok(bufinfo) + } +} + +fn recon_b_frame(b_buf: &mut NAVideoBuffer<u8>, bck_buf: &NAVideoBuffer<u8>, fwd_buf: &NAVideoBuffer<u8>, + mb_w: usize, mb_h: usize, b_data: &[BMB], bdsp: &BlockDSP) { + let mut cbpi = CBPInfo::new(); + let mut cur_mb = 0; + cbpi.reset(mb_w); + for mb_y in 0..mb_h { + for mb_x in 0..mb_w { + let num_mv = b_data[cur_mb].num_mv; + let is_fwd = b_data[cur_mb].fwd; + let cbp = b_data[cur_mb].cbp; + cbpi.set_cbp(mb_x, cbp); + if num_mv == 1 { + bdsp.copy_blocks(b_buf, fwd_buf, mb_x * 16, mb_y * 16, 16, 16, b_data[cur_mb].mv_b[0]); + if !is_fwd { + bdsp.avg_blocks(b_buf, bck_buf, mb_x * 16, mb_y * 16, 16, 16, b_data[cur_mb].mv_f[0]); + } + } else { + for blk_no in 0..4 { + let xpos = mb_x * 16 + (blk_no & 1) * 8; + let ypos = mb_y * 16 + (blk_no & 2) * 4; + bdsp.copy_blocks(b_buf, fwd_buf, xpos, ypos, 8, 8, b_data[cur_mb].mv_b[blk_no]); + if !is_fwd { + bdsp.avg_blocks(b_buf, bck_buf, xpos, ypos, 8, 8, b_data[cur_mb].mv_f[blk_no]); + } + } + } + if cbp != 0 { + blockdsp::add_blocks(b_buf, mb_x, mb_y, &b_data[cur_mb].blk); + } + cur_mb += 1; + } + cbpi.update_row(); + } +} diff --git a/nihav-codec-support/src/codecs/h263/mod.rs b/nihav-codec-support/src/codecs/h263/mod.rs new file mode 100644 index 0000000..33d71ba --- /dev/null +++ b/nihav-codec-support/src/codecs/h263/mod.rs @@ -0,0 +1,392 @@ +use nihav_core::codecs::DecoderResult; +use super::{MV, ZERO_MV}; +use nihav_core::frame::NAVideoBuffer; + +#[allow(clippy::many_single_char_names)] +pub mod code; +pub mod data; +#[allow(clippy::needless_range_loop)] +pub mod decoder; + +pub trait BlockDecoder { + fn decode_pichdr(&mut self) -> DecoderResult<PicInfo>; + fn decode_slice_header(&mut self, pinfo: &PicInfo) -> DecoderResult<SliceInfo>; + fn decode_block_header(&mut self, pinfo: &PicInfo, sinfo: &SliceInfo, sstate: &SliceState) -> DecoderResult<BlockInfo>; + fn decode_block_intra(&mut self, info: &BlockInfo, sstate: &SliceState, quant: u8, no: usize, coded: bool, blk: &mut [i16; 64]) -> DecoderResult<()>; + fn decode_block_inter(&mut self, info: &BlockInfo, sstate: &SliceState, quant: u8, no: usize, coded: bool, blk: &mut [i16; 64]) -> DecoderResult<()>; + fn is_slice_end(&mut self) -> bool; +} + +pub trait BlockDSP { + fn idct(&self, blk: &mut [i16; 64]); + fn copy_blocks(&self, dst: &mut NAVideoBuffer<u8>, src: &NAVideoBuffer<u8>, xpos: usize, ypos: usize, w: usize, h: usize, mv: MV); + fn avg_blocks(&self, dst: &mut NAVideoBuffer<u8>, src: &NAVideoBuffer<u8>, xpos: usize, ypos: usize, w: usize, h: usize, mv: MV); + fn filter_row(&self, buf: &mut NAVideoBuffer<u8>, mb_y: usize, mb_w: usize, cbpi: &CBPInfo); +} + +#[allow(dead_code)] +#[derive(Debug,Clone,Copy,PartialEq)] +pub enum Type { + I, P, PB, Skip, B, Special +} + +impl Type { + pub fn is_ref(self) -> bool { + match self { + Type::I | Type::P | Type::PB => true, + _ => false, + } + } +} + +#[allow(dead_code)] +#[derive(Debug,Clone,Copy)] +pub struct PBInfo { + trb: u8, + dbquant: u8, + improved: bool, +} + +impl PBInfo { + pub fn new(trb: u8, dbquant: u8, improved: bool) -> Self { + PBInfo{ trb, dbquant, improved } + } + pub fn get_trb(self) -> u8 { self.trb } + pub fn get_dbquant(self) -> u8 { self.dbquant } + pub fn is_improved(self) -> bool { self.improved } +} + +#[allow(dead_code)] +#[derive(Debug,Clone,Copy)] +pub struct PicInfo { + pub w: usize, + pub h: usize, + pub mode: Type, + pub mvmode: MVMode, + pub umv: bool, + pub apm: bool, + pub quant: u8, + pub pb: Option<PBInfo>, + pub ts: u16, + pub plusinfo: Option<PlusInfo>, +} + +#[allow(dead_code)] +impl PicInfo { + pub fn new(w: usize, h: usize, mode: Type, mvmode: MVMode, umv: bool, apm: bool, quant: u8, ts: u16, pb: Option<PBInfo>, plusinfo: Option<PlusInfo>) -> Self { + PicInfo { + w, h, mode, mvmode, + umv, apm, quant, + pb, ts, plusinfo + } + } + pub fn get_width(&self) -> usize { self.w } + pub fn get_height(&self) -> usize { self.h } + pub fn get_mode(&self) -> Type { self.mode } + pub fn get_quant(&self) -> u8 { self.quant } + pub fn get_apm(&self) -> bool { self.apm } + pub fn is_pb(&self) -> bool { self.pb.is_some() } + pub fn is_ipb(&self) -> bool { + if let Some(ref pbi) = self.pb { + pbi.is_improved() + } else { + false + } + } + pub fn get_ts(&self) -> u16 { self.ts } + pub fn get_pbinfo(&self) -> PBInfo { self.pb.unwrap() } + pub fn get_plusifo(&self) -> Option<PlusInfo> { self.plusinfo } + pub fn get_mvmode(&self) -> MVMode { self.mvmode } +} + +#[allow(dead_code)] +#[derive(Debug,Clone,Copy)] +pub struct PlusInfo { + pub aic: bool, + pub deblock: bool, + pub aiv_mode: bool, + pub mq_mode: bool, +} + +impl PlusInfo { + pub fn new(aic: bool, deblock: bool, aiv_mode: bool, mq_mode: bool) -> Self { + PlusInfo { aic, deblock, aiv_mode, mq_mode } + } +} + +#[allow(dead_code)] +#[derive(Debug,Clone,Copy)] +pub struct SliceInfo { + pub mb_x: usize, + pub mb_y: usize, + pub mb_end: usize, + pub quant: u8, +} + +#[allow(dead_code)] +#[derive(Debug,Clone,Copy)] +pub struct SliceState { + pub is_iframe: bool, + pub mb_x: usize, + pub mb_y: usize, + pub first_line: bool, + pub first_mb: bool, + pub slice_mb_x: usize, + pub slice_mb_y: usize, + pub quant: u8, +} + +const SLICE_NO_END: usize = 99999999; + +impl SliceInfo { + pub fn new(mb_x: usize, mb_y: usize, mb_end: usize, quant: u8) -> Self { + SliceInfo{ mb_x, mb_y, mb_end, quant } + } + pub fn new_gob(mb_x: usize, mb_y: usize, quant: u8) -> Self { + SliceInfo{ mb_x, mb_y, mb_end: SLICE_NO_END, quant } + } + pub fn get_default_slice(pinfo: &PicInfo) -> Self { + SliceInfo{ mb_x: 0, mb_y: 0, mb_end: SLICE_NO_END, quant: pinfo.get_quant() } + } + pub fn get_quant(&self) -> u8 { self.quant } + pub fn is_at_end(&self, mb_pos: usize) -> bool { self.mb_end == mb_pos } + pub fn needs_check(&self) -> bool { self.mb_end == SLICE_NO_END } +} + +impl SliceState { + pub fn new(is_iframe: bool) -> Self { + SliceState { + is_iframe, mb_x: 0, mb_y: 0, first_line: true, first_mb: true, + slice_mb_x: 0, slice_mb_y: 0, quant: 0 + } + } + pub fn next_mb(&mut self) { + self.mb_x += 1; self.first_mb = false; + if self.mb_x >= self.slice_mb_x && self.mb_y > self.slice_mb_y { + self.first_line = false; + } + } + pub fn new_row(&mut self) { + self.mb_x = 0; self.mb_y += 1; + if self.mb_x >= self.slice_mb_x && self.mb_y > self.slice_mb_y { + self.first_line = false; + } + self.first_mb = true; + } + pub fn reset_slice(&mut self, smb_x: usize, smb_y: usize) { + self.slice_mb_x = smb_x; + self.slice_mb_y = smb_y; + self.first_line = true; + self.first_mb = true; + } +} + +#[derive(Debug,Clone,Copy)] +pub struct BlockInfo { + pub intra: bool, + pub skip: bool, + pub mode: Type, + pub cbp: u8, + pub q: u8, + pub mv: [MV; 4], + pub num_mv: usize, + pub bpart: bool, + pub b_cbp: u8, + pub mv2: [MV; 2], + pub num_mv2: usize, + pub fwd: bool, + pub acpred: ACPredMode, +} + +#[allow(dead_code)] +#[derive(Debug,Clone,Copy)] +pub struct BBlockInfo { + present: bool, + cbp: u8, + num_mv: usize, + fwd: bool, +} + +#[allow(non_camel_case_types)] +#[derive(Debug,Clone,Copy)] +pub enum BlockMVInfo { + Intra, + Inter_1MV(MV), + Inter_4MV([MV; 4]), +} + +#[allow(dead_code)] +#[derive(Debug,Clone,Copy,PartialEq)] +pub enum ACPredMode { + None, + DC, + Ver, + Hor, +} + +#[allow(dead_code)] +impl BlockInfo { + pub fn new(mode: Type, cbp: u8, q: u8) -> Self { + BlockInfo { + intra: mode == Type::I, + skip: (cbp == 0) && (mode != Type::I), + mode, + cbp, + q, + mv: [MV::new(0, 0), MV::new(0, 0), MV::new(0, 0), MV::new(0, 0)], + num_mv: 0, + bpart: false, + b_cbp: 0, + mv2: [ZERO_MV, ZERO_MV], + num_mv2: 0, + fwd: false, + acpred: ACPredMode::None, + } + } + pub fn is_intra(&self) -> bool { self.intra } + pub fn is_skipped(&self) -> bool { self.skip } + pub fn get_mode(&self) -> Type { self.mode } + pub fn get_cbp(&self) -> u8 { self.cbp } + pub fn get_q(&self) -> u8 { self.q } + pub fn get_num_mvs(&self) -> usize { self.num_mv } + pub fn get_mv(&self, idx: usize) -> MV { self.mv[idx] } + pub fn has_b_part(&self) -> bool { self.bpart } + pub fn get_cbp_b(&self) -> u8 { self.b_cbp } + pub fn get_num_mvs2(&self) -> usize { self.num_mv2 } + pub fn get_mv2(&self, idx: usize) -> MV { self.mv2[idx] } + pub fn set_mv(&mut self, mvs: &[MV]) { + if !mvs.is_empty() { self.skip = false; } + let mut mv_arr: [MV; 4] = [MV::new(0, 0), MV::new(0, 0), MV::new(0, 0), MV::new(0, 0)]; + for i in 0..mvs.len() { mv_arr[i] = mvs[i]; } + self.mv = mv_arr; + self.num_mv = mvs.len(); + } + pub fn set_bpart(&mut self, bbinfo: BBlockInfo) { + self.bpart = bbinfo.present; + self.b_cbp = bbinfo.cbp; + self.fwd = bbinfo.fwd; + self.num_mv2 = bbinfo.get_num_mv(); + } + pub fn set_b_mv(&mut self, mvs: &[MV]) { + if !mvs.is_empty() { self.skip = false; } + let mut mv_arr: [MV; 2] = [ZERO_MV, ZERO_MV]; + for i in 0..mvs.len() { mv_arr[i] = mvs[i]; } + self.mv2 = mv_arr; + self.num_mv2 = mvs.len(); + } + pub fn is_b_fwd(&self) -> bool { self.fwd } + pub fn set_acpred(&mut self, acpred: ACPredMode) { self.acpred = acpred } + pub fn get_acpred(&self) -> ACPredMode { self.acpred } +} + +impl BBlockInfo { + pub fn new(present: bool, cbp: u8, num_mv: usize, fwd: bool) -> Self { + BBlockInfo { + present, + cbp, + num_mv, + fwd, + } + } + pub fn get_num_mv(&self) -> usize { self.num_mv } +} + +#[derive(Debug,Clone,Copy)] +pub enum MVMode { + Old, + Long, + UMV, +} + +pub trait H263MVTrait { + fn add_umv(pred_mv: MV, add: MV, mvmode: MVMode) -> MV; + fn scale(&self, trb: u16, trd: u16) -> MV; + fn b_sub(pvec: MV, fwdvec: MV, bvec: MV, trb: u16, trd: u16) -> MV; +} + +impl H263MVTrait for MV { + fn add_umv(pred_mv: MV, add: MV, mvmode: MVMode) -> MV { + let mut new_mv = pred_mv + add; + match mvmode { + MVMode::Old => { + if new_mv.x >= 64 { new_mv.x -= 64; } + else if new_mv.x <= -64 { new_mv.x += 64; } + if new_mv.y >= 64 { new_mv.y -= 64; } + else if new_mv.y <= -64 { new_mv.y += 64; } + }, + MVMode::Long => { + if new_mv.x > 31 { new_mv.x -= 64; } + else if new_mv.x < -32 { new_mv.x += 64; } + if new_mv.y > 31 { new_mv.y -= 64; } + else if new_mv.y < -32 { new_mv.y += 64; } + }, + MVMode::UMV => { + if pred_mv.x > 32 && new_mv.x > 63 { new_mv.x -= 64; } + if pred_mv.x < -31 && new_mv.x < -63 { new_mv.x += 64; } + if pred_mv.y > 32 && new_mv.y > 63 { new_mv.y -= 64; } + if pred_mv.y < -31 && new_mv.y < -63 { new_mv.y += 64; } + }, + }; + new_mv + } + fn scale(&self, trb: u16, trd: u16) -> MV { + if (trd == 0) || (trb == 0) { + ZERO_MV + } else { + MV { x: (((self.x as i32) * (trb as i32)) / (trd as i32)) as i16, y: (((self.y as i32) * (trb as i32)) / (trd as i32)) as i16 } + } + } + fn b_sub(pvec: MV, fwdvec: MV, bvec: MV, trb: u16, trd: u16) -> MV { + let bscale = (trb as i32) - (trd as i32); + let x = if bvec.x != 0 { fwdvec.x - pvec.x } else if trd != 0 { (bscale * (pvec.x as i32) / (trd as i32)) as i16 } else { 0 }; + let y = if bvec.y != 0 { fwdvec.y - pvec.y } else if trd != 0 { (bscale * (pvec.y as i32) / (trd as i32)) as i16 } else { 0 }; + MV { x, y } + } +} + +#[allow(dead_code)] +pub struct CBPInfo { + cbp: Vec<u8>, + q: Vec<u8>, + mb_w: usize, +} + +impl CBPInfo { + fn new() -> Self { CBPInfo{ cbp: Vec::new(), q: Vec::new(), mb_w: 0 } } + fn reset(&mut self, mb_w: usize) { + self.mb_w = mb_w; + self.cbp.truncate(0); + self.cbp.resize(self.mb_w * 2, 0); + self.q.truncate(0); + self.q.resize(self.mb_w * 2, 0); + } + fn update_row(&mut self) { + for i in 0..self.mb_w { + self.cbp[i] = self.cbp[self.mb_w + i]; + self.q[i] = self.q[self.mb_w + i]; + } + } + fn set_cbp(&mut self, mb_x: usize, cbp: u8) { + self.cbp[self.mb_w + mb_x] = cbp; + } + fn set_q(&mut self, mb_x: usize, q: u8) { + self.q[self.mb_w + mb_x] = q; + } + pub fn get_q(&self, mb_x: usize) -> u8 { self.q[mb_x] } + pub fn is_coded(&self, mb_x: usize, blk_no: usize) -> bool { + (self.cbp[self.mb_w + mb_x] & (1 << (5 - blk_no))) != 0 + } + pub fn is_coded_top(&self, mb_x: usize, blk_no: usize) -> bool { + let cbp = self.cbp[self.mb_w + mb_x]; + let cbp_top = self.cbp[mb_x]; + match blk_no { + 0 => { (cbp_top & 0b001000) != 0 }, + 1 => { (cbp_top & 0b000100) != 0 }, + 2 => { (cbp & 0b100000) != 0 }, + 3 => { (cbp & 0b010000) != 0 }, + 4 => { (cbp_top & 0b000010) != 0 }, + _ => { (cbp_top & 0b000001) != 0 }, + } + } +} + diff --git a/nihav-codec-support/src/codecs/mod.rs b/nihav-codec-support/src/codecs/mod.rs new file mode 100644 index 0000000..4353ad9 --- /dev/null +++ b/nihav-codec-support/src/codecs/mod.rs @@ -0,0 +1,313 @@ +//! Decoder support functions and definitions. +use std::fmt; +use std::ops::{Add, AddAssign, Sub, SubAssign}; + +pub use nihav_core::frame::*; +use std::mem; + +/// Frame manager for hold-and-modify codecs. +/// +/// This frame manager simplifies frame management for the case when codec decodes new frame by updating parts of the previous frame. +/// +/// # Examples +/// +/// ````norun +/// let mut frame = if is_intra_frame { +/// allocate_video_frame() +/// } else { +/// let ret = shuffler.clone_ref(); +/// if ret.is_none() { +/// return Err(DecodingError::MissingReference); +/// } +/// ret.unwrap() +/// }; +/// // output data into the frame +/// shuffler.add_frame(frame.clone()); // tells frame manager to use the frame as the next reference +/// ```` +#[allow(dead_code)] +pub struct HAMShuffler { + lastframe: Option<NAVideoBufferRef<u8>>, +} + +impl HAMShuffler { + /// Constructs a new instance of frame manager. + #[allow(dead_code)] + pub fn new() -> Self { HAMShuffler { lastframe: None } } + /// Clears the reference. + #[allow(dead_code)] + pub fn clear(&mut self) { self.lastframe = None; } + /// Sets a new frame reference. + #[allow(dead_code)] + pub fn add_frame(&mut self, buf: NAVideoBufferRef<u8>) { + self.lastframe = Some(buf); + } + /// Provides a copy of the reference frame if present or `None` if it is not. + #[allow(dead_code)] + pub 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 + } + } + /// Returns the original saved reference frame or `None` if it is not present. + #[allow(dead_code)] + pub fn get_output_frame(&mut self) -> Option<NAVideoBufferRef<u8>> { + match self.lastframe { + Some(ref frm) => Some(frm.clone()), + None => None, + } + } +} + +impl Default for HAMShuffler { + fn default() -> Self { Self { lastframe: None } } +} + +/// Frame manager for codecs with intra and inter frames. +/// +/// This frame manager simplifies frame management for the case when codec decodes new frame using previous frame as source of some data. +/// +/// # Examples +/// +/// ````norun +/// let mut frame = allocate_video_frame(); +/// if is_inter_frame { +/// let ret = shuffler.get_ref(); +/// if ret.is_none() { +/// return Err(DecodingError::MissingReference); +/// } +/// let ref_frame = ret.unwrap(); +/// // keep decoding using data from ref_frame +/// } +/// shuffler.add_frame(frame.clone()); // tells frame manager to use the frame as the next reference +/// ```` +#[allow(dead_code)] +pub struct IPShuffler { + lastframe: Option<NAVideoBufferRef<u8>>, +} + +impl IPShuffler { + /// Constructs a new instance of frame manager. + #[allow(dead_code)] + pub fn new() -> Self { IPShuffler { lastframe: None } } + /// Clears the reference. + #[allow(dead_code)] + pub fn clear(&mut self) { self.lastframe = None; } + /// Sets a new frame reference. + #[allow(dead_code)] + pub fn add_frame(&mut self, buf: NAVideoBufferRef<u8>) { + self.lastframe = Some(buf); + } + /// Returns the original saved reference frame or `None` if it is not present. + #[allow(dead_code)] + pub fn get_ref(&mut self) -> Option<NAVideoBufferRef<u8>> { + if let Some(ref frm) = self.lastframe { + Some(frm.clone()) + } else { + None + } + } +} + +impl Default for IPShuffler { + fn default() -> Self { Self { lastframe: None } } +} + +/// Frame manager for codecs with I-, P- and B-frames. +/// +/// This frame manager simplifies frame management for the case when codec uses I/P/B frame scheme. +/// +/// # Examples +/// +/// ````norun +/// let mut frame = allocate_video_frame(); +/// for mb in all_macroblocks { +/// // decode macroblock type +/// match mb_type { +/// MBType::Inter => { +/// do_mc(&mut frame, shuffler.get_lastref().unwrap()); +/// }, +/// MBType::BForward => { +/// do_mc(&mut frame, shuffler.get_b_fwdref().unwrap()); +/// }, +/// MBType::BBackward => { +/// do_mc(&mut frame, shuffler.get_b_bwdref().unwrap()); +/// }, +/// // handle the rest of cases +/// }; +/// if is_random_access_frame { +/// shuffler.clear(); // remove all saved references +/// } +/// if is_intra_frame || is_p_frame { +/// shuffler.add_frame(frame.clone()); // tells frame manager to use the frame as the next reference +/// } +/// ```` +#[allow(dead_code)] +pub struct IPBShuffler { + lastframe: Option<NAVideoBufferRef<u8>>, + nextframe: Option<NAVideoBufferRef<u8>>, +} + +impl IPBShuffler { + /// Constructs a new instance of frame manager. + #[allow(dead_code)] + pub fn new() -> Self { IPBShuffler { lastframe: None, nextframe: None } } + /// Clears the reference. + #[allow(dead_code)] + pub fn clear(&mut self) { self.lastframe = None; self.nextframe = None; } + /// Sets a new frame reference. + #[allow(dead_code)] + pub fn add_frame(&mut self, buf: NAVideoBufferRef<u8>) { + mem::swap(&mut self.lastframe, &mut self.nextframe); + self.lastframe = Some(buf); + } + /// Returns the previous reference frame or `None` if it is not present. + #[allow(dead_code)] + pub fn get_lastref(&mut self) -> Option<NAVideoBufferRef<u8>> { + if let Some(ref frm) = self.lastframe { + Some(frm.clone()) + } else { + None + } + } + /// Returns second last reference frame or `None` if it is not present. + #[allow(dead_code)] + pub fn get_nextref(&mut self) -> Option<NAVideoBufferRef<u8>> { + if let Some(ref frm) = self.nextframe { + Some(frm.clone()) + } else { + None + } + } + /// Returns the temporally following reference for B-frame or `None` if it is not present. + #[allow(dead_code)] + pub fn get_b_fwdref(&mut self) -> Option<NAVideoBufferRef<u8>> { + if let Some(ref frm) = self.nextframe { + Some(frm.clone()) + } else { + None + } + } + /// Returns the temporally preceeding reference for B-frame or `None` if it is not present. + #[allow(dead_code)] + pub fn get_b_bwdref(&mut self) -> Option<NAVideoBufferRef<u8>> { + if let Some(ref frm) = self.lastframe { + Some(frm.clone()) + } else { + None + } + } +} + +impl Default for IPBShuffler { + fn default() -> Self { Self { lastframe: None, nextframe: None } } +} + +/// Motion vector data type. +/// +/// # Examples +/// +/// ``` +/// use nihav_codec_support::codecs::MV; +/// +/// let mv0 = MV::new(1, 3); +/// let mv1 = MV { x: 2, y: 3 }; // choose whatever style you prefer +/// let mv2 = mv1 - mv0; +/// let mv_pred = MV::pred(mv0, mv1, mv2); // get median prediction for the vectors (1, 0) +/// ``` +#[derive(Debug,Clone,Copy,Default,PartialEq)] +pub struct MV { + /// X coordinate of the vector. + pub x: i16, + /// Y coordinate of the vector. + pub y: i16, +} + +#[allow(clippy::many_single_char_names)] +#[allow(clippy::collapsible_if)] +impl MV { + /// Creates a new motion vector instance. + pub fn new(x: i16, y: i16) -> Self { MV{ x, y } } + /// Predicts median from provided motion vectors. + /// + /// Each component of the vector is predicted as the median of corresponding input vector components. + pub fn pred(a: MV, b: MV, c: MV) -> Self { + let x; + if a.x < b.x { + if b.x < c.x { + x = b.x; + } else { + if a.x < c.x { x = c.x; } else { x = a.x; } + } + } else { + if b.x < c.x { + if a.x < c.x { x = a.x; } else { x = c.x; } + } else { + x = b.x; + } + } + let y; + if a.y < b.y { + if b.y < c.y { + y = b.y; + } else { + if a.y < c.y { y = c.y; } else { y = a.y; } + } + } else { + if b.y < c.y { + if a.y < c.y { y = a.y; } else { y = c.y; } + } else { + y = b.y; + } + } + MV { x, y } + } +} + +/// Zero motion vector. +pub const ZERO_MV: MV = MV { x: 0, y: 0 }; + +impl Add for MV { + type Output = MV; + fn add(self, other: MV) -> MV { MV { x: self.x + other.x, y: self.y + other.y } } +} + +impl AddAssign for MV { + fn add_assign(&mut self, other: MV) { self.x += other.x; self.y += other.y; } +} + +impl Sub for MV { + type Output = MV; + fn sub(self, other: MV) -> MV { MV { x: self.x - other.x, y: self.y - other.y } } +} + +impl SubAssign for MV { + fn sub_assign(&mut self, other: MV) { self.x -= other.x; self.y -= other.y; } +} + +impl fmt::Display for MV { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{},{}", self.x, self.y) + } +} + +#[cfg(any(feature="blockdsp"))] +pub mod blockdsp; + +#[cfg(feature="h263")] +pub mod h263; + +/// The common 8x8 zigzag scan. +pub const ZIGZAG: [usize; 64] = [ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63 +]; diff --git a/nihav-codec-support/src/data/mod.rs b/nihav-codec-support/src/data/mod.rs new file mode 100644 index 0000000..35a8973 --- /dev/null +++ b/nihav-codec-support/src/data/mod.rs @@ -0,0 +1,73 @@ +//! Auxiliary data structures for codecs. + +/// Line cache. +/// +/// In the decoding process of many codecs there is a need to store some previously decoded information and only immediate top neighbours are used. +/// This can be done by storing either the full information for the whole frame or just the top line and move information for last decoded row to the top every time when row decoding is done. +/// `GenericCache` implements the second approach. +/// +/// # Examples +/// +/// Create a cache for one line and use top pixel for prediction: +/// ``` +/// use nihav_codec_support::data::GenericCache; +/// +/// # let width = 640; +/// # let height = 480; +/// # let mut dst: Vec<u8> = vec![0; width]; +/// let mut linecache: GenericCache<u8> = GenericCache::new(1, width, 0x80); +/// for _ in 0..height { +/// for x in 0..width { +/// # let delta = 32; +/// dst[x] = linecache.data[linecache.xpos + x - linecache.stride] + delta; +/// } +/// linecache.update_row(); +/// } +/// ``` +pub struct GenericCache<T: Copy> { + /// Number of rows that are processed at once. + pub height: usize, + /// Number of elements in one row. + pub stride: usize, + /// Start of curent data. + pub xpos: usize, + /// Data. + pub data: Vec<T>, + /// Default value to fill the cache with. + pub default: T, +} + +impl<T:Copy> GenericCache<T> { + /// Constructs a new instance of `GenericCache`. + pub fn new(height: usize, stride: usize, default: T) -> Self { + let mut ret = Self { + stride, + height, + xpos: 0, + data: Vec::with_capacity((height + 1) * stride), + default, + }; + ret.reset(); + ret + } + /// Reports the total amount of elements stored. + pub fn full_size(&self) -> usize { self.stride * (self.height + 1) } + /// Resets the cache state. + pub fn reset(&mut self) { + self.data.truncate(0); + let size = self.full_size(); + self.data.resize(size, self.default); + self.xpos = self.stride + 1; + } + /// Updates cache state for the next line. + pub fn update_row(&mut self) { + for i in 0..self.stride { + self.data[i] = self.data[self.height * self.stride + i]; + } + self.data.truncate(self.stride); + let size = self.full_size(); + self.data.resize(size, self.default); + self.xpos = self.stride + 1; + } +} + diff --git a/nihav-codec-support/src/dsp/dct.rs b/nihav-codec-support/src/dsp/dct.rs new file mode 100644 index 0000000..2a74449 --- /dev/null +++ b/nihav-codec-support/src/dsp/dct.rs @@ -0,0 +1,512 @@ +//! Discrete 1-D cosine and sine transforms. +use std::f32::consts; + +/// A list of DCT and DST modes. +#[allow(non_camel_case_types)] +#[derive(Clone,Copy,Debug,PartialEq)] +pub enum DCTMode { + DCT_I, + DCT_II, + DCT_III, + DCT_IV, + DST_I, + DST_II, + DST_III, + DST_IV, +} + +/// DCT/DST working context. +#[allow(dead_code)] +pub struct DCT { + tmp: Vec<f32>, + tab: Vec<f32>, + swaps: Vec<usize>, + perms: Vec<usize>, + mode: DCTMode, + size: usize, + is_pow2: bool, + perm_tab: Vec<usize>, +} + +impl DCT { + /// Constructs a new context for the selected DCT or DST operation. + pub fn new(mode: DCTMode, size: usize) -> Self { + let bits = 31 - (size as u32).leading_zeros(); + let is_pow2 = (size & (size - 1)) == 0; + let mut tmp: Vec<f32>; + let mut swaps: Vec<usize> = Vec::new(); + let mut perms: Vec<usize>; + let mut perm_tab: Vec<usize>; + tmp = Vec::with_capacity(size); + tmp.resize(size, 0.0); + if !is_pow2 { + perms = Vec::new(); + perm_tab = Vec::new(); + } else { + perms = Vec::with_capacity(size); + for i in 0..size { perms.push(swp_idx(i, bits)); } + gen_swaps_for_perm(&mut swaps, &perms); + + perm_tab = Vec::with_capacity(size); + perm_tab.push(0); // padding + perm_tab.push(0); // size = 1 + perm_tab.push(0); // size = 2 + perm_tab.push(1); + for blen in 2..=bits { + let ssize = 1 << blen; + for i in 0..ssize { perm_tab.push(swp_idx(i, blen)); } + } + } + let mut tab: Vec<f32>; + match mode { + DCTMode::DCT_II | + DCTMode::DST_II | + DCTMode::DCT_III | + DCTMode::DST_III | + DCTMode::DCT_IV => { + tab = Vec::with_capacity(size * 2); + tab.push(1.0); // padding + tab.push(0.0); + tab.push((consts::PI / 8.0).sin()); // size = 1 + tab.push((consts::PI / 8.0).cos()); + if bits > 1 { + for blen in 1..=bits { + let tsize = 1 << blen; + let base = consts::PI / ((tsize * 8) as f32); + for i in 0..tsize { + let phi = ((i * 2 + 1) as f32) * base; + tab.push(phi.sin()); + tab.push(phi.cos()); + } + } + } + }, +/* DCTMode::DST_IV => { + },*/ + _ => { tab = Vec::new(); }, + }; + + Self { tmp, tab, mode, size, swaps, perms, is_pow2, perm_tab } + } + fn can_do_fast(&mut self) -> bool { + if !self.is_pow2 { return false; } + match self.mode { + DCTMode::DCT_I | DCTMode::DST_I | DCTMode::DST_IV => false, + _ => true, + } + } + fn inplace_fast_dct(&mut self, dst: &mut [f32]) { + match self.mode { + DCTMode::DCT_II => { + dct_II_inplace(dst, self.size, 1, &self.tab, &self.perm_tab); + }, + DCTMode::DST_II => { + dst_II_inplace(dst, self.size, 1, &self.tab, &self.perm_tab); + }, + DCTMode::DCT_III => { + dct_III_inplace(dst, self.size, 1, &self.tab, &self.perm_tab); + }, + DCTMode::DST_III => { + dst_III_inplace(dst, self.size, 1, &self.tab, &self.perm_tab); + }, + DCTMode::DCT_IV => { + dct_IV_inplace(dst, self.size, 1, &self.tab, &self.perm_tab); + }, + _ => unreachable!(), + }; + } + /// Performs DCT/DST. + pub fn do_dct(&mut self, src: &[f32], dst: &mut [f32]) { + if self.can_do_fast() { + for (i, ni) in self.perms.iter().enumerate() { dst[i] = src[*ni]; } + self.inplace_fast_dct(dst); + } else { + do_ref_dct(self.mode, src, dst, self.size); + } + } + /// Performs inplace DCT/DST. + pub fn do_dct_inplace(&mut self, buf: &mut [f32]) { + if self.can_do_fast() { + swap_buf(buf, &self.swaps); + self.inplace_fast_dct(buf); + } else { + self.tmp.copy_from_slice(&buf[0..self.size]); + do_ref_dct(self.mode, &self.tmp, buf, self.size); + } + } + /// Returns the scale for output normalisation. + pub fn get_scale(&self) -> f32 { + let fsize = self.size as f32; + match self.mode { + DCTMode::DCT_I => 2.0 / (fsize - 1.0), + DCTMode::DST_I => 2.0 / (fsize + 1.0), + DCTMode::DCT_II => 1.0, + DCTMode::DCT_III=> 1.0, + DCTMode::DST_II => 1.0, + DCTMode::DST_III=> 1.0, + DCTMode::DCT_IV => 1.0, + _ => 2.0 / fsize, + } + } +} + +fn reverse_bits(inval: u32) -> u32 { + const REV_TAB: [u8; 16] = [ + 0b0000, 0b1000, 0b0100, 0b1100, 0b0010, 0b1010, 0b0110, 0b1110, + 0b0001, 0b1001, 0b0101, 0b1101, 0b0011, 0b1011, 0b0111, 0b1111, + ]; + + let mut ret = 0; + let mut val = inval; + for _ in 0..8 { + ret = (ret << 4) | (REV_TAB[(val & 0xF) as usize] as u32); + val >>= 4; + } + ret +} + +fn swp_idx(idx: usize, bits: u32) -> usize { + let s = reverse_bits(idx as u32) as usize; + s >> (32 - bits) +} + +fn gen_swaps_for_perm(swaps: &mut Vec<usize>, perms: &[usize]) { + let mut idx_arr: Vec<usize> = Vec::with_capacity(perms.len()); + for i in 0..perms.len() { idx_arr.push(i); } + let mut run_size = 0; + let mut run_pos = 0; + for idx in 0..perms.len() { + if perms[idx] == idx_arr[idx] { + if run_size == 0 { run_pos = idx; } + run_size += 1; + } else { + for i in 0..run_size { + swaps.push(run_pos + i); + } + run_size = 0; + let mut spos = idx + 1; + while idx_arr[spos] != perms[idx] { spos += 1; } + idx_arr[spos] = idx_arr[idx]; + idx_arr[idx] = perms[idx]; + swaps.push(spos); + } + } +} + +fn swap_buf(buf: &mut [f32], swaps: &[usize]) { + for (idx, nidx) in swaps.iter().enumerate() { + if idx != *nidx { + buf.swap(*nidx, idx); + } + } +} + +fn do_ref_dct(mode: DCTMode, src: &[f32], dst: &mut [f32], size: usize) { + match mode { + DCTMode::DCT_I => dct_I_ref(src, dst, size), + DCTMode::DST_I => dst_I_ref(src, dst, size), + DCTMode::DCT_II => dct_II_ref(src, dst, size), + DCTMode::DST_II => dst_II_ref(src, dst, size), + DCTMode::DCT_III => dct_III_ref(src, dst, size), + DCTMode::DST_III => dst_III_ref(src, dst, size), + DCTMode::DCT_IV => dct_IV_ref(src, dst, size), + DCTMode::DST_IV => dst_IV_ref(src, dst, size), + }; +} + +#[allow(non_snake_case)] +fn dct_I_ref(src: &[f32], dst: &mut [f32], size: usize) { + let base = consts::PI / ((size - 1) as f32); + for k in 0..size { + let mut sum = (src[0] + (if (k & 1) != 0 { -src[size - 1] } else { src[size - 1] })) * 0.5; + for n in 1..size-1 { + sum += src[n] * (base * ((n * k) as f32)).cos(); + } + dst[k] = sum; + } +} + +#[allow(non_snake_case)] +fn dst_I_ref(src: &[f32], dst: &mut [f32], size: usize) { + let base = consts::PI / ((size + 1) as f32); + for k in 0..size { + let mut sum = 0.0; + for n in 0..size { + sum += src[n] * (base * (((n + 1) * (k + 1)) as f32)).sin(); + } + dst[k] = sum; + } +} + +#[allow(non_snake_case)] +fn dct_II_ref(src: &[f32], dst: &mut [f32], size: usize) { + let base = consts::PI / (size as f32); + for k in 0..size { + let mut sum = 0.0; + for n in 0..size { + sum += src[n] * (base * ((n as f32) + 0.5) * (k as f32)).cos(); + } + dst[k] = sum * (if k == 0 { (1.0 / (size as f32)).sqrt() } else { (2.0 / (size as f32)).sqrt() }); + } +} + +#[allow(non_snake_case)] +fn dst_II_ref(src: &[f32], dst: &mut [f32], size: usize) { + let base = consts::PI / (size as f32); + for k in 0..size { + let mut sum = 0.0; + let kmod = (k + 1) as f32; + for n in 0..size { + sum += src[n] * (base * ((n as f32) + 0.5) * kmod).sin(); + } + dst[k] = sum * (2.0 / (size as f32)).sqrt(); + } + dst[size - 1] /= consts::SQRT_2; +} + +#[allow(non_snake_case)] +fn dct_III_ref(src: &[f32], dst: &mut [f32], size: usize) { + let base = consts::PI / (size as f32); + for k in 0..size { + let mut sum = src[0] / consts::SQRT_2; + let kmod = (k as f32) + 0.5; + for n in 1..size { + sum += src[n] * (base * (n as f32) * kmod).cos(); + } + dst[k] = sum * (2.0 / (size as f32)).sqrt(); + } +} + +#[allow(non_snake_case)] +fn dst_III_ref(src: &[f32], dst: &mut [f32], size: usize) { + let base = consts::PI / (size as f32); + for k in 0..size { + let mut sum = 0.0; + let kmod = (k as f32) + 0.5; + for n in 0..size-1 { + sum += src[n] * (base * ((n + 1) as f32) * kmod).sin(); + } + sum += src[size - 1] / consts::SQRT_2 * (base * (size as f32) * kmod).sin(); + dst[k] = sum * (2.0 / (size as f32)).sqrt(); + } +} + +#[allow(non_snake_case)] +fn dct_IV_ref(src: &[f32], dst: &mut [f32], size: usize) { + let base = consts::PI / (size as f32); + for k in 0..size { + let mut sum = 0.0; + let kmod = (k as f32) + 0.5; + for n in 0..size { + sum += src[n] * (base * ((n as f32) + 0.5) * kmod).cos(); + } + dst[k] = sum; + } +} + +#[allow(non_snake_case)] +fn dst_IV_ref(src: &[f32], dst: &mut [f32], size: usize) { + let base = consts::PI / (size as f32); + for k in 0..size { + let mut sum = 0.0; + let kmod = (k as f32) + 0.5; + for n in 0..size { + sum += src[n] * (base * ((n as f32) + 0.5) * kmod).sin(); + } + dst[k] = sum; + } +} + +const DCT_II_C0: f32 = 0.65328148243818826393; // cos(1*PI/8) / sqrt(2) +const DCT_II_C1: f32 = 0.27059805007309849220; // cos(3*PI/8) / sqrt(2) + +#[allow(non_snake_case)] +fn dct_II_inplace(buf: &mut [f32], size: usize, step: usize, tab: &[f32], perm_tab: &[usize]) { + match size { + 0 | 1 => {}, + 2 => { + let i0 = buf[0]; + let i1 = buf[step]; + buf[0] = (i0 + i1) / consts::SQRT_2; + buf[step] = (i0 - i1) / consts::SQRT_2; + }, + 4 => { + let i0 = buf[0 * step]; + let i1 = buf[2 * step]; + let i2 = buf[1 * step]; + let i3 = buf[3 * step]; + let t0 = (i0 + i3) * 0.5; + let t1 = (i1 + i2) * 0.5; + buf[0 * step] = t0 + t1; + buf[2 * step] = t0 - t1; + let t0 = i0 - i3; + let t1 = i1 - i2; + buf[1 * step] = DCT_II_C0 * t0 + DCT_II_C1 * t1; + buf[3 * step] = DCT_II_C1 * t0 - DCT_II_C0 * t1; + }, + _ => { + let hsize = size >> 1; + for i in 0..hsize { + let i0 = buf[i * step]; + let i1 = buf[(size - 1 - i) * step]; + if (i & 1) == 0 { + buf[i * step] = (i0 + i1) / consts::SQRT_2; + buf[(size - 1 - i) * step] = (i0 - i1) / consts::SQRT_2; + } else { + buf[i * step] = (i1 - i0) / consts::SQRT_2; + buf[(size - 1 - i) * step] = (i1 + i0) / consts::SQRT_2; + } + } + dct_II_inplace(buf, hsize, step * 2, tab, perm_tab); + dct_II_part2_inplace(&mut buf[step..], hsize, step * 2, tab, perm_tab); + }, + }; +} + +#[allow(non_snake_case)] +fn dct_II_part2_inplace(buf: &mut [f32], size: usize, step: usize, tab: &[f32], perm_tab: &[usize]) { + let hsize = size >> 1; +// todo optimise for size = 4 + for i in 0..hsize { + let i0 = buf[perm_tab[size + i] * step]; + let i1 = buf[perm_tab[size + size - i - 1] * step]; + let c0 = tab[size + i * 2 + 0]; + let c1 = tab[size + i * 2 + 1]; + buf[perm_tab[size + i] * step] = c0 * i0 + c1 * i1; + buf[perm_tab[size + size - i - 1] * step] = c0 * i1 - c1 * i0; + } + + dct_II_inplace(buf, hsize, step * 2, tab, perm_tab); + dst_II_inplace(&mut buf[step..], hsize, step * 2, tab, perm_tab); + + buf[(size - 1) * step] = -buf[(size - 1) * step]; + for i in 1..hsize { + let (i0, i1) = if (i & 1) == 0 { + (buf[i * step * 2], -buf[i * step * 2 - step]) + } else { + (buf[i * step * 2], buf[i * step * 2 - step]) + }; + buf[i * step * 2 - step] = (i0 + i1) / consts::SQRT_2; + buf[i * step * 2] = (i0 - i1) / consts::SQRT_2; + } +} + +#[allow(non_snake_case)] +fn dst_II_inplace(buf: &mut [f32], size: usize, step: usize, tab: &[f32], perm_tab: &[usize]) { + if size <= 1 { return; } + let hsize = size >> 1; + for i in hsize..size { buf[i * step] = -buf[i * step]; } + dct_II_inplace(buf, size, step, tab, perm_tab); + for i in 0..hsize { + let idx0 = i * step; + let idx1 = (size - 1 - i) * step; + buf.swap(idx0, idx1); + } +} + +#[allow(non_snake_case)] +fn dct_III_inplace(buf: &mut [f32], size: usize, step: usize, tab: &[f32], perm_tab: &[usize]) { + if size <= 1 { return; } + let hsize = size >> 1; + dct_III_inplace(buf, hsize, step, tab, perm_tab); + dct_IV_inplace(&mut buf[step*hsize..], hsize, step, tab, perm_tab); + for i in 0..(size >> 2) { + buf.swap((size - 1 - i) * step, (hsize + i) * step); + } + for i in 0..hsize { + let i0 = buf[i * step] / consts::SQRT_2; + let i1 = buf[(size-i-1) * step] / consts::SQRT_2; + buf[i * step] = i0 + i1; + buf[(size-i-1) * step] = i0 - i1; + } +} + +#[allow(non_snake_case)] +fn dst_III_inplace(buf: &mut [f32], size: usize, step: usize, tab: &[f32], perm_tab: &[usize]) { + if size <= 1 { return; } + let hsize = size >> 1; + for i in 0..hsize { + let idx0 = i * step; + let idx1 = (size - 1 - i) * step; + buf.swap(idx0, idx1); + } + dct_III_inplace(buf, size, step, tab, perm_tab); + for i in 0..hsize { buf[i * 2 * step + step] = -buf[i * 2 * step + step]; } +} + +#[allow(non_snake_case)] +fn dct_IV_inplace(buf: &mut [f32], size: usize, step: usize, tab: &[f32], perm_tab: &[usize]) { + if size <= 1 { return; } + let hsize = size >> 1; + + for i in 0..hsize { + let idx0 = perm_tab[size + i]; + let idx1 = size - 1 - idx0; + let i0 = buf[idx0 * step]; + let i1 = buf[idx1 * step]; + let c0 = tab[size + i * 2 + 1]; + let c1 = tab[size + i * 2 + 0]; + buf[idx0 * step] = c0 * i0 + c1 * i1; + buf[idx1 * step] = c0 * i1 - c1 * i0; + } + for i in (hsize+1..size).step_by(2) { + buf[i] = -buf[i]; + } + dct_II_inplace(buf, hsize, step * 2, tab, perm_tab); + dct_II_inplace(&mut buf[step..], hsize, step * 2, tab, perm_tab); + for i in 0..(size >> 2) { + buf.swap((size - 1 - i * 2) * step, (i * 2 + 1) * step); + } + for i in (3..size).step_by(4) { + buf[i] = -buf[i]; + } + buf[0] *= consts::SQRT_2; + buf[(size - 1) * step] *= -consts::SQRT_2; + for i in 0..hsize-1 { + let i0 = buf[(i * 2 + 2) * step]; + let i1 = buf[(i * 2 + 1) * step]; + buf[(i * 2 + 2) * step] = i0 + i1; + buf[(i * 2 + 1) * step] = i0 - i1; + } + for i in 0..size { + buf[i * step] /= consts::SQRT_2; + } +} + +#[cfg(test)] +mod test { + use super::*; + + fn test_pair(mode: DCTMode, invmode: DCTMode, size: usize) { + println!("testing {:?} -> {:?}", mode, invmode); + let mut fin: Vec<f32> = Vec::with_capacity(size); + let mut out: Vec<f32> = Vec::with_capacity(size); + out.resize(size, 0.0); + let mut seed: u32 = 42; + for _ in 0..size { + seed = seed.wrapping_mul(1664525).wrapping_add(1013904223); + let val = (seed >> 16) as i16; + fin.push((val as f32) / 256.0); + } + let mut dct = DCT::new(mode, size); + dct.do_dct(&fin, &mut out); + let mut dct = DCT::new(invmode, size); + dct.do_dct_inplace(&mut out); + + let scale = dct.get_scale(); + for i in 0..fin.len() { + assert!((fin[i] - out[i]*scale).abs() < 1.0e-2); + } + } + #[test] + fn test_dct() { + test_pair(DCTMode::DCT_I, DCTMode::DCT_I, 32); + test_pair(DCTMode::DST_I, DCTMode::DST_I, 32); + test_pair(DCTMode::DCT_II, DCTMode::DCT_III, 32); + test_pair(DCTMode::DST_II, DCTMode::DST_III, 32); + test_pair(DCTMode::DCT_III, DCTMode::DCT_II, 32); + test_pair(DCTMode::DST_III, DCTMode::DST_II, 32); + test_pair(DCTMode::DCT_IV, DCTMode::DCT_IV, 32); + test_pair(DCTMode::DST_IV, DCTMode::DST_IV, 32); + } +} diff --git a/nihav-codec-support/src/dsp/fft.rs b/nihav-codec-support/src/dsp/fft.rs new file mode 100644 index 0000000..4629f75 --- /dev/null +++ b/nihav-codec-support/src/dsp/fft.rs @@ -0,0 +1,912 @@ +//! FFT and RDFT implementation. +use std::f32::{self, consts}; +use std::ops::{Not, Neg, Add, AddAssign, Sub, SubAssign, Mul, MulAssign}; +use std::fmt; + +/// Complex number. +#[repr(C)] +#[derive(Debug,Clone,Copy,PartialEq)] +pub struct FFTComplex { + /// Real part of the numner. + pub re: f32, + /// Complex part of the number. + pub im: f32, +} + +impl FFTComplex { + /// Calculates `exp(i * val)`. + pub fn exp(val: f32) -> Self { + FFTComplex { re: val.cos(), im: val.sin() } + } + /// Returns `-Im + i * Re`. + pub fn rotate(self) -> Self { + FFTComplex { re: -self.im, im: self.re } + } + /// Multiplies complex number by scalar. + pub fn scale(self, scale: f32) -> Self { + FFTComplex { re: self.re * scale, im: self.im * scale } + } +} + +impl Neg for FFTComplex { + type Output = FFTComplex; + fn neg(self) -> Self::Output { + FFTComplex { re: -self.re, im: -self.im } + } +} + +impl Not for FFTComplex { + type Output = FFTComplex; + fn not(self) -> Self::Output { + FFTComplex { re: self.re, im: -self.im } + } +} + +impl Add for FFTComplex { + type Output = FFTComplex; + fn add(self, other: Self) -> Self::Output { + FFTComplex { re: self.re + other.re, im: self.im + other.im } + } +} + +impl AddAssign for FFTComplex { + fn add_assign(&mut self, other: Self) { + self.re += other.re; + self.im += other.im; + } +} + +impl Sub for FFTComplex { + type Output = FFTComplex; + fn sub(self, other: Self) -> Self::Output { + FFTComplex { re: self.re - other.re, im: self.im - other.im } + } +} + +impl SubAssign for FFTComplex { + fn sub_assign(&mut self, other: Self) { + self.re -= other.re; + self.im -= other.im; + } +} + +impl Mul for FFTComplex { + type Output = FFTComplex; + fn mul(self, other: Self) -> Self::Output { + FFTComplex { re: self.re * other.re - self.im * other.im, + im: self.im * other.re + self.re * other.im } + } +} + +impl MulAssign for FFTComplex { + fn mul_assign(&mut self, other: Self) { + let re = self.re * other.re - self.im * other.im; + let im = self.im * other.re + self.re * other.im; + self.re = re; + self.im = im; + } +} + +impl fmt::Display for FFTComplex { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "({}, {})", self.re, self.im) + } +} + +/// Complex number with zero value. +pub const FFTC_ZERO: FFTComplex = FFTComplex { re: 0.0, im: 0.0 }; + +/// Calculates forward or inverse FFT in the straightforward way. +pub fn generic_fft(data: &mut [FFTComplex], forward: bool) { + let mut tmp = Vec::with_capacity(data.len()); + tmp.resize(data.len(), FFTC_ZERO); + let base = if forward { -consts::PI * 2.0 / (data.len() as f32) } + else { consts::PI * 2.0 / (data.len() as f32) }; + for k in 0..data.len() { + let mut sum = FFTC_ZERO; + for n in 0..data.len() { + let w = FFTComplex::exp(base * ((n * k) as f32)); + sum += data[n] * w; + } + tmp[k] = sum; + } + for k in 0..data.len() { + data[k] = tmp[k]; + } +} + +struct FFTData { + table: Vec<FFTComplex>, + tmp: Vec<FFTComplex>, + twiddle: Vec<FFTComplex>, + size: usize, + step: usize, + div: usize, +} + +struct FFTGeneric {} + +const FFT3_CONST: f32 = 0.86602540378443864677; +const FFT5_CONST1: FFTComplex = FFTComplex { re: 0.80901699437494742410, im: 0.58778525229247312915 }; +const FFT5_CONST2: FFTComplex = FFTComplex { re: 0.30901699437494742411, im: 0.95105651629515357211 }; + +fn twiddle5(a: FFTComplex, b: FFTComplex, c: FFTComplex) -> (FFTComplex, FFTComplex) { + let re = a.re * c.re; + let im = a.im * c.re; + let diffre = b.im * c.im; + let diffim = b.re * c.im; + + (FFTComplex { re: re - diffre, im: im + diffim }, FFTComplex { re: re + diffre, im: im - diffim }) +} + +impl FFTGeneric { + fn new_data(size: usize, forward: bool) -> FFTData { + let mut table: Vec<FFTComplex> = Vec::with_capacity(size * size); + table.resize(size * size, FFTC_ZERO); + let base = consts::PI * 2.0 / (size as f32); + if forward { + for n in 0..size { + for k in 0..size { + table[n * size + k] = FFTComplex::exp(-base * ((n * k) as f32)); + } + } + } else { + for n in 0..size { + for k in 0..size { + table[n * size + k] = FFTComplex::exp( base * ((n * k) as f32)); + } + } + } + let mut tmp = Vec::with_capacity(size); + tmp.resize(size, FFTC_ZERO); + FFTData { table, tmp, twiddle: Vec::new(), size, step: 0, div: 0 } + } + fn fft(tbl: &mut FFTData, size: usize, data: &mut [FFTComplex], step: usize) { + if size == 3 { + let s0 = data[step * 0]; + let s1 = data[step * 1]; + let s2 = data[step * 2]; + let t0 = s1 + s2; + data[step * 0] += t0; + let t1 = s0 - t0.scale(0.5); + let t2 = (s2 - s1).rotate().scale(FFT3_CONST); + data[step * 1] = t1 + t2; + data[step * 2] = t1 - t2; + return; + } + if size == 5 { + let s0 = data[step * 0]; + let s1 = data[step * 1]; + let s2 = data[step * 2]; + let s3 = data[step * 3]; + let s4 = data[step * 4]; + + let t0 = s1 + s4; + let t1 = s1 - s4; + let t2 = s2 + s3; + let t3 = s2 - s3; + let (t4, t5) = twiddle5(t0, t1, FFT5_CONST2); + let (t6, t7) = twiddle5(t2, t3, FFT5_CONST1); + let (t8, t9) = twiddle5(t0, t1, FFT5_CONST1); + let (ta, tb) = twiddle5(t2, t3, FFT5_CONST2); + + data[step * 0] = s0 + t0 + t2; + data[step * 1] = s0 + t5 - t6; + data[step * 2] = s0 - t8 + ta; + data[step * 3] = s0 - t9 + tb; + data[step * 4] = s0 + t4 - t7; + return; + } + for k in 0..tbl.size { + tbl.tmp[k] = FFTC_ZERO; + for n in 0..tbl.size { + tbl.tmp[k] += data[n * step] * tbl.table[k * tbl.size + n]; + } + } + for n in 0..tbl.size { + data[n * step] = tbl.tmp[n]; + } + } + fn ifft(tbl: &mut FFTData, size: usize, data: &mut [FFTComplex], step: usize) { + if size == 3 { + let s0 = data[step * 0]; + let s1 = data[step * 1]; + let s2 = data[step * 2]; + let t0 = s1 + s2; + data[step * 0] += t0; + let t1 = s0 - t0.scale(0.5); + let t2 = (s2 - s1).rotate().scale(FFT3_CONST); + data[step * 1] = t1 - t2; + data[step * 2] = t1 + t2; + return; + } + if size == 5 { + let s0 = data[step * 0]; + let s1 = data[step * 1]; + let s2 = data[step * 2]; + let s3 = data[step * 3]; + let s4 = data[step * 4]; + + let t0 = s1 + s4; + let t1 = s1 - s4; + let t2 = s2 + s3; + let t3 = s2 - s3; + let (t4, t5) = twiddle5(t0, t1, FFT5_CONST2); + let (t6, t7) = twiddle5(t2, t3, FFT5_CONST1); + let (t8, t9) = twiddle5(t0, t1, FFT5_CONST1); + let (ta, tb) = twiddle5(t2, t3, FFT5_CONST2); + + data[step * 0] = s0 + t0 + t2; + data[step * 1] = s0 + t4 - t7; + data[step * 2] = s0 - t9 + tb; + data[step * 3] = s0 - t8 + ta; + data[step * 4] = s0 + t5 - t6; + return; + } + Self::fft(tbl, size, data, step); + } +} + +struct FFTSplitRadix {} + +impl FFTSplitRadix { + fn new_data(bits: u8, _forward: bool) -> FFTData { + let size = 1 << bits; + let mut table = Vec::with_capacity(size); + for _ in 0..4 { table.push(FFTC_ZERO); } + for b in 3..=bits { + let qsize = (1 << (b - 2)) as usize; + let base = -consts::PI / ((qsize * 2) as f32); + for k in 0..qsize { + table.push(FFTComplex::exp(base * ((k * 1) as f32))); + table.push(FFTComplex::exp(base * ((k * 3) as f32))); + } + } + FFTData { table, tmp: Vec::new(), twiddle: Vec::new(), size, step: 0, div: 0 } + } + fn fft(fftdata: &mut FFTData, bits: u8, data: &mut [FFTComplex]) { + if bits == 0 { return; } + if bits == 1 { + let sum01 = data[0] + data[1]; + let dif01 = data[0] - data[1]; + data[0] = sum01; + data[1] = dif01; + return; + } + if bits == 2 { + let sum01 = data[0] + data[2]; + let dif01 = data[0] - data[2]; + let sum23 = data[1] + data[3]; + let dif23 = data[1] - data[3]; + data[0] = sum01 + sum23; + data[1] = dif01 - dif23.rotate(); + data[2] = sum01 - sum23; + data[3] = dif01 + dif23.rotate(); + return; + } + let qsize = (1 << (bits - 2)) as usize; + let hsize = (1 << (bits - 1)) as usize; + let q3size = qsize + hsize; + + Self::fft(fftdata, bits - 1, &mut data[0 ..hsize]); + Self::fft(fftdata, bits - 2, &mut data[hsize ..q3size]); + Self::fft(fftdata, bits - 2, &mut data[q3size..]); + let off = hsize; + { + let t3 = data[0 + hsize] + data[0 + q3size]; + let t4 = (data[0 + hsize] - data[0 + q3size]).rotate(); + let e1 = data[0]; + let e2 = data[0 + qsize]; + data[0] = e1 + t3; + data[0 + qsize] = e2 - t4; + data[0 + hsize] = e1 - t3; + data[0 + q3size] = e2 + t4; + } + for k in 1..qsize { + let t1 = fftdata.table[off + k * 2 + 0] * data[k + hsize]; + let t2 = fftdata.table[off + k * 2 + 1] * data[k + q3size]; + let t3 = t1 + t2; + let t4 = (t1 - t2).rotate(); + let e1 = data[k]; + let e2 = data[k + qsize]; + data[k] = e1 + t3; + data[k + qsize] = e2 - t4; + data[k + hsize] = e1 - t3; + data[k + qsize * 3] = e2 + t4; + } + } + fn ifft(fftdata: &mut FFTData, bits: u8, data: &mut [FFTComplex]) { + if bits == 0 { return; } + if bits == 1 { + let sum01 = data[0] + data[1]; + let dif01 = data[0] - data[1]; + data[0] = sum01; + data[1] = dif01; + return; + } + if bits == 2 { + let sum01 = data[0] + data[2]; + let dif01 = data[0] - data[2]; + let sum23 = data[1] + data[3]; + let dif23 = data[1] - data[3]; + data[0] = sum01 + sum23; + data[1] = dif01 + dif23.rotate(); + data[2] = sum01 - sum23; + data[3] = dif01 - dif23.rotate(); + return; + } + let qsize = (1 << (bits - 2)) as usize; + let hsize = (1 << (bits - 1)) as usize; + let q3size = qsize + hsize; + + Self::ifft(fftdata, bits - 1, &mut data[0 ..hsize]); + Self::ifft(fftdata, bits - 2, &mut data[hsize ..q3size]); + Self::ifft(fftdata, bits - 2, &mut data[q3size..]); + let off = hsize; + { + let t3 = data[0 + hsize] + data[0 + q3size]; + let t4 = (data[0 + hsize] - data[0 + q3size]).rotate(); + let e1 = data[0]; + let e2 = data[0 + qsize]; + data[0] = e1 + t3; + data[0 + qsize] = e2 + t4; + data[0 + hsize] = e1 - t3; + data[0 + q3size] = e2 - t4; + } + for k in 1..qsize { + let t1 = !fftdata.table[off + k * 2 + 0] * data[k + hsize]; + let t2 = !fftdata.table[off + k * 2 + 1] * data[k + q3size]; + let t3 = t1 + t2; + let t4 = (t1 - t2).rotate(); + let e1 = data[k]; + let e2 = data[k + qsize]; + data[k] = e1 + t3; + data[k + qsize] = e2 + t4; + data[k + hsize] = e1 - t3; + data[k + qsize * 3] = e2 - t4; + } + } +} + +struct FFT15 {} + +const FFT15_INSWAP: [usize; 20] = [ 0, 5, 10, 42, 3, 8, 13, 42, 6, 11, 1, 42, 9, 14, 4, 42, 12, 2, 7, 42 ]; +const FFT15_OUTSWAP: [usize; 20] = [ 0, 10, 5, 42, 6, 1, 11, 42, 12, 7, 2, 42, 3, 13, 8, 42, 9, 4, 14, 42 ]; + +impl FFT15 { + fn new_data(size: usize, _forward: bool) -> FFTData { + FFTData { table: Vec::new(), tmp: Vec::new(), twiddle: Vec::new(), size, step: 0, div: 0 } + } + fn fft3(dst: &mut [FFTComplex], src: &[FFTComplex], step: usize, n: usize) { + let s0 = src[0]; + let s1 = src[1]; + let s2 = src[2]; + + let t0 = s1 + s2; + let t1 = s0 - t0.scale(0.5); + let t2 = (s2 - s1).rotate().scale(FFT3_CONST); + + dst[FFT15_OUTSWAP[n * 4 + 0] * step] = s0 + t0; + dst[FFT15_OUTSWAP[n * 4 + 1] * step] = t1 + t2; + dst[FFT15_OUTSWAP[n * 4 + 2] * step] = t1 - t2; + } + fn ifft3(dst: &mut [FFTComplex], src: &[FFTComplex], step: usize, n: usize) { + let s0 = src[0]; + let s1 = src[1]; + let s2 = src[2]; + + let t0 = s1 + s2; + let t1 = s0 - t0.scale(0.5); + let t2 = (s2 - s1).rotate().scale(FFT3_CONST); + + dst[FFT15_OUTSWAP[n * 4 + 0] * step] = s0 + t0; + dst[FFT15_OUTSWAP[n * 4 + 1] * step] = t1 - t2; + dst[FFT15_OUTSWAP[n * 4 + 2] * step] = t1 + t2; + } + fn fft5(dst: &mut [FFTComplex], src: &[FFTComplex], step: usize, n: usize) { + let s0 = src[FFT15_INSWAP[n + 0 * 4] * step]; + let s1 = src[FFT15_INSWAP[n + 1 * 4] * step]; + let s2 = src[FFT15_INSWAP[n + 2 * 4] * step]; + let s3 = src[FFT15_INSWAP[n + 3 * 4] * step]; + let s4 = src[FFT15_INSWAP[n + 4 * 4] * step]; + + let t0 = s1 + s4; + let t1 = s1 - s4; + let t2 = s2 + s3; + let t3 = s2 - s3; + let (t4, t5) = twiddle5(t0, t1, FFT5_CONST2); + let (t6, t7) = twiddle5(t2, t3, FFT5_CONST1); + let (t8, t9) = twiddle5(t0, t1, FFT5_CONST1); + let (ta, tb) = twiddle5(t2, t3, FFT5_CONST2); + + dst[0 * 3] = s0 + t0 + t2; + dst[1 * 3] = s0 + t5 - t6; + dst[2 * 3] = s0 - t8 + ta; + dst[3 * 3] = s0 - t9 + tb; + dst[4 * 3] = s0 + t4 - t7; + } + fn ifft5(dst: &mut [FFTComplex], src: &[FFTComplex], step: usize, n: usize) { + let s0 = src[FFT15_INSWAP[n + 0 * 4] * step]; + let s1 = src[FFT15_INSWAP[n + 1 * 4] * step]; + let s2 = src[FFT15_INSWAP[n + 2 * 4] * step]; + let s3 = src[FFT15_INSWAP[n + 3 * 4] * step]; + let s4 = src[FFT15_INSWAP[n + 4 * 4] * step]; + + let t0 = s1 + s4; + let t1 = s1 - s4; + let t2 = s2 + s3; + let t3 = s2 - s3; + let (t4, t5) = twiddle5(t0, t1, FFT5_CONST2); + let (t6, t7) = twiddle5(t2, t3, FFT5_CONST1); + let (t8, t9) = twiddle5(t0, t1, FFT5_CONST1); + let (ta, tb) = twiddle5(t2, t3, FFT5_CONST2); + + dst[0 * 3] = s0 + t0 + t2; + dst[1 * 3] = s0 + t4 - t7; + dst[2 * 3] = s0 - t9 + tb; + dst[3 * 3] = s0 - t8 + ta; + dst[4 * 3] = s0 + t5 - t6; + } + fn fft(_fftdata: &mut FFTData, data: &mut [FFTComplex], step: usize) { + let mut tmp = [FFTC_ZERO; 15]; + for n in 0..3 { + Self::fft5(&mut tmp[n..], data, step, n); + } + for n in 0..5 { + Self::fft3(data, &tmp[n * 3..][..3], step, n); + } + } + fn ifft(_fftdata: &mut FFTData, data: &mut [FFTComplex], step: usize) { + let mut tmp = [FFTC_ZERO; 15]; + for n in 0..3 { + Self::ifft5(&mut tmp[n..], data, step, n); + } + for n in 0..5 { + Self::ifft3(data, &tmp[n * 3..][..3], step, n); + } + } +} + + +enum FFTMode { + Generic(usize), + SplitRadix(u8), + Prime15, +} + +impl FFTMode { + fn permute(&self, perms: &mut [usize]) { + match *self { + FFTMode::Generic(_) => {}, + FFTMode::SplitRadix(bits) => { + let div = perms.len() >> bits; + gen_sr_perms(perms, 1 << bits); + if div > 1 { + for i in 0..(1 << bits) { + perms[i] *= div; + } + for i in 1..div { + for j in 0..(1 << bits) { + perms[(i << bits) + j] = perms[j] + i; + } + } + } + }, + FFTMode::Prime15 => {}, + }; + } + fn do_fft(&self, fftdata: &mut FFTData, data: &mut [FFTComplex]) { + match *self { + FFTMode::Generic(size) => FFTGeneric::fft(fftdata, size, data, 1), + FFTMode::SplitRadix(bits) => FFTSplitRadix::fft(fftdata, bits, data), + FFTMode::Prime15 => FFT15::fft(fftdata, data, 1), + }; + } + fn do_fft2(&self, fftdata: &mut FFTData, data: &mut [FFTComplex], step: usize) { + match *self { + FFTMode::Generic(size) => FFTGeneric::fft(fftdata, size, data, step), + FFTMode::SplitRadix(_) => unreachable!(), + FFTMode::Prime15 => FFT15::fft(fftdata, data, step), + }; + } + fn do_ifft(&self, fftdata: &mut FFTData, data: &mut [FFTComplex]) { + match *self { + FFTMode::Generic(size) => FFTGeneric::ifft(fftdata, size, data, 1), + FFTMode::SplitRadix(bits) => FFTSplitRadix::ifft(fftdata, bits, data), + FFTMode::Prime15 => FFT15::ifft(fftdata, data, 1), + }; + } + fn do_ifft2(&self, fftdata: &mut FFTData, data: &mut [FFTComplex], step: usize) { + match *self { + FFTMode::Generic(size) => FFTGeneric::ifft(fftdata, size, data, step), + FFTMode::SplitRadix(_) => unreachable!(), + FFTMode::Prime15 => FFT15::ifft(fftdata, data, step), + }; + } + fn get_size(&self) -> usize { + match *self { + FFTMode::Generic(size) => size, + FFTMode::SplitRadix(bits) => 1 << bits, + FFTMode::Prime15 => 15, + } + } +} + +/// FFT working context. +pub struct FFT { + perms: Vec<usize>, + swaps: Vec<usize>, + ffts: Vec<(FFTMode, FFTData)>, +} + +impl FFT { + /// Calculates Fourier transform. + pub fn do_fft(&mut self, src: &[FFTComplex], dst: &mut [FFTComplex]) { + for k in 0..src.len() { dst[k] = src[self.perms[k]]; } + self.do_fft_core(dst); + } + /// Calculates inverse Fourier transform. + pub fn do_ifft(&mut self, src: &[FFTComplex], dst: &mut [FFTComplex]) { + for k in 0..src.len() { dst[k] = src[self.perms[k]]; } + self.do_ifft_core(dst); + } + /// Performs inplace FFT. + pub fn do_fft_inplace(&mut self, data: &mut [FFTComplex]) { + for idx in 0..self.swaps.len() { + let nidx = self.swaps[idx]; + if idx != nidx { + data.swap(nidx, idx); + } + } + self.do_fft_core(data); + } + /// Performs inplace inverse FFT. + pub fn do_ifft_inplace(&mut self, data: &mut [FFTComplex]) { + for idx in 0..self.swaps.len() { + let nidx = self.swaps[idx]; + if idx != nidx { + data.swap(nidx, idx); + } + } + self.do_ifft_core(data); + } + fn do_fft_core(&mut self, data: &mut [FFTComplex]) { + for el in self.ffts.iter_mut() { + let (mode, ref mut fftdata) = el; + let bsize = mode.get_size(); + let div = fftdata.div; + let step = fftdata.step; + if step == 1 { + mode.do_fft(fftdata, data); + for i in 1..div { + mode.do_fft(fftdata, &mut data[i * bsize..]); + } + } else { + mode.do_fft2(fftdata, data, div); + let mut toff = bsize; + for i in 1..div { + for j in 1..bsize { + data[i + j * div] *= fftdata.twiddle[toff + j]; + } + mode.do_fft2(fftdata, &mut data[i..], div); + toff += bsize; + } + } + } + } + fn do_ifft_core(&mut self, data: &mut [FFTComplex]) { + for el in self.ffts.iter_mut() { + let (mode, ref mut fftdata) = el; + let bsize = mode.get_size(); + let div = fftdata.div; + let step = fftdata.step; + if step == 1 { + mode.do_ifft(fftdata, data); + for i in 1..div { + mode.do_ifft(fftdata, &mut data[i * bsize..]); + } + } else { + mode.do_ifft2(fftdata, data, div); + let mut toff = bsize; + for i in 1..div { + for j in 1..bsize { + data[i + j * div] *= fftdata.twiddle[toff + j]; + } + mode.do_ifft2(fftdata, &mut data[i..], div); + toff += bsize; + } + } + } + } +} + +/// [`FFT`] context creator. +/// +/// [`FFT`]: ./struct.FFT.html +pub struct FFTBuilder { +} + +/*fn reverse_bits(inval: u32) -> u32 { + const REV_TAB: [u8; 16] = [ + 0b0000, 0b1000, 0b0100, 0b1100, 0b0010, 0b1010, 0b0110, 0b1110, + 0b0001, 0b1001, 0b0101, 0b1101, 0b0011, 0b1011, 0b0111, 0b1111, + ]; + + let mut ret = 0; + let mut val = inval; + for _ in 0..8 { + ret = (ret << 4) | (REV_TAB[(val & 0xF) as usize] as u32); + val = val >> 4; + } + ret +} + +fn swp_idx(idx: usize, bits: u32) -> usize { + let s = reverse_bits(idx as u32) as usize; + s >> (32 - bits) +}*/ + +fn gen_sr_perms(swaps: &mut [usize], size: usize) { + if size <= 4 { return; } + let mut evec: Vec<usize> = Vec::with_capacity(size / 2); + let mut ovec1: Vec<usize> = Vec::with_capacity(size / 4); + let mut ovec2: Vec<usize> = Vec::with_capacity(size / 4); + for k in 0..size/4 { + evec.push (swaps[k * 4 + 0]); + ovec1.push(swaps[k * 4 + 1]); + evec.push (swaps[k * 4 + 2]); + ovec2.push(swaps[k * 4 + 3]); + } + for k in 0..size/2 { swaps[k] = evec[k]; } + for k in 0..size/4 { swaps[k + size/2] = ovec1[k]; } + for k in 0..size/4 { swaps[k + 3*size/4] = ovec2[k]; } + gen_sr_perms(&mut swaps[0..size/2], size/2); + gen_sr_perms(&mut swaps[size/2..3*size/4], size/4); + gen_sr_perms(&mut swaps[3*size/4..], size/4); +} + +fn gen_swaps_for_perm(swaps: &mut Vec<usize>, perms: &[usize]) { + let mut idx_arr: Vec<usize> = Vec::with_capacity(perms.len()); + for i in 0..perms.len() { idx_arr.push(i); } + let mut run_size = 0; + let mut run_pos = 0; + for idx in 0..perms.len() { + if perms[idx] == idx_arr[idx] { + if run_size == 0 { run_pos = idx; } + run_size += 1; + } else { + for i in 0..run_size { + swaps.push(run_pos + i); + } + run_size = 0; + let mut spos = idx + 1; + while idx_arr[spos] != perms[idx] { spos += 1; } + idx_arr[spos] = idx_arr[idx]; + idx_arr[idx] = perms[idx]; + swaps.push(spos); + } + } +} + +impl FFTBuilder { + fn generate_twiddle(data: &mut FFTData, size: usize, cur_size: usize, forward: bool) { + if size == cur_size { return; } + data.twiddle = Vec::with_capacity(size); + let div = size / cur_size; + let base = if forward { -2.0 * consts::PI / (size as f32) } else { 2.0 * consts::PI / (size as f32) }; + for n in 0..div { + for k in 0..cur_size { + data.twiddle.push(FFTComplex::exp(base * ((k * n) as f32))); + } + } + } + /// Constructs a new `FFT` context. + pub fn new_fft(size: usize, forward: bool) -> FFT { + let mut ffts: Vec<(FFTMode, FFTData)> = Vec::with_capacity(1); + let mut perms: Vec<usize> = Vec::with_capacity(size); + let mut swaps: Vec<usize> = Vec::with_capacity(size); + let mut rem_size = size; + if rem_size.trailing_zeros() > 0 { + let bits = rem_size.trailing_zeros() as u8; + let mut data = FFTSplitRadix::new_data(bits, forward); + Self::generate_twiddle(&mut data, size, 1 << bits, forward); + data.step = 1; + data.div = rem_size >> bits; + ffts.push((FFTMode::SplitRadix(bits), data)); + rem_size >>= bits; + } + if (rem_size % 15) == 0 { + let mut data = FFT15::new_data(size, forward); + Self::generate_twiddle(&mut data, size, 15, forward); + data.step = size / rem_size; + data.div = size / rem_size; + ffts.push((FFTMode::Prime15, data)); + rem_size /= 15; + } + if rem_size > 1 { + let mut data = FFTGeneric::new_data(rem_size, forward); + Self::generate_twiddle(&mut data, size, rem_size, forward); + data.step = size / rem_size; + data.div = size / rem_size; + ffts.push((FFTMode::Generic(rem_size), data)); + } + + for i in 0..size { + perms.push(i); + } + for (mode, _) in ffts.iter().rev() { + mode.permute(&mut perms); + } + gen_swaps_for_perm(&mut swaps, perms.as_slice()); + + FFT { perms, swaps, ffts } + } +} + +/// RDFT working context. +pub struct RDFT { + table: Vec<FFTComplex>, + fft: FFT, + fwd: bool, + size: usize, + fwd_fft: bool, +} + +fn crossadd(a: FFTComplex, b: FFTComplex) -> FFTComplex { + FFTComplex { re: a.re + b.re, im: a.im - b.im } +} + +impl RDFT { + /// Calculates RDFT. + pub fn do_rdft(&mut self, src: &[FFTComplex], dst: &mut [FFTComplex]) { + dst.copy_from_slice(src); + self.do_rdft_inplace(dst); + } + /// Calculates inplace RDFT. + pub fn do_rdft_inplace(&mut self, buf: &mut [FFTComplex]) { + if !self.fwd { + for n in 0..self.size/2 { + let in0 = buf[n + 1]; + let in1 = buf[self.size - n - 1]; + + let t0 = crossadd(in0, in1); + let t1 = FFTComplex { re: in1.im + in0.im, im: in1.re - in0.re }; + let tab = self.table[n]; + let t2 = FFTComplex { re: t1.im * tab.im + t1.re * tab.re, im: t1.im * tab.re - t1.re * tab.im }; + + buf[n + 1] = FFTComplex { re: t0.im - t2.im, im: t0.re - t2.re }; // (t0 - t2).conj().rotate() + buf[self.size - n - 1] = (t0 + t2).rotate(); + } + let a = buf[0].re; + let b = buf[0].im; + buf[0].re = a - b; + buf[0].im = a + b; + } + if self.fwd_fft { + self.fft.do_fft_inplace(buf); + } else { + self.fft.do_ifft_inplace(buf); + } + if self.fwd { + for n in 0..self.size/2 { + let in0 = buf[n + 1]; + let in1 = buf[self.size - n - 1]; + + let t0 = crossadd(in0, in1).scale(0.5); + let t1 = FFTComplex { re: in0.im + in1.im, im: in0.re - in1.re }; + let t2 = t1 * self.table[n]; + + buf[n + 1] = crossadd(t0, t2); + buf[self.size - n - 1] = FFTComplex { re: t0.re - t2.re, im: -(t0.im + t2.im) }; + } + let a = buf[0].re; + let b = buf[0].im; + buf[0].re = a + b; + buf[0].im = a - b; + } else { + for n in 0..self.size { + buf[n] = FFTComplex{ re: buf[n].im, im: buf[n].re }; + } + } + } +} + +/// [`RDFT`] context creator. +/// +/// [`RDFT`]: ./struct.FFT.html +pub struct RDFTBuilder { +} + +impl RDFTBuilder { + /// Constructs a new `RDFT` context. + pub fn new_rdft(size: usize, forward: bool, forward_fft: bool) -> RDFT { + let mut table: Vec<FFTComplex> = Vec::with_capacity(size / 4); + let (base, scale) = if forward { (consts::PI / (size as f32), 0.5) } else { (-consts::PI / (size as f32), 1.0) }; + for i in 0..size/2 { + table.push(FFTComplex::exp(base * ((i + 1) as f32)).scale(scale)); + } + let fft = FFTBuilder::new_fft(size, forward_fft); + RDFT { table, fft, size, fwd: forward, fwd_fft: forward_fft } + } +} + + +#[cfg(test)] +mod test { + use super::*; + + fn test_fft(size: usize) { + println!("testing FFT {}", size); + let mut fin: Vec<FFTComplex> = Vec::with_capacity(size); + let mut fout1: Vec<FFTComplex> = Vec::with_capacity(size); + let mut fout2: Vec<FFTComplex> = Vec::with_capacity(size); + fin.resize(size, FFTC_ZERO); + fout1.resize(size, FFTC_ZERO); + fout2.resize(size, FFTC_ZERO); + let mut fft = FFTBuilder::new_fft(size, true); + let mut seed: u32 = 42; + for i in 0..fin.len() { + seed = seed.wrapping_mul(1664525).wrapping_add(1013904223); + let val = (seed >> 16) as i16; + fin[i].re = (val as f32) / 256.0; + seed = seed.wrapping_mul(1664525).wrapping_add(1013904223); + let val = (seed >> 16) as i16; + fin[i].im = (val as f32) / 256.0; + } + fft.do_fft(&fin, &mut fout1); + fout2.copy_from_slice(&fin); + generic_fft(&mut fout2, true); + + for i in 0..fin.len() { + assert!((fout1[i].re - fout2[i].re).abs() < 1.0); + assert!((fout1[i].im - fout2[i].im).abs() < 1.0); + } + let mut ifft = FFTBuilder::new_fft(size, false); + ifft.do_ifft_inplace(&mut fout1); + generic_fft(&mut fout2, false); + + let sc = 1.0 / (size as f32); + for i in 0..fin.len() { + assert!((fin[i].re - fout1[i].re * sc).abs() < 1.0); + assert!((fin[i].im - fout1[i].im * sc).abs() < 1.0); + assert!((fout1[i].re - fout2[i].re).abs() * sc < 1.0); + assert!((fout1[i].im - fout2[i].im).abs() * sc < 1.0); + } + } + + #[test] + fn test_ffts() { + test_fft(3); + test_fft(5); + test_fft(16); + test_fft(15); + test_fft(60); + test_fft(256); + test_fft(240); + } + + #[test] + fn test_rdft() { + let mut fin: [FFTComplex; 128] = [FFTC_ZERO; 128]; + let mut fout1: [FFTComplex; 128] = [FFTC_ZERO; 128]; + let mut rdft = RDFTBuilder::new_rdft(fin.len(), true, true); + let mut seed: u32 = 42; + for i in 0..fin.len() { + seed = seed.wrapping_mul(1664525).wrapping_add(1013904223); + let val = (seed >> 16) as i16; + fin[i].re = (val as f32) / 256.0; + seed = seed.wrapping_mul(1664525).wrapping_add(1013904223); + let val = (seed >> 16) as i16; + fin[i].im = (val as f32) / 256.0; + } + rdft.do_rdft(&fin, &mut fout1); + let mut irdft = RDFTBuilder::new_rdft(fin.len(), false, true); + irdft.do_rdft_inplace(&mut fout1); + + for i in 0..fin.len() { + let tst = fout1[i].scale(0.5/(fout1.len() as f32)); + assert!((tst.re - fin[i].re).abs() < 1.0); + assert!((tst.im - fin[i].im).abs() < 1.0); + } + } +} diff --git a/nihav-codec-support/src/dsp/mdct.rs b/nihav-codec-support/src/dsp/mdct.rs new file mode 100644 index 0000000..e6ed3dc --- /dev/null +++ b/nihav-codec-support/src/dsp/mdct.rs @@ -0,0 +1,81 @@ +//! Modified Discrete Cosine transform functionality. +use std::f32::consts; +use super::fft::*; + +/// IMDCT working context. +pub struct IMDCT { + twiddle: Vec<FFTComplex>, + fft: FFT, + size: usize, + z: Vec<FFTComplex>, +} + +/* +fn imdct(src: &[f32], dst: &mut [f32], length: usize) { + for n in 0..length*2 { + dst[n] = 0.0; + for k in 0..length { + dst[n] += src[k] * (consts::PI / (length as f32) * ((n as f32) + 0.5 + ((length/2) as f32)) * ((k as f32) + 0.5)).cos(); + } + } +}*/ + +impl IMDCT { + /// Constructs a new instance of `IMDCT` context. + pub fn new(size: usize, scaledown: bool) -> Self { + let mut twiddle: Vec<FFTComplex> = Vec::with_capacity(size / 4); + let factor = 2.0 * consts::PI / ((8 * size) as f32); + let scale = if scaledown { (1.0 / (size as f32)).sqrt() } else { 1.0 }; + for k in 0..size/4 { + twiddle.push(FFTComplex::exp(factor * ((8 * k + 1) as f32)).scale(scale)); + } + let fft = FFTBuilder::new_fft(size/4, false); + let mut z: Vec<FFTComplex> = Vec::with_capacity(size / 2); + z.resize(size / 2, FFTC_ZERO); + IMDCT { twiddle, fft, size, z } + } + /// Calculates IMDCT. + pub fn imdct(&mut self, src: &[f32], dst: &mut [f32]) { + let size2 = self.size / 2; + let size4 = self.size / 4; + let size8 = self.size / 8; + for k in 0..size4 { + let c = FFTComplex { re: src[size2 - 2 * k - 1], im: src[ 2 * k] }; + self.z[k] = c * self.twiddle[k]; + } + self.fft.do_ifft_inplace(&mut self.z); + for k in 0..size4 { + self.z[k] *= self.twiddle[k]; + } + for n in 0..size8 { + dst[ 2 * n] = -self.z[size8 + n] .im; + dst[ 2 * n + 1] = self.z[size8 - n - 1].re; + dst[ size4 + 2 * n] = -self.z[ n] .re; + dst[ size4 + 2 * n + 1] = self.z[size4 - n - 1].im; + dst[ size2 + 2 * n] = -self.z[size8 + n] .re; + dst[ size2 + 2 * n + 1] = self.z[size8 - n - 1].im; + dst[3 * size4 + 2 * n] = self.z[ n] .im; + dst[3 * size4 + 2 * n + 1] = -self.z[size4 - n - 1].re; + } + } + /// Calculates only non-mirrored part of IMDCT. + pub fn imdct_half(&mut self, src: &[f32], dst: &mut [f32]) { + let size2 = self.size / 2; + let size4 = self.size / 4; + let size8 = self.size / 8; + for k in 0..size4 { + let c = FFTComplex { re: src[size2 - 2 * k - 1], im: src[ 2 * k] }; + self.z[k] = c * self.twiddle[k]; + } + self.fft.do_ifft_inplace(&mut self.z); + for k in 0..size4 { + self.z[k] *= self.twiddle[k]; + } + for n in 0..size8 { + dst[ 2 * n] = -self.z[ n] .re; + dst[ 2 * n + 1] = self.z[size4 - n - 1].im; + dst[size4 + 2 * n] = -self.z[size8 + n] .re; + dst[size4 + 2 * n + 1] = self.z[size8 - n - 1].im; + } + } +} diff --git a/nihav-codec-support/src/dsp/mod.rs b/nihav-codec-support/src/dsp/mod.rs new file mode 100644 index 0000000..2ff322d --- /dev/null +++ b/nihav-codec-support/src/dsp/mod.rs @@ -0,0 +1,11 @@ +//! DSP routines. +#[cfg(feature="dct")] +#[allow(clippy::erasing_op)] +pub mod dct; +#[cfg(feature="fft")] +#[allow(clippy::erasing_op)] +pub mod fft; +#[cfg(feature="mdct")] +pub mod mdct; +#[cfg(feature="dsp_window")] +pub mod window; diff --git a/nihav-codec-support/src/dsp/window.rs b/nihav-codec-support/src/dsp/window.rs new file mode 100644 index 0000000..eb350e3 --- /dev/null +++ b/nihav-codec-support/src/dsp/window.rs @@ -0,0 +1,59 @@ +//! Window generating functions. +use std::f32::consts; + +/// Known window types. +#[derive(Debug,Clone,Copy,PartialEq)] +pub enum WindowType { + /// Simple square window. + Square, + /// Simple sine window. + Sine, + /// Kaiser-Bessel derived window. + KaiserBessel(f32), +} + +/// Calculates window coefficients for the requested window type and size. +/// +/// Set `half` flag to calculate only the first half of the window. +pub fn generate_window(mode: WindowType, scale: f32, size: usize, half: bool, dst: &mut [f32]) { + match mode { + WindowType::Square => { + for n in 0..size { dst[n] = scale; } + }, + WindowType::Sine => { + let param = if half { + consts::PI / ((2 * size) as f32) + } else { + consts::PI / (size as f32) + }; + for n in 0..size { + dst[n] = (((n as f32) + 0.5) * param).sin() * scale; + } + }, + WindowType::KaiserBessel(alpha) => { + let dlen = if half { size as f32 } else { (size as f32) * 0.5 }; + let alpha2 = f64::from((alpha * consts::PI / dlen) * (alpha * consts::PI / dlen)); + + let mut kb: Vec<f64> = Vec::with_capacity(size); + let mut sum = 0.0; + for n in 0..size { + let b = bessel_i0(((n * (size - n)) as f64) * alpha2); + sum += b; + kb.push(sum); + } + sum += 1.0; + for n in 0..size { + dst[n] = (kb[n] / sum).sqrt() as f32; + } + }, + }; +} + +fn bessel_i0(inval: f64) -> f64 { + let mut val: f64 = 1.0; + for n in (1..64).rev() { + val *= inval / f64::from(n * n); + val += 1.0; + } + val +} diff --git a/nihav-codec-support/src/lib.rs b/nihav-codec-support/src/lib.rs new file mode 100644 index 0000000..e2c2ef6 --- /dev/null +++ b/nihav-codec-support/src/lib.rs @@ -0,0 +1,19 @@ +//! Code and data for easier development of NihAV decoders. +#[allow(clippy::cast_lossless)] +#[allow(clippy::identity_op)] +#[allow(clippy::too_many_arguments)] +#[allow(clippy::unreadable_literal)] +pub mod codecs; + +#[cfg(feature="dsp")] +#[allow(clippy::excessive_precision)] +#[allow(clippy::identity_op)] +#[allow(clippy::needless_range_loop)] +#[allow(clippy::unreadable_literal)] +pub mod dsp; + +pub mod data; + +pub mod test; + +extern crate nihav_core; diff --git a/nihav-codec-support/src/test/dec_video.rs b/nihav-codec-support/src/test/dec_video.rs new file mode 100644 index 0000000..cca4cb4 --- /dev/null +++ b/nihav-codec-support/src/test/dec_video.rs @@ -0,0 +1,536 @@ +//! Routines for testing decoders. +use std::fs::File; +use std::io::prelude::*; +use nihav_core::frame::*; +use nihav_core::codecs::*; +use nihav_core::demuxers::*; +//use nihav_core::io::byteio::*; +use nihav_core::scale::*; +use super::wavwriter::WavWriter; +use super::md5::MD5; +pub use super::ExpectedTestResult; + +const OUTPUT_PREFIX: &str = "assets/test_out"; + +fn write_pgmyuv(pfx: &str, strno: usize, num: u64, frm: NAFrameRef) { + if let NABufferType::None = frm.get_buffer() { return; } + let name = format!("{}/{}out{:02}_{:06}.pgm", OUTPUT_PREFIX, pfx, strno, num); + let mut ofile = File::create(name).unwrap(); + let buf = frm.get_buffer().get_vbuf().unwrap(); + let (w, h) = buf.get_dimensions(0); + let (w2, h2) = buf.get_dimensions(1); + let has_alpha = buf.get_info().get_format().has_alpha(); + let mut tot_h = h + h2; + if has_alpha { + tot_h += h; + } + if w2 > w/2 { + tot_h += h2; + } + let hdr = format!("P5\n{} {}\n255\n", w, tot_h); + ofile.write_all(hdr.as_bytes()).unwrap(); + let dta = buf.get_data(); + let ls = buf.get_stride(0); + let mut idx = 0; + let mut idx2 = w; + let is_flipped = buf.get_info().is_flipped(); + if is_flipped { + idx += h * ls; + idx2 += h * ls; + } + for _ in 0..h { + if is_flipped { + idx -= ls; + idx2 -= ls; + } + let line = &dta[idx..idx2]; + ofile.write_all(line).unwrap(); + if !is_flipped { + idx += ls; + idx2 += ls; + } + } + if w2 <= w/2 { + let pad: Vec<u8> = vec![0xFF; (w - w2 * 2) / 2]; + let mut base1 = buf.get_offset(1); + let stride1 = buf.get_stride(1); + let mut base2 = buf.get_offset(2); + let stride2 = buf.get_stride(2); + if is_flipped { + base1 += h2 * stride1; + base2 += h2 * stride2; + } + for _ in 0..h2 { + if is_flipped { + base1 -= stride1; + base2 -= stride2; + } + let bend1 = base1 + w2; + let line = &dta[base1..bend1]; + ofile.write_all(line).unwrap(); + ofile.write_all(pad.as_slice()).unwrap(); + + let bend2 = base2 + w2; + let line = &dta[base2..bend2]; + ofile.write_all(line).unwrap(); + ofile.write_all(pad.as_slice()).unwrap(); + + if !is_flipped { + base1 += stride1; + base2 += stride2; + } + } + } else { + let pad: Vec<u8> = vec![0xFF; w - w2]; + let mut base1 = buf.get_offset(1); + let stride1 = buf.get_stride(1); + if is_flipped { + base1 += h2 * stride1; + } + for _ in 0..h2 { + if is_flipped { + base1 -= stride1; + } + let bend1 = base1 + w2; + let line = &dta[base1..bend1]; + ofile.write_all(line).unwrap(); + ofile.write_all(pad.as_slice()).unwrap(); + if !is_flipped { + base1 += stride1; + } + } + let mut base2 = buf.get_offset(2); + let stride2 = buf.get_stride(2); + if is_flipped { + base2 += h2 * stride2; + } + for _ in 0..h2 { + if is_flipped { + base2 -= stride2; + } + let bend2 = base2 + w2; + let line = &dta[base2..bend2]; + ofile.write_all(line).unwrap(); + ofile.write_all(pad.as_slice()).unwrap(); + if !is_flipped { + base2 += stride2; + } + } + } + if has_alpha { + let ls = buf.get_stride(3); + let mut idx = buf.get_offset(3); + let mut idx2 = idx + w; + if is_flipped { + idx += h * ls; + idx2 += h * ls; + } + for _ in 0..h { + if is_flipped { + idx -= ls; + idx2 -= ls; + } + let line = &dta[idx..idx2]; + ofile.write_all(line).unwrap(); + if !is_flipped { + idx += ls; + idx2 += ls; + } + } + } +} + +fn write_palppm(pfx: &str, strno: usize, num: u64, frm: NAFrameRef) { + let name = format!("{}/{}out{:02}_{:06}.ppm", OUTPUT_PREFIX, pfx, strno, num); + let mut ofile = File::create(name).unwrap(); + let buf = frm.get_buffer().get_vbuf().unwrap(); + let (w, h) = buf.get_dimensions(0); + let paloff = buf.get_offset(1); + let hdr = format!("P6\n{} {}\n255\n", w, h); + ofile.write_all(hdr.as_bytes()).unwrap(); + let dta = buf.get_data(); + let ls = buf.get_stride(0); + let offs: [usize; 3] = [ + buf.get_info().get_format().get_chromaton(0).unwrap().get_offset() as usize, + buf.get_info().get_format().get_chromaton(1).unwrap().get_offset() as usize, + buf.get_info().get_format().get_chromaton(2).unwrap().get_offset() as usize + ]; + let mut idx = 0; + let mut line: Vec<u8> = vec![0; w * 3]; + for _ in 0..h { + let src = &dta[idx..(idx+w)]; + for x in 0..w { + let pix = src[x] as usize; + line[x * 3 + 0] = dta[paloff + pix * 3 + offs[0]]; + line[x * 3 + 1] = dta[paloff + pix * 3 + offs[1]]; + line[x * 3 + 2] = dta[paloff + pix * 3 + offs[2]]; + } + ofile.write_all(line.as_slice()).unwrap(); + idx += ls; + } +} + +fn write_ppm(pfx: &str, strno: usize, num: u64, frm: NAFrameRef) { + let name = format!("{}/{}out{:02}_{:06}.ppm", OUTPUT_PREFIX, pfx, strno, num); + let mut ofile = File::create(name).unwrap(); + let info = frm.get_buffer().get_video_info().unwrap(); + let mut dpic = alloc_video_buffer(NAVideoInfo::new(info.get_width(), info.get_height(), false, RGB24_FORMAT), 0).unwrap(); + let ifmt = ScaleInfo { width: info.get_width(), height: info.get_height(), fmt: info.get_format() }; + let ofmt = ScaleInfo { width: info.get_width(), height: info.get_height(), fmt: RGB24_FORMAT }; + let mut scaler = NAScale::new(ifmt, ofmt).unwrap(); + scaler.convert(&frm.get_buffer(), &mut dpic).unwrap(); + let buf = dpic.get_vbuf().unwrap(); + let (w, h) = buf.get_dimensions(0); + let hdr = format!("P6\n{} {}\n255\n", w, h); + ofile.write_all(hdr.as_bytes()).unwrap(); + let dta = buf.get_data(); + let stride = buf.get_stride(0); + for src in dta.chunks(stride) { + ofile.write_all(&src[0..w*3]).unwrap(); + } +} + +/*fn open_wav_out(pfx: &str, strno: usize) -> WavWriter { + let name = format!("assets/{}out{:02}.wav", pfx, strno); + let mut file = File::create(name).unwrap(); + let mut fw = FileWriter::new_write(&mut file); + let mut wr = ByteWriter::new(&mut fw); + WavWriter::new(&mut wr) +}*/ + +/// Tests decoding of provided file and optionally outputs video frames as PNM (PPM for RGB video, PGM for YUV). +/// +/// This function expects the following arguments: +/// * `demuxer` - container format name (used to find proper demuxer for it) +/// * `name` - input file name +/// * `limit` - optional PTS value after which decoding is stopped +/// * `decode_video`/`decode_audio` - flags for enabling video/audio decoding +/// * `video_pfx` - prefix for video frames written as pictures (if enabled then output picture names should look like `<crate_name>/assets/test_out/PFXout00_000000.ppm` +/// * `dmx_reg` and `dec_reg` - registered demuxers and decoders that should contain demuxer and decoder(s) needed to decode the provided file. +/// +/// Since the function is intended for tests, it will panic instead of returning an error. +pub fn test_file_decoding(demuxer: &str, name: &str, limit: Option<u64>, + decode_video: bool, decode_audio: bool, + video_pfx: Option<&str>, + dmx_reg: &RegisteredDemuxers, dec_reg: &RegisteredDecoders) { + let dmx_f = dmx_reg.find_demuxer(demuxer).unwrap(); + let mut file = File::open(name).unwrap(); + let mut fr = FileReader::new_read(&mut file); + let mut br = ByteReader::new(&mut fr); + let mut dmx = create_demuxer(dmx_f, &mut br).unwrap(); + + let mut decs: Vec<Option<(Box<NADecoderSupport>, Box<dyn NADecoder>)>> = Vec::new(); + for i in 0..dmx.get_num_streams() { + let s = dmx.get_stream(i).unwrap(); + let info = s.get_info(); + let decfunc = dec_reg.find_decoder(info.get_name()); + if let Some(df) = decfunc { + if (decode_video && info.is_video()) || (decode_audio && info.is_audio()) { + let mut dec = (df)(); + let mut dsupp = Box::new(NADecoderSupport::new()); + dec.init(&mut dsupp, info).unwrap(); + decs.push(Some((dsupp, dec))); + } else { + decs.push(None); + } + } else { + decs.push(None); + } + } + + loop { + let pktres = dmx.get_frame(); + if let Err(e) = pktres { + if e == DemuxerError::EOF { break; } + panic!("error"); + } + let pkt = pktres.unwrap(); + let streamno = pkt.get_stream().get_id() as usize; + if let Some((ref mut dsupp, ref mut dec)) = decs[streamno] { + if let (Some(lim), Some(ppts)) = (limit, pkt.get_pts()) { + if ppts > lim { break; } + } + let frm = dec.decode(dsupp, &pkt).unwrap(); + if pkt.get_stream().get_info().is_video() && video_pfx.is_some() && frm.get_frame_type() != FrameType::Skip { + let pfx = video_pfx.unwrap(); + let pts = if let Some(fpts) = frm.get_pts() { fpts } else { pkt.get_pts().unwrap() }; + let vinfo = frm.get_buffer().get_video_info().unwrap(); + if vinfo.get_format().is_paletted() { + write_palppm(pfx, streamno, pts, frm); + } else if vinfo.get_format().get_model().is_yuv() { + write_pgmyuv(pfx, streamno, pts, frm); + } else if vinfo.get_format().get_model().is_rgb() { + write_ppm(pfx, streamno, pts, frm); + } else { +panic!(" unknown format"); + } + } + } + } +} + +/// Tests audio decoder with the content in the provided file and optionally outputs decoded audio. +/// +/// The syntax is very similar to [`test_file_decoding`] except that it is intended for testing audio codecs. +/// +/// Since the function is intended for tests, it will panic instead of returning an error. +/// +/// [`test_file_decoding`]: ./fn.test_file_decoding.html +pub fn test_decode_audio(demuxer: &str, name: &str, limit: Option<u64>, audio_pfx: Option<&str>, + dmx_reg: &RegisteredDemuxers, dec_reg: &RegisteredDecoders) { + let dmx_f = dmx_reg.find_demuxer(demuxer).unwrap(); + let mut file = File::open(name).unwrap(); + let mut fr = FileReader::new_read(&mut file); + let mut br = ByteReader::new(&mut fr); + let mut dmx = create_demuxer(dmx_f, &mut br).unwrap(); + + let mut decs: Vec<Option<(Box<NADecoderSupport>, Box<dyn NADecoder>)>> = Vec::new(); + for i in 0..dmx.get_num_streams() { + let s = dmx.get_stream(i).unwrap(); + let info = s.get_info(); + let decfunc = dec_reg.find_decoder(info.get_name()); + if let Some(df) = decfunc { + if info.is_audio() { + let mut dec = (df)(); + let mut dsupp = Box::new(NADecoderSupport::new()); + dec.init(&mut dsupp, info).unwrap(); + decs.push(Some((dsupp, dec))); + } else { + decs.push(None); + } + } else { + decs.push(None); + } + } + + if let Some(audio_pfx) = audio_pfx { + let name = format!("{}/{}out.wav", OUTPUT_PREFIX, audio_pfx); + let file = File::create(name).unwrap(); + let mut fw = FileWriter::new_write(file); + let mut wr = ByteWriter::new(&mut fw); + let mut wwr = WavWriter::new(&mut wr); + let mut wrote_header = false; + + loop { + let pktres = dmx.get_frame(); + if let Err(e) = pktres { + if e == DemuxerError::EOF { break; } + panic!("error"); + } + let pkt = pktres.unwrap(); + if limit.is_some() && pkt.get_pts().is_some() && pkt.get_pts().unwrap() > limit.unwrap() { + break; + } + let streamno = pkt.get_stream().get_id() as usize; + if let Some((ref mut dsupp, ref mut dec)) = decs[streamno] { + let frm = dec.decode(dsupp, &pkt).unwrap(); + if frm.get_info().is_audio() { + if !wrote_header { + wwr.write_header(frm.get_info().as_ref().get_properties().get_audio_info().unwrap()).unwrap(); + wrote_header = true; + } + wwr.write_frame(frm.get_buffer()).unwrap(); + } + } + } + } else { + loop { + let pktres = dmx.get_frame(); + if let Err(e) = pktres { + if e == DemuxerError::EOF { break; } + panic!("error"); + } + let pkt = pktres.unwrap(); + if limit.is_some() && pkt.get_pts().is_some() && pkt.get_pts().unwrap() > limit.unwrap() { + break; + } + let streamno = pkt.get_stream().get_id() as usize; + if let Some((ref mut dsupp, ref mut dec)) = decs[streamno] { + let _ = dec.decode(dsupp, &pkt).unwrap(); + } + } + } +} + +fn frame_checksum(md5: &mut MD5, frm: NAFrameRef) { + match frm.get_buffer() { + NABufferType::Video(ref vb) => { + md5.update_hash(vb.get_data()); + }, + NABufferType::Video16(ref vb) => { + let mut samp = [0u8; 2]; + let data = vb.get_data(); + for el in data.iter() { + samp[0] = (*el >> 8) as u8; + samp[1] = (*el >> 0) as u8; + md5.update_hash(&samp); + } + }, + NABufferType::Video32(ref vb) => { + let mut samp = [0u8; 4]; + let data = vb.get_data(); + for el in data.iter() { + samp[0] = (*el >> 24) as u8; + samp[1] = (*el >> 16) as u8; + samp[2] = (*el >> 8) as u8; + samp[3] = (*el >> 0) as u8; + md5.update_hash(&samp); + } + }, + NABufferType::VideoPacked(ref vb) => { + md5.update_hash(vb.get_data()); + }, + NABufferType::AudioU8(ref ab) => { + md5.update_hash(ab.get_data()); + }, + NABufferType::AudioI16(ref ab) => { + let mut samp = [0u8; 2]; + let data = ab.get_data(); + for el in data.iter() { + samp[0] = (*el >> 8) as u8; + samp[1] = (*el >> 0) as u8; + md5.update_hash(&samp); + } + }, + NABufferType::AudioI32(ref ab) => { + let mut samp = [0u8; 4]; + let data = ab.get_data(); + for el in data.iter() { + samp[0] = (*el >> 24) as u8; + samp[1] = (*el >> 16) as u8; + samp[2] = (*el >> 8) as u8; + samp[3] = (*el >> 0) as u8; + md5.update_hash(&samp); + } + }, + NABufferType::AudioF32(ref ab) => { + let mut samp = [0u8; 4]; + let data = ab.get_data(); + for el in data.iter() { + let bits = el.to_bits(); + samp[0] = (bits >> 24) as u8; + samp[1] = (bits >> 16) as u8; + samp[2] = (bits >> 8) as u8; + samp[3] = (bits >> 0) as u8; + md5.update_hash(&samp); + } + }, + NABufferType::AudioPacked(ref ab) => { + md5.update_hash(ab.get_data()); + }, + NABufferType::Data(ref db) => { + md5.update_hash(db.as_ref()); + }, + NABufferType::None => {}, + }; +} + +/// Tests decoder for requested codec in provided file. +/// +/// This functions tries to decode a stream corresponding to `dec_name` codec in input file and validate the results against expected ones. +/// +/// Since the function is intended for tests, it will panic instead of returning an error. +/// +/// # Examples +/// +/// Test RealVideo 4 decoder in test stream: +/// ```no_run +/// use nihav_codec_support::test::ExpectedTestResult; +/// use nihav_codec_support::test::dec_video::test_decoding; +/// use nihav_core::codecs::RegisteredDecoders; +/// use nihav_core::demuxers::RegisteredDemuxers; +/// +/// let mut dmx_reg = RegisteredDemuxers::new(); +/// let mut dec_reg = RegisteredDecoders::new(); +/// // ... register RealMedia demuxers and RealVideo decoders ... +/// test_decoding("realmedia", "rv40", "assets/test_file.rmvb", None, &dmx_reg, &dec_reg, ExpectedTestResult::MD5([0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f])); +/// ``` +pub fn test_decoding(demuxer: &str, dec_name: &str, filename: &str, limit: Option<u64>, + dmx_reg: &RegisteredDemuxers, dec_reg: &RegisteredDecoders, + test: ExpectedTestResult) { + let dmx_f = dmx_reg.find_demuxer(demuxer).unwrap(); + let mut file = File::open(filename).unwrap(); + let mut fr = FileReader::new_read(&mut file); + let mut br = ByteReader::new(&mut fr); + let mut dmx = create_demuxer(dmx_f, &mut br).unwrap(); + + let mut decs: Vec<Option<(Box<NADecoderSupport>, Box<dyn NADecoder>)>> = Vec::new(); + let mut found = false; + for i in 0..dmx.get_num_streams() { + let s = dmx.get_stream(i).unwrap(); + let info = s.get_info(); +println!("stream {} codec {} / {}", i, info.get_name(), dec_name); + if !found && (info.get_name() == dec_name) { + let decfunc = dec_reg.find_decoder(info.get_name()); + if let Some(df) = decfunc { + let mut dec = (df)(); + let mut dsupp = Box::new(NADecoderSupport::new()); + dec.init(&mut dsupp, info).unwrap(); + decs.push(Some((dsupp, dec))); + found = true; + } else { + decs.push(None); + } + } else { + decs.push(None); + } + } + + let mut md5 = MD5::new(); + let mut frameiter = if let ExpectedTestResult::MD5Frames(ref vec) = test { + Some(vec.iter()) + } else { + None + }; + loop { + let pktres = dmx.get_frame(); + if let Err(e) = pktres { + if e == DemuxerError::EOF { break; } + panic!("error"); + } + let pkt = pktres.unwrap(); + let streamno = pkt.get_stream().get_id() as usize; + if let Some((ref mut dsupp, ref mut dec)) = decs[streamno] { + if limit.is_some() && pkt.get_pts().is_some() && pkt.get_pts().unwrap() > limit.unwrap() { + break; + } + let frm = dec.decode(dsupp, &pkt).unwrap(); + match &test { + ExpectedTestResult::Decodes => {}, + ExpectedTestResult::MD5(_) => { frame_checksum(&mut md5, frm); }, + ExpectedTestResult::MD5Frames(_) => { + md5 = MD5::new(); + frame_checksum(&mut md5, frm); + md5.finish(); + if let Some(ref mut iter) = frameiter { + let ret = iter.next(); + if ret.is_none() { break; } + let ref_hash = ret.unwrap(); + let mut hash = [0u32; 4]; + md5.get_hash(&mut hash); +println!("frame pts {:?} hash {}", pkt.get_pts(), md5); + assert_eq!(&hash, ref_hash); + } + }, + ExpectedTestResult::GenerateMD5Frames => { + md5 = MD5::new(); + frame_checksum(&mut md5, frm); + md5.finish(); + let mut hash = [0u32; 4]; + md5.get_hash(&mut hash); +println!("frame pts {:?} hash [0x{:08x}, 0x{:08x}, 0x{:08x}, 0x{:08x}],", pkt.get_pts(), hash[0], hash[1], hash[2], hash[3]); + }, + }; + } + } + if let ExpectedTestResult::MD5(ref ref_hash) = test { + md5.finish(); + let mut hash = [0u32; 4]; + md5.get_hash(&mut hash); +println!("full hash {}", md5); + assert_eq!(&hash, ref_hash); + } + if let ExpectedTestResult::GenerateMD5Frames = test { + panic!("generated hashes"); + } +} diff --git a/nihav-codec-support/src/test/md5.rs b/nihav-codec-support/src/test/md5.rs new file mode 100644 index 0000000..af9a155 --- /dev/null +++ b/nihav-codec-support/src/test/md5.rs @@ -0,0 +1,172 @@ +use std::fmt; + +const MD5_SHIFTS: [u8; 64] = [ + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 +]; + +const MD5_K: [u32; 64] = [ + 0xD76AA478, 0xE8C7B756, 0x242070DB, 0xC1BDCEEE, 0xF57C0FAF, 0x4787C62A, 0xA8304613, 0xFD469501, + 0x698098D8, 0x8B44F7AF, 0xFFFF5BB1, 0x895CD7BE, 0x6B901122, 0xFD987193, 0xA679438E, 0x49B40821, + 0xF61E2562, 0xC040B340, 0x265E5A51, 0xE9B6C7AA, 0xD62F105D, 0x02441453, 0xD8A1E681, 0xE7D3FBC8, + 0x21E1CDE6, 0xC33707D6, 0xF4D50D87, 0x455A14ED, 0xA9E3E905, 0xFCEFA3F8, 0x676F02D9, 0x8D2A4C8A, + 0xFFFA3942, 0x8771F681, 0x6D9D6122, 0xFDE5380C, 0xA4BEEA44, 0x4BDECFA9, 0xF6BB4B60, 0xBEBFBC70, + 0x289B7EC6, 0xEAA127FA, 0xD4EF3085, 0x04881D05, 0xD9D4D039, 0xE6DB99E5, 0x1FA27CF8, 0xC4AC5665, + 0xF4292244, 0x432AFF97, 0xAB9423A7, 0xFC93A039, 0x655B59C3, 0x8F0CCC92, 0xFFEFF47D, 0x85845DD1, + 0x6FA87E4F, 0xFE2CE6E0, 0xA3014314, 0x4E0811A1, 0xF7537E82, 0xBD3AF235, 0x2AD7D2BB, 0xEB86D391 +]; + +const INITIAL_MD5_HASH: [u32; 4] = [ 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476 ]; + +fn box0(b: u32, c: u32, d: u32) -> u32 { (b & c) | (!b & d) } +fn box1(b: u32, c: u32, d: u32) -> u32 { (d & b) | (!d & c) } +fn box2(b: u32, c: u32, d: u32) -> u32 { b ^ c ^ d } +fn box3(b: u32, c: u32, d: u32) -> u32 { c ^ (b | !d) } + +#[derive(Clone)] +pub struct MD5 { + pub hash: [u32; 4], + inwords: [u32; 16], + buf: [u8; 64], + pos: usize, + count: usize, +} + +impl PartialEq for MD5 { + fn eq(&self, other: &Self) -> bool { + self.hash == other.hash + } +} + +impl Eq for MD5 { } + +impl fmt::Display for MD5 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:08x}{:08x}{:08x}{:08x}", self.hash[0].swap_bytes(), self.hash[1].swap_bytes(), self.hash[2].swap_bytes(), self.hash[3].swap_bytes()) + } +} + +macro_rules! round { + ($a: ident, $b: ident, $c: ident, $d: ident, $box: ident, $k: expr, $inval: expr) => { + let f = $box($b, $c, $d).wrapping_add($a).wrapping_add(MD5_K[$k]).wrapping_add($inval); + $a = $d; + $d = $c; + $c = $b; + $b = $b.wrapping_add(f.rotate_left(MD5_SHIFTS[$k].into())); + } +} + +#[allow(dead_code)] +impl MD5 { + pub fn new() -> Self { + Self { + hash: INITIAL_MD5_HASH, + inwords: [0; 16], + buf: [0; 64], + pos: 0, + count: 0, + } + } + fn calc_one_block(&mut self) { + let mut a = self.hash[0]; + let mut b = self.hash[1]; + let mut c = self.hash[2]; + let mut d = self.hash[3]; + + for (out, src) in self.inwords.iter_mut().zip(self.buf.chunks_exact(4)) { + *out = (u32::from(src[0]) << 0) | + (u32::from(src[1]) << 8) | + (u32::from(src[2]) << 16) | + (u32::from(src[3]) << 24); + } + + for k in 0..16 { round!(a, b, c, d, box0, k, self.inwords[k]); } + for k in 16..32 { round!(a, b, c, d, box1, k, self.inwords[(5 * k + 1) & 0xF]); } + for k in 32..48 { round!(a, b, c, d, box2, k, self.inwords[(3 * k + 5) & 0xF]); } + for k in 48..64 { round!(a, b, c, d, box3, k, self.inwords[(7 * k) & 0xF]); } + + self.hash[0] = self.hash[0].wrapping_add(a); + self.hash[1] = self.hash[1].wrapping_add(b); + self.hash[2] = self.hash[2].wrapping_add(c); + self.hash[3] = self.hash[3].wrapping_add(d); + + self.pos = 0; + } + pub fn update_hash(&mut self, src: &[u8]) { + for byte in src.iter() { + self.buf[self.pos] = *byte; + self.pos += 1; + if self.pos == 64 { + self.calc_one_block(); + } + } + self.count += src.len(); + } + pub fn finish(&mut self) { + self.buf[self.pos] = 0x80; + self.pos += 1; + if self.pos > 48 { + while self.pos < 64 { + self.buf[self.pos] = 0x00; + self.pos += 1; + } + self.calc_one_block(); + } + while self.pos < 64 { + self.buf[self.pos] = 0x00; + self.pos += 1; + } + for i in 0..8 { + self.buf[56 + i] = ((self.count * 8) >> (i * 8)) as u8; + } + self.calc_one_block(); + } + pub fn get_hash(&self, dst: &mut [u32; 4]) { + for (dst, src) in dst.iter_mut().zip(self.hash.iter()) { + *dst = src.swap_bytes(); + } + } + pub fn get_hash_bytes(&self, dst: &mut [u8; 4]) { + for (dst, src) in dst.chunks_exact_mut(4).zip(self.hash.iter()) { + dst[0] = (*src >> 0) as u8; + dst[1] = (*src >> 8) as u8; + dst[2] = (*src >> 16) as u8; + dst[3] = (*src >> 24) as u8; + } + } + pub fn calculate_hash(src: &[u8], hash: &mut [u32; 4]) { + let mut md5 = Self::new(); + md5.update_hash(src); + md5.finish(); + md5.get_hash(hash); + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_md5() { + let mut hash = [0u32; 4]; + + MD5::calculate_hash(&[], &mut hash); + assert_eq!(hash, [ 0xD41D8CD9, 0x8F00B204, 0xE9800998, 0xECF8427E ]); + + MD5::calculate_hash(b"abc", &mut hash); + assert_eq!(hash, [ 0x90015098, 0x3CD24FB0, 0xD6963F7D, 0x28E17F72 ]); + + MD5::calculate_hash(b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", &mut hash); + assert_eq!(hash, [ 0x8215EF07, 0x96A20BCA, 0xAAE116D3, 0x876C664A ]); + + let mut md5 = MD5::new(); + for _ in 0..1000000 { + md5.update_hash(b"a"); + } + md5.finish(); + md5.get_hash(&mut hash); + assert_eq!(hash, [ 0x7707D6AE, 0x4E027C70, 0xEEA2A935, 0xC2296F21 ]); + } +} diff --git a/nihav-codec-support/src/test/mod.rs b/nihav-codec-support/src/test/mod.rs new file mode 100644 index 0000000..367be24 --- /dev/null +++ b/nihav-codec-support/src/test/mod.rs @@ -0,0 +1,21 @@ +//! Decoder testing functionality. +//! +//! This module provides functions that may be used in internal test to check that decoders produce output and that they produce expected output. +pub mod dec_video; +pub mod wavwriter; + +mod md5; // for internal checksums only + +/// Decoder testing modes. +/// +/// NihAV has its own MD5 hasher that calculates hash for input frame in a deterministic way (i.e. no endianness issues) and it outputs hash as `[u32; 4]`. During testing the resulting hash will be printed out so when the test fails you can find what was the calculated hash (and use it as a reference if you are implementing a new test). +pub enum ExpectedTestResult { + /// Decoding runs without errors. + Decodes, + /// Full decoded output is expected to be equal to this MD5 hash. + MD5([u32; 4]), + /// Each decoded frame hash should be equal to the corresponding MD5 hash. + MD5Frames(Vec<[u32; 4]>), + /// Test function should report decoded frame hashes to be used as the reference later. + GenerateMD5Frames, +} diff --git a/nihav-codec-support/src/test/wavwriter.rs b/nihav-codec-support/src/test/wavwriter.rs new file mode 100644 index 0000000..982fbc3 --- /dev/null +++ b/nihav-codec-support/src/test/wavwriter.rs @@ -0,0 +1,122 @@ +//! Audio output in WAV format. +use nihav_core::io::byteio::*; +use nihav_core::frame::*; +use std::io::SeekFrom; + +/// WAVE output writer. +pub struct WavWriter<'a> { + io: &'a mut ByteWriter<'a>, + data_pos: u64, +} + +fn write_byte(wr: &mut ByteWriter, sample: u8) -> ByteIOResult<()> { + wr.write_byte(sample) +} + +fn write_s16(wr: &mut ByteWriter, sample: i16) -> ByteIOResult<()> { + wr.write_u16le(sample as u16) +} + +fn write_s32(wr: &mut ByteWriter, sample: i32) -> ByteIOResult<()> { + wr.write_u16le((sample >> 16) as u16) +} + +fn write_f32(wr: &mut ByteWriter, sample: f32) -> ByteIOResult<()> { + let mut out = (sample * 32768.0) as i32; + if out < -32768 { out = -32768; } + if out > 32767 { out = 32767; } + if out < 0 { out += 65536; } + wr.write_u16le(out as u16) +} + +macro_rules! write_data { + ($wr:expr, $buf:expr, $write:ident) => ({ + let len = $buf.get_length(); + let ainfo = $buf.get_info(); + let nch = ainfo.get_channels() as usize; + let mut offs: Vec<usize> = Vec::with_capacity(nch); + for ch in 0..nch { offs.push($buf.get_offset(ch)); } + let data = $buf.get_data(); + + for i in 0..len { + for ch in 0..nch { + let sample = data[offs[ch] + i]; + $write($wr, sample)?; + } + } + }) +} + +impl<'a> WavWriter<'a> { + /// Constructs a new `WavWriter` instance. + pub fn new(io: &'a mut ByteWriter<'a>) -> Self { + WavWriter { io, data_pos: 0 } + } + /// Writes audio format information to the file header. + /// + /// This function should be called exactly once before writing actual audio data. + pub fn write_header(&mut self, ainfo: NAAudioInfo) -> ByteIOResult<()> { + let bits = ainfo.get_format().get_bits() as usize; + + self.io.write_buf(b"RIFF")?; + self.io.write_u32le(0)?; + self.io.write_buf(b"WAVE")?; + + self.io.write_buf(b"fmt ")?; + self.io.write_u32le(16)?; + self.io.write_u16le(0x0001)?; // PCM + self.io.write_u16le(u16::from(ainfo.get_channels()))?; + self.io.write_u32le(ainfo.get_sample_rate())?; + + if bits < 16 { + self.io.write_u32le(u32::from(ainfo.get_channels()) * ainfo.get_sample_rate())?; + self.io.write_u16le(u16::from(ainfo.get_channels()))?; // block align + self.io.write_u16le(8)?; + } else { + self.io.write_u32le(2 * u32::from(ainfo.get_channels()) * ainfo.get_sample_rate())?; + self.io.write_u16le(u16::from(2 * ainfo.get_channels()))?; // block align + self.io.write_u16le(16)?; + } + + self.io.write_buf(b"data")?; + self.io.write_u32le(0)?; + + self.data_pos = self.io.tell(); + Ok(()) + } + /// Writes audio data. + pub fn write_frame(&mut self, abuf: NABufferType) -> ByteIOResult<()> { + match abuf { + NABufferType::AudioU8(ref buf) => { + write_data!(self.io, buf, write_byte); + } + NABufferType::AudioI16(ref buf) => { + write_data!(self.io, buf, write_s16); + } + NABufferType::AudioI32(ref buf) => { + write_data!(self.io, buf, write_s32); + } + NABufferType::AudioF32(ref buf) => { + write_data!(self.io, buf, write_f32); + } + NABufferType::AudioPacked(ref buf) => { + self.io.write_buf(buf.get_data().as_slice())?; + } + _ => {}, + }; + Ok(()) + } +} + +impl<'a> Drop for WavWriter<'a> { + #[allow(unused_variables)] + fn drop(&mut self) { + let size = self.io.tell(); + if (self.data_pos > 0) && (size >= self.data_pos) { + let res = self.io.seek(SeekFrom::Start(4)); + let res = self.io.write_u32le((size - 8) as u32); + let res = self.io.seek(SeekFrom::Start(self.data_pos - 4)); + let res = self.io.write_u32le(((size as u64) - self.data_pos) as u32); + } + } +} |