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