use core::future::Future;
use core::pin::Pin;
use g2g_core::{
Caps, ConfigureOutcome, Dim, Frame, G2gError, InputAggregator, MultiInputElement, OutputSink,
PipelinePacket, PropError, PropKind, PropValue, PropertySpec, Rate, RawVideoFormat,
};
use crate::props::{fixed_caps, hosted_element_props};
#[derive(Debug)]
pub struct PyAggregator {
#[cfg_attr(not(feature = "python"), allow(dead_code))]
module: String,
#[cfg_attr(not(feature = "python"), allow(dead_code))]
class: String,
#[cfg_attr(not(feature = "python"), allow(dead_code))]
draw_label: bool,
cuda_frames: bool,
inputs: usize,
accept: Caps,
produce: Option<Caps>,
fixed: Option<Caps>,
agg: InputAggregator<Frame>,
emitted: u64,
#[cfg_attr(not(feature = "python"), allow(dead_code))]
params: Vec<(String, PropValue)>,
#[cfg(feature = "python")]
worker: Option<crate::host::PyWorker>,
}
impl PyAggregator {
pub fn new(module: impl Into<String>, class: impl Into<String>, inputs: usize) -> Self {
Self {
module: module.into(),
class: class.into(),
draw_label: false,
cuda_frames: false,
inputs,
accept: Caps::RawVideo {
format: RawVideoFormat::Rgba8,
width: Dim::Any,
height: Dim::Any,
framerate: Rate::Any,
interlace: g2g_core::Interlace::Any,
},
produce: None,
fixed: None,
agg: InputAggregator::new(inputs),
emitted: 0,
params: Vec::new(),
#[cfg(feature = "python")]
worker: None,
}
}
pub fn with_accept(mut self, caps: Caps) -> Self {
self.accept = caps;
self
}
pub fn with_produce(mut self, caps: Caps) -> Self {
self.produce = Some(caps);
self
}
pub fn with_draw_label(mut self, on: bool) -> Self {
self.draw_label = on;
self
}
pub fn with_cuda_frames(mut self, on: bool) -> Self {
self.cuda_frames = on;
self
}
pub fn emitted_count(&self) -> u64 {
self.emitted
}
#[cfg(feature = "python")]
async fn run_batch(&self, frames: Vec<Frame>, caps: &Caps) -> Result<Vec<Frame>, G2gError> {
self.worker
.as_ref()
.ok_or(G2gError::NotConfigured)?
.run_batch(frames, caps)
.await
}
#[cfg(not(feature = "python"))]
async fn run_batch(&self, _frames: Vec<Frame>, _caps: &Caps) -> Result<Vec<Frame>, G2gError> {
Err(G2gError::UnsupportedDomain)
}
async fn drain(&mut self, out: &mut dyn OutputSink) -> Result<(), G2gError> {
let caps = self.fixed.clone().ok_or(G2gError::NotConfigured)?;
while let Some(round) = self.agg.take_round() {
let frames: Vec<Frame> = round.into_iter().map(|(_input, frame)| frame).collect();
for processed in self.run_batch(frames, &caps).await? {
out.push(PipelinePacket::DataFrame(processed)).await?;
self.emitted += 1;
}
}
Ok(())
}
}
impl MultiInputElement for PyAggregator {
type ProcessFuture<'a>
= Pin<Box<dyn Future<Output = Result<(), G2gError>> + 'a>>
where
Self: 'a;
fn input_count(&self) -> usize {
self.inputs
}
fn intercept_caps(&self, _input: usize, upstream_caps: &Caps) -> Result<Caps, G2gError> {
upstream_caps.intersect(&self.accept)
}
fn configure_pipeline(
&mut self,
_input: usize,
absolute_caps: &Caps,
) -> Result<ConfigureOutcome, G2gError> {
absolute_caps.intersect(&self.accept)?;
self.fixed = Some(absolute_caps.clone());
#[cfg(feature = "python")]
{
if self.module.is_empty() || self.class.is_empty() {
return Err(G2gError::NotConfigured);
}
if self.worker.is_none() {
self.worker = Some(crate::host::PyWorker::spawn(
&self.module,
&self.class,
self.draw_label,
&self.params,
)?);
}
}
Ok(ConfigureOutcome::Accepted)
}
fn output_caps(&self) -> Result<Caps, G2gError> {
self.produce
.clone()
.or_else(|| self.fixed.clone())
.ok_or(G2gError::NotConfigured)
}
fn process<'a>(
&'a mut self,
input: usize,
packet: PipelinePacket,
out: &'a mut dyn OutputSink,
) -> Self::ProcessFuture<'a> {
Box::pin(async move {
match packet {
PipelinePacket::DataFrame(frame) => {
self.agg.push(input, frame);
self.drain(out).await?;
}
PipelinePacket::CapsChanged(c) => {
c.intersect(&self.accept)?;
}
PipelinePacket::Eos => {
self.agg.mark_ended(input);
self.drain(out).await?;
}
_ => {}
}
Ok(())
})
}
fn propose_allocation_for_input(
&self,
_input: usize,
caps: &Caps,
) -> Option<g2g_core::AllocationParams> {
let Caps::RawVideo {
format,
width: Dim::Fixed(width),
height: Dim::Fixed(height),
..
} = caps
else {
return None;
};
let size = crate::format::frame_bytes(*format, *width, *height);
Some(if self.cuda_frames {
g2g_core::AllocationParams::cuda(size, 1, 1)
} else {
g2g_core::AllocationParams::system(size, 1)
})
}
fn properties(&self) -> &'static [PropertySpec] {
PYAGGREGATOR_PROPS
}
fn set_property(&mut self, name: &str, value: PropValue) -> Result<(), PropError> {
match name {
"module" => {
self.module = value.as_str().ok_or(PropError::Type)?.to_string();
Ok(())
}
"class" => {
self.class = value.as_str().ok_or(PropError::Type)?.to_string();
Ok(())
}
"draw-label" => {
self.draw_label = value.as_bool().ok_or(PropError::Type)?;
Ok(())
}
"cuda-frames" => {
self.cuda_frames = value.as_bool().ok_or(PropError::Type)?;
Ok(())
}
"input-caps" => {
self.accept = fixed_caps(value.as_str().ok_or(PropError::Type)?)?;
Ok(())
}
"output-caps" => {
self.produce = Some(fixed_caps(value.as_str().ok_or(PropError::Type)?)?);
Ok(())
}
other => {
crate::props::forward(&mut self.params, other, value);
Ok(())
}
}
}
fn get_property(&self, name: &str) -> Option<PropValue> {
match name {
"module" => Some(PropValue::Str(self.module.clone())),
"class" => Some(PropValue::Str(self.class.clone())),
"draw-label" => Some(PropValue::Bool(self.draw_label)),
"cuda-frames" => Some(PropValue::Bool(self.cuda_frames)),
"input-caps" => Some(PropValue::Str(self.accept.to_gst_string())),
"output-caps" => self
.produce
.as_ref()
.map(|c| PropValue::Str(c.to_gst_string())),
other => self
.params
.iter()
.find(|(k, _)| k == other)
.map(|(_, v)| v.clone()),
}
}
}
static PYAGGREGATOR_PROPS: &[PropertySpec] = hosted_element_props![
PropertySpec::new(
"module",
PropKind::Str,
"Python module to import (the aggregator element)",
),
PropertySpec::new(
"class",
PropKind::Str,
"class within the module to instantiate",
),
PropertySpec::new(
"draw-label",
PropKind::Bool,
"overlay the inferred label on the anchor frame",
)
.with_default("false"),
PropertySpec::new(
"cuda-frames",
PropKind::Bool,
"batch GPU-resident CUDA frames (needs g2g_process_cuda_batch, NV12 / P010)",
)
.with_default("false"),
PropertySpec::new(
"input-caps",
PropKind::Str,
"caps accepted on every input pad, e.g. audio/x-raw,format=S16LE,rate=16000",
)
.with_default("video/x-raw,format=RGBA"),
PropertySpec::new(
"output-caps",
PropKind::Str,
"caps produced downstream when the hosted element changes media type, e.g. text/x-raw,format=utf8",
),
];