mod info;
mod ioctl;
mod media;
mod request;
mod topology;
mod util;
mod video;
pub mod vp8;
pub use info::DeviceInfo;
use log::{debug, trace};
pub use media::Media;
pub use request::Request;
pub use topology::{EntityFunction, InterfaceDevnode, LinkFlags, PadFlags, Topology};
pub use video::{
BufType, Buffer, Capability, CapsFlags, ExportBuffer, ExtControl, ExtControls, FmtDesc, Format,
Memory, Video,
};
use core::num::NonZeroUsize;
use nix::poll::{poll, PollFd, PollFlags};
use nix::sys::mman::{mmap, munmap, MapFlags, ProtFlags};
use std::collections::HashMap;
use std::os::fd::OwnedFd;
use std::os::linux::fs::MetadataExt;
use std::path::Path;
use std::rc::Rc;
struct DrmFormats;
impl DrmFormats {
pub const VP8F: u32 = 0x46385056;
pub const NV12: u32 = 0x3231564e;
pub const ST12: u32 = 0x32315453;
}
struct DrmModifiers;
impl DrmModifiers {
pub const LINEAR: u64 = 0;
pub const ALLWINNER_TILED: u64 = 0x09000000_00000001;
}
#[derive(Debug)]
pub enum Error {
IoError(std::io::Error),
Errno(nix::errno::Errno),
NoM2M,
FormatNotFound,
}
use std::fmt;
impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "media::Error")
}
}
impl std::error::Error for Error {}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Error {
Error::IoError(err)
}
}
impl From<nix::errno::Errno> for Error {
fn from(err: nix::errno::Errno) -> Error {
Error::Errno(err)
}
}
#[derive(Debug)]
pub struct Plane {
pub offset: u32,
pub pitch: u32,
}
#[derive(Debug)]
pub struct Frame {
pub width: u32,
pub height: u32,
pub format: u32,
pub modifier: u64,
pub planes: [Option<Plane>; 4],
}
pub struct Vp8Decoder {
media: Rc<Media>,
video: Rc<Video>,
ctrl: vp8::CtrlVp8Frame,
out_format: u32,
out_modifier: u64,
}
pub struct Decoder {
devices: HashMap<u32, (Rc<Media>, Rc<Video>)>,
}
impl Decoder {
#[inline(never)]
pub fn find_devices() -> Result<Decoder, Error> {
let mut videos = HashMap::new();
let mut medias = HashMap::new();
for dir_entry in std::fs::read_dir("/dev")? {
let dir_entry = dir_entry?;
let file_name = dir_entry.file_name();
let file_name = file_name.to_str().unwrap();
if file_name.starts_with("video") {
let path = dir_entry.path();
if let Ok(metadata) = path.metadata() {
let dev_t = metadata.st_rdev();
videos.insert(dev_t, path);
}
} else if file_name.starts_with("media") {
let path = dir_entry.path();
if let Some((media, media_interfaces)) = Self::discover_media(&path) {
let media = Rc::new(media);
for interface in media_interfaces {
medias.insert(interface, Rc::clone(&media));
}
} else {
debug!("{} doesn’t have a valid decoder, skipping.", path.display());
}
}
}
let mut devices = HashMap::new();
for (interface, media) in medias {
let path = &videos[&interface];
if let Ok((video, formats)) = Self::discover_video(path) {
let video = Rc::new(video);
for format in formats {
devices.insert(format, (Rc::clone(&media), Rc::clone(&video)));
}
} else {
debug!("{} isn’t a valid decoder, skipping.", path.display());
}
}
Ok(Decoder { devices })
}
pub fn for_vp8(&self, ctrl: vp8::CtrlVp8Frame) -> Option<Vp8Decoder> {
if let Some((media, video)) = self.devices.get(&DrmFormats::VP8F) {
Some(Vp8Decoder {
media: media.clone(),
video: video.clone(),
ctrl,
out_format: 0,
out_modifier: 0,
})
} else {
None
}
}
fn discover_media(path: &Path) -> Option<(Media, Vec<u64>)> {
let media = Media::open(path).ok()?;
let topology = media.get_topology().ok()?;
trace!("Found media {}", path.display());
for interface in topology.interfaces() {
trace!(" {interface:?}");
}
for pad in topology.pads() {
trace!(" {pad:?}");
}
for link in topology.links() {
trace!(" {link:?}");
}
let mut interfaces = Vec::new();
for entity in topology.entities() {
trace!(" {entity:?}");
if entity.function() == EntityFunction::ProcVideoDecoder {
trace!(" … is a decoder!");
let mut interface_id = None;
for pad in topology.get_pads_for_entity(entity.id()) {
trace!(" {pad:?}");
let pad_id = if pad.flags().contains(PadFlags::SOURCE) {
let link = topology.get_link_by_source_id(pad.id())?;
trace!(" {link:?}");
link.sink_id()
} else
{
let link = topology.get_link_by_sink_id(pad.id())?;
trace!(" {link:?}");
link.source_id()
};
let pad = topology.get_pad(pad_id)?;
trace!(" {pad:?}");
let entity = topology.get_entity(pad.entity_id())?;
trace!(" {entity:?}");
let link = topology.get_link_by_sink_id(entity.id())?;
trace!(" {link:?}");
assert!(link.flags().contains(LinkFlags::INTERFACE_LINK));
if let Some(interface_id) = interface_id {
assert_eq!(interface_id, link.source_id());
} else {
interface_id = Some(link.source_id());
}
}
let interface = topology.get_interface(interface_id.unwrap())?;
trace!(" {interface:?}");
let InterfaceDevnode { major, minor } = interface.devnode();
let dev_t = nix::sys::stat::makedev(major as u64, minor as u64);
trace!(" {major},{minor} -> {dev_t}");
interfaces.push(dev_t);
}
}
Some((media, interfaces))
}
fn discover_video(path: &Path) -> Result<(Video, Vec<u32>), Error> {
let video = Video::open(path)?;
let caps = video.querycap()?;
if !caps.capabilities().contains(CapsFlags::VIDEO_M2M) {
return Err(Error::NoM2M);
}
let formats = video
.enum_fmts(BufType::VideoOutput)?
.iter()
.map(|fmt| fmt.pixelformat())
.collect();
Ok((video, formats))
}
}
impl Vp8Decoder {
pub fn media_device_info(&mut self) -> Result<DeviceInfo, Error> {
Ok(self.media.device_info()?)
}
pub fn video_querycap(&mut self) -> Result<Capability, Error> {
Ok(self.video.querycap()?)
}
pub fn set_input_length(&mut self, length: u32) -> Result<(), Error> {
let format = DrmFormats::VP8F;
let width = self.ctrl.width as u32;
let height = self.ctrl.height as u32;
let mut format = Format::new(BufType::VideoOutput, width, height, format, length);
self.video.s_fmt(&mut format)?;
Ok(())
}
pub fn set_output_format(&mut self, format: u32) -> Result<Format, Error> {
let mut found_format = false;
for fmt in self.video.enum_fmts(BufType::VideoCapture)? {
if fmt.pixelformat() == format {
found_format = true;
}
}
if !found_format {
return Err(Error::FormatNotFound);
}
match format {
DrmFormats::ST12 => {
self.out_format = DrmFormats::NV12;
self.out_modifier = DrmModifiers::ALLWINNER_TILED;
}
fmt => {
self.out_format = fmt;
self.out_modifier = DrmModifiers::LINEAR;
}
}
let width = self.ctrl.width as u32;
let height = self.ctrl.height as u32;
let mut format = Format::new(BufType::VideoCapture, width, height, format, 0);
self.video.s_fmt(&mut format)?;
Ok(format)
}
pub fn start_vp8_decode(&mut self, data: &[u8]) -> std::io::Result<OwnedFd> {
let video = &self.video;
let media = &self.media;
let request = media.request_alloc()?;
video.reqbufs(Memory::Mmap, BufType::VideoOutput, 1)?;
let mut out_buf = Buffer::new(BufType::VideoOutput);
video.querybuf(&mut out_buf)?;
unsafe {
let len = NonZeroUsize::new(out_buf.length() as usize).unwrap();
let map = mmap(
None,
len,
ProtFlags::PROT_WRITE,
MapFlags::MAP_SHARED,
Some(&video),
out_buf.offset() as _,
)?;
std::ptr::copy(data.as_ptr(), map as *mut u8, data.len());
munmap(map, out_buf.length() as usize)?;
}
out_buf.set_bytesused(data.len());
out_buf.set_request(&request);
video.reqbufs(Memory::Mmap, BufType::VideoCapture, 1)?;
let mut cap_buf = Buffer::new(BufType::VideoCapture);
video.querybuf(&mut cap_buf)?;
const O_CLOEXEC: u32 = 0x00080000;
let mut cap_expbuf = ExportBuffer::new(BufType::VideoCapture, 0, O_CLOEXEC);
video.expbuf(&mut cap_expbuf)?;
video.qbuf(&mut out_buf).unwrap();
video.qbuf(&mut cap_buf).unwrap();
video.streamon(BufType::VideoOutput).unwrap();
video.streamon(BufType::VideoCapture).unwrap();
let mut ext_ctrl = ExtControl::new(vp8::uapi::V4L2_CID_STATELESS_VP8_FRAME, &mut self.ctrl);
let mut ext_ctrls = ExtControls::new(Some(&request), 1, &mut ext_ctrl);
video.s_ext_ctrls(&mut ext_ctrls)?;
request.queue().unwrap();
Ok(cap_expbuf.into_fd())
}
pub fn poll(&mut self) -> Result<(), Error> {
let mut fds = [PollFd::new(&self.video, PollFlags::POLLIN)];
poll(&mut fds[..], -1)?;
Ok(())
}
pub fn finish_vp8_decode(&mut self, out_fmt: Format) -> std::io::Result<Frame> {
let video = &self.video;
let mut out_buf = Buffer::new(BufType::VideoOutput);
let mut cap_buf = Buffer::new(BufType::VideoCapture);
video.dqbuf(&mut out_buf)?;
video.dqbuf(&mut cap_buf)?;
if cap_buf.is_error() {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"error while decoding",
));
}
video.streamoff(BufType::VideoOutput)?;
video.streamoff(BufType::VideoCapture)?;
let width = self.ctrl.width as u32;
let height = self.ctrl.height as u32;
let pitch = out_fmt.bytesperline();
let offset = out_fmt.height() * pitch;
let planes = [
Some(Plane {
offset: 0,
pitch,
}),
Some(Plane {
offset,
pitch,
}),
None,
None,
];
let frame = Frame {
width,
height,
format: self.out_format,
modifier: self.out_modifier,
planes,
};
Ok(frame)
}
}