devices/virtio/video/encoder/backend/
vda.rs

1// Copyright 2021 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::collections::btree_map::Entry;
6use std::collections::BTreeMap;
7
8use anyhow::anyhow;
9use anyhow::Context;
10use base::error;
11use base::warn;
12use base::AsRawDescriptor;
13use base::IntoRawDescriptor;
14use libvda::encode::EncodeCapabilities;
15use libvda::encode::VeaImplType;
16use libvda::encode::VeaInstance;
17
18use super::*;
19use crate::virtio::video::encoder::*;
20use crate::virtio::video::error::VideoError;
21use crate::virtio::video::error::VideoResult;
22use crate::virtio::video::format::Bitrate;
23use crate::virtio::video::format::Format;
24use crate::virtio::video::format::FormatDesc;
25use crate::virtio::video::format::FormatRange;
26use crate::virtio::video::format::FrameFormat;
27use crate::virtio::video::format::Level;
28use crate::virtio::video::format::Profile;
29use crate::virtio::video::resource::GuestResource;
30use crate::virtio::video::resource::GuestResourceHandle;
31
32impl From<Bitrate> for libvda::encode::Bitrate {
33    fn from(bitrate: Bitrate) -> Self {
34        libvda::encode::Bitrate {
35            mode: match bitrate {
36                Bitrate::Vbr { .. } => libvda::encode::BitrateMode::VBR,
37                Bitrate::Cbr { .. } => libvda::encode::BitrateMode::CBR,
38            },
39            target: bitrate.target(),
40            peak: match &bitrate {
41                // No need to specify peak if mode is CBR.
42                Bitrate::Cbr { .. } => 0,
43                Bitrate::Vbr { peak, .. } => *peak,
44            },
45        }
46    }
47}
48
49/// A VDA encoder backend that can be passed to `EncoderDevice::new` in order to create a working
50/// encoder.
51pub struct LibvdaEncoder {
52    instance: VeaInstance,
53    capabilities: EncoderCapabilities,
54}
55
56impl LibvdaEncoder {
57    pub fn new() -> VideoResult<Self> {
58        let instance = VeaInstance::new(VeaImplType::Gavea)?;
59
60        let EncodeCapabilities {
61            input_formats,
62            output_formats,
63        } = instance.get_capabilities();
64
65        if input_formats.is_empty() || output_formats.is_empty() {
66            error!("No input or output formats.");
67            return Err(VideoError::InvalidFormat);
68        }
69
70        let input_format_descs: Vec<FormatDesc> = input_formats
71            .iter()
72            .map(|input_format| {
73                let format = match input_format {
74                    libvda::PixelFormat::NV12 => Format::NV12,
75                    libvda::PixelFormat::YV12 => Format::YUV420,
76                };
77
78                // VEA's GetSupportedProfiles does not return resolution information.
79                // The input formats are retrieved by querying minigbm.
80                // TODO(alexlau): Populate this with real information.
81
82                FormatDesc {
83                    mask: !(u64::MAX << output_formats.len()),
84                    format,
85                    frame_formats: vec![FrameFormat {
86                        width: FormatRange {
87                            min: 2,
88                            max: 4096,
89                            step: 1,
90                        },
91                        height: FormatRange {
92                            min: 2,
93                            max: 4096,
94                            step: 1,
95                        },
96                        bitrates: vec![FormatRange {
97                            min: 0,
98                            max: 8000,
99                            step: 1,
100                        }],
101                    }],
102                    plane_align: 1,
103                }
104            })
105            .collect();
106
107        if !input_format_descs
108            .iter()
109            .any(|fd| fd.format == Format::NV12)
110        {
111            // NV12 is currently the only supported pixel format for libvda.
112            error!("libvda encoder does not support NV12.");
113            return Err(VideoError::InvalidFormat);
114        }
115
116        struct ParsedFormat {
117            profiles: Vec<Profile>,
118            max_width: u32,
119            max_height: u32,
120        }
121        let mut parsed_formats: BTreeMap<Format, ParsedFormat> = BTreeMap::new();
122
123        for output_format in output_formats.iter() {
124            // TODO(alexlau): Consider using `max_framerate_numerator` and
125            // `max_framerate_denominator`.
126            let libvda::encode::OutputProfile {
127                profile: libvda_profile,
128                max_width,
129                max_height,
130                ..
131            } = output_format;
132
133            let profile = match Profile::from_libvda_profile(*libvda_profile) {
134                Some(p) => p,
135                None => {
136                    warn!("Skipping unsupported libvda profile: {:?}", libvda_profile);
137                    continue;
138                }
139            };
140
141            match parsed_formats.entry(profile.to_format()) {
142                Entry::Occupied(mut occupied_entry) => {
143                    let parsed_format = occupied_entry.get_mut();
144                    parsed_format.profiles.push(profile);
145                    // If we get different libvda profiles of the same VIRTIO_VIDEO_FORMAT
146                    // (Format) that have different max resolutions or bitrates, take the
147                    // minimum between all of the different profiles.
148                    parsed_format.max_width = std::cmp::min(*max_width, parsed_format.max_width);
149                    parsed_format.max_height = std::cmp::min(*max_height, parsed_format.max_height);
150                }
151                Entry::Vacant(vacant_entry) => {
152                    vacant_entry.insert(ParsedFormat {
153                        profiles: vec![profile],
154                        max_width: *max_width,
155                        max_height: *max_height,
156                    });
157                }
158            }
159        }
160
161        let mut output_format_descs = vec![];
162        let mut coded_format_profiles = BTreeMap::new();
163        for (format, parsed_format) in parsed_formats.into_iter() {
164            let ParsedFormat {
165                mut profiles,
166                max_width,
167                max_height,
168            } = parsed_format;
169
170            output_format_descs.push(FormatDesc {
171                mask: !(u64::MAX << output_formats.len()),
172                format,
173                frame_formats: vec![FrameFormat {
174                    width: FormatRange {
175                        min: 2,
176                        max: max_width,
177                        step: 1,
178                    },
179                    height: FormatRange {
180                        min: 2,
181                        max: max_height,
182                        step: 1,
183                    },
184                    bitrates: vec![FormatRange {
185                        min: 0,
186                        max: 8000,
187                        step: 1,
188                    }],
189                }],
190                plane_align: 1,
191            });
192
193            profiles.sort_unstable();
194            coded_format_profiles.insert(format, profiles);
195        }
196
197        Ok(LibvdaEncoder {
198            instance,
199            capabilities: EncoderCapabilities {
200                input_format_descs,
201                output_format_descs,
202                coded_format_profiles,
203            },
204        })
205    }
206}
207
208impl Encoder for LibvdaEncoder {
209    type Session = LibvdaEncoderSession;
210
211    fn query_capabilities(&self) -> VideoResult<EncoderCapabilities> {
212        Ok(self.capabilities.clone())
213    }
214
215    fn start_session(&mut self, config: SessionConfig) -> VideoResult<LibvdaEncoderSession> {
216        if config.dst_params.format.is_none() {
217            return Err(VideoError::InvalidArgument);
218        }
219
220        let input_format = match config
221            .src_params
222            .format
223            .ok_or(VideoError::InvalidArgument)?
224        {
225            Format::NV12 => libvda::PixelFormat::NV12,
226            Format::YUV420 => libvda::PixelFormat::YV12,
227            unsupported_format => {
228                error!("Unsupported libvda format: {}", unsupported_format);
229                return Err(VideoError::InvalidArgument);
230            }
231        };
232
233        let output_profile = match config.dst_profile.to_libvda_profile() {
234            Some(p) => p,
235            None => {
236                error!("Unsupported libvda profile");
237                return Err(VideoError::InvalidArgument);
238            }
239        };
240
241        let config = libvda::encode::Config {
242            input_format,
243            input_visible_width: config.src_params.frame_width,
244            input_visible_height: config.src_params.frame_height,
245            output_profile,
246            bitrate: config.dst_bitrate.into(),
247            initial_framerate: if config.frame_rate == 0 {
248                None
249            } else {
250                Some(config.frame_rate)
251            },
252            h264_output_level: config.dst_h264_level.map(|level| {
253                // This value is aligned to the H264 standard definition of SPS.level_idc.
254                match level {
255                    Level::H264_1_0 => 10,
256                    Level::H264_1_1 => 11,
257                    Level::H264_1_2 => 12,
258                    Level::H264_1_3 => 13,
259                    Level::H264_2_0 => 20,
260                    Level::H264_2_1 => 21,
261                    Level::H264_2_2 => 22,
262                    Level::H264_3_0 => 30,
263                    Level::H264_3_1 => 31,
264                    Level::H264_3_2 => 32,
265                    Level::H264_4_0 => 40,
266                    Level::H264_4_1 => 41,
267                    Level::H264_4_2 => 42,
268                    Level::H264_5_0 => 50,
269                    Level::H264_5_1 => 51,
270                }
271            }),
272        };
273
274        let session = self.instance.open_session(config)?;
275
276        Ok(LibvdaEncoderSession {
277            session,
278            next_input_buffer_id: 1,
279            next_output_buffer_id: 1,
280        })
281    }
282
283    fn stop_session(&mut self, _session: LibvdaEncoderSession) -> VideoResult<()> {
284        // Resources will be freed when `_session` is dropped.
285        Ok(())
286    }
287}
288
289pub struct LibvdaEncoderSession {
290    session: libvda::encode::Session,
291    next_input_buffer_id: InputBufferId,
292    next_output_buffer_id: OutputBufferId,
293}
294
295impl EncoderSession for LibvdaEncoderSession {
296    fn encode(
297        &mut self,
298        resource: GuestResource,
299        timestamp: u64,
300        force_keyframe: bool,
301    ) -> VideoResult<InputBufferId> {
302        let input_buffer_id = self.next_input_buffer_id;
303        let desc = match resource.handle {
304            GuestResourceHandle::VirtioObject(handle) => handle.desc,
305            _ => {
306                return Err(VideoError::BackendFailure(anyhow!(
307                    "VDA backend only supports virtio object resources"
308                )))
309            }
310        };
311
312        let libvda_planes = resource
313            .planes
314            .iter()
315            .map(|plane| libvda::FramePlane {
316                offset: plane.offset as i32,
317                stride: plane.stride as i32,
318            })
319            .collect::<Vec<_>>();
320
321        self.session.encode(
322            input_buffer_id as i32,
323            // Steal the descriptor of the resource, as libvda will close it.
324            desc.into_raw_descriptor(),
325            &libvda_planes,
326            timestamp as i64,
327            force_keyframe,
328        )?;
329
330        self.next_input_buffer_id = self.next_input_buffer_id.wrapping_add(1);
331
332        Ok(input_buffer_id)
333    }
334
335    fn use_output_buffer(
336        &mut self,
337        resource: GuestResourceHandle,
338        offset: u32,
339        size: u32,
340    ) -> VideoResult<OutputBufferId> {
341        let output_buffer_id = self.next_output_buffer_id;
342        let desc = match resource {
343            GuestResourceHandle::VirtioObject(handle) => handle.desc,
344            _ => {
345                return Err(VideoError::BackendFailure(anyhow!(
346                    "VDA backend only supports virtio object resources"
347                )))
348            }
349        };
350
351        self.session.use_output_buffer(
352            output_buffer_id as i32,
353            // Steal the descriptor of the resource, as libvda will close it.
354            desc.into_raw_descriptor(),
355            offset,
356            size,
357        )?;
358
359        self.next_output_buffer_id = self.next_output_buffer_id.wrapping_add(1);
360
361        Ok(output_buffer_id)
362    }
363
364    fn flush(&mut self) -> VideoResult<()> {
365        self.session
366            .flush()
367            .context("while flushing")
368            .map_err(VideoError::BackendFailure)
369    }
370
371    fn request_encoding_params_change(
372        &mut self,
373        bitrate: Bitrate,
374        framerate: u32,
375    ) -> VideoResult<()> {
376        self.session
377            .request_encoding_params_change(bitrate.into(), framerate)
378            .context("while requesting encoder parameter change")
379            .map_err(VideoError::BackendFailure)
380    }
381
382    fn event_pipe(&self) -> &dyn AsRawDescriptor {
383        self.session.pipe()
384    }
385
386    fn read_event(&mut self) -> VideoResult<EncoderEvent> {
387        let event = self.session.read_event()?;
388
389        use libvda::encode::Event::*;
390        let encoder_event = match event {
391            RequireInputBuffers {
392                input_count,
393                input_frame_width,
394                input_frame_height,
395                output_buffer_size,
396            } => EncoderEvent::RequireInputBuffers {
397                input_count,
398                input_frame_width,
399                input_frame_height,
400                output_buffer_size,
401            },
402            ProcessedInputBuffer(id) => EncoderEvent::ProcessedInputBuffer { id: id as u32 },
403            ProcessedOutputBuffer {
404                output_buffer_id,
405                payload_size,
406                key_frame,
407                timestamp,
408                ..
409            } => EncoderEvent::ProcessedOutputBuffer {
410                id: output_buffer_id as u32,
411                bytesused: payload_size,
412                keyframe: key_frame,
413                timestamp: timestamp as u64,
414            },
415            FlushResponse { flush_done } => EncoderEvent::FlushResponse { flush_done },
416            NotifyError(err) => EncoderEvent::NotifyError {
417                error: VideoError::BackendFailure(anyhow!(err)),
418            },
419        };
420
421        Ok(encoder_event)
422    }
423}