Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/devices/src/virtio/media/decoder_adapter.rs
5394 views
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
5
//! An adapter from the virtio-media protocol to the video devices originally used with
6
//! virtio-video.
7
//!
8
//! This allows to reuse already-existing crosvm virtio-video devices with
9
//! virtio-media.
10
11
use std::os::fd::BorrowedFd;
12
use std::sync::Arc;
13
14
use base::AsRawDescriptor;
15
use base::SharedMemory;
16
use base::WaitContext;
17
use sync::Mutex;
18
use virtio_media::devices::video_decoder::StreamParams;
19
use virtio_media::devices::video_decoder::VideoDecoderBackend;
20
use virtio_media::devices::video_decoder::VideoDecoderBackendEvent;
21
use virtio_media::devices::video_decoder::VideoDecoderBackendSession;
22
use virtio_media::devices::video_decoder::VideoDecoderBufferBacking;
23
use virtio_media::devices::video_decoder::VideoDecoderSession;
24
use virtio_media::ioctl::IoctlResult;
25
use virtio_media::v4l2r;
26
use virtio_media::v4l2r::bindings;
27
use virtio_media::v4l2r::ioctl::V4l2MplaneFormat;
28
use virtio_media::v4l2r::PixelFormat;
29
use virtio_media::v4l2r::QueueClass;
30
use virtio_media::v4l2r::QueueDirection;
31
use virtio_media::v4l2r::QueueType;
32
33
use crate::virtio::video::decoder::backend::DecoderEvent;
34
use crate::virtio::video::decoder::backend::DecoderSession;
35
use crate::virtio::video::decoder::capability::Capability;
36
use crate::virtio::video::decoder::DecoderBackend;
37
use crate::virtio::video::format::Format;
38
use crate::virtio::video::format::FramePlane;
39
use crate::virtio::video::format::PlaneFormat;
40
use crate::virtio::video::resource::GuestMemArea;
41
use crate::virtio::video::resource::GuestMemHandle;
42
use crate::virtio::video::resource::GuestResource;
43
use crate::virtio::video::resource::GuestResourceHandle;
44
45
/// Use 1 MB input buffers by default. This should probably be resolution-dependent.
46
const INPUT_BUFFER_SIZE: u32 = 1024 * 1024;
47
48
/// Returns the V4L2 `(bytesperline, sizeimage)` for the given `format` and `coded_size`.
49
fn buffer_sizes_for_format(format: Format, coded_size: (u32, u32)) -> (u32, u32) {
50
match PlaneFormat::get_plane_layout(format, coded_size.0, coded_size.1) {
51
None => (0, INPUT_BUFFER_SIZE),
52
Some(layout) => (
53
layout.first().map(|p| p.stride).unwrap_or(0),
54
layout.iter().map(|p| p.plane_size).sum(),
55
),
56
}
57
}
58
59
impl From<Format> for PixelFormat {
60
fn from(format: Format) -> Self {
61
PixelFormat::from_fourcc(match format {
62
Format::NV12 => b"NV12",
63
Format::YUV420 => b"YV12",
64
Format::H264 => b"H264",
65
Format::Hevc => b"HEVC",
66
Format::VP8 => b"VP80",
67
Format::VP9 => b"VP90",
68
})
69
}
70
}
71
72
impl TryFrom<PixelFormat> for Format {
73
type Error = ();
74
75
fn try_from(pixelformat: PixelFormat) -> Result<Self, Self::Error> {
76
match &pixelformat.to_fourcc() {
77
b"NV12" => Ok(Format::NV12),
78
b"YV12" => Ok(Format::YUV420),
79
b"H264" => Ok(Format::H264),
80
b"HEVC" => Ok(Format::Hevc),
81
b"VP80" => Ok(Format::VP8),
82
b"VP90" => Ok(Format::VP9),
83
_ => Err(()),
84
}
85
}
86
}
87
88
pub struct VirtioVideoAdapterBuffer {
89
// Plane backing memory, for MMAP buffers only.
90
shm: Vec<SharedMemory>,
91
// Switched to `true` once the buffer's backing memory has been registered with
92
// `use_output_buffer`.
93
registered: bool,
94
}
95
96
impl VideoDecoderBufferBacking for VirtioVideoAdapterBuffer {
97
fn new(queue: QueueType, index: u32, sizes: &[usize]) -> IoctlResult<Self> {
98
Ok(Self {
99
shm: sizes
100
.iter()
101
.enumerate()
102
.map(|(plane, size)| {
103
SharedMemory::new(
104
format!("virtio_media {:?} {}-{}", queue.direction(), index, plane),
105
*size as u64,
106
)
107
})
108
.collect::<Result<_, base::Error>>()
109
.map_err(|_| libc::ENOMEM)?,
110
registered: false,
111
})
112
}
113
114
fn fd_for_plane(&self, plane_idx: usize) -> Option<BorrowedFd> {
115
self.shm.get(plane_idx).map(|shm|
116
// SAFETY: the shm'd fd is valid and `BorrowedFd` ensures its use won't outlive us.
117
unsafe { BorrowedFd::borrow_raw(shm.descriptor.as_raw_descriptor())})
118
}
119
}
120
121
enum EosBuffer {
122
/// No EOS buffer queued yet and no EOS pending.
123
None,
124
/// EOS buffer is available, and no EOS pending.
125
Available(u32),
126
/// EOS is pending but we have no buffer queued yet.
127
Awaiting,
128
}
129
130
pub struct VirtioVideoAdapterSession<D: DecoderBackend> {
131
/// Session ID.
132
id: u32,
133
134
/// The backend session can only be created once we know the input format.
135
backend_session: Option<D::Session>,
136
137
/// Proxy poller. We need this because this backend creates the actual session with a delay,
138
/// but the device needs the session FD as soon as it is created.
139
poller: WaitContext<u32>,
140
141
/// Shared reference to the device backend. Required so we can create the session lazily.
142
backend: Arc<Mutex<D>>,
143
144
input_format: Format,
145
output_format: Format,
146
/// Coded size currently set for the CAPTURE buffers.
147
coded_size: (u32, u32),
148
/// Stream parameters, as obtained from the stream itself.
149
stream_params: StreamParams,
150
151
/// Whether the initial DRC event has been received. The backend will ignore CAPTURE buffers
152
/// queued before that.
153
initial_drc_received: bool,
154
/// Whether the `set_output_parameters` of the backend needs to be called before a CAPTURE
155
/// buffer is sent.
156
need_set_output_params: bool,
157
158
/// Indices of CAPTURE buffers that are queued but not send to the backend.
159
pending_output_buffers: Vec<(u32, Option<GuestResource>)>,
160
161
/// Index of the capture buffer we kept in order to signal EOS.
162
eos_capture_buffer: EosBuffer,
163
164
/// Number of output buffers that have been allocated, as reported by the decoder device.
165
///
166
/// This is required to call `set_output_parameters` on the virtio-video session.
167
num_output_buffers: usize,
168
}
169
170
impl<D: DecoderBackend> VirtioVideoAdapterSession<D> {
171
/// Get the currently existing backend session if it exists, or create it using the current
172
/// input format if it doesn't.
173
fn get_or_create_session(&mut self) -> IoctlResult<&mut D::Session> {
174
let backend_session = match &mut self.backend_session {
175
Some(session) => session,
176
b @ None => {
177
let backend_session =
178
self.backend
179
.lock()
180
.new_session(self.input_format)
181
.map_err(|e| {
182
base::error!(
183
"{:#}",
184
anyhow::anyhow!("while creating backend session: {:#}", e)
185
);
186
libc::EIO
187
})?;
188
self.poller
189
.add(backend_session.event_pipe(), self.id)
190
.map_err(|e| {
191
base::error!("failed to listen to events of session {}: {:#}", self.id, e);
192
libc::EIO
193
})?;
194
b.get_or_insert(backend_session)
195
}
196
};
197
198
Ok(backend_session)
199
}
200
201
fn try_send_pending_output_buffers(&mut self) -> IoctlResult<()> {
202
// We cannot send output buffers until the initial DRC event is received.
203
if !self.initial_drc_received {
204
return Ok(());
205
}
206
207
let _ = self.get_or_create_session();
208
// Will always succeed before `get_or_create_session` has been called. Ideally we would use
209
// its result directly, but this borrows a mutable reference to `self` which would prevent
210
// other borrows later in this method.
211
let Some(backend_session) = &mut self.backend_session else {
212
unreachable!()
213
};
214
215
// Leave early if we have no pending output buffers. This ensures we only set the
216
// parameters right before the first buffer is queued.
217
if self.pending_output_buffers.is_empty() {
218
return Ok(());
219
}
220
221
// Set the output parameters if this is the first CAPTURE buffer we queue after a
222
// resolution change event.
223
if self.need_set_output_params {
224
let output_format = self.output_format;
225
226
backend_session
227
.set_output_parameters(self.num_output_buffers, output_format)
228
.map_err(|_| libc::EIO)?;
229
self.need_set_output_params = false;
230
}
231
232
for (index, resource) in self.pending_output_buffers.drain(..) {
233
match resource {
234
Some(resource) => backend_session
235
.use_output_buffer(index as i32, resource)
236
.map_err(|_| libc::EIO)?,
237
None => backend_session
238
.reuse_output_buffer(index as i32)
239
.map_err(|_| libc::EIO)?,
240
}
241
}
242
243
Ok(())
244
}
245
}
246
247
impl<D: DecoderBackend> VideoDecoderBackendSession for VirtioVideoAdapterSession<D> {
248
type BufferStorage = VirtioVideoAdapterBuffer;
249
250
fn current_format(&self, direction: QueueDirection) -> V4l2MplaneFormat {
251
let format = match direction {
252
QueueDirection::Output => self.input_format,
253
QueueDirection::Capture => self.output_format,
254
};
255
let (bytesperline, sizeimage) = buffer_sizes_for_format(format, self.coded_size);
256
257
let mut plane_fmt: [bindings::v4l2_plane_pix_format; bindings::VIDEO_MAX_PLANES as usize] =
258
Default::default();
259
plane_fmt[0] = bindings::v4l2_plane_pix_format {
260
bytesperline,
261
sizeimage,
262
reserved: Default::default(),
263
};
264
265
let pix_mp = bindings::v4l2_pix_format_mplane {
266
width: self.coded_size.0,
267
height: self.coded_size.1,
268
pixelformat: PixelFormat::from(format).to_u32(),
269
field: bindings::v4l2_field_V4L2_FIELD_NONE,
270
plane_fmt,
271
num_planes: 1,
272
flags: 0,
273
..Default::default()
274
};
275
276
V4l2MplaneFormat::from((direction, pix_mp))
277
}
278
279
fn stream_params(&self) -> StreamParams {
280
self.stream_params.clone()
281
}
282
283
fn drain(&mut self) -> IoctlResult<()> {
284
if let Some(backend_session) = &mut self.backend_session {
285
backend_session.flush().map_err(|_| libc::EIO)
286
} else {
287
Ok(())
288
}
289
}
290
291
fn clear_output_buffers(&mut self) -> IoctlResult<()> {
292
self.pending_output_buffers.clear();
293
self.eos_capture_buffer = EosBuffer::None;
294
if let Some(session) = &mut self.backend_session {
295
session.clear_output_buffers().map_err(|_| libc::EIO)
296
} else {
297
Ok(())
298
}
299
}
300
301
fn next_event(&mut self) -> Option<VideoDecoderBackendEvent> {
302
if let Some(backend_session) = &mut self.backend_session {
303
let event = backend_session.read_event().unwrap();
304
305
match event {
306
DecoderEvent::NotifyEndOfBitstreamBuffer(id) => {
307
Some(VideoDecoderBackendEvent::InputBufferDone {
308
buffer_id: id,
309
error: 0,
310
})
311
}
312
DecoderEvent::ProvidePictureBuffers {
313
min_num_buffers,
314
width,
315
height,
316
visible_rect,
317
} => {
318
self.stream_params = StreamParams {
319
// Add one extra buffer to keep one on the side in order to signal EOS.
320
min_output_buffers: min_num_buffers + 1,
321
coded_size: (width as u32, height as u32),
322
visible_rect: v4l2r::Rect {
323
left: visible_rect.left,
324
top: visible_rect.top,
325
width: visible_rect.right.saturating_sub(visible_rect.left) as u32,
326
height: visible_rect.bottom.saturating_sub(visible_rect.top) as u32,
327
},
328
};
329
self.coded_size = self.stream_params.coded_size;
330
self.need_set_output_params = true;
331
self.initial_drc_received = true;
332
333
// We can queue pending picture buffers now.
334
if self.try_send_pending_output_buffers().is_err() {
335
base::error!(
336
"failed to send pending output buffers after resolution change event"
337
);
338
}
339
340
Some(VideoDecoderBackendEvent::StreamFormatChanged)
341
}
342
DecoderEvent::PictureReady {
343
picture_buffer_id,
344
timestamp,
345
..
346
} => {
347
let timestamp = bindings::timeval {
348
tv_sec: (timestamp / 1_000_000) as i64,
349
tv_usec: (timestamp % 1_000_000) as i64,
350
};
351
let bytes_used = buffer_sizes_for_format(self.output_format, self.coded_size).1;
352
353
Some(VideoDecoderBackendEvent::FrameCompleted {
354
buffer_id: picture_buffer_id as u32,
355
timestamp,
356
bytes_used: vec![bytes_used],
357
is_last: false,
358
})
359
}
360
DecoderEvent::FlushCompleted(res) => {
361
if let Err(err) = res {
362
base::error!("flush command failed: {:#}", err);
363
}
364
365
// Regardless of the result, the flush has completed.
366
// Use the buffer we kept aside for signaling EOS, or wait until the next
367
// buffer is queued so we can use it for that purpose.
368
match self.eos_capture_buffer {
369
EosBuffer::Available(buffer_id) => {
370
Some(VideoDecoderBackendEvent::FrameCompleted {
371
buffer_id,
372
timestamp: Default::default(),
373
bytes_used: vec![0],
374
is_last: true,
375
})
376
}
377
_ => {
378
self.eos_capture_buffer = EosBuffer::Awaiting;
379
None
380
}
381
}
382
}
383
DecoderEvent::ResetCompleted(_) => todo!(),
384
DecoderEvent::NotifyError(_) => todo!(),
385
}
386
} else {
387
None
388
}
389
}
390
391
fn buffers_allocated(&mut self, direction: QueueDirection, num_buffers: u32) {
392
if matches!(direction, QueueDirection::Capture) {
393
self.num_output_buffers = num_buffers as usize;
394
}
395
}
396
397
fn poll_fd(&self) -> Option<BorrowedFd> {
398
// SAFETY: safe because WaitContext has a valid FD and BorrowedFd ensures its use doesn't
399
// outlive us.
400
Some(unsafe { BorrowedFd::borrow_raw(self.poller.as_raw_descriptor()) })
401
}
402
403
fn decode(
404
&mut self,
405
input: &Self::BufferStorage,
406
index: u32,
407
timestamp: bindings::timeval,
408
bytes_used: u32,
409
) -> IoctlResult<()> {
410
let backend_session = self.get_or_create_session()?;
411
let timestamp = timestamp.tv_sec as u64 * 1_000_000 + timestamp.tv_usec as u64;
412
let resource = GuestResourceHandle::GuestPages(GuestMemHandle {
413
desc: input.shm[0]
414
.descriptor
415
.try_clone()
416
.map_err(|_| libc::ENOMEM)?,
417
mem_areas: vec![GuestMemArea {
418
offset: 0,
419
length: input.shm[0].size as usize,
420
}],
421
});
422
423
backend_session
424
.decode(index, timestamp, resource, 0, bytes_used)
425
.map_err(|_| libc::EIO)
426
}
427
428
fn use_as_output(&mut self, index: u32, backing: &mut Self::BufferStorage) -> IoctlResult<()> {
429
match self.eos_capture_buffer {
430
EosBuffer::None => {
431
self.eos_capture_buffer = EosBuffer::Available(index);
432
Ok(())
433
}
434
EosBuffer::Awaiting => {
435
// TODO: mmm how do we do that?
436
// Answer: we don't! We just return a buffer event with the LAST flag set, and the
437
// higher-level event handler takes care of sending eos!
438
//
439
// ... and how do we trigger the event for that buffer?
440
//
441
// self.send_eos(self, v4l2_buffer.index());
442
Ok(())
443
}
444
EosBuffer::Available(_) => {
445
let resource = if !backing.registered {
446
let resource = GuestResourceHandle::GuestPages(GuestMemHandle {
447
desc: backing.shm[0]
448
.descriptor
449
.try_clone()
450
.map_err(|_| libc::ENOMEM)?,
451
mem_areas: vec![GuestMemArea {
452
offset: 0,
453
length: backing.shm[0].size as usize,
454
}],
455
});
456
457
let plane_formats = PlaneFormat::get_plane_layout(
458
self.output_format,
459
self.coded_size.0,
460
self.coded_size.1,
461
)
462
.ok_or_else(|| {
463
base::error!("could not obtain plane layout for output buffer");
464
libc::EINVAL
465
})?;
466
467
let mut buffer_offset = 0;
468
let resource_handle = GuestResource {
469
handle: resource,
470
planes: plane_formats
471
.into_iter()
472
.map(|p| {
473
let plane_offset = buffer_offset;
474
buffer_offset += p.plane_size;
475
476
FramePlane {
477
offset: plane_offset as usize,
478
stride: p.stride as usize,
479
size: p.plane_size as usize,
480
}
481
})
482
.collect(),
483
width: self.coded_size.0,
484
height: self.coded_size.1,
485
format: self.output_format,
486
guest_cpu_mappable: false,
487
};
488
489
backing.registered = true;
490
491
Some(resource_handle)
492
} else {
493
None
494
};
495
496
self.pending_output_buffers.push((index, resource));
497
498
self.try_send_pending_output_buffers()
499
}
500
}
501
}
502
}
503
504
pub struct VirtioVideoAdapter<D: DecoderBackend> {
505
backend: Arc<Mutex<D>>,
506
capability: Capability,
507
}
508
509
impl<D: DecoderBackend> VirtioVideoAdapter<D> {
510
pub fn new(backend: D) -> Self {
511
let capability = backend.get_capabilities();
512
Self {
513
backend: Arc::new(Mutex::new(backend)),
514
capability,
515
}
516
}
517
}
518
519
impl<D: DecoderBackend> VideoDecoderBackend for VirtioVideoAdapter<D> {
520
type Session = VirtioVideoAdapterSession<D>;
521
522
fn new_session(&mut self, id: u32) -> IoctlResult<Self::Session> {
523
let first_input_format = self
524
.capability
525
.input_formats()
526
.first()
527
.ok_or(libc::ENODEV)?;
528
let first_output_format = self
529
.capability
530
.output_formats()
531
.first()
532
.ok_or(libc::ENODEV)?;
533
534
let coded_size = first_input_format
535
.frame_formats
536
.first()
537
.map(|f| (f.width.min, f.height.min))
538
.unwrap_or((0, 0));
539
540
Ok(VirtioVideoAdapterSession {
541
id,
542
backend_session: None,
543
poller: WaitContext::new().map_err(|_| libc::EIO)?,
544
backend: Arc::clone(&self.backend),
545
input_format: first_input_format.format,
546
coded_size,
547
output_format: first_output_format.format,
548
stream_params: StreamParams {
549
min_output_buffers: 1,
550
coded_size,
551
visible_rect: v4l2r::Rect {
552
left: 0,
553
top: 0,
554
width: coded_size.0,
555
height: coded_size.1,
556
},
557
},
558
initial_drc_received: false,
559
need_set_output_params: false,
560
pending_output_buffers: Default::default(),
561
num_output_buffers: 0,
562
eos_capture_buffer: EosBuffer::None,
563
})
564
}
565
566
fn close_session(&mut self, _session: Self::Session) {}
567
568
fn enum_formats(
569
&self,
570
_session: &VideoDecoderSession<Self::Session>,
571
direction: QueueDirection,
572
index: u32,
573
) -> Option<bindings::v4l2_fmtdesc> {
574
let formats = match direction {
575
QueueDirection::Output => self.capability.input_formats(),
576
QueueDirection::Capture => self.capability.output_formats(),
577
};
578
let fmt = formats.get(index as usize)?;
579
580
Some(bindings::v4l2_fmtdesc {
581
index,
582
type_: QueueType::from_dir_and_class(direction, QueueClass::VideoMplane) as u32,
583
pixelformat: PixelFormat::from(fmt.format).to_u32(),
584
..Default::default()
585
})
586
}
587
588
fn frame_sizes(&self, pixel_format: u32) -> Option<bindings::v4l2_frmsize_stepwise> {
589
let format = Format::try_from(PixelFormat::from_u32(pixel_format)).ok()?;
590
let frame_sizes = self
591
.capability
592
.input_formats()
593
.iter()
594
.chain(self.capability.output_formats().iter())
595
.find(|f| f.format == format)
596
.and_then(|f| f.frame_formats.first())?;
597
598
Some(bindings::v4l2_frmsize_stepwise {
599
min_width: frame_sizes.width.min,
600
max_width: frame_sizes.width.max,
601
step_width: frame_sizes.width.step,
602
min_height: frame_sizes.height.min,
603
max_height: frame_sizes.height.max,
604
step_height: frame_sizes.height.step,
605
})
606
}
607
608
fn adjust_format(
609
&self,
610
session: &Self::Session,
611
direction: QueueDirection,
612
format: V4l2MplaneFormat,
613
) -> V4l2MplaneFormat {
614
let pix_mp: &bindings::v4l2_pix_format_mplane = format.as_ref();
615
616
let available_formats = match direction {
617
QueueDirection::Output => self.capability.input_formats(),
618
QueueDirection::Capture => self.capability.output_formats(),
619
};
620
621
let pixel_format = Format::try_from(PixelFormat::from_u32(pix_mp.pixelformat)).ok();
622
623
// If the received pixel format is valid, find the format in our capabilities that matches,
624
// otherwise fall back to the first format.
625
let Some(matching_format) = pixel_format
626
.and_then(|format| available_formats.iter().find(|f| f.format == format))
627
.or_else(|| available_formats.first())
628
else {
629
// There are no formats defined at all for the device, so return a bogus one like a
630
// V4L2 device would do.
631
return V4l2MplaneFormat::from((direction, Default::default()));
632
};
633
634
// Now check that the requested resolution is within the supported range, or fallback to
635
// an arbitrary one.
636
let (mut width, mut height) = matching_format
637
.frame_formats
638
.iter()
639
.find(|format| {
640
let width = pix_mp.width;
641
let height = pix_mp.height;
642
(format.width.min..format.width.max).contains(&width)
643
&& (format.height.min..format.height.max).contains(&height)
644
})
645
.map(|_| (pix_mp.width, pix_mp.height))
646
.unwrap_or_else(|| {
647
matching_format
648
.frame_formats
649
.first()
650
.map(|format| {
651
(
652
std::cmp::min(format.width.max, pix_mp.width),
653
(std::cmp::min(format.height.max, pix_mp.height)),
654
)
655
})
656
.unwrap_or((0, 0))
657
});
658
659
// CAPTURE resolution cannot be lower than OUTPUT one.
660
if direction == QueueDirection::Capture {
661
width = std::cmp::max(width, session.coded_size.0);
662
height = std::cmp::max(height, session.coded_size.1);
663
}
664
665
// We only support one plane per buffer for now.
666
let num_planes = 1;
667
let (bytesperline, sizeimage) =
668
buffer_sizes_for_format(matching_format.format, (width, height));
669
let mut plane_fmt: [bindings::v4l2_plane_pix_format; 8] = Default::default();
670
plane_fmt[0] = bindings::v4l2_plane_pix_format {
671
bytesperline,
672
sizeimage,
673
reserved: Default::default(),
674
};
675
676
V4l2MplaneFormat::from((
677
direction,
678
bindings::v4l2_pix_format_mplane {
679
width,
680
height,
681
pixelformat: PixelFormat::from(matching_format.format).to_u32(),
682
field: bindings::v4l2_field_V4L2_FIELD_NONE,
683
plane_fmt,
684
num_planes,
685
flags: 0,
686
..Default::default()
687
},
688
))
689
}
690
691
fn apply_format(
692
&self,
693
session: &mut Self::Session,
694
direction: QueueDirection,
695
format: &V4l2MplaneFormat,
696
) {
697
match direction {
698
QueueDirection::Output => {
699
session.input_format =
700
Format::try_from(format.pixelformat()).unwrap_or_else(|_| {
701
self.capability
702
.input_formats()
703
.first()
704
.map(|f| f.format)
705
.unwrap_or(Format::H264)
706
});
707
let coded_size = format.size();
708
// Setting the resolution manually affects the stream parameters.
709
if coded_size != (0, 0) {
710
let coded_size = format.size();
711
session.coded_size = coded_size;
712
}
713
}
714
QueueDirection::Capture => {
715
session.output_format =
716
Format::try_from(format.pixelformat()).unwrap_or_else(|_| {
717
self.capability
718
.output_formats()
719
.first()
720
.map(|f| f.format)
721
.unwrap_or(Format::NV12)
722
});
723
}
724
}
725
}
726
}
727
728