use crate::error::Error;
use rav1e::prelude::*;
pub struct Encoder {
quantizer: u8,
speed: u8,
}
impl Encoder {
pub fn with_quality(mut self, quality: f32) -> Self {
debug_assert!((1. ..=100.).contains(&quality));
self.quantizer = quality_to_quantizer(quality);
self
}
pub fn with_speed(mut self, speed: u8) -> Self {
debug_assert!((1..=10).contains(&speed));
self.speed = speed;
self
}
}
impl Default for Encoder {
fn default() -> Self {
Self {
quantizer: quality_to_quantizer(80.),
speed: 5,
}
}
}
impl Encoder {
pub fn encode_rgb<'a>(
&'a self,
width: usize,
height: usize,
pixels: impl Iterator<Item = &'a [u8]> + Send + Sync,
) -> Result<Vec<u8>, Error> {
let planes = pixels.map(|px| {
let (y, u, v) = rgb_to_ycbcr(px, BT601);
[y, u, v]
});
self.encode_raw_planes(
width,
height,
planes,
PixelRange::Full,
MatrixCoefficients::BT601,
)
}
fn encode_raw_planes(
&self,
width: usize,
height: usize,
planes: impl IntoIterator<Item = [u8; 3]> + Send,
color_pixel_range: PixelRange,
matrix_coefficients: MatrixCoefficients,
) -> Result<Vec<u8>, Error> {
let color_description = Some(ColorDescription {
transfer_characteristics: TransferCharacteristics::SRGB,
color_primaries: ColorPrimaries::BT709, matrix_coefficients,
});
let encode_color = move || {
encode_to_av1(
&Av1EncodeConfig {
width,
height,
quantizer: self.quantizer.into(),
speed: SpeedTweaks::from_my_preset(self.speed, self.quantizer),
pixel_range: color_pixel_range,
chroma_sampling: ChromaSampling::Cs444,
color_description,
},
move |frame| init_frame_3(width, height, planes, frame),
)
};
let color = encode_color()?;
let avif_file = avif_serialize::Aviffy::new()
.matrix_coefficients(match matrix_coefficients {
MatrixCoefficients::BT601 => avif_serialize::constants::MatrixCoefficients::Bt601,
_ => {
return Err(Error::Unsupported("matrix coefficients"));
}
})
.to_vec(&color, None, width as u32, height as u32, 8);
Ok(avif_file)
}
}
const BT601: [f32; 3] = [0.2990, 0.5870, 0.1140];
fn rgb_to_ycbcr(px: &[u8], matrix: [f32; 3]) -> (u8, u8, u8) {
let r = f32::from(px[0]);
let g = f32::from(px[1]);
let b = f32::from(px[2]);
let max_value = ((1 << 8) - 1) as f32;
let scale = max_value / 255.;
let shift = (max_value * 0.5).round();
let y = scale * matrix[0] * r + scale * matrix[1] * g + scale * matrix[2] * b;
let cb = (b * scale - y).mul_add(0.5 / (1. - matrix[2]), shift);
let cr = (r * scale - y).mul_add(0.5 / (1. - matrix[0]), shift);
(y.round() as u8, cb.round() as u8, cr.round() as u8)
}
fn quality_to_quantizer(quality: f32) -> u8 {
let q = quality / 100.;
let x = if q >= 0.85 {
(1. - q) * 3.
} else if q > 0.25 {
1. - 0.125 - q * 0.5
} else {
1. - q
};
(x * 255.).round() as u8
}
#[derive(Debug, Copy, Clone)]
struct SpeedTweaks {
pub speed_preset: u8,
pub fast_deblock: Option<bool>,
pub reduced_tx_set: Option<bool>,
pub tx_domain_distortion: Option<bool>,
pub tx_domain_rate: Option<bool>,
pub encode_bottomup: Option<bool>,
pub rdo_tx_decision: Option<bool>,
pub cdef: Option<bool>,
pub lrf: Option<bool>,
pub sgr_complexity_full: Option<bool>,
pub use_satd_subpel: Option<bool>,
pub inter_tx_split: Option<bool>,
pub fine_directional_intra: Option<bool>,
pub complex_prediction_modes: Option<bool>,
pub partition_range: Option<(u8, u8)>,
pub min_tile_size: u16,
}
impl SpeedTweaks {
pub fn from_my_preset(speed: u8, quantizer: u8) -> Self {
let low_quality = quantizer < quality_to_quantizer(55.);
let high_quality = quantizer > quality_to_quantizer(80.);
let max_block_size = if high_quality { 16 } else { 64 };
Self {
speed_preset: speed,
partition_range: Some(match speed {
0 => (4, 64.min(max_block_size)),
1 if low_quality => (4, 64.min(max_block_size)),
2 if low_quality => (4, 32.min(max_block_size)),
1..=4 => (4, 16),
5..=8 => (8, 16),
_ => (16, 16),
}),
complex_prediction_modes: Some(speed <= 1), sgr_complexity_full: Some(speed <= 2),
encode_bottomup: Some(speed <= 2),
rdo_tx_decision: Some(speed <= 4 && !high_quality), reduced_tx_set: Some(speed == 4 || speed >= 9),
fine_directional_intra: Some(speed <= 6),
fast_deblock: Some(speed >= 7 && !high_quality),
lrf: Some(low_quality && speed <= 8), cdef: Some(low_quality && speed <= 9),
inter_tx_split: Some(speed >= 9), tx_domain_rate: Some(speed >= 10),
tx_domain_distortion: None, use_satd_subpel: Some(false), min_tile_size: match speed {
0 => 4096,
1 => 2048,
2 => 1024,
3 => 512,
4 => 256,
_ => 128,
} * if high_quality { 2 } else { 1 },
}
}
pub(crate) fn speed_settings(&self) -> SpeedSettings {
let mut speed_settings = SpeedSettings::from_preset(self.speed_preset);
speed_settings.multiref = false;
speed_settings.rdo_lookahead_frames = 1;
speed_settings.scene_detection_mode = SceneDetectionSpeed::None;
speed_settings.motion.include_near_mvs = false;
if let Some(v) = self.fast_deblock {
speed_settings.fast_deblock = v;
}
if let Some(v) = self.reduced_tx_set {
speed_settings.transform.reduced_tx_set = v;
}
if let Some(v) = self.tx_domain_distortion {
speed_settings.transform.tx_domain_distortion = v;
}
if let Some(v) = self.tx_domain_rate {
speed_settings.transform.tx_domain_rate = v;
}
if let Some(v) = self.encode_bottomup {
speed_settings.partition.encode_bottomup = v;
}
if let Some(v) = self.rdo_tx_decision {
speed_settings.transform.rdo_tx_decision = v;
}
if let Some(v) = self.cdef {
speed_settings.cdef = v;
}
if let Some(v) = self.lrf {
speed_settings.lrf = v;
}
if let Some(v) = self.inter_tx_split {
speed_settings.transform.enable_inter_tx_split = v;
}
if let Some(v) = self.sgr_complexity_full {
speed_settings.sgr_complexity = if v {
SGRComplexityLevel::Full
} else {
SGRComplexityLevel::Reduced
}
};
if let Some(v) = self.use_satd_subpel {
speed_settings.motion.use_satd_subpel = v;
}
if let Some(v) = self.fine_directional_intra {
speed_settings.prediction.fine_directional_intra = v;
}
if let Some(v) = self.complex_prediction_modes {
speed_settings.prediction.prediction_modes = if v {
PredictionModesSetting::ComplexAll
} else {
PredictionModesSetting::Simple
}
};
if let Some((min, max)) = self.partition_range {
debug_assert!(min <= max);
fn sz(s: u8) -> BlockSize {
match s {
4 => BlockSize::BLOCK_4X4,
8 => BlockSize::BLOCK_8X8,
16 => BlockSize::BLOCK_16X16,
32 => BlockSize::BLOCK_32X32,
64 => BlockSize::BLOCK_64X64,
128 => BlockSize::BLOCK_128X128,
_ => panic!("bad size {s}"),
}
}
speed_settings.partition.partition_range = PartitionRange::new(sz(min), sz(max));
}
speed_settings
}
}
struct Av1EncodeConfig {
pub width: usize,
pub height: usize,
pub quantizer: usize,
pub speed: SpeedTweaks,
pub pixel_range: PixelRange,
pub chroma_sampling: ChromaSampling,
pub color_description: Option<ColorDescription>,
}
fn rav1e_config(p: &Av1EncodeConfig) -> Config {
let tiles = {
let threads = 1;
threads.min((p.width * p.height) / (p.speed.min_tile_size as usize).pow(2))
};
let speed_settings = p.speed.speed_settings();
Config::new().with_encoder_config(EncoderConfig {
width: p.width,
height: p.height,
time_base: Rational::new(1, 1),
sample_aspect_ratio: Rational::new(1, 1),
bit_depth: 8,
chroma_sampling: p.chroma_sampling,
chroma_sample_position: ChromaSamplePosition::Unknown,
pixel_range: p.pixel_range,
color_description: p.color_description,
mastering_display: None,
content_light: None,
enable_timing_info: false,
still_picture: true,
error_resilient: false,
switch_frame_interval: 0,
min_key_frame_interval: 0,
max_key_frame_interval: 0,
reservoir_frame_delay: None,
low_latency: false,
quantizer: p.quantizer,
min_quantizer: p.quantizer as _,
bitrate: 0,
tune: Tune::Psychovisual,
tile_cols: 0,
tile_rows: 0,
tiles,
film_grain_params: None,
level_idx: None,
speed_settings,
})
}
fn init_frame_3(
width: usize,
height: usize,
planes: impl IntoIterator<Item = [u8; 3]> + Send,
frame: &mut Frame<u8>,
) -> Result<(), Error> {
let mut f = frame.planes.iter_mut();
let mut planes = planes.into_iter();
let mut y = f.next().unwrap().mut_slice(Default::default());
let mut u = f.next().unwrap().mut_slice(Default::default());
let mut v = f.next().unwrap().mut_slice(Default::default());
for ((y, u), v) in y
.rows_iter_mut()
.zip(u.rows_iter_mut())
.zip(v.rows_iter_mut())
.take(height)
{
let y = &mut y[..width];
let u = &mut u[..width];
let v = &mut v[..width];
for ((y, u), v) in y.iter_mut().zip(u).zip(v) {
let px = planes.next().ok_or(Error::TooFewPixels)?;
*y = px[0];
*u = px[1];
*v = px[2];
}
}
Ok(())
}
fn encode_to_av1(
p: &Av1EncodeConfig,
init: impl FnOnce(&mut Frame<u8>) -> Result<(), Error>,
) -> Result<Vec<u8>, Error> {
let mut ctx: Context<u8> = rav1e_config(p)
.new_context()
.map_err(|e| Error::Encoding(format!("Invalid Config: {e}")))?;
let mut frame = ctx.new_frame();
init(&mut frame)?;
ctx.send_frame(frame)
.map_err(|e| Error::Encoding(format!("Send Frame: {e}")))?;
ctx.flush();
let mut out = Vec::new();
loop {
match ctx.receive_packet() {
Ok(mut packet) => match packet.frame_type {
FrameType::KEY => {
out.append(&mut packet.data);
}
_ => continue,
},
Err(EncoderStatus::Encoded) | Err(EncoderStatus::LimitReached) => break,
Err(err) => Err(err).map_err(|e| Error::Encoding(format!("Receive Packet: {e}")))?,
}
}
Ok(out)
}