use std::collections::VecDeque;
use std::time::{Duration, Instant};
use bytes::Bytes;
use moq_net::Timestamp;
use v4l::v4l_sys::{V4L2_CID_MIN_BUFFERS_FOR_CAPTURE, V4L2_DEC_CMD_START, V4L2_DEC_CMD_STOP};
use super::{Backend, Codec, Config};
use crate::v4l2::{self, Dequeue, Device, Dir, Format, Planes, Queue, Rect, Request, Role};
use crate::{Error, Frame, Size, Surface};
pub(crate) const NAME: &str = "v4l2";
const ROLE: Role = Role {
env: "MOQ_V4L2_DECODER",
input: &[v4l2::H264],
output: v4l2::RAW,
};
const CODED_BUFFERS: u32 = 4;
const CODED_SIZE: u32 = 1024 * 1024;
const SPARE_PICTURES: u32 = 3;
const DEFAULT_PICTURES: u32 = 8;
const BUFFER_TIMEOUT: Duration = Duration::from_millis(500);
const SOURCE_CHANGE_TIMEOUT: Duration = Duration::from_millis(250);
const SOURCE_CHANGE_LIMIT: Duration = Duration::from_secs(5);
const DRAIN_TIMEOUT: Duration = Duration::from_millis(500);
const POLL_INTERVAL: Duration = Duration::from_millis(5);
const REMEMBERED: usize = 64;
pub(crate) struct V4l2 {
device: Device,
coded: Queue,
pictures: Option<Pictures>,
since: Option<Instant>,
submitted: VecDeque<(Duration, Timestamp)>,
}
struct Pictures {
queue: Queue,
planes: Planes,
}
impl V4l2 {
pub(crate) fn open(codec: Codec, _config: &Config) -> Result<Box<dyn Backend>, Error> {
if codec != Codec::H264 {
return Err(Error::UnsupportedCodec(format!("{NAME} decodes H.264 only")));
}
let device = v4l2::open(&ROLE)?;
device.subscribe_source_change()?;
let coded = device.set_format(
Dir::Output,
&Request {
pixelformat: v4l2::H264,
size: Size::new(1920, 1088),
sizeimage: Some(CODED_SIZE),
color: None,
},
)?;
if coded.pixelformat != v4l2::H264 {
return Err(Error::Codec(anyhow::anyhow!(
"V4L2 decoder answered an H264 request with {}",
v4l2::name(coded.pixelformat)
)));
}
let mut coded = Queue::alloc(&device, Dir::Output, coded, CODED_BUFFERS)?;
coded.stream_on(&device)?;
tracing::info!(
decoder = NAME,
device = %device.path().display(),
"opened H.264 decoder"
);
Ok(Box::new(Self {
device,
coded,
pictures: None,
since: None,
submitted: VecDeque::new(),
}))
}
fn submit(&mut self, access_unit: &Bytes, timestamp: Timestamp) -> Result<(), Error> {
let deadline = Instant::now() + BUFFER_TIMEOUT;
let index = loop {
self.reclaim_coded()?;
if let Some(index) = self.coded.take_free() {
break index;
}
if Instant::now() >= deadline {
return Err(Error::Codec(anyhow::anyhow!(
"V4L2 decoder held every input buffer for {BUFFER_TIMEOUT:?}"
)));
}
self.device.wait(POLL_INTERVAL);
};
let capacity = self.coded.plane(index, 0).len();
if access_unit.len() > capacity {
self.coded.reclaim(index);
return Err(Error::Codec(anyhow::anyhow!(
"access unit of {} bytes exceeds the V4L2 decoder's {capacity} byte buffer",
access_unit.len()
)));
}
self.coded.plane_mut(index, 0)[..access_unit.len()].copy_from_slice(access_unit);
let bytesused = [access_unit.len() as u32];
let key = key(timestamp);
self.coded.queue(&self.device, index, &bytesused, key)?;
remember(&mut self.submitted, key, timestamp);
Ok(())
}
fn reclaim_coded(&mut self) -> Result<usize, Error> {
let mut reclaimed = 0;
while let Some(buffer) = self.coded.dequeue(&self.device)?.buffer() {
if buffer.failed() {
tracing::warn!(
decoder = NAME,
buffer = buffer.index,
"V4L2 decoder could not decode an access unit"
);
}
self.coded.reclaim(buffer.index);
reclaimed += 1;
}
Ok(reclaimed)
}
fn negotiate(&mut self) -> Result<(), Error> {
if let Some(pictures) = self.pictures.take() {
pictures.queue.release(&self.device)?;
}
let format = self.capture_format()?;
let visible = self
.device
.visible(Dir::Capture)
.unwrap_or_else(|| Rect::whole(format.size));
let planes = Planes::new(&format, visible)?;
let minimum = self
.device
.control(V4L2_CID_MIN_BUFFERS_FOR_CAPTURE)
.map_or(DEFAULT_PICTURES, |minimum| minimum.max(1) as u32 + SPARE_PICTURES);
tracing::info!(
decoder = NAME,
format = v4l2::name(format.pixelformat),
coded = %format.size,
visible = %visible.size,
left = visible.left,
top = visible.top,
buffers = minimum,
"V4L2 decoder negotiated its output"
);
let mut queue = Queue::alloc(&self.device, Dir::Capture, format, minimum)?;
while let Some(index) = queue.take_free() {
queue.queue(&self.device, index, &[], Duration::ZERO)?;
}
queue.stream_on(&self.device)?;
self.pictures = Some(Pictures { queue, planes });
Ok(())
}
fn capture_format(&self) -> Result<Format, Error> {
let format = self.device.format(Dir::Capture)?;
if v4l2::RAW.contains(&format.pixelformat) {
return Ok(format);
}
let offered = self.device.formats(Dir::Capture)?;
let Some(&pixelformat) = v4l2::RAW.iter().find(|code| offered.contains(code)) else {
return Err(Error::Codec(anyhow::anyhow!(
"V4L2 decoder defaults to {} and offers no 8-bit 4:2:0 format for this stream",
v4l2::name(format.pixelformat)
)));
};
tracing::debug!(
decoder = NAME,
default = v4l2::name(format.pixelformat),
selected = v4l2::name(pixelformat),
"V4L2 decoder defaulted to a format this cannot read"
);
self.device.set_format(
Dir::Capture,
&Request {
pixelformat,
size: format.size,
sizeimage: None,
color: None,
},
)
}
fn drain(&mut self, frames: &mut Vec<Frame>) -> Result<bool, Error> {
let Some(pictures) = &self.pictures else {
return Ok(false);
};
loop {
let buffer = match pictures.queue.dequeue(&self.device)? {
Dequeue::Buffer(buffer) => buffer,
Dequeue::Empty => return Ok(false),
Dequeue::Ended => return Ok(true),
};
let decoded = match buffer.written(0) {
0 => None,
_ if buffer.failed() => {
tracing::warn!(
decoder = NAME,
buffer = buffer.index,
"V4L2 decoder flagged a picture bad"
);
None
}
_ => Some(pictures.planes.read(&pictures.queue, &buffer)?),
};
pictures.queue.queue(&self.device, buffer.index, &[], Duration::ZERO)?;
if let Some(decoded) = decoded {
let timestamp = restore(&mut self.submitted, buffer.timestamp)?;
frames.push(Frame::new(Surface::I420(decoded), timestamp));
}
if buffer.last() {
return Ok(true);
}
}
}
fn drain_tail(&mut self) -> Result<Vec<Frame>, Error> {
let mut frames = Vec::new();
let deadline = Instant::now() + DRAIN_TIMEOUT;
loop {
if self.drain(&mut frames)? {
return Ok(frames);
}
if Instant::now() >= deadline {
tracing::warn!(
decoder = NAME,
frames = frames.len(),
"V4L2 decoder did not end the sequence within {DRAIN_TIMEOUT:?}"
);
return Ok(frames);
}
self.device.wait(POLL_INTERVAL);
}
}
fn drain_sequence(&mut self) -> Result<(Vec<Frame>, bool), Error> {
let mut frames = Vec::new();
let mut ended = false;
let mut changed = false;
let mut deadline = Instant::now() + DRAIN_TIMEOUT;
loop {
let before = frames.len();
let reclaimed = self.reclaim_coded()?;
changed |= self.device.take_source_change();
if !ended {
ended = self.drain(&mut frames)?;
}
if ended && (changed || self.coded.outstanding() == 0) {
return Ok((frames, changed));
}
if reclaimed > 0 || frames.len() > before {
deadline = Instant::now() + DRAIN_TIMEOUT;
} else if Instant::now() >= deadline {
return Err(Error::Codec(anyhow::anyhow!(
"V4L2 decoder did not finish its drain within {DRAIN_TIMEOUT:?}, holding {} access unit(s)",
self.coded.outstanding()
)));
}
self.device.wait(POLL_INTERVAL);
}
}
}
fn key(timestamp: Timestamp) -> Duration {
Duration::from_micros(timestamp.as_micros() as u64)
}
fn remember(submitted: &mut VecDeque<(Duration, Timestamp)>, key: Duration, timestamp: Timestamp) {
if submitted.len() == REMEMBERED {
submitted.pop_front();
}
submitted.push_back((key, timestamp));
}
fn restore(submitted: &mut VecDeque<(Duration, Timestamp)>, key: Duration) -> Result<Timestamp, Error> {
let found = submitted.iter().position(|(at, _)| *at == key);
match found.and_then(|at| submitted.remove(at)) {
Some((_, timestamp)) => Ok(timestamp),
None => Ok(Timestamp::from_micros(key.as_micros() as u64)?),
}
}
impl Backend for V4l2 {
fn decode(&mut self, access_unit: Bytes, timestamp: Timestamp, _keyframe: bool) -> Result<Vec<Frame>, Error> {
self.submit(&access_unit, timestamp)?;
let mut frames = Vec::new();
if self.pictures.is_none() {
let since = *self.since.get_or_insert_with(Instant::now);
let deadline = Instant::now() + SOURCE_CHANGE_TIMEOUT;
while !self.device.take_source_change() {
if Instant::now() >= deadline {
let waited = since.elapsed();
if waited >= SOURCE_CHANGE_LIMIT {
return Err(Error::Codec(anyhow::anyhow!(
"V4L2 decoder did not report the stream's size within {waited:?}"
)));
}
tracing::debug!(
decoder = NAME,
?waited,
"V4L2 decoder has not reported the stream's size"
);
return Ok(Vec::new());
}
self.device.wait(POLL_INTERVAL);
}
self.negotiate()?;
} else if self.device.take_source_change() {
frames = self.drain_tail()?;
self.negotiate()?;
}
self.drain(&mut frames)?;
Ok(frames)
}
fn flush(&mut self) -> Result<Vec<Frame>, Error> {
if self.pictures.is_none() {
self.coded.restart(&self.device)?;
self.submitted.clear();
self.since = None;
return Ok(Vec::new());
}
let mut frames = Vec::new();
loop {
self.device.decoder_cmd(V4L2_DEC_CMD_STOP)?;
let (tail, changed) = self.drain_sequence()?;
frames.extend(tail);
if changed {
self.negotiate()?;
}
self.device.decoder_cmd(V4L2_DEC_CMD_START)?;
if changed {
continue;
}
break;
}
self.submitted.clear();
self.since = None;
Ok(frames)
}
fn name(&self) -> &str {
NAME
}
}
#[cfg(test)]
mod tests {
use super::*;
fn micros(micros: u64) -> Timestamp {
Timestamp::from_micros(micros).unwrap()
}
#[test]
fn a_picture_carries_its_access_unit_timestamp_unchanged() {
let ninety_khz = moq_net::Timescale::new(90_000).unwrap();
let first = Timestamp::new(3003, ninety_khz).unwrap();
let second = Timestamp::new(6006, ninety_khz).unwrap();
assert_ne!(Timestamp::from_micros(first.as_micros() as u64).unwrap(), first);
let mut submitted = VecDeque::new();
remember(&mut submitted, key(first), first);
remember(&mut submitted, key(second), second);
assert_eq!(restore(&mut submitted, key(second)).unwrap(), second);
assert_eq!(restore(&mut submitted, key(first)).unwrap(), first);
assert!(submitted.is_empty());
}
#[test]
fn an_unknown_picture_keeps_the_buffer_timestamp() {
let mut submitted = VecDeque::new();
remember(&mut submitted, key(micros(10)), micros(10));
assert_eq!(restore(&mut submitted, Duration::from_micros(7)).unwrap(), micros(7));
assert_eq!(submitted.len(), 1);
}
#[test]
fn remembered_access_units_are_bounded() {
let mut submitted = VecDeque::new();
for at in 0..=REMEMBERED as u64 {
remember(&mut submitted, key(micros(at)), micros(at));
}
assert_eq!(submitted.len(), REMEMBERED);
assert_eq!(restore(&mut submitted, key(micros(0))).unwrap(), micros(0));
assert_eq!(submitted.len(), REMEMBERED);
assert_eq!(restore(&mut submitted, key(micros(1))).unwrap(), micros(1));
assert_eq!(submitted.len(), REMEMBERED - 1);
}
}