1 // Copyright 2020 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 use std::collections::btree_map::Entry;
6 use std::collections::BTreeMap;
7 use std::convert::TryFrom;
8
9 use anyhow::anyhow;
10 use base::error;
11 use base::warn;
12 use base::AsRawDescriptor;
13 use base::IntoRawDescriptor;
14 use libvda::decode::Event as LibvdaEvent;
15
16 use crate::virtio::video::decoder::backend::*;
17 use crate::virtio::video::decoder::Capability;
18 use crate::virtio::video::error::VideoError;
19 use crate::virtio::video::error::VideoResult;
20 use crate::virtio::video::format::*;
21
22 /// Since libvda only accepts 32-bit timestamps, we are going to truncate the frame 64-bit timestamp
23 /// (of nanosecond granularity) to only keep seconds granularity. This would result in information
24 /// being lost on a regular client, but the Android C2 decoder only sends timestamps with second
25 /// granularity, so this approach is going to work there. However, this means that this backend is
26 /// very unlikely to work with any other guest software. We accept this fact because it is
27 /// impossible to use outside of ChromeOS anyway.
28 const TIMESTAMP_TRUNCATE_FACTOR: u64 = 1_000_000_000;
29
30 impl TryFrom<Format> for libvda::Profile {
31 type Error = VideoError;
32
try_from(format: Format) -> Result<Self, Self::Error>33 fn try_from(format: Format) -> Result<Self, Self::Error> {
34 Ok(match format {
35 Format::VP8 => libvda::Profile::VP8,
36 Format::VP9 => libvda::Profile::VP9Profile0,
37 Format::H264 => libvda::Profile::H264ProfileBaseline,
38 Format::Hevc => libvda::Profile::HevcProfileMain,
39 _ => {
40 error!("specified format {} is not supported by VDA", format);
41 return Err(VideoError::InvalidParameter);
42 }
43 })
44 }
45 }
46
47 impl TryFrom<Format> for libvda::PixelFormat {
48 type Error = VideoError;
49
try_from(format: Format) -> Result<Self, Self::Error>50 fn try_from(format: Format) -> Result<Self, Self::Error> {
51 Ok(match format {
52 Format::NV12 => libvda::PixelFormat::NV12,
53 _ => {
54 error!("specified format {} is not supported by VDA", format);
55 return Err(VideoError::InvalidParameter);
56 }
57 })
58 }
59 }
60
61 impl From<&FramePlane> for libvda::FramePlane {
from(plane: &FramePlane) -> Self62 fn from(plane: &FramePlane) -> Self {
63 libvda::FramePlane {
64 offset: plane.offset as i32,
65 stride: plane.stride as i32,
66 }
67 }
68 }
69
70 impl From<libvda::decode::Event> for DecoderEvent {
from(event: libvda::decode::Event) -> Self71 fn from(event: libvda::decode::Event) -> Self {
72 // We cannot use the From trait here since neither libvda::decode::Response
73 // no std::result::Result are defined in the current crate.
74 fn vda_response_to_result(resp: libvda::decode::Response) -> VideoResult<()> {
75 match resp {
76 libvda::decode::Response::Success => Ok(()),
77 resp => Err(VideoError::BackendFailure(anyhow!("VDA failure: {}", resp))),
78 }
79 }
80
81 match event {
82 LibvdaEvent::ProvidePictureBuffers {
83 min_num_buffers,
84 width,
85 height,
86 visible_rect_left,
87 visible_rect_top,
88 visible_rect_right,
89 visible_rect_bottom,
90 } => DecoderEvent::ProvidePictureBuffers {
91 min_num_buffers,
92 width,
93 height,
94 visible_rect: Rect {
95 left: visible_rect_left,
96 top: visible_rect_top,
97 right: visible_rect_right,
98 bottom: visible_rect_bottom,
99 },
100 },
101 LibvdaEvent::PictureReady {
102 buffer_id,
103 bitstream_id,
104 left,
105 top,
106 right,
107 bottom,
108 } => DecoderEvent::PictureReady {
109 picture_buffer_id: buffer_id,
110 // Restore the truncated timestamp to its original value (hopefully).
111 timestamp: TIMESTAMP_TRUNCATE_FACTOR.wrapping_mul(bitstream_id as u64),
112 visible_rect: Rect {
113 left,
114 top,
115 right,
116 bottom,
117 },
118 },
119 LibvdaEvent::NotifyEndOfBitstreamBuffer { bitstream_id } => {
120 // We will patch the timestamp to the actual bitstream ID in `read_event`.
121 DecoderEvent::NotifyEndOfBitstreamBuffer(bitstream_id as u32)
122 }
123 LibvdaEvent::NotifyError(resp) => DecoderEvent::NotifyError(
124 VideoError::BackendFailure(anyhow!("VDA failure: {}", resp)),
125 ),
126 LibvdaEvent::ResetResponse(resp) => {
127 DecoderEvent::ResetCompleted(vda_response_to_result(resp))
128 }
129 LibvdaEvent::FlushResponse(resp) => {
130 DecoderEvent::FlushCompleted(vda_response_to_result(resp))
131 }
132 }
133 }
134 }
135
136 // Used by DecoderSession::get_capabilities().
from_pixel_format( fmt: &libvda::PixelFormat, mask: u64, width_range: FormatRange, height_range: FormatRange, ) -> FormatDesc137 fn from_pixel_format(
138 fmt: &libvda::PixelFormat,
139 mask: u64,
140 width_range: FormatRange,
141 height_range: FormatRange,
142 ) -> FormatDesc {
143 let format = match fmt {
144 libvda::PixelFormat::NV12 => Format::NV12,
145 libvda::PixelFormat::YV12 => Format::YUV420,
146 };
147
148 let frame_formats = vec![FrameFormat {
149 width: width_range,
150 height: height_range,
151 bitrates: Vec::new(),
152 }];
153
154 FormatDesc {
155 mask,
156 format,
157 frame_formats,
158 plane_align: 1,
159 }
160 }
161
162 pub struct VdaDecoderSession {
163 vda_session: libvda::decode::Session,
164 format: Option<libvda::PixelFormat>,
165 /// libvda can only handle 32-bit timestamps, so we will give it the buffer ID as a timestamp
166 /// and map it back to the actual timestamp using this table when a decoded frame is produced.
167 timestamp_to_resource_id: BTreeMap<u32, u32>,
168 }
169
170 impl DecoderSession for VdaDecoderSession {
set_output_parameters(&mut self, buffer_count: usize, format: Format) -> VideoResult<()>171 fn set_output_parameters(&mut self, buffer_count: usize, format: Format) -> VideoResult<()> {
172 self.format = Some(libvda::PixelFormat::try_from(format)?);
173 Ok(self.vda_session.set_output_buffer_count(buffer_count)?)
174 }
175
decode( &mut self, resource_id: u32, timestamp: u64, resource: GuestResourceHandle, offset: u32, bytes_used: u32, ) -> VideoResult<()>176 fn decode(
177 &mut self,
178 resource_id: u32,
179 timestamp: u64,
180 resource: GuestResourceHandle,
181 offset: u32,
182 bytes_used: u32,
183 ) -> VideoResult<()> {
184 let handle = match resource {
185 GuestResourceHandle::VirtioObject(handle) => handle,
186 _ => {
187 return Err(VideoError::BackendFailure(anyhow!(
188 "VDA backend only supports virtio object resources"
189 )))
190 }
191 };
192
193 // While the virtio-video driver handles timestamps as nanoseconds, Chrome assumes
194 // per-second timestamps coming. So, we need a conversion from nsec to sec. Note that this
195 // value should not be an unix time stamp but a frame number that the Android V4L2 C2
196 // decoder passes to the driver as a 32-bit integer in our implementation. So, overflow must
197 // not happen in this conversion.
198 let truncated_timestamp = (timestamp / TIMESTAMP_TRUNCATE_FACTOR) as u32;
199 self.timestamp_to_resource_id
200 .insert(truncated_timestamp, resource_id);
201
202 if truncated_timestamp as u64 * TIMESTAMP_TRUNCATE_FACTOR != timestamp {
203 warn!("truncation of timestamp {} resulted in precision loss. Only send timestamps with second granularity to this backend.", timestamp);
204 }
205
206 Ok(self.vda_session.decode(
207 truncated_timestamp as i32, // bitstream_id
208 // Steal the descriptor of the resource, as libvda will close it.
209 handle.desc.into_raw_descriptor(),
210 offset,
211 bytes_used,
212 )?)
213 }
214
flush(&mut self) -> VideoResult<()>215 fn flush(&mut self) -> VideoResult<()> {
216 Ok(self.vda_session.flush()?)
217 }
218
reset(&mut self) -> VideoResult<()>219 fn reset(&mut self) -> VideoResult<()> {
220 Ok(self.vda_session.reset()?)
221 }
222
clear_output_buffers(&mut self) -> VideoResult<()>223 fn clear_output_buffers(&mut self) -> VideoResult<()> {
224 Ok(())
225 }
226
event_pipe(&self) -> &dyn AsRawDescriptor227 fn event_pipe(&self) -> &dyn AsRawDescriptor {
228 self.vda_session.pipe()
229 }
230
use_output_buffer( &mut self, picture_buffer_id: i32, resource: GuestResource, ) -> VideoResult<()>231 fn use_output_buffer(
232 &mut self,
233 picture_buffer_id: i32,
234 resource: GuestResource,
235 ) -> VideoResult<()> {
236 let handle = match resource.handle {
237 GuestResourceHandle::VirtioObject(handle) => handle,
238 _ => {
239 return Err(VideoError::BackendFailure(anyhow!(
240 "VDA backend only supports virtio object resources"
241 )))
242 }
243 };
244 let vda_planes: Vec<libvda::FramePlane> = resource.planes.iter().map(Into::into).collect();
245
246 Ok(self.vda_session.use_output_buffer(
247 picture_buffer_id,
248 self.format.ok_or(VideoError::BackendFailure(anyhow!(
249 "set_output_parameters() must be called before use_output_buffer()"
250 )))?,
251 // Steal the descriptor of the resource, as libvda will close it.
252 handle.desc.into_raw_descriptor(),
253 &vda_planes,
254 handle.modifier,
255 )?)
256 }
257
reuse_output_buffer(&mut self, picture_buffer_id: i32) -> VideoResult<()>258 fn reuse_output_buffer(&mut self, picture_buffer_id: i32) -> VideoResult<()> {
259 Ok(self.vda_session.reuse_output_buffer(picture_buffer_id)?)
260 }
261
read_event(&mut self) -> VideoResult<DecoderEvent>262 fn read_event(&mut self) -> VideoResult<DecoderEvent> {
263 self.vda_session
264 .read_event()
265 .map(Into::into)
266 // Libvda returned the truncated timestamp that we gave it as the timestamp of this
267 // buffer. Replace it with the bitstream ID that was passed to `decode` for this
268 // resource.
269 .map(|mut e| {
270 if let DecoderEvent::NotifyEndOfBitstreamBuffer(timestamp) = &mut e {
271 let bitstream_id = self
272 .timestamp_to_resource_id
273 .remove(timestamp)
274 .unwrap_or_else(|| {
275 error!("timestamp {} not registered!", *timestamp);
276 0
277 });
278 *timestamp = bitstream_id;
279 }
280 e
281 })
282 .map_err(Into::into)
283 }
284 }
285
286 /// A VDA decoder backend that can be passed to `Decoder::new` in order to create a working decoder.
287 pub struct LibvdaDecoder(libvda::decode::VdaInstance);
288
289 impl LibvdaDecoder {
290 /// Create a decoder backend instance that can be used to instantiate an decoder.
new(backend_type: libvda::decode::VdaImplType) -> VideoResult<Self>291 pub fn new(backend_type: libvda::decode::VdaImplType) -> VideoResult<Self> {
292 Ok(Self(libvda::decode::VdaInstance::new(backend_type)?))
293 }
294 }
295
296 impl DecoderBackend for LibvdaDecoder {
297 type Session = VdaDecoderSession;
298
new_session(&mut self, format: Format) -> VideoResult<Self::Session>299 fn new_session(&mut self, format: Format) -> VideoResult<Self::Session> {
300 let profile = libvda::Profile::try_from(format)?;
301
302 Ok(VdaDecoderSession {
303 vda_session: self.0.open_session(profile).map_err(|e| {
304 error!("failed to open a session for {:?}: {}", format, e);
305 VideoError::InvalidOperation
306 })?,
307 format: None,
308 timestamp_to_resource_id: Default::default(),
309 })
310 }
311
get_capabilities(&self) -> Capability312 fn get_capabilities(&self) -> Capability {
313 let caps = libvda::decode::VdaInstance::get_capabilities(&self.0);
314
315 // Raise the first |# of supported raw formats|-th bits because we can assume that any
316 // combination of (a coded format, a raw format) is valid in Chrome.
317 let mask = !(u64::max_value() << caps.output_formats.len());
318
319 let mut in_fmts = vec![];
320 let mut profiles: BTreeMap<Format, Vec<Profile>> = Default::default();
321 for fmt in caps.input_formats.iter() {
322 match Profile::from_libvda_profile(fmt.profile) {
323 Some(profile) => {
324 let format = profile.to_format();
325 in_fmts.push(FormatDesc {
326 mask,
327 format,
328 frame_formats: vec![FrameFormat {
329 width: FormatRange {
330 min: fmt.min_width,
331 max: fmt.max_width,
332 step: 1,
333 },
334 height: FormatRange {
335 min: fmt.min_height,
336 max: fmt.max_height,
337 step: 1,
338 },
339 bitrates: Vec::new(),
340 }],
341 plane_align: 1,
342 });
343 match profiles.entry(format) {
344 Entry::Occupied(mut e) => e.get_mut().push(profile),
345 Entry::Vacant(e) => {
346 e.insert(vec![profile]);
347 }
348 }
349 }
350 None => {
351 warn!(
352 "No virtio-video equivalent for libvda profile, skipping: {:?}",
353 fmt.profile
354 );
355 }
356 }
357 }
358
359 let levels: BTreeMap<Format, Vec<Level>> = if profiles.contains_key(&Format::H264) {
360 // We only support Level 1.0 for H.264.
361 vec![(Format::H264, vec![Level::H264_1_0])]
362 .into_iter()
363 .collect()
364 } else {
365 Default::default()
366 };
367
368 // Prepare {min, max} of {width, height}.
369 // While these values are associated with each input format in libvda,
370 // they are associated with each output format in virtio-video protocol.
371 // Thus, we compute max of min values and min of max values here.
372 let min_width = caps.input_formats.iter().map(|fmt| fmt.min_width).max();
373 let max_width = caps.input_formats.iter().map(|fmt| fmt.max_width).min();
374 let min_height = caps.input_formats.iter().map(|fmt| fmt.min_height).max();
375 let max_height = caps.input_formats.iter().map(|fmt| fmt.max_height).min();
376 let width_range = FormatRange {
377 min: min_width.unwrap_or(0),
378 max: max_width.unwrap_or(0),
379 step: 1,
380 };
381 let height_range = FormatRange {
382 min: min_height.unwrap_or(0),
383 max: max_height.unwrap_or(0),
384 step: 1,
385 };
386
387 // Raise the first |# of supported coded formats|-th bits because we can assume that any
388 // combination of (a coded format, a raw format) is valid in Chrome.
389 let mask = !(u64::max_value() << caps.input_formats.len());
390 let out_fmts = caps
391 .output_formats
392 .iter()
393 .map(|fmt| from_pixel_format(fmt, mask, width_range, height_range))
394 .collect();
395
396 Capability::new(in_fmts, out_fmts, profiles, levels)
397 }
398 }
399