Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/devices/src/virtio/video/encoder/backend/vda.rs
5394 views
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
5
use std::collections::btree_map::Entry;
6
use std::collections::BTreeMap;
7
8
use anyhow::anyhow;
9
use anyhow::Context;
10
use base::error;
11
use base::warn;
12
use base::AsRawDescriptor;
13
use base::IntoRawDescriptor;
14
use libvda::encode::EncodeCapabilities;
15
use libvda::encode::VeaImplType;
16
use libvda::encode::VeaInstance;
17
18
use super::*;
19
use crate::virtio::video::encoder::*;
20
use crate::virtio::video::error::VideoError;
21
use crate::virtio::video::error::VideoResult;
22
use crate::virtio::video::format::Bitrate;
23
use crate::virtio::video::format::Format;
24
use crate::virtio::video::format::FormatDesc;
25
use crate::virtio::video::format::FormatRange;
26
use crate::virtio::video::format::FrameFormat;
27
use crate::virtio::video::format::Level;
28
use crate::virtio::video::format::Profile;
29
use crate::virtio::video::resource::GuestResource;
30
use crate::virtio::video::resource::GuestResourceHandle;
31
32
impl 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.
51
pub struct LibvdaEncoder {
52
instance: VeaInstance,
53
capabilities: EncoderCapabilities,
54
}
55
56
impl 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
208
impl 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
289
pub struct LibvdaEncoderSession {
290
session: libvda::encode::Session,
291
next_input_buffer_id: InputBufferId,
292
next_output_buffer_id: OutputBufferId,
293
}
294
295
impl 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
}
424
425