cros_codecs/encoder/stateless/vp9/
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_SURFACE;
11use libva::BufferType;
12use libva::Display;
13use libva::EncPictureParameter;
14use libva::EncPictureParameterBufferVP9;
15use libva::EncSequenceParameter;
16use libva::EncSequenceParameterBufferVP9;
17use libva::Picture;
18use libva::Surface;
19use libva::SurfaceMemoryDescriptor;
20use libva::VAProfile::VAProfileVP9Profile0;
21use libva::VAProfile::VAProfileVP9Profile2;
22use libva::VP9EncPicFlags;
23use libva::VP9EncRefFlags;
24
25use crate::backend::vaapi::encoder::tunings_to_libva_rc;
26use crate::backend::vaapi::encoder::CodedOutputPromise;
27use crate::backend::vaapi::encoder::Reconstructed;
28use crate::backend::vaapi::encoder::VaapiBackend;
29use crate::codec::vp9::parser::BitDepth;
30use crate::codec::vp9::parser::FrameType;
31use crate::codec::vp9::parser::InterpolationFilter;
32use crate::codec::vp9::parser::ALTREF_FRAME;
33use crate::codec::vp9::parser::GOLDEN_FRAME;
34use crate::codec::vp9::parser::LAST_FRAME;
35use crate::codec::vp9::parser::NUM_REF_FRAMES;
36use crate::encoder::stateless::vp9::predictor::MAX_Q_IDX;
37use crate::encoder::stateless::vp9::predictor::MIN_Q_IDX;
38use crate::encoder::stateless::vp9::BackendRequest;
39use crate::encoder::stateless::vp9::ReferenceUse;
40use crate::encoder::stateless::vp9::StatelessEncoder;
41use crate::encoder::stateless::vp9::StatelessVP9EncoderBackend;
42use crate::encoder::stateless::ReadyPromise;
43use crate::encoder::stateless::StatelessBackendResult;
44use crate::encoder::stateless::StatelessVideoEncoderBackend;
45use crate::encoder::vp9::EncoderConfig;
46use crate::encoder::vp9::VP9;
47use crate::encoder::EncodeResult;
48use crate::encoder::RateControl;
49use crate::BlockingMode;
50use crate::Fourcc;
51use crate::Resolution;
52
53impl<M, Handle> StatelessVideoEncoderBackend<VP9> for VaapiBackend<M, Handle>
54where
55    M: SurfaceMemoryDescriptor,
56    Handle: Borrow<Surface<M>>,
57{
58    type Picture = Handle;
59    type Reconstructed = Reconstructed;
60    type CodedPromise = CodedOutputPromise<M, Handle>;
61    type ReconPromise = ReadyPromise<Self::Reconstructed>;
62}
63
64impl<M, Handle> StatelessVP9EncoderBackend for VaapiBackend<M, Handle>
65where
66    M: SurfaceMemoryDescriptor,
67    Handle: Borrow<Surface<M>>,
68{
69    fn encode_frame(
70        &mut self,
71        request: BackendRequest<Self::Picture, Self::Reconstructed>,
72    ) -> StatelessBackendResult<(Self::ReconPromise, Self::CodedPromise)> {
73        let coded_buf = self.new_coded_buffer(&request.tunings.rate_control)?;
74        let recon = self.new_scratch_picture()?;
75
76        // Use bitrate from RateControl or ask driver to ignore
77        let bits_per_second = request.tunings.rate_control.bitrate_target().unwrap_or(0) as u32;
78
79        let seq_param = BufferType::EncSequenceParameter(EncSequenceParameter::VP9(
80            EncSequenceParameterBufferVP9::new(
81                request.input_meta.layout.size.width,
82                request.input_meta.layout.size.height,
83                0,
84                10,
85                2000,
86                bits_per_second,
87                1024,
88            ),
89        ));
90
91        // From va_enc_vp9.h `ref_frame_ctrl_l0` documentation
92        const LAST_FRAME_AS_REF: u32 = 0x01;
93        const GOLDEN_FRAME_AS_REF: u32 = 0x02;
94        const ALTREF_FRAME_AS_REF: u32 = 0x04;
95
96        let mut references = Vec::<Rc<dyn Any>>::new();
97        let mut reference_frames = [VA_INVALID_SURFACE; NUM_REF_FRAMES];
98
99        let mut ref_frame_ctrl_l0 = 0;
100        let mut ref_frame_ctrl_l1 = 0;
101
102        let refs = [
103            (&request.last_frame_ref, LAST_FRAME - 1, LAST_FRAME_AS_REF),
104            (
105                &request.golden_frame_ref,
106                GOLDEN_FRAME - 1,
107                GOLDEN_FRAME_AS_REF,
108            ),
109            (
110                &request.altref_frame_ref,
111                ALTREF_FRAME - 1,
112                ALTREF_FRAME_AS_REF,
113            ),
114        ];
115
116        for (r, ref_idx, ref_ctrl) in refs {
117            let Some((ref_frame, ref_use)) = r else {
118                continue;
119            };
120
121            reference_frames[request.header.ref_frame_idx[ref_idx] as usize] =
122                ref_frame.surface_id();
123            references.push(ref_frame.clone());
124
125            match ref_use {
126                ReferenceUse::Single => ref_frame_ctrl_l0 |= ref_ctrl,
127                ReferenceUse::Compound => ref_frame_ctrl_l1 |= ref_ctrl,
128                ReferenceUse::Hybrid => {
129                    ref_frame_ctrl_l0 |= ref_ctrl;
130                    ref_frame_ctrl_l1 |= ref_ctrl;
131                }
132            }
133        }
134
135        let force_kf =
136            request.header.frame_type == FrameType::KeyFrame || request.input_meta.force_keyframe;
137
138        let ref_flags = VP9EncRefFlags::new(
139            // Force keyframe if requested
140            force_kf as u32,
141            ref_frame_ctrl_l0,
142            ref_frame_ctrl_l1,
143            request.header.ref_frame_idx[LAST_FRAME - 1] as u32,
144            request.header.ref_frame_sign_bias[LAST_FRAME] as u32,
145            request.header.ref_frame_idx[GOLDEN_FRAME - 1] as u32,
146            request.header.ref_frame_sign_bias[GOLDEN_FRAME] as u32,
147            request.header.ref_frame_idx[ALTREF_FRAME - 1] as u32,
148            request.header.ref_frame_sign_bias[ALTREF_FRAME] as u32,
149            0,
150        );
151
152        // From va_enc_vp9.h `mcomp_filter_type` documentation
153        let mcomp_filter_type = match request.header.interpolation_filter {
154            InterpolationFilter::EightTap => 0,
155            InterpolationFilter::EightTapSmooth => 1,
156            InterpolationFilter::EightTapSharp => 2,
157            InterpolationFilter::Bilinear => 3,
158            InterpolationFilter::Switchable => 4,
159        };
160
161        // TODO: show_existing_frame
162        assert!(!request.header.show_existing_frame);
163
164        // From va_enc_vp9.h `comp_prediction_mode` documentation
165        const PRED_MODE_SINGLE: u32 = 0x00;
166        // const PRED_MODE_COMPOUND: u32 = 0x01;
167        const PRED_MODE_HYBRID: u32 = 0x02;
168
169        let comp_prediction_mode = if ref_frame_ctrl_l1 != 0 {
170            // Use hybrid prediction mode if any future reference frame are enabled
171            PRED_MODE_HYBRID
172        } else {
173            PRED_MODE_SINGLE
174        };
175
176        let pic_flags = VP9EncPicFlags::new(
177            request.header.frame_type as u32,
178            request.header.show_frame as u32,
179            request.header.error_resilient_mode as u32,
180            request.header.intra_only as u32,
181            request.header.allow_high_precision_mv as u32,
182            mcomp_filter_type,
183            request.header.frame_parallel_decoding_mode as u32,
184            request.header.reset_frame_context as u32,
185            request.header.refresh_frame_context as u32,
186            request.header.frame_context_idx as u32,
187            request.header.seg.enabled as u32,
188            request.header.seg.temporal_update as u32,
189            request.header.seg.update_map as u32,
190            request.header.lossless as u32,
191            comp_prediction_mode,
192            1,
193            0,
194        );
195
196        let pic_param = BufferType::EncPictureParameter(EncPictureParameter::VP9(
197            EncPictureParameterBufferVP9::new(
198                request.header.width,
199                request.header.height,
200                request.header.render_width,
201                request.header.render_height,
202                recon.surface_id(),
203                reference_frames,
204                coded_buf.id(),
205                &ref_flags,
206                &pic_flags,
207                request.header.refresh_frame_flags,
208                request.header.quant.base_q_idx,
209                request.header.quant.delta_q_y_dc,
210                request.header.quant.delta_q_uv_ac,
211                request.header.quant.delta_q_uv_dc,
212                request.header.lf.level,
213                request.header.lf.sharpness,
214                request.header.lf.ref_deltas,
215                request.header.lf.mode_deltas,
216                0,
217                0,
218                0,
219                0,
220                0,
221                0,
222                0,
223                request.header.tile_rows_log2,
224                request.header.tile_cols_log2,
225                // Don't skip frames
226                0,
227                0,
228                0,
229            ),
230        ));
231
232        let rc_param =
233            tunings_to_libva_rc::<{ MIN_Q_IDX as u32 }, { MAX_Q_IDX as u32 }>(&request.tunings)?;
234        let rc_param =
235            libva::BufferType::EncMiscParameter(libva::EncMiscParameter::RateControl(rc_param));
236
237        let mut picture = Picture::new(
238            request.input_meta.timestamp,
239            Rc::clone(self.context()),
240            request.input,
241        );
242
243        picture.add_buffer(self.context().create_buffer(seq_param)?);
244        picture.add_buffer(self.context().create_buffer(pic_param)?);
245        picture.add_buffer(self.context().create_buffer(rc_param)?);
246
247        // Start processing the picture encoding
248        let picture = picture.begin().context("picture begin")?;
249        let picture = picture.render().context("picture render")?;
250        let picture = picture.end().context("picture end")?;
251
252        // libva will handle the synchronization of reconstructed surface with implicit fences.
253        // Therefore return the reconstructed frame immediately.
254        let reference_promise = ReadyPromise::from(recon);
255
256        let bitstream_promise =
257            CodedOutputPromise::new(picture, references, coded_buf, request.coded_output);
258
259        Ok((reference_promise, bitstream_promise))
260    }
261}
262
263impl<M, Handle> StatelessEncoder<Handle, VaapiBackend<M, Handle>>
264where
265    M: SurfaceMemoryDescriptor,
266    Handle: Borrow<Surface<M>>,
267{
268    pub fn new_vaapi(
269        display: Rc<Display>,
270        config: EncoderConfig,
271        fourcc: Fourcc,
272        coded_size: Resolution,
273        low_power: bool,
274        blocking_mode: BlockingMode,
275    ) -> EncodeResult<Self> {
276        let bitrate_control = match config.initial_tunings.rate_control {
277            RateControl::ConstantBitrate(_) => libva::constants::VA_RC_CBR,
278            RateControl::ConstantQuality(_) => libva::constants::VA_RC_CQP,
279        };
280
281        let va_profile = match config.bit_depth {
282            BitDepth::Depth8 => VAProfileVP9Profile0,
283            BitDepth::Depth10 | BitDepth::Depth12 => VAProfileVP9Profile2,
284        };
285
286        let backend = VaapiBackend::new(
287            display,
288            va_profile,
289            fourcc,
290            coded_size,
291            bitrate_control,
292            low_power,
293        )?;
294        Self::new_vp9(backend, config, blocking_mode)
295    }
296}
297
298#[cfg(test)]
299pub(super) mod tests {
300    use std::rc::Rc;
301
302    use libva::constants::VA_RT_FORMAT_YUV420;
303    use libva::constants::VA_RT_FORMAT_YUV420_10;
304    use libva::Display;
305    use libva::UsageHint;
306    use libva::VAEntrypoint::VAEntrypointEncSliceLP;
307
308    use super::*;
309    use crate::backend::vaapi::encoder::tests::upload_test_frame_nv12;
310    use crate::backend::vaapi::encoder::tests::TestFrameGenerator;
311    use crate::backend::vaapi::encoder::VaapiBackend;
312    use crate::backend::vaapi::surface_pool::PooledVaSurface;
313    use crate::backend::vaapi::surface_pool::VaSurfacePool;
314    use crate::codec::vp9::parser::Header;
315    use crate::decoder::FramePool;
316    use crate::encoder::simple_encode_loop;
317    use crate::encoder::stateless::vp9::BackendRequest;
318    use crate::encoder::stateless::vp9::EncoderConfig;
319    use crate::encoder::stateless::vp9::StatelessEncoder;
320    use crate::encoder::stateless::BackendPromise;
321    use crate::encoder::stateless::StatelessEncoderBackendImport;
322    use crate::encoder::FrameMetadata;
323    use crate::encoder::Tunings;
324    use crate::utils::IvfFileHeader;
325    use crate::utils::IvfFrameHeader;
326    use crate::FrameLayout;
327    use crate::PlaneLayout;
328    use crate::Resolution;
329
330    #[test]
331    // Ignore this test by default as it requires libva-compatible hardware.
332    #[ignore]
333    fn test_simple_encode_frame() {
334        type Descriptor = ();
335        type Surface = libva::Surface<Descriptor>;
336        const WIDTH: u32 = 256;
337        const HEIGHT: u32 = 256;
338        let fourcc = b"NV12".into();
339
340        let frame_layout = FrameLayout {
341            format: (fourcc, 0),
342            size: Resolution {
343                width: WIDTH,
344                height: HEIGHT,
345            },
346            planes: vec![
347                PlaneLayout {
348                    buffer_index: 0,
349                    offset: 0,
350                    stride: WIDTH as usize,
351                },
352                PlaneLayout {
353                    buffer_index: 0,
354                    offset: (WIDTH * HEIGHT) as usize,
355                    stride: WIDTH as usize,
356                },
357            ],
358        };
359
360        let display = Display::open().unwrap();
361        let entrypoints = display
362            .query_config_entrypoints(VAProfileVP9Profile0)
363            .unwrap();
364        let low_power = entrypoints.contains(&VAEntrypointEncSliceLP);
365
366        let mut backend = VaapiBackend::<Descriptor, Surface>::new(
367            Rc::clone(&display),
368            VAProfileVP9Profile0,
369            fourcc,
370            Resolution {
371                width: WIDTH,
372                height: HEIGHT,
373            },
374            libva::constants::VA_RC_CBR,
375            low_power,
376        )
377        .unwrap();
378
379        let mut surfaces = display
380            .create_surfaces(
381                VA_RT_FORMAT_YUV420,
382                Some(frame_layout.format.0 .0),
383                WIDTH,
384                HEIGHT,
385                Some(UsageHint::USAGE_HINT_ENCODER),
386                vec![()],
387            )
388            .unwrap();
389
390        let surface = surfaces.pop().unwrap();
391
392        upload_test_frame_nv12(&display, &surface, 0.0);
393
394        let input_meta = FrameMetadata {
395            layout: frame_layout,
396            force_keyframe: false,
397            timestamp: 0,
398        };
399
400        let pic = backend.import_picture(&input_meta, surface).unwrap();
401
402        let header = Header {
403            frame_type: FrameType::KeyFrame,
404            show_frame: true,
405            error_resilient_mode: false,
406            width: WIDTH,
407            height: HEIGHT,
408            render_and_frame_size_different: false,
409            render_width: WIDTH,
410            render_height: HEIGHT,
411            intra_only: true,
412            refresh_frame_flags: 0x01,
413            ref_frame_idx: [0, 0, 0],
414
415            ..Default::default()
416        };
417
418        let request = BackendRequest {
419            header,
420            input: pic,
421            input_meta,
422            last_frame_ref: None,
423            golden_frame_ref: None,
424            altref_frame_ref: None,
425            tunings: Tunings {
426                rate_control: RateControl::ConstantBitrate(30_000),
427                ..Default::default()
428            },
429            coded_output: Vec::new(),
430        };
431
432        let (_, output) = backend.encode_frame(request).unwrap();
433        let output = output.sync().unwrap();
434
435        let write_to_file = std::option_env!("CROS_CODECS_TEST_WRITE_TO_FILE") == Some("true");
436        if write_to_file {
437            use std::io::Write;
438
439            let mut out = std::fs::File::create("test_simple_encode_frame.vp9.ivf").unwrap();
440
441            let file_header =
442                IvfFileHeader::new(IvfFileHeader::CODEC_VP9, WIDTH as u16, HEIGHT as u16, 30, 1);
443
444            let frame_header = IvfFrameHeader {
445                frame_size: output.len() as u32,
446                timestamp: 0,
447            };
448
449            file_header.writo_into(&mut out).unwrap();
450            frame_header.writo_into(&mut out).unwrap();
451
452            out.write_all(&output).unwrap();
453            out.flush().unwrap();
454        }
455    }
456
457    #[test]
458    // Ignore this test by default as it requires libva-compatible hardware.
459    #[ignore]
460    fn test_vaapi_encoder() {
461        type VaapiVp9Encoder<'l> =
462            StatelessEncoder<PooledVaSurface<()>, VaapiBackend<(), PooledVaSurface<()>>>;
463
464        const WIDTH: usize = 512;
465        const HEIGHT: usize = 512;
466        const FRAME_COUNT: u64 = 100;
467
468        let _ = env_logger::try_init();
469
470        let display = libva::Display::open().unwrap();
471        let entrypoints = display
472            .query_config_entrypoints(VAProfileVP9Profile0)
473            .unwrap();
474        let low_power = entrypoints.contains(&VAEntrypointEncSliceLP);
475
476        let config = EncoderConfig {
477            resolution: Resolution {
478                width: WIDTH as u32,
479                height: HEIGHT as u32,
480            },
481            initial_tunings: Tunings {
482                rate_control: RateControl::ConstantBitrate(200_000),
483                framerate: 30,
484                ..Default::default()
485            },
486            ..Default::default()
487        };
488
489        let frame_layout = FrameLayout {
490            format: (b"NV12".into(), 0),
491            size: Resolution {
492                width: WIDTH as u32,
493                height: HEIGHT as u32,
494            },
495            planes: vec![
496                PlaneLayout {
497                    buffer_index: 0,
498                    offset: 0,
499                    stride: WIDTH,
500                },
501                PlaneLayout {
502                    buffer_index: 0,
503                    offset: WIDTH * HEIGHT,
504                    stride: WIDTH,
505                },
506            ],
507        };
508
509        let mut encoder = VaapiVp9Encoder::new_vaapi(
510            Rc::clone(&display),
511            config,
512            frame_layout.format.0,
513            frame_layout.size,
514            low_power,
515            BlockingMode::Blocking,
516        )
517        .unwrap();
518
519        let mut pool = VaSurfacePool::new(
520            Rc::clone(&display),
521            VA_RT_FORMAT_YUV420,
522            Some(UsageHint::USAGE_HINT_ENCODER),
523            Resolution {
524                width: WIDTH as u32,
525                height: HEIGHT as u32,
526            },
527        );
528
529        pool.add_frames(vec![(); 16]).unwrap();
530
531        let mut frame_producer = TestFrameGenerator::new(FRAME_COUNT, display, pool, frame_layout);
532
533        let mut bitstream = Vec::new();
534
535        let file_header = IvfFileHeader::new(
536            IvfFileHeader::CODEC_VP9,
537            WIDTH as u16,
538            HEIGHT as u16,
539            30,
540            FRAME_COUNT as u32,
541        );
542
543        file_header.writo_into(&mut bitstream).unwrap();
544
545        simple_encode_loop(&mut encoder, &mut frame_producer, |coded| {
546            let header = IvfFrameHeader {
547                timestamp: coded.metadata.timestamp,
548                frame_size: coded.bitstream.len() as u32,
549            };
550
551            header.writo_into(&mut bitstream).unwrap();
552            bitstream.extend(coded.bitstream);
553        })
554        .unwrap();
555
556        let write_to_file = std::option_env!("CROS_CODECS_TEST_WRITE_TO_FILE") == Some("true");
557        if write_to_file {
558            use std::io::Write;
559            let mut out = std::fs::File::create("test_vaapi_encoder.vp9.ivf").unwrap();
560            out.write_all(&bitstream).unwrap();
561            out.flush().unwrap();
562        }
563    }
564
565    #[test]
566    // Ignore this test by default as it requires libva-compatible hardware.
567    #[ignore]
568    fn test_vaapi_encoder_p010() {
569        type VaapiVp9Encoder<'l> =
570            StatelessEncoder<PooledVaSurface<()>, VaapiBackend<(), PooledVaSurface<()>>>;
571
572        const WIDTH: usize = 512;
573        const HEIGHT: usize = 512;
574        const FRAME_COUNT: u64 = 100;
575
576        let _ = env_logger::try_init();
577
578        let display = libva::Display::open().unwrap();
579        let entrypoints = display
580            .query_config_entrypoints(VAProfileVP9Profile2)
581            .unwrap();
582        let low_power = entrypoints.contains(&VAEntrypointEncSliceLP);
583
584        let config = EncoderConfig {
585            bit_depth: BitDepth::Depth10,
586            resolution: Resolution {
587                width: WIDTH as u32,
588                height: HEIGHT as u32,
589            },
590            initial_tunings: Tunings {
591                rate_control: RateControl::ConstantBitrate(200_000),
592                framerate: 30,
593                ..Default::default()
594            },
595            ..Default::default()
596        };
597
598        let frame_layout = FrameLayout {
599            format: (b"P010".into(), 0),
600            size: Resolution {
601                width: WIDTH as u32,
602                height: HEIGHT as u32,
603            },
604            planes: vec![
605                PlaneLayout {
606                    buffer_index: 0,
607                    offset: 0,
608                    stride: WIDTH,
609                },
610                PlaneLayout {
611                    buffer_index: 0,
612                    offset: WIDTH * HEIGHT,
613                    stride: WIDTH,
614                },
615            ],
616        };
617
618        let mut encoder = VaapiVp9Encoder::new_vaapi(
619            Rc::clone(&display),
620            config,
621            frame_layout.format.0,
622            frame_layout.size,
623            low_power,
624            BlockingMode::Blocking,
625        )
626        .unwrap();
627
628        let mut pool = VaSurfacePool::new(
629            Rc::clone(&display),
630            VA_RT_FORMAT_YUV420_10,
631            Some(UsageHint::USAGE_HINT_ENCODER),
632            Resolution {
633                width: WIDTH as u32,
634                height: HEIGHT as u32,
635            },
636        );
637
638        pool.add_frames(vec![(); 16]).unwrap();
639
640        let mut frame_producer = TestFrameGenerator::new(FRAME_COUNT, display, pool, frame_layout);
641
642        let mut bitstream = Vec::new();
643
644        let file_header = IvfFileHeader::new(
645            IvfFileHeader::CODEC_VP9,
646            WIDTH as u16,
647            HEIGHT as u16,
648            30,
649            FRAME_COUNT as u32,
650        );
651
652        file_header.writo_into(&mut bitstream).unwrap();
653
654        simple_encode_loop(&mut encoder, &mut frame_producer, |coded| {
655            let header = IvfFrameHeader {
656                timestamp: coded.metadata.timestamp,
657                frame_size: coded.bitstream.len() as u32,
658            };
659
660            header.writo_into(&mut bitstream).unwrap();
661            bitstream.extend(coded.bitstream);
662        })
663        .unwrap();
664
665        let write_to_file = std::option_env!("CROS_CODECS_TEST_WRITE_TO_FILE") == Some("true");
666        if write_to_file {
667            use std::io::Write;
668            let mut out = std::fs::File::create("test_vaapi_encoder_p010.vp9.ivf").unwrap();
669            out.write_all(&bitstream).unwrap();
670            out.flush().unwrap();
671        }
672    }
673}