// Copyright 2022 The ChromiumOS Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. //! Implements a lightweight and safe interface over `libva`. //! //! The starting point to using this crate is to open a [`Display`], from which a [`Context`] and //! [`Surface`]s can be allocated and used for doing actual work. mod bindings; pub mod buffer; mod config; mod context; mod display; mod generic_value; mod image; mod picture; mod surface; mod usage_hint; pub use bindings::VAConfigAttrib; pub use bindings::VAConfigAttribType; pub use bindings::VADRMPRIMESurfaceDescriptor; pub use bindings::VAEntrypoint; pub use bindings::VAImageFormat; pub use bindings::VAProfile; pub use bindings::VASurfaceAttrib; pub use bindings::VASurfaceAttribExternalBuffers; pub use bindings::VASurfaceAttribType; pub use bindings::VASurfaceID; pub use bindings::VASurfaceStatus; pub use bindings::_VADRMPRIMESurfaceDescriptor__bindgen_ty_1 as VADRMPRIMESurfaceDescriptorObject; pub use bindings::_VADRMPRIMESurfaceDescriptor__bindgen_ty_2 as VADRMPRIMESurfaceDescriptorLayer; pub use buffer::*; pub use config::*; pub use context::*; pub use display::*; pub use generic_value::*; pub use image::*; pub use picture::*; pub use surface::*; pub use usage_hint::*; use std::num::NonZeroI32; use crate::bindings::VAStatus; /// A `VAStatus` that is guaranteed to not be `VA_STATUS_SUCCESS`. #[derive(Debug)] pub struct VaError(NonZeroI32); impl VaError { /// Returns the `VAStatus` of this error. pub fn va_status(&self) -> VAStatus { self.0.get() as VAStatus } } impl std::fmt::Display for VaError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use std::ffi::CStr; // Safe because `vaErrorStr` will return a pointer to a statically allocated, null // terminated C string. The pointer is guaranteed to never be null. let err_str = unsafe { CStr::from_ptr(bindings::vaErrorStr(self.0.get())) } .to_str() .unwrap(); f.write_str(err_str) } } impl std::error::Error for VaError {} /// Checks a VA return value and returns a `VaError` if it is not `VA_STATUS_SUCCESS`. /// /// This can be used on the return value of any VA function returning `VAStatus` in order to /// convert it to a proper Rust `Result`. fn va_check(code: VAStatus) -> Result<(), VaError> { match code as u32 { bindings::VA_STATUS_SUCCESS => Ok(()), _ => Err(VaError(unsafe { NonZeroI32::new_unchecked(code) })), } } #[cfg(test)] mod tests { use std::io::Write; use std::rc::Rc; use super::*; /// Returns a 32-bit CRC for the visible part of `image`, which must be in NV12 format. fn crc_nv12_image(image: &Image) -> u32 { let data = image.as_ref(); let va_image = image.image(); let offsets = &va_image.offsets; let pitches = &va_image.pitches; let width = va_image.width as usize; let height = va_image.height as usize; // We only support NV12 images assert_eq!(va_image.format.fourcc, u32::from_ne_bytes(*b"NV12")); // Consistency check assert_eq!(va_image.num_planes, 2); let mut hasher = crc32fast::Hasher::new(); let offset = offsets[0] as usize; let pitch = pitches[0] as usize; let y_plane = data[offset..(offset + pitch * height)] .chunks(pitch) .map(|line| &line[0..width]); let offset = offsets[1] as usize; let pitch = pitches[1] as usize; let uv_plane = data[offset..(offset + pitch * ((height + 1) / 2))] .chunks(pitch) .map(|line| &line[0..width]); for line in y_plane.chain(uv_plane) { hasher.update(line); } hasher.finalize() } #[test] // Ignore this test by default as it requires libva-compatible hardware. #[ignore] fn libva_utils_mpeg2vldemo() { // Adapted from let display = Display::open().unwrap(); assert!(!display.query_vendor_string().unwrap().is_empty()); let profiles = display.query_config_profiles().unwrap(); assert!(!profiles.is_empty()); let profile = bindings::VAProfile::VAProfileMPEG2Main; let entrypoints = display.query_config_entrypoints(profile).unwrap(); assert!(!entrypoints.is_empty()); assert!(entrypoints .iter() .any(|e| *e == bindings::VAEntrypoint::VAEntrypointVLD)); let format = bindings::VA_RT_FORMAT_YUV420; let width = 16u32; let height = 16u32; let mut attrs = vec![bindings::VAConfigAttrib { type_: bindings::VAConfigAttribType::VAConfigAttribRTFormat, value: 0, }]; let entrypoint = bindings::VAEntrypoint::VAEntrypointVLD; display .get_config_attributes(profile, entrypoint, &mut attrs) .unwrap(); assert!(attrs[0].value != bindings::VA_ATTRIB_NOT_SUPPORTED); assert!(attrs[0].value & bindings::VA_RT_FORMAT_YUV420 != 0); let config = display.create_config(attrs, profile, entrypoint).unwrap(); let mut surfaces = display .create_surfaces( format, None, width, height, Some(UsageHint::USAGE_HINT_DECODER), vec![()], ) .unwrap(); let context = display .create_context( &config, width, ((height + 15) / 16) * 16, Some(&surfaces), true, ) .unwrap(); // The picture data is adapted from libva-utils at decode/mpeg2vldemo.cpp // Data dump of a 16x16 MPEG2 video clip,it has one I frame let mut mpeg2_clip: Vec = vec![ 0x00, 0x00, 0x01, 0xb3, 0x01, 0x00, 0x10, 0x13, 0xff, 0xff, 0xe0, 0x18, 0x00, 0x00, 0x01, 0xb5, 0x14, 0x8a, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0xb8, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0f, 0xff, 0xf8, 0x00, 0x00, 0x01, 0xb5, 0x8f, 0xff, 0xf3, 0x41, 0x80, 0x00, 0x00, 0x01, 0x01, 0x13, 0xe1, 0x00, 0x15, 0x81, 0x54, 0xe0, 0x2a, 0x05, 0x43, 0x00, 0x2d, 0x60, 0x18, 0x01, 0x4e, 0x82, 0xb9, 0x58, 0xb1, 0x83, 0x49, 0xa4, 0xa0, 0x2e, 0x05, 0x80, 0x4b, 0x7a, 0x00, 0x01, 0x38, 0x20, 0x80, 0xe8, 0x05, 0xff, 0x60, 0x18, 0xe0, 0x1d, 0x80, 0x98, 0x01, 0xf8, 0x06, 0x00, 0x54, 0x02, 0xc0, 0x18, 0x14, 0x03, 0xb2, 0x92, 0x80, 0xc0, 0x18, 0x94, 0x42, 0x2c, 0xb2, 0x11, 0x64, 0xa0, 0x12, 0x5e, 0x78, 0x03, 0x3c, 0x01, 0x80, 0x0e, 0x80, 0x18, 0x80, 0x6b, 0xca, 0x4e, 0x01, 0x0f, 0xe4, 0x32, 0xc9, 0xbf, 0x01, 0x42, 0x69, 0x43, 0x50, 0x4b, 0x01, 0xc9, 0x45, 0x80, 0x50, 0x01, 0x38, 0x65, 0xe8, 0x01, 0x03, 0xf3, 0xc0, 0x76, 0x00, 0xe0, 0x03, 0x20, 0x28, 0x18, 0x01, 0xa9, 0x34, 0x04, 0xc5, 0xe0, 0x0b, 0x0b, 0x04, 0x20, 0x06, 0xc0, 0x89, 0xff, 0x60, 0x12, 0x12, 0x8a, 0x2c, 0x34, 0x11, 0xff, 0xf6, 0xe2, 0x40, 0xc0, 0x30, 0x1b, 0x7a, 0x01, 0xa9, 0x0d, 0x00, 0xac, 0x64, ]; let picture_coding_extension = MPEG2PictureCodingExtension::new(0, 3, 0, 1, 0, 0, 0, 0, 0, 1, 1); let pic_param = PictureParameterBufferMPEG2::new( 16, 16, 0xffffffff, 0xffffffff, 1, 0xffff, &picture_coding_extension, ); let pic_param = BufferType::PictureParameter(PictureParameter::MPEG2(pic_param)); let iq_matrix = IQMatrixBufferMPEG2::new( 1, 1, 0, 0, [ 8, 16, 16, 19, 16, 19, 22, 22, 22, 22, 22, 22, 26, 24, 26, 27, 27, 27, 26, 26, 26, 26, 27, 27, 27, 29, 29, 29, 34, 34, 34, 29, 29, 29, 27, 27, 29, 29, 32, 32, 34, 34, 37, 38, 37, 35, 35, 34, 35, 38, 38, 40, 40, 40, 48, 48, 46, 46, 56, 56, 58, 69, 69, 83, ], [ 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ], [0; 64], [0; 64], ); let iq_matrix = BufferType::IQMatrix(IQMatrix::MPEG2(iq_matrix)); let slice_param = SliceParameterBufferMPEG2::new(150, 0, 0, 38, 0, 0, 2, 0); let slice_param = BufferType::SliceParameter(SliceParameter::MPEG2(slice_param)); let test_data_offset = 47; let slice_data = BufferType::SliceData(mpeg2_clip.drain(test_data_offset..).collect()); let buffers = vec![ context.create_buffer(pic_param).unwrap(), context.create_buffer(slice_param).unwrap(), context.create_buffer(iq_matrix).unwrap(), context.create_buffer(slice_data).unwrap(), ]; let mut picture = Picture::new(0, Rc::clone(&context), surfaces.remove(0)); for buffer in buffers { picture.add_buffer(buffer); } // Actual client code can just chain the calls. let picture = picture.begin().unwrap(); let picture = picture.render().unwrap(); let picture = picture.end().unwrap(); let picture = picture.sync().map_err(|(e, _)| e).unwrap(); // Test whether we can map the resulting surface to obtain the raw yuv // data let image_fmts = display.query_image_formats().unwrap(); let image_fmt = image_fmts .into_iter() .find(|f| f.fourcc == bindings::VA_FOURCC_NV12) .expect("No valid VAImageFormat found for NV12"); let resolution = (width, height); let image = picture .create_image(image_fmt, resolution, resolution) .unwrap(); assert_eq!(crc_nv12_image(&image), 0xa5713e52); } #[test] // Ignore this test by default as it requires libva-compatible hardware. #[ignore] fn enc_h264_demo() { // Based on `gst-launch-1.0 videotestsrc num-buffers=1 ! video/x-raw,width=64,height=64,format=NV12 ! vaapih264enc ! filesink location=frame.h264` // Frame created using `gst-launch-1.0 videotestsrc num-buffers=1 ! video/x-raw,width=64,height=64,format=NV12 ! filesink location=src/test_frame.nv12` let raw_frame_nv12 = include_bytes!("test_frame.nv12"); let display = Display::open().unwrap(); let format = bindings::VA_RT_FORMAT_YUV420; let entrypoint = bindings::VAEntrypoint::VAEntrypointEncSliceLP; let profile = bindings::VAProfile::VAProfileH264ConstrainedBaseline; let width = 64u32; let height = 64u32; let mut attrs = vec![bindings::VAConfigAttrib { type_: bindings::VAConfigAttribType::VAConfigAttribRTFormat, value: 0, }]; display .get_config_attributes(profile, entrypoint, &mut attrs) .unwrap(); let config = display.create_config(attrs, profile, entrypoint).unwrap(); let mut surfaces = display .create_surfaces( format, None, width, height, Some(UsageHint::USAGE_HINT_ENCODER), vec![()], ) .unwrap(); let context = display .create_context(&config, width, height, Some(&surfaces), true) .unwrap(); let seq_fields = H264EncSeqFields::new( 1, // 4:2:0 1, // Only frames 0, 0, 0, 1, 0, 2, 0, ); let image_fmts = display.query_image_formats().unwrap(); let image_fmt = image_fmts .into_iter() .find(|f| f.fourcc == bindings::VA_FOURCC_NV12) .expect("No valid VAImageFormat found for NV12"); let surface = surfaces.pop().unwrap(); let surface_id = surface.id(); let coded_buffer = context.create_enc_coded(raw_frame_nv12.len()).unwrap(); let mut image = Image::create_from(&surface, image_fmt, (width, height), (width, height)).unwrap(); let va_image = *image.image(); let dest = image.as_mut(); let data = &raw_frame_nv12[..]; let width = width as usize; let height = height as usize; let mut src = data; let mut dst = &mut dest[va_image.offsets[0] as usize..]; // Copy luma for _ in 0..height { dst[..width].copy_from_slice(&src[..width]); dst = &mut dst[va_image.pitches[0] as usize..]; src = &src[width..]; } // Advance to the offset of the chroma plane let mut src = &data[width * height..]; let mut dst = &mut dest[va_image.offsets[1] as usize..]; let height = height / 2; // Copy chroma for _ in 0..height { dst[..width].copy_from_slice(&src[..width]); dst = &mut dst[va_image.pitches[1] as usize..]; src = &src[width..]; } drop(image); let sps = BufferType::EncSequenceParameter(EncSequenceParameter::H264( EncSequenceParameterBufferH264::new( 0, 10, 10, 30, 1, 0, 1, (width / 16) as u16, // width / 16 (height / 16) as u16, // height / 16 &seq_fields, 0, 0, 0, 0, 0, [0; 256], None, Some(H264VuiFields::new(1, 1, 0, 0, 0, 1, 0, 0)), 255, 1, 1, 1, 60, ), )); let sps = context.create_buffer(sps).unwrap(); let ref_frames: [PictureH264; 16] = (0..16) .map(|_| { PictureH264::new( bindings::VA_INVALID_ID, 0, bindings::VA_INVALID_SURFACE, 0, 0, ) }) .collect::>() .try_into() .unwrap_or_else(|_| { panic!(); }); let pps = BufferType::EncPictureParameter(EncPictureParameter::H264( EncPictureParameterBufferH264::new( PictureH264::new(surface_id, 0, 0, 0, 0), ref_frames, coded_buffer.id(), 0, 0, 0, 0, 26, 0, 0, 0, 0, &H264EncPicFields::new(1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0), ), )); let pps = context.create_buffer(pps).unwrap(); let ref_pic_list_0: [PictureH264; 32] = (0..32) .map(|_| { PictureH264::new( bindings::VA_INVALID_ID, 0, bindings::VA_INVALID_SURFACE, 0, 0, ) }) .collect::>() .try_into() .unwrap_or_else(|_| { panic!(); }); let ref_pic_list_1: [PictureH264; 32] = (0..32) .map(|_| { PictureH264::new( bindings::VA_INVALID_ID, 0, bindings::VA_INVALID_SURFACE, 0, 0, ) }) .collect::>() .try_into() .unwrap_or_else(|_| { panic!(); }); let slice = BufferType::EncSliceParameter(EncSliceParameter::H264( EncSliceParameterBufferH264::new( 0, ((width / 16) * (height / 16)) as u32, bindings::VA_INVALID_ID, 2, // I 0, 1, 0, 0, [0, 0], 1, 0, 0, 0, ref_pic_list_0, ref_pic_list_1, 0, 0, 0, [0; 32], [0; 32], 0, [[0; 2]; 32], [[0; 2]; 32], 0, [0; 32], [0; 32], 0, [[0; 2]; 32], [[0; 2]; 32], 0, 0, 0, 2, 2, ), )); let slice = context.create_buffer(slice).unwrap(); let mut picture = Picture::new(0, Rc::clone(&context), surface); picture.add_buffer(pps); picture.add_buffer(sps); picture.add_buffer(slice); let picture = picture.begin().unwrap(); let picture = picture.render().unwrap(); let picture = picture.end().unwrap(); let _ = picture.sync().map_err(|(e, _)| e).unwrap(); let coded_buf = MappedCodedBuffer::new(&coded_buffer).unwrap(); assert_ne!(coded_buf.segments().len(), 0); for segment in coded_buf.iter() { assert_ne!(segment.buf.len(), 0); } const WRITE_TO_FILE: bool = false; if WRITE_TO_FILE { let raw_sps_bitstream: Vec = vec![ 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0xc0, 0x0a, 0xab, 0x42, 0x12, 0x7f, 0xe0, 0x00, 0x20, 0x00, 0x22, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, 0x79, 0x28, ]; let raw_pps_bitstream: Vec = vec![0x00, 0x00, 0x00, 0x01, 0x68, 0xce, 0x3c, 0x80]; let mut raw_file = std::fs::File::create("libva_utils_enc_h264_demo.h264").unwrap(); raw_file.write_all(&raw_sps_bitstream).unwrap(); raw_file.write_all(&raw_pps_bitstream).unwrap(); for segment in coded_buf.segments() { raw_file.write_all(segment.buf).unwrap(); } raw_file.flush().unwrap(); } } }