Skip to content

Commit db8c8bd

Browse files
committed
Implement custom lens_facing control for emulated_camera_mplane.
Bug: b/502639876 Test: cvd create --media="type=v4l2_emulated_camera_mplane,lens_facing=FRONT" && adb shell "su 0 v4l2-ctl -d /dev/video1 --list-ctrls"
1 parent ed0dd19 commit db8c8bd

2 files changed

Lines changed: 189 additions & 4 deletions

File tree

  • base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_mplane/src

base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_mplane/src/device.rs

Lines changed: 173 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ use v4l2r::bindings::v4l2_requestbuffers;
3030
use v4l2r::ioctl::BufferCapabilities;
3131
use v4l2r::ioctl::BufferField;
3232
use v4l2r::ioctl::BufferFlags;
33+
use v4l2r::ioctl::CtrlId;
34+
use v4l2r::ioctl::CtrlWhich;
35+
use v4l2r::ioctl::EventType as V4l2EventType;
36+
use v4l2r::ioctl::QueryCtrlFlags;
37+
use v4l2r::ioctl::SubscribeEventFlags;
3338
use v4l2r::ioctl::V4l2Buffer;
3439
use v4l2r::ioctl::V4l2PlanesWithBackingMut;
3540
use v4l2r::memory::MemoryType;
@@ -45,10 +50,33 @@ use virtio_media::ioctl::virtio_media_dispatch_ioctl;
4550
use virtio_media::memfd::MemFdBuffer;
4651
use virtio_media::mmap::MmapMappingManager;
4752
use virtio_media::protocol::DequeueBufferEvent;
53+
use virtio_media::protocol::SessionEvent;
4854
use virtio_media::protocol::SgEntry;
4955
use virtio_media::protocol::V4l2Event;
5056
use virtio_media::protocol::V4l2Ioctl;
5157
use virtio_media::protocol::VIRTIO_MEDIA_MMAP_FLAG_RW;
58+
use std::str::FromStr;
59+
60+
/// https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#LENS_FACING_FRONT
61+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62+
pub enum LensFacing {
63+
Front = 0,
64+
Back = 1,
65+
External = 2,
66+
}
67+
68+
impl FromStr for LensFacing {
69+
type Err = String;
70+
71+
fn from_str(s: &str) -> Result<Self, Self::Err> {
72+
match s {
73+
"FRONT" => Ok(LensFacing::Front),
74+
"BACK" => Ok(LensFacing::Back),
75+
"EXTERNAL" => Ok(LensFacing::External),
76+
_ => Err(format!("Invalid lens facing: {}. Expected FRONT, BACK, or EXTERNAL", s)),
77+
}
78+
}
79+
}
5280

5381
/// Current status of a buffer.
5482
#[derive(Debug, PartialEq, Eq)]
@@ -243,18 +271,40 @@ pub struct EmulatedCamera<Q: VirtioMediaEventQueue, HM: VirtioMediaHostMemoryMap
243271
/// same time. It will fails if we allow simultaneous sessions to be active, so we need this
244272
/// artificial limitation to make it pass fully.
245273
active_session: Option<u32>,
274+
/// Lens facing configuration.
275+
lens_facing: LensFacing,
246276
}
247277

248278
impl<Q, HM> EmulatedCamera<Q, HM>
249279
where
250280
Q: VirtioMediaEventQueue,
251281
HM: VirtioMediaHostMemoryMapper,
252282
{
253-
pub fn new(evt_queue: Q, mapper: HM) -> Self {
283+
pub fn new(evt_queue: Q, mapper: HM, lens_facing: LensFacing) -> Self {
254284
Self {
255285
evt_queue,
256286
mmap_manager: MmapMappingManager::from(mapper),
257287
active_session: None,
288+
lens_facing,
289+
}
290+
}
291+
292+
fn lens_facing_query_ext_ctrl(&self) -> bindings::v4l2_query_ext_ctrl {
293+
let name_str = "LENS_FACING";
294+
let mut name = [0u8; 32];
295+
name[0..name_str.len()].copy_from_slice(name_str.as_bytes());
296+
bindings::v4l2_query_ext_ctrl {
297+
id: CID_LENS_FACING,
298+
type_: bindings::v4l2_ctrl_type_V4L2_CTRL_TYPE_INTEGER,
299+
name: name.map(|b| b as i8),
300+
minimum: LensFacing::Front as i64,
301+
maximum: LensFacing::External as i64,
302+
step: 1,
303+
default_value: self.lens_facing as i64,
304+
flags: bindings::V4L2_CTRL_FLAG_READ_ONLY,
305+
elems: 1,
306+
elem_size: std::mem::size_of::<u32>() as u32,
307+
..Default::default()
258308
}
259309
}
260310
}
@@ -337,6 +387,10 @@ where
337387
}
338388
}
339389

390+
// Use an offset for virtio-media custom camera class control id values.
391+
const CID_OFFSET: u32 = bindings::V4L2_CID_CAMERA_CLASS_BASE + 0x100;
392+
const CID_LENS_FACING: u32 = CID_OFFSET + 1;
393+
340394
const PIXELFORMAT: u32 = PixelFormat::from_fourcc(b"YM12").to_u32();
341395
const WIDTH: u32 = 640;
342396
const HEIGHT: u32 = 480;
@@ -776,4 +830,122 @@ where
776830
..Default::default()
777831
})
778832
}
833+
834+
/// https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-queryctrl.html#control-flags
835+
fn query_ext_ctrl(
836+
&mut self,
837+
_session: &Self::Session,
838+
id: CtrlId,
839+
flags: QueryCtrlFlags,
840+
) -> IoctlResult<bindings::v4l2_query_ext_ctrl> {
841+
let id: u32 = unsafe { std::mem::transmute(id) };
842+
// If V4L2_CTRL_FLAG_NEXT_CTRL present returns the first control with a higher ID.
843+
if flags.contains(QueryCtrlFlags::NEXT) {
844+
if id < CID_LENS_FACING {
845+
return Ok(self.lens_facing_query_ext_ctrl());
846+
}
847+
} else if id == CID_LENS_FACING {
848+
return Ok(self.lens_facing_query_ext_ctrl());
849+
}
850+
return Err(libc::EINVAL);
851+
}
852+
853+
fn g_ext_ctrls(
854+
&mut self,
855+
_session: &Self::Session,
856+
_which: CtrlWhich,
857+
ctrls: &mut bindings::v4l2_ext_controls,
858+
ctrl_array: &mut Vec<bindings::v4l2_ext_control>,
859+
_user_regions: Vec<Vec<SgEntry>>,
860+
) -> IoctlResult<()> {
861+
for ctrl in ctrl_array {
862+
match ctrl.id {
863+
CID_LENS_FACING => {
864+
ctrl.__bindgen_anon_1.value = self.lens_facing as i32;
865+
}
866+
_ => {
867+
ctrls.error_idx = ctrls.count;
868+
return Err(libc::EINVAL);
869+
}
870+
}
871+
}
872+
Ok(())
873+
}
874+
875+
fn try_ext_ctrls(
876+
&mut self,
877+
_session: &Self::Session,
878+
_which: CtrlWhich,
879+
ctrls: &mut bindings::v4l2_ext_controls,
880+
ctrl_array: &mut Vec<bindings::v4l2_ext_control>,
881+
_user_regions: Vec<Vec<SgEntry>>,
882+
) -> IoctlResult<()> {
883+
for (idx, ctrl) in ctrl_array.iter_mut().enumerate() {
884+
ctrls.error_idx = idx as u32;
885+
let err_code = match ctrl.id {
886+
CID_LENS_FACING => libc::EACCES,
887+
_ => libc::EINVAL,
888+
};
889+
return Err(err_code);
890+
}
891+
Ok(())
892+
}
893+
894+
fn s_ext_ctrls(
895+
&mut self,
896+
_session: &mut Self::Session,
897+
_which: CtrlWhich,
898+
ctrls: &mut bindings::v4l2_ext_controls,
899+
ctrl_array: &mut Vec<bindings::v4l2_ext_control>,
900+
_user_regions: Vec<Vec<SgEntry>>,
901+
) -> IoctlResult<()> {
902+
for ctrl in ctrl_array {
903+
ctrls.error_idx = ctrls.count;
904+
let err_code = match ctrl.id {
905+
CID_LENS_FACING => libc::EACCES,
906+
_ => libc::EINVAL,
907+
};
908+
return Err(err_code);
909+
}
910+
Ok(())
911+
}
912+
913+
fn subscribe_event(
914+
&mut self,
915+
session: &mut Self::Session,
916+
event: V4l2EventType,
917+
flags: SubscribeEventFlags,
918+
) -> IoctlResult<()> {
919+
if !flags.contains(SubscribeEventFlags::SEND_INITIAL) {
920+
return Err(libc::EINVAL);
921+
}
922+
match event {
923+
V4l2EventType::Ctrl(id) => match id {
924+
CID_LENS_FACING => {
925+
let ctrl_event = bindings::v4l2_event {
926+
type_: bindings::V4L2_EVENT_CTRL,
927+
id: CID_LENS_FACING,
928+
..Default::default()
929+
};
930+
self.evt_queue
931+
.send_event(V4l2Event::Event(SessionEvent::new(session.id, ctrl_event)));
932+
Ok(())
933+
}
934+
_ => Err(libc::EINVAL),
935+
},
936+
_ => Err(libc::EINVAL),
937+
}
938+
}
939+
940+
fn unsubscribe_event(
941+
&mut self,
942+
_session: &mut Self::Session,
943+
event: bindings::v4l2_event_subscription,
944+
) -> IoctlResult<()> {
945+
return if event.type_ == bindings::V4L2_EVENT_CTRL && event.id == CID_LENS_FACING {
946+
Ok(())
947+
} else {
948+
Err(libc::EINVAL)
949+
};
950+
}
779951
}

base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_mplane/src/main.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@ use virtio_media::protocol::VirtioMediaDeviceConfig;
2424
use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap};
2525

2626
mod device;
27+
use device::LensFacing;
2728

2829
#[derive(Debug, Error)]
2930
pub(crate) enum Error {
31+
#[error("Invalid argument: {0}")]
32+
InvalidArgument(String),
3033
#[error("Could not create daemon: {0}")]
3134
CouldNotCreateDaemon(vhost_user_backend::Error),
3235
#[error("Fatal error: {0}")]
@@ -44,19 +47,26 @@ struct CmdLineArgs {
4447
/// Log verbosity, one of Off, Error, Warning, Info, Debug, Trace.
4548
#[clap(short, long, default_value_t = log::LevelFilter::Debug)]
4649
verbosity: log::LevelFilter,
50+
/// Lens facing configuration: FRONT, BACK, or EXTERNAL.
51+
#[clap(long, value_name = "LENS_FACING", default_value = "BACK")]
52+
lens_facing: String,
4753
}
4854

4955
#[derive(PartialEq, Debug)]
5056
struct Config {
5157
socket_path: PathBuf,
58+
lens_facing: LensFacing,
5259
}
5360

5461
impl TryFrom<CmdLineArgs> for Config {
5562
type Error = Error;
5663

5764
fn try_from(args: CmdLineArgs) -> Result<Self> {
65+
let lens_facing = args.lens_facing.parse::<LensFacing>()
66+
.map_err(Error::InvalidArgument)?;
5867
Ok(Config {
5968
socket_path: args.socket_path,
69+
lens_facing,
6070
})
6171
}
6272
}
@@ -80,14 +90,17 @@ fn start_backend(config: Config) -> Result<()> {
8090
// across VMs restarts rather than having to manually start the binary again.
8191
loop {
8292
use virtio_media::v4l2r::ioctl::Capabilities;
83-
let config = VirtioMediaDeviceConfig {
93+
let device_config = VirtioMediaDeviceConfig {
8494
device_caps: (Capabilities::VIDEO_CAPTURE_MPLANE | Capabilities::STREAMING).bits(),
8595
device_type: VFL_TYPE_VIDEO,
8696
card,
8797
};
98+
let lens_facing = config.lens_facing;
8899
let backend = Arc::new(RwLock::new(VhuMediaBackend::new(
89-
config,
90-
|event_queue, host_mapper| crate::device::EmulatedCamera::new(event_queue, host_mapper),
100+
device_config,
101+
move |event_queue, host_mapper| {
102+
crate::device::EmulatedCamera::new(event_queue, host_mapper, lens_facing)
103+
},
91104
)));
92105
let mut daemon = VhostUserDaemon::new(
93106
String::from("vhost-user-media-backend"),

0 commit comments

Comments
 (0)