1 // Copyright 2022 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 //! Implements a lightweight and safe interface over `libva`.
6 //!
7 //! The starting point to using this crate is to open a [`Display`], from which a [`Context`] and
8 //! [`Surface`]s can be allocated and used for doing actual work.
9 
10 mod bindings;
11 pub mod buffer;
12 mod config;
13 mod context;
14 mod display;
15 mod generic_value;
16 mod image;
17 mod picture;
18 mod surface;
19 mod usage_hint;
20 
21 pub use bindings::VAConfigAttrib;
22 pub use bindings::VAConfigAttribType;
23 pub use bindings::VADRMPRIMESurfaceDescriptor;
24 pub use bindings::VAEntrypoint;
25 pub use bindings::VAImageFormat;
26 pub use bindings::VAProfile;
27 pub use bindings::VASurfaceAttrib;
28 pub use bindings::VASurfaceAttribExternalBuffers;
29 pub use bindings::VASurfaceAttribType;
30 pub use bindings::VASurfaceID;
31 pub use bindings::VASurfaceStatus;
32 pub use bindings::_VADRMPRIMESurfaceDescriptor__bindgen_ty_1 as VADRMPRIMESurfaceDescriptorObject;
33 pub use bindings::_VADRMPRIMESurfaceDescriptor__bindgen_ty_2 as VADRMPRIMESurfaceDescriptorLayer;
34 pub use buffer::*;
35 pub use config::*;
36 pub use context::*;
37 pub use display::*;
38 pub use generic_value::*;
39 pub use image::*;
40 pub use picture::*;
41 pub use surface::*;
42 pub use usage_hint::*;
43 
44 use std::num::NonZeroI32;
45 
46 use crate::bindings::VAStatus;
47 
48 /// A `VAStatus` that is guaranteed to not be `VA_STATUS_SUCCESS`.
49 #[derive(Debug)]
50 pub struct VaError(NonZeroI32);
51 
52 impl VaError {
53     /// Returns the `VAStatus` of this error.
va_status(&self) -> VAStatus54     pub fn va_status(&self) -> VAStatus {
55         self.0.get() as VAStatus
56     }
57 }
58 
59 impl std::fmt::Display for VaError {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result60     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61         use std::ffi::CStr;
62 
63         // Safe because `vaErrorStr` will return a pointer to a statically allocated, null
64         // terminated C string. The pointer is guaranteed to never be null.
65         let err_str = unsafe { CStr::from_ptr(bindings::vaErrorStr(self.0.get())) }
66             .to_str()
67             .unwrap();
68         f.write_str(err_str)
69     }
70 }
71 
72 impl std::error::Error for VaError {}
73 
74 /// Checks a VA return value and returns a `VaError` if it is not `VA_STATUS_SUCCESS`.
75 ///
76 /// This can be used on the return value of any VA function returning `VAStatus` in order to
77 /// convert it to a proper Rust `Result`.
va_check(code: VAStatus) -> Result<(), VaError>78 fn va_check(code: VAStatus) -> Result<(), VaError> {
79     match code as u32 {
80         bindings::VA_STATUS_SUCCESS => Ok(()),
81         _ => Err(VaError(unsafe { NonZeroI32::new_unchecked(code) })),
82     }
83 }
84 
85 #[cfg(test)]
86 mod tests {
87     use std::io::Write;
88     use std::rc::Rc;
89 
90     use super::*;
91 
92     /// Returns a 32-bit CRC for the visible part of `image`, which must be in NV12 format.
crc_nv12_image(image: &Image) -> u3293     fn crc_nv12_image(image: &Image) -> u32 {
94         let data = image.as_ref();
95         let va_image = image.image();
96         let offsets = &va_image.offsets;
97         let pitches = &va_image.pitches;
98         let width = va_image.width as usize;
99         let height = va_image.height as usize;
100 
101         // We only support NV12 images
102         assert_eq!(va_image.format.fourcc, u32::from_ne_bytes(*b"NV12"));
103         // Consistency check
104         assert_eq!(va_image.num_planes, 2);
105 
106         let mut hasher = crc32fast::Hasher::new();
107 
108         let offset = offsets[0] as usize;
109         let pitch = pitches[0] as usize;
110         let y_plane = data[offset..(offset + pitch * height)]
111             .chunks(pitch)
112             .map(|line| &line[0..width]);
113 
114         let offset = offsets[1] as usize;
115         let pitch = pitches[1] as usize;
116         let uv_plane = data[offset..(offset + pitch * ((height + 1) / 2))]
117             .chunks(pitch)
118             .map(|line| &line[0..width]);
119 
120         for line in y_plane.chain(uv_plane) {
121             hasher.update(line);
122         }
123 
124         hasher.finalize()
125     }
126 
127     #[test]
128     // Ignore this test by default as it requires libva-compatible hardware.
129     #[ignore]
libva_utils_mpeg2vldemo()130     fn libva_utils_mpeg2vldemo() {
131         // Adapted from <https://github.com/intel/libva-utils/blob/master/decode/mpeg2vldemo.cpp>
132         let display = Display::open().unwrap();
133 
134         assert!(!display.query_vendor_string().unwrap().is_empty());
135         let profiles = display.query_config_profiles().unwrap();
136         assert!(!profiles.is_empty());
137 
138         let profile = bindings::VAProfile::VAProfileMPEG2Main;
139         let entrypoints = display.query_config_entrypoints(profile).unwrap();
140         assert!(!entrypoints.is_empty());
141         assert!(entrypoints
142             .iter()
143             .any(|e| *e == bindings::VAEntrypoint::VAEntrypointVLD));
144 
145         let format = bindings::VA_RT_FORMAT_YUV420;
146         let width = 16u32;
147         let height = 16u32;
148 
149         let mut attrs = vec![bindings::VAConfigAttrib {
150             type_: bindings::VAConfigAttribType::VAConfigAttribRTFormat,
151             value: 0,
152         }];
153 
154         let entrypoint = bindings::VAEntrypoint::VAEntrypointVLD;
155         display
156             .get_config_attributes(profile, entrypoint, &mut attrs)
157             .unwrap();
158         assert!(attrs[0].value != bindings::VA_ATTRIB_NOT_SUPPORTED);
159         assert!(attrs[0].value & bindings::VA_RT_FORMAT_YUV420 != 0);
160 
161         let config = display.create_config(attrs, profile, entrypoint).unwrap();
162 
163         let mut surfaces = display
164             .create_surfaces(
165                 format,
166                 None,
167                 width,
168                 height,
169                 Some(UsageHint::USAGE_HINT_DECODER),
170                 vec![()],
171             )
172             .unwrap();
173         let context = display
174             .create_context(
175                 &config,
176                 width,
177                 ((height + 15) / 16) * 16,
178                 Some(&surfaces),
179                 true,
180             )
181             .unwrap();
182 
183         // The picture data is adapted from libva-utils at decode/mpeg2vldemo.cpp
184         // Data dump of a 16x16 MPEG2 video clip,it has one I frame
185         let mut mpeg2_clip: Vec<u8> = vec![
186             0x00, 0x00, 0x01, 0xb3, 0x01, 0x00, 0x10, 0x13, 0xff, 0xff, 0xe0, 0x18, 0x00, 0x00,
187             0x01, 0xb5, 0x14, 0x8a, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0xb8, 0x00, 0x08,
188             0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0f, 0xff, 0xf8, 0x00, 0x00, 0x01, 0xb5,
189             0x8f, 0xff, 0xf3, 0x41, 0x80, 0x00, 0x00, 0x01, 0x01, 0x13, 0xe1, 0x00, 0x15, 0x81,
190             0x54, 0xe0, 0x2a, 0x05, 0x43, 0x00, 0x2d, 0x60, 0x18, 0x01, 0x4e, 0x82, 0xb9, 0x58,
191             0xb1, 0x83, 0x49, 0xa4, 0xa0, 0x2e, 0x05, 0x80, 0x4b, 0x7a, 0x00, 0x01, 0x38, 0x20,
192             0x80, 0xe8, 0x05, 0xff, 0x60, 0x18, 0xe0, 0x1d, 0x80, 0x98, 0x01, 0xf8, 0x06, 0x00,
193             0x54, 0x02, 0xc0, 0x18, 0x14, 0x03, 0xb2, 0x92, 0x80, 0xc0, 0x18, 0x94, 0x42, 0x2c,
194             0xb2, 0x11, 0x64, 0xa0, 0x12, 0x5e, 0x78, 0x03, 0x3c, 0x01, 0x80, 0x0e, 0x80, 0x18,
195             0x80, 0x6b, 0xca, 0x4e, 0x01, 0x0f, 0xe4, 0x32, 0xc9, 0xbf, 0x01, 0x42, 0x69, 0x43,
196             0x50, 0x4b, 0x01, 0xc9, 0x45, 0x80, 0x50, 0x01, 0x38, 0x65, 0xe8, 0x01, 0x03, 0xf3,
197             0xc0, 0x76, 0x00, 0xe0, 0x03, 0x20, 0x28, 0x18, 0x01, 0xa9, 0x34, 0x04, 0xc5, 0xe0,
198             0x0b, 0x0b, 0x04, 0x20, 0x06, 0xc0, 0x89, 0xff, 0x60, 0x12, 0x12, 0x8a, 0x2c, 0x34,
199             0x11, 0xff, 0xf6, 0xe2, 0x40, 0xc0, 0x30, 0x1b, 0x7a, 0x01, 0xa9, 0x0d, 0x00, 0xac,
200             0x64,
201         ];
202 
203         let picture_coding_extension =
204             MPEG2PictureCodingExtension::new(0, 3, 0, 1, 0, 0, 0, 0, 0, 1, 1);
205         let pic_param = PictureParameterBufferMPEG2::new(
206             16,
207             16,
208             0xffffffff,
209             0xffffffff,
210             1,
211             0xffff,
212             &picture_coding_extension,
213         );
214 
215         let pic_param = BufferType::PictureParameter(PictureParameter::MPEG2(pic_param));
216 
217         let iq_matrix = IQMatrixBufferMPEG2::new(
218             1,
219             1,
220             0,
221             0,
222             [
223                 8, 16, 16, 19, 16, 19, 22, 22, 22, 22, 22, 22, 26, 24, 26, 27, 27, 27, 26, 26, 26,
224                 26, 27, 27, 27, 29, 29, 29, 34, 34, 34, 29, 29, 29, 27, 27, 29, 29, 32, 32, 34, 34,
225                 37, 38, 37, 35, 35, 34, 35, 38, 38, 40, 40, 40, 48, 48, 46, 46, 56, 56, 58, 69, 69,
226                 83,
227             ],
228             [
229                 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,
230                 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,
231                 0, 0, 0, 0, 0, 0, 0, 0, 0,
232             ],
233             [0; 64],
234             [0; 64],
235         );
236 
237         let iq_matrix = BufferType::IQMatrix(IQMatrix::MPEG2(iq_matrix));
238 
239         let slice_param = SliceParameterBufferMPEG2::new(150, 0, 0, 38, 0, 0, 2, 0);
240 
241         let slice_param = BufferType::SliceParameter(SliceParameter::MPEG2(slice_param));
242 
243         let test_data_offset = 47;
244         let slice_data = BufferType::SliceData(mpeg2_clip.drain(test_data_offset..).collect());
245 
246         let buffers = vec![
247             context.create_buffer(pic_param).unwrap(),
248             context.create_buffer(slice_param).unwrap(),
249             context.create_buffer(iq_matrix).unwrap(),
250             context.create_buffer(slice_data).unwrap(),
251         ];
252 
253         let mut picture = Picture::new(0, Rc::clone(&context), surfaces.remove(0));
254         for buffer in buffers {
255             picture.add_buffer(buffer);
256         }
257 
258         // Actual client code can just chain the calls.
259         let picture = picture.begin().unwrap();
260         let picture = picture.render().unwrap();
261         let picture = picture.end().unwrap();
262         let picture = picture.sync().map_err(|(e, _)| e).unwrap();
263 
264         // Test whether we can map the resulting surface to obtain the raw yuv
265         // data
266         let image_fmts = display.query_image_formats().unwrap();
267         let image_fmt = image_fmts
268             .into_iter()
269             .find(|f| f.fourcc == bindings::VA_FOURCC_NV12)
270             .expect("No valid VAImageFormat found for NV12");
271 
272         let resolution = (width, height);
273         let image = picture
274             .create_image(image_fmt, resolution, resolution)
275             .unwrap();
276 
277         assert_eq!(crc_nv12_image(&image), 0xa5713e52);
278     }
279 
280     #[test]
281     // Ignore this test by default as it requires libva-compatible hardware.
282     #[ignore]
enc_h264_demo()283     fn enc_h264_demo() {
284         // Based on `gst-launch-1.0 videotestsrc num-buffers=1 ! video/x-raw,width=64,height=64,format=NV12 ! vaapih264enc ! filesink location=frame.h264`
285         // 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`
286         let raw_frame_nv12 = include_bytes!("test_frame.nv12");
287 
288         let display = Display::open().unwrap();
289 
290         let format = bindings::VA_RT_FORMAT_YUV420;
291         let entrypoint = bindings::VAEntrypoint::VAEntrypointEncSliceLP;
292         let profile = bindings::VAProfile::VAProfileH264ConstrainedBaseline;
293         let width = 64u32;
294         let height = 64u32;
295 
296         let mut attrs = vec![bindings::VAConfigAttrib {
297             type_: bindings::VAConfigAttribType::VAConfigAttribRTFormat,
298             value: 0,
299         }];
300 
301         display
302             .get_config_attributes(profile, entrypoint, &mut attrs)
303             .unwrap();
304 
305         let config = display.create_config(attrs, profile, entrypoint).unwrap();
306 
307         let mut surfaces = display
308             .create_surfaces(
309                 format,
310                 None,
311                 width,
312                 height,
313                 Some(UsageHint::USAGE_HINT_ENCODER),
314                 vec![()],
315             )
316             .unwrap();
317 
318         let context = display
319             .create_context(&config, width, height, Some(&surfaces), true)
320             .unwrap();
321 
322         let seq_fields = H264EncSeqFields::new(
323             1, // 4:2:0
324             1, // Only frames
325             0, 0, 0, 1, 0, 2, 0,
326         );
327 
328         let image_fmts = display.query_image_formats().unwrap();
329         let image_fmt = image_fmts
330             .into_iter()
331             .find(|f| f.fourcc == bindings::VA_FOURCC_NV12)
332             .expect("No valid VAImageFormat found for NV12");
333 
334         let surface = surfaces.pop().unwrap();
335         let surface_id = surface.id();
336 
337         let coded_buffer = context.create_enc_coded(raw_frame_nv12.len()).unwrap();
338 
339         let mut image =
340             Image::create_from(&surface, image_fmt, (width, height), (width, height)).unwrap();
341 
342         let va_image = *image.image();
343         let dest = image.as_mut();
344         let data = &raw_frame_nv12[..];
345         let width = width as usize;
346         let height = height as usize;
347 
348         let mut src = data;
349         let mut dst = &mut dest[va_image.offsets[0] as usize..];
350 
351         // Copy luma
352         for _ in 0..height {
353             dst[..width].copy_from_slice(&src[..width]);
354             dst = &mut dst[va_image.pitches[0] as usize..];
355             src = &src[width..];
356         }
357 
358         // Advance to the offset of the chroma plane
359         let mut src = &data[width * height..];
360         let mut dst = &mut dest[va_image.offsets[1] as usize..];
361 
362         let height = height / 2;
363 
364         // Copy chroma
365         for _ in 0..height {
366             dst[..width].copy_from_slice(&src[..width]);
367             dst = &mut dst[va_image.pitches[1] as usize..];
368             src = &src[width..];
369         }
370         drop(image);
371 
372         let sps = BufferType::EncSequenceParameter(EncSequenceParameter::H264(
373             EncSequenceParameterBufferH264::new(
374                 0,
375                 10,
376                 10,
377                 30,
378                 1,
379                 0,
380                 1,
381                 (width / 16) as u16,  // width / 16
382                 (height / 16) as u16, // height / 16
383                 &seq_fields,
384                 0,
385                 0,
386                 0,
387                 0,
388                 0,
389                 [0; 256],
390                 None,
391                 Some(H264VuiFields::new(1, 1, 0, 0, 0, 1, 0, 0)),
392                 255,
393                 1,
394                 1,
395                 1,
396                 60,
397             ),
398         ));
399 
400         let sps = context.create_buffer(sps).unwrap();
401 
402         let ref_frames: [PictureH264; 16] = (0..16)
403             .map(|_| {
404                 PictureH264::new(
405                     bindings::VA_INVALID_ID,
406                     0,
407                     bindings::VA_INVALID_SURFACE,
408                     0,
409                     0,
410                 )
411             })
412             .collect::<Vec<_>>()
413             .try_into()
414             .unwrap_or_else(|_| {
415                 panic!();
416             });
417 
418         let pps = BufferType::EncPictureParameter(EncPictureParameter::H264(
419             EncPictureParameterBufferH264::new(
420                 PictureH264::new(surface_id, 0, 0, 0, 0),
421                 ref_frames,
422                 coded_buffer.id(),
423                 0,
424                 0,
425                 0,
426                 0,
427                 26,
428                 0,
429                 0,
430                 0,
431                 0,
432                 &H264EncPicFields::new(1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0),
433             ),
434         ));
435 
436         let pps = context.create_buffer(pps).unwrap();
437 
438         let ref_pic_list_0: [PictureH264; 32] = (0..32)
439             .map(|_| {
440                 PictureH264::new(
441                     bindings::VA_INVALID_ID,
442                     0,
443                     bindings::VA_INVALID_SURFACE,
444                     0,
445                     0,
446                 )
447             })
448             .collect::<Vec<_>>()
449             .try_into()
450             .unwrap_or_else(|_| {
451                 panic!();
452             });
453 
454         let ref_pic_list_1: [PictureH264; 32] = (0..32)
455             .map(|_| {
456                 PictureH264::new(
457                     bindings::VA_INVALID_ID,
458                     0,
459                     bindings::VA_INVALID_SURFACE,
460                     0,
461                     0,
462                 )
463             })
464             .collect::<Vec<_>>()
465             .try_into()
466             .unwrap_or_else(|_| {
467                 panic!();
468             });
469 
470         let slice = BufferType::EncSliceParameter(EncSliceParameter::H264(
471             EncSliceParameterBufferH264::new(
472                 0,
473                 ((width / 16) * (height / 16)) as u32,
474                 bindings::VA_INVALID_ID,
475                 2, // I
476                 0,
477                 1,
478                 0,
479                 0,
480                 [0, 0],
481                 1,
482                 0,
483                 0,
484                 0,
485                 ref_pic_list_0,
486                 ref_pic_list_1,
487                 0,
488                 0,
489                 0,
490                 [0; 32],
491                 [0; 32],
492                 0,
493                 [[0; 2]; 32],
494                 [[0; 2]; 32],
495                 0,
496                 [0; 32],
497                 [0; 32],
498                 0,
499                 [[0; 2]; 32],
500                 [[0; 2]; 32],
501                 0,
502                 0,
503                 0,
504                 2,
505                 2,
506             ),
507         ));
508 
509         let slice = context.create_buffer(slice).unwrap();
510 
511         let mut picture = Picture::new(0, Rc::clone(&context), surface);
512         picture.add_buffer(pps);
513         picture.add_buffer(sps);
514         picture.add_buffer(slice);
515 
516         let picture = picture.begin().unwrap();
517         let picture = picture.render().unwrap();
518         let picture = picture.end().unwrap();
519         let _ = picture.sync().map_err(|(e, _)| e).unwrap();
520 
521         let coded_buf = MappedCodedBuffer::new(&coded_buffer).unwrap();
522         assert_ne!(coded_buf.segments().len(), 0);
523 
524         for segment in coded_buf.iter() {
525             assert_ne!(segment.buf.len(), 0);
526         }
527 
528         const WRITE_TO_FILE: bool = false;
529         if WRITE_TO_FILE {
530             let raw_sps_bitstream: Vec<u8> = vec![
531                 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0xc0, 0x0a, 0xab, 0x42, 0x12, 0x7f, 0xe0, 0x00,
532                 0x20, 0x00, 0x22, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, 0x79, 0x28,
533             ];
534 
535             let raw_pps_bitstream: Vec<u8> = vec![0x00, 0x00, 0x00, 0x01, 0x68, 0xce, 0x3c, 0x80];
536 
537             let mut raw_file = std::fs::File::create("libva_utils_enc_h264_demo.h264").unwrap();
538             raw_file.write_all(&raw_sps_bitstream).unwrap();
539             raw_file.write_all(&raw_pps_bitstream).unwrap();
540 
541             for segment in coded_buf.segments() {
542                 raw_file.write_all(segment.buf).unwrap();
543             }
544 
545             raw_file.flush().unwrap();
546         }
547     }
548 }
549