Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/devices/src/virtio/video/encoder/backend/ffmpeg.rs
5394 views
1
// Copyright 2022 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::BTreeMap;
6
use std::collections::VecDeque;
7
use std::os::raw::c_int;
8
use std::ptr;
9
use std::sync::Arc;
10
use std::sync::Weak;
11
12
use anyhow::anyhow;
13
use anyhow::Context;
14
use base::error;
15
use base::AsRawDescriptor;
16
use base::MappedRegion;
17
use base::MemoryMappingArena;
18
use ffmpeg::avcodec::AvBufferSource;
19
use ffmpeg::avcodec::AvCodec;
20
use ffmpeg::avcodec::AvCodecContext;
21
use ffmpeg::avcodec::AvCodecIterator;
22
use ffmpeg::avcodec::AvFrame;
23
use ffmpeg::avcodec::AvPacket;
24
use ffmpeg::avcodec::Dimensions;
25
use ffmpeg::avcodec::TryReceiveResult;
26
use ffmpeg::max_buffer_alignment;
27
use ffmpeg::AVPictureType_AV_PICTURE_TYPE_I;
28
use ffmpeg::AVRational;
29
use ffmpeg::AV_PKT_FLAG_KEY;
30
31
use crate::virtio::video::encoder::backend::Encoder;
32
use crate::virtio::video::encoder::backend::EncoderSession;
33
use crate::virtio::video::encoder::EncoderCapabilities;
34
use crate::virtio::video::encoder::EncoderEvent;
35
use crate::virtio::video::encoder::InputBufferId;
36
use crate::virtio::video::encoder::OutputBufferId;
37
use crate::virtio::video::encoder::SessionConfig;
38
use crate::virtio::video::error::VideoError;
39
use crate::virtio::video::error::VideoResult;
40
use crate::virtio::video::ffmpeg::TryAsAvFrameExt;
41
use crate::virtio::video::format::Bitrate;
42
use crate::virtio::video::format::Format;
43
use crate::virtio::video::format::FormatDesc;
44
use crate::virtio::video::format::FormatRange;
45
use crate::virtio::video::format::FrameFormat;
46
use crate::virtio::video::format::Profile;
47
use crate::virtio::video::resource::BufferHandle;
48
use crate::virtio::video::resource::GuestResource;
49
use crate::virtio::video::resource::GuestResourceHandle;
50
use crate::virtio::video::utils::EventQueue;
51
use crate::virtio::video::utils::SyncEventQueue;
52
53
/// Structure wrapping a backing memory mapping for an input frame that can be used as a libavcodec
54
/// buffer source. It also sends a `ProcessedInputBuffer` event when dropped.
55
struct InputBuffer {
56
/// Memory mapping to the input frame.
57
mapping: MemoryMappingArena,
58
/// Bistream ID that will be sent as part of the `ProcessedInputBuffer` event.
59
buffer_id: InputBufferId,
60
/// Pointer to the event queue to send the `ProcessedInputBuffer` event to. The event will
61
/// not be sent if the pointer becomes invalid.
62
event_queue: Weak<SyncEventQueue<EncoderEvent>>,
63
}
64
65
impl Drop for InputBuffer {
66
fn drop(&mut self) {
67
match self.event_queue.upgrade() {
68
None => (),
69
// If the event queue is still valid, send the event signaling we can be reused.
70
Some(event_queue) => event_queue
71
.queue_event(EncoderEvent::ProcessedInputBuffer { id: self.buffer_id })
72
.unwrap_or_else(|e| {
73
error!("cannot send end of input buffer notification: {:#}", e)
74
}),
75
}
76
}
77
}
78
79
impl AvBufferSource for InputBuffer {
80
fn as_ptr(&self) -> *const u8 {
81
self.mapping.as_ptr()
82
}
83
84
fn len(&self) -> usize {
85
self.mapping.size()
86
}
87
88
fn is_empty(&self) -> bool {
89
self.len() == 0
90
}
91
}
92
93
enum CodecJob {
94
Frame(AvFrame),
95
Flush,
96
}
97
98
pub struct FfmpegEncoderSession {
99
/// Queue of events waiting to be read by the client.
100
event_queue: Arc<SyncEventQueue<EncoderEvent>>,
101
102
/// FIFO of jobs submitted by the client and waiting to be performed.
103
codec_jobs: VecDeque<CodecJob>,
104
/// Queue of (unfilled) output buffers to fill with upcoming encoder output.
105
output_queue: VecDeque<(OutputBufferId, MemoryMappingArena)>,
106
/// `true` if a flush is pending. While a pending flush exist, input buffers are temporarily
107
/// held on and not sent to the encoder. An actual flush call will be issued when we run out of
108
/// output buffers (to defend against FFmpeg bugs), and we'll try to receive outputs again
109
/// until we receive another code indicating the flush has completed, at which point this
110
/// flag will be reset.
111
is_flushing: bool,
112
113
/// The libav context for this session.
114
context: AvCodecContext,
115
116
next_input_buffer_id: InputBufferId,
117
next_output_buffer_id: OutputBufferId,
118
}
119
120
impl FfmpegEncoderSession {
121
/// Try to send one input frame to the codec for encode.
122
///
123
/// Returns `Ok(true)` if the frame was successfully queued, `Ok(false)` if the frame was not
124
/// queued due to the queue being full or an in-progress flushing, and `Err` in case of errors.
125
fn try_send_input_job(&mut self) -> VideoResult<bool> {
126
// When a flush is queued, drain buffers.
127
if self.is_flushing {
128
return Ok(false);
129
}
130
131
match self.codec_jobs.front() {
132
Some(CodecJob::Frame(b)) => {
133
let result = self
134
.context
135
.try_send_frame(b)
136
.context("while sending frame")
137
.map_err(VideoError::BackendFailure);
138
// This look awkward but we have to do it like this since VideoResult doesn't
139
// implement PartialEq.
140
if let Ok(false) = result {
141
} else {
142
self.codec_jobs.pop_front().unwrap();
143
}
144
result
145
}
146
Some(CodecJob::Flush) => {
147
self.codec_jobs.pop_front().unwrap();
148
149
// Queue a flush. The actual flush will be performed when receive returns EAGAIN.
150
self.is_flushing = true;
151
Ok(true)
152
}
153
None => Ok(false),
154
}
155
}
156
157
/// Try to retrieve one encoded packet from the codec, and if success, deliver it to the guest.
158
///
159
/// Returns `Ok(true)` if the packet was successfully retrieved and the guest was signaled,
160
/// `Ok(false)` if there's no full packet available right now, and `Err` in case of error.
161
fn try_receive_packet(&mut self) -> VideoResult<bool> {
162
let (buffer_id, out_buf) = match self.output_queue.front_mut() {
163
Some(p) => p,
164
None => return Ok(false),
165
};
166
167
let mut packet = AvPacket::empty();
168
169
match self
170
.context
171
.try_receive_packet(&mut packet)
172
.context("while receiving packet")
173
{
174
Ok(TryReceiveResult::TryAgain) => {
175
if !self.is_flushing {
176
return Ok(false);
177
}
178
179
// Flush the encoder, then move on to draining.
180
if let Err(err) = self.context.flush_encoder() {
181
self.is_flushing = false;
182
self.event_queue
183
.queue_event(EncoderEvent::FlushResponse { flush_done: false })
184
.context("while flushing")
185
.map_err(VideoError::BackendFailure)?;
186
return Err(err)
187
.context("while flushing")
188
.map_err(VideoError::BackendFailure);
189
}
190
self.try_receive_packet()
191
}
192
Ok(TryReceiveResult::FlushCompleted) => {
193
self.is_flushing = false;
194
self.event_queue
195
.queue_event(EncoderEvent::FlushResponse { flush_done: true })
196
.map_err(Into::into)
197
.map_err(VideoError::BackendFailure)?;
198
self.context.reset();
199
Ok(false)
200
}
201
Ok(TryReceiveResult::Received) => {
202
let packet_size = packet.as_ref().size as usize;
203
if packet_size > out_buf.size() {
204
return Err(VideoError::BackendFailure(anyhow!(
205
"encoded packet does not fit in output buffer"
206
)));
207
}
208
// SAFETY:
209
// Safe because packet.as_ref().data and out_buf.as_ptr() are valid references and
210
// we did bound check above.
211
unsafe {
212
ptr::copy_nonoverlapping(packet.as_ref().data, out_buf.as_ptr(), packet_size);
213
}
214
self.event_queue
215
.queue_event(EncoderEvent::ProcessedOutputBuffer {
216
id: *buffer_id,
217
bytesused: packet.as_ref().size as _,
218
keyframe: (packet.as_ref().flags as u32 & AV_PKT_FLAG_KEY) != 0,
219
timestamp: packet.as_ref().dts as _,
220
})
221
.map_err(Into::into)
222
.map_err(VideoError::BackendFailure)?;
223
self.output_queue.pop_front();
224
Ok(true)
225
}
226
Err(e) => Err(VideoError::BackendFailure(e)),
227
}
228
}
229
230
/// Try to progress through the encoding pipeline, either by sending input frames or by
231
/// retrieving output packets and delivering them to the guest.
232
fn try_encode(&mut self) -> VideoResult<()> {
233
// Go through the pipeline stages as long as it makes some kind of progress.
234
loop {
235
let mut progress = false;
236
// Use |= instead of || to avoid short-circuiting, which is harmless but makes the
237
// execution order weird.
238
progress |= self.try_send_input_job()?;
239
progress |= self.try_receive_packet()?;
240
if !progress {
241
break;
242
}
243
}
244
Ok(())
245
}
246
}
247
248
impl EncoderSession for FfmpegEncoderSession {
249
fn encode(
250
&mut self,
251
resource: GuestResource,
252
timestamp: u64,
253
force_keyframe: bool,
254
) -> VideoResult<InputBufferId> {
255
let buffer_id = self.next_input_buffer_id;
256
self.next_input_buffer_id = buffer_id.wrapping_add(1);
257
258
let mut frame: AvFrame = resource
259
.try_as_av_frame(|mapping| InputBuffer {
260
mapping,
261
buffer_id,
262
event_queue: Arc::downgrade(&self.event_queue),
263
})
264
.context("while creating input AvFrame")
265
.map_err(VideoError::BackendFailure)?;
266
267
if force_keyframe {
268
frame.set_pict_type(AVPictureType_AV_PICTURE_TYPE_I);
269
}
270
frame.set_pts(timestamp as i64);
271
self.codec_jobs.push_back(CodecJob::Frame(frame));
272
self.try_encode()?;
273
274
Ok(buffer_id)
275
}
276
277
fn use_output_buffer(
278
&mut self,
279
resource: GuestResourceHandle,
280
offset: u32,
281
size: u32,
282
) -> VideoResult<OutputBufferId> {
283
let buffer_id = self.next_output_buffer_id;
284
self.next_output_buffer_id = buffer_id.wrapping_add(1);
285
286
let mapping = resource
287
.get_mapping(offset as usize, size as usize)
288
.context("while mapping output buffer")
289
.map_err(VideoError::BackendFailure)?;
290
291
self.output_queue.push_back((buffer_id, mapping));
292
self.try_encode()?;
293
Ok(buffer_id)
294
}
295
296
fn flush(&mut self) -> VideoResult<()> {
297
if self.is_flushing {
298
return Err(VideoError::BackendFailure(anyhow!(
299
"flush is already in progress"
300
)));
301
}
302
self.codec_jobs.push_back(CodecJob::Flush);
303
self.try_encode()?;
304
Ok(())
305
}
306
307
fn request_encoding_params_change(
308
&mut self,
309
bitrate: Bitrate,
310
framerate: u32,
311
) -> VideoResult<()> {
312
match bitrate {
313
Bitrate::Cbr { target } => {
314
self.context.set_bit_rate(target as u64);
315
}
316
Bitrate::Vbr { target, peak } => {
317
self.context.set_bit_rate(target as u64);
318
self.context.set_max_bit_rate(peak as u64);
319
}
320
}
321
// TODO(b/241492607): support fractional frame rates.
322
self.context.set_time_base(AVRational {
323
num: 1,
324
den: framerate as c_int,
325
});
326
Ok(())
327
}
328
329
fn event_pipe(&self) -> &dyn AsRawDescriptor {
330
self.event_queue.as_ref()
331
}
332
333
fn read_event(&mut self) -> VideoResult<EncoderEvent> {
334
self.event_queue
335
.dequeue_event()
336
.context("while reading encoder event")
337
.map_err(VideoError::BackendFailure)
338
}
339
}
340
341
pub struct FfmpegEncoder {
342
codecs: BTreeMap<Format, AvCodec>,
343
}
344
345
impl FfmpegEncoder {
346
/// Create a new ffmpeg encoder backend instance.
347
pub fn new() -> Self {
348
// Find all the encoders supported by libav and store them.
349
let codecs = AvCodecIterator::new()
350
.filter_map(|codec| {
351
if !codec.is_encoder() {
352
return None;
353
}
354
355
let codec_name = codec.name();
356
357
// Only retain software encoders we know with their corresponding format. Other
358
// encoder might depend on hardware (e.g. *_qsv) which we can't use.
359
let format = match codec_name {
360
"libx264" => Format::H264,
361
"libvpx" => Format::VP8,
362
"libvpx-vp9" => Format::VP9,
363
"libx265" => Format::Hevc,
364
_ => return None,
365
};
366
367
Some((format, codec))
368
})
369
.collect();
370
371
Self { codecs }
372
}
373
}
374
375
impl Encoder for FfmpegEncoder {
376
type Session = FfmpegEncoderSession;
377
378
fn query_capabilities(&self) -> VideoResult<EncoderCapabilities> {
379
let codecs = &self.codecs;
380
let mut format_idx = BTreeMap::new();
381
let mut input_format_descs = vec![];
382
let output_format_descs = codecs
383
.iter()
384
.enumerate()
385
.map(|(i, (&format, codec))| {
386
let mut in_formats = 0;
387
for in_format in codec.pixel_format_iter() {
388
if let Ok(in_format) = Format::try_from(in_format) {
389
let idx = format_idx.entry(in_format).or_insert_with(|| {
390
let idx = input_format_descs.len();
391
input_format_descs.push(FormatDesc {
392
mask: 0,
393
format: in_format,
394
frame_formats: vec![FrameFormat {
395
// These frame sizes are arbitrary, but avcodec does not seem to
396
// have any specific restriction in that regard (or any way to
397
// query the supported resolutions).
398
width: FormatRange {
399
min: 64,
400
max: 16384,
401
step: 1,
402
},
403
height: FormatRange {
404
min: 64,
405
max: 16384,
406
step: 1,
407
},
408
bitrates: Default::default(),
409
}],
410
plane_align: max_buffer_alignment() as u32,
411
});
412
idx
413
});
414
input_format_descs[*idx].mask |= 1 << i;
415
in_formats |= 1 << *idx;
416
}
417
}
418
FormatDesc {
419
mask: in_formats,
420
format,
421
frame_formats: vec![FrameFormat {
422
// These frame sizes are arbitrary, but avcodec does not seem to have any
423
// specific restriction in that regard (or any way to query the supported
424
// resolutions).
425
width: FormatRange {
426
min: 64,
427
max: 16384,
428
step: 1,
429
},
430
height: FormatRange {
431
min: 64,
432
max: 16384,
433
step: 1,
434
},
435
bitrates: Default::default(),
436
}],
437
plane_align: max_buffer_alignment() as u32,
438
}
439
})
440
.collect();
441
// TODO(ishitatsuyuki): right now we haven't plumbed the profile handling yet and will use
442
// a hard coded set of profiles. Make this support more profiles when
443
// we implement the conversion between virtio and ffmpeg profiles.
444
let coded_format_profiles = codecs
445
.iter()
446
.map(|(&format, _codec)| {
447
(
448
format,
449
match format {
450
Format::H264 => vec![Profile::H264Baseline],
451
Format::Hevc => vec![Profile::HevcMain],
452
Format::VP8 => vec![Profile::VP8Profile0],
453
Format::VP9 => vec![Profile::VP9Profile0],
454
_ => vec![],
455
},
456
)
457
})
458
.collect();
459
let caps = EncoderCapabilities {
460
input_format_descs,
461
output_format_descs,
462
coded_format_profiles,
463
};
464
465
Ok(caps)
466
}
467
468
fn start_session(&mut self, config: SessionConfig) -> VideoResult<Self::Session> {
469
let dst_format = config
470
.dst_params
471
.format
472
.ok_or(VideoError::InvalidOperation)?;
473
let codec = self
474
.codecs
475
.get(&dst_format)
476
.ok_or(VideoError::InvalidFormat)?;
477
let pix_fmt = config
478
.src_params
479
.format
480
.ok_or(VideoError::InvalidOperation)?
481
.try_into()
482
.map_err(|_| VideoError::InvalidFormat)?;
483
let context = codec
484
.build_encoder()
485
.and_then(|mut b| {
486
b.set_pix_fmt(pix_fmt);
487
b.set_dimensions(Dimensions {
488
width: config.src_params.frame_width,
489
height: config.src_params.frame_height,
490
});
491
b.set_time_base(AVRational {
492
num: 1,
493
den: config.frame_rate as _,
494
});
495
b.build()
496
})
497
.context("while creating new session")
498
.map_err(VideoError::BackendFailure)?;
499
let session = FfmpegEncoderSession {
500
event_queue: Arc::new(
501
EventQueue::new()
502
.context("while creating encoder session")
503
.map_err(VideoError::BackendFailure)?
504
.into(),
505
),
506
codec_jobs: Default::default(),
507
output_queue: Default::default(),
508
is_flushing: false,
509
context,
510
next_input_buffer_id: 0,
511
next_output_buffer_id: 0,
512
};
513
session
514
.event_queue
515
.queue_event(EncoderEvent::RequireInputBuffers {
516
input_count: 4,
517
input_frame_height: config.src_params.frame_height,
518
input_frame_width: config.src_params.frame_width,
519
output_buffer_size: 16 * 1024 * 1024,
520
})
521
.context("while sending buffer request")
522
.map_err(VideoError::BackendFailure)?;
523
Ok(session)
524
}
525
526
fn stop_session(&mut self, _session: Self::Session) -> VideoResult<()> {
527
// Just Drop.
528
Ok(())
529
}
530
}
531
532