use super::*;
pub(super) fn fg_bind_inputs(
filter_graphs: &mut Vec<FilterGraph>,
demuxs: &mut Vec<Demuxer>,
) -> Result<()> {
if filter_graphs.is_empty() {
return Ok(());
}
bind_fg_inputs_by_fg(filter_graphs)?;
for filter_graph in filter_graphs.iter_mut() {
for i in 0..filter_graph.inputs.len() {
fg_complex_bind_input(filter_graph, i, demuxs)?;
}
}
Ok(())
}
struct FilterLabel {
linklabel: String,
media_type: AVMediaType,
}
pub(super) fn bind_fg_inputs_by_fg(filter_graphs: &mut Vec<FilterGraph>) -> Result<()> {
let fg_labels = filter_graphs
.iter()
.map(|filter_graph| {
let inputs = filter_graph
.inputs
.iter()
.map(|input| FilterLabel {
linklabel: input.linklabel.clone(),
media_type: input.media_type,
})
.collect::<Vec<_>>();
let outputs = filter_graph
.outputs
.iter()
.map(|output| FilterLabel {
linklabel: output.linklabel.clone(),
media_type: output.media_type,
})
.collect::<Vec<_>>();
(inputs, outputs)
})
.collect::<Vec<_>>();
for (i, (inputs, _outputs)) in fg_labels.iter().enumerate() {
for (input_pad_idx, input_filter_label) in inputs.iter().enumerate() {
if input_filter_label.linklabel.is_empty() {
continue;
}
'outer: for (j, (_inputs, outputs)) in fg_labels.iter().enumerate() {
if i == j {
continue;
}
for (output_idx, output_filter_label) in outputs.iter().enumerate() {
if output_filter_label.linklabel != input_filter_label.linklabel {
continue;
}
if output_filter_label.media_type != input_filter_label.media_type {
warn!(target: LOG_TARGET,
"Tried to connect {:?} output to {:?} input",
output_filter_label.media_type, input_filter_label.media_type
);
return Err(FilterGraphParseError::InvalidArgument.into());
}
{
let filter_graph = &filter_graphs[j];
let output_filter = &filter_graph.outputs[output_idx];
if output_filter.has_dst() {
continue;
}
}
let (sender, finished_flag_list) = {
let filter_graph = &mut filter_graphs[i];
filter_graph.get_src_sender()
};
{
let filter_graph = &mut filter_graphs[j];
filter_graph.outputs[output_idx].set_dst(sender);
filter_graph.outputs[output_idx].fg_input_index = input_pad_idx;
filter_graph.outputs[output_idx].finished_flag_list = finished_flag_list;
}
filter_graphs[i].inputs[input_pad_idx].bound = true;
break 'outer;
}
}
}
}
Ok(())
}
pub(super) fn fg_complex_bind_input(
filter_graph: &mut FilterGraph,
input_filter_index: usize,
demuxs: &mut Vec<Demuxer>,
) -> Result<()> {
if filter_graph.inputs[input_filter_index].bound {
return Ok(());
}
let graph_desc = &filter_graph.graph_desc;
let input_filter = &mut filter_graph.inputs[input_filter_index];
let (demux_idx, stream_idx) = if !input_filter.linklabel.is_empty()
&& input_filter.linklabel != "in"
{
let (demux_idx, stream_idx) = fg_find_input_idx_by_linklabel(
&input_filter.linklabel,
input_filter.media_type,
demuxs,
graph_desc,
)?;
info!(target: LOG_TARGET,
"Binding filter input with label '{}' to input stream {stream_idx}:{demux_idx}",
input_filter.linklabel
);
(demux_idx, stream_idx)
} else {
let mut demux_idx = -1i32;
let mut stream_idx = 0;
for (d_idx, demux) in demuxs.iter().enumerate() {
for (st_idx, intput_stream) in demux.get_streams().iter().enumerate() {
if intput_stream.is_used() {
continue;
}
if intput_stream.codec_type == input_filter.media_type {
demux_idx = d_idx as i32;
stream_idx = st_idx;
break;
}
}
if demux_idx >= 0 {
break;
}
}
if demux_idx < 0 {
warn!(target: LOG_TARGET,
"Cannot find a matching stream for unlabeled input pad {}",
input_filter.name
);
return Err(FilterGraphParseError::InvalidArgument.into());
}
debug!(target: LOG_TARGET, "FilterGraph binding unlabeled input {input_filter_index} to input stream {stream_idx}:{demux_idx}");
(demux_idx as usize, stream_idx)
};
let demux = &mut demuxs[demux_idx];
ifilter_bind_ist(filter_graph, input_filter_index, stream_idx, demux)
}
#[cfg(docsrs)]
pub(super) fn ifilter_bind_ist(
filter_graph: &mut FilterGraph,
input_index: usize,
stream_idx: usize,
demux: &mut Demuxer,
) -> Result<()> {
Ok(())
}
#[cfg(not(docsrs))]
pub(super) fn ifilter_bind_ist(
filter_graph: &mut FilterGraph,
input_index: usize,
stream_idx: usize,
demux: &mut Demuxer,
) -> Result<()> {
unsafe {
let input_filter = &mut filter_graph.inputs[input_index];
let ist = *(*demux.in_fmt_ctx_ptr()).streams.add(stream_idx);
let par = (*ist).codecpar;
if (*par).codec_type == AVMEDIA_TYPE_VIDEO {
let framerate = if demux.framerate.num > 0 && demux.framerate.den > 0 {
demux.framerate
} else {
av_guess_frame_rate(demux.in_fmt_ctx_ptr(), ist, null_mut())
};
input_filter.opts.framerate = framerate;
} else if (*par).codec_type == AVMEDIA_TYPE_SUBTITLE {
input_filter.opts.sub2video_width = (*par).width;
input_filter.opts.sub2video_height = (*par).height;
if input_filter.opts.sub2video_width <= 0 || input_filter.opts.sub2video_height <= 0 {
let nb_streams = (*demux.in_fmt_ctx_ptr()).nb_streams;
for j in 0..nb_streams {
let par1 = (**(*demux.in_fmt_ctx_ptr()).streams.add(j as usize)).codecpar;
if (*par1).codec_type == AVMEDIA_TYPE_VIDEO {
input_filter.opts.sub2video_width =
std::cmp::max(input_filter.opts.sub2video_width, (*par1).width);
input_filter.opts.sub2video_height =
std::cmp::max(input_filter.opts.sub2video_height, (*par1).height);
}
}
}
if input_filter.opts.sub2video_width <= 0 || input_filter.opts.sub2video_height <= 0 {
input_filter.opts.sub2video_width =
std::cmp::max(input_filter.opts.sub2video_width, 720);
input_filter.opts.sub2video_height =
std::cmp::max(input_filter.opts.sub2video_height, 576);
}
demux.get_stream_mut(stream_idx).have_sub2video = true;
}
let dec_ctx = {
let input_stream = demux.get_stream_mut(stream_idx);
avcodec_alloc_context3(input_stream.codec.as_ptr())
};
if dec_ctx.is_null() {
return Err(FilterGraphParseError::OutOfMemory.into());
}
let _codec_ctx = CodecContext::new(dec_ctx);
let ret = avcodec_parameters_to_context(dec_ctx, par);
if ret < 0 {
return Err(FilterGraphParseError::from(ret).into());
}
(*dec_ctx).pkt_timebase = (*ist).time_base;
let fallback = input_filter.opts.fallback.as_mut_ptr();
if (*dec_ctx).codec_type == AVMEDIA_TYPE_AUDIO {
(*fallback).format = (*dec_ctx).sample_fmt as i32;
(*fallback).sample_rate = (*dec_ctx).sample_rate;
let ret = av_channel_layout_copy(&mut (*fallback).ch_layout, &(*dec_ctx).ch_layout);
if ret < 0 {
return Err(FilterGraphParseError::from(ret).into());
}
} else if (*dec_ctx).codec_type == AVMEDIA_TYPE_VIDEO {
(*fallback).format = (*dec_ctx).pix_fmt as i32;
(*fallback).width = (*dec_ctx).width;
(*fallback).height = (*dec_ctx).height;
(*fallback).sample_aspect_ratio = (*dec_ctx).sample_aspect_ratio;
(*fallback).colorspace = (*dec_ctx).colorspace;
(*fallback).color_range = (*dec_ctx).color_range;
}
(*fallback).time_base = (*dec_ctx).pkt_timebase;
if demux.autorotate {
input_filter.opts.flags |= IFILTER_FLAG_AUTOROTATE;
}
let tsoffset = if demux.copy_ts {
let mut tsoffset = if demux.start_time_us.is_some() {
demux.start_time_us.unwrap()
} else {
0
};
if (*demux.in_fmt_ctx_ptr()).start_time != ffmpeg_sys_next::AV_NOPTS_VALUE {
tsoffset += (*demux.in_fmt_ctx_ptr()).start_time
}
tsoffset
} else {
0
};
if demux.start_time_us.is_some() {
input_filter.opts.trim_start_us = Some(tsoffset);
}
input_filter.opts.trim_end_us = demux.recording_time_us;
let (sender, finished_flag_list) = filter_graph.get_src_sender();
{
let input_stream = demux.get_stream_mut(stream_idx);
input_stream.add_fg_dst(sender, input_index, finished_flag_list);
};
let node = Arc::make_mut(&mut filter_graph.node);
let SchNode::Filter { inputs, .. } = node else {
unreachable!()
};
let Some(slot) = inputs.get_mut(input_index) else {
return Err(Error::Bug);
};
*slot = Some(demux.node.clone());
demux.connect_stream(stream_idx);
Ok(())
}
}
fn fg_find_input_idx_by_linklabel(
linklabel: &str,
filter_media_type: AVMediaType,
demuxs: &mut Vec<Demuxer>,
desc: &str,
) -> Result<(usize, usize)> {
let new_linklabel = if linklabel.starts_with("[") && linklabel.ends_with("]") {
if linklabel.len() <= 2 {
warn!(target: LOG_TARGET, "Filter linklabel is empty");
return Err(InvalidFilterSpecifier(desc.to_string()).into());
} else {
&linklabel[1..linklabel.len() - 1]
}
} else {
linklabel
};
let (file_idx, remainder) =
strtol(new_linklabel).map_err(|_| FilterGraphParseError::InvalidArgument)?;
if file_idx < 0 || file_idx as usize >= demuxs.len() {
return Err(InvalidFileIndexInFg(file_idx as usize, desc.to_string()).into());
}
let file_idx = file_idx as usize;
let spec_str = if remainder.is_empty() {
""
} else if remainder.starts_with(':') {
&remainder[1..]
} else {
remainder
};
let stream_spec = if spec_str.is_empty() {
let mut spec = StreamSpecifier::default();
spec.media_type = Some(filter_media_type);
spec
} else {
StreamSpecifier::parse(spec_str).map_err(|e| {
warn!(target: LOG_TARGET,
"Invalid stream specifier in filter linklabel '{}': {}",
linklabel, e
);
FilterGraphParseError::InvalidArgument
})?
};
let demux = &demuxs[file_idx];
unsafe {
let fmt_ctx = demux.in_fmt_ctx_ptr();
let mut subtitle_only_match = false;
for (idx, _) in demux.get_streams().iter().enumerate() {
let avstream = *(*fmt_ctx).streams.add(idx);
if stream_spec.matches(fmt_ctx, avstream) {
let codec_type = (*avstream).codecpar.as_ref().unwrap().codec_type;
if codec_type == filter_media_type {
return Ok((file_idx, idx));
}
if codec_type == AVMEDIA_TYPE_SUBTITLE && filter_media_type == AVMEDIA_TYPE_VIDEO {
subtitle_only_match = true;
}
}
}
if subtitle_only_match {
error!(target: LOG_TARGET,
"Stream specifier '{remainder}' in filtergraph description {desc} \
matches a subtitle stream, but subtitle streams as filtergraph \
inputs (sub2video) are not supported"
);
return Err(FilterGraphParseError::InvalidArgument.into());
}
}
warn!(target: LOG_TARGET,
"Stream specifier '{}' in filtergraph description {} matches no streams.",
remainder, desc
);
Err(FilterGraphParseError::InvalidArgument.into())
}
pub(super) fn init_filter_graphs(filter_complexs: Vec<FilterComplex>) -> Result<Vec<FilterGraph>> {
let mut filter_graphs = Vec::with_capacity(filter_complexs.len());
for (i, filter) in filter_complexs.iter().enumerate() {
let filter_graph = init_filter_graph(
i,
&filter.filter_descs,
filter.hw_device.clone(),
filter.sws_opts.clone(),
filter.swr_opts.clone(),
)?;
filter_graphs.push(filter_graph);
}
Ok(filter_graphs)
}
#[cfg(docsrs)]
pub(super) fn init_filter_graph(
fg_index: usize,
filter_desc: &str,
hw_device: Option<String>,
sws_opts: Option<String>,
swr_opts: Option<String>,
) -> Result<FilterGraph> {
Err(Error::Bug)
}
#[cfg(not(docsrs))]
pub(super) fn init_filter_graph(
fg_index: usize,
filter_desc: &str,
hw_device: Option<String>,
sws_opts: Option<String>,
swr_opts: Option<String>,
) -> Result<FilterGraph> {
let desc_cstr = CString::new(filter_desc)?;
if let Some(hw_device) = &hw_device {
let err = init_filter_hw_device(hw_device);
if err < 0 {
return Err(Error::FilterGraph(FilterGraphOperationError::ParseError(
FilterGraphParseError::from(err),
)));
}
}
unsafe {
let graph = crate::raw::FilterGraph::alloc().ok_or(FilterGraphParseError::OutOfMemory)?;
(*graph.as_ptr()).nb_threads = 1;
let mut seg = null_mut();
let mut ret = avfilter_graph_segment_parse(graph.as_ptr(), desc_cstr.as_ptr(), 0, &mut seg);
if ret < 0 {
return Err(FilterGraphParseError::from(ret).into());
}
ret = avfilter_graph_segment_create_filters(seg, 0);
if ret < 0 {
avfilter_graph_segment_free(&mut seg);
return Err(FilterGraphParseError::from(ret).into());
}
if let Some(dev) = hw_device_for_filter() {
for i in 0..(*graph.as_ptr()).nb_filters {
let f = *(*graph.as_ptr()).filters.add(i as usize);
if (*(*f).filter).flags & AVFILTER_FLAG_HWDEVICE == 0 {
continue;
}
(*f).hw_device_ctx = av_buffer_ref(dev.device_ref());
if (*f).hw_device_ctx.is_null() {
avfilter_graph_segment_free(&mut seg);
return Err(FilterGraphParseError::OutOfMemory.into());
}
}
}
#[cfg(not(docsrs))]
{
ret = graph_opts_apply(seg);
}
if ret < 0 {
avfilter_graph_segment_free(&mut seg);
return Err(FilterGraphParseError::from(ret).into());
}
let mut inputs = crate::raw::FilterInOut::empty();
let mut outputs = crate::raw::FilterInOut::empty();
ret = avfilter_graph_segment_apply(seg, 0, inputs.as_out_ptr(), outputs.as_out_ptr());
avfilter_graph_segment_free(&mut seg);
if ret < 0 {
return Err(FilterGraphParseError::from(ret).into());
}
let input_filters = inouts_to_input_filters(fg_index, inputs.as_ptr())?;
let output_filters = inouts_to_output_filters(outputs.as_ptr())?;
if output_filters.is_empty() {
return Err(FilterZeroOutputs);
}
if input_filters.is_empty() {
return Err(FilterZeroInputs);
}
let filter_graph = FilterGraph::new(
filter_desc.to_string(),
input_filters,
output_filters,
sws_opts,
swr_opts,
);
Ok(filter_graph)
}
}
unsafe fn inouts_to_input_filters(
fg_index: usize,
inouts: *mut AVFilterInOut,
) -> Result<Vec<InputFilter>> {
let mut cur = inouts;
let mut filterinouts = Vec::new();
let mut filter_index = 0;
while !cur.is_null() {
let linklabel = if (*cur).name.is_null() {
""
} else {
let linklabel = CStr::from_ptr((*cur).name);
let result = linklabel.to_str();
if result.is_err() {
return Err(FilterDescUtf8);
}
result.unwrap()
};
let filter_ctx = (*cur).filter_ctx;
let media_type = avfilter_pad_get_type((*filter_ctx).input_pads, (*cur).pad_idx);
let pads = (*filter_ctx).input_pads;
let nb_pads = (*filter_ctx).nb_inputs;
let name = describe_filter_link(cur, filter_ctx, pads, nb_pads)?;
let fallback = frame_alloc()?;
let mut filter = InputFilter::new(linklabel.to_string(), media_type, name, fallback);
filter.opts.name = format!("fg:{fg_index}:{filter_index}");
filterinouts.push(filter);
cur = (*cur).next;
filter_index += 1;
}
Ok(filterinouts)
}
unsafe fn inouts_to_output_filters(inouts: *mut AVFilterInOut) -> Result<Vec<OutputFilter>> {
let mut cur = inouts;
let mut output_filters = Vec::new();
while !cur.is_null() {
let linklabel = if (*cur).name.is_null() {
""
} else {
let linklabel = CStr::from_ptr((*cur).name);
let result = linklabel.to_str();
if result.is_err() {
return Err(FilterDescUtf8);
}
result.unwrap()
};
let filter_ctx = (*cur).filter_ctx;
let media_type = avfilter_pad_get_type((*filter_ctx).output_pads, (*cur).pad_idx);
let pads = (*filter_ctx).output_pads;
let nb_pads = (*filter_ctx).nb_outputs;
let name = describe_filter_link(cur, filter_ctx, pads, nb_pads)?;
let filter = OutputFilter::new(linklabel.to_string(), media_type, name);
output_filters.push(filter);
cur = (*cur).next;
}
Ok(output_filters)
}
unsafe fn describe_filter_link(
cur: *mut AVFilterInOut,
filter_ctx: *mut AVFilterContext,
pads: *mut AVFilterPad,
nb_pads: c_uint,
) -> Result<String> {
let filter = (*filter_ctx).filter;
let name = (*filter).name;
let name = CStr::from_ptr(name);
let result = name.to_str();
if result.is_err() {
return Err(FilterNameUtf8);
}
let name = result.unwrap();
let name = if nb_pads > 1 {
name.to_string()
} else {
let pad_name = avfilter_pad_get_name(pads, (*cur).pad_idx);
let pad_name = CStr::from_ptr(pad_name);
let result = pad_name.to_str();
if result.is_err() {
return Err(FilterNameUtf8);
}
let pad_name = result.unwrap();
format!("{name}:{pad_name}")
};
Ok(name)
}