1use crate::core::context::demuxer::Demuxer;
2use crate::core::context::ffmpeg_context_builder::FfmpegContextBuilder;
3use crate::core::context::filter_complex::FilterComplex;
4use crate::core::context::filter_graph::FilterGraph;
5use crate::core::context::input::Input;
6use crate::core::context::input_filter::{InputFilter, IFILTER_FLAG_AUTOROTATE};
7use crate::core::context::muxer::Muxer;
8use crate::core::context::output::{Output, StreamMap};
9use crate::core::metadata::StreamSpecifier;
10use crate::core::context::output_filter::{
11 OutputFilter, OFILTER_FLAG_AUDIO_24BIT, OFILTER_FLAG_AUTOSCALE, OFILTER_FLAG_DISABLE_CONVERT,
12};
13use crate::core::context::{frame_alloc, CodecContext};
14use crate::core::scheduler::ffmpeg_scheduler;
15use crate::core::scheduler::ffmpeg_scheduler::{FfmpegScheduler, Initialization};
16#[cfg(not(feature = "docs-rs"))]
17use crate::core::scheduler::filter_task::graph_opts_apply;
18use crate::core::scheduler::input_controller::SchNode;
19use crate::error::Error::{
20 FileSameAsInput, FilterDescUtf8, FilterNameUtf8, FilterZeroOutputs,
21 FrameFilterStreamTypeNoMatched, FrameFilterTypeNoMatched, ParseInteger,
22};
23use crate::error::FilterGraphParseError::{
24 InvalidFileIndexInFg, InvalidFilterSpecifier, OutputUnconnected,
25};
26use crate::error::{
27 AllocOutputContextError, FilterGraphParseError, FindStreamError, OpenInputError,
28 OpenOutputError,
29};
30use crate::error::{Error, Result};
31use crate::filter::frame_pipeline::FramePipeline;
32use crate::util::ffmpeg_utils::{hashmap_to_avdictionary, DictGuard};
33#[cfg(not(feature = "docs-rs"))]
34use ffmpeg_sys_next::AVChannelOrder::AV_CHANNEL_ORDER_UNSPEC;
35#[cfg(not(feature = "docs-rs"))]
36use ffmpeg_sys_next::{
37 avformat_query_codec, AVSTREAM_EVENT_FLAG_NEW_PACKETS, AV_DISPOSITION_ATTACHED_PIC,
38 AV_DISPOSITION_DEFAULT,
39};
40#[cfg(not(feature = "docs-rs"))]
41use ffmpeg_sys_next::AVCodecConfig::*;
42use ffmpeg_sys_next::AVCodecID::{AV_CODEC_ID_AC3, AV_CODEC_ID_MP3, AV_CODEC_ID_NONE};
43use ffmpeg_sys_next::AVColorRange::AVCOL_RANGE_UNSPECIFIED;
44use ffmpeg_sys_next::AVColorSpace::AVCOL_SPC_UNSPECIFIED;
45use ffmpeg_sys_next::AVMediaType::{
46 AVMEDIA_TYPE_ATTACHMENT, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_DATA, AVMEDIA_TYPE_SUBTITLE,
47 AVMEDIA_TYPE_VIDEO,
48};
49use ffmpeg_sys_next::AVPixelFormat::AV_PIX_FMT_NONE;
50use ffmpeg_sys_next::AVSampleFormat::AV_SAMPLE_FMT_NONE;
51use ffmpeg_sys_next::{
52 av_add_q, av_channel_layout_default, av_codec_get_id, av_codec_get_tag2,
53 av_freep, av_get_exact_bits_per_sample, av_get_pix_fmt, av_guess_codec, av_guess_format, av_guess_frame_rate,
54 av_inv_q, av_malloc, av_rescale_q, av_seek_frame, avcodec_alloc_context3,
55 avcodec_descriptor_get, avcodec_descriptor_get_by_name, avcodec_find_encoder,
56 avcodec_find_encoder_by_name, avcodec_get_name, avcodec_parameters_from_context,
57 avcodec_parameters_to_context, avfilter_graph_alloc, avfilter_graph_free, avfilter_inout_free,
58 avfilter_pad_get_name, avfilter_pad_get_type, avformat_alloc_context,
59 avformat_alloc_output_context2, avformat_close_input, avformat_find_stream_info,
60 avformat_flush, avformat_free_context, avformat_open_input, avio_alloc_context,
61 avio_open2, AVCodec, AVCodecID, AVColorRange, AVColorSpace, AVFilterContext,
62 AVFilterInOut, AVFilterPad, AVFormatContext, AVMediaType, AVOutputFormat, AVPixelFormat,
63 AVRational, AVSampleFormat, AVStream, AVERROR_ENCODER_NOT_FOUND, AVFMT_FLAG_CUSTOM_IO,
64 AVFMT_GLOBALHEADER, AVFMT_NOBINSEARCH, AVFMT_NOFILE, AVFMT_NOGENSEARCH, AVFMT_NOSTREAMS,
65 AVIO_FLAG_WRITE, AVSEEK_FLAG_BACKWARD, AV_CODEC_PROP_BITMAP_SUB, AV_CODEC_PROP_TEXT_SUB,
66 AV_TIME_BASE,
67};
68#[cfg(not(feature = "docs-rs"))]
69use ffmpeg_sys_next::{
70 av_channel_layout_copy, av_packet_side_data_new, avcodec_get_supported_config,
71 avfilter_graph_segment_apply, avfilter_graph_segment_create_filters,
72 avfilter_graph_segment_free, avfilter_graph_segment_parse, AVChannelLayout,
73};
74use log::{debug, error, info, warn};
75use std::collections::HashMap;
76use std::ffi::{c_uint, c_void, CStr, CString};
77use std::ptr::{null, null_mut};
78use std::sync::Arc;
79
80pub struct FfmpegContext {
81 pub(crate) independent_readrate: bool,
82 pub(crate) demuxs: Vec<Demuxer>,
83 pub(crate) filter_graphs: Vec<FilterGraph>,
84 pub(crate) muxs: Vec<Muxer>,
85 pub(crate) scheduler_status: Arc<std::sync::atomic::AtomicUsize>,
88 pub(crate) interrupt_state: Arc<crate::core::context::InterruptState>,
89}
90
91unsafe impl Send for FfmpegContext {}
97
98impl FfmpegContext {
99 pub fn builder() -> FfmpegContextBuilder {
112 FfmpegContextBuilder::new()
113 }
114
115 pub fn start(self) -> Result<FfmpegScheduler<ffmpeg_scheduler::Running>> {
141 let ffmpeg_scheduler = FfmpegScheduler::new(self);
142 ffmpeg_scheduler.start()
143 }
144
145 #[allow(dead_code)]
146 pub(crate) fn new(
147 inputs: Vec<Input>,
148 filter_complexs: Vec<FilterComplex>,
149 outputs: Vec<Output>,
150 ) -> Result<FfmpegContext> {
151 Self::new_with_options(false, inputs, filter_complexs, outputs, false)
152 }
153
154 pub(crate) fn new_with_options(
155 mut independent_readrate: bool,
156 mut inputs: Vec<Input>,
157 filter_complexs: Vec<FilterComplex>,
158 mut outputs: Vec<Output>,
159 copy_ts: bool,
160 ) -> Result<FfmpegContext> {
161 check_duplicate_inputs_outputs(&inputs, &outputs)?;
162
163 crate::core::initialize_ffmpeg();
164
165 let scheduler_status = Arc::new(std::sync::atomic::AtomicUsize::new(
169 crate::core::scheduler::ffmpeg_scheduler::STATUS_INIT,
170 ));
171 let interrupt_state = Arc::new(crate::core::context::InterruptState::new(
172 scheduler_status.clone(),
173 ));
174
175 let mut demuxs = open_input_files(&mut inputs, copy_ts, &interrupt_state)?;
176
177 if demuxs.len() <= 1 {
178 independent_readrate = false;
179 }
180
181 let mut filter_graphs = if !filter_complexs.is_empty() {
182 let mut filter_graphs = init_filter_graphs(filter_complexs)?;
183 fg_bind_inputs(&mut filter_graphs, &mut demuxs)?;
184 filter_graphs
185 } else {
186 Vec::new()
187 };
188
189 let mut muxs = open_output_files(&mut outputs, copy_ts, &interrupt_state)?;
190
191 outputs_bind(&mut muxs, &mut filter_graphs, &mut demuxs)?;
192
193 for mux in muxs.iter_mut() {
200 if mux.recording_time_us.is_none() {
201 let mapping = mux.stream_input_mapping();
202 if !mapping.is_empty() {
203 let first_input = mapping[0].1.0;
204 let all_same_input = mapping.iter().all(|(_, (idx, _))| *idx == first_input);
205 if all_same_input {
206 if let Some(demux) = demuxs.get(first_input) {
207 if let Some(recording_time) = demux.recording_time_us {
208 mux.recording_time_us = Some(recording_time);
209 }
210 }
211 }
212 }
213 }
214 }
215
216 correct_input_start_times(&mut demuxs, copy_ts);
217
218 check_output_streams(&muxs)?;
219
220 check_fg_bindings(&filter_graphs)?;
221
222 check_frame_filter_pipeline(&muxs, &demuxs)?;
223
224 Ok(Self {
225 independent_readrate,
226 demuxs,
227 filter_graphs,
228 muxs,
229 scheduler_status,
230 interrupt_state,
231 })
232 }
233}
234
235const START_AT_ZERO: bool = false;
236
237fn correct_input_start_times(demuxs: &mut Vec<Demuxer>, copy_ts: bool) {
238 for (i, demux) in demuxs.iter_mut().enumerate() {
239 unsafe {
240 let is = demux.in_fmt_ctx;
241
242 demux.start_time_effective = (*is).start_time;
243 if (*is).start_time == ffmpeg_sys_next::AV_NOPTS_VALUE
244 || (*(*is).iformat).flags & ffmpeg_sys_next::AVFMT_TS_DISCONT == 0
245 {
246 continue;
247 }
248
249 let mut new_start_time = i64::MAX;
250 let stream_count = (*is).nb_streams;
251 for j in 0..stream_count {
252 let st = *(*is).streams.add(j as usize);
253 if (*st).discard == ffmpeg_sys_next::AVDiscard::AVDISCARD_ALL
254 || (*st).start_time == ffmpeg_sys_next::AV_NOPTS_VALUE
255 {
256 continue;
257 }
258 new_start_time = std::cmp::min(
259 new_start_time,
260 av_rescale_q(
261 (*st).start_time,
262 (*st).time_base,
263 ffmpeg_sys_next::AV_TIME_BASE_Q,
264 ),
265 );
266 }
267 let diff = new_start_time - (*is).start_time;
268 if diff != 0 {
269 debug!("Correcting start time of Input #{i} by {diff}us.");
270 demux.start_time_effective = new_start_time;
271 if copy_ts && START_AT_ZERO {
272 demux.ts_offset = -new_start_time;
273 } else if !copy_ts {
274 let abs_start_seek = (*is).start_time + demux.start_time_us.unwrap_or(0);
275 demux.ts_offset = if abs_start_seek > new_start_time {
276 -abs_start_seek
277 } else {
278 -new_start_time
279 };
280 } else if copy_ts {
281 demux.ts_offset = 0;
282 }
283
284 }
286 }
287 }
288}
289
290fn check_pipeline<T>(
291 frame_pipelines: Option<&Vec<FramePipeline>>,
292 streams: &[T],
293 tag: &str,
294 get_stream_index: impl Fn(&T) -> usize,
295 get_codec_type: impl Fn(&T) -> &AVMediaType,
296) -> Result<()> {
297 let tag_cap = {
298 let mut chars = tag.chars();
299 match chars.next() {
300 None => String::new(),
301 Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
302 }
303 };
304
305 frame_pipelines
306 .into_iter()
307 .flat_map(|pipelines| pipelines.iter())
308 .try_for_each(|pipeline| {
309 if let Some(idx) = pipeline.stream_index {
310 streams
311 .iter()
312 .any(|s| {
313 get_stream_index(s) == idx && get_codec_type(s) == &pipeline.media_type
314 })
315 .then_some(())
316 .ok_or_else(|| {
317 Into::<crate::error::Error>::into(FrameFilterStreamTypeNoMatched(
318 tag_cap.clone(),
319 idx,
320 format!("{:?}", pipeline.media_type),
321 ))
322 })
323 } else {
324 streams
325 .iter()
326 .any(|s| get_codec_type(s) == &pipeline.media_type)
327 .then_some(())
328 .ok_or_else(|| {
329 FrameFilterTypeNoMatched(tag.into(), format!("{:?}", pipeline.media_type))
330 })
331 }
332 })?;
333 Ok(())
334}
335
336fn check_frame_filter_pipeline(muxs: &[Muxer], demuxs: &[Demuxer]) -> Result<()> {
337 muxs.iter().try_for_each(|mux| {
338 check_pipeline(
339 mux.frame_pipelines.as_ref(),
340 mux.get_streams(),
341 "output",
342 |s| s.stream_index,
343 |s| &s.codec_type,
344 )
345 })?;
346 demuxs.iter().try_for_each(|demux| {
347 check_pipeline(
348 demux.frame_pipelines.as_ref(),
349 demux.get_streams(),
350 "input",
351 |s| s.stream_index,
352 |s| &s.codec_type,
353 )
354 })?;
355 Ok(())
356}
357
358fn check_fg_bindings(filter_graphs: &Vec<FilterGraph>) -> Result<()> {
359 for filter_graph in filter_graphs {
361 for (i, output_filter) in filter_graph.outputs.iter().enumerate() {
362 if !output_filter.has_dst() {
363 let linklabel = if output_filter.linklabel.is_empty() {
364 "unlabeled".to_string()
365 } else {
366 output_filter.linklabel.clone()
367 };
368 return Err(OutputUnconnected(output_filter.name.clone(), i, linklabel).into());
369 }
370 }
371 }
372 Ok(())
373}
374
375impl From<FfmpegContext> for FfmpegScheduler<Initialization> {
376 fn from(val: FfmpegContext) -> Self {
377 FfmpegScheduler::new(val)
378 }
379}
380
381fn check_output_streams(muxs: &Vec<Muxer>) -> Result<()> {
382 for mux in muxs {
383 unsafe {
384 let oformat = (*mux.out_fmt_ctx).oformat;
385 if !mux.has_src() && (*oformat).flags & AVFMT_NOSTREAMS == 0 {
386 warn!("Output file does not contain any stream");
387 return Err(OpenOutputError::NotContainStream.into());
388 }
389 }
390 }
391 Ok(())
392}
393
394unsafe fn process_metadata(mux: &Muxer, demuxs: &Vec<Demuxer>) -> Result<()> {
402 use crate::core::metadata::MetadataType;
403 use crate::core::metadata::{
404 copy_chapters_from_input, copy_metadata, copy_metadata_default, of_add_metadata,
405 };
406
407 let input_ctxs: Vec<*const AVFormatContext> = demuxs
409 .iter()
410 .map(|d| d.in_fmt_ctx as *const AVFormatContext)
411 .collect();
412
413 let mut metadata_global_manual = false;
414 let mut metadata_streams_manual = false;
415 let mut metadata_chapters_manual = false;
416
417 let mut mark_manual = |meta_type: &MetadataType| -> () {
420 match meta_type {
421 MetadataType::Global => metadata_global_manual = true,
422 MetadataType::Stream(_) => metadata_streams_manual = true,
423 MetadataType::Chapter(_) => metadata_chapters_manual = true,
424 MetadataType::Program(_) => {}
425 }
426 };
427
428 for mapping in &mux.metadata_map {
429 mark_manual(&mapping.src_type);
430 mark_manual(&mapping.dst_type);
431
432 if mapping.input_index >= input_ctxs.len() {
433 log::warn!(
434 "Metadata mapping references non-existent input file index {}",
435 mapping.input_index
436 );
437 continue;
438 }
439
440 let input_ctx = input_ctxs[mapping.input_index];
441 if let Err(e) = copy_metadata(
442 input_ctx,
443 mux.out_fmt_ctx,
444 &mapping.src_type,
445 &mapping.dst_type,
446 ) {
447 log::warn!("Failed to copy metadata from mapping: {}", e);
448 }
449 }
450
451 if mux.auto_copy_metadata && !metadata_chapters_manual {
453 if let Some(source_demux) = demuxs
454 .iter()
455 .find(|d| unsafe { !d.in_fmt_ctx.is_null() && (*d.in_fmt_ctx).nb_chapters > 0 })
456 {
457 if let Err(e) = copy_chapters_from_input(
458 source_demux.in_fmt_ctx,
459 source_demux.ts_offset,
460 mux.out_fmt_ctx,
461 mux.start_time_us,
462 mux.recording_time_us,
463 true,
464 ) {
465 log::warn!("Failed to copy chapters: {}", e);
466 }
467 }
468 }
469
470 let stream_input_mapping = mux.stream_input_mapping();
473 let encoding_streams = mux.encoding_streams();
474
475 if mux.auto_copy_metadata {
476 if let Err(e) = copy_metadata_default(
477 &input_ctxs,
478 mux.out_fmt_ctx,
479 mux.nb_streams,
480 &stream_input_mapping,
481 &encoding_streams,
482 mux.recording_time_us.is_some(),
483 mux.auto_copy_metadata,
484 metadata_global_manual,
485 metadata_streams_manual,
486 ) {
487 log::warn!("Failed to apply default metadata behavior: {}", e);
488 }
489 }
490
491 if let Err(e) = of_add_metadata(
494 mux.out_fmt_ctx,
495 &mux.global_metadata,
496 &mux.stream_metadata,
497 &mux.chapter_metadata,
498 &mux.program_metadata,
499 ) {
500 log::warn!("Failed to add user metadata: {}", e);
501 }
502
503 Ok(())
504}
505
506fn is_filter_output_linklabel(linklabel: &str) -> bool {
509 linklabel.starts_with('[')
510}
511
512unsafe fn expand_stream_maps(
516 mux: &mut Muxer,
517 demuxs: &[Demuxer],
518) -> Result<()> {
519 let stream_map_specs = std::mem::take(&mut mux.stream_map_specs);
520
521 for spec in stream_map_specs {
522 let (linklabel, is_negative) = if spec.linklabel.starts_with('-') {
524 (&spec.linklabel[1..], true)
525 } else {
526 (spec.linklabel.as_str(), false)
527 };
528
529 if is_filter_output_linklabel(linklabel) {
531 let pure_linklabel = if linklabel.starts_with('[') {
533 if let Some(end_pos) = linklabel.find(']') {
535 &linklabel[1..end_pos]
536 } else {
537 warn!("Invalid output link label: {}.", linklabel);
538 return Err(Error::OpenOutput(OpenOutputError::InvalidArgument));
539 }
540 } else {
541 linklabel
542 };
543
544 if pure_linklabel.is_empty() {
546 warn!("Invalid output link label: {}.", linklabel);
547 return Err(Error::OpenOutput(OpenOutputError::InvalidArgument));
548 }
549
550 mux.stream_maps.push(StreamMap {
552 file_index: 0, stream_index: 0, linklabel: Some(pure_linklabel.to_string()),
555 copy: spec.copy,
556 disabled: false,
557 });
558 continue;
559 }
560
561 let parse_result = strtol(linklabel);
564 if parse_result.is_err() {
565 mux.stream_maps.push(StreamMap {
568 file_index: 0,
569 stream_index: 0,
570 linklabel: Some(linklabel.to_string()),
571 copy: spec.copy,
572 disabled: false,
573 });
574 continue;
575 }
576
577 let (file_idx, remainder) = parse_result.unwrap();
578
579 if file_idx < 0 || file_idx as usize >= demuxs.len() {
581 warn!("Invalid input file index: {}.", file_idx);
582 return Err(Error::OpenOutput(OpenOutputError::InvalidArgument));
583 }
584 let file_idx = file_idx as usize;
585
586 let (spec_str, allow_unused) = if remainder.ends_with('?') {
589 (&remainder[..remainder.len()-1], true)
590 } else {
591 (remainder, false)
592 };
593
594 let stream_spec = if spec_str.is_empty() {
595 StreamSpecifier::default()
597 } else {
598 let spec_str = if spec_str.starts_with(':') {
600 &spec_str[1..]
601 } else {
602 spec_str
603 };
604
605 StreamSpecifier::parse(spec_str).map_err(|e| {
606 warn!("Invalid stream specifier in '{}': {}", linklabel, e);
607 Error::OpenOutput(OpenOutputError::InvalidArgument)
608 })?
609 };
610
611 if is_negative {
613 for existing_map in &mut mux.stream_maps {
614 if existing_map.linklabel.is_none() &&
616 existing_map.file_index == file_idx {
617 let demux = &demuxs[file_idx];
619 let fmt_ctx = demux.in_fmt_ctx;
620 let avstream = *(*fmt_ctx).streams.add(existing_map.stream_index);
621
622 if stream_spec.matches(fmt_ctx, avstream) {
623 existing_map.disabled = true;
624 }
625 }
626 }
627 continue;
629 }
630
631 let demux = &demuxs[file_idx];
633 let fmt_ctx = demux.in_fmt_ctx;
634 let mut matched_count = 0;
635
636 for (stream_idx, _) in demux.get_streams().iter().enumerate() {
637 let avstream = *(*fmt_ctx).streams.add(stream_idx);
638
639 if stream_spec.matches(fmt_ctx, avstream) {
641 mux.stream_maps.push(StreamMap {
642 file_index: file_idx,
643 stream_index: stream_idx,
644 linklabel: None,
645 copy: spec.copy,
646 disabled: false,
647 });
648 matched_count += 1;
649 }
650 }
651
652 if matched_count == 0 {
654 if allow_unused {
655 info!(
657 "Stream map '{}' matches no streams; ignoring.",
658 linklabel
659 );
660 } else {
661 warn!(
663 "Stream map '{}' matches no streams.\n\
664 To ignore this, add a trailing '?' to the map.",
665 linklabel
666 );
667 return Err(Error::OpenOutput(
668 OpenOutputError::MatchesNoStreams(linklabel.to_string())
669 ));
670 }
671 }
672 }
673
674 Ok(())
675}
676
677fn outputs_bind(
678 muxs: &mut Vec<Muxer>,
679 filter_graphs: &mut Vec<FilterGraph>,
680 demuxs: &mut Vec<Demuxer>,
681) -> Result<()> {
682 unsafe {
687 for mux in muxs.iter_mut() {
688 if !mux.stream_map_specs.is_empty() {
689 expand_stream_maps(mux, demuxs)?;
690 }
691 }
692 }
693
694 for (i, mux) in muxs.iter_mut().enumerate() {
695 if mux.stream_maps.is_empty() {
696 let mut auto_disable = 0i32;
700 if mux.video_disable {
701 auto_disable |= 1 << (AVMEDIA_TYPE_VIDEO as i32);
702 }
703 if mux.audio_disable {
704 auto_disable |= 1 << (AVMEDIA_TYPE_AUDIO as i32);
705 }
706 if mux.subtitle_disable {
707 auto_disable |= 1 << (AVMEDIA_TYPE_SUBTITLE as i32);
708 }
709 if mux.data_disable {
710 auto_disable |= 1 << (AVMEDIA_TYPE_DATA as i32);
711 }
712 output_bind_by_unlabeled_filter(i, mux, filter_graphs, &mut auto_disable)?;
713 map_auto_streams(i, mux, demuxs, filter_graphs, auto_disable)?;
715 } else {
716 for stream_map in mux.stream_maps.clone() {
717 map_manual(i, mux, &stream_map, filter_graphs, demuxs)?;
718 }
719 }
720
721 unsafe {
725 process_metadata(mux, demuxs)?;
726 }
727 }
728
729 Ok(())
730}
731
732fn map_manual(
733 index: usize,
734 mux: &mut Muxer,
735 stream_map: &StreamMap,
736 filter_graphs: &mut Vec<FilterGraph>,
737 demuxs: &mut Vec<Demuxer>,
738) -> Result<()> {
739 if stream_map.disabled {
741 return Ok(());
742 }
743
744 if let Some(linklabel) = &stream_map.linklabel {
746 for filter_graph in filter_graphs.iter_mut() {
748 for i in 0..filter_graph.outputs.len() {
749 let option = {
750 let output_filter = &filter_graph.outputs[i];
751 if output_filter.has_dst()
752 || output_filter.linklabel.is_empty()
753 || &output_filter.linklabel != linklabel
754 {
755 continue;
756 }
757
758 choose_encoder(mux, output_filter.media_type)?
759 };
760
761 match option {
762 None => {
763 error!(
766 "Filtering and streamcopy cannot be used together. \
767 No encoder available for filter output type {:?}.",
768 filter_graph.outputs[i].media_type
769 );
770 return Err(OpenOutputError::InvalidArgument.into());
771 }
772 Some((codec_id, enc)) => {
773 return ofilter_bind_ost(
774 index,
775 mux,
776 filter_graph,
777 i,
778 codec_id,
779 enc,
780 None,
781 false,
782 )
783 .map(|_| ());
784 }
785 }
786 }
787 }
788
789 warn!(
791 "Output with label '{}' does not exist in any defined filter graph, \
792 or was already used elsewhere.",
793 linklabel
794 );
795 return Err(OpenOutputError::InvalidArgument.into());
796 }
797
798 let demux_idx = stream_map.file_index;
801 let stream_index = stream_map.stream_index;
802
803 let demux = &mut demuxs[demux_idx];
804
805 let demux_node = demux.node.clone();
807 let (media_type, input_stream_duration, input_stream_time_base) = {
808 let input_stream = demux.get_stream_mut(stream_index);
809 (input_stream.codec_type, input_stream.duration, input_stream.time_base)
810 };
811
812 match media_type {
816 AVMEDIA_TYPE_VIDEO if mux.video_disable => {
817 info!("Skipping video stream mapping (video_disable=true)");
818 return Ok(());
819 }
820 AVMEDIA_TYPE_AUDIO if mux.audio_disable => {
821 info!("Skipping audio stream mapping (audio_disable=true)");
822 return Ok(());
823 }
824 AVMEDIA_TYPE_SUBTITLE if mux.subtitle_disable => {
825 info!("Skipping subtitle stream mapping (subtitle_disable=true)");
826 return Ok(());
827 }
828 AVMEDIA_TYPE_DATA if mux.data_disable => {
829 info!("Skipping data stream mapping (data_disable=true)");
830 return Ok(());
831 }
832 _ => {}
833 }
834
835 info!(
836 "Binding output stream to input {}:{} ({})",
837 demux_idx, stream_index,
838 match media_type {
839 AVMEDIA_TYPE_VIDEO => "video",
840 AVMEDIA_TYPE_AUDIO => "audio",
841 AVMEDIA_TYPE_SUBTITLE => "subtitle",
842 AVMEDIA_TYPE_DATA => "data",
843 AVMEDIA_TYPE_ATTACHMENT => "attachment",
844 _ => "unknown",
845 }
846 );
847
848 let option = choose_encoder(mux, media_type)?;
849
850 match option {
851 None => {
852 let (packet_sender, _st, output_stream_index) = mux.new_stream(demux_node)?;
854 demux.add_packet_dst(packet_sender, stream_index, output_stream_index);
855 mux.register_stream_source(output_stream_index, demux_idx, stream_index, false);
856
857 unsafe {
858 streamcopy_init(
859 mux,
860 *(*demux.in_fmt_ctx).streams.add(stream_index),
861 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
862 demux.framerate,
863 )?;
864 rescale_duration(
865 input_stream_duration,
866 input_stream_time_base,
867 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
868 );
869 mux.stream_ready()
870 }
871 }
872 Some((codec_id, enc)) => {
873 if stream_map.copy {
875 let (packet_sender, _st, output_stream_index) = mux.new_stream(demux_node)?;
877 demux.add_packet_dst(packet_sender, stream_index, output_stream_index);
878 mux.register_stream_source(output_stream_index, demux_idx, stream_index, false);
879
880 unsafe {
881 streamcopy_init(
882 mux,
883 *(*demux.in_fmt_ctx).streams.add(stream_index),
884 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
885 demux.framerate,
886 )?;
887 rescale_duration(
888 input_stream_duration,
889 input_stream_time_base,
890 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
891 );
892 mux.stream_ready()
893 }
894 } else if media_type == AVMEDIA_TYPE_VIDEO || media_type == AVMEDIA_TYPE_AUDIO {
895 init_simple_filtergraph(
896 demux,
897 stream_index,
898 codec_id,
899 enc,
900 index,
901 mux,
902 filter_graphs,
903 demux_idx,
904 )?;
905 } else {
906 let (frame_sender, output_stream_index) =
907 mux.add_enc_stream(media_type, enc, demux_node, false)?;
908 let input_stream = demux.get_stream_mut(stream_index);
909 input_stream.add_dst(frame_sender);
910 demux.connect_stream(stream_index);
911 mux.register_stream_source(output_stream_index, demux_idx, stream_index, true);
912
913 unsafe {
914 rescale_duration(
915 input_stream_duration,
916 input_stream_time_base,
917 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
918 );
919 }
920 }
921 }
922 }
923
924 Ok(())
925}
926
927#[cfg(not(feature = "docs-rs"))]
928fn set_channel_layout(
929 ch_layout: &mut AVChannelLayout,
930 ch_layouts: &Option<Vec<AVChannelLayout>>,
931 layout_requested: &AVChannelLayout,
932) -> Result<()> {
933 unsafe {
934 if layout_requested.order != AV_CHANNEL_ORDER_UNSPEC {
936 let ret = av_channel_layout_copy(ch_layout, layout_requested);
937 if ret < 0 {
938 return Err(OpenOutputError::from(ret).into());
939 }
940 return Ok(());
941 }
942
943 if ch_layouts.is_none() {
946 av_channel_layout_default(ch_layout, layout_requested.nb_channels);
947 return Ok(());
948 }
949
950 if let Some(layouts) = ch_layouts {
952 for layout in layouts {
953 if layout.nb_channels == layout_requested.nb_channels {
954 let ret = av_channel_layout_copy(ch_layout, layout);
955 if ret < 0 {
956 return Err(OpenOutputError::from(ret).into());
957 }
958 return Ok(());
959 }
960 }
961 }
962
963 av_channel_layout_default(ch_layout, layout_requested.nb_channels);
965 Ok(())
966 }
967}
968
969#[cfg(feature = "docs-rs")]
970fn configure_output_filter_opts(
971 index: usize,
972 mux: &mut Muxer,
973 output_filter: &mut OutputFilter,
974 codec_id: AVCodecID,
975 enc: *const AVCodec,
976 output_stream_index: usize,
977) -> Result<()> {
978 Ok(())
979}
980
981#[cfg(not(feature = "docs-rs"))]
982fn configure_output_filter_opts(
983 index: usize,
984 mux: &mut Muxer,
985 output_filter: &mut OutputFilter,
986 codec_id: AVCodecID,
987 enc: *const AVCodec,
988 output_stream_index: usize,
989) -> Result<()> {
990 unsafe {
991 output_filter.opts.name = format!("#{index}:{output_stream_index}");
992 output_filter.opts.enc = enc;
993 output_filter.opts.trim_start_us = mux.start_time_us;
994 output_filter.opts.trim_duration_us = mux.recording_time_us;
995 output_filter.opts.ts_offset = mux.start_time_us;
996
997 output_filter.opts.flags = OFILTER_FLAG_DISABLE_CONVERT
998 | OFILTER_FLAG_AUTOSCALE
999 | if av_get_exact_bits_per_sample(codec_id) == 24 {
1000 OFILTER_FLAG_AUDIO_24BIT
1001 } else {
1002 0
1003 };
1004
1005 let enc_ctx = avcodec_alloc_context3(enc);
1006 if enc_ctx.is_null() {
1007 return Err(OpenOutputError::OutOfMemory.into());
1008 }
1009 let _codec_ctx = CodecContext::new(enc_ctx);
1010
1011 (*enc_ctx).thread_count = 0;
1012
1013 if output_filter.media_type == AVMEDIA_TYPE_VIDEO {
1014 let mut formats: *const AVPixelFormat = null();
1016 let mut ret = avcodec_get_supported_config(
1017 enc_ctx,
1018 null(),
1019 AV_CODEC_CONFIG_PIX_FORMAT,
1020 0,
1021 &mut formats as *mut _ as *mut *const libc::c_void,
1022 null_mut(),
1023 );
1024 if ret < 0 {
1025 return Err(OpenOutputError::from(ret).into());
1026 }
1027
1028 let mut current = formats;
1029 let mut format_list = Vec::new();
1030 let mut count = 0;
1031 const MAX_FORMATS: usize = 512;
1032 while !current.is_null() && *current != AV_PIX_FMT_NONE && count < MAX_FORMATS {
1033 format_list.push(*current);
1034 current = current.add(1);
1035 count += 1;
1036 }
1037 if count >= MAX_FORMATS {
1038 warn!("Reached maximum format limit");
1039 }
1040 output_filter.opts.formats = Some(format_list);
1041
1042 let mut framerates: *const AVRational = null();
1044 ret = avcodec_get_supported_config(
1045 enc_ctx,
1046 null(),
1047 AV_CODEC_CONFIG_FRAME_RATE,
1048 0,
1049 &mut framerates as *mut _ as *mut *const libc::c_void,
1050 null_mut(),
1051 );
1052 if ret < 0 {
1053 return Err(OpenOutputError::from(ret).into());
1054 }
1055 let mut framerate_list = Vec::new();
1056 let mut current = framerates;
1057 let mut count = 0;
1058 const MAX_FRAMERATES: usize = 64;
1059 while !current.is_null()
1060 && (*current).num != 0
1061 && (*current).den != 0
1062 && count < MAX_FRAMERATES
1063 {
1064 framerate_list.push(*current);
1065 current = current.add(1);
1066 count += 1;
1067 }
1068 if count >= MAX_FRAMERATES {
1069 warn!("Reached maximum framerate limit");
1070 }
1071 output_filter.opts.framerates = Some(framerate_list);
1072
1073 if let Some(framerate) = mux.framerate {
1074 output_filter.opts.framerate = framerate;
1075 }
1076
1077 if let Some(framerate_max) = mux.framerate_max {
1080 if mux.framerate.is_some() {
1081 error!("Only one of framerate and framerate_max can be set for an output");
1082 return Err(Error::OpenOutput(OpenOutputError::InvalidArgument));
1083 }
1084 output_filter.opts.framerate_max = framerate_max;
1085 }
1086
1087 if let Some(pix_fmt) = mux.pix_fmt {
1090 output_filter.opts.format = pix_fmt;
1091 }
1092
1093 let mut color_spaces: *const AVColorSpace = null();
1095 ret = avcodec_get_supported_config(
1096 enc_ctx,
1097 null(),
1098 AV_CODEC_CONFIG_COLOR_SPACE,
1099 0,
1100 &mut color_spaces as *mut _ as *mut *const libc::c_void,
1101 null_mut(),
1102 );
1103 if ret < 0 {
1104 return Err(OpenOutputError::from(ret).into());
1105 }
1106 let mut color_space_list = Vec::new();
1107 let mut current = color_spaces;
1108 let mut count = 0;
1109 const MAX_COLOR_SPACES: usize = 128;
1110 while !current.is_null()
1111 && *current != AVCOL_SPC_UNSPECIFIED
1112 && count < MAX_COLOR_SPACES
1113 {
1114 color_space_list.push(*current);
1115 current = current.add(1);
1116 count += 1;
1117 }
1118 if count >= MAX_COLOR_SPACES {
1119 warn!("Reached maximum color space limit");
1120 }
1121 output_filter.opts.color_spaces = Some(color_space_list);
1122
1123 let mut color_ranges: *const AVColorRange = null();
1125 ret = avcodec_get_supported_config(
1126 enc_ctx,
1127 null(),
1128 AV_CODEC_CONFIG_COLOR_RANGE,
1129 0,
1130 &mut color_ranges as *mut _ as *mut *const libc::c_void,
1131 null_mut(),
1132 );
1133 if ret < 0 {
1134 return Err(OpenOutputError::from(ret).into());
1135 }
1136 let mut color_range_list = Vec::new();
1137 let mut current = color_ranges;
1138 let mut count = 0;
1139 const MAX_COLOR_RANGES: usize = 64;
1140 while !current.is_null()
1141 && *current != AVCOL_RANGE_UNSPECIFIED
1142 && count < MAX_COLOR_RANGES
1143 {
1144 color_range_list.push(*current);
1145 current = current.add(1);
1146 count += 1;
1147 }
1148 if count >= MAX_COLOR_RANGES {
1149 warn!("Reached maximum color range limit");
1150 }
1151 output_filter.opts.color_ranges = Some(color_range_list);
1152
1153 let stream = &mux.get_streams()[output_stream_index];
1154 output_filter.opts.vsync_method = stream.vsync_method;
1155 } else {
1156 if let Some(sample_fmt) = &mux.audio_sample_fmt {
1157 output_filter.opts.audio_format = *sample_fmt;
1158 }
1159 let mut audio_formats: *const AVSampleFormat = null();
1161 let mut ret = avcodec_get_supported_config(
1162 enc_ctx,
1163 null(),
1164 AV_CODEC_CONFIG_SAMPLE_FORMAT,
1165 0,
1166 &mut audio_formats as *mut _ as *mut _,
1167 null_mut(),
1168 );
1169 if ret < 0 {
1170 return Err(OpenOutputError::from(ret).into());
1171 }
1172
1173 let mut current = audio_formats;
1174 let mut audio_format_list = Vec::new();
1175 let mut count = 0;
1176 const MAX_AUDIO_FORMATS: usize = 32;
1177 while !current.is_null() && *current != AV_SAMPLE_FMT_NONE && count < MAX_AUDIO_FORMATS
1178 {
1179 audio_format_list.push(*current);
1180 current = current.add(1);
1181 count += 1;
1182 }
1183 if count >= MAX_AUDIO_FORMATS {
1184 warn!("Reached maximum audio format limit");
1185 }
1186 output_filter.opts.audio_formats = Some(audio_format_list);
1187
1188 if let Some(audio_sample_rate) = &mux.audio_sample_rate {
1189 output_filter.opts.sample_rate = *audio_sample_rate;
1190 }
1191 let mut rates: *const i32 = null();
1193 ret = avcodec_get_supported_config(
1194 enc_ctx,
1195 null(),
1196 AV_CODEC_CONFIG_SAMPLE_RATE,
1197 0,
1198 &mut rates as *mut _ as *mut _,
1199 null_mut(),
1200 );
1201 if ret < 0 {
1202 return Err(OpenOutputError::from(ret).into());
1203 }
1204 let mut rate_list = Vec::new();
1205 let mut current = rates;
1206 let mut count = 0;
1207 const MAX_SAMPLE_RATES: usize = 64;
1208 while !current.is_null() && *current != 0 && count < MAX_SAMPLE_RATES {
1209 rate_list.push(*current);
1210 current = current.add(1);
1211 count += 1;
1212 }
1213 if count >= MAX_SAMPLE_RATES {
1214 warn!("Reached maximum sample rate limit");
1215 }
1216 output_filter.opts.sample_rates = Some(rate_list);
1217
1218 if let Some(channels) = &mux.audio_channels {
1219 output_filter.opts.ch_layout.nb_channels = *channels;
1220 }
1221 let mut layouts: *const AVChannelLayout = null();
1223 ret = avcodec_get_supported_config(
1224 enc_ctx,
1225 null(),
1226 AV_CODEC_CONFIG_CHANNEL_LAYOUT,
1227 0,
1228 &mut layouts as *mut _ as *mut _,
1229 null_mut(),
1230 );
1231 if ret < 0 {
1232 return Err(OpenOutputError::from(ret).into());
1233 }
1234 let mut layout_list = Vec::new();
1235 let mut current = layouts;
1236 let mut count = 0;
1237 const MAX_CHANNEL_LAYOUTS: usize = 128;
1238 while !current.is_null()
1239 && (*current).order != AV_CHANNEL_ORDER_UNSPEC
1240 && count < MAX_CHANNEL_LAYOUTS
1241 {
1242 layout_list.push(*current);
1243 current = current.add(1);
1244 count += 1;
1245 }
1246 if count >= MAX_CHANNEL_LAYOUTS {
1247 warn!("Reached maximum channel layout limit");
1248 }
1249 output_filter.opts.ch_layouts = Some(layout_list);
1250
1251 if output_filter.opts.ch_layout.nb_channels > 0 {
1254 let layout_requested = output_filter.opts.ch_layout;
1255 set_channel_layout(
1256 &mut output_filter.opts.ch_layout,
1257 &output_filter.opts.ch_layouts,
1258 &layout_requested,
1259 )?;
1260 }
1261 }
1262 };
1263 Ok(())
1264}
1265
1266fn map_auto_streams(
1267 mux_index: usize,
1268 mux: &mut Muxer,
1269 demuxs: &mut Vec<Demuxer>,
1270 filter_graphs: &mut Vec<FilterGraph>,
1271 auto_disable: i32,
1272) -> Result<()> {
1273 unsafe {
1274 let oformat = (*mux.out_fmt_ctx).oformat;
1275 map_auto_stream(
1276 mux_index,
1277 mux,
1278 demuxs,
1279 oformat,
1280 AVMEDIA_TYPE_VIDEO,
1281 filter_graphs,
1282 auto_disable,
1283 )?;
1284 map_auto_stream(
1285 mux_index,
1286 mux,
1287 demuxs,
1288 oformat,
1289 AVMEDIA_TYPE_AUDIO,
1290 filter_graphs,
1291 auto_disable,
1292 )?;
1293 map_auto_subtitle(mux, demuxs, oformat, auto_disable)?;
1294 map_auto_data(mux, demuxs, oformat, auto_disable)?;
1295 }
1296 Ok(())
1297}
1298
1299#[cfg(feature = "docs-rs")]
1300unsafe fn map_auto_subtitle(
1301 mux: &mut Muxer,
1302 demuxs: &mut Vec<Demuxer>,
1303 oformat: *const AVOutputFormat,
1304 auto_disable: i32,
1305) -> Result<()> {
1306 Ok(())
1307}
1308
1309#[cfg(not(feature = "docs-rs"))]
1310unsafe fn map_auto_subtitle(
1311 mux: &mut Muxer,
1312 demuxs: &mut Vec<Demuxer>,
1313 oformat: *const AVOutputFormat,
1314 auto_disable: i32,
1315) -> Result<()> {
1316 if auto_disable & (1 << AVMEDIA_TYPE_SUBTITLE as i32) != 0 {
1317 return Ok(());
1318 }
1319
1320 let output_codec = avcodec_find_encoder((*oformat).subtitle_codec);
1324 if output_codec.is_null() && mux.subtitle_codec.is_none() {
1325 return Ok(());
1326 }
1327 let output_descriptor = if output_codec.is_null() {
1328 null()
1329 } else {
1330 avcodec_descriptor_get((*output_codec).id)
1331 };
1332
1333 for (demux_idx, demux) in demuxs.iter_mut().enumerate() {
1338 for stream_index in 0..demux.get_streams().len() {
1339 if demux.get_stream(stream_index).codec_type != AVMEDIA_TYPE_SUBTITLE {
1340 continue;
1341 }
1342
1343 let input_descriptor =
1344 avcodec_descriptor_get((*demux.get_stream(stream_index).codec_parameters).codec_id);
1345 let mut input_props = 0;
1346 if !input_descriptor.is_null() {
1347 input_props =
1348 (*input_descriptor).props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB);
1349 }
1350 let mut output_props = 0;
1351 if !output_descriptor.is_null() {
1352 output_props =
1353 (*output_descriptor).props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB);
1354 }
1355
1356 let compatible = mux.subtitle_codec.is_some()
1359 || input_props & output_props != 0
1360 || !input_descriptor.is_null() && !output_descriptor.is_null()
1362 && ((*input_descriptor).props == 0 || (*output_descriptor).props == 0);
1363 if !compatible {
1364 continue;
1365 }
1366
1367 let option = choose_encoder(mux, AVMEDIA_TYPE_SUBTITLE)?;
1370 if let Some((_codec_id, enc)) = option {
1371 let (frame_sender, output_stream_index) =
1372 mux.add_enc_stream(AVMEDIA_TYPE_SUBTITLE, enc, demux.node.clone(), false)?;
1373 demux.get_stream_mut(stream_index).add_dst(frame_sender);
1374 demux.connect_stream(stream_index);
1375 mux.register_stream_source(output_stream_index, demux_idx, stream_index, true);
1376 let input_stream = demux.get_stream(stream_index);
1377 unsafe {
1378 rescale_duration(
1379 input_stream.duration,
1380 input_stream.time_base,
1381 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
1382 );
1383 }
1384 } else {
1385 let input_stream = demux.get_stream(stream_index);
1386 let input_stream_duration = input_stream.duration;
1387 let input_stream_time_base = input_stream.time_base;
1388
1389 let (packet_sender, _st, output_stream_index) =
1390 mux.new_stream(demux.node.clone())?;
1391 demux.add_packet_dst(packet_sender, stream_index, output_stream_index);
1392 mux.register_stream_source(output_stream_index, demux_idx, stream_index, false);
1393
1394 unsafe {
1395 streamcopy_init(
1396 mux,
1397 *(*demux.in_fmt_ctx).streams.add(stream_index),
1398 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
1399 demux.framerate,
1400 )?;
1401 rescale_duration(
1402 input_stream_duration,
1403 input_stream_time_base,
1404 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
1405 );
1406 mux.stream_ready()
1407 }
1408 }
1409 return Ok(());
1410 }
1411 }
1412
1413 Ok(())
1414}
1415
1416#[cfg(feature = "docs-rs")]
1417unsafe fn map_auto_data(
1418 mux: &mut Muxer,
1419 demuxs: &mut Vec<Demuxer>,
1420 oformat: *const AVOutputFormat,
1421 auto_disable: i32,
1422) -> Result<()> {
1423 Ok(())
1424}
1425
1426#[cfg(not(feature = "docs-rs"))]
1427unsafe fn map_auto_data(
1428 mux: &mut Muxer,
1429 demuxs: &mut Vec<Demuxer>,
1430 oformat: *const AVOutputFormat,
1431 auto_disable: i32,
1432) -> Result<()> {
1433 if auto_disable & (1 << AVMEDIA_TYPE_DATA as i32) != 0 {
1434 return Ok(());
1435 }
1436
1437 let codec_id = av_guess_codec(
1439 oformat,
1440 null(),
1441 (*mux.out_fmt_ctx).url,
1442 null(),
1443 AVMEDIA_TYPE_DATA,
1444 );
1445
1446 if codec_id == AV_CODEC_ID_NONE {
1447 return Ok(());
1448 }
1449
1450 for (demux_idx, demux) in demuxs.iter_mut().enumerate() {
1451 let option = demux
1452 .get_streams()
1453 .iter()
1454 .enumerate()
1455 .find_map(|(index, input_stream)| {
1456 if input_stream.codec_type == AVMEDIA_TYPE_DATA
1457 && (*input_stream.codec_parameters).codec_id == codec_id
1458 {
1459 Some(index)
1460 } else {
1461 None
1462 }
1463 });
1464
1465 if option.is_none() {
1466 continue;
1467 }
1468
1469 let stream_index = option.unwrap();
1470 let option = choose_encoder(mux, AVMEDIA_TYPE_DATA)?;
1471
1472 if option.is_some() {
1473 unreachable!("DATA streams do not have encoders in FFmpeg");
1476 } else {
1477 let input_stream = demux.get_stream(stream_index);
1481 let input_stream_duration = input_stream.duration;
1482 let input_stream_time_base = input_stream.time_base;
1483
1484 let (packet_sender, _st, output_stream_index) = mux.new_stream(demux.node.clone())?;
1485 demux.add_packet_dst(packet_sender, stream_index, output_stream_index);
1486 mux.register_stream_source(output_stream_index, demux_idx, stream_index, false);
1487
1488 streamcopy_init(
1489 mux,
1490 *(*demux.in_fmt_ctx).streams.add(stream_index),
1491 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
1492 demux.framerate,
1493 )?;
1494 rescale_duration(
1495 input_stream_duration,
1496 input_stream_time_base,
1497 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
1498 );
1499 mux.stream_ready()
1500 }
1501
1502 break;
1503 }
1504
1505 Ok(())
1506}
1507
1508#[cfg(feature = "docs-rs")]
1509unsafe fn map_auto_stream(
1510 mux_index: usize,
1511 mux: &mut Muxer,
1512 demuxs: &mut Vec<Demuxer>,
1513 oformat: *const AVOutputFormat,
1514 media_type: AVMediaType,
1515 filter_graphs: &mut Vec<FilterGraph>,
1516 auto_disable: i32,
1517) -> Result<()> {
1518 Ok(())
1519}
1520
1521#[cfg(not(feature = "docs-rs"))]
1522unsafe fn map_auto_stream(
1523 mux_index: usize,
1524 mux: &mut Muxer,
1525 demuxs: &mut Vec<Demuxer>,
1526 oformat: *const AVOutputFormat,
1527 media_type: AVMediaType,
1528 filter_graphs: &mut Vec<FilterGraph>,
1529 auto_disable: i32,
1530) -> Result<()> {
1531 if auto_disable & (1 << media_type as i32) != 0 {
1532 return Ok(());
1533 }
1534 if (media_type == AVMEDIA_TYPE_VIDEO
1535 || media_type == AVMEDIA_TYPE_AUDIO
1536 || media_type == AVMEDIA_TYPE_DATA)
1537 && av_guess_codec(oformat, null(), (*mux.out_fmt_ctx).url, null(), media_type)
1538 == AV_CODEC_ID_NONE
1539 {
1540 return Ok(());
1541 }
1542
1543 let selected = if media_type == AVMEDIA_TYPE_VIDEO || media_type == AVMEDIA_TYPE_AUDIO {
1547 unsafe { select_best_stream(demuxs, oformat, media_type) }
1548 } else {
1549 demuxs.iter().enumerate().find_map(|(demux_idx, demux)| {
1550 demux
1551 .get_streams()
1552 .iter()
1553 .position(|input_stream| input_stream.codec_type == media_type)
1554 .map(|stream_index| (demux_idx, stream_index))
1555 })
1556 };
1557 let Some((demux_idx, stream_index)) = selected else {
1558 return Ok(());
1559 };
1560
1561 let demux = &mut demuxs[demux_idx];
1562 let input_file_idx = demux_idx;
1563 let option = choose_encoder(mux, media_type)?;
1564
1565 if let Some((codec_id, enc)) = option {
1566 if media_type == AVMEDIA_TYPE_VIDEO || media_type == AVMEDIA_TYPE_AUDIO {
1567 init_simple_filtergraph(
1568 demux,
1569 stream_index,
1570 codec_id,
1571 enc,
1572 mux_index,
1573 mux,
1574 filter_graphs,
1575 input_file_idx,
1576 )?;
1577 } else {
1578 let (frame_sender, output_stream_index) =
1579 mux.add_enc_stream(media_type, enc, demux.node.clone(), false)?;
1580 demux.get_stream_mut(stream_index).add_dst(frame_sender);
1581 demux.connect_stream(stream_index);
1582 mux.register_stream_source(output_stream_index, input_file_idx, stream_index, true);
1583 let input_stream = demux.get_stream(stream_index);
1584 unsafe {
1585 rescale_duration(
1586 input_stream.duration,
1587 input_stream.time_base,
1588 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
1589 );
1590 }
1591 }
1592
1593 return Ok(());
1594 }
1595
1596 let input_stream = demux.get_stream(stream_index);
1599 let input_stream_duration = input_stream.duration;
1600 let input_stream_time_base = input_stream.time_base;
1601
1602 let (packet_sender, _st, output_stream_index) = mux.new_stream(demux.node.clone())?;
1603 demux.add_packet_dst(packet_sender, stream_index, output_stream_index);
1604 mux.register_stream_source(output_stream_index, input_file_idx, stream_index, false);
1605
1606 unsafe {
1607 streamcopy_init(
1608 mux,
1609 *(*demux.in_fmt_ctx).streams.add(stream_index),
1610 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
1611 demux.framerate,
1612 )?;
1613 rescale_duration(
1614 input_stream_duration,
1615 input_stream_time_base,
1616 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
1617 );
1618 mux.stream_ready()
1619 }
1620
1621 Ok(())
1622}
1623
1624#[cfg(not(feature = "docs-rs"))]
1630unsafe fn select_best_stream(
1631 demuxs: &[Demuxer],
1632 oformat: *const AVOutputFormat,
1633 media_type: AVMediaType,
1634) -> Option<(usize, usize)> {
1635 const APIC_TAG: i32 =
1636 (b'A' as i32) | ((b'P' as i32) << 8) | ((b'I' as i32) << 16) | ((b'C' as i32) << 24);
1637 let qcr = if media_type == AVMEDIA_TYPE_VIDEO {
1638 avformat_query_codec(oformat, (*oformat).video_codec, 0)
1639 } else {
1640 0
1641 };
1642
1643 let mut best: Option<(usize, usize)> = None;
1644 let mut best_score: i64 = 0;
1645
1646 for (demux_idx, demux) in demuxs.iter().enumerate() {
1647 let mut file_best: Option<usize> = None;
1648 let mut file_best_score: i64 = 0;
1649
1650 for (stream_index, input_stream) in demux.get_streams().iter().enumerate() {
1651 if input_stream.codec_type != media_type {
1652 continue;
1653 }
1654 let par = input_stream.codec_parameters;
1659 let st = input_stream.stream.inner;
1660 let attached_pic = (*st).disposition & AV_DISPOSITION_ATTACHED_PIC != 0;
1661
1662 let mut score: i64 = if media_type == AVMEDIA_TYPE_VIDEO {
1663 (*par).width as i64 * (*par).height as i64
1664 } else {
1665 (*par).ch_layout.nb_channels as i64
1666 };
1667 if (*st).event_flags & AVSTREAM_EVENT_FLAG_NEW_PACKETS != 0 {
1668 score += 100_000_000;
1669 }
1670 if (*st).disposition & AV_DISPOSITION_DEFAULT != 0 {
1671 score += 5_000_000;
1672 }
1673 if media_type == AVMEDIA_TYPE_VIDEO && qcr != APIC_TAG && attached_pic {
1674 score = 1;
1676 }
1677
1678 if score > file_best_score {
1679 if media_type == AVMEDIA_TYPE_VIDEO && qcr == APIC_TAG && !attached_pic {
1680 continue;
1682 }
1683 file_best_score = score;
1684 file_best = Some(stream_index);
1685 }
1686 }
1687
1688 if let Some(stream_index) = file_best {
1689 let st = demux.get_stream(stream_index).stream.inner;
1690 let attached_pic = (*st).disposition & AV_DISPOSITION_ATTACHED_PIC != 0;
1691 if (media_type != AVMEDIA_TYPE_VIDEO || qcr == APIC_TAG || !attached_pic)
1694 && (*st).disposition & AV_DISPOSITION_DEFAULT != 0
1695 {
1696 file_best_score -= 5_000_000;
1697 }
1698 if file_best_score > best_score {
1699 best_score = file_best_score;
1700 best = Some((demux_idx, stream_index));
1701 }
1702 }
1703 }
1704
1705 best
1706}
1707
1708fn init_simple_filtergraph(
1709 demux: &mut Demuxer,
1710 stream_index: usize,
1711 codec_id: AVCodecID,
1712 enc: *const AVCodec,
1713 mux_index: usize,
1714 mux: &mut Muxer,
1715 filter_graphs: &mut Vec<FilterGraph>,
1716 input_file_idx: usize,
1717) -> Result<()> {
1718 let codec_type = demux.get_stream(stream_index).codec_type;
1719
1720 let filter_desc = if codec_type == AVMEDIA_TYPE_VIDEO {
1721 "null"
1722 } else {
1723 "anull"
1724 };
1725 let mut filter_graph = init_filter_graph(filter_graphs.len(), filter_desc, None)?;
1726
1727 ifilter_bind_ist(&mut filter_graph, 0, stream_index, demux)?;
1731 let single_stream_direct_input = demux.get_streams().len() == 1;
1734 ofilter_bind_ost(
1735 mux_index,
1736 mux,
1737 &mut filter_graph,
1738 0,
1739 codec_id,
1740 enc,
1741 Some((input_file_idx, stream_index)),
1742 single_stream_direct_input,
1743 )?;
1744
1745 filter_graphs.push(filter_graph);
1746
1747 Ok(())
1748}
1749
1750unsafe fn rescale_duration(src_duration: i64, src_time_base: AVRational, stream: *mut AVStream) {
1751 (*stream).duration = av_rescale_q(src_duration, src_time_base, (*stream).time_base);
1752}
1753
1754#[cfg(feature = "docs-rs")]
1755fn streamcopy_init(
1756 mux: &mut Muxer,
1757 input_stream: *mut AVStream,
1758 output_stream: *mut AVStream,
1759 input_framerate: AVRational,
1760) -> Result<()> {
1761 Ok(())
1762}
1763
1764#[cfg(not(feature = "docs-rs"))]
1765fn streamcopy_init(
1766 mux: &mut Muxer,
1767 input_stream: *mut AVStream,
1768 output_stream: *mut AVStream,
1769 input_framerate: AVRational,
1770) -> Result<()> {
1771 unsafe {
1772 let codec_ctx = avcodec_alloc_context3(null_mut());
1773 if codec_ctx.is_null() {
1774 return Err(OpenOutputError::OutOfMemory.into());
1775 }
1776 let _codec_context = CodecContext::new(codec_ctx);
1777
1778 let mut ret = avcodec_parameters_to_context(codec_ctx, (*input_stream).codecpar);
1779 if ret < 0 {
1780 error!("Error setting up codec context options.");
1781 return Err(OpenOutputError::from(ret).into());
1782 }
1783
1784 ret = avcodec_parameters_from_context((*output_stream).codecpar, codec_ctx);
1785 if ret < 0 {
1786 error!("Error getting reference codec parameters.");
1787 return Err(OpenOutputError::from(ret).into());
1788 }
1789
1790 let mut codec_tag = 0;
1796 if codec_tag == 0 {
1797 let ct = (*(*mux.out_fmt_ctx).oformat).codec_tag;
1798 let mut codec_tag_tmp = 0;
1799 if ct.is_null()
1800 || av_codec_get_id(ct, (*(*output_stream).codecpar).codec_tag)
1801 == (*(*output_stream).codecpar).codec_id
1802 || av_codec_get_tag2(
1803 ct,
1804 (*(*output_stream).codecpar).codec_id,
1805 &mut codec_tag_tmp,
1806 ) == 0
1807 {
1808 codec_tag = (*(*output_stream).codecpar).codec_tag;
1809 }
1810 }
1811 (*(*output_stream).codecpar).codec_tag = codec_tag;
1812
1813 let codec_type = (*(*output_stream).codecpar).codec_type;
1819 let mut fr = AVRational { num: 0, den: 0 };
1820 if codec_type == AVMEDIA_TYPE_VIDEO {
1821 fr = mux.framerate.unwrap_or(AVRational { num: 0, den: 0 });
1822 if fr.num == 0 {
1823 fr = input_framerate;
1824 }
1825 }
1826
1827 if fr.num != 0 {
1828 (*output_stream).avg_frame_rate = fr;
1829 } else {
1830 (*output_stream).avg_frame_rate = (*input_stream).avg_frame_rate;
1831 }
1832
1833 if (*output_stream).time_base.num <= 0 || (*output_stream).time_base.den <= 0 {
1835 if fr.num != 0 {
1836 (*output_stream).time_base = av_inv_q(fr);
1837 } else {
1838 (*output_stream).time_base =
1839 av_add_q((*input_stream).time_base, AVRational { num: 0, den: 1 });
1840 }
1841 }
1842
1843 for i in 0..(*(*input_stream).codecpar).nb_coded_side_data {
1844 let sd_src = (*(*input_stream).codecpar)
1845 .coded_side_data
1846 .offset(i as isize);
1847
1848 let sd_dst = av_packet_side_data_new(
1849 &mut (*(*output_stream).codecpar).coded_side_data,
1850 &mut (*(*output_stream).codecpar).nb_coded_side_data,
1851 (*sd_src).type_,
1852 (*sd_src).size,
1853 0,
1854 );
1855 if sd_dst.is_null() {
1856 return Err(OpenOutputError::OutOfMemory.into());
1857 }
1858 std::ptr::copy_nonoverlapping(
1859 (*sd_src).data as *const u8,
1860 (*sd_dst).data,
1861 (*sd_src).size,
1862 );
1863 }
1864
1865 match (*(*output_stream).codecpar).codec_type {
1866 AVMEDIA_TYPE_AUDIO => {
1867 if ((*(*output_stream).codecpar).block_align == 1
1868 || (*(*output_stream).codecpar).block_align == 1152
1869 || (*(*output_stream).codecpar).block_align == 576)
1870 && (*(*output_stream).codecpar).codec_id == AV_CODEC_ID_MP3
1871 {
1872 (*(*output_stream).codecpar).block_align = 0;
1873 }
1874 if (*(*output_stream).codecpar).codec_id == AV_CODEC_ID_AC3 {
1875 (*(*output_stream).codecpar).block_align = 0;
1876 }
1877 }
1878 AVMEDIA_TYPE_VIDEO => {
1879 let sar = if (*input_stream).sample_aspect_ratio.num != 0 {
1880 (*input_stream).sample_aspect_ratio
1881 } else {
1882 (*(*output_stream).codecpar).sample_aspect_ratio
1883 };
1884 (*output_stream).sample_aspect_ratio = sar;
1885 (*(*output_stream).codecpar).sample_aspect_ratio = sar;
1886 (*output_stream).r_frame_rate = (*input_stream).r_frame_rate;
1887 }
1888 _ => {}
1889 }
1890 };
1891 Ok(())
1892}
1893
1894fn output_bind_by_unlabeled_filter(
1895 index: usize,
1896 mux: &mut Muxer,
1897 filter_graphs: &mut Vec<FilterGraph>,
1898 auto_disable: &mut i32,
1899) -> Result<()> {
1900 let fg_len = filter_graphs.len();
1901
1902 for i in 0..fg_len {
1903 let filter_graph = &mut filter_graphs[i];
1904
1905 for i in 0..filter_graph.outputs.len() {
1906 let media_type = filter_graph.outputs[i].media_type;
1907
1908 if *auto_disable & (1 << media_type as i32) != 0 {
1910 continue;
1911 }
1912
1913 let option = {
1914 let output_filter = &filter_graph.outputs[i];
1915 if (!output_filter.linklabel.is_empty() && output_filter.linklabel != "out")
1916 || output_filter.has_dst()
1917 {
1918 continue;
1919 }
1920
1921 choose_encoder(mux, output_filter.media_type)?
1922 };
1923
1924 match option {
1925 None => {
1926 warn!(
1927 "An unexpected media_type {:?} appears in output_filter",
1928 media_type
1929 );
1930 }
1931 Some((codec_id, enc)) => {
1932 *auto_disable |= 1 << media_type as i32;
1933 ofilter_bind_ost(index, mux, filter_graph, i, codec_id, enc, None, false)?;
1934 }
1935 }
1936 }
1937 }
1938
1939 Ok(())
1940}
1941
1942fn ofilter_bind_ost(
1943 index: usize,
1944 mux: &mut Muxer,
1945 filter_graph: &mut FilterGraph,
1946 output_filter_index: usize,
1947 codec_id: AVCodecID,
1948 enc: *const AVCodec,
1949 stream_source: Option<(usize, usize)>,
1950 single_stream_direct_input: bool,
1951) -> Result<usize> {
1952 let output_filter = &mut filter_graph.outputs[output_filter_index];
1953 let (frame_sender, output_stream_index) = mux.add_enc_stream(
1954 output_filter.media_type,
1955 enc,
1956 filter_graph.node.clone(),
1957 single_stream_direct_input,
1958 )?;
1959 output_filter.set_dst(frame_sender);
1960
1961 if let Some((file_idx, stream_idx)) = stream_source {
1962 mux.register_stream_source(output_stream_index, file_idx, stream_idx, true);
1963 }
1964
1965 configure_output_filter_opts(
1966 index,
1967 mux,
1968 output_filter,
1969 codec_id,
1970 enc,
1971 output_stream_index,
1972 )?;
1973 Ok(output_stream_index)
1974}
1975
1976fn choose_encoder(
1977 mux: &Muxer,
1978 media_type: AVMediaType,
1979) -> Result<Option<(AVCodecID, *const AVCodec)>> {
1980 let media_codec = match media_type {
1981 AVMEDIA_TYPE_VIDEO => mux.video_codec.clone(),
1982 AVMEDIA_TYPE_AUDIO => mux.audio_codec.clone(),
1983 AVMEDIA_TYPE_SUBTITLE => mux.subtitle_codec.clone(),
1984 _ => return Ok(None),
1985 };
1986
1987 match media_codec {
1988 None => {
1989 let url = CString::new(&*mux.url)?;
1990 unsafe {
1991 let codec_id = av_guess_codec(
1992 (*mux.out_fmt_ctx).oformat,
1993 null(),
1994 url.as_ptr(),
1995 null(),
1996 media_type,
1997 );
1998 let enc = avcodec_find_encoder(codec_id);
1999 if enc.is_null() {
2000 let format_name = (*(*mux.out_fmt_ctx).oformat).name;
2001 let format_name = CStr::from_ptr(format_name).to_str();
2002 let codec_name = avcodec_get_name(codec_id);
2003 let codec_name = CStr::from_ptr(codec_name).to_str();
2004 if let (Ok(format_name), Ok(codec_name)) = (format_name, codec_name) {
2005 error!("Automatic encoder selection failed Default encoder for format {format_name} (codec {codec_name}) is probably disabled. Please choose an encoder manually.");
2006 }
2007 return Err(OpenOutputError::from(AVERROR_ENCODER_NOT_FOUND).into());
2008 }
2009
2010 return Ok(Some((codec_id, enc)));
2011 }
2012 }
2013 Some(media_codec) if media_codec != "copy" => unsafe {
2014 let media_codec_cstr = CString::new(media_codec.clone())?;
2015
2016 let mut enc = avcodec_find_encoder_by_name(media_codec_cstr.as_ptr());
2017 let desc = avcodec_descriptor_get_by_name(media_codec_cstr.as_ptr());
2018
2019 if enc.is_null() && !desc.is_null() {
2020 enc = avcodec_find_encoder((*desc).id);
2021 if !enc.is_null() {
2022 let codec_name = (*enc).name;
2023 let codec_name = CStr::from_ptr(codec_name).to_str();
2024 let desc_name = (*desc).name;
2025 let desc_name = CStr::from_ptr(desc_name).to_str();
2026 if let (Ok(codec_name), Ok(desc_name)) = (codec_name, desc_name) {
2027 debug!("Matched encoder '{codec_name}' for codec '{desc_name}'.");
2028 }
2029 }
2030 }
2031
2032 if enc.is_null() {
2033 error!("Unknown encoder '{media_codec}'");
2034 return Err(OpenOutputError::from(AVERROR_ENCODER_NOT_FOUND).into());
2035 }
2036
2037 if (*enc).type_ != media_type {
2038 error!("Invalid encoder type '{media_codec}'");
2039 return Err(OpenOutputError::InvalidArgument.into());
2040 }
2041 let codec_id = (*enc).id;
2042 return Ok(Some((codec_id, enc)));
2043 },
2044 _ => {}
2045 };
2046
2047 Ok(None)
2048}
2049
2050fn check_duplicate_inputs_outputs(inputs: &[Input], outputs: &[Output]) -> Result<()> {
2051 for output in outputs {
2052 if let Some(output_url) = &output.url {
2053 for input in inputs {
2054 if let Some(input_url) = &input.url {
2055 if input_url == output_url {
2056 return Err(FileSameAsInput(input_url.clone()));
2057 }
2058 }
2059 }
2060 }
2061 }
2062 Ok(())
2063}
2064
2065fn open_output_files(
2066 outputs: &mut Vec<Output>,
2067 copy_ts: bool,
2068 interrupt_state: &Arc<crate::core::context::InterruptState>,
2069) -> Result<Vec<Muxer>> {
2070 let mut muxs = Vec::new();
2071
2072 for (i, output) in outputs.iter_mut().enumerate() {
2073 unsafe {
2074 let result = open_output_file(i, output, copy_ts, interrupt_state);
2075 if let Err(e) = result {
2076 return Err(e);
2078 }
2079 let mux = result.unwrap();
2080 muxs.push(mux)
2081 }
2082 }
2083 Ok(muxs)
2084}
2085
2086#[cfg(feature = "docs-rs")]
2087unsafe fn open_output_file(
2088 index: usize,
2089 output: &mut Output,
2090 copy_ts: bool,
2091 interrupt_state: &Arc<crate::core::context::InterruptState>,
2092) -> Result<Muxer> {
2093 Err(Error::Bug)
2094}
2095
2096#[cfg(not(feature = "docs-rs"))]
2097unsafe fn open_output_file(
2098 index: usize,
2099 output: &mut Output,
2100 copy_ts: bool,
2101 interrupt_state: &Arc<crate::core::context::InterruptState>,
2102) -> Result<Muxer> {
2103 let mut out_fmt_ctx = null_mut();
2104 let format = get_format(&output.format)?;
2105 match &output.url {
2106 None => {
2107 if output.write_callback.is_none() {
2108 error!("input url and write_callback is none.");
2109 return Err(OpenOutputError::InvalidSink.into());
2110 }
2111
2112 let write_callback = output.write_callback.take().unwrap();
2113
2114 let avio_ctx_buffer_size = 1024 * 64;
2115 let mut avio_ctx_buffer = av_malloc(avio_ctx_buffer_size);
2116 if avio_ctx_buffer.is_null() {
2117 return Err(OpenOutputError::OutOfMemory.into());
2118 }
2119
2120 let have_seek_callback = output.seek_callback.is_some();
2121 let input_opaque = Box::new(OutputOpaque {
2122 write: write_callback,
2123 seek: output.seek_callback.take(),
2124 });
2125 let opaque = Box::into_raw(input_opaque) as *mut libc::c_void;
2126
2127 let avio_ctx = avio_alloc_context(
2128 avio_ctx_buffer as *mut libc::c_uchar,
2129 avio_ctx_buffer_size as i32,
2130 1,
2131 opaque,
2132 None,
2133 Some(write_packet_wrapper),
2134 if have_seek_callback {
2135 Some(seek_output_packet_wrapper)
2136 } else {
2137 None
2138 },
2139 );
2140 if avio_ctx.is_null() {
2141 av_freep(&mut avio_ctx_buffer as *mut _ as *mut c_void);
2142 let _ = Box::from_raw(opaque as *mut OutputOpaque);
2144 return Err(OpenOutputError::OutOfMemory.into());
2145 }
2146
2147 let ret = avformat_alloc_output_context2(&mut out_fmt_ctx, format, null(), null());
2148 if out_fmt_ctx.is_null() {
2149 warn!("Error initializing the muxer for write_callback");
2150 crate::core::context::free_output_opaque(avio_ctx);
2152 return Err(AllocOutputContextError::from(ret).into());
2153 }
2154
2155 if !have_seek_callback && output_requires_seek(out_fmt_ctx) {
2156 crate::core::context::free_output_opaque(avio_ctx);
2157 avformat_free_context(out_fmt_ctx);
2158 warn!("The output format supports seeking, but no seek callback is provided. This may cause issues.");
2159 return Err(OpenOutputError::SeekFunctionMissing.into());
2160 }
2161
2162 (*out_fmt_ctx).pb = avio_ctx;
2163 (*out_fmt_ctx).flags |= AVFMT_FLAG_CUSTOM_IO;
2164 }
2165 Some(url) => {
2166 let url_cstr = if url == "-" {
2167 CString::new("pipe:")?
2168 } else {
2169 CString::new(url.as_str())?
2170 };
2171 let ret =
2172 avformat_alloc_output_context2(&mut out_fmt_ctx, format, null(), url_cstr.as_ptr());
2173 if out_fmt_ctx.is_null() {
2174 warn!("Error initializing the muxer for {url}");
2175 return Err(AllocOutputContextError::from(ret).into());
2176 }
2177
2178 (*out_fmt_ctx).interrupt_callback = ffmpeg_sys_next::AVIOInterruptCB {
2182 callback: Some(crate::core::context::output_interrupt_cb),
2183 opaque: Arc::as_ptr(interrupt_state) as *mut c_void,
2184 };
2185
2186 let output_format = (*out_fmt_ctx).oformat;
2187 if (*output_format).flags & AVFMT_NOFILE == 0 {
2188 let ret = avio_open2(
2189 &mut (*out_fmt_ctx).pb,
2190 url_cstr.as_ptr(),
2191 AVIO_FLAG_WRITE,
2192 &(*out_fmt_ctx).interrupt_callback,
2193 null_mut(),
2194 );
2195 if ret < 0 {
2196 warn!("Error opening output {url}");
2197 return Err(OpenOutputError::from(ret).into());
2198 }
2199 }
2200 }
2201 }
2202
2203 let recording_time_us = match output.stop_time_us {
2204 None => output.recording_time_us,
2205 Some(stop_time_us) => {
2206 let start_time_us = output.start_time_us.unwrap_or(0);
2207 if stop_time_us <= start_time_us {
2208 error!("stop_time_us value smaller than start_time_us; aborting.");
2209 return Err(OpenOutputError::InvalidArgument.into());
2210 } else {
2211 Some(stop_time_us - start_time_us)
2212 }
2213 }
2214 };
2215
2216 let url = output
2217 .url
2218 .clone()
2219 .unwrap_or_else(|| format!("write_callback[{index}]"));
2220
2221 let video_codec_opts = convert_options(output.video_codec_opts.clone())?;
2222 let audio_codec_opts = convert_options(output.audio_codec_opts.clone())?;
2223 let subtitle_codec_opts = convert_options(output.subtitle_codec_opts.clone())?;
2224 let format_opts = convert_options(output.format_opts.clone())?;
2225 let format_opts = maybe_enable_image2_update(
2226 out_fmt_ctx,
2227 output.url.as_deref(),
2228 output.max_video_frames,
2229 format_opts,
2230 );
2231
2232 let pix_fmt = match &output.pix_fmt {
2237 Some(fmt_str) => {
2238 let cstr = CString::new(fmt_str.as_str())?;
2239 let pf = av_get_pix_fmt(cstr.as_ptr());
2240 if pf == AV_PIX_FMT_NONE {
2241 return Err(OpenOutputError::UnknownPixelFormat(fmt_str.clone()).into());
2242 } else {
2243 Some(pf)
2244 }
2245 }
2246 None => None,
2247 };
2248
2249 let mux = Muxer::new(
2250 url,
2251 output.url.is_none(),
2252 out_fmt_ctx,
2253 output.frame_pipelines.take(),
2254 output.stream_map_specs.clone(),
2255 output.stream_maps.clone(),
2256 output.video_codec.clone(),
2257 output.audio_codec.clone(),
2258 output.subtitle_codec.clone(),
2259 output.start_time_us,
2260 recording_time_us,
2261 output.framerate,
2262 output.framerate_max,
2263 output.vsync_method,
2264 output.bits_per_raw_sample,
2265 output.audio_sample_rate,
2266 output.audio_channels,
2267 output.audio_sample_fmt,
2268 output.video_qscale,
2269 output.audio_qscale,
2270 output.max_video_frames,
2271 output.max_audio_frames,
2272 output.max_subtitle_frames,
2273 video_codec_opts,
2274 audio_codec_opts,
2275 subtitle_codec_opts,
2276 format_opts,
2277 copy_ts,
2278 output.global_metadata.clone(),
2279 output.stream_metadata.clone(),
2280 output.chapter_metadata.clone(),
2281 output.program_metadata.clone(),
2282 output.metadata_map.clone(),
2283 output.auto_copy_metadata,
2284 output.video_disable,
2285 output.audio_disable,
2286 output.subtitle_disable,
2287 output.data_disable,
2288 pix_fmt,
2289 );
2290
2291 Ok(mux)
2292}
2293
2294fn get_format(format_option: &Option<String>) -> Result<*const AVOutputFormat> {
2295 match format_option {
2296 None => Ok(null()),
2297 Some(format_str) => unsafe {
2298 let mut format_cstr = CString::new(format_str.to_string())?;
2299 let mut format = av_guess_format(format_cstr.as_ptr(), null(), null());
2300 if format.is_null() {
2301 format_cstr = CString::new(format!("tmp.{format_str}"))?;
2302 format = av_guess_format(null(), format_cstr.as_ptr(), null());
2303 }
2304 if format.is_null() {
2305 return Err(OpenOutputError::FormatUnsupported(format_str.to_string()).into());
2306 }
2307 Ok(format)
2308 },
2309 }
2310}
2311
2312unsafe fn output_requires_seek(fmt_ctx: *mut AVFormatContext) -> bool {
2313 if fmt_ctx.is_null() {
2314 return false;
2315 }
2316
2317 let mut format_name = "unknown".to_string();
2318
2319 if !(*fmt_ctx).oformat.is_null() {
2320 let oformat = (*fmt_ctx).oformat;
2321 format_name = CStr::from_ptr((*oformat).name)
2322 .to_string_lossy()
2323 .into_owned();
2324 let flags = (*oformat).flags;
2325 let no_file = flags & AVFMT_NOFILE != 0;
2326 let global_header = flags & AVFMT_GLOBALHEADER != 0;
2327
2328 log::debug!(
2329 "Output format '{format_name}' - No file: {}, Global header: {}",
2330 if no_file { "True" } else { "False" },
2331 if global_header { "True" } else { "False" }
2332 );
2333
2334 let format_names: Vec<&str> = format_name.split(',').collect();
2336 if format_names
2337 .iter()
2338 .any(|&f| matches!(f, "mp4" | "mov" | "mkv" | "avi" | "flac" | "ogg" | "webm"))
2339 {
2340 log::debug!("Output format '{format_name}' typically requires seeking.");
2341 return true;
2342 }
2343
2344 if format_names.iter().any(|&f| {
2346 matches!(
2347 f,
2348 "mpegts" | "hls" | "m3u8" | "udp" | "rtp" | "rtp_mpegts" | "http" | "srt"
2349 )
2350 }) {
2351 log::debug!("Output format '{format_name}' does not typically require seeking.");
2352 return false;
2353 }
2354
2355 if format_name == "flv" {
2357 log::debug!("Output format 'flv' detected. It is highly recommended to set `seek_callback()` to avoid potential issues with 'Failed to update header with correct duration' and 'Failed to update header with correct filesize'.");
2358 return false;
2359 }
2360
2361 if no_file {
2363 log::debug!(
2364 "Output format '{format_name}' uses AVFMT_NOFILE. Seeking is likely unnecessary."
2365 );
2366 return false;
2367 }
2368
2369 if global_header {
2371 log::debug!(
2372 "Output format '{format_name}' uses AVFMT_GLOBALHEADER. Seeking may be required."
2373 );
2374 return true;
2375 }
2376 } else {
2377 log::debug!("Output format is null. Cannot determine if seeking is required.");
2378 }
2379
2380 log::debug!("Output format '{format_name}' does not match any known rules. Assuming seeking is not required.");
2382 false
2383}
2384
2385pub(super) struct InputOpaque {
2386 pub(super) read: Box<dyn FnMut(&mut [u8]) -> i32 + Send>,
2387 pub(super) seek: Option<Box<dyn FnMut(i64, i32) -> i64 + Send>>,
2388}
2389
2390pub(super) struct OutputOpaque {
2391 pub(super) write: Box<dyn FnMut(&[u8]) -> i32 + Send>,
2392 pub(super) seek: Option<Box<dyn FnMut(i64, i32) -> i64 + Send>>,
2393}
2394
2395unsafe extern "C" fn write_packet_wrapper(
2396 opaque: *mut libc::c_void,
2397 buf: *const u8,
2398 buf_size: libc::c_int,
2399) -> libc::c_int {
2400 if buf.is_null() || buf_size <= 0 {
2403 return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO);
2404 }
2405 let context = &mut *(opaque as *mut OutputOpaque);
2406
2407 let slice = std::slice::from_raw_parts(buf, buf_size as usize);
2408
2409 (context.write)(slice)
2410}
2411
2412unsafe extern "C" fn read_packet_wrapper(
2413 opaque: *mut libc::c_void,
2414 buf: *mut u8,
2415 buf_size: libc::c_int,
2416) -> libc::c_int {
2417 if buf.is_null() || buf_size <= 0 {
2420 return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO);
2421 }
2422
2423 let context = &mut *(opaque as *mut InputOpaque);
2424
2425 let slice = std::slice::from_raw_parts_mut(buf, buf_size as usize);
2426
2427 (context.read)(slice)
2428}
2429
2430unsafe extern "C" fn seek_input_packet_wrapper(
2431 opaque: *mut libc::c_void,
2432 offset: i64,
2433 whence: libc::c_int,
2434) -> i64 {
2435 let context = &mut *(opaque as *mut InputOpaque);
2436
2437 if let Some(seek_func) = &mut context.seek {
2438 (*seek_func)(offset, whence)
2439 } else {
2440 ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE) as i64
2441 }
2442}
2443
2444unsafe extern "C" fn seek_output_packet_wrapper(
2445 opaque: *mut libc::c_void,
2446 offset: i64,
2447 whence: libc::c_int,
2448) -> i64 {
2449 let context = &mut *(opaque as *mut OutputOpaque);
2450
2451 if let Some(seek_func) = &mut context.seek {
2452 (*seek_func)(offset, whence)
2453 } else {
2454 ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE) as i64
2455 }
2456}
2457
2458fn fg_bind_inputs(filter_graphs: &mut Vec<FilterGraph>, demuxs: &mut Vec<Demuxer>) -> Result<()> {
2459 if filter_graphs.is_empty() {
2460 return Ok(());
2461 }
2462 bind_fg_inputs_by_fg(filter_graphs)?;
2463
2464 for filter_graph in filter_graphs.iter_mut() {
2465 for i in 0..filter_graph.inputs.len() {
2466 fg_complex_bind_input(filter_graph, i, demuxs)?;
2467 }
2468 }
2469
2470 Ok(())
2471}
2472
2473struct FilterLabel {
2474 linklabel: String,
2475 media_type: AVMediaType,
2476}
2477
2478fn bind_fg_inputs_by_fg(filter_graphs: &mut Vec<FilterGraph>) -> Result<()> {
2479 let fg_labels = filter_graphs
2480 .iter()
2481 .map(|filter_graph| {
2482 let inputs = filter_graph
2483 .inputs
2484 .iter()
2485 .map(|input| FilterLabel {
2486 linklabel: input.linklabel.clone(),
2487 media_type: input.media_type,
2488 })
2489 .collect::<Vec<_>>();
2490 let outputs = filter_graph
2491 .outputs
2492 .iter()
2493 .map(|output| FilterLabel {
2494 linklabel: output.linklabel.clone(),
2495 media_type: output.media_type,
2496 })
2497 .collect::<Vec<_>>();
2498 (inputs, outputs)
2499 })
2500 .collect::<Vec<_>>();
2501
2502 for (i, (inputs, _outputs)) in fg_labels.iter().enumerate() {
2503 for (input_pad_idx, input_filter_label) in inputs.iter().enumerate() {
2504 if input_filter_label.linklabel.is_empty() {
2505 continue;
2506 }
2507
2508 'outer: for (j, (_inputs, outputs)) in fg_labels.iter().enumerate() {
2509 if i == j {
2510 continue;
2511 }
2512
2513 for (output_idx, output_filter_label) in outputs.iter().enumerate() {
2514 if output_filter_label.linklabel != input_filter_label.linklabel {
2515 continue;
2516 }
2517 if output_filter_label.media_type != input_filter_label.media_type {
2518 warn!(
2519 "Tried to connect {:?} output to {:?} input",
2520 output_filter_label.media_type, input_filter_label.media_type
2521 );
2522 return Err(FilterGraphParseError::InvalidArgument.into());
2523 }
2524
2525 {
2526 let filter_graph = &filter_graphs[j];
2527 let output_filter = &filter_graph.outputs[output_idx];
2528 if output_filter.has_dst() {
2529 continue;
2530 }
2531 }
2532
2533 let (sender, finished_flag_list) = {
2534 let filter_graph = &mut filter_graphs[i];
2535 filter_graph.get_src_sender()
2536 };
2537
2538 {
2539 let filter_graph = &mut filter_graphs[j];
2540 filter_graph.outputs[output_idx].set_dst(sender);
2541 filter_graph.outputs[output_idx].fg_input_index = input_pad_idx;
2545 filter_graph.outputs[output_idx].finished_flag_list = finished_flag_list;
2546 }
2547 filter_graphs[i].inputs[input_pad_idx].bound = true;
2550
2551 break 'outer;
2552 }
2553 }
2554 }
2555 }
2556 Ok(())
2557}
2558
2559fn fg_complex_bind_input(
2560 filter_graph: &mut FilterGraph,
2561 input_filter_index: usize,
2562 demuxs: &mut Vec<Demuxer>,
2563) -> Result<()> {
2564 if filter_graph.inputs[input_filter_index].bound {
2568 return Ok(());
2569 }
2570
2571 let graph_desc = &filter_graph.graph_desc;
2572 let input_filter = &mut filter_graph.inputs[input_filter_index];
2573 let (demux_idx, stream_idx) = if !input_filter.linklabel.is_empty()
2574 && input_filter.linklabel != "in"
2575 {
2576 let (demux_idx, stream_idx) = fg_find_input_idx_by_linklabel(
2577 &input_filter.linklabel,
2578 input_filter.media_type,
2579 demuxs,
2580 graph_desc,
2581 )?;
2582
2583 info!(
2584 "Binding filter input with label '{}' to input stream {stream_idx}:{demux_idx}",
2585 input_filter.linklabel
2586 );
2587 (demux_idx, stream_idx)
2588 } else {
2589 let mut demux_idx = -1i32;
2590 let mut stream_idx = 0;
2591 for (d_idx, demux) in demuxs.iter().enumerate() {
2592 for (st_idx, intput_stream) in demux.get_streams().iter().enumerate() {
2593 if intput_stream.is_used() {
2594 continue;
2595 }
2596 if intput_stream.codec_type == input_filter.media_type {
2597 demux_idx = d_idx as i32;
2598 stream_idx = st_idx;
2599 break;
2600 }
2601 }
2602 if demux_idx >= 0 {
2603 break;
2604 }
2605 }
2606
2607 if demux_idx < 0 {
2608 warn!(
2609 "Cannot find a matching stream for unlabeled input pad {}",
2610 input_filter.name
2611 );
2612 return Err(FilterGraphParseError::InvalidArgument.into());
2613 }
2614
2615 debug!("FilterGraph binding unlabeled input {input_filter_index} to input stream {stream_idx}:{demux_idx}");
2616
2617 (demux_idx as usize, stream_idx)
2618 };
2619
2620 let demux = &mut demuxs[demux_idx];
2621
2622 ifilter_bind_ist(filter_graph, input_filter_index, stream_idx, demux)
2623}
2624
2625#[cfg(feature = "docs-rs")]
2626fn ifilter_bind_ist(
2627 filter_graph: &mut FilterGraph,
2628 input_index: usize,
2629 stream_idx: usize,
2630 demux: &mut Demuxer,
2631) -> Result<()> {
2632 Ok(())
2633}
2634
2635#[cfg(not(feature = "docs-rs"))]
2636fn ifilter_bind_ist(
2637 filter_graph: &mut FilterGraph,
2638 input_index: usize,
2639 stream_idx: usize,
2640 demux: &mut Demuxer,
2641) -> Result<()> {
2642 unsafe {
2643 let input_filter = &mut filter_graph.inputs[input_index];
2644 let ist = *(*demux.in_fmt_ctx).streams.add(stream_idx);
2645 let par = (*ist).codecpar;
2646 if (*par).codec_type == AVMEDIA_TYPE_VIDEO {
2647 let framerate = if demux.framerate.num > 0 && demux.framerate.den > 0 {
2651 demux.framerate
2652 } else {
2653 av_guess_frame_rate(demux.in_fmt_ctx, ist, null_mut())
2654 };
2655 input_filter.opts.framerate = framerate;
2656 } else if (*par).codec_type == AVMEDIA_TYPE_SUBTITLE {
2657 input_filter.opts.sub2video_width = (*par).width;
2658 input_filter.opts.sub2video_height = (*par).height;
2659
2660 if input_filter.opts.sub2video_width <= 0 || input_filter.opts.sub2video_height <= 0 {
2661 let nb_streams = (*demux.in_fmt_ctx).nb_streams;
2662 for j in 0..nb_streams {
2663 let par1 = (**(*demux.in_fmt_ctx).streams.add(j as usize)).codecpar;
2664 if (*par1).codec_type == AVMEDIA_TYPE_VIDEO {
2665 input_filter.opts.sub2video_width =
2666 std::cmp::max(input_filter.opts.sub2video_width, (*par1).width);
2667 input_filter.opts.sub2video_height =
2668 std::cmp::max(input_filter.opts.sub2video_height, (*par1).height);
2669 }
2670 }
2671 }
2672
2673 if input_filter.opts.sub2video_width <= 0 || input_filter.opts.sub2video_height <= 0 {
2674 input_filter.opts.sub2video_width =
2675 std::cmp::max(input_filter.opts.sub2video_width, 720);
2676 input_filter.opts.sub2video_height =
2677 std::cmp::max(input_filter.opts.sub2video_height, 576);
2678 }
2679
2680 demux.get_stream_mut(stream_idx).have_sub2video = true;
2681 }
2682
2683 let dec_ctx = {
2684 let input_stream = demux.get_stream_mut(stream_idx);
2685 avcodec_alloc_context3(input_stream.codec.as_ptr())
2686 };
2687 if dec_ctx.is_null() {
2688 return Err(FilterGraphParseError::OutOfMemory.into());
2689 }
2690 let _codec_ctx = CodecContext::new(dec_ctx);
2691
2692 let fallback = input_filter.opts.fallback.as_mut_ptr();
2693 if (*dec_ctx).codec_type == AVMEDIA_TYPE_AUDIO {
2694 (*fallback).format = (*dec_ctx).sample_fmt as i32;
2695 (*fallback).sample_rate = (*dec_ctx).sample_rate;
2696
2697 let ret = av_channel_layout_copy(&mut (*fallback).ch_layout, &(*dec_ctx).ch_layout);
2698 if ret < 0 {
2699 return Err(FilterGraphParseError::from(ret).into());
2700 }
2701 } else if (*dec_ctx).codec_type == AVMEDIA_TYPE_VIDEO {
2702 (*fallback).format = (*dec_ctx).pix_fmt as i32;
2703 (*fallback).width = (*dec_ctx).width;
2704 (*fallback).height = (*dec_ctx).height;
2705 (*fallback).sample_aspect_ratio = (*dec_ctx).sample_aspect_ratio;
2706 (*fallback).colorspace = (*dec_ctx).colorspace;
2707 (*fallback).color_range = (*dec_ctx).color_range;
2708 }
2709 (*fallback).time_base = (*dec_ctx).pkt_timebase;
2710
2711 if demux.autorotate {
2714 input_filter.opts.flags |= IFILTER_FLAG_AUTOROTATE;
2715 }
2716
2717 let tsoffset = if demux.copy_ts {
2718 let mut tsoffset = if demux.start_time_us.is_some() {
2719 demux.start_time_us.unwrap()
2720 } else {
2721 0
2722 };
2723 if (*demux.in_fmt_ctx).start_time != ffmpeg_sys_next::AV_NOPTS_VALUE {
2724 tsoffset += (*demux.in_fmt_ctx).start_time
2725 }
2726 tsoffset
2727 } else {
2728 0
2729 };
2730 if demux.start_time_us.is_some() {
2731 input_filter.opts.trim_start_us = Some(tsoffset);
2732 }
2733 input_filter.opts.trim_end_us = demux.recording_time_us;
2734
2735 let (sender, finished_flag_list) = filter_graph.get_src_sender();
2736 {
2737 let input_stream = demux.get_stream_mut(stream_idx);
2738 input_stream.add_fg_dst(sender, input_index, finished_flag_list);
2739 };
2740
2741 let node = Arc::make_mut(&mut filter_graph.node);
2742 let SchNode::Filter { inputs, .. } = node else {
2743 unreachable!()
2744 };
2745 inputs.insert(input_index, demux.node.clone());
2746
2747 demux.connect_stream(stream_idx);
2748 Ok(())
2749 }
2750}
2751
2752fn fg_find_input_idx_by_linklabel(
2756 linklabel: &str,
2757 filter_media_type: AVMediaType,
2758 demuxs: &mut Vec<Demuxer>,
2759 desc: &str,
2760) -> Result<(usize, usize)> {
2761 let new_linklabel = if linklabel.starts_with("[") && linklabel.ends_with("]") {
2763 if linklabel.len() <= 2 {
2764 warn!("Filter linklabel is empty");
2765 return Err(InvalidFilterSpecifier(desc.to_string()).into());
2766 } else {
2767 &linklabel[1..linklabel.len() - 1]
2768 }
2769 } else {
2770 linklabel
2771 };
2772
2773 let (file_idx, remainder) = strtol(new_linklabel).map_err(|_| {
2775 FilterGraphParseError::InvalidArgument
2776 })?;
2777
2778 if file_idx < 0 || file_idx as usize >= demuxs.len() {
2779 return Err(InvalidFileIndexInFg(file_idx as usize, desc.to_string()).into());
2780 }
2781 let file_idx = file_idx as usize;
2782
2783 let spec_str = if remainder.is_empty() {
2785 ""
2787 } else if remainder.starts_with(':') {
2788 &remainder[1..]
2789 } else {
2790 remainder
2791 };
2792
2793 let stream_spec = if spec_str.is_empty() {
2794 let mut spec = StreamSpecifier::default();
2796 spec.media_type = Some(filter_media_type);
2797 spec
2798 } else {
2799 StreamSpecifier::parse(spec_str).map_err(|e| {
2801 warn!("Invalid stream specifier in filter linklabel '{}': {}", linklabel, e);
2802 FilterGraphParseError::InvalidArgument
2803 })?
2804 };
2805
2806 let demux = &demuxs[file_idx];
2808 unsafe {
2809 let fmt_ctx = demux.in_fmt_ctx;
2810
2811 let mut subtitle_only_match = false;
2812 for (idx, _) in demux.get_streams().iter().enumerate() {
2813 let avstream = *(*fmt_ctx).streams.add(idx);
2814
2815 if stream_spec.matches(fmt_ctx, avstream) {
2816 let codec_type = (*avstream).codecpar.as_ref().unwrap().codec_type;
2818 if codec_type == filter_media_type {
2819 return Ok((file_idx, idx));
2820 }
2821 if codec_type == AVMEDIA_TYPE_SUBTITLE
2822 && filter_media_type == AVMEDIA_TYPE_VIDEO
2823 {
2824 subtitle_only_match = true;
2825 }
2826 }
2827 }
2828
2829 if subtitle_only_match {
2830 error!(
2834 "Stream specifier '{remainder}' in filtergraph description {desc} \
2835 matches a subtitle stream, but subtitle streams as filtergraph \
2836 inputs (sub2video) are not supported"
2837 );
2838 return Err(FilterGraphParseError::InvalidArgument.into());
2839 }
2840 }
2841
2842 warn!(
2844 "Stream specifier '{}' in filtergraph description {} matches no streams.",
2845 remainder, desc
2846 );
2847 Err(FilterGraphParseError::InvalidArgument.into())
2848}
2849
2850fn strtol(input: &str) -> Result<(i64, &str)> {
2854 let mut chars = input.chars().peekable();
2855 let mut negative = false;
2856
2857 if let Some(&ch) = chars.peek() {
2858 if ch == '-' {
2859 negative = true;
2860 chars.next();
2861 } else if !ch.is_ascii_digit() {
2862 return Err(ParseInteger);
2863 }
2864 }
2865
2866 let number_start = input.len() - chars.clone().collect::<String>().len();
2867
2868 let number_str: String = chars.by_ref().take_while(|ch| ch.is_ascii_digit()).collect();
2869
2870 if number_str.is_empty() {
2871 return Err(ParseInteger);
2872 }
2873
2874 let number: i64 = number_str.parse().map_err(|_| ParseInteger)?;
2875
2876 let remainder_index = number_start + number_str.len();
2877 let remainder = &input[remainder_index..];
2878
2879 if negative {
2880 Ok((-number, remainder))
2881 } else {
2882 Ok((number, remainder))
2883 }
2884}
2885
2886fn init_filter_graphs(filter_complexs: Vec<FilterComplex>) -> Result<Vec<FilterGraph>> {
2887 let mut filter_graphs = Vec::with_capacity(filter_complexs.len());
2888 for (i, filter) in filter_complexs.iter().enumerate() {
2889 let filter_graph = init_filter_graph(i, &filter.filter_descs, filter.hw_device.clone())?;
2890 filter_graphs.push(filter_graph);
2891 }
2892 Ok(filter_graphs)
2893}
2894
2895#[cfg(feature = "docs-rs")]
2896fn init_filter_graph(
2897 fg_index: usize,
2898 filter_desc: &str,
2899 hw_device: Option<String>,
2900) -> Result<FilterGraph> {
2901 Err(Error::Bug)
2902}
2903
2904#[cfg(not(feature = "docs-rs"))]
2905fn init_filter_graph(
2906 fg_index: usize,
2907 filter_desc: &str,
2908 hw_device: Option<String>,
2909) -> Result<FilterGraph> {
2910 let desc_cstr = CString::new(filter_desc)?;
2911
2912 unsafe {
2913 let mut graph = avfilter_graph_alloc();
2916 (*graph).nb_threads = 1;
2917
2918 let mut seg = null_mut();
2919 let mut ret = avfilter_graph_segment_parse(graph, desc_cstr.as_ptr(), 0, &mut seg);
2920 if ret < 0 {
2921 avfilter_graph_free(&mut graph);
2922 return Err(FilterGraphParseError::from(ret).into());
2923 }
2924
2925 ret = avfilter_graph_segment_create_filters(seg, 0);
2926 if ret < 0 {
2927 avfilter_graph_free(&mut graph);
2928 avfilter_graph_segment_free(&mut seg);
2929 return Err(FilterGraphParseError::from(ret).into());
2930 }
2931
2932 #[cfg(not(feature = "docs-rs"))]
2933 {
2934 ret = graph_opts_apply(seg);
2935 }
2936 if ret < 0 {
2937 avfilter_graph_segment_free(&mut seg);
2938 avfilter_graph_free(&mut graph);
2939 return Err(FilterGraphParseError::from(ret).into());
2940 }
2941
2942 let mut inputs = null_mut();
2943 let mut outputs = null_mut();
2944 ret = avfilter_graph_segment_apply(seg, 0, &mut inputs, &mut outputs);
2945 avfilter_graph_segment_free(&mut seg);
2946
2947 if ret < 0 {
2948 avfilter_inout_free(&mut inputs);
2949 avfilter_inout_free(&mut outputs);
2950 avfilter_graph_free(&mut graph);
2951 return Err(FilterGraphParseError::from(ret).into());
2952 }
2953
2954 let input_filters = inouts_to_input_filters(fg_index, inputs)?;
2955 let output_filters = inouts_to_output_filters(outputs)?;
2956
2957 if output_filters.is_empty() {
2958 avfilter_inout_free(&mut inputs);
2959 avfilter_inout_free(&mut outputs);
2960 avfilter_graph_free(&mut graph);
2961 return Err(FilterZeroOutputs);
2962 }
2963
2964 let filter_graph = FilterGraph::new(
2965 filter_desc.to_string(),
2966 hw_device,
2967 input_filters,
2968 output_filters,
2969 );
2970
2971 avfilter_inout_free(&mut inputs);
2972 avfilter_inout_free(&mut outputs);
2973 avfilter_graph_free(&mut graph);
2974
2975 Ok(filter_graph)
2976 }
2977}
2978
2979unsafe fn inouts_to_input_filters(
2980 fg_index: usize,
2981 inouts: *mut AVFilterInOut,
2982) -> Result<Vec<InputFilter>> {
2983 let mut cur = inouts;
2984 let mut filterinouts = Vec::new();
2985 let mut filter_index = 0;
2986 while !cur.is_null() {
2987 let linklabel = if (*cur).name.is_null() {
2988 ""
2989 } else {
2990 let linklabel = CStr::from_ptr((*cur).name);
2991 let result = linklabel.to_str();
2992 if result.is_err() {
2993 return Err(FilterDescUtf8);
2994 }
2995 result.unwrap()
2996 };
2997
2998 let filter_ctx = (*cur).filter_ctx;
2999 let media_type = avfilter_pad_get_type((*filter_ctx).input_pads, (*cur).pad_idx);
3000
3001 let pads = (*filter_ctx).input_pads;
3002 let nb_pads = (*filter_ctx).nb_inputs;
3003
3004 let name = describe_filter_link(cur, filter_ctx, pads, nb_pads)?;
3005
3006 let fallback = frame_alloc()?;
3007
3008 let mut filter = InputFilter::new(linklabel.to_string(), media_type, name, fallback);
3009 filter.opts.name = format!("fg:{fg_index}:{filter_index}");
3010 filterinouts.push(filter);
3011
3012 cur = (*cur).next;
3013 filter_index += 1;
3014 }
3015 Ok(filterinouts)
3016}
3017
3018unsafe fn inouts_to_output_filters(inouts: *mut AVFilterInOut) -> Result<Vec<OutputFilter>> {
3019 let mut cur = inouts;
3020 let mut output_filters = Vec::new();
3021 while !cur.is_null() {
3022 let linklabel = if (*cur).name.is_null() {
3023 ""
3024 } else {
3025 let linklabel = CStr::from_ptr((*cur).name);
3026 let result = linklabel.to_str();
3027 if result.is_err() {
3028 return Err(FilterDescUtf8);
3029 }
3030 result.unwrap()
3031 };
3032
3033 let filter_ctx = (*cur).filter_ctx;
3034 let media_type = avfilter_pad_get_type((*filter_ctx).output_pads, (*cur).pad_idx);
3035
3036 let pads = (*filter_ctx).output_pads;
3037 let nb_pads = (*filter_ctx).nb_outputs;
3038
3039 let name = describe_filter_link(cur, filter_ctx, pads, nb_pads)?;
3040
3041 let filter = OutputFilter::new(linklabel.to_string(), media_type, name);
3042 output_filters.push(filter);
3043
3044 cur = (*cur).next;
3045 }
3046 Ok(output_filters)
3047}
3048
3049unsafe fn describe_filter_link(
3050 cur: *mut AVFilterInOut,
3051 filter_ctx: *mut AVFilterContext,
3052 pads: *mut AVFilterPad,
3053 nb_pads: c_uint,
3054) -> Result<String> {
3055 let filter = (*filter_ctx).filter;
3056 let name = (*filter).name;
3057 let name = CStr::from_ptr(name);
3058 let result = name.to_str();
3059 if result.is_err() {
3060 return Err(FilterNameUtf8);
3061 }
3062 let name = result.unwrap();
3063
3064 let name = if nb_pads > 1 {
3065 name.to_string()
3066 } else {
3067 let pad_name = avfilter_pad_get_name(pads, (*cur).pad_idx);
3068 let pad_name = CStr::from_ptr(pad_name);
3069 let result = pad_name.to_str();
3070 if result.is_err() {
3071 return Err(FilterNameUtf8);
3072 }
3073 let pad_name = result.unwrap();
3074 format!("{name}:{pad_name}")
3075 };
3076 Ok(name)
3077}
3078
3079fn open_input_files(
3080 inputs: &mut Vec<Input>,
3081 copy_ts: bool,
3082 interrupt_state: &Arc<crate::core::context::InterruptState>,
3083) -> Result<Vec<Demuxer>> {
3084 let mut demuxs = Vec::new();
3085 for (i, input) in inputs.iter_mut().enumerate() {
3086 unsafe {
3087 let result = open_input_file(i, input, copy_ts, interrupt_state);
3088 if let Err(e) = result {
3089 return Err(e);
3091 }
3092 let demux = result.unwrap();
3093 demuxs.push(demux)
3094 }
3095 }
3096 Ok(demuxs)
3097}
3098
3099#[cfg(feature = "docs-rs")]
3100unsafe fn open_input_file(
3101 index: usize,
3102 input: &mut Input,
3103 copy_ts: bool,
3104 interrupt_state: &Arc<crate::core::context::InterruptState>,
3105) -> Result<Demuxer> {
3106 Err(Error::Bug)
3107}
3108
3109#[cfg(not(feature = "docs-rs"))]
3110unsafe fn open_input_file(
3111 index: usize,
3112 input: &mut Input,
3113 copy_ts: bool,
3114 interrupt_state: &Arc<crate::core::context::InterruptState>,
3115) -> Result<Demuxer> {
3116 let mut in_fmt_ctx = avformat_alloc_context();
3117 if in_fmt_ctx.is_null() {
3118 return Err(OpenInputError::OutOfMemory.into());
3119 }
3120
3121 (*in_fmt_ctx).interrupt_callback = ffmpeg_sys_next::AVIOInterruptCB {
3124 callback: Some(crate::core::context::input_interrupt_cb),
3125 opaque: Arc::as_ptr(interrupt_state) as *mut c_void,
3126 };
3127
3128 let recording_time_us = match input.stop_time_us {
3129 None => input.recording_time_us,
3130 Some(stop_time_us) => {
3131 let start_time_us = input.start_time_us.unwrap_or(0);
3132 if stop_time_us <= start_time_us {
3133 error!("stop_time_us value smaller than start_time_us; aborting.");
3134 return Err(OpenOutputError::InvalidArgument.into());
3135 } else {
3136 Some(stop_time_us - start_time_us)
3137 }
3138 }
3139 };
3140
3141 let file_iformat = if let Some(format) = &input.format {
3142 let format_cstr = CString::new(format.clone())?;
3143
3144 let file_iformat = ffmpeg_sys_next::av_find_input_format(format_cstr.as_ptr());
3145 if file_iformat.is_null() {
3146 error!("Unknown input format: '{format}'");
3147 return Err(OpenInputError::InvalidFormat(format.clone()).into());
3148 }
3149 file_iformat
3150 } else {
3151 null()
3152 };
3153
3154 let input_opts = convert_options(input.input_opts.clone())?;
3155 let mut input_opts = DictGuard::new(hashmap_to_avdictionary(&input_opts));
3158
3159 let mut injected_scan_all_pmts = false;
3160 match &input.url {
3161 None => {
3162 if input.read_callback.is_none() {
3163 error!("input url and read_callback is none.");
3164 return Err(OpenInputError::InvalidSource.into());
3165 }
3166
3167 let avio_ctx_buffer_size = 1024 * 64;
3168 let mut avio_ctx_buffer = av_malloc(avio_ctx_buffer_size);
3169 if avio_ctx_buffer.is_null() {
3170 avformat_close_input(&mut in_fmt_ctx);
3171 return Err(OpenInputError::OutOfMemory.into());
3172 }
3173
3174 let have_seek_callback = input.seek_callback.is_some();
3175 let input_opaque = Box::new(InputOpaque {
3176 read: input.read_callback.take().unwrap(),
3177 seek: input.seek_callback.take(),
3178 });
3179 let opaque = Box::into_raw(input_opaque) as *mut libc::c_void;
3180
3181 let avio_ctx = avio_alloc_context(
3182 avio_ctx_buffer as *mut libc::c_uchar,
3183 avio_ctx_buffer_size as i32,
3184 0,
3185 opaque,
3186 Some(read_packet_wrapper),
3187 None,
3188 if have_seek_callback {
3189 Some(seek_input_packet_wrapper)
3190 } else {
3191 None
3192 },
3193 );
3194 if avio_ctx.is_null() {
3195 av_freep(&mut avio_ctx_buffer as *mut _ as *mut c_void);
3196 let _ = Box::from_raw(opaque as *mut InputOpaque);
3198 avformat_close_input(&mut in_fmt_ctx);
3199 return Err(OpenInputError::OutOfMemory.into());
3200 }
3201
3202 (*in_fmt_ctx).pb = avio_ctx;
3203 (*in_fmt_ctx).flags = AVFMT_FLAG_CUSTOM_IO;
3204
3205 let ret =
3206 avformat_open_input(&mut in_fmt_ctx, null(), file_iformat, input_opts.as_double_ptr());
3207 if ret < 0 {
3208 avformat_close_input(&mut in_fmt_ctx);
3211 crate::core::context::free_input_opaque(avio_ctx);
3212 return Err(OpenInputError::from(ret).into());
3213 }
3214
3215 let ret = avformat_find_stream_info(in_fmt_ctx, null_mut());
3216 if ret < 0 {
3217 avformat_close_input(&mut in_fmt_ctx);
3218 crate::core::context::free_input_opaque(avio_ctx);
3219 return Err(FindStreamError::from(ret).into());
3220 }
3221
3222 if !have_seek_callback && input_requires_seek(in_fmt_ctx) {
3223 avformat_close_input(&mut in_fmt_ctx);
3224 crate::core::context::free_input_opaque(avio_ctx);
3225 warn!("The input format supports seeking, but no seek callback is provided. This may cause issues.");
3226 return Err(OpenInputError::SeekFunctionMissing.into());
3227 }
3228 }
3229 Some(url) => {
3230 let url_cstr = CString::new(url.as_str())?;
3231
3232 let scan_all_pmts_key = CString::new("scan_all_pmts")?;
3233 if ffmpeg_sys_next::av_dict_get(
3234 input_opts.as_ptr(),
3235 scan_all_pmts_key.as_ptr(),
3236 null(),
3237 ffmpeg_sys_next::AV_DICT_MATCH_CASE,
3238 )
3239 .is_null()
3240 {
3241 let scan_all_pmts_value = CString::new("1")?;
3242 ffmpeg_sys_next::av_dict_set(
3243 input_opts.as_double_ptr(),
3244 scan_all_pmts_key.as_ptr(),
3245 scan_all_pmts_value.as_ptr(),
3246 ffmpeg_sys_next::AV_DICT_DONT_OVERWRITE,
3247 );
3248 injected_scan_all_pmts = true;
3249 };
3250 (*in_fmt_ctx).flags |= ffmpeg_sys_next::AVFMT_FLAG_NONBLOCK;
3251
3252 let mut ret = avformat_open_input(
3253 &mut in_fmt_ctx,
3254 url_cstr.as_ptr(),
3255 file_iformat,
3256 input_opts.as_double_ptr(),
3257 );
3258 if ret < 0 {
3259 avformat_close_input(&mut in_fmt_ctx);
3260 return Err(OpenInputError::from(ret).into());
3261 }
3262
3263 ret = avformat_find_stream_info(in_fmt_ctx, null_mut());
3264 if ret < 0 {
3265 avformat_close_input(&mut in_fmt_ctx);
3266 return Err(FindStreamError::from(ret).into());
3267 }
3268 }
3269 }
3270
3271 if injected_scan_all_pmts {
3275 input_opts.remove(&CString::new("scan_all_pmts")?);
3276 }
3277 for key in input_opts.leftover_keys() {
3278 warn!("Option '{key}' was not recognized by input {index}");
3279 }
3280
3281 let mut timestamp = input.start_time_us.unwrap_or(0);
3282 if (*in_fmt_ctx).start_time != ffmpeg_sys_next::AV_NOPTS_VALUE {
3284 timestamp += (*in_fmt_ctx).start_time;
3285 }
3286
3287 if let Some(start_time_us) = input.start_time_us {
3289 let mut seek_timestamp = timestamp;
3290
3291 if (*(*in_fmt_ctx).iformat).flags & ffmpeg_sys_next::AVFMT_SEEK_TO_PTS == 0 {
3292 let mut dts_heuristic = false;
3293 let stream_count = (*in_fmt_ctx).nb_streams;
3294
3295 for i in 0..stream_count {
3296 let stream = *(*in_fmt_ctx).streams.add(i as usize);
3297 let par = (*stream).codecpar;
3298 if (*par).video_delay != 0 {
3299 dts_heuristic = true;
3300 break;
3301 }
3302 }
3303 if dts_heuristic {
3304 seek_timestamp -= 3 * AV_TIME_BASE as i64 / 23;
3305 }
3306 }
3307 let ret = ffmpeg_sys_next::avformat_seek_file(
3308 in_fmt_ctx,
3309 -1,
3310 i64::MIN,
3311 seek_timestamp,
3312 seek_timestamp,
3313 0,
3314 );
3315 if ret < 0 {
3316 warn!(
3317 "could not seek to position {:.3}",
3318 start_time_us as f64 / AV_TIME_BASE as f64
3319 );
3320 }
3321 }
3322
3323 let url = input
3324 .url
3325 .clone()
3326 .unwrap_or_else(|| format!("read_callback[{index}]"));
3327
3328 let demux = Demuxer::new(
3329 url,
3330 input.url.is_none(),
3331 in_fmt_ctx,
3332 0 - if copy_ts { 0 } else { timestamp },
3333 input.frame_pipelines.take(),
3334 input.video_codec.clone(),
3335 input.audio_codec.clone(),
3336 input.subtitle_codec.clone(),
3337 input.readrate,
3338 input.start_time_us,
3339 recording_time_us,
3340 input.exit_on_error,
3341 input.stream_loop,
3342 input.hwaccel.clone(),
3343 input.hwaccel_device.clone(),
3344 input.hwaccel_output_format.clone(),
3345 copy_ts,
3346 input.autorotate.unwrap_or(true), input.ts_scale.unwrap_or(1.0), match input.framerate { Some((num, den)) => AVRational { num, den },
3350 None => AVRational { num: 0, den: 0 },
3351 },
3352 input.log_level_offset.unwrap_or(0),
3353 )?;
3354
3355 Ok(demux)
3356}
3357
3358fn maybe_enable_image2_update(
3368 out_fmt_ctx: *mut AVFormatContext,
3369 url: Option<&str>,
3370 max_video_frames: Option<i64>,
3371 format_opts: Option<HashMap<CString, CString>>,
3372) -> Option<HashMap<CString, CString>> {
3373 if max_video_frames != Some(1) {
3374 return format_opts;
3375 }
3376 let Some(url) = url else {
3381 return format_opts;
3382 };
3383 if url.contains('%') {
3384 return format_opts;
3385 }
3386 let is_image2 = unsafe {
3391 let oformat = (*out_fmt_ctx).oformat;
3392 !oformat.is_null()
3393 && !(*oformat).name.is_null()
3394 && std::ffi::CStr::from_ptr((*oformat).name).to_bytes() == b"image2"
3395 };
3396 if !is_image2 {
3397 return format_opts;
3398 }
3399
3400 let mut opts = format_opts.unwrap_or_default();
3401 let user_configured = opts.keys().any(|key| {
3402 matches!(key.to_bytes(), b"update" | b"strftime" | b"frame_pts")
3403 });
3404 if !user_configured {
3405 info!("single-image output detected (max_video_frames=1): enabling image2 'update' mode");
3406 opts.insert(CString::new("update").unwrap(), CString::new("1").unwrap());
3408 }
3409 Some(opts)
3410}
3411
3412fn convert_options(
3413 opts: Option<HashMap<String, String>>,
3414) -> Result<Option<HashMap<CString, CString>>> {
3415 if opts.is_none() {
3416 return Ok(None);
3417 }
3418
3419 let converted = opts.map(|map| {
3420 map.into_iter()
3421 .map(|(k, v)| Ok((CString::new(k)?, CString::new(v)?)))
3422 .collect::<Result<HashMap<CString, CString>, _>>() });
3424
3425 converted.transpose() }
3427
3428unsafe fn input_requires_seek(fmt_ctx: *mut AVFormatContext) -> bool {
3429 if fmt_ctx.is_null() {
3430 return false;
3431 }
3432
3433 let mut format_name = "unknown".to_string();
3434 let mut format_names: Vec<&str> = Vec::with_capacity(0);
3435
3436 if !(*fmt_ctx).iformat.is_null() {
3437 let iformat = (*fmt_ctx).iformat;
3438 format_name = CStr::from_ptr((*iformat).name)
3439 .to_string_lossy()
3440 .into_owned();
3441 let flags = (*iformat).flags;
3442 let no_binsearch = flags & AVFMT_NOBINSEARCH != 0;
3443 let no_gensearch = flags & AVFMT_NOGENSEARCH != 0;
3444
3445 log::debug!(
3446 "Input format '{format_name}' - Binary search: {}, Generic search: {}",
3447 if no_binsearch { "Disabled" } else { "Enabled" },
3448 if no_gensearch { "Disabled" } else { "Enabled" }
3449 );
3450
3451 format_names = format_name.split(',').collect();
3452
3453 if format_names.iter().any(|&f| {
3454 matches!(
3455 f,
3456 "mp4" | "mkv" | "avi" | "mov" | "flac" | "wav" | "aac" | "ogg" | "mp3" | "webm"
3457 )
3458 })
3459 && !no_binsearch && !no_gensearch {
3460 return true;
3461 }
3462
3463 if format_names.iter().any(|&f| {
3464 matches!(
3465 f,
3466 "hls" | "m3u8" | "mpegts" | "mms" | "udp" | "rtp" | "rtp_mpegts" | "http" | "srt"
3467 )
3468 }) {
3469 log::debug!("Live stream detected ({format_name}). Seeking is not possible.");
3470 return false;
3471 }
3472
3473 if no_binsearch && no_gensearch {
3474 log::debug!("Input format '{format_name}' has both NOBINSEARCH and NOGENSEARCH set. Seeking is likely restricted.");
3475 }
3476 }
3477
3478 let format_duration = (*fmt_ctx).duration;
3479
3480 if format_names.contains(&"flv") {
3481 if format_duration <= 0 {
3482 log::debug!(
3483 "Input format 'flv' detected with no valid duration. Seeking is not possible."
3484 );
3485 } else {
3486 log::warn!("Input format 'flv' detected with a valid duration. While seeking may still be possible, it is highly recommended to add a `seek_callback()` for optimal input handling, especially when seeking or random access to specific segments is required.");
3487 }
3488 return false;
3489 }
3490
3491 if format_duration > 0 {
3492 log::debug!("Format '{format_name}' has a duration of {format_duration}. Seeking is likely possible.");
3493 return true;
3494 }
3495
3496 let mut video_stream_index = -1;
3497 for i in 0..(*fmt_ctx).nb_streams {
3498 let stream = *(*fmt_ctx).streams.offset(i as isize);
3499 if (*stream).codecpar.is_null() {
3500 continue;
3501 }
3502 if (*(*stream).codecpar).codec_type == AVMEDIA_TYPE_VIDEO {
3503 video_stream_index = i as i32;
3504 break;
3505 }
3506 }
3507
3508 let stream_index = if video_stream_index >= 0 {
3509 video_stream_index
3510 } else {
3511 -1
3512 };
3513
3514 let original_pos = if !(*fmt_ctx).pb.is_null() {
3515 (*(*fmt_ctx).pb).pos
3516 } else {
3517 -1
3518 };
3519
3520 if original_pos >= 0 {
3521 let seek_target = AV_TIME_BASE as i64;
3522 let seek_result = av_seek_frame(fmt_ctx, stream_index, seek_target, AVSEEK_FLAG_BACKWARD);
3523
3524 if seek_result >= 0 {
3525 log::debug!("Seek test successful.");
3526
3527 (*(*fmt_ctx).pb).pos = original_pos;
3528 avformat_flush(fmt_ctx);
3529 log::debug!("Restored fmt_ctx.pb.pos to {original_pos} and flushed format context.",);
3530 return true;
3531 } else {
3532 log::debug!("Seek test failed (return code {seek_result}). This format likely does not support seeking.");
3533 }
3534 }
3535
3536 false
3537}
3538
3539#[cfg(test)]
3540mod tests {
3541 use std::ffi::{CStr, CString};
3542 use std::ptr::null_mut;
3543
3544 use crate::core::context::ffmpeg_context::{strtol, FfmpegContext, Output};
3545 use ffmpeg_sys_next::{
3546 avfilter_graph_alloc, avfilter_graph_free, avfilter_graph_parse_ptr, avfilter_inout_free,
3547 };
3548
3549 use crate::core::context::ffmpeg_context::{bind_fg_inputs_by_fg, fg_complex_bind_input};
3550 use crate::core::context::filter_graph::FilterGraph;
3551 use crate::core::context::input_filter::InputFilter;
3552 use crate::core::context::null_frame;
3553 use crate::core::context::output_filter::OutputFilter;
3554 use ffmpeg_sys_next::AVMediaType::AVMEDIA_TYPE_VIDEO;
3555
3556 fn test_input(linklabel: &str, name: &str) -> InputFilter {
3557 InputFilter::new(
3558 linklabel.to_string(),
3559 AVMEDIA_TYPE_VIDEO,
3560 name.to_string(),
3561 null_frame(),
3562 )
3563 }
3564
3565 fn test_graph(inputs: Vec<InputFilter>, outputs: Vec<OutputFilter>) -> FilterGraph {
3566 FilterGraph::new("null".to_string(), None, inputs, outputs)
3567 }
3568
3569 #[test]
3570 fn cross_graph_binding_uses_input_pad_index_and_marks_bound() {
3571 let producer = test_graph(
3577 vec![test_input("", "in0")],
3578 vec![OutputFilter::new(
3579 "mid".to_string(),
3580 AVMEDIA_TYPE_VIDEO,
3581 "out0".to_string(),
3582 )],
3583 );
3584 let consumer = test_graph(
3585 vec![test_input("mid", "in0"), test_input("", "in1")],
3586 vec![OutputFilter::new(
3587 String::new(),
3588 AVMEDIA_TYPE_VIDEO,
3589 "out0".to_string(),
3590 )],
3591 );
3592 let mut graphs = vec![producer, consumer];
3593
3594 bind_fg_inputs_by_fg(&mut graphs).unwrap();
3595
3596 assert!(
3597 graphs[0].outputs[0].has_dst(),
3598 "producer output must be connected to the consumer"
3599 );
3600 assert_eq!(
3601 graphs[0].outputs[0].fg_input_index, 0,
3602 "fg_input_index must be the consumer's input PAD index, not its graph index"
3603 );
3604 assert_eq!(
3605 graphs[0].outputs[0].finished_flag_list.len(),
3606 2,
3607 "the producer must hold the consumer's per-pad finished flags"
3608 );
3609 assert!(
3610 graphs[1].inputs[0].bound,
3611 "the cross-connected pad must be marked bound"
3612 );
3613 assert!(
3614 !graphs[1].inputs[1].bound,
3615 "unrelated pads must stay unbound"
3616 );
3617 }
3618
3619 #[test]
3620 fn complex_bind_skips_already_bound_labeled_input() {
3621 let mut consumer = test_graph(
3622 vec![test_input("mid", "in0")],
3623 vec![OutputFilter::new(
3624 String::new(),
3625 AVMEDIA_TYPE_VIDEO,
3626 "out0".to_string(),
3627 )],
3628 );
3629 consumer.inputs[0].bound = true;
3630
3631 let result = fg_complex_bind_input(&mut consumer, 0, &mut Vec::new());
3634 assert!(
3635 result.is_ok(),
3636 "a pad already bound to another graph must not be re-bound: {result:?}"
3637 );
3638 }
3639
3640 #[test]
3641 fn complex_bind_skips_bound_reserved_in_label() {
3642 let mut consumer = test_graph(
3645 vec![test_input("in", "in0")],
3646 vec![OutputFilter::new(
3647 String::new(),
3648 AVMEDIA_TYPE_VIDEO,
3649 "out0".to_string(),
3650 )],
3651 );
3652 consumer.inputs[0].bound = true;
3653
3654 let result = fg_complex_bind_input(&mut consumer, 0, &mut Vec::new());
3655 assert!(
3656 result.is_ok(),
3657 "a bound pad labeled 'in' must not fall through to stream auto-binding: {result:?}"
3658 );
3659 }
3660
3661 #[test]
3662 fn test_filter() {
3663 let desc_cstr = CString::new("[1:v][2:v]concat=n=2:v=1:a=0[vout]").unwrap();
3664 unsafe {
3667 let mut graph = avfilter_graph_alloc();
3668 let mut inputs = null_mut();
3669 let mut outputs = null_mut();
3670
3671 let ret = avfilter_graph_parse_ptr(
3672 graph,
3673 desc_cstr.as_ptr(),
3674 &mut inputs,
3675 &mut outputs,
3676 null_mut(),
3677 );
3678 if ret < 0 {
3679 avfilter_inout_free(&mut inputs);
3680 avfilter_inout_free(&mut outputs);
3681 avfilter_graph_free(&mut graph);
3682 println!("err ret:{}", crate::util::ffmpeg_utils::av_err2str(ret));
3683 return;
3684 }
3685
3686 println!("inputs.is_null:{}", inputs.is_null());
3687 println!("outputs.is_null:{}", outputs.is_null());
3688
3689 let mut cur = inputs;
3690 while !cur.is_null() {
3691 let input_name = CStr::from_ptr((*cur).name);
3692 println!("Input name: {}", input_name.to_str().unwrap());
3693 cur = (*cur).next;
3694 }
3695
3696 let output_name = CStr::from_ptr((*outputs).name);
3697 println!("Output name: {}", output_name.to_str().unwrap());
3698
3699 let filter_ctx = (*outputs).filter_ctx;
3700 avfilter_inout_free(&mut outputs);
3701 println!("filter_ctx.is_null:{}", filter_ctx.is_null());
3702 }
3703 }
3704
3705 #[test]
3706 fn test_new() {
3707 let _ = env_logger::builder()
3708 .filter_level(log::LevelFilter::Debug)
3709 .is_test(true)
3710 .try_init();
3711 let _ffmpeg_context = FfmpegContext::new(
3712 vec!["test.mp4".to_string().into()],
3713 vec!["hue=s=0".to_string().into()],
3714 vec!["output.mp4".to_string().into()],
3715 )
3716 .unwrap();
3717 let _ffmpeg_context = FfmpegContext::new(
3718 vec!["test.mp4".into()],
3719 vec!["[0:v]hue=s=0".into()],
3720 vec!["output.mp4".to_string().into()],
3721 )
3722 .unwrap();
3723 let _ffmpeg_context = FfmpegContext::new(
3724 vec!["test.mp4".into()],
3725 vec!["hue=s=0[my-out]".into()],
3726 vec![Output::from("output.mp4").add_stream_map("my-out")],
3727 )
3728 .unwrap();
3729 let result = FfmpegContext::new(
3730 vec!["test.mp4".into()],
3731 vec!["hue=s=0".into()],
3732 vec![Output::from("output.mp4").add_stream_map("0:v?")],
3733 );
3734 assert!(result.is_err());
3735 let result = FfmpegContext::new(
3736 vec!["test.mp4".into()],
3737 vec!["hue=s=0".into()],
3738 vec![Output::from("output.mp4").add_stream_map_with_copy("1:v?")],
3739 );
3740 assert!(result.is_err());
3741 let result = FfmpegContext::new(
3742 vec!["test.mp4".into()],
3743 vec!["hue=s=0[fg-out]".into()],
3744 vec![
3745 Output::from("output.mp4").add_stream_map("my-out?"),
3746 Output::from("output.mp4").add_stream_map("fg-out"),
3747 ],
3748 );
3749 assert!(result.is_err());
3750 let result = FfmpegContext::new(
3752 vec!["test.mp4".into()],
3753 vec!["hue=s=0".into()],
3754 vec![Output::from("output.mp4").add_stream_map_with_copy("1:v")],
3755 );
3756 assert!(result.is_err());
3757 let result = FfmpegContext::new(
3758 vec!["test.mp4".into()],
3759 vec!["hue=s=0[fg-out]".into()],
3760 vec![Output::from("output.mp4").add_stream_map("fg-out?")],
3761 );
3762 assert!(result.is_err());
3763 }
3764
3765 #[test]
3766 fn test_builder() {
3767 let _ = env_logger::builder()
3768 .filter_level(log::LevelFilter::Debug)
3769 .is_test(true)
3770 .try_init();
3771
3772 let _context1 = FfmpegContext::builder()
3773 .input("test.mp4")
3774 .filter_desc("hue=s=0")
3775 .output("output.mp4")
3776 .build()
3777 .unwrap();
3778
3779 let _context2 = FfmpegContext::builder()
3780 .inputs(vec!["test.mp4"])
3781 .filter_descs(vec!["hue=s=0"])
3782 .outputs(vec!["output.mp4"])
3783 .build()
3784 .unwrap();
3785 }
3786
3787 #[test]
3788 fn test_strtol() {
3789 let input = "-123---abc";
3790 let result = strtol(input);
3791 assert_eq!(result.unwrap(), (-123, "---abc"));
3792
3793 let input = "123---abc";
3794 let result = strtol(input);
3795 assert_eq!(result.unwrap(), (123, "---abc"));
3796
3797 let input = "-123aa";
3798 let result = strtol(input);
3799 assert_eq!(result.unwrap(), (-123, "aa"));
3800
3801 let input = "-aa";
3802 let result = strtol(input);
3803 assert!(result.is_err());
3804
3805 let input = "abc";
3806 let result = strtol(input);
3807 assert!(result.is_err())
3808 }
3809}