cros_codecs/encoder/stateless/h264/
vaapi.rs

1// Copyright 2024 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
5use std::any::Any;
6use std::borrow::Borrow;
7use std::rc::Rc;
8
9use anyhow::Context;
10use libva::constants::VA_INVALID_ID;
11use libva::constants::VA_PICTURE_H264_LONG_TERM_REFERENCE;
12use libva::constants::VA_PICTURE_H264_SHORT_TERM_REFERENCE;
13use libva::BufferType;
14use libva::Display;
15use libva::EncCodedBuffer;
16use libva::EncPictureParameter;
17use libva::EncPictureParameterBufferH264;
18use libva::EncSequenceParameter;
19use libva::EncSequenceParameterBufferH264;
20use libva::EncSliceParameter;
21use libva::EncSliceParameterBufferH264;
22use libva::H264EncFrameCropOffsets;
23use libva::H264EncPicFields;
24use libva::H264EncSeqFields;
25use libva::H264VuiFields;
26use libva::Picture;
27use libva::PictureH264;
28use libva::Surface;
29use libva::SurfaceMemoryDescriptor;
30use libva::VAProfile;
31
32use crate::backend::vaapi::encoder::tunings_to_libva_rc;
33use crate::backend::vaapi::encoder::CodedOutputPromise;
34use crate::backend::vaapi::encoder::Reconstructed;
35use crate::backend::vaapi::encoder::VaapiBackend;
36use crate::codec::h264::parser::Pps;
37use crate::codec::h264::parser::Profile;
38use crate::codec::h264::parser::SliceHeader;
39use crate::codec::h264::parser::Sps;
40use crate::encoder::h264::EncoderConfig;
41use crate::encoder::h264::H264;
42use crate::encoder::stateless::h264::predictor::MAX_QP;
43use crate::encoder::stateless::h264::predictor::MIN_QP;
44use crate::encoder::stateless::h264::BackendRequest;
45use crate::encoder::stateless::h264::DpbEntry;
46use crate::encoder::stateless::h264::DpbEntryMeta;
47use crate::encoder::stateless::h264::IsReference;
48use crate::encoder::stateless::h264::StatelessEncoder;
49use crate::encoder::stateless::h264::StatelessH264EncoderBackend;
50use crate::encoder::stateless::ReadyPromise;
51use crate::encoder::stateless::StatelessBackendError;
52use crate::encoder::stateless::StatelessBackendResult;
53use crate::encoder::stateless::StatelessVideoEncoderBackend;
54use crate::encoder::EncodeResult;
55use crate::encoder::RateControl;
56use crate::BlockingMode;
57use crate::Fourcc;
58use crate::Resolution;
59
60type Request<'l, H> = BackendRequest<H, Reconstructed>;
61
62impl<M, H> StatelessVideoEncoderBackend<H264> for VaapiBackend<M, H>
63where
64    M: SurfaceMemoryDescriptor,
65    H: std::borrow::Borrow<Surface<M>> + 'static,
66{
67    type Picture = H;
68    type Reconstructed = Reconstructed;
69    type CodedPromise = CodedOutputPromise<M, H>;
70    type ReconPromise = ReadyPromise<Self::Reconstructed>;
71}
72
73impl<M, H> VaapiBackend<M, H>
74where
75    M: SurfaceMemoryDescriptor,
76    H: std::borrow::Borrow<Surface<M>> + 'static,
77{
78    /// Builds an invalid [`libva::PictureH264`]. This is usually a place
79    /// holder to fill staticly sized array.
80    fn build_invalid_va_h264_pic_enc() -> libva::PictureH264 {
81        libva::PictureH264::new(
82            libva::constants::VA_INVALID_ID,
83            0,
84            libva::constants::VA_PICTURE_H264_INVALID,
85            0,
86            0,
87        )
88    }
89
90    /// Builds [`libva::PictureH264`] from `frame`
91    fn build_h264_pic(surface: &Reconstructed, meta: &DpbEntryMeta) -> PictureH264 {
92        let flags = match meta.is_reference {
93            IsReference::No => 0,
94            IsReference::LongTerm => VA_PICTURE_H264_LONG_TERM_REFERENCE,
95            IsReference::ShortTerm => VA_PICTURE_H264_SHORT_TERM_REFERENCE,
96        };
97
98        PictureH264::new(
99            surface.surface_id(),
100            meta.frame_num,
101            flags,
102            meta.poc as i32,
103            meta.poc as i32,
104        )
105    }
106
107    /// Builds [`BufferType::EncSequenceParameter`] from `sps`
108    fn build_enc_seq_param(
109        sps: &Sps,
110        bits_per_second: u32,
111        intra_period: u32,
112        ip_period: u32,
113    ) -> BufferType {
114        let intra_idr_period = intra_period;
115
116        let seq_fields = H264EncSeqFields::new(
117            sps.chroma_format_idc as u32,
118            sps.frame_mbs_only_flag as u32,
119            sps.mb_adaptive_frame_field_flag as u32,
120            sps.seq_scaling_matrix_present_flag as u32,
121            sps.direct_8x8_inference_flag as u32,
122            sps.log2_max_frame_num_minus4 as u32,
123            sps.pic_order_cnt_type as u32,
124            sps.log2_max_pic_order_cnt_lsb_minus4 as u32,
125            sps.delta_pic_order_always_zero_flag as u32,
126        );
127
128        let frame_crop = if sps.frame_cropping_flag {
129            Some(H264EncFrameCropOffsets::new(
130                sps.frame_crop_left_offset,
131                sps.frame_crop_right_offset,
132                sps.frame_crop_top_offset,
133                sps.frame_crop_bottom_offset,
134            ))
135        } else {
136            None
137        };
138
139        let vui_fields = if sps.vui_parameters_present_flag {
140            Some(H264VuiFields::new(
141                sps.vui_parameters.aspect_ratio_idc as u32,
142                sps.vui_parameters.timing_info_present_flag as u32,
143                sps.vui_parameters.bitstream_restriction_flag as u32,
144                sps.vui_parameters.log2_max_mv_length_horizontal,
145                sps.vui_parameters.log2_max_mv_length_vertical,
146                sps.vui_parameters.fixed_frame_rate_flag as u32,
147                sps.vui_parameters.low_delay_hrd_flag as u32,
148                sps.vui_parameters.motion_vectors_over_pic_boundaries_flag as u32,
149            ))
150        } else {
151            None
152        };
153
154        let mut offset_for_ref_frame = [0i32; 256];
155        offset_for_ref_frame[..255].copy_from_slice(&sps.offset_for_ref_frame[..]);
156
157        BufferType::EncSequenceParameter(EncSequenceParameter::H264(
158            EncSequenceParameterBufferH264::new(
159                sps.seq_parameter_set_id,
160                sps.level_idc as u8,
161                intra_period,
162                intra_idr_period,
163                ip_period,
164                bits_per_second,
165                sps.max_num_ref_frames as u32,
166                sps.pic_width_in_mbs_minus1 + 1,
167                sps.pic_height_in_map_units_minus1 + 1,
168                &seq_fields,
169                sps.bit_depth_luma_minus8,
170                sps.bit_depth_chroma_minus8,
171                sps.num_ref_frames_in_pic_order_cnt_cycle,
172                sps.offset_for_non_ref_pic,
173                sps.offset_for_top_to_bottom_field,
174                offset_for_ref_frame,
175                frame_crop,
176                vui_fields,
177                sps.vui_parameters.aspect_ratio_idc,
178                sps.vui_parameters.sar_width as u32,
179                sps.vui_parameters.sar_height as u32,
180                sps.vui_parameters.num_units_in_tick,
181                sps.vui_parameters.time_scale,
182            ),
183        ))
184    }
185
186    /// Builds [`BufferType::EncPictureParameter`] from [`Request`] and sets bitstream
187    /// output to `coded_buf`.
188    fn build_enc_pic_param(
189        request: &Request<'_, H>,
190        coded_buf: &EncCodedBuffer,
191        recon: &Reconstructed,
192    ) -> BufferType {
193        let pic_fields = H264EncPicFields::new(
194            request.is_idr as u32,
195            (request.dpb_meta.is_reference != IsReference::No) as u32,
196            request.pps.entropy_coding_mode_flag as u32,
197            request.pps.weighted_pred_flag as u32,
198            request.pps.weighted_bipred_idc as u32,
199            request.pps.constrained_intra_pred_flag as u32,
200            request.pps.transform_8x8_mode_flag as u32,
201            request.pps.deblocking_filter_control_present_flag as u32,
202            request.pps.redundant_pic_cnt_present_flag as u32,
203            0,
204            request.pps.pic_scaling_matrix_present_flag as u32,
205        );
206
207        let curr_pic = Self::build_h264_pic(recon, &request.dpb_meta);
208
209        assert!(request.ref_list_0.len() + request.ref_list_1.len() <= 16);
210
211        let mut reference_frames: [PictureH264; 16] = (0..16)
212            .map(|_| Self::build_invalid_va_h264_pic_enc())
213            .collect::<Vec<_>>()
214            .try_into()
215            .unwrap_or_else(|_| panic!());
216
217        for (idx, ref_frame) in request
218            .ref_list_0
219            .iter()
220            .chain(request.ref_list_1.iter())
221            .enumerate()
222            .take(16)
223        {
224            reference_frames[idx] = Self::build_h264_pic(&ref_frame.recon_pic, &ref_frame.meta);
225        }
226
227        BufferType::EncPictureParameter(EncPictureParameter::H264(
228            EncPictureParameterBufferH264::new(
229                curr_pic,
230                reference_frames,
231                coded_buf.id(),
232                request.pps.pic_parameter_set_id,
233                request.pps.seq_parameter_set_id,
234                0, // last_pic, don't appned EOS
235                request.dpb_meta.frame_num as u16,
236                (request.pps.pic_init_qp_minus26 + 26) as u8,
237                request.pps.num_ref_idx_l0_default_active_minus1,
238                request.pps.num_ref_idx_l1_default_active_minus1,
239                request.pps.chroma_qp_index_offset,
240                request.pps.second_chroma_qp_index_offset,
241                &pic_fields,
242            ),
243        ))
244    }
245
246    /// Builds [`BufferType::EncSliceParameter`]
247    fn build_enc_slice_param(
248        pps: &Pps,
249        header: &SliceHeader,
250        ref_list_0: &[Rc<DpbEntry<Reconstructed>>],
251        ref_list_1: &[Rc<DpbEntry<Reconstructed>>],
252        num_macroblocks: u32,
253    ) -> BufferType {
254        let mut ref_pic_list_0: [PictureH264; 32] = (0..32)
255            .map(|_| Self::build_invalid_va_h264_pic_enc())
256            .collect::<Vec<_>>()
257            .try_into()
258            .unwrap_or_else(|_| panic!());
259
260        for (idx, ref_frame) in ref_list_0.iter().enumerate().take(16) {
261            ref_pic_list_0[idx] = Self::build_h264_pic(&ref_frame.recon_pic, &ref_frame.meta);
262        }
263
264        let mut ref_pic_list_1: [PictureH264; 32] = (0..32)
265            .map(|_| Self::build_invalid_va_h264_pic_enc())
266            .collect::<Vec<_>>()
267            .try_into()
268            .unwrap_or_else(|_| panic!());
269
270        for (idx, ref_frame) in ref_list_1.iter().enumerate().take(16) {
271            ref_pic_list_1[idx] = Self::build_h264_pic(&ref_frame.recon_pic, &ref_frame.meta);
272        }
273
274        let mut luma_weight_l0_flag = false;
275        let mut luma_offset_l0 = [0i16; 32];
276
277        if header.pred_weight_table.luma_weight_l0 != [0i16; 32] {
278            luma_weight_l0_flag = true;
279            for (i, val) in header.pred_weight_table.luma_offset_l0.iter().enumerate() {
280                luma_offset_l0[i] = (*val).into();
281            }
282        }
283
284        let mut chroma_weight_l0_flag = false;
285        let mut chroma_offset_l0 = [[0i16; 2]; 32];
286
287        if header.pred_weight_table.chroma_weight_l0 != [[0i16; 2]; 32] {
288            chroma_weight_l0_flag = true;
289            for (i, val) in header.pred_weight_table.chroma_offset_l0.iter().enumerate() {
290                chroma_offset_l0[i] = [val[0].into(), val[1].into()];
291            }
292        }
293
294        let mut luma_weight_l1_flag = false;
295        let mut luma_offset_l1 = [0i16; 32];
296
297        if header.pred_weight_table.luma_weight_l1 != [0i16; 32] {
298            luma_weight_l1_flag = true;
299            for (i, val) in header.pred_weight_table.luma_offset_l1.iter().enumerate() {
300                luma_offset_l1[i] = *val;
301            }
302        }
303
304        let mut chroma_weight_l1_flag = false;
305        let mut chroma_offset_l1 = [[0i16; 2]; 32];
306
307        if header.pred_weight_table.chroma_weight_l1 != [[0i16; 2]; 32] {
308            chroma_weight_l1_flag = true;
309            for (i, val) in header.pred_weight_table.chroma_offset_l1.iter().enumerate() {
310                chroma_offset_l1[i] = [val[0].into(), val[1].into()];
311            }
312        }
313
314        let (num_ref_idx_l0_active_minus1, num_ref_idx_l1_active_minus1) =
315            if header.num_ref_idx_active_override_flag {
316                (
317                    header.num_ref_idx_l0_active_minus1,
318                    header.num_ref_idx_l1_active_minus1,
319                )
320            } else {
321                (
322                    pps.num_ref_idx_l0_default_active_minus1,
323                    pps.num_ref_idx_l1_default_active_minus1,
324                )
325            };
326        BufferType::EncSliceParameter(EncSliceParameter::H264(EncSliceParameterBufferH264::new(
327            header.first_mb_in_slice,
328            num_macroblocks,
329            VA_INVALID_ID,
330            header.slice_type as u8,
331            pps.pic_parameter_set_id,
332            header.idr_pic_id,
333            header.pic_order_cnt_lsb,
334            header.delta_pic_order_cnt_bottom,
335            header.delta_pic_order_cnt,
336            header.direct_spatial_mv_pred_flag as u8,
337            header.num_ref_idx_active_override_flag as u8,
338            num_ref_idx_l0_active_minus1,
339            num_ref_idx_l1_active_minus1,
340            ref_pic_list_0,
341            ref_pic_list_1,
342            header.pred_weight_table.luma_log2_weight_denom,
343            header.pred_weight_table.chroma_log2_weight_denom,
344            luma_weight_l0_flag as u8,
345            header.pred_weight_table.luma_weight_l0,
346            luma_offset_l0,
347            chroma_weight_l0_flag as u8,
348            header.pred_weight_table.chroma_weight_l0,
349            chroma_offset_l0,
350            luma_weight_l1_flag as u8,
351            header.pred_weight_table.luma_weight_l1,
352            luma_offset_l1,
353            chroma_weight_l1_flag as u8,
354            header.pred_weight_table.chroma_weight_l1,
355            chroma_offset_l1,
356            header.cabac_init_idc,
357            header.slice_qp_delta,
358            header.disable_deblocking_filter_idc,
359            header.slice_alpha_c0_offset_div2,
360            header.slice_beta_offset_div2,
361        )))
362    }
363}
364
365impl<M, H> StatelessH264EncoderBackend for VaapiBackend<M, H>
366where
367    M: SurfaceMemoryDescriptor,
368    H: Borrow<Surface<M>> + 'static,
369{
370    fn encode_slice(
371        &mut self,
372        request: Request<'_, H>,
373    ) -> StatelessBackendResult<(Self::ReconPromise, Self::CodedPromise)> {
374        let coded_buf = self.new_coded_buffer(&request.tunings.rate_control)?;
375        let recon = self.new_scratch_picture()?;
376
377        // Use bitrate from RateControl or ask driver to ignore
378        let bits_per_second = request.tunings.rate_control.bitrate_target().unwrap_or(0) as u32;
379        let seq_param = Self::build_enc_seq_param(
380            &request.sps,
381            bits_per_second,
382            request.intra_period,
383            request.ip_period,
384        );
385
386        let pic_param = Self::build_enc_pic_param(&request, &coded_buf, &recon);
387        let slice_param = Self::build_enc_slice_param(
388            &request.pps,
389            &request.header,
390            &request.ref_list_0,
391            &request.ref_list_1,
392            request.num_macroblocks as u32,
393        );
394
395        // Clone reference frames
396        let references: Vec<Rc<dyn Any>> = request
397            .ref_list_0
398            .iter()
399            .cloned()
400            .chain(request.ref_list_1.iter().cloned())
401            .map(|entry| entry as Rc<dyn Any>)
402            .collect();
403
404        // Clone picture using [`Picture::new_from_same_surface`] to avoid
405        // creatig a shared cell picture between its references and processed
406        // picture.
407        let mut picture = Picture::new(
408            request.dpb_meta.frame_num as u64,
409            Rc::clone(self.context()),
410            request.input,
411        );
412
413        let rc_param =
414            tunings_to_libva_rc::<{ MIN_QP as u32 }, { MAX_QP as u32 }>(&request.tunings)?;
415        let rc_param = BufferType::EncMiscParameter(libva::EncMiscParameter::RateControl(rc_param));
416
417        picture.add_buffer(self.context().create_buffer(seq_param)?);
418        picture.add_buffer(self.context().create_buffer(pic_param)?);
419        picture.add_buffer(self.context().create_buffer(slice_param)?);
420        picture.add_buffer(self.context().create_buffer(rc_param)?);
421
422        // Start processing the picture encoding
423        let picture = picture.begin().context("picture begin")?;
424        let picture = picture.render().context("picture render")?;
425        let picture = picture.end().context("picture end")?;
426
427        // HACK: Make sure that slice nalu start code is at least 4 bytes.
428        // TODO: Use packed headers to supply slice header with nalu start code of size 4 and get
429        // rid of this hack.
430        let mut coded_output = request.coded_output;
431        coded_output.push(0);
432
433        // libva will handle the synchronization of reconstructed surface with implicit fences.
434        // Therefore return the reconstructed frame immediately.
435        let reference_promise = ReadyPromise::from(recon);
436
437        let bitstream_promise =
438            CodedOutputPromise::new(picture, references, coded_buf, coded_output);
439
440        Ok((reference_promise, bitstream_promise))
441    }
442}
443
444impl<M, H> StatelessEncoder<H, VaapiBackend<M, H>>
445where
446    M: SurfaceMemoryDescriptor,
447    H: Borrow<libva::Surface<M>> + 'static,
448{
449    pub fn new_vaapi(
450        display: Rc<Display>,
451        config: EncoderConfig,
452        fourcc: Fourcc,
453        coded_size: Resolution,
454        low_power: bool,
455        blocking_mode: BlockingMode,
456    ) -> EncodeResult<Self> {
457        let va_profile = match config.profile {
458            Profile::Baseline => VAProfile::VAProfileH264ConstrainedBaseline,
459            Profile::Main => VAProfile::VAProfileH264Main,
460            Profile::High => VAProfile::VAProfileH264High,
461            _ => return Err(StatelessBackendError::UnsupportedProfile.into()),
462        };
463
464        let bitrate_control = match config.initial_tunings.rate_control {
465            RateControl::ConstantBitrate(_) => libva::constants::VA_RC_CBR,
466            RateControl::ConstantQuality(_) => libva::constants::VA_RC_CQP,
467        };
468
469        let backend = VaapiBackend::new(
470            display,
471            va_profile,
472            fourcc,
473            coded_size,
474            bitrate_control,
475            low_power,
476        )?;
477
478        Self::new_h264(backend, config, blocking_mode)
479    }
480}
481
482#[cfg(test)]
483pub(super) mod tests {
484    use libva::constants::VA_RT_FORMAT_YUV420;
485    use libva::Display;
486    use libva::UsageHint;
487    use libva::VAEntrypoint::VAEntrypointEncSliceLP;
488    use libva::VAProfile::VAProfileH264Main;
489
490    use super::*;
491    use crate::backend::vaapi::encoder::tests::upload_test_frame_nv12;
492    use crate::backend::vaapi::encoder::tests::TestFrameGenerator;
493    use crate::backend::vaapi::surface_pool::PooledVaSurface;
494    use crate::backend::vaapi::surface_pool::VaSurfacePool;
495    use crate::codec::h264::parser::Level;
496    use crate::codec::h264::parser::PpsBuilder;
497    use crate::codec::h264::parser::Profile;
498    use crate::codec::h264::parser::SliceHeaderBuilder;
499    use crate::codec::h264::parser::SliceType;
500    use crate::codec::h264::parser::SpsBuilder;
501    use crate::decoder::FramePool;
502    use crate::encoder::simple_encode_loop;
503    use crate::encoder::stateless::h264::BackendRequest;
504    use crate::encoder::stateless::h264::EncoderConfig;
505    use crate::encoder::stateless::h264::StatelessEncoder;
506    use crate::encoder::stateless::BackendPromise;
507    use crate::encoder::stateless::StatelessEncoderBackendImport;
508    use crate::encoder::FrameMetadata;
509    use crate::encoder::Tunings;
510    use crate::FrameLayout;
511    use crate::PlaneLayout;
512    use crate::Resolution;
513
514    #[test]
515    // Ignore this test by default as it requires libva-compatible hardware.
516    #[ignore]
517    fn test_simple_encode_slice() {
518        type Descriptor = ();
519        type Surface = libva::Surface<Descriptor>;
520        const WIDTH: u32 = 256;
521        const HEIGHT: u32 = 256;
522        let fourcc = b"NV12".into();
523
524        let frame_layout = FrameLayout {
525            format: (fourcc, 0),
526            size: Resolution {
527                width: WIDTH,
528                height: HEIGHT,
529            },
530            planes: vec![
531                PlaneLayout {
532                    buffer_index: 0,
533                    offset: 0,
534                    stride: WIDTH as usize,
535                },
536                PlaneLayout {
537                    buffer_index: 0,
538                    offset: (WIDTH * HEIGHT) as usize,
539                    stride: WIDTH as usize,
540                },
541            ],
542        };
543
544        let display = Display::open().unwrap();
545        let entrypoints = display.query_config_entrypoints(VAProfileH264Main).unwrap();
546        let low_power = entrypoints.contains(&VAEntrypointEncSliceLP);
547
548        let mut backend = VaapiBackend::<Descriptor, Surface>::new(
549            Rc::clone(&display),
550            VAProfileH264Main,
551            fourcc,
552            Resolution {
553                width: WIDTH,
554                height: HEIGHT,
555            },
556            libva::constants::VA_RC_CBR,
557            low_power,
558        )
559        .unwrap();
560
561        let mut surfaces = display
562            .create_surfaces(
563                VA_RT_FORMAT_YUV420,
564                Some(frame_layout.format.0 .0),
565                WIDTH,
566                HEIGHT,
567                Some(UsageHint::USAGE_HINT_ENCODER),
568                vec![()],
569            )
570            .unwrap();
571
572        let surface = surfaces.pop().unwrap();
573
574        upload_test_frame_nv12(&display, &surface, 0.0);
575
576        let input_meta = FrameMetadata {
577            layout: frame_layout,
578            force_keyframe: false,
579            timestamp: 0,
580        };
581
582        let pic = backend.import_picture(&input_meta, surface).unwrap();
583
584        let sps = SpsBuilder::new()
585            .seq_parameter_set_id(0)
586            .profile_idc(Profile::Main)
587            .level_idc(Level::L4)
588            .resolution(WIDTH, HEIGHT)
589            .chroma_format_idc(3)
590            .frame_mbs_only_flag(true)
591            .direct_8x8_inference_flag(true)
592            .max_num_ref_frames(1)
593            .max_frame_num(32)
594            .pic_order_cnt_type(0)
595            .max_pic_order_cnt_lsb(128)
596            .delta_pic_order_always_zero_flag(false)
597            .bit_depth_chroma(8)
598            .bit_depth_luma(8)
599            .sar_resolution(1, 1)
600            .build();
601
602        let pps = PpsBuilder::new(Rc::clone(&sps))
603            .pic_parameter_set_id(0)
604            .pic_init_qp_minus26(0)
605            .deblocking_filter_control_present_flag(true)
606            .build();
607
608        let header = SliceHeaderBuilder::new(&pps)
609            .slice_type(SliceType::I)
610            .first_mb_in_slice(0)
611            .idr_pic_id(0)
612            .build();
613
614        let dpb_entry_meta = DpbEntryMeta {
615            poc: 0,
616            frame_num: 0,
617            is_reference: IsReference::ShortTerm,
618        };
619
620        let request = BackendRequest {
621            sps: Rc::clone(&sps),
622            pps: Rc::clone(&pps),
623            header,
624            dpb_meta: dpb_entry_meta,
625            input: pic,
626            input_meta,
627            ref_list_0: vec![],
628            ref_list_1: vec![],
629            intra_period: 1,
630            ip_period: 0,
631            num_macroblocks: (WIDTH * HEIGHT) as usize / (16 * 16),
632            is_idr: true,
633            tunings: Tunings {
634                rate_control: RateControl::ConstantBitrate(30_000),
635                ..Default::default()
636            },
637            coded_output: vec![],
638        };
639
640        let (_, output) = backend.encode_slice(request).unwrap();
641        let output = output.sync().unwrap();
642
643        let write_to_file = std::option_env!("CROS_CODECS_TEST_WRITE_TO_FILE") == Some("true");
644        if write_to_file {
645            use std::io::Write;
646
647            use crate::codec::h264::synthesizer::Synthesizer;
648            let mut out = std::fs::File::create("test_simple_encode_slice.h264").unwrap();
649
650            Synthesizer::<'_, Sps, &mut std::fs::File>::synthesize(3, &sps, &mut out, true)
651                .unwrap();
652            Synthesizer::<'_, Pps, &mut std::fs::File>::synthesize(3, &pps, &mut out, true)
653                .unwrap();
654            out.write_all(&output).unwrap();
655            out.flush().unwrap();
656        }
657    }
658
659    #[test]
660    // Ignore this test by default as it requires libva-compatible hardware.
661    #[ignore]
662    fn test_vaapi_encoder() {
663        type VaapiH264Encoder<'l> =
664            StatelessEncoder<PooledVaSurface<()>, VaapiBackend<(), PooledVaSurface<()>>>;
665
666        const WIDTH: usize = 512;
667        const HEIGHT: usize = 512;
668
669        let _ = env_logger::try_init();
670
671        let display = libva::Display::open().unwrap();
672        let entrypoints = display.query_config_entrypoints(VAProfileH264Main).unwrap();
673        let low_power = entrypoints.contains(&VAEntrypointEncSliceLP);
674
675        let config = EncoderConfig {
676            profile: Profile::Main,
677            resolution: Resolution {
678                width: WIDTH as u32,
679                height: HEIGHT as u32,
680            },
681            initial_tunings: Tunings {
682                rate_control: RateControl::ConstantBitrate(1_200_000),
683                framerate: 30,
684                ..Default::default()
685            },
686            ..Default::default()
687        };
688
689        let frame_layout = FrameLayout {
690            format: (b"NV12".into(), 0),
691            size: Resolution {
692                width: WIDTH as u32,
693                height: HEIGHT as u32,
694            },
695            planes: vec![
696                PlaneLayout {
697                    buffer_index: 0,
698                    offset: 0,
699                    stride: WIDTH,
700                },
701                PlaneLayout {
702                    buffer_index: 0,
703                    offset: WIDTH * HEIGHT,
704                    stride: WIDTH,
705                },
706            ],
707        };
708
709        let mut encoder = VaapiH264Encoder::new_vaapi(
710            Rc::clone(&display),
711            config,
712            frame_layout.format.0,
713            frame_layout.size,
714            low_power,
715            BlockingMode::Blocking,
716        )
717        .unwrap();
718
719        let mut pool = VaSurfacePool::new(
720            Rc::clone(&display),
721            VA_RT_FORMAT_YUV420,
722            Some(UsageHint::USAGE_HINT_ENCODER),
723            Resolution {
724                width: WIDTH as u32,
725                height: HEIGHT as u32,
726            },
727        );
728
729        pool.add_frames(vec![(); 16]).unwrap();
730
731        let mut frame_producer = TestFrameGenerator::new(100, display, pool, frame_layout);
732
733        let mut bitstream = Vec::new();
734
735        simple_encode_loop(&mut encoder, &mut frame_producer, |coded| {
736            bitstream.extend(coded.bitstream)
737        })
738        .unwrap();
739
740        let write_to_file = std::option_env!("CROS_CODECS_TEST_WRITE_TO_FILE") == Some("true");
741        if write_to_file {
742            use std::io::Write;
743            let mut out = std::fs::File::create("test_vaapi_encoder.h264").unwrap();
744            out.write_all(&bitstream).unwrap();
745            out.flush().unwrap();
746        }
747    }
748}