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