use super::*;
thread_local! {
static ENC_SCRATCH: std::cell::RefCell<(Vec<u16>, Vec<u16>, Vec<u16>)> =
const { std::cell::RefCell::new((Vec::new(), Vec::new(), Vec::new())) };
}
#[derive(Clone, Copy)]
pub struct AvifParams {
pub quality: u8,
pub alpha_quality: u8,
pub speed: i8,
pub threads: u32,
}
impl Default for AvifParams {
fn default() -> Self {
AvifParams {
quality: 60,
alpha_quality: 60,
speed: 8,
threads: 1,
}
}
}
pub fn encode_avif(
pixels: &[u8],
w: usize,
h: usize,
channels: usize,
p: &AvifParams,
icc: Option<&[u8]>,
) -> Result<Vec<u8>> {
ensure!(
channels == 3 || channels == 4,
"unsupported channel count {channels}"
);
ensure!(pixels.len() >= w * h * channels, "pixel buffer too small");
let color = ENC_SCRATCH.with(|s| {
let (y_plane, cb_plane, cr_plane) = &mut *s.borrow_mut();
rgb_to_yuv420_10bit(pixels, w, h, channels, y_plane, cb_plane, cr_plane);
encode_svt(
y_plane,
cb_plane,
cr_plane,
w,
h,
quality_to_qp(p.quality),
false,
p,
)
})?;
let has_alpha = channels == 4 && pixels[..w * h * 4].chunks_exact(4).any(|px| px[3] != 255);
let alpha = if has_alpha {
let a_plane: Vec<u16> = pixels
.chunks_exact(4)
.map(|px| ((px[3] as u32 * 1023 + 128) / 255) as u16)
.collect();
let uv = vec![0u16; w.div_ceil(2) * h.div_ceil(2)];
Some(encode_svt(
&a_plane,
&uv,
&uv,
w,
h,
quality_to_qp(p.alpha_quality),
true,
p,
)?)
} else {
None
};
Ok(finish_avif(&color, alpha.as_deref(), w, h, icc))
}
pub(crate) fn start_color_session(w: usize, h: usize, p: &AvifParams) -> Result<SvtSession> {
SvtSession::create(w, h, quality_to_qp(p.quality), false, p)
}
pub(crate) fn encode_avif_with_session(
session: SvtSession,
y_plane: &[u16],
cb_plane: &[u16],
cr_plane: &[u16],
icc: Option<&[u8]>,
) -> Result<Vec<u8>> {
let (w, h) = (session.w, session.h);
let color = session.encode(y_plane, cb_plane, cr_plane)?;
Ok(finish_avif(&color, None, w, h, icc))
}
pub(crate) fn encode_avif_rgb_with_session(
session: SvtSession,
pixels: &[u8],
w: usize,
h: usize,
icc: Option<&[u8]>,
) -> Result<Vec<u8>> {
ensure!(pixels.len() >= w * h * 3, "pixel buffer too small");
ensure!(
(session.w, session.h) == (w, h),
"preheated session dims mismatch"
);
let color = ENC_SCRATCH.with(|s| {
let (y_plane, cb_plane, cr_plane) = &mut *s.borrow_mut();
rgb_to_yuv420_10bit(pixels, w, h, 3, y_plane, cb_plane, cr_plane);
session.encode(y_plane, cb_plane, cr_plane)
})?;
Ok(finish_avif(&color, None, w, h, icc))
}
pub(super) fn finish_avif(
color: &[u8],
alpha: Option<&[u8]>,
w: usize,
h: usize,
icc: Option<&[u8]>,
) -> Vec<u8> {
let mut fy = avif_serialize::Aviffy::new();
fy.matrix_coefficients(avif_serialize::constants::MatrixCoefficients::Bt601)
.full_color_range(true)
.set_chroma_subsampling((true, true));
let out = fy.to_vec(color, alpha, w as u32, h as u32, 10);
if let Some(patched) = icc.and_then(|icc| embed_icc(&out, icc)) {
return patched;
}
out
}
#[allow(clippy::too_many_arguments)]
pub(super) fn encode_svt(
y_plane: &[u16],
cb_plane: &[u16],
cr_plane: &[u16],
w: usize,
h: usize,
qp: u32,
aux_alpha: bool,
p: &AvifParams,
) -> Result<Vec<u8>> {
let session = SvtSession::create(w, h, qp, aux_alpha, p)?;
session.encode(y_plane, cb_plane, cr_plane)
}
pub(crate) struct SvtSession {
handle: *mut svt::EbComponentType,
w: usize,
h: usize,
aux_alpha: bool,
}
unsafe impl Send for SvtSession {}
impl Drop for SvtSession {
fn drop(&mut self) {
unsafe {
svt::svt_av1_enc_deinit(self.handle);
svt::svt_av1_enc_deinit_handle(self.handle);
}
}
}
impl SvtSession {
pub(crate) fn create(
w: usize,
h: usize,
qp: u32,
aux_alpha: bool,
p: &AvifParams,
) -> Result<SvtSession> {
let timing = crate::config::config().timing;
let t0 = std::time::Instant::now();
unsafe {
let mut handle: *mut svt::EbComponentType = std::ptr::null_mut();
let mut config: svt::EbSvtAv1EncConfiguration = std::mem::zeroed();
let err = svt::svt_av1_enc_init_handle(&mut handle, &mut config);
ensure!(
err == svt::EbErrorType::EB_ErrorNone,
"svt init_handle: {err:?}"
);
let session = SvtSession {
handle,
w,
h,
aux_alpha,
};
config.encoder_color_format = svt::EbColorFormat::EB_YUV420;
config.encoder_bit_depth = 10;
if aux_alpha {
config.color_primaries = 2; config.transfer_characteristics = 2; config.matrix_coefficients = 2; } else {
config.color_primaries = 1; config.transfer_characteristics = 13; config.matrix_coefficients = 6; }
config.color_range = 1; config.source_width = w as u32;
config.source_height = h as u32;
config.level_of_parallelism = p.threads;
config.aq_mode = 2;
config.rate_control_mode = 0;
config.min_qp_allowed = 0;
config.max_qp_allowed = 63;
config.qp = qp;
config.enc_mode = p.speed;
config.force_key_frames = true;
config.avif = true;
let tune = std::ffi::CString::new("tune").unwrap();
let three = std::ffi::CString::new("3").unwrap();
ensure!(
svt::svt_av1_enc_parse_parameter(&mut config, tune.as_ptr(), three.as_ptr())
== svt::EbErrorType::EB_ErrorNone,
"svt tune=3"
);
let err = svt::svt_av1_enc_set_parameter(handle, &mut config);
ensure!(
err == svt::EbErrorType::EB_ErrorNone,
"svt set_parameter: {err:?}"
);
let err = svt::svt_av1_enc_init(handle);
ensure!(
err == svt::EbErrorType::EB_ErrorNone,
"svt enc_init: {err:?}"
);
if timing {
eprintln!(
"timing svt-init({w}x{h}{}) {:.1}ms",
if aux_alpha { " alpha" } else { "" },
t0.elapsed().as_secs_f64() * 1e3,
);
}
Ok(session)
}
}
pub(crate) fn encode(
self,
y_plane: &[u16],
cb_plane: &[u16],
cr_plane: &[u16],
) -> Result<Vec<u8>> {
let (w, h) = (self.w, self.h);
let cw = w.div_ceil(2);
let timing = crate::config::config().timing;
let t0 = std::time::Instant::now();
unsafe {
let mut io: svt::EbSvtIOFormat = std::mem::zeroed();
io.luma = y_plane.as_ptr() as *mut u8;
io.cb = cb_plane.as_ptr() as *mut u8;
io.cr = cr_plane.as_ptr() as *mut u8;
io.y_stride = w as u32;
io.cb_stride = cw as u32;
io.cr_stride = cw as u32;
let mut input: svt::EbBufferHeaderType = std::mem::zeroed();
input.size = std::mem::size_of::<svt::EbBufferHeaderType>() as u32;
input.p_buffer = (&mut io) as *mut svt::EbSvtIOFormat as *mut u8;
input.n_filled_len = (y_plane.len() * 2 + (cb_plane.len() + cr_plane.len()) * 2) as u32;
input.pic_type = svt::EbAv1PictureType::EB_AV1_KEY_PICTURE;
input.pts = 0;
let err = svt::svt_av1_enc_send_picture(self.handle, &mut input);
ensure!(
err == svt::EbErrorType::EB_ErrorNone,
"svt send_picture: {err:?}"
);
let mut eos: svt::EbBufferHeaderType = std::mem::zeroed();
eos.size = std::mem::size_of::<svt::EbBufferHeaderType>() as u32;
eos.flags = svt::EB_BUFFERFLAG_EOS;
let err = svt::svt_av1_enc_send_picture(self.handle, &mut eos);
ensure!(err == svt::EbErrorType::EB_ErrorNone, "svt eos: {err:?}");
let mut av1 = Vec::new();
loop {
let mut out: *mut svt::EbBufferHeaderType = std::ptr::null_mut();
let res = svt::svt_av1_enc_get_packet(self.handle, &mut out, 1);
if !out.is_null() {
let ob = &*out;
if !ob.p_buffer.is_null() && ob.n_filled_len > 0 {
av1.extend_from_slice(std::slice::from_raw_parts(
ob.p_buffer,
ob.n_filled_len as usize,
));
}
let at_eos = ob.flags & svt::EB_BUFFERFLAG_EOS != 0;
svt::svt_av1_enc_release_out_buffer(&mut out);
if at_eos {
break;
}
}
ensure!(
res == svt::EbErrorType::EB_ErrorNone,
"svt get_packet: {res:?}"
);
}
if timing {
eprintln!(
"timing svt-enc({w}x{h}{}) {:.1}ms",
if self.aux_alpha { " alpha" } else { "" },
t0.elapsed().as_secs_f64() * 1e3,
);
}
ensure!(!av1.is_empty(), "svt produced no output");
Ok(av1)
}
}
}