#[allow(non_camel_case_types)]
#[allow(non_snake_case)]
pub mod sys;
use bitflags::bitflags;
use const_enum::const_enum_camel;
use sys::media_pad_desc;
use sys::media_v2_intf_devnode;
use std::ffi::CStr;
use std::fmt;
use std::mem;
use std::io;
use std::os::fd::RawFd;
use std::str;
use sys::{media_v2_entity, media_v2_link, media_v2_pad, media_v2_topology, media_v2_interface, media_link_desc};
macro_rules! id_debug {
($newtype:ident) => {
impl std::fmt::Debug for $newtype {
fn fmt(&self, formatter: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
formatter.write_fmt(format_args!("{}({})", stringify!($newtype), self.0))
}
}
};
}
pub trait Zeroed where Self: Sized {
fn zeroed() -> Self {
unsafe { mem::zeroed() }
}
}
#[repr(C)]
#[repr(packed)]
pub struct MediaDeviceInfo {
driver_name: [u8; 16],
device_name: [u8; 32],
serial_number: [u8; 40],
bus_info: [u8; 32],
media_version: u32,
hardware_device_version: u32,
driver_version: u32,
reserved: [u32; 31],
}
impl Zeroed for MediaDeviceInfo {}
impl fmt::Debug for MediaDeviceInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MediaDeviceInfo")
.field("driver", &self.get_driver())
.field("device", &self.get_device())
.finish()
}
}
fn str_from_slice(s: &[u8]) -> &str {
let full = str::from_utf8(s).unwrap();
let end_byte = full.char_indices()
.find(|(_i, c)| *c == '\0')
.map(|(i, _c)| i)
.unwrap_or(full.len());
full.split_at(end_byte).0
}
impl MediaDeviceInfo {
pub fn get_driver(&self) -> &str {
str_from_slice(&self.driver_name)
}
pub fn get_device(&self) -> &str {
str_from_slice(&self.device_name)
}
}
nix::ioctl_readwrite!(media_ioc_device_info, b'|', 0x00, MediaDeviceInfo);
impl Zeroed for media_v2_topology {}
impl media_v2_topology {
pub fn set_entities(&mut self, entities: &mut Vec<media_v2_entity>) {
self.num_entities = entities.len() as u32;
self.ptr_entities = entities.as_mut_ptr() as u64;
}
pub fn set_links(&mut self, links: &mut Vec<media_v2_link>) {
self.num_links = links.len() as u32;
self.ptr_links = links.as_mut_ptr() as u64;
}
pub fn set_interfaces(&mut self, arr: &mut Vec<media_v2_interface>) {
self.num_interfaces = arr.len() as u32;
self.ptr_interfaces = arr.as_mut_ptr() as u64;
}
pub fn set_pads(&mut self, arr: &mut Vec<media_v2_pad>) {
self.num_pads = arr.len() as u32;
self.ptr_pads = arr.as_mut_ptr() as u64;
}
}
#[derive(PartialEq, Eq, Clone, Copy, Hash)]
pub struct EntityId(pub u32);
id_debug!(EntityId);
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum EntityName {
Text(String),
Bytes([::std::os::raw::c_char; 64usize]),
}
const_enum_camel! {
enum MediaEntF, sys, MEDIA_ENT_F_ {
UNKNOWN,
V4L2_SUBDEV_UNKNOWN,
DTV_DEMOD,
TS_DEMUX,
DTV_CA,
DTV_NET_DECAP,
IO_V4L,
IO_DTV,
IO_VBI,
IO_SWRADIO,
CAM_SENSOR,
FLASH,
LENS,
TUNER,
IF_VID_DECODER,
IF_AUD_DECODER,
AUDIO_CAPTURE,
AUDIO_PLAYBACK,
AUDIO_MIXER,
PROC_VIDEO_COMPOSER,
PROC_VIDEO_PIXEL_FORMATTER,
PROC_VIDEO_PIXEL_ENC_CONV,
PROC_VIDEO_LUT,
PROC_VIDEO_SCALER,
PROC_VIDEO_STATISTICS,
PROC_VIDEO_ENCODER,
PROC_VIDEO_DECODER,
PROC_VIDEO_ISP,
VID_MUX,
VID_IF_BRIDGE,
ATV_DECODER,
DV_DECODER,
DV_ENCODER,
}
}
bitflags! {
#[derive(Debug, Clone, Copy)]
pub struct MediaEntFl: u32 {
const DEFAULT = sys::MEDIA_ENT_FL_DEFAULT;
const CONNECTOR = sys::MEDIA_ENT_FL_CONNECTOR;
}
}
#[derive(Debug, Clone)]
pub struct MediaV2Entity {
pub id: EntityId,
pub name: EntityName,
pub function: MediaEntF,
pub flags: MediaEntFl,
}
impl From<media_v2_entity> for MediaV2Entity {
fn from(v: media_v2_entity) -> Self {
let name: &[u8; 64] = unsafe { mem::transmute(&v.name) };
let name = CStr::from_bytes_until_nul(name).ok()
.and_then(|name| name.to_str().ok())
.map(String::from)
.ok_or_else(|| v.name.clone());
Self {
id: EntityId(v.id),
name: match name {
Ok(n) => EntityName::Text(n),
Err(b) => EntityName::Bytes(b),
},
function: v.function.into(),
flags: MediaEntFl::from_bits_retain(v.flags),
}
}
}
impl Zeroed for media_v2_entity {}
#[derive(PartialEq, Eq, Clone, Copy)]
pub struct InterfaceId(pub u32);
id_debug!(InterfaceId);
const_enum_camel! {
enum MediaIntfT, sys, MEDIA_INTF_T_ {
DVB_FE,
DVB_DEMUX,
DVB_DVR,
DVB_CA,
DVB_NET,
V4L_VIDEO,
V4L_VBI,
V4L_RADIO,
V4L_SUBDEV,
V4L_SWRADIO,
V4L_TOUCH,
ALSA_PCM_CAPTURE,
ALSA_PCM_PLAYBACK,
ALSA_CONTROL,
}
}
#[derive(Debug, Clone)]
pub struct MediaV2Interface {
pub id: InterfaceId,
pub intf_type: MediaIntfT,
pub flags: u32,
pub devnode: media_v2_intf_devnode,
}
impl From<&media_v2_interface> for MediaV2Interface {
fn from(v: &media_v2_interface) -> Self {
MediaV2Interface {
id: InterfaceId(v.id),
intf_type: v.intf_type.into(),
flags: v.flags,
devnode: unsafe { v.__bindgen_anon_1.devnode },
}
}
}
impl Zeroed for media_v2_interface {}
#[repr(C)]
#[repr(packed)]
#[derive(Clone, Copy, Debug)]
pub struct MediaV2IntfDevnode {
pub major: u32,
pub minor: u32,
}
#[derive(Debug, Clone)]
pub struct MediaV2Link {
pub id: u32,
pub flags: MediaLnkFl,
pub connection: MediaLinkType,
}
#[derive(Debug, Clone)]
pub enum MediaLinkType {
Data {
source: PadId,
sink: PadId,
},
Interface {
interface: InterfaceId,
entity: EntityId,
},
Ancillary {
source: EntityId,
sink: EntityId,
},
Other {
typ: u32,
source: u32,
sink: u32
},
}
bitflags! {
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct MediaLnkFl: u32 {
const ENABLED = sys::MEDIA_LNK_FL_ENABLED;
const IMMUTABLE = sys::MEDIA_LNK_FL_IMMUTABLE;
const DYNAMIC = sys::MEDIA_LNK_FL_DYNAMIC;
}
}
impl From<media_v2_link> for MediaV2Link {
fn from(v: media_v2_link) ->Self {
Self {
id: v.id,
flags: MediaLnkFl::from_bits_retain(v.flags & !sys::MEDIA_LNK_FL_LINK_TYPE),
connection: match v.flags & sys::MEDIA_LNK_FL_LINK_TYPE {
sys::MEDIA_LNK_FL_DATA_LINK => MediaLinkType::Data {
source: PadId(v.source_id),
sink: PadId(v.sink_id),
},
sys::MEDIA_LNK_FL_INTERFACE_LINK => MediaLinkType::Interface {
interface: InterfaceId(v.source_id),
entity: EntityId(v.sink_id),
},
sys::MEDIA_LNK_FL_ANCILLARY_LINK => MediaLinkType::Ancillary {
source: EntityId(v.source_id),
sink: EntityId(v.sink_id),
},
other => MediaLinkType::Other {
typ: other,
source: v.source_id,
sink: v.sink_id,
}
},
}
}
}
impl Zeroed for media_v2_link {}
#[derive(PartialEq, Eq, Clone, Copy, Hash)]
pub struct PadId(pub u32);
id_debug!(PadId);
#[derive(Debug, Copy, Clone)]
pub struct MediaV2Pad {
pub id: PadId,
pub entity_id: EntityId,
pub role: PadRole,
pub must_connect: bool,
pub index: u32,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum PadRole {
Sink,
Source,
}
impl From<media_v2_pad> for MediaV2Pad {
fn from(v: media_v2_pad) ->Self {
Self {
id: PadId(v.id),
entity_id: EntityId(v.entity_id),
role: if v.flags & sys::MEDIA_PAD_FL_SINK != 0 {
PadRole::Sink
} else {
PadRole::Source
},
must_connect: v.flags & sys::MEDIA_PAD_FL_MUST_CONNECT != 0,
index: v.index,
}
}
}
impl Zeroed for media_v2_pad {}
nix::ioctl_readwrite!(media_ioc_g_topology, b'|', 0x04, media_v2_topology);
#[derive(Debug, Clone)]
pub struct MediaV2Topology {
pub version: sys::__u64,
pub links: Vec<MediaV2Link>,
pub entities: Vec<MediaV2Entity>,
pub interfaces: Vec<MediaV2Interface>,
pub pads: Vec<MediaV2Pad>,
}
impl MediaV2Topology {
pub fn read_from_rawfd(fd: RawFd) -> io::Result<Self> {
let mut topology = media_v2_topology::zeroed();
unsafe {
media_ioc_g_topology(fd, &mut topology)
}?;
let mut links: Vec<_> = (0..topology.num_links)
.map(|_| media_v2_link::zeroed())
.collect();
let mut entities: Vec<_> = (0..topology.num_entities)
.map(|_| media_v2_entity::zeroed())
.collect();
let mut interfaces: Vec<_> = (0..topology.num_interfaces)
.map(|_| media_v2_interface::zeroed())
.collect();
let mut pads: Vec<_> = (0..topology.num_pads)
.map(|_| Zeroed::zeroed())
.collect();
let mut topology = media_v2_topology::zeroed();
topology.set_links(&mut links);
topology.set_entities(&mut entities);
topology.set_interfaces(&mut interfaces);
topology.set_pads(&mut pads);
unsafe {
media_ioc_g_topology(fd, &mut topology)
}?;
Ok(MediaV2Topology {
version: topology.topology_version,
links: links.into_iter().map(MediaV2Link::from).collect(),
interfaces: interfaces.iter().map(MediaV2Interface::from).collect(),
entities: entities.into_iter().map(MediaV2Entity::from).collect(),
pads: pads.into_iter().map(MediaV2Pad::from).collect(),
})
}
}
nix::ioctl_readwrite!(media_ioc_setup_link, b'|', 0x03, media_link_desc);
impl Zeroed for media_link_desc {}
#[derive(Debug, Clone)]
pub struct MediaLinkDesc {
pub source: MediaPadDesc,
pub sink: MediaPadDesc,
pub state: LinkEnabled,
}
impl From<MediaLinkDesc> for media_link_desc {
fn from(value: MediaLinkDesc) -> Self {
Self {
source: value.source.into(),
sink: value.sink.into(),
flags: match value.state {
LinkEnabled::Enabled => MediaLnkFl::ENABLED.bits(),
LinkEnabled::Disabled => 0,
},
..Self::zeroed()
}
}
}
#[derive(Debug, Clone)]
pub enum LinkEnabled {
Enabled,
Disabled,
}
impl Zeroed for media_pad_desc {}
#[derive(Debug, Clone)]
pub struct MediaPadDesc {
pub entity: EntityId,
pub index: u16,
}
impl From<MediaPadDesc> for media_pad_desc {
fn from(value: MediaPadDesc) -> Self {
Self {
entity: value.entity.0,
index: value.index,
flags: 0,
..Self::zeroed()
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn entity_bad_string() {
let v = [0xfeu8; mem::size_of::<media_v2_entity>()];
dbg!(v.len());
let entity: media_v2_entity = unsafe { mem::transmute_copy(&v) };
let entity = MediaV2Entity::from(entity);
match entity.name {
EntityName::Bytes(_) => {},
EntityName::Text(_) => panic!("bad string should not get accepted"),
}
}
}