use crate::mmal::{self, Component, Event, Mmal, Pool, PortAction, PortInfo};
use crate::timer::Timer;
use crate::vchiq::Vchiq;
const COMPONENT: &str = "ril.video_decode";
pub const MAX_PORT_BUFFERS: usize = mmal::POOL_CAPACITY;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Error {
Mmal(mmal::Error),
TooManyBuffers,
NoBuffers,
BufferTooSmall {
provided: usize,
required: usize,
},
WrongState,
DecoderError,
}
impl From<mmal::Error> for Error {
fn from(error: mmal::Error) -> Self {
Error::Mmal(error)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FrameFormat {
pub width: u32,
pub height: u32,
pub crop_width: u32,
pub crop_height: u32,
pub frame_rate_num: i32,
pub frame_rate_den: i32,
pub frame_size: usize,
}
impl FrameFormat {
pub fn u_offset(&self) -> usize {
(self.width * self.height) as usize
}
pub fn v_offset(&self) -> usize {
self.u_offset() + ((self.width / 2) * (self.height / 2)) as usize
}
}
#[derive(Debug)]
pub struct Frame {
pub buffer: &'static mut [u8],
pub length: usize,
pub pts: i64,
pub flags: u32,
pub format: FrameFormat,
}
pub struct VideoDecoder {
mmal: Mmal,
component: Component,
input: PortInfo,
output: PortInfo,
input_pool: Pool,
output_pool: Pool,
started: bool,
output_enabled: bool,
output_in_flight: usize,
format: Option<FrameFormat>,
eos: bool,
}
impl VideoDecoder {
pub fn new(vchiq: Vchiq, timer: &Timer) -> Result<Self, Error> {
let mut mmal = Mmal::new(vchiq, timer)?;
let component = mmal.component_create(COMPONENT, timer)?;
let mut input = mmal.port_info_get(&component, mmal::PORT_TYPE_INPUT, 0, timer)?;
input.es_type = mmal::ES_TYPE_VIDEO;
input.encoding = mmal::ENCODING_H264;
input.encoding_variant = 0;
let output = mmal.port_info_get(&component, mmal::PORT_TYPE_OUTPUT, 0, timer)?;
Ok(Self {
mmal,
component,
input,
output,
input_pool: Pool::new(),
output_pool: Pool::new(),
started: false,
output_enabled: false,
output_in_flight: 0,
format: None,
eos: false,
})
}
pub fn add_input_buffer(&mut self, buffer: &'static mut [u8]) -> Result<(), Error> {
if self.started {
return Err(Error::WrongState);
}
if !self.input_pool.add(buffer) {
return Err(Error::TooManyBuffers);
}
Ok(())
}
pub fn add_output_buffer(&mut self, buffer: &'static mut [u8]) -> Result<(), Error> {
if self.started {
return Err(Error::WrongState);
}
if !self.output_pool.add(buffer) {
return Err(Error::TooManyBuffers);
}
Ok(())
}
pub fn start(&mut self, timer: &Timer) -> Result<(), Error> {
if self.started {
return Err(Error::WrongState);
}
if self.input_pool.total() == 0 || self.output_pool.total() == 0 {
return Err(Error::NoBuffers);
}
let smallest = self.input_pool.smallest();
if smallest < self.input.buffer_size_min as usize {
return Err(Error::BufferTooSmall {
provided: smallest,
required: self.input.buffer_size_min as usize,
});
}
self.input.buffer_num = self
.input_pool
.total()
.max(self.input.buffer_num_min as usize) as u32;
self.input.buffer_size = smallest as u32;
self.mmal.port_info_set(&mut self.input, timer)?;
self.output.es_type = mmal::ES_TYPE_VIDEO;
self.output.encoding = mmal::ENCODING_I420;
self.output.encoding_variant = 0;
self.output.buffer_num = self
.output_pool
.total()
.max(self.output.buffer_num_min as usize) as u32;
self.mmal.port_info_set(&mut self.output, timer)?;
self.mmal.component_enable(&self.component, timer)?;
self.mmal
.port_action(&self.output, PortAction::Enable, timer)?;
self.output.enabled = true;
self.output_enabled = true;
self.mmal
.port_action(&self.input, PortAction::Enable, timer)?;
self.input.enabled = true;
self.started = true;
Ok(())
}
pub fn feed(
&mut self,
data: &[u8],
flags: u32,
pts: i64,
timer: &Timer,
) -> Result<usize, Error> {
if !self.started {
return Err(Error::WrongState);
}
if data.is_empty() {
return Ok(0);
}
let Some(buffer) = self.input_pool.take() else {
return Ok(0);
};
let length = data.len().min(buffer.len());
buffer[..length].copy_from_slice(&data[..length]);
self.mmal
.send_buffer(&self.input, buffer, length, flags, pts, timer)?;
Ok(length)
}
pub fn finish(&mut self, timer: &Timer) -> Result<(), Error> {
if !self.started {
return Err(Error::WrongState);
}
let Some(buffer) = self.input_pool.take() else {
return Err(Error::WrongState);
};
self.mmal.send_buffer(
&self.input,
buffer,
0,
mmal::BUFFER_FLAG_EOS,
mmal::TIME_UNKNOWN,
timer,
)?;
Ok(())
}
pub fn poll(&mut self, timer: &Timer) -> Result<Option<Frame>, Error> {
while let Some(event) = self.mmal.poll()? {
match event {
Event::Buffer {
port,
buffer,
length,
flags,
pts,
} => {
if port == self.input.handle {
self.input_pool.put(buffer);
continue;
}
self.output_in_flight = self.output_in_flight.saturating_sub(1);
if flags & mmal::BUFFER_FLAG_EOS != 0 {
self.eos = true;
}
match (length, self.format) {
(1.., Some(format)) => {
return Ok(Some(Frame {
buffer,
length,
pts,
flags,
format,
}))
}
_ => {
self.output_pool.put(buffer);
self.refill_output(timer)?;
}
}
}
Event::PortEvent { cmd, .. } => match cmd {
mmal::EVENT_FORMAT_CHANGED => self.reconfigure_output(timer)?,
mmal::EVENT_EOS => self.eos = true,
mmal::EVENT_ERROR => return Err(Error::DecoderError),
_ => {}
},
}
}
Ok(None)
}
pub fn recycle(&mut self, frame: Frame, timer: &Timer) -> Result<(), Error> {
self.output_pool.put(frame.buffer);
self.refill_output(timer)
}
pub fn format(&self) -> Option<FrameFormat> {
self.format
}
pub fn end_of_stream(&self) -> bool {
self.eos
}
pub fn mmal(&mut self) -> &mut Mmal {
&mut self.mmal
}
pub fn input_port(&self) -> &PortInfo {
&self.input
}
pub fn output_port(&self) -> &PortInfo {
&self.output
}
fn reconfigure_output(&mut self, timer: &Timer) -> Result<(), Error> {
let Some(changed) = mmal::parse_format_changed(self.mmal.event_data()) else {
return Ok(());
};
if self.output_enabled {
self.mmal
.port_action(&self.output, PortAction::Disable, timer)?;
self.output_enabled = false;
self.output.enabled = false;
self.drain_output(timer)?;
}
self.output = self
.mmal
.port_info_get(&self.component, mmal::PORT_TYPE_OUTPUT, 0, timer)?;
self.output.es_type = mmal::ES_TYPE_VIDEO;
self.output.encoding = mmal::ENCODING_I420;
self.output.encoding_variant = 0;
self.output.video = changed.video;
self.output.buffer_num = self
.output_pool
.total()
.max(changed.buffer_num_min as usize) as u32;
self.output.buffer_size = changed.buffer_size_min.max(changed.buffer_size_recommended);
let smallest = self.output_pool.smallest();
if smallest < self.output.buffer_size as usize {
return Err(Error::BufferTooSmall {
provided: smallest,
required: self.output.buffer_size as usize,
});
}
self.output.buffer_size = smallest as u32;
self.mmal.port_info_set(&mut self.output, timer)?;
self.mmal
.port_action(&self.output, PortAction::Enable, timer)?;
self.output.enabled = true;
self.format = Some(FrameFormat {
width: changed.video.width,
height: changed.video.height,
crop_width: changed.video.crop_width.max(0) as u32,
crop_height: changed.video.crop_height.max(0) as u32,
frame_rate_num: changed.video.frame_rate_num,
frame_rate_den: changed.video.frame_rate_den,
frame_size: (changed.video.width * changed.video.height * 3 / 2) as usize,
});
self.output_enabled = true;
self.refill_output(timer)
}
fn drain_output(&mut self, timer: &Timer) -> Result<(), Error> {
let deadline = timer.now_micros() + DRAIN_TIMEOUT_US;
while self.output_in_flight > 0 {
if let Some(Event::Buffer { port, buffer, .. }) = self.mmal.poll()? {
if port == self.input.handle {
self.input_pool.put(buffer);
} else {
self.output_in_flight = self.output_in_flight.saturating_sub(1);
self.output_pool.put(buffer);
}
}
if timer.now_micros() > deadline {
self.output_in_flight = 0;
}
}
Ok(())
}
fn refill_output(&mut self, timer: &Timer) -> Result<(), Error> {
if !self.output_enabled || self.format.is_none() {
return Ok(());
}
while let Some(buffer) = self.output_pool.take() {
self.mmal
.send_buffer(&self.output, buffer, 0, 0, mmal::TIME_UNKNOWN, timer)?;
self.output_in_flight += 1;
}
Ok(())
}
}
const DRAIN_TIMEOUT_US: u64 = 2_000_000;