blob: e71be453cdc22e3e69bf49c62767be9db155755d [file] [log] [blame]
// Copyright 2024 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::any::Any;
use std::borrow::Borrow;
use std::rc::Rc;
use anyhow::Context;
use libva::constants::VA_INVALID_SURFACE;
use libva::BufferType;
use libva::Display;
use libva::EncPictureParameter;
use libva::EncPictureParameterBufferVP9;
use libva::EncSequenceParameter;
use libva::EncSequenceParameterBufferVP9;
use libva::Picture;
use libva::Surface;
use libva::SurfaceMemoryDescriptor;
use libva::VAProfile::VAProfileVP9Profile0;
use libva::VAProfile::VAProfileVP9Profile2;
use libva::VP9EncPicFlags;
use libva::VP9EncRefFlags;
use crate::backend::vaapi::encoder::tunings_to_libva_rc;
use crate::backend::vaapi::encoder::CodedOutputPromise;
use crate::backend::vaapi::encoder::Reconstructed;
use crate::backend::vaapi::encoder::VaapiBackend;
use crate::codec::vp9::parser::BitDepth;
use crate::codec::vp9::parser::FrameType;
use crate::codec::vp9::parser::InterpolationFilter;
use crate::codec::vp9::parser::ALTREF_FRAME;
use crate::codec::vp9::parser::GOLDEN_FRAME;
use crate::codec::vp9::parser::LAST_FRAME;
use crate::codec::vp9::parser::NUM_REF_FRAMES;
use crate::encoder::stateless::vp9::predictor::MAX_Q_IDX;
use crate::encoder::stateless::vp9::predictor::MIN_Q_IDX;
use crate::encoder::stateless::vp9::BackendRequest;
use crate::encoder::stateless::vp9::ReferenceUse;
use crate::encoder::stateless::vp9::StatelessEncoder;
use crate::encoder::stateless::vp9::StatelessVP9EncoderBackend;
use crate::encoder::stateless::ReadyPromise;
use crate::encoder::stateless::StatelessBackendResult;
use crate::encoder::stateless::StatelessVideoEncoderBackend;
use crate::encoder::vp9::EncoderConfig;
use crate::encoder::vp9::VP9;
use crate::encoder::EncodeResult;
use crate::encoder::RateControl;
use crate::BlockingMode;
use crate::Fourcc;
use crate::Resolution;
impl<M, Handle> StatelessVideoEncoderBackend<VP9> for VaapiBackend<M, Handle>
where
M: SurfaceMemoryDescriptor,
Handle: Borrow<Surface<M>>,
{
type Picture = Handle;
type Reconstructed = Reconstructed;
type CodedPromise = CodedOutputPromise<M, Handle>;
type ReconPromise = ReadyPromise<Self::Reconstructed>;
}
impl<M, Handle> StatelessVP9EncoderBackend for VaapiBackend<M, Handle>
where
M: SurfaceMemoryDescriptor,
Handle: Borrow<Surface<M>>,
{
fn encode_frame(
&mut self,
request: BackendRequest<Self::Picture, Self::Reconstructed>,
) -> StatelessBackendResult<(Self::ReconPromise, Self::CodedPromise)> {
let coded_buf = self.new_coded_buffer(&request.tunings.rate_control)?;
let recon = self.new_scratch_picture()?;
// Use bitrate from RateControl or ask driver to ignore
let bits_per_second = request.tunings.rate_control.bitrate_target().unwrap_or(0) as u32;
let seq_param = BufferType::EncSequenceParameter(EncSequenceParameter::VP9(
EncSequenceParameterBufferVP9::new(
request.input_meta.layout.size.width,
request.input_meta.layout.size.height,
0,
10,
2000,
bits_per_second,
1024,
),
));
// From va_enc_vp9.h `ref_frame_ctrl_l0` documentation
const LAST_FRAME_AS_REF: u32 = 0x01;
const GOLDEN_FRAME_AS_REF: u32 = 0x02;
const ALTREF_FRAME_AS_REF: u32 = 0x04;
let mut references = Vec::<Rc<dyn Any>>::new();
let mut reference_frames = [VA_INVALID_SURFACE; NUM_REF_FRAMES];
let mut ref_frame_ctrl_l0 = 0;
let mut ref_frame_ctrl_l1 = 0;
let refs = [
(&request.last_frame_ref, LAST_FRAME - 1, LAST_FRAME_AS_REF),
(
&request.golden_frame_ref,
GOLDEN_FRAME - 1,
GOLDEN_FRAME_AS_REF,
),
(
&request.altref_frame_ref,
ALTREF_FRAME - 1,
ALTREF_FRAME_AS_REF,
),
];
for (r, ref_idx, ref_ctrl) in refs {
let Some((ref_frame, ref_use)) = r else {
continue;
};
reference_frames[request.header.ref_frame_idx[ref_idx] as usize] =
ref_frame.surface_id();
references.push(ref_frame.clone());
match ref_use {
ReferenceUse::Single => ref_frame_ctrl_l0 |= ref_ctrl,
ReferenceUse::Compound => ref_frame_ctrl_l1 |= ref_ctrl,
ReferenceUse::Hybrid => {
ref_frame_ctrl_l0 |= ref_ctrl;
ref_frame_ctrl_l1 |= ref_ctrl;
}
}
}
let force_kf =
request.header.frame_type == FrameType::KeyFrame || request.input_meta.force_keyframe;
let ref_flags = VP9EncRefFlags::new(
// Force keyframe if requested
force_kf as u32,
ref_frame_ctrl_l0,
ref_frame_ctrl_l1,
request.header.ref_frame_idx[LAST_FRAME - 1] as u32,
request.header.ref_frame_sign_bias[LAST_FRAME] as u32,
request.header.ref_frame_idx[GOLDEN_FRAME - 1] as u32,
request.header.ref_frame_sign_bias[GOLDEN_FRAME] as u32,
request.header.ref_frame_idx[ALTREF_FRAME - 1] as u32,
request.header.ref_frame_sign_bias[ALTREF_FRAME] as u32,
0,
);
// From va_enc_vp9.h `mcomp_filter_type` documentation
let mcomp_filter_type = match request.header.interpolation_filter {
InterpolationFilter::EightTap => 0,
InterpolationFilter::EightTapSmooth => 1,
InterpolationFilter::EightTapSharp => 2,
InterpolationFilter::Bilinear => 3,
InterpolationFilter::Switchable => 4,
};
// TODO: show_existing_frame
assert!(!request.header.show_existing_frame);
// From va_enc_vp9.h `comp_prediction_mode` documentation
const PRED_MODE_SINGLE: u32 = 0x00;
// const PRED_MODE_COMPOUND: u32 = 0x01;
const PRED_MODE_HYBRID: u32 = 0x02;
let comp_prediction_mode = if ref_frame_ctrl_l1 != 0 {
// Use hybrid prediction mode if any future reference frame are enabled
PRED_MODE_HYBRID
} else {
PRED_MODE_SINGLE
};
let pic_flags = VP9EncPicFlags::new(
request.header.frame_type as u32,
request.header.show_frame as u32,
request.header.error_resilient_mode as u32,
request.header.intra_only as u32,
request.header.allow_high_precision_mv as u32,
mcomp_filter_type,
request.header.frame_parallel_decoding_mode as u32,
request.header.reset_frame_context as u32,
request.header.refresh_frame_context as u32,
request.header.frame_context_idx as u32,
request.header.seg.enabled as u32,
request.header.seg.temporal_update as u32,
request.header.seg.update_map as u32,
request.header.lossless as u32,
comp_prediction_mode,
1,
0,
);
let pic_param = BufferType::EncPictureParameter(EncPictureParameter::VP9(
EncPictureParameterBufferVP9::new(
request.header.width,
request.header.height,
request.header.render_width,
request.header.render_height,
recon.surface_id(),
reference_frames,
coded_buf.id(),
&ref_flags,
&pic_flags,
request.header.refresh_frame_flags,
request.header.quant.base_q_idx,
request.header.quant.delta_q_y_dc,
request.header.quant.delta_q_uv_ac,
request.header.quant.delta_q_uv_dc,
request.header.lf.level,
request.header.lf.sharpness,
request.header.lf.ref_deltas,
request.header.lf.mode_deltas,
0,
0,
0,
0,
0,
0,
0,
request.header.tile_rows_log2,
request.header.tile_cols_log2,
// Don't skip frames
0,
0,
0,
),
));
let rc_param =
tunings_to_libva_rc::<{ MIN_Q_IDX as u32 }, { MAX_Q_IDX as u32 }>(&request.tunings)?;
let rc_param =
libva::BufferType::EncMiscParameter(libva::EncMiscParameter::RateControl(rc_param));
let mut picture = Picture::new(
request.input_meta.timestamp,
Rc::clone(self.context()),
request.input,
);
picture.add_buffer(self.context().create_buffer(seq_param)?);
picture.add_buffer(self.context().create_buffer(pic_param)?);
picture.add_buffer(self.context().create_buffer(rc_param)?);
// Start processing the picture encoding
let picture = picture.begin().context("picture begin")?;
let picture = picture.render().context("picture render")?;
let picture = picture.end().context("picture end")?;
// libva will handle the synchronization of reconstructed surface with implicit fences.
// Therefore return the reconstructed frame immediately.
let reference_promise = ReadyPromise::from(recon);
let bitstream_promise =
CodedOutputPromise::new(picture, references, coded_buf, request.coded_output);
Ok((reference_promise, bitstream_promise))
}
}
impl<M, Handle> StatelessEncoder<Handle, VaapiBackend<M, Handle>>
where
M: SurfaceMemoryDescriptor,
Handle: Borrow<Surface<M>>,
{
pub fn new_vaapi(
display: Rc<Display>,
config: EncoderConfig,
fourcc: Fourcc,
coded_size: Resolution,
low_power: bool,
blocking_mode: BlockingMode,
) -> EncodeResult<Self> {
let bitrate_control = match config.initial_tunings.rate_control {
RateControl::ConstantBitrate(_) => libva::constants::VA_RC_CBR,
RateControl::ConstantQuality(_) => libva::constants::VA_RC_CQP,
};
let va_profile = match config.bit_depth {
BitDepth::Depth8 => VAProfileVP9Profile0,
BitDepth::Depth10 | BitDepth::Depth12 => VAProfileVP9Profile2,
};
let backend = VaapiBackend::new(
display,
va_profile,
fourcc,
coded_size,
bitrate_control,
low_power,
)?;
Self::new_vp9(backend, config, blocking_mode)
}
}
#[cfg(test)]
pub(super) mod tests {
use std::rc::Rc;
use libva::constants::VA_RT_FORMAT_YUV420;
use libva::constants::VA_RT_FORMAT_YUV420_10;
use libva::Display;
use libva::UsageHint;
use libva::VAEntrypoint::VAEntrypointEncSliceLP;
use super::*;
use crate::backend::vaapi::encoder::tests::upload_test_frame_nv12;
use crate::backend::vaapi::encoder::tests::TestFrameGenerator;
use crate::backend::vaapi::encoder::VaapiBackend;
use crate::backend::vaapi::surface_pool::PooledVaSurface;
use crate::backend::vaapi::surface_pool::VaSurfacePool;
use crate::bitstream_utils::IvfFileHeader;
use crate::bitstream_utils::IvfFrameHeader;
use crate::codec::vp9::parser::Header;
use crate::decoder::FramePool;
use crate::encoder::simple_encode_loop;
use crate::encoder::stateless::vp9::BackendRequest;
use crate::encoder::stateless::vp9::EncoderConfig;
use crate::encoder::stateless::vp9::StatelessEncoder;
use crate::encoder::stateless::BackendPromise;
use crate::encoder::stateless::StatelessEncoderBackendImport;
use crate::encoder::FrameMetadata;
use crate::encoder::Tunings;
use crate::FrameLayout;
use crate::PlaneLayout;
use crate::Resolution;
#[test]
// Ignore this test by default as it requires libva-compatible hardware.
#[ignore]
fn test_simple_encode_frame() {
type Descriptor = ();
type Surface = libva::Surface<Descriptor>;
const WIDTH: u32 = 256;
const HEIGHT: u32 = 256;
let fourcc = b"NV12".into();
let frame_layout = FrameLayout {
format: (fourcc, 0),
size: Resolution {
width: WIDTH,
height: HEIGHT,
},
planes: vec![
PlaneLayout {
buffer_index: 0,
offset: 0,
stride: WIDTH as usize,
},
PlaneLayout {
buffer_index: 0,
offset: (WIDTH * HEIGHT) as usize,
stride: WIDTH as usize,
},
],
};
let display = Display::open().unwrap();
let entrypoints = display
.query_config_entrypoints(VAProfileVP9Profile0)
.unwrap();
let low_power = entrypoints.contains(&VAEntrypointEncSliceLP);
let mut backend = VaapiBackend::<Descriptor, Surface>::new(
Rc::clone(&display),
VAProfileVP9Profile0,
fourcc,
Resolution {
width: WIDTH,
height: HEIGHT,
},
libva::constants::VA_RC_CBR,
low_power,
)
.unwrap();
let mut surfaces = display
.create_surfaces(
VA_RT_FORMAT_YUV420,
Some(frame_layout.format.0 .0),
WIDTH,
HEIGHT,
Some(UsageHint::USAGE_HINT_ENCODER),
vec![()],
)
.unwrap();
let surface = surfaces.pop().unwrap();
upload_test_frame_nv12(&display, &surface, 0.0);
let input_meta = FrameMetadata {
layout: frame_layout,
force_keyframe: false,
timestamp: 0,
};
let pic = backend.import_picture(&input_meta, surface).unwrap();
let header = Header {
frame_type: FrameType::KeyFrame,
show_frame: true,
error_resilient_mode: false,
width: WIDTH,
height: HEIGHT,
render_and_frame_size_different: false,
render_width: WIDTH,
render_height: HEIGHT,
intra_only: true,
refresh_frame_flags: 0x01,
ref_frame_idx: [0, 0, 0],
..Default::default()
};
let request = BackendRequest {
header,
input: pic,
input_meta,
last_frame_ref: None,
golden_frame_ref: None,
altref_frame_ref: None,
tunings: Tunings {
rate_control: RateControl::ConstantBitrate(30_000),
..Default::default()
},
coded_output: Vec::new(),
};
let (_, output) = backend.encode_frame(request).unwrap();
let output = output.sync().unwrap();
let write_to_file = std::option_env!("CROS_CODECS_TEST_WRITE_TO_FILE") == Some("true");
if write_to_file {
use std::io::Write;
let mut out = std::fs::File::create("test_simple_encode_frame.vp9.ivf").unwrap();
let file_header =
IvfFileHeader::new(IvfFileHeader::CODEC_VP9, WIDTH as u16, HEIGHT as u16, 30, 1);
let frame_header = IvfFrameHeader {
frame_size: output.len() as u32,
timestamp: 0,
};
file_header.writo_into(&mut out).unwrap();
frame_header.writo_into(&mut out).unwrap();
out.write_all(&output).unwrap();
out.flush().unwrap();
}
}
#[test]
// Ignore this test by default as it requires libva-compatible hardware.
#[ignore]
fn test_vaapi_encoder() {
type VaapiVp9Encoder<'l> =
StatelessEncoder<PooledVaSurface<()>, VaapiBackend<(), PooledVaSurface<()>>>;
const WIDTH: usize = 512;
const HEIGHT: usize = 512;
const FRAME_COUNT: u64 = 100;
let _ = env_logger::try_init();
let display = libva::Display::open().unwrap();
let entrypoints = display
.query_config_entrypoints(VAProfileVP9Profile0)
.unwrap();
let low_power = entrypoints.contains(&VAEntrypointEncSliceLP);
let config = EncoderConfig {
resolution: Resolution {
width: WIDTH as u32,
height: HEIGHT as u32,
},
initial_tunings: Tunings {
rate_control: RateControl::ConstantBitrate(200_000),
framerate: 30,
..Default::default()
},
..Default::default()
};
let frame_layout = FrameLayout {
format: (b"NV12".into(), 0),
size: Resolution {
width: WIDTH as u32,
height: HEIGHT as u32,
},
planes: vec![
PlaneLayout {
buffer_index: 0,
offset: 0,
stride: WIDTH,
},
PlaneLayout {
buffer_index: 0,
offset: WIDTH * HEIGHT,
stride: WIDTH,
},
],
};
let mut encoder = VaapiVp9Encoder::new_vaapi(
Rc::clone(&display),
config,
frame_layout.format.0,
frame_layout.size,
low_power,
BlockingMode::Blocking,
)
.unwrap();
let mut pool = VaSurfacePool::new(
Rc::clone(&display),
VA_RT_FORMAT_YUV420,
Some(UsageHint::USAGE_HINT_ENCODER),
Resolution {
width: WIDTH as u32,
height: HEIGHT as u32,
},
);
pool.add_frames(vec![(); 16]).unwrap();
let mut frame_producer = TestFrameGenerator::new(FRAME_COUNT, display, pool, frame_layout);
let mut bitstream = Vec::new();
let file_header = IvfFileHeader::new(
IvfFileHeader::CODEC_VP9,
WIDTH as u16,
HEIGHT as u16,
30,
FRAME_COUNT as u32,
);
file_header.writo_into(&mut bitstream).unwrap();
simple_encode_loop(&mut encoder, &mut frame_producer, |coded| {
let header = IvfFrameHeader {
timestamp: coded.metadata.timestamp,
frame_size: coded.bitstream.len() as u32,
};
header.writo_into(&mut bitstream).unwrap();
bitstream.extend(coded.bitstream);
})
.unwrap();
let write_to_file = std::option_env!("CROS_CODECS_TEST_WRITE_TO_FILE") == Some("true");
if write_to_file {
use std::io::Write;
let mut out = std::fs::File::create("test_vaapi_encoder.vp9.ivf").unwrap();
out.write_all(&bitstream).unwrap();
out.flush().unwrap();
}
}
#[test]
// Ignore this test by default as it requires libva-compatible hardware.
#[ignore]
fn test_vaapi_encoder_p010() {
type VaapiVp9Encoder<'l> =
StatelessEncoder<PooledVaSurface<()>, VaapiBackend<(), PooledVaSurface<()>>>;
const WIDTH: usize = 512;
const HEIGHT: usize = 512;
const FRAME_COUNT: u64 = 100;
let _ = env_logger::try_init();
let display = libva::Display::open().unwrap();
let entrypoints = display
.query_config_entrypoints(VAProfileVP9Profile2)
.unwrap();
let low_power = entrypoints.contains(&VAEntrypointEncSliceLP);
let config = EncoderConfig {
bit_depth: BitDepth::Depth10,
resolution: Resolution {
width: WIDTH as u32,
height: HEIGHT as u32,
},
initial_tunings: Tunings {
rate_control: RateControl::ConstantBitrate(200_000),
framerate: 30,
..Default::default()
},
..Default::default()
};
let frame_layout = FrameLayout {
format: (b"P010".into(), 0),
size: Resolution {
width: WIDTH as u32,
height: HEIGHT as u32,
},
planes: vec![
PlaneLayout {
buffer_index: 0,
offset: 0,
stride: WIDTH,
},
PlaneLayout {
buffer_index: 0,
offset: WIDTH * HEIGHT,
stride: WIDTH,
},
],
};
let mut encoder = VaapiVp9Encoder::new_vaapi(
Rc::clone(&display),
config,
frame_layout.format.0,
frame_layout.size,
low_power,
BlockingMode::Blocking,
)
.unwrap();
let mut pool = VaSurfacePool::new(
Rc::clone(&display),
VA_RT_FORMAT_YUV420_10,
Some(UsageHint::USAGE_HINT_ENCODER),
Resolution {
width: WIDTH as u32,
height: HEIGHT as u32,
},
);
pool.add_frames(vec![(); 16]).unwrap();
let mut frame_producer = TestFrameGenerator::new(FRAME_COUNT, display, pool, frame_layout);
let mut bitstream = Vec::new();
let file_header = IvfFileHeader::new(
IvfFileHeader::CODEC_VP9,
WIDTH as u16,
HEIGHT as u16,
30,
FRAME_COUNT as u32,
);
file_header.writo_into(&mut bitstream).unwrap();
simple_encode_loop(&mut encoder, &mut frame_producer, |coded| {
let header = IvfFrameHeader {
timestamp: coded.metadata.timestamp,
frame_size: coded.bitstream.len() as u32,
};
header.writo_into(&mut bitstream).unwrap();
bitstream.extend(coded.bitstream);
})
.unwrap();
let write_to_file = std::option_env!("CROS_CODECS_TEST_WRITE_TO_FILE") == Some("true");
if write_to_file {
use std::io::Write;
let mut out = std::fs::File::create("test_vaapi_encoder_p010.vp9.ivf").unwrap();
out.write_all(&bitstream).unwrap();
out.flush().unwrap();
}
}
}