blob: f3596bd968f030be5283a1af0387737731224b3d [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::borrow::Borrow;
use std::fs::File;
use std::io::Read;
use std::io::Write;
use std::rc::Rc;
use cros_codecs::backend::vaapi::surface_pool::PooledVaSurface;
use cros_codecs::backend::vaapi::surface_pool::VaSurfacePool;
use cros_codecs::bitstream_utils::IvfFileHeader;
use cros_codecs::bitstream_utils::IvfFrameHeader;
use cros_codecs::decoder::FramePool;
use cros_codecs::encoder::av1::EncoderConfig as AV1EncoderConfig;
use cros_codecs::encoder::h264::EncoderConfig as H264EncoderConfig;
use cros_codecs::encoder::stateless::av1;
use cros_codecs::encoder::stateless::h264;
use cros_codecs::encoder::stateless::vp9;
use cros_codecs::encoder::vp9::EncoderConfig as VP9EncoderConfig;
use cros_codecs::encoder::FrameMetadata;
use cros_codecs::encoder::RateControl;
use cros_codecs::encoder::Tunings;
use cros_codecs::encoder::VideoEncoder;
use cros_codecs::image_processing::extend_border_nv12;
use cros_codecs::image_processing::i420_to_nv12_chroma;
use cros_codecs::image_processing::nv12_copy;
use cros_codecs::BlockingMode;
use cros_codecs::DecodedFormat;
use cros_codecs::Fourcc;
use cros_codecs::FrameLayout;
use cros_codecs::PlaneLayout;
use cros_codecs::Resolution;
use crate::util::Args;
use crate::util::Codec;
fn upload_img<M: libva::SurfaceMemoryDescriptor>(
display: &Rc<libva::Display>,
surface: &libva::Surface<M>,
resolution: Resolution,
input_coded_resolution: Resolution,
data: &[u8],
input_fourcc: DecodedFormat,
) -> FrameLayout {
let input_y = &data[0..input_coded_resolution.get_area()];
let mut tmp_input_uv: Vec<u8> = Vec::new();
let input_uv = match input_fourcc {
DecodedFormat::NV12 => {
&data[input_coded_resolution.get_area()..(input_coded_resolution.get_area() * 3 / 2)]
}
DecodedFormat::I420 => {
tmp_input_uv.resize(input_coded_resolution.get_area() / 2, 0);
let input_u = &data
[input_coded_resolution.get_area()..(input_coded_resolution.get_area() * 5 / 4)];
let input_v = &data[(input_coded_resolution.get_area() * 5 / 4)
..(input_coded_resolution.get_area() * 3 / 2)];
i420_to_nv12_chroma(input_u, input_v, tmp_input_uv.as_mut_slice());
tmp_input_uv.as_slice()
}
_ => panic!("Unsupported input format!"),
};
let image_fmts = display.query_image_formats().unwrap();
let image_fmt = image_fmts
.into_iter()
.find(|f| f.fourcc == libva::VA_FOURCC_NV12)
.unwrap();
let mut image = libva::Image::create_from(
surface,
image_fmt,
(resolution.width, resolution.height),
(resolution.width, resolution.height),
)
.unwrap();
let va_image = *image.image();
let dst = image.as_mut();
let (dst_y, dst_uv) =
(&mut dst[va_image.offsets[0] as usize..]).split_at_mut(va_image.offsets[1] as usize);
nv12_copy(
input_y,
input_coded_resolution.width as usize,
dst_y,
va_image.pitches[0] as usize,
input_uv,
input_coded_resolution.width as usize,
dst_uv,
va_image.pitches[1] as usize,
resolution.width as usize,
resolution.height as usize,
);
extend_border_nv12(
dst_y,
dst_uv,
resolution.width as usize,
resolution.height as usize,
va_image.pitches[0] as usize,
va_image.height as usize,
);
drop(image);
surface.sync().unwrap();
FrameLayout {
format: (Fourcc::from(b"NV12"), 0),
size: resolution,
planes: vec![
PlaneLayout {
buffer_index: 0,
offset: va_image.offsets[0] as usize,
stride: va_image.pitches[0] as usize,
},
PlaneLayout {
buffer_index: 0,
offset: va_image.offsets[1] as usize,
stride: va_image.pitches[1] as usize,
},
],
}
}
fn new_h264_vaapi_encoder(
args: &Args,
display: &Rc<libva::Display>,
) -> Box<dyn VideoEncoder<PooledVaSurface<()>>> {
let resolution = Resolution {
width: args.width,
height: args.height,
};
let config = H264EncoderConfig {
resolution,
initial_tunings: Tunings {
framerate: args.framerate,
rate_control: RateControl::ConstantBitrate(args.bitrate),
..Default::default()
},
..Default::default()
};
let fourcc = b"NV12".into();
let encoder = h264::StatelessEncoder::new_vaapi(
Rc::clone(display),
config,
fourcc,
resolution,
args.low_power,
BlockingMode::Blocking,
)
.expect("Unable to create encoder");
Box::new(encoder)
}
fn new_vp9_vaapi_encoder(
args: &Args,
display: &Rc<libva::Display>,
) -> Box<dyn VideoEncoder<PooledVaSurface<()>>> {
let resolution = Resolution {
width: args.width,
height: args.height,
};
let config = VP9EncoderConfig {
resolution,
initial_tunings: Tunings {
framerate: args.framerate,
rate_control: RateControl::ConstantBitrate(args.bitrate),
..Default::default()
},
..Default::default()
};
let fourcc = b"NV12".into();
let encoder = vp9::StatelessEncoder::new_vaapi(
Rc::clone(display),
config,
fourcc,
resolution,
args.low_power,
BlockingMode::Blocking,
)
.expect("Unable to create encoder");
Box::new(encoder)
}
fn new_av1_vaapi_encoder(
args: &Args,
display: &Rc<libva::Display>,
) -> Box<dyn VideoEncoder<PooledVaSurface<()>>> {
let resolution = Resolution {
width: args.width,
height: args.height,
};
let config = AV1EncoderConfig {
resolution,
initial_tunings: Tunings {
framerate: args.framerate,
rate_control: RateControl::ConstantBitrate(args.bitrate),
..Default::default()
},
..Default::default()
};
let fourcc = b"NV12".into();
let encoder = av1::StatelessEncoder::new_vaapi(
Rc::clone(display),
config,
fourcc,
resolution,
args.low_power,
BlockingMode::Blocking,
)
.expect("Unable to create encoder");
Box::new(encoder)
}
pub fn do_encode(mut input: File, args: Args) -> () {
let display = libva::Display::open().unwrap();
let codec = args.codec.unwrap_or_default();
let mut encoder = match codec {
Codec::H264 => new_h264_vaapi_encoder(&args, &display),
Codec::VP9 => new_vp9_vaapi_encoder(&args, &display),
Codec::AV1 => new_av1_vaapi_encoder(&args, &display),
_ => panic!("Unsupported format!"),
};
let mut pool = VaSurfacePool::new(
Rc::clone(&display),
libva::VA_RT_FORMAT_YUV420,
Some(libva::UsageHint::USAGE_HINT_ENCODER),
Resolution {
width: args.width,
height: args.height,
},
);
pool.add_frames(vec![(); 16]).unwrap();
let coded_width = args.coded_width.unwrap_or(args.width);
let coded_height = args.coded_height.unwrap_or(args.height);
let coded_frame_size: usize = (coded_width * coded_height * 3 / 2) as usize;
let mut output = args.output.map(|output| File::create(output).unwrap());
if let Some(ref mut output) = output {
if codec == Codec::VP9 {
let hdr = IvfFileHeader::new(
IvfFileHeader::CODEC_VP9,
args.width as u16,
args.height as u16,
args.framerate,
args.count as u32,
);
hdr.writo_into(output).unwrap();
}
}
let mut buf = vec![0u8; coded_frame_size];
for i in 0..args.count {
input.read_exact(&mut buf[..]).unwrap();
let handle = pool.get_surface().unwrap();
let layout = upload_img(
&display,
handle.borrow(),
(args.width, args.height).into(),
(coded_width, coded_height).into(),
&buf[..],
args.fourcc,
);
let input_frame = FrameMetadata {
layout,
timestamp: i as u64,
force_keyframe: false,
};
encoder.encode(input_frame, handle).unwrap();
while let Some(coded) = encoder.poll().unwrap() {
if let Some(ref mut output) = output {
if codec == Codec::VP9 {
let hdr = IvfFrameHeader {
timestamp: coded.metadata.timestamp,
frame_size: coded.bitstream.len() as u32,
};
hdr.writo_into(output).unwrap();
}
output.write_all(&coded.bitstream).unwrap();
}
}
}
encoder.drain().unwrap();
while let Some(coded) = encoder.poll().unwrap() {
if let Some(ref mut output) = output {
if codec == Codec::VP9 {
let hdr = IvfFrameHeader {
timestamp: coded.metadata.timestamp,
frame_size: coded.bitstream.len() as u32,
};
hdr.writo_into(output).unwrap();
}
output.write_all(&coded.bitstream).unwrap();
}
}
}