Commit 4322cfb7 authored by Timothy B. Terriberry's avatar Timothy B. Terriberry Committed by Derek Buitenhuis
Browse files

Initial implementations of 2-pass rate control.

PUBLIC API CHANGE:
-Addition of EncoderStatus::NotReady
-Addition of Context::twopass_out()
-Addition of Context::twopass_bytes_needed()
-Addition of Context::twopass_in()
parent cc071643
......@@ -661,7 +661,7 @@ pub(crate) struct ContextInner<T: Pixel> {
/// A storage space for reordered frames.
packet_data: Vec<u8>,
segment_output_frameno_start: u64,
segment_input_frameno_start: u64,
pub(crate) segment_input_frameno_start: u64,
keyframe_detector: SceneChangeDetector<T>,
pub(crate) config: EncoderConfig,
rc_state: RCState,
......@@ -691,6 +691,10 @@ pub enum EncoderStatus {
Encoded,
/// Generic fatal error
Failure,
/// A frame was encoded in the first pass of a 2-pass encode, but its stats
/// data was not retrieved with twopass_out(), or not enough stats data was
/// provided in the second pass of a 2-pass encode to encode the next frame.
NotReady
}
pub struct Packet<T: Pixel> {
......@@ -741,6 +745,44 @@ impl<T: Pixel> Context<T> {
self.inner.send_frame(frame)
}
/// Retrieve the first-pass data of a two-pass encode for the frame that was
/// just encoded. This should be called BEFORE every call to receive_packet()
/// (including the very first one), even if no packet was produced by the
/// last call to receive_packet, if any (i.e., *EncoderStatus::Encoded* was
/// returned). It needs to be called once more after
/// *EncoderStatus::LimitReached* is returned, to retrieve the header that
/// should be written to the front of the stats file (overwriting the
/// placeholder header that was emitted at the start of encoding).
///
/// It is still safe to call this function when receive_packet() returns any
/// other error. It will return *None* instead of returning a duplicate copy
/// of the previous frame's data.
pub fn twopass_out(&mut self) -> Option<&[u8]> {
let params = self.inner.rc_state.get_twopass_out_params(&self.inner);
self.inner.rc_state.twopass_out(params)
}
/// Ask how many bytes of the stats file are needed before the next frame
/// of the second pass in a two-pass encode can be encoded. This is a lower
/// bound (more might be required), but if 0 is returned, then encoding can
/// proceed. This is just a hint to the application, and does not need to
/// be called for encoding the second pass to work, so long as the
/// application continues to provide more data to twopass_in() in a loop
/// until twopass_in() returns 0.
pub fn twopass_bytes_needed(&mut self) -> usize {
self.inner.rc_state.twopass_in(None).unwrap_or(0)
}
/// Provide stats data produced in the first pass of a two-pass encode to the
/// second pass. On success this returns the number of bytes of that data
/// which were consumed. When encoding the second pass of a two-pass encode,
/// this should be called repeatedly in a loop before every call to
/// receive_packet() (including the very first one) until no bytes are
/// consumed, or until twopass_bytes_needed() returns 0.
pub fn twopass_in(&mut self, buf: &[u8]) -> Result<usize, EncoderStatus> {
self.inner.rc_state.twopass_in(Some(buf)).or(Err(EncoderStatus::Failure))
}
pub fn receive_packet(&mut self) -> Result<Packet<T>, EncoderStatus> {
let inner = &mut self.inner;
let pool = &mut self.pool;
......@@ -970,8 +1012,12 @@ impl<T: Pixel> ContextInner<T> {
Ok((fi, true))
}
pub(crate) fn done_processing(&self) -> bool {
self.limit != 0 && self.frames_processed == self.limit
}
pub fn receive_packet(&mut self) -> Result<Packet<T>, EncoderStatus> {
if self.limit != 0 && self.frames_processed == self.limit {
if self.done_processing() {
return Err(EncoderStatus::LimitReached);
}
......@@ -993,6 +1039,9 @@ impl<T: Pixel> ContextInner<T> {
let ret = {
let fi = self.frame_invariants.get_mut(&cur_output_frameno).unwrap();
if fi.show_existing_frame {
if !self.rc_state.ready() {
return Err(EncoderStatus::NotReady);
}
let mut fs = FrameState::new(fi);
let sef_data = encode_show_existing_frame(fi, &mut fs);
......@@ -1011,6 +1060,9 @@ impl<T: Pixel> ContextInner<T> {
self.output_frameno += 1;
self.finalize_packet(rec, &fi)
} else if let Some(f) = self.frame_q.get(&fi.input_frameno) {
if !self.rc_state.ready() {
return Err(EncoderStatus::NotReady);
}
if let Some(frame) = f.clone() {
let fti = fi.get_frame_subtype();
let qps =
......
......@@ -34,6 +34,8 @@ pub struct CliOptions {
pub skip: usize,
pub verbose: bool,
pub threads: usize,
pub pass1file_name: Option<String>,
pub pass2file_name: Option<String>
}
pub fn parse_cli() -> CliOptions {
......@@ -82,12 +84,16 @@ pub fn parse_cli() -> CliOptions {
)
// ENCODING SETTINGS
.arg(
Arg::with_name("PASS")
.help("Specify first-pass or second-pass to run as a two-pass encode; If not provided, will run a one-pass encode")
.short("p")
.long("pass")
Arg::with_name("FIRST_PASS")
.help("Perform the first pass of a two-pass encode, saving the pass data to the specified file for future passes")
.long("first-pass")
.takes_value(true)
)
.arg(
Arg::with_name("SECOND_PASS")
.help("Perform the second pass of a two-pass encode, reading the pass data saved from a previous pass from the specified file")
.long("second-pass")
.takes_value(true)
.possible_values(&["1", "2"])
)
.arg(
Arg::with_name("LIMIT")
......@@ -341,6 +347,8 @@ pub fn parse_cli() -> CliOptions {
skip: matches.value_of("SKIP").unwrap().parse().unwrap(),
verbose: matches.is_present("VERBOSE"),
threads,
pass1file_name: matches.value_of("FIRST_PASS").map(|s| s.to_owned()),
pass2file_name: matches.value_of("SECOND_PASS").map(|s| s.to_owned())
}
}
......@@ -467,7 +475,7 @@ fn parse_config(matches: &ArgMatches<'_>) -> EncoderConfig {
cfg.bitrate = bitrate.checked_mul(1000).expect("Bitrate too high");
cfg.reservoir_frame_delay = matches.value_of("RESERVOIR_FRAME_DELAY").map(|reservior_frame_delay| reservior_frame_delay.parse().unwrap());
cfg.show_psnr = matches.is_present("PSNR");
cfg.pass = matches.value_of("PASS").map(|pass| pass.parse().unwrap());
cfg.pass = None;
cfg.stats_file = if cfg.pass.is_some() {
Some(PathBuf::from(matches.value_of("STATS_FILE").unwrap()))
} else {
......
......@@ -18,6 +18,7 @@ use rav1e::prelude::*;
use std::io;
use std::io::Write;
use std::io::Read;
use std::io::Seek;
use std::path::Path;
use std::sync::Arc;
use crate::decoder::Decoder;
......@@ -69,10 +70,51 @@ impl<D: Decoder> Source<D> {
fn process_frame<T: Pixel, D: Decoder>(
ctx: &mut Context<T>, output_file: &mut dyn Muxer,
source: &mut Source<D>,
pass1file: Option<&mut File>,
pass2file: Option<&mut File>,
buffer: &mut [u8],
buf_pos: &mut usize,
mut y4m_enc: Option<&mut y4m::Encoder<'_, Box<dyn Write>>>
) -> Option<Vec<FrameSummary>> {
let y4m_details = source.input.get_video_details();
let mut frame_summaries = Vec::new();
let mut pass1file = pass1file;
let mut pass2file = pass2file;
// Submit first pass data to pass 2.
if let Some(passfile) = pass2file.as_mut() {
loop {
let mut bytes = ctx.twopass_bytes_needed();
if bytes == 0 {
break;
}
// Read in some more bytes, if necessary.
bytes = bytes.min(buffer.len() - *buf_pos);
if bytes > 0 {
passfile.read_exact(&mut buffer[*buf_pos..*buf_pos + bytes])
.expect("Could not read frame data from two-pass data file!");
}
// And pass them off.
let consumed = ctx.twopass_in(&buffer[*buf_pos..*buf_pos + bytes])
.expect("Error submitting pass data in second pass.");
// If the encoder consumed the whole buffer, reset it.
if consumed >= bytes {
*buf_pos = 0;
}
else {
*buf_pos += consumed;
}
}
}
// Extract first pass data from pass 1.
// We call this before encoding any frames to ensure we are in 2-pass mode
// and to get the placeholder header data.
if let Some(passfile) = pass1file.as_mut() {
if let Some(outbuf) = ctx.twopass_out() {
passfile.write_all(outbuf)
.expect("Unable to write to two-pass data file.");
}
}
let pkt_wrapped = ctx.receive_packet();
match pkt_wrapped {
Ok(pkt) => {
......@@ -89,11 +131,25 @@ fn process_frame<T: Pixel, D: Decoder>(
unreachable!();
}
Err(EncoderStatus::LimitReached) => {
if let Some(passfile) = pass1file.as_mut() {
if let Some(outbuf) = ctx.twopass_out() {
// The last block of data we get is the summary data that needs to go
// at the start of the pass file.
// Seek to the start so we can write it there.
passfile.seek(std::io::SeekFrom::Start(0))
.expect("Unable to seek in two-pass data file.");
passfile.write_all(outbuf)
.expect("Unable to write to two-pass data file.");
}
}
return None;
}
Err(EncoderStatus::Failure) => {
panic!("Failed to encode video");
}
Err(EncoderStatus::NotReady) => {
panic!("Mis-managed handling of two-pass stats data");
}
Err(EncoderStatus::Encoded) => {}
}
Some(frame_summaries)
......@@ -110,13 +166,27 @@ fn do_encode<T: Pixel, D: Decoder>(
cfg: Config, verbose: bool, mut progress: ProgressInfo,
output: &mut dyn Muxer,
source: &mut Source<D>,
pass1file_name: Option<&String>,
pass2file_name: Option<&String>,
mut y4m_enc: Option<y4m::Encoder<'_, Box<dyn Write>>>
) {
let mut ctx: Context<T> = cfg.new_context();
let mut pass2file = pass2file_name.map(|f| {
File::open(f)
.unwrap_or_else(|_| panic!("Unable to open \"{}\" for reading two-pass data.", f))
});
let mut pass1file = pass1file_name.map(|f| {
File::create(f)
.unwrap_or_else(|_| panic!("Unable to open \"{}\" for writing two-pass data.", f))
});
let mut buffer: [u8; 80] = [0; 80];
let mut buf_pos = 0;
while let Some(frame_info) =
process_frame(&mut ctx, &mut *output, source, y4m_enc.as_mut())
process_frame(&mut ctx, &mut *output, source, pass1file.as_mut(),
pass2file.as_mut(), &mut buffer, &mut buf_pos, y4m_enc.as_mut())
{
for frame in frame_info {
progress.add_frame(frame);
......@@ -235,11 +305,13 @@ fn main() {
if video_info.bit_depth == 8 {
do_encode::<u8, y4m::Decoder<'_, Box<dyn Read>>>(
cfg, cli.verbose, progress, &mut *cli.io.output, &mut source, y4m_enc
cfg, cli.verbose, progress, &mut *cli.io.output, &mut source,
cli.pass1file_name.as_ref(), cli.pass2file_name.as_ref(), y4m_enc
)
} else {
do_encode::<u16, y4m::Decoder<'_, Box<dyn Read>>>(
cfg, cli.verbose, progress, &mut *cli.io.output, &mut source, y4m_enc
cfg, cli.verbose, progress, &mut *cli.io.output, &mut source,
cli.pass1file_name.as_ref(), cli.pass2file_name.as_ref(), y4m_enc
)
}
}
......@@ -28,6 +28,20 @@ pub const FRAME_SUBTYPE_B0: usize = 2;
pub const FRAME_SUBTYPE_B1: usize = 3;
pub const FRAME_SUBTYPE_SEF: usize = 4;
const PASS_SINGLE: i32 = 0;
const PASS_1: i32 = 1;
const PASS_2: i32 = 2;
// Magic value at the start of the 2-pass stats file
const TWOPASS_MAGIC: i32 = 0x50324156;
// Version number for the 2-pass stats file
const TWOPASS_VERSION: i32 = 1;
// 4 byte magic + 4 byte version + 4 byte TU count + 4 byte SEF frame count
// + FRAME_NSUBTYPES*(4 byte frame count + 1 byte exp + 8 byte scale_sum)
const TWOPASS_HEADER_SZ: usize = 16 + FRAME_NSUBTYPES*(4 + 1 + 8);
// 4 byte frame type (show_frame and fti jointly coded) + 4 byte log_scale_q24
const TWOPASS_PACKET_SZ: usize = 8;
const SEF_BITS: i64 = 24;
// The scale of AV1 quantizer tables (relative to the pixel domain), i.e., Q3.
......@@ -271,6 +285,21 @@ const fn q24_to_q57(v: i32) -> i64 {
(v as i64) << 33
}
// Binary exponentiation of a log_scale with 24-bit fractional precision and
// saturation.
// log_scale: A binary logarithm in Q24 format.
// Return: The binary exponential in Q24 format, saturated to 2**47 - 1 if
// log_scale was too large.
fn bexp_q24(log_scale: i32) -> i64 {
if log_scale < 23 << 24 {
let ret = bexp64(((log_scale as i64) << 33) + q57(24));
if ret < (1i64 << 47) - 1 {
return ret;
}
}
(1i64 << 47) - 1
}
#[rustfmt::skip]
const ROUGH_TAN_LOOKUP: &[u16; 18] = &[
0, 358, 722, 1098, 1491, 1910,
......@@ -369,6 +398,29 @@ impl IIRBessel2 {
}
}
#[derive(Copy, Clone)]
struct RCFrameMetrics {
// The log base 2 of the scale factor for this frame in Q24 format.
log_scale_q24: i32,
// The frame type from pass 1
fti: usize,
// Whether or not the frame was hidden in pass 1
show_frame: bool,
// TODO: The input frame number corresponding to this frame in the input.
// input_frameno: u32
// TODO vfr: PTS
}
impl RCFrameMetrics {
fn new() -> RCFrameMetrics {
RCFrameMetrics {
log_scale_q24: 0,
fti: 0,
show_frame: false
}
}
}
pub struct RCState {
// The target bit-rate in bits per second.
target_bitrate: i32,
......@@ -376,6 +428,9 @@ pub struct RCState {
// We use TUs because in our leaky bucket model, we only add bits to the
// reservoir on TU boundaries.
reservoir_frame_delay: i32,
// Whether or not the reservoir_frame_delay was explicitly specified by the
// user, or is the default value.
reservoir_frame_delay_is_set: bool,
// The maximum quantizer index to allow (for the luma AC coefficients, other
// quantizers will still be adjusted to match).
maybe_ac_qi_max: Option<u8>,
......@@ -387,10 +442,12 @@ pub struct RCState {
cap_overflow: bool,
// Can the reservoir go negative?
cap_underflow: bool,
// The log of the first-pass base quantizer.
pass1_log_base_q: i64,
// Two-pass mode state.
// 0 => 1-pass encoding.
// 1 => 1st pass of 2-pass encoding.
// 2 => 2nd pass of 2-pass encoding.
// PASS_SINGLE => 1-pass encoding.
// PASS_1 => 1st pass of 2-pass encoding.
// PASS_2 => 2nd pass of 2-pass encoding.
twopass_state: i32,
// The log of the number of pixels in a frame in Q57 format.
log_npixels: i64,
......@@ -419,13 +476,60 @@ pub struct RCState {
// TODO vfr: vfrfilter: IIRBessel2,
// The number of frames of each type we have seen, for filter adaptation
// purposes.
// These are only 32 bits to guarantee that we can sum the scales over the
// whole file without overflow in a 64-bit int.
// That limits us to 2.268 years at 60 fps (minus 33% with re-ordering).
nframes: [i32; FRAME_NSUBTYPES],
inter_delay: [i32; FRAME_NSUBTYPES - 1],
inter_delay_target: i32,
// The total accumulated estimation bias.
rate_bias: i64,
// The number of (non-Show Existing Frame) frames that have been encoded.
nencoded_frames: i64
nencoded_frames: i64,
// The number of Show Existing Frames that have been emitted.
nsef_frames: i64,
// Buffer for current frame metrics.
twopass_buffer: [u8; TWOPASS_HEADER_SZ + TWOPASS_PACKET_SZ],
// The current byte position in the frame metrics buffer.
// When 2-pass encoding is enabled, this is set to 0 after each frame is
// encoded, and be must non-zero before the next frame can be encoded.
// In pass 1, it represents the number of bytes that have been stored in the
// output buffer, and is set to a non-zero value by twopass_out().
// In pass 2, it represents the number of bytes that have been parsed from
// the input buffer, and is set to a non-zero value by twopass_in().
twopass_buffer_bytes: usize,
// In pass 2, this represents the number of bytes that are available in the
// input buffer.
twopass_buffer_fill: usize,
// TODO: Add a way to force the next frame to be a keyframe in 2-pass mode.
// Right now we are relying on keyframe detection to detect the same
// keyframes.
// The metrics for the previous frame.
prev_metrics: RCFrameMetrics,
// The metrics for the current frame.
cur_metrics: RCFrameMetrics,
// The buffered metrics for future frames.
frame_metrics: Vec<RCFrameMetrics>,
// The total number of frames still in use in the circular metric buffer.
nframe_metrics: usize,
// The index of the current frame in the circular metric buffer.
frame_metrics_head: usize,
// The TU count.
ntus_total: i32,
// The remaining TU count.
ntus_left: i32,
// The frame count of each frame subtype in the whole file.
nframes_total: [i32; FRAME_NSUBTYPES + 1],
// The sum of those counts.
nframes_total_total: i32,
// The number of frames of each subtype yet to be processed.
nframes_left: [i32; FRAME_NSUBTYPES + 1],
// The sum of the scale values for each frame subtype.
scale_sum: [i64; FRAME_NSUBTYPES],
// The number of TUs represented by the current scale sums.
scale_window_ntus: i32,
// The frame count of each frame subtype in the current scale window.
scale_window_nframes: [i32; FRAME_NSUBTYPES + 1],
}
// TODO: Separate qi values for each color plane.
......@@ -487,6 +591,20 @@ impl QuantizerParameters {
}
}
// The parameters that are required by twopass_out().
// We need a reference to the enclosing ContextInner to compute these, but
// twopass_out() cannot take such a reference, since it needs a &mut self
// reference to do its job, and RCState is contained inside ContextInner.
// In practice we don't modify anything in RCState until after we're finished
// reading from ContextInner, but Rust's borrow checker does not have a way to
// express that.
// There's probably a cleaner way to do this, but going with something simple
// for now, since this is not exposed in the public API.
pub(crate) struct TwoPassOutParams {
pass1_log_base_q: i64,
done_processing: bool
}
impl RCState {
pub fn new(
frame_width: i32, frame_height: i32, framerate_num: i64,
......@@ -555,13 +673,14 @@ impl RCState {
RCState {
target_bitrate,
reservoir_frame_delay,
reservoir_frame_delay_is_set: maybe_reservoir_frame_delay.is_some(),
maybe_ac_qi_max,
ac_qi_min,
drop_frames: false,
cap_overflow: true,
cap_underflow: false,
// TODO: Support multiple passes.
twopass_state: 0,
pass1_log_base_q: 0,
twopass_state: PASS_SINGLE,
log_npixels: blog64(npixels),
bits_per_frame,
reservoir_fullness: reservoir_target,
......@@ -580,7 +699,24 @@ impl RCState {
inter_delay: [INTER_DELAY_TARGET_MIN; FRAME_NSUBTYPES - 1],
inter_delay_target: reservoir_frame_delay >> 1,
rate_bias: 0,
nencoded_frames: 0
nencoded_frames: 0,
nsef_frames: 0,
twopass_buffer: [0; TWOPASS_HEADER_SZ + TWOPASS_PACKET_SZ],
twopass_buffer_bytes: 0,
twopass_buffer_fill: 0,
prev_metrics: RCFrameMetrics::new(),
cur_metrics: RCFrameMetrics::new(),
frame_metrics: Vec::new(),
nframe_metrics: 0,
frame_metrics_head: 0,
ntus_total: 0,
ntus_left: 0,
nframes_total: [0; FRAME_NSUBTYPES + 1],
nframes_total_total: 0,
nframes_left: [0; FRAME_NSUBTYPES + 1],
scale_sum: [0; FRAME_NSUBTYPES],
scale_window_ntus: 0,
scale_window_nframes: [0; FRAME_NSUBTYPES + 1],
}
}
......@@ -613,146 +749,273 @@ impl RCState {
+ DQP_Q57[fti];
QuantizerParameters::new_from_log_q(log_base_q, log_q, bit_depth)
} else {
let mut nframes: [i32; FRAME_NSUBTYPES + 1] = [0; FRAME_NSUBTYPES + 1];
let mut log_scale: [i64; FRAME_NSUBTYPES] = self.log_scale;
let mut reservoir_tus = self.reservoir_frame_delay.min(self.ntus_left);
let mut reservoir_frames = 0;
let mut log_cur_scale = (self.scalefilter[fti].y[0] as i64) << 33;
match self.twopass_state {
// Single pass only right now.
// First pass of 2-pass mode: use a fixed base quantizer.
PASS_1 => {
// Adjust the quantizer for the frame type, result is Q57:
let log_q = ((self.pass1_log_base_q
+ (1i64 << 11)) >> 12)*(MQP_Q12[fti] as i64) + DQP_Q57[fti];
return QuantizerParameters::new_from_log_q(self.pass1_log_base_q,
log_q, ctx.config.bit_depth);
},
// Second pass of 2-pass mode: we know exactly how much of each frame
// type there is in the current buffer window, and have estimates for
// the scales.
PASS_2 => {
let mut scale_sum: [i64; FRAME_NSUBTYPES] = self.scale_sum;
let mut scale_window_nframes: [i32; FRAME_NSUBTYPES + 1]
= self.scale_window_nframes;
// Intentionally exclude Show Existing Frame frames from this.
for ftj in 0..FRAME_NSUBTYPES {
reservoir_frames += scale_window_nframes[ftj];
}
// If we're approaching the end of the file, add some slack to keep
// us from slamming into a rail.
// Our rate accuracy goes down, but it keeps the result sensible.
// We position the target where the first forced keyframe beyond the
// end of the file would be (for consistency with 1-pass mode).
// TODO: let mut buf_pad = self.reservoir_frame_delay.min(...);
// if buf_delay < buf_pad {
// buf_pad -= buf_delay;
// }
// else ...
// Otherwise, search for the last keyframe in the buffer window and
// target that.
// Currently we only do this when using a finite buffer.
// We could save the position of the last keyframe in the stream in
// the summary data and do it with a whole-file buffer as well, but
// it isn't likely to make a difference.
if !self.frame_metrics.is_empty() {
let mut fm_tail = self.frame_metrics_head + self.nframe_metrics;
if fm_tail >= self.frame_metrics.len() {
fm_tail -= self.frame_metrics.len();
}
let mut fmi = fm_tail;
loop {
if fmi == 0 {
fmi += self.frame_metrics.len();
}
fmi -= 1;
// Stop before we remove the first frame.
if fmi == self.frame_metrics_head {
break;
}
// If we find a keyframe, remove it and everything past it.
if self.frame_metrics[fmi].fti == FRAME_SUBTYPE_I {
while fmi != fm_tail {
let m = &self.frame_metrics[fmi];
let ftj = m.fti;
scale_window_nframes[ftj] -= 1;
if ftj < FRAME_NSUBTYPES {
scale_sum[ftj] -= bexp_q24(m.log_scale_q24);
reservoir_frames -= 1;
}
if m.show_frame {
reservoir_tus -= 1;
}
fmi += 1;
if fmi >= self.frame_metrics.len() {
fmi = 0;
}
}
// And stop scanning backwards.
break;
}
}
}
nframes = scale_window_nframes;
// If we're not using the same frame type as in pass 1 (because
// someone changed some encoding parameters), remove that scale
// estimate.
// We'll add a replacement for the correct frame type below.
if self.cur_metrics.fti != fti {
scale_window_nframes[self.cur_metrics.fti] -= 1;
if self.cur_metrics.fti != FRAME_SUBTYPE_SEF {
scale_sum[self.cur_metrics.fti]
-= bexp_q24(self.cur_metrics.log_scale_q24);
}
}
else {
log_cur_scale = (self.cur_metrics.log_scale_q24 as i64) << 33;
}
// If we're approaching the end of the file, add some slack to keep
// us from slamming into a rail.
// Our rate accuracy goes down, but it keeps the result sensible.
// We position the target where the first forced keyframe beyond the
// end of the file would be (for consistency with 1-pass mode).
if reservoir_tus >= self.ntus_left
&& self.ntus_total as u64 > ctx.segment_input_frameno_start {
let nfinal_segment_tus = self.ntus_total
- (ctx.segment_input_frameno_start as i32);
if ctx.config.max_key_frame_interval as i32 > nfinal_segment_tus {
let reservoir_pad = (ctx.config.max_key_frame_interval as i32
- nfinal_segment_tus)
.min(self.reservoir_frame_delay - reservoir_tus);
let (guessed_reservoir_frames, guessed_reservoir_tus) =
ctx.guess_frame_subtypes(&mut nframes,
reservoir_tus + reservoir_pad);
reservoir_frames = guessed_reservoir_frames;
reservoir_tus = guessed_reservoir_tus;
}
}
// Blend in the low-pass filtered scale according to how many
// frames of each type we need to add compared to the actual sums in
// our window.
for ftj in 0..FRAME_NSUBTYPES {
let scale = scale_sum[ftj] + bexp_q24(self.scalefilter[ftj].y[0])*
(nframes[ftj] - scale_window_nframes[ftj]) as i64;
log_scale[ftj] = if nframes[ftj] > 0 {
blog64(scale) - blog64(nframes[ftj] as i64) - q57(24)
}
else {
-self.log_npixels
};