Commit ea4bf00c authored by Timothy B. Terriberry's avatar Timothy B. Terriberry Committed by David Michael Barr
Browse files

Add a skeleton rate control API.

This only implements the "constant quantizer" strategy, and uses
 the same per-frame-type QP index adjustments as the current code.
However, it is one step towards the true quantizer-based
 adjustments required by the rate control model.

This should result in no change in encoded quality.
parent a6132e1b
......@@ -11,6 +11,7 @@ use bitstream_io::*;
use crate::encoder::*;
use crate::metrics::calculate_frame_psnr;
use crate::partition::*;
use crate::rate::RCState;
use crate::scenechange::SceneChangeDetector;
use self::EncoderStatus::*;
......@@ -354,6 +355,7 @@ impl Config {
segment_start_frame: 0,
keyframe_detector: SceneChangeDetector::new(self.enc.bit_depth),
config: *self,
rc_state: RCState::new()
}
}
}
......@@ -377,6 +379,7 @@ pub struct Context {
segment_start_frame: u64,
keyframe_detector: SceneChangeDetector,
pub config: Config,
rc_state: RCState
}
#[derive(Clone, Copy, Debug)]
......@@ -611,6 +614,9 @@ impl Context {
self.idx += 1;
if let Some(frame) = f.clone() {
let fti = fi.get_frame_subtype();
let qps = self.rc_state.select_qi(fi, fti);
fi.set_quantizers(&qps);
let mut fs = FrameState::new_with_frame(fi, frame.clone());
let data = encode_frame(fi, &mut fs);
......
......@@ -18,6 +18,9 @@ use crate::me::*;
use crate::partition::*;
use crate::plane::*;
use crate::quantize::*;
use crate::rate::QuantizerParameters;
use crate::rate::FRAME_SUBTYPE_I;
use crate::rate::FRAME_SUBTYPE_P;
use crate::rdo::*;
use crate::segmentation::*;
use crate::transform::*;
......@@ -584,6 +587,17 @@ pub struct FrameInvariants {
pub enable_early_exit: bool,
}
fn pos_to_lvl(pos: u64, pyramid_depth: u64) -> u64 {
// Derive level within pyramid for a frame with a given coding order position
// For example, with a pyramid of depth 2, the 2 least significant bits of the
// position determine the level:
// 00 -> 0
// 01 -> 2
// 10 -> 1
// 11 -> 2
pyramid_depth - (pos | (1 << pyramid_depth)).trailing_zeros() as u64
}
impl FrameInvariants {
pub fn new(config: EncoderConfig, sequence: Sequence) -> FrameInvariants {
// Speed level decides the minimum partition size, i.e. higher speed --> larger min partition size,
......@@ -660,10 +674,6 @@ impl FrameInvariants {
fi.show_frame = true;
fi.show_existing_frame = false;
fi.frame_to_show_map_idx = 0;
let q_boost = 15;
let qi = (fi.config.quantizer.max(1 + q_boost).min(255 + q_boost)
- q_boost) as u8;
fi.set_quantizer(qi);
fi.primary_ref_frame = PRIMARY_REF_NONE;
fi.number = segment_start_frame;
for i in 0..INTER_REFS_PER_FRAME {
......@@ -721,17 +731,6 @@ impl FrameInvariants {
return (fi, false);
}
fn pos_to_lvl(pos: u64, pyramid_depth: u64) -> u64 {
// Derive level within pyramid for a frame with a given coding order position
// For example, with a pyramid of depth 2, the 2 least significant bits of the
// position determine the level:
// 00 -> 0
// 01 -> 2
// 10 -> 1
// 11 -> 2
pyramid_depth - (pos | (1 << pyramid_depth)).trailing_zeros() as u64
}
let lvl = if !inter_cfg.reorder {
0
} else if inter_cfg.idx_in_group < inter_cfg.pyramid_depth {
......@@ -758,9 +757,6 @@ impl FrameInvariants {
1 << slot_idx
};
let q_drop = 15 * lvl as usize;
let qi = (fi.config.quantizer.min(255 - q_drop) + q_drop) as u8;
fi.set_quantizer(qi);
let second_ref_frame = if !inter_cfg.multiref {
NONE_FRAME
} else if !inter_cfg.reorder || inter_cfg.idx_in_group == 0 {
......@@ -821,21 +817,43 @@ impl FrameInvariants {
(fi, true)
}
pub fn set_quantizer(&mut self, qi: u8) {
self.base_q_idx = qi;
pub fn get_frame_subtype(&self) -> usize {
if self.frame_type == FrameType::KEY {
FRAME_SUBTYPE_I
} else {
let inter_cfg = self.inter_cfg.unwrap();
let lvl = if !inter_cfg.reorder {
0
} else if inter_cfg.idx_in_group < inter_cfg.pyramid_depth {
inter_cfg.idx_in_group
} else {
pos_to_lvl(
inter_cfg.idx_in_group - inter_cfg.pyramid_depth + 1,
inter_cfg.pyramid_depth
)
};
FRAME_SUBTYPE_P + (lvl as usize)
}
}
pub fn set_quantizers(&mut self, qps: &QuantizerParameters) {
self.base_q_idx = qps.ac_qi;
// TODO: Separate qi values for each color plane.
if self.frame_type != FrameType::KEY {
self.cdef_bits = 3 - ((self.base_q_idx.max(128) - 128) >> 5);
} else {
self.cdef_bits = 3;
}
let q = dc_q(self.base_q_idx, self.dc_delta_q[0], self.sequence.bit_depth)
as f64;
// Convert q into Q0 precision, given that libaom quantizers are Q3
let q0 = q / 8.0;
// Lambda formula from doc/theoretical_results.lyx in the daala repo.
// Use Q0 quantizer since lambda will be applied to Q0 pixel domain.
self.lambda = q0 * q0 * std::f64::consts::LN_2 / 6.0;
self.base_q_idx = qps.ac_qi;
// TODO: Separate qi values for each color plane.
debug_assert!(qps.dc_qi as i32 - qps.ac_qi as i32 >= -128);
debug_assert!((qps.dc_qi as i32 - qps.ac_qi as i32) < 128);
for pi in 0..3 {
self.dc_delta_q[pi] = (qps.dc_qi as i32 - qps.ac_qi as i32) as i8;
self.ac_delta_q[pi] = 0;
}
self.lambda =
qps.lambda * ((1 << 2 * (self.sequence.bit_depth - 8)) as f64);
self.me_lambda = self.lambda.sqrt();
}
}
......
......@@ -38,6 +38,7 @@ pub mod me;
pub mod metrics;
pub mod scan_order;
pub mod scenechange;
pub mod rate;
mod api;
......
......@@ -49,6 +49,41 @@ pub fn ac_q(qindex: u8, delta_q: i8, bit_depth: usize) -> i16 {
table[(qindex as isize + delta_q as isize).max(0).min(255) as usize]
}
// TODO: Handle lossless properly.
fn select_qi(quantizer: i64, qlookup: &[i16; QINDEX_RANGE]) -> u8 {
if quantizer < qlookup[MINQ] as i64 {
MINQ as u8
} else if quantizer >= qlookup[MAXQ] as i64 {
MAXQ as u8
} else {
match qlookup.binary_search(&(quantizer as i16)) {
Ok(qi) => qi as u8,
Err(qi) => {
debug_assert!(qi > MINQ);
debug_assert!(qi <= MAXQ);
// Pick the closest quantizer in the log domain.
let qthresh = (qlookup[qi - 1] as i32) * (qlookup[qi] as i32);
let q2_i32 = (quantizer as i32) * (quantizer as i32);
if q2_i32 < qthresh {
(qi - 1) as u8
} else {
qi as u8
}
}
}
}
}
pub fn select_ac_qi(quantizer: i64, bit_depth: usize) -> u8 {
let qlookup = match bit_depth {
8 => &ac_qlookup_Q3,
10 => &ac_qlookup_10_Q3,
12 => &ac_qlookup_12_Q3,
_ => unimplemented!()
};
select_qi(quantizer, qlookup)
}
#[derive(Debug, Default, Clone, Copy)]
pub struct QuantizationContext {
log_tx_scale: usize,
......
// Copyright (c) 2019, The rav1e contributors. All rights reserved
//
// This source code is subject to the terms of the BSD 2 Clause License and
// the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
// was not distributed with this source code in the LICENSE file, you can
// obtain it at www.aomedia.org/license/software. If the Alliance for Open
// Media Patent License 1.0 was not distributed with this source code in the
// PATENTS file, you can obtain it at www.aomedia.org/license/patent.
use crate::encoder::FrameInvariants;
use crate::quantize::ac_q;
use crate::quantize::dc_q;
use crate::quantize::select_ac_qi;
// The number of frame sub-types for which we track distinct parameters.
pub const FRAME_NSUBTYPES: usize = 4;
pub const FRAME_SUBTYPE_I: usize = 0;
pub const FRAME_SUBTYPE_P: usize = 1;
pub const FRAME_SUBTYPE_B0: usize = 2;
pub const FRAME_SUBTYPE_B1: usize = 3;
// The scale of AV1 quantizer tables (relative to the pixel domain), i.e., Q3.
const QSCALE: i32 = 3;
// Integer binary logarithm of a 64-bit value.
// v: A 64-bit value.
// Returns floor(log2(v)) + 1, or 0 if v == 0.
// This is the number of bits that would be required to represent v in two's
// complement notation with all of the leading zeros stripped.
// TODO: Mark const once leading_zeros() as a constant function stabilizes.
fn ilog64(v: i64) -> i32 {
64 - (v.leading_zeros() as i32)
}
// Convert an integer into a Q57 fixed-point fraction.
// The integer must be in the range -64 to 63, inclusive.
const fn q57(v: i32) -> i64 {
// TODO: Add assert if it ever becomes possible to do in a const function.
(v as i64) << 57
}
#[cfg_attr(rustfmt, rustfmt_skip)]
const ATANH_LOG2: &[i64; 32] = &[
0x32B803473F7AD0F4, 0x2F2A71BD4E25E916, 0x2E68B244BB93BA06,
0x2E39FB9198CE62E4, 0x2E2E683F68565C8F, 0x2E2B850BE2077FC1,
0x2E2ACC58FE7B78DB, 0x2E2A9E2DE52FD5F2, 0x2E2A92A338D53EEC,
0x2E2A8FC08F5E19B6, 0x2E2A8F07E51A485E, 0x2E2A8ED9BA8AF388,
0x2E2A8ECE2FE7384A, 0x2E2A8ECB4D3E4B1A, 0x2E2A8ECA94940FE8,
0x2E2A8ECA6669811D, 0x2E2A8ECA5ADEDD6A, 0x2E2A8ECA57FC347E,
0x2E2A8ECA57438A43, 0x2E2A8ECA57155FB4, 0x2E2A8ECA5709D510,
0x2E2A8ECA5706F267, 0x2E2A8ECA570639BD, 0x2E2A8ECA57060B92,
0x2E2A8ECA57060008, 0x2E2A8ECA5705FD25, 0x2E2A8ECA5705FC6C,
0x2E2A8ECA5705FC3E, 0x2E2A8ECA5705FC33, 0x2E2A8ECA5705FC30,
0x2E2A8ECA5705FC2F, 0x2E2A8ECA5705FC2F
];
// Computes the binary exponential of logq57.
// input: a log base 2 in Q57 format.
// output: a 64 bit integer in Q0 (no fraction).
// TODO: Mark const once we can use local variables in a const function.
fn bexp64(logq57: i64) -> i64 {
let ipart = (logq57 >> 57) as i32;
if ipart < 0 {
return 0;
}
if ipart >= 63 {
return 0x7FFFFFFFFFFFFFFF;
}
// z is the fractional part of the log in Q62 format.
// We need 1 bit of headroom since the magnitude can get larger than 1
// during the iteration, and a sign bit.
let mut z = logq57 - q57(ipart);
let mut w: i64;
if z != 0 {
// Rust has 128 bit multiplies, so it should be possible to do this
// faster without losing accuracy.
z <<= 5;
// w is the exponential in Q61 format (since it also needs headroom and can
// get as large as 2.0); we could get another bit if we dropped the sign,
// but we'll recover that bit later anyway.
// Ideally this should start out as
// \lim_{n->\infty} 2^{61}/\product_{i=1}^n \sqrt{1-2^{-2i}}
// but in order to guarantee convergence we have to repeat iterations 4,
// 13 (=3*4+1), and 40 (=3*13+1, etc.), so it winds up somewhat larger.
w = 0x26A3D0E401DD846D;
let mut i: i64 = 0;
loop {
let mask = -((z < 0) as i64);
w += ((w >> (i + 1)) + mask) ^ mask;
z -= (ATANH_LOG2[i as usize] + mask) ^ mask;
// Repeat iteration 4.
if i >= 3 {
break;
}
z *= 2;
i += 1;
}
loop {
let mask = -((z < 0) as i64);
w += ((w >> (i + 1)) + mask) ^ mask;
z -= (ATANH_LOG2[i as usize] + mask) ^ mask;
// Repeat iteration 13.
if i >= 12 {
break;
}
z *= 2;
i += 1;
}
while i < 32 {
let mask = -((z < 0) as i64);
w += ((w >> (i + 1)) + mask) ^ mask;
z = (z - ((ATANH_LOG2[i as usize] + mask) ^ mask)) * 2;
i += 1;
}
// Skip the remaining iterations unless we really require that much
// precision.
// We could have bailed out earlier for smaller iparts, but that would
// require initializing w from a table, as the limit doesn't converge to
// 61-bit precision until n=30.
let mut wlo: i32 = 0;
if ipart > 30 {
// For these iterations, we just update the low bits, as the high bits
// can't possibly be affected.
// OD_ATANH_LOG2 has also converged (it actually did so one iteration
// earlier, but that's no reason for an extra special case).
loop {
let mask = -((z < 0) as i64);
wlo += (((w >> i) + mask) ^ mask) as i32;
z -= (ATANH_LOG2[31] + mask) ^ mask;
// Repeat iteration 40.
if i >= 39 {
break;
}
z *= 2;
i += 1;
}
while i < 61 {
let mask = -((z < 0) as i64);
wlo += (((w >> i) + mask) ^ mask) as i32;
z = (z - ((ATANH_LOG2[31] + mask) ^ mask)) * 2;
i += 1;
}
}
w = (w << 1) + (wlo as i64);
} else {
w = 1i64 << 62;
}
if ipart < 62 {
w = ((w >> (61 - ipart)) + 1) >> 1;
}
w
}
// Computes the binary log of w.
// input: a 64-bit integer in Q0 (no fraction).
// output: a 64-bit log in Q57.
// TODO: Mark const once we can use local variables in a const function.
fn blog64(w: i64) -> i64 {
let mut w = w;
if w <= 0 {
return -1;
}
let ipart = ilog64(w) - 1;
if ipart > 61 {
w >>= ipart - 61;
} else {
w <<= 61 - ipart;
}
// z is the fractional part of the log in Q61 format.
let mut z: i64 = 0;
if (w & (w - 1)) != 0 {
// Rust has 128 bit multiplies, so it should be possible to do this
// faster without losing accuracy.
// x and y are the cosh() and sinh(), respectively, in Q61 format.
// We are computing z = 2*atanh(y/x) = 2*atanh((w - 1)/(w + 1)).
let mut x = w + (1i64 << 61);
let mut y = w - (1i64 << 61);
for i in 0..4 {
let mask = -((y < 0) as i64);
z += ((ATANH_LOG2[i as usize] >> i) + mask) ^ mask;
let u = x >> (i + 1);
x -= ((y >> (i + 1)) + mask) ^ mask;
y -= (u + mask) ^ mask;
}
// Repeat iteration 4.
for i in 3..13 {
let mask = -((y < 0) as i64);
z += ((ATANH_LOG2[i as usize] >> i) + mask) ^ mask;
let u = x >> (i + 1);
x -= ((y >> (i + 1)) + mask) ^ mask;
y -= (u + mask) ^ mask;
}
// Repeat iteration 13.
for i in 12..32 {
let mask = -((y < 0) as i64);
z += ((ATANH_LOG2[i as usize] >> i) + mask) ^ mask;
let u = x >> (i + 1);
x -= ((y >> (i + 1)) + mask) ^ mask;
y -= (u + mask) ^ mask;
}
// OD_ATANH_LOG2 has converged.
for i in 32..40 {
let mask = -((y < 0) as i64);
z += ((ATANH_LOG2[31] >> i) + mask) ^ mask;
let u = x >> (i + 1);
x -= ((y >> (i + 1)) + mask) ^ mask;
y -= (u + mask) ^ mask;
}
// Repeat iteration 40.
for i in 39..62 {
let mask = -((y < 0) as i64);
z += ((ATANH_LOG2[31] >> i) + mask) ^ mask;
let u = x >> (i + 1);
x -= ((y >> (i + 1)) + mask) ^ mask;
y -= (u + mask) ^ mask;
}
z = (z + 8) >> 4;
}
q57(ipart) + z
}
pub struct RCState {
// TODO: Add state needed for actual bitrate targeting.
}
// TODO: Separate qi values for each color plane.
pub struct QuantizerParameters {
// The full-precision log quantizer modulated by the current frame type upon
// which our quantizer indices are based (including any adjustments to
// prevent buffer overrun or underrun).
// This is used when estimating the scale parameter once we know the actual
// bit usage of a frame.
pub log_target_q: i64,
pub dc_qi: u8,
pub ac_qi: u8,
pub lambda: f64
}
const Q57_SQUARE_EXP_SCALE: f64 =
(2.0 * ::std::f64::consts::LN_2) / ((1i64 << 57) as f64);
impl QuantizerParameters {
fn new_from_log_q(
log_target_dc_q: i64, log_target_ac_q: i64, bit_depth: i32
) -> QuantizerParameters {
let quantizer = bexp64(log_target_ac_q + q57(QSCALE + bit_depth - 8));
let qi = select_ac_qi(quantizer, bit_depth as usize);
QuantizerParameters {
log_target_q: log_target_dc_q,
dc_qi: qi,
ac_qi: qi,
lambda: (::std::f64::consts::LN_2 / 6.0)
* ((log_target_dc_q as f64) * Q57_SQUARE_EXP_SCALE).exp()
}
}
}
impl RCState {
pub fn new() -> RCState {
RCState {}
}
// TODO: Separate quantizers for Cb and Cr.
pub fn select_qi(
&self, fi: &FrameInvariants, fti: usize
) -> QuantizerParameters {
// Rate control is not active.
// Derive quantizer directly from frame type.
// TODO: Rename "quantizer" something that indicates it is a quantizer
// index, and move it somewhere more sensible (or choose a better way to
// parameterize a "quality" configuration parameter).
let base_qi = fi.config.quantizer;
let bit_depth = fi.sequence.bit_depth as i32;
// Adjust base_qi for the frame type.
// TODO: Adjust the quantizer (not the index) instead to avoid issues with
// the non-linearity of the AV1 quantizer tables.
let qi = if fti == FRAME_SUBTYPE_I {
let q_boost = 15;
base_qi.max(1 + q_boost).min(255 + q_boost) - q_boost
} else {
let q_drop = 15 * (fti - FRAME_SUBTYPE_P);
base_qi.min(255 - q_drop) + q_drop
};
// We want to use the DC quantizer to calculate lambda, but we cannot use
// it to map log_q back to qi, because the DC quantizer table entries
// are not unique.
// The corresponding AC quantizer table entries are unique.
let dc_quantizer = dc_q(qi as u8, 0, bit_depth as usize) as i64;
let ac_quantizer = ac_q(qi as u8, 0, bit_depth as usize) as i64;
// Get the log quantizers as Q57.
let log_dc_q = blog64(dc_quantizer) - q57(QSCALE + bit_depth - 8);
let log_ac_q = blog64(ac_quantizer) - q57(QSCALE + bit_depth - 8);
QuantizerParameters::new_from_log_q(log_dc_q, log_ac_q, bit_depth)
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment