1use crate::core::context::demuxer::Demuxer;
2use crate::core::context::ffmpeg_context_builder::FfmpegContextBuilder;
3use crate::core::context::filter_complex::FilterComplex;
4use crate::core::context::filter_graph::FilterGraph;
5use crate::core::context::input::Input;
6use crate::core::context::input_filter::{InputFilter, IFILTER_FLAG_AUTOROTATE};
7use crate::core::context::muxer::Muxer;
8use crate::core::context::output::{Output, StreamMap};
9use crate::core::metadata::StreamSpecifier;
10use crate::core::context::output_filter::{
11 OutputFilter, OFILTER_FLAG_AUDIO_24BIT, OFILTER_FLAG_AUTOSCALE, OFILTER_FLAG_DISABLE_CONVERT,
12};
13use crate::core::context::{frame_alloc, CodecContext};
14use crate::core::scheduler::ffmpeg_scheduler;
15use crate::core::scheduler::ffmpeg_scheduler::{FfmpegScheduler, Initialization};
16#[cfg(not(feature = "docs-rs"))]
17use crate::core::scheduler::filter_task::graph_opts_apply;
18use crate::core::scheduler::input_controller::SchNode;
19use crate::error::Error::{
20 FileSameAsInput, FilterDescUtf8, FilterNameUtf8, FilterZeroOutputs,
21 FrameFilterStreamTypeNoMatched, FrameFilterTypeNoMatched, ParseInteger,
22};
23use crate::error::FilterGraphParseError::{
24 InvalidFileIndexInFg, InvalidFilterSpecifier, OutputUnconnected,
25};
26use crate::error::{
27 AllocOutputContextError, FilterGraphParseError, FindStreamError, OpenInputError,
28 OpenOutputError,
29};
30use crate::error::{Error, Result};
31use crate::filter::frame_pipeline::FramePipeline;
32use crate::util::ffmpeg_utils::hashmap_to_avdictionary;
33#[cfg(not(feature = "docs-rs"))]
34use ffmpeg_sys_next::AVChannelOrder::AV_CHANNEL_ORDER_UNSPEC;
35#[cfg(not(feature = "docs-rs"))]
36use ffmpeg_sys_next::AVCodecConfig::*;
37use ffmpeg_sys_next::AVCodecID::{AV_CODEC_ID_AC3, AV_CODEC_ID_MP3, AV_CODEC_ID_NONE};
38use ffmpeg_sys_next::AVColorRange::AVCOL_RANGE_UNSPECIFIED;
39use ffmpeg_sys_next::AVColorSpace::AVCOL_SPC_UNSPECIFIED;
40use ffmpeg_sys_next::AVMediaType::{
41 AVMEDIA_TYPE_ATTACHMENT, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_DATA, AVMEDIA_TYPE_SUBTITLE,
42 AVMEDIA_TYPE_VIDEO,
43};
44use ffmpeg_sys_next::AVPixelFormat::AV_PIX_FMT_NONE;
45use ffmpeg_sys_next::AVSampleFormat::AV_SAMPLE_FMT_NONE;
46use ffmpeg_sys_next::{
47 av_add_q, av_channel_layout_default, av_codec_get_id, av_codec_get_tag2, av_dict_free,
48 av_freep, av_get_exact_bits_per_sample, av_guess_codec, av_guess_format, av_guess_frame_rate,
49 av_inv_q, av_malloc, av_rescale_q, av_seek_frame, avcodec_alloc_context3,
50 avcodec_descriptor_get, avcodec_descriptor_get_by_name, avcodec_find_encoder,
51 avcodec_find_encoder_by_name, avcodec_get_name, avcodec_parameters_from_context,
52 avcodec_parameters_to_context, avfilter_graph_alloc, avfilter_graph_free, avfilter_inout_free,
53 avfilter_pad_get_name, avfilter_pad_get_type, avformat_alloc_context,
54 avformat_alloc_output_context2, avformat_close_input, avformat_find_stream_info,
55 avformat_flush, avformat_free_context, avformat_open_input, avio_alloc_context,
56 avio_context_free, avio_open, AVCodec, AVCodecID, AVColorRange, AVColorSpace, AVFilterContext,
57 AVFilterInOut, AVFilterPad, AVFormatContext, AVMediaType, AVOutputFormat, AVPixelFormat,
58 AVRational, AVSampleFormat, AVStream, AVERROR_ENCODER_NOT_FOUND, AVFMT_FLAG_CUSTOM_IO,
59 AVFMT_GLOBALHEADER, AVFMT_NOBINSEARCH, AVFMT_NOFILE, AVFMT_NOGENSEARCH, AVFMT_NOSTREAMS,
60 AVIO_FLAG_WRITE, AVSEEK_FLAG_BACKWARD, AV_CODEC_PROP_BITMAP_SUB, AV_CODEC_PROP_TEXT_SUB,
61 AV_TIME_BASE,
62};
63#[cfg(not(feature = "docs-rs"))]
64use ffmpeg_sys_next::{
65 av_channel_layout_copy, av_packet_side_data_new, avcodec_get_supported_config,
66 avfilter_graph_segment_apply, avfilter_graph_segment_create_filters,
67 avfilter_graph_segment_free, avfilter_graph_segment_parse, AVChannelLayout,
68};
69use log::{debug, error, info, warn};
70use std::collections::HashMap;
71use std::ffi::{c_uint, c_void, CStr, CString};
72use std::ptr::{null, null_mut};
73use std::sync::Arc;
74
75pub struct FfmpegContext {
76 pub(crate) independent_readrate: bool,
77 pub(crate) demuxs: Vec<Demuxer>,
78 pub(crate) filter_graphs: Vec<FilterGraph>,
79 pub(crate) muxs: Vec<Muxer>,
80}
81
82unsafe impl Send for FfmpegContext {}
83unsafe impl Sync for FfmpegContext {}
84
85impl FfmpegContext {
86 pub fn builder() -> FfmpegContextBuilder {
99 FfmpegContextBuilder::new()
100 }
101
102 pub fn start(self) -> Result<FfmpegScheduler<ffmpeg_scheduler::Running>> {
128 let ffmpeg_scheduler = FfmpegScheduler::new(self);
129 ffmpeg_scheduler.start()
130 }
131
132 #[allow(dead_code)]
133 pub(crate) fn new(
134 inputs: Vec<Input>,
135 filter_complexs: Vec<FilterComplex>,
136 outputs: Vec<Output>,
137 ) -> Result<FfmpegContext> {
138 Self::new_with_options(false, inputs, filter_complexs, outputs, false)
139 }
140
141 pub(crate) fn new_with_options(
142 mut independent_readrate: bool,
143 mut inputs: Vec<Input>,
144 filter_complexs: Vec<FilterComplex>,
145 mut outputs: Vec<Output>,
146 copy_ts: bool,
147 ) -> Result<FfmpegContext> {
148 check_duplicate_inputs_outputs(&inputs, &outputs)?;
149
150 crate::core::initialize_ffmpeg();
151
152 let mut demuxs = open_input_files(&mut inputs, copy_ts)?;
153
154 if demuxs.len() <= 1 {
155 independent_readrate = false;
156 }
157
158 let mut filter_graphs = if !filter_complexs.is_empty() {
159 let mut filter_graphs = init_filter_graphs(filter_complexs)?;
160 fg_bind_inputs(&mut filter_graphs, &mut demuxs)?;
161 filter_graphs
162 } else {
163 Vec::new()
164 };
165
166 let mut muxs = open_output_files(&mut outputs, copy_ts)?;
167
168 outputs_bind(&mut muxs, &mut filter_graphs, &mut demuxs)?;
169
170 for mux in muxs.iter_mut() {
177 if mux.recording_time_us.is_none() {
178 let mapping = mux.stream_input_mapping();
179 if !mapping.is_empty() {
180 let first_input = mapping[0].1.0;
181 let all_same_input = mapping.iter().all(|(_, (idx, _))| *idx == first_input);
182 if all_same_input {
183 if let Some(demux) = demuxs.get(first_input) {
184 if let Some(recording_time) = demux.recording_time_us {
185 mux.recording_time_us = Some(recording_time);
186 }
187 }
188 }
189 }
190 }
191 }
192
193 correct_input_start_times(&mut demuxs, copy_ts);
194
195 check_output_streams(&muxs)?;
196
197 check_fg_bindings(&filter_graphs)?;
198
199 check_frame_filter_pipeline(&muxs, &demuxs)?;
200
201 Ok(Self {
202 independent_readrate,
203 demuxs,
204 filter_graphs,
205 muxs,
206 })
207 }
208}
209
210const START_AT_ZERO: bool = false;
211
212fn correct_input_start_times(demuxs: &mut Vec<Demuxer>, copy_ts: bool) {
213 for (i, demux) in demuxs.iter_mut().enumerate() {
214 unsafe {
215 let is = demux.in_fmt_ctx;
216
217 demux.start_time_effective = (*is).start_time;
218 if (*is).start_time == ffmpeg_sys_next::AV_NOPTS_VALUE
219 || (*(*is).iformat).flags & ffmpeg_sys_next::AVFMT_TS_DISCONT == 0
220 {
221 continue;
222 }
223
224 let mut new_start_time = i64::MAX;
225 let stream_count = (*is).nb_streams;
226 for j in 0..stream_count {
227 let st = *(*is).streams.add(j as usize);
228 if (*st).discard == ffmpeg_sys_next::AVDiscard::AVDISCARD_ALL
229 || (*st).start_time == ffmpeg_sys_next::AV_NOPTS_VALUE
230 {
231 continue;
232 }
233 new_start_time = std::cmp::min(
234 new_start_time,
235 av_rescale_q(
236 (*st).start_time,
237 (*st).time_base,
238 ffmpeg_sys_next::AV_TIME_BASE_Q,
239 ),
240 );
241 }
242 let diff = new_start_time - (*is).start_time;
243 if diff != 0 {
244 debug!("Correcting start time of Input #{i} by {diff}us.");
245 demux.start_time_effective = new_start_time;
246 if copy_ts && START_AT_ZERO {
247 demux.ts_offset = -new_start_time;
248 } else if !copy_ts {
249 let abs_start_seek = (*is).start_time + demux.start_time_us.unwrap_or(0);
250 demux.ts_offset = if abs_start_seek > new_start_time {
251 -abs_start_seek
252 } else {
253 -new_start_time
254 };
255 } else if copy_ts {
256 demux.ts_offset = 0;
257 }
258
259 }
261 }
262 }
263}
264
265fn check_pipeline<T>(
266 frame_pipelines: Option<&Vec<FramePipeline>>,
267 streams: &[T],
268 tag: &str,
269 get_stream_index: impl Fn(&T) -> usize,
270 get_codec_type: impl Fn(&T) -> &AVMediaType,
271) -> Result<()> {
272 let tag_cap = {
273 let mut chars = tag.chars();
274 match chars.next() {
275 None => String::new(),
276 Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
277 }
278 };
279
280 frame_pipelines
281 .into_iter()
282 .flat_map(|pipelines| pipelines.iter())
283 .try_for_each(|pipeline| {
284 if let Some(idx) = pipeline.stream_index {
285 streams
286 .iter()
287 .any(|s| {
288 get_stream_index(s) == idx && get_codec_type(s) == &pipeline.media_type
289 })
290 .then_some(())
291 .ok_or_else(|| {
292 Into::<crate::error::Error>::into(FrameFilterStreamTypeNoMatched(
293 tag_cap.clone(),
294 idx,
295 format!("{:?}", pipeline.media_type),
296 ))
297 })
298 } else {
299 streams
300 .iter()
301 .any(|s| get_codec_type(s) == &pipeline.media_type)
302 .then_some(())
303 .ok_or_else(|| {
304 FrameFilterTypeNoMatched(tag.into(), format!("{:?}", pipeline.media_type))
305 })
306 }
307 })?;
308 Ok(())
309}
310
311fn check_frame_filter_pipeline(muxs: &[Muxer], demuxs: &[Demuxer]) -> Result<()> {
312 muxs.iter().try_for_each(|mux| {
313 check_pipeline(
314 mux.frame_pipelines.as_ref(),
315 mux.get_streams(),
316 "output",
317 |s| s.stream_index,
318 |s| &s.codec_type,
319 )
320 })?;
321 demuxs.iter().try_for_each(|demux| {
322 check_pipeline(
323 demux.frame_pipelines.as_ref(),
324 demux.get_streams(),
325 "input",
326 |s| s.stream_index,
327 |s| &s.codec_type,
328 )
329 })?;
330 Ok(())
331}
332
333fn check_fg_bindings(filter_graphs: &Vec<FilterGraph>) -> Result<()> {
334 for filter_graph in filter_graphs {
336 for (i, output_filter) in filter_graph.outputs.iter().enumerate() {
337 if !output_filter.has_dst() {
338 let linklabel = if output_filter.linklabel.is_empty() {
339 "unlabeled".to_string()
340 } else {
341 output_filter.linklabel.clone()
342 };
343 return Err(OutputUnconnected(output_filter.name.clone(), i, linklabel).into());
344 }
345 }
346 }
347 Ok(())
348}
349
350impl From<FfmpegContext> for FfmpegScheduler<Initialization> {
351 fn from(val: FfmpegContext) -> Self {
352 FfmpegScheduler::new(val)
353 }
354}
355
356fn check_output_streams(muxs: &Vec<Muxer>) -> Result<()> {
357 for mux in muxs {
358 unsafe {
359 let oformat = (*mux.out_fmt_ctx).oformat;
360 if !mux.has_src() && (*oformat).flags & AVFMT_NOSTREAMS == 0 {
361 warn!("Output file does not contain any stream");
362 return Err(OpenOutputError::NotContainStream.into());
363 }
364 }
365 }
366 Ok(())
367}
368
369unsafe fn process_metadata(mux: &Muxer, demuxs: &Vec<Demuxer>) -> Result<()> {
377 use crate::core::metadata::MetadataType;
378 use crate::core::metadata::{
379 copy_chapters_from_input, copy_metadata, copy_metadata_default, of_add_metadata,
380 };
381
382 let input_ctxs: Vec<*const AVFormatContext> = demuxs
384 .iter()
385 .map(|d| d.in_fmt_ctx as *const AVFormatContext)
386 .collect();
387
388 let mut metadata_global_manual = false;
389 let mut metadata_streams_manual = false;
390 let mut metadata_chapters_manual = false;
391
392 let mut mark_manual = |meta_type: &MetadataType| -> () {
395 match meta_type {
396 MetadataType::Global => metadata_global_manual = true,
397 MetadataType::Stream(_) => metadata_streams_manual = true,
398 MetadataType::Chapter(_) => metadata_chapters_manual = true,
399 MetadataType::Program(_) => {}
400 }
401 };
402
403 for mapping in &mux.metadata_map {
404 mark_manual(&mapping.src_type);
405 mark_manual(&mapping.dst_type);
406
407 if mapping.input_index >= input_ctxs.len() {
408 log::warn!(
409 "Metadata mapping references non-existent input file index {}",
410 mapping.input_index
411 );
412 continue;
413 }
414
415 let input_ctx = input_ctxs[mapping.input_index];
416 if let Err(e) = copy_metadata(
417 input_ctx,
418 mux.out_fmt_ctx,
419 &mapping.src_type,
420 &mapping.dst_type,
421 ) {
422 log::warn!("Failed to copy metadata from mapping: {}", e);
423 }
424 }
425
426 if mux.auto_copy_metadata && !metadata_chapters_manual {
428 if let Some(source_demux) = demuxs
429 .iter()
430 .find(|d| unsafe { !d.in_fmt_ctx.is_null() && (*d.in_fmt_ctx).nb_chapters > 0 })
431 {
432 if let Err(e) = copy_chapters_from_input(
433 source_demux.in_fmt_ctx,
434 source_demux.ts_offset,
435 mux.out_fmt_ctx,
436 mux.start_time_us,
437 mux.recording_time_us,
438 true,
439 ) {
440 log::warn!("Failed to copy chapters: {}", e);
441 }
442 }
443 }
444
445 let stream_input_mapping = mux.stream_input_mapping();
448 let encoding_streams = mux.encoding_streams();
449
450 if mux.auto_copy_metadata {
451 if let Err(e) = copy_metadata_default(
452 &input_ctxs,
453 mux.out_fmt_ctx,
454 mux.nb_streams,
455 &stream_input_mapping,
456 &encoding_streams,
457 mux.recording_time_us.is_some(),
458 mux.auto_copy_metadata,
459 metadata_global_manual,
460 metadata_streams_manual,
461 ) {
462 log::warn!("Failed to apply default metadata behavior: {}", e);
463 }
464 }
465
466 if let Err(e) = of_add_metadata(
469 mux.out_fmt_ctx,
470 &mux.global_metadata,
471 &mux.stream_metadata,
472 &mux.chapter_metadata,
473 &mux.program_metadata,
474 ) {
475 log::warn!("Failed to add user metadata: {}", e);
476 }
477
478 Ok(())
479}
480
481fn is_filter_output_linklabel(linklabel: &str) -> bool {
484 linklabel.starts_with('[')
485}
486
487unsafe fn expand_stream_maps(
491 mux: &mut Muxer,
492 demuxs: &[Demuxer],
493) -> Result<()> {
494 let stream_map_specs = std::mem::take(&mut mux.stream_map_specs);
495
496 for spec in stream_map_specs {
497 let (linklabel, is_negative) = if spec.linklabel.starts_with('-') {
499 (&spec.linklabel[1..], true)
500 } else {
501 (spec.linklabel.as_str(), false)
502 };
503
504 if is_filter_output_linklabel(linklabel) {
506 let pure_linklabel = if linklabel.starts_with('[') {
508 if let Some(end_pos) = linklabel.find(']') {
510 &linklabel[1..end_pos]
511 } else {
512 warn!("Invalid output link label: {}.", linklabel);
513 return Err(Error::OpenOutput(OpenOutputError::InvalidArgument));
514 }
515 } else {
516 linklabel
517 };
518
519 if pure_linklabel.is_empty() {
521 warn!("Invalid output link label: {}.", linklabel);
522 return Err(Error::OpenOutput(OpenOutputError::InvalidArgument));
523 }
524
525 mux.stream_maps.push(StreamMap {
527 file_index: 0, stream_index: 0, linklabel: Some(pure_linklabel.to_string()),
530 copy: spec.copy,
531 disabled: false,
532 });
533 continue;
534 }
535
536 let parse_result = strtol(linklabel);
539 if parse_result.is_err() {
540 mux.stream_maps.push(StreamMap {
543 file_index: 0,
544 stream_index: 0,
545 linklabel: Some(linklabel.to_string()),
546 copy: spec.copy,
547 disabled: false,
548 });
549 continue;
550 }
551
552 let (file_idx, remainder) = parse_result.unwrap();
553
554 if file_idx < 0 || file_idx as usize >= demuxs.len() {
556 warn!("Invalid input file index: {}.", file_idx);
557 return Err(Error::OpenOutput(OpenOutputError::InvalidArgument));
558 }
559 let file_idx = file_idx as usize;
560
561 let (spec_str, allow_unused) = if remainder.ends_with('?') {
564 (&remainder[..remainder.len()-1], true)
565 } else {
566 (remainder, false)
567 };
568
569 let stream_spec = if spec_str.is_empty() {
570 StreamSpecifier::default()
572 } else {
573 let spec_str = if spec_str.starts_with(':') {
575 &spec_str[1..]
576 } else {
577 spec_str
578 };
579
580 StreamSpecifier::parse(spec_str).map_err(|e| {
581 warn!("Invalid stream specifier in '{}': {}", linklabel, e);
582 Error::OpenOutput(OpenOutputError::InvalidArgument)
583 })?
584 };
585
586 if is_negative {
588 for existing_map in &mut mux.stream_maps {
589 if existing_map.linklabel.is_none() &&
591 existing_map.file_index == file_idx {
592 let demux = &demuxs[file_idx];
594 let fmt_ctx = demux.in_fmt_ctx;
595 let avstream = *(*fmt_ctx).streams.add(existing_map.stream_index);
596
597 if stream_spec.matches(fmt_ctx, avstream) {
598 existing_map.disabled = true;
599 }
600 }
601 }
602 continue;
604 }
605
606 let demux = &demuxs[file_idx];
608 let fmt_ctx = demux.in_fmt_ctx;
609 let mut matched_count = 0;
610
611 for (stream_idx, _) in demux.get_streams().iter().enumerate() {
612 let avstream = *(*fmt_ctx).streams.add(stream_idx);
613
614 if stream_spec.matches(fmt_ctx, avstream) {
616 mux.stream_maps.push(StreamMap {
617 file_index: file_idx,
618 stream_index: stream_idx,
619 linklabel: None,
620 copy: spec.copy,
621 disabled: false,
622 });
623 matched_count += 1;
624 }
625 }
626
627 if matched_count == 0 {
629 if allow_unused {
630 info!(
632 "Stream map '{}' matches no streams; ignoring.",
633 linklabel
634 );
635 } else {
636 warn!(
638 "Stream map '{}' matches no streams.\n\
639 To ignore this, add a trailing '?' to the map.",
640 linklabel
641 );
642 return Err(Error::OpenOutput(
643 OpenOutputError::MatchesNoStreams(linklabel.to_string())
644 ));
645 }
646 }
647 }
648
649 Ok(())
650}
651
652fn outputs_bind(
653 muxs: &mut Vec<Muxer>,
654 filter_graphs: &mut Vec<FilterGraph>,
655 demuxs: &mut Vec<Demuxer>,
656) -> Result<()> {
657 unsafe {
662 for mux in muxs.iter_mut() {
663 if !mux.stream_map_specs.is_empty() {
664 expand_stream_maps(mux, demuxs)?;
665 }
666 }
667 }
668
669 for (i, mux) in muxs.iter_mut().enumerate() {
670 if mux.stream_maps.is_empty() {
671 let mut auto_disable = 0;
672 output_bind_by_unlabeled_filter(i, mux, filter_graphs, &mut auto_disable)?;
673 map_auto_streams(i, mux, demuxs, filter_graphs, auto_disable)?;
675 } else {
676 for stream_map in mux.stream_maps.clone() {
677 map_manual(i, mux, &stream_map, filter_graphs, demuxs)?;
678 }
679 }
680
681 unsafe {
685 process_metadata(mux, demuxs)?;
686 }
687 }
688
689 Ok(())
690}
691
692fn map_manual(
693 index: usize,
694 mux: &mut Muxer,
695 stream_map: &StreamMap,
696 filter_graphs: &mut Vec<FilterGraph>,
697 demuxs: &mut Vec<Demuxer>,
698) -> Result<()> {
699 if stream_map.disabled {
701 return Ok(());
702 }
703
704 if let Some(linklabel) = &stream_map.linklabel {
706 for filter_graph in filter_graphs.iter_mut() {
708 for i in 0..filter_graph.outputs.len() {
709 let option = {
710 let output_filter = &filter_graph.outputs[i];
711 if output_filter.has_dst()
712 || output_filter.linklabel.is_empty()
713 || &output_filter.linklabel != linklabel
714 {
715 continue;
716 }
717
718 choose_encoder(mux, output_filter.media_type)?
719 };
720
721 match option {
722 None => {
723 warn!(
724 "An unexpected media_type {:?} appears in output_filter",
725 filter_graph.outputs[i].media_type
726 );
727 }
728 Some((codec_id, enc)) => {
729 return ofilter_bind_ost(index, mux, filter_graph, i, codec_id, enc, None)
730 .map(|_| ());
731 }
732 }
733 }
734 }
735
736 warn!(
738 "Output with label '{}' does not exist in any defined filter graph, \
739 or was already used elsewhere.",
740 linklabel
741 );
742 return Err(OpenOutputError::InvalidArgument.into());
743 }
744
745 let demux_idx = stream_map.file_index;
748 let stream_index = stream_map.stream_index;
749
750 let demux = &mut demuxs[demux_idx];
751
752 let demux_node = demux.node.clone();
754 let (media_type, input_stream_duration, input_stream_time_base) = {
755 let input_stream = demux.get_stream_mut(stream_index);
756 (input_stream.codec_type, input_stream.duration, input_stream.time_base)
757 };
758
759 info!(
760 "Binding output stream to input {}:{} ({})",
761 demux_idx, stream_index,
762 match media_type {
763 AVMEDIA_TYPE_VIDEO => "video",
764 AVMEDIA_TYPE_AUDIO => "audio",
765 AVMEDIA_TYPE_SUBTITLE => "subtitle",
766 AVMEDIA_TYPE_DATA => "data",
767 AVMEDIA_TYPE_ATTACHMENT => "attachment",
768 _ => "unknown",
769 }
770 );
771
772 let option = choose_encoder(mux, media_type)?;
773
774 match option {
775 None => {
776 let (packet_sender, _st, output_stream_index) = mux.new_stream(demux_node)?;
778 demux.add_packet_dst(packet_sender, stream_index, output_stream_index);
779 mux.register_stream_source(output_stream_index, demux_idx, stream_index, false);
780
781 unsafe {
782 streamcopy_init(
783 mux,
784 *(*demux.in_fmt_ctx).streams.add(stream_index),
785 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
786 demux.framerate,
787 )?;
788 rescale_duration(
789 input_stream_duration,
790 input_stream_time_base,
791 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
792 );
793 mux.stream_ready()
794 }
795 }
796 Some((codec_id, enc)) => {
797 if stream_map.copy {
799 let (packet_sender, _st, output_stream_index) = mux.new_stream(demux_node)?;
801 demux.add_packet_dst(packet_sender, stream_index, output_stream_index);
802 mux.register_stream_source(output_stream_index, demux_idx, stream_index, false);
803
804 unsafe {
805 streamcopy_init(
806 mux,
807 *(*demux.in_fmt_ctx).streams.add(stream_index),
808 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
809 demux.framerate,
810 )?;
811 rescale_duration(
812 input_stream_duration,
813 input_stream_time_base,
814 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
815 );
816 mux.stream_ready()
817 }
818 } else if media_type == AVMEDIA_TYPE_VIDEO || media_type == AVMEDIA_TYPE_AUDIO {
819 init_simple_filtergraph(
820 demux,
821 stream_index,
822 codec_id,
823 enc,
824 index,
825 mux,
826 filter_graphs,
827 demux_idx,
828 )?;
829 } else {
830 let (frame_sender, output_stream_index) =
831 mux.add_enc_stream(media_type, enc, demux_node)?;
832 let input_stream = demux.get_stream_mut(stream_index);
833 input_stream.add_dst(frame_sender);
834 demux.connect_stream(stream_index);
835 mux.register_stream_source(output_stream_index, demux_idx, stream_index, true);
836
837 unsafe {
838 rescale_duration(
839 input_stream_duration,
840 input_stream_time_base,
841 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
842 );
843 }
844 }
845 }
846 }
847
848 Ok(())
849}
850
851#[cfg(not(feature = "docs-rs"))]
852fn set_channel_layout(
853 ch_layout: &mut AVChannelLayout,
854 ch_layouts: &Option<Vec<AVChannelLayout>>,
855 layout_requested: &AVChannelLayout,
856) -> Result<()> {
857 unsafe {
858 if layout_requested.order != AV_CHANNEL_ORDER_UNSPEC {
860 let ret = av_channel_layout_copy(ch_layout, layout_requested);
861 if ret < 0 {
862 return Err(OpenOutputError::from(ret).into());
863 }
864 return Ok(());
865 }
866
867 if ch_layouts.is_none() {
870 av_channel_layout_default(ch_layout, layout_requested.nb_channels);
871 return Ok(());
872 }
873
874 if let Some(layouts) = ch_layouts {
876 for layout in layouts {
877 if layout.nb_channels == layout_requested.nb_channels {
878 let ret = av_channel_layout_copy(ch_layout, layout);
879 if ret < 0 {
880 return Err(OpenOutputError::from(ret).into());
881 }
882 return Ok(());
883 }
884 }
885 }
886
887 av_channel_layout_default(ch_layout, layout_requested.nb_channels);
889 Ok(())
890 }
891}
892
893#[cfg(feature = "docs-rs")]
894fn configure_output_filter_opts(
895 index: usize,
896 mux: &mut Muxer,
897 output_filter: &mut OutputFilter,
898 codec_id: AVCodecID,
899 enc: *const AVCodec,
900 output_stream_index: usize,
901) -> Result<()> {
902 Ok(())
903}
904
905#[cfg(not(feature = "docs-rs"))]
906fn configure_output_filter_opts(
907 index: usize,
908 mux: &mut Muxer,
909 output_filter: &mut OutputFilter,
910 codec_id: AVCodecID,
911 enc: *const AVCodec,
912 output_stream_index: usize,
913) -> Result<()> {
914 unsafe {
915 output_filter.opts.name = format!("#{index}:{output_stream_index}");
916 output_filter.opts.enc = enc;
917 output_filter.opts.trim_start_us = mux.start_time_us;
918 output_filter.opts.trim_duration_us = mux.recording_time_us;
919 output_filter.opts.ts_offset = mux.start_time_us;
920
921 output_filter.opts.flags = OFILTER_FLAG_DISABLE_CONVERT
922 | OFILTER_FLAG_AUTOSCALE
923 | if av_get_exact_bits_per_sample(codec_id) == 24 {
924 OFILTER_FLAG_AUDIO_24BIT
925 } else {
926 0
927 };
928
929 let enc_ctx = avcodec_alloc_context3(enc);
930 if enc_ctx.is_null() {
931 return Err(OpenOutputError::OutOfMemory.into());
932 }
933 let _codec_ctx = CodecContext::new(enc_ctx);
934
935 (*enc_ctx).thread_count = 0;
936
937 if output_filter.media_type == AVMEDIA_TYPE_VIDEO {
938 let mut formats: *const AVPixelFormat = null();
940 let mut ret = avcodec_get_supported_config(
941 enc_ctx,
942 null(),
943 AV_CODEC_CONFIG_PIX_FORMAT,
944 0,
945 &mut formats as *mut _ as *mut *const libc::c_void,
946 null_mut(),
947 );
948 if ret < 0 {
949 return Err(OpenOutputError::from(ret).into());
950 }
951
952 let mut current = formats;
953 let mut format_list = Vec::new();
954 let mut count = 0;
955 const MAX_FORMATS: usize = 512;
956 while !current.is_null() && *current != AV_PIX_FMT_NONE && count < MAX_FORMATS {
957 format_list.push(*current);
958 current = current.add(1);
959 count += 1;
960 }
961 if count >= MAX_FORMATS {
962 warn!("Reached maximum format limit");
963 }
964 output_filter.opts.formats = Some(format_list);
965
966 let mut framerates: *const AVRational = null();
968 ret = avcodec_get_supported_config(
969 enc_ctx,
970 null(),
971 AV_CODEC_CONFIG_FRAME_RATE,
972 0,
973 &mut framerates as *mut _ as *mut *const libc::c_void,
974 null_mut(),
975 );
976 if ret < 0 {
977 return Err(OpenOutputError::from(ret).into());
978 }
979 let mut framerate_list = Vec::new();
980 let mut current = framerates;
981 let mut count = 0;
982 const MAX_FRAMERATES: usize = 64;
983 while !current.is_null()
984 && (*current).num != 0
985 && (*current).den != 0
986 && count < MAX_FRAMERATES
987 {
988 framerate_list.push(*current);
989 current = current.add(1);
990 count += 1;
991 }
992 if count >= MAX_FRAMERATES {
993 warn!("Reached maximum framerate limit");
994 }
995 output_filter.opts.framerates = Some(framerate_list);
996
997 if let Some(framerate) = mux.framerate {
998 output_filter.opts.framerate = framerate;
999 }
1000
1001 let mut color_spaces: *const AVColorSpace = null();
1003 ret = avcodec_get_supported_config(
1004 enc_ctx,
1005 null(),
1006 AV_CODEC_CONFIG_COLOR_SPACE,
1007 0,
1008 &mut color_spaces as *mut _ as *mut *const libc::c_void,
1009 null_mut(),
1010 );
1011 if ret < 0 {
1012 return Err(OpenOutputError::from(ret).into());
1013 }
1014 let mut color_space_list = Vec::new();
1015 let mut current = color_spaces;
1016 let mut count = 0;
1017 const MAX_COLOR_SPACES: usize = 128;
1018 while !current.is_null()
1019 && *current != AVCOL_SPC_UNSPECIFIED
1020 && count < MAX_COLOR_SPACES
1021 {
1022 color_space_list.push(*current);
1023 current = current.add(1);
1024 count += 1;
1025 }
1026 if count >= MAX_COLOR_SPACES {
1027 warn!("Reached maximum color space limit");
1028 }
1029 output_filter.opts.color_spaces = Some(color_space_list);
1030
1031 let mut color_ranges: *const AVColorRange = null();
1033 ret = avcodec_get_supported_config(
1034 enc_ctx,
1035 null(),
1036 AV_CODEC_CONFIG_COLOR_RANGE,
1037 0,
1038 &mut color_ranges as *mut _ as *mut *const libc::c_void,
1039 null_mut(),
1040 );
1041 if ret < 0 {
1042 return Err(OpenOutputError::from(ret).into());
1043 }
1044 let mut color_range_list = Vec::new();
1045 let mut current = color_ranges;
1046 let mut count = 0;
1047 const MAX_COLOR_RANGES: usize = 64;
1048 while !current.is_null()
1049 && *current != AVCOL_RANGE_UNSPECIFIED
1050 && count < MAX_COLOR_RANGES
1051 {
1052 color_range_list.push(*current);
1053 current = current.add(1);
1054 count += 1;
1055 }
1056 if count >= MAX_COLOR_RANGES {
1057 warn!("Reached maximum color range limit");
1058 }
1059 output_filter.opts.color_ranges = Some(color_range_list);
1060
1061 let stream = &mux.get_streams()[output_stream_index];
1062 output_filter.opts.vsync_method = stream.vsync_method;
1063 } else {
1064 if let Some(sample_fmt) = &mux.audio_sample_fmt {
1065 output_filter.opts.audio_format = *sample_fmt;
1066 }
1067 let mut audio_formats: *const AVSampleFormat = null();
1069 let mut ret = avcodec_get_supported_config(
1070 enc_ctx,
1071 null(),
1072 AV_CODEC_CONFIG_SAMPLE_FORMAT,
1073 0,
1074 &mut audio_formats as *mut _ as *mut _,
1075 null_mut(),
1076 );
1077 if ret < 0 {
1078 return Err(OpenOutputError::from(ret).into());
1079 }
1080
1081 let mut current = audio_formats;
1082 let mut audio_format_list = Vec::new();
1083 let mut count = 0;
1084 const MAX_AUDIO_FORMATS: usize = 32;
1085 while !current.is_null() && *current != AV_SAMPLE_FMT_NONE && count < MAX_AUDIO_FORMATS
1086 {
1087 audio_format_list.push(*current);
1088 current = current.add(1);
1089 count += 1;
1090 }
1091 if count >= MAX_AUDIO_FORMATS {
1092 warn!("Reached maximum audio format limit");
1093 }
1094 output_filter.opts.audio_formats = Some(audio_format_list);
1095
1096 if let Some(audio_sample_rate) = &mux.audio_sample_rate {
1097 output_filter.opts.sample_rate = *audio_sample_rate;
1098 }
1099 let mut rates: *const i32 = null();
1101 ret = avcodec_get_supported_config(
1102 enc_ctx,
1103 null(),
1104 AV_CODEC_CONFIG_SAMPLE_RATE,
1105 0,
1106 &mut rates as *mut _ as *mut _,
1107 null_mut(),
1108 );
1109 if ret < 0 {
1110 return Err(OpenOutputError::from(ret).into());
1111 }
1112 let mut rate_list = Vec::new();
1113 let mut current = rates;
1114 let mut count = 0;
1115 const MAX_SAMPLE_RATES: usize = 64;
1116 while !current.is_null() && *current != 0 && count < MAX_SAMPLE_RATES {
1117 rate_list.push(*current);
1118 current = current.add(1);
1119 count += 1;
1120 }
1121 if count >= MAX_SAMPLE_RATES {
1122 warn!("Reached maximum sample rate limit");
1123 }
1124 output_filter.opts.sample_rates = Some(rate_list);
1125
1126 if let Some(channels) = &mux.audio_channels {
1127 output_filter.opts.ch_layout.nb_channels = *channels;
1128 }
1129 let mut layouts: *const AVChannelLayout = null();
1131 ret = avcodec_get_supported_config(
1132 enc_ctx,
1133 null(),
1134 AV_CODEC_CONFIG_CHANNEL_LAYOUT,
1135 0,
1136 &mut layouts as *mut _ as *mut _,
1137 null_mut(),
1138 );
1139 if ret < 0 {
1140 return Err(OpenOutputError::from(ret).into());
1141 }
1142 let mut layout_list = Vec::new();
1143 let mut current = layouts;
1144 let mut count = 0;
1145 const MAX_CHANNEL_LAYOUTS: usize = 128;
1146 while !current.is_null()
1147 && (*current).order != AV_CHANNEL_ORDER_UNSPEC
1148 && count < MAX_CHANNEL_LAYOUTS
1149 {
1150 layout_list.push(*current);
1151 current = current.add(1);
1152 count += 1;
1153 }
1154 if count >= MAX_CHANNEL_LAYOUTS {
1155 warn!("Reached maximum channel layout limit");
1156 }
1157 output_filter.opts.ch_layouts = Some(layout_list);
1158
1159 if output_filter.opts.ch_layout.nb_channels > 0 {
1162 let layout_requested = output_filter.opts.ch_layout;
1163 set_channel_layout(
1164 &mut output_filter.opts.ch_layout,
1165 &output_filter.opts.ch_layouts,
1166 &layout_requested,
1167 )?;
1168 }
1169 }
1170 };
1171 Ok(())
1172}
1173
1174fn map_auto_streams(
1175 mux_index: usize,
1176 mux: &mut Muxer,
1177 demuxs: &mut Vec<Demuxer>,
1178 filter_graphs: &mut Vec<FilterGraph>,
1179 auto_disable: i32,
1180) -> Result<()> {
1181 unsafe {
1182 let oformat = (*mux.out_fmt_ctx).oformat;
1183 map_auto_stream(
1184 mux_index,
1185 mux,
1186 demuxs,
1187 oformat,
1188 AVMEDIA_TYPE_VIDEO,
1189 filter_graphs,
1190 auto_disable,
1191 )?;
1192 map_auto_stream(
1193 mux_index,
1194 mux,
1195 demuxs,
1196 oformat,
1197 AVMEDIA_TYPE_AUDIO,
1198 filter_graphs,
1199 auto_disable,
1200 )?;
1201 map_auto_subtitle(mux, demuxs, oformat, auto_disable)?;
1202 map_auto_data(mux, demuxs, oformat, auto_disable)?;
1203 }
1204 Ok(())
1205}
1206
1207#[cfg(feature = "docs-rs")]
1208unsafe fn map_auto_subtitle(
1209 mux: &mut Muxer,
1210 demuxs: &mut Vec<Demuxer>,
1211 oformat: *const AVOutputFormat,
1212 auto_disable: i32,
1213) -> Result<()> {
1214 Ok(())
1215}
1216
1217#[cfg(not(feature = "docs-rs"))]
1218unsafe fn map_auto_subtitle(
1219 mux: &mut Muxer,
1220 demuxs: &mut Vec<Demuxer>,
1221 oformat: *const AVOutputFormat,
1222 auto_disable: i32,
1223) -> Result<()> {
1224 if auto_disable & (1 << AVMEDIA_TYPE_SUBTITLE as i32) != 0 {
1225 return Ok(());
1226 }
1227
1228 let output_codec = avcodec_find_encoder((*oformat).subtitle_codec);
1229 if output_codec.is_null() {
1230 return Ok(());
1231 }
1232 let output_descriptor = avcodec_descriptor_get((*output_codec).id);
1233
1234 for (demux_idx, demux) in demuxs.iter_mut().enumerate() {
1235 let option = demux
1236 .get_streams()
1237 .iter()
1238 .enumerate()
1239 .find_map(|(index, input_stream)| {
1240 if input_stream.codec_type == AVMEDIA_TYPE_SUBTITLE {
1241 Some(index)
1242 } else {
1243 None
1244 }
1245 });
1246
1247 if option.is_none() {
1248 continue;
1249 }
1250
1251 let stream_index = option.unwrap();
1252
1253 let input_descriptor =
1254 avcodec_descriptor_get((*demux.get_stream(stream_index).codec_parameters).codec_id);
1255 let mut input_props = 0;
1256 if !input_descriptor.is_null() {
1257 input_props =
1258 (*input_descriptor).props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB);
1259 }
1260 let mut output_props = 0;
1261 if !output_descriptor.is_null() {
1262 output_props =
1263 (*output_descriptor).props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB);
1264 }
1265
1266 if input_props & output_props != 0 ||
1267 !input_descriptor.is_null() && !output_descriptor.is_null() &&
1269 ((*input_descriptor).props == 0 || (*output_descriptor).props == 0)
1270 {
1271 let option = choose_encoder(mux, AVMEDIA_TYPE_SUBTITLE)?;
1272
1273 if let Some((_codec_id, enc)) = option {
1274 let (frame_sender, output_stream_index) =
1275 mux.add_enc_stream(AVMEDIA_TYPE_SUBTITLE, enc, demux.node.clone())?;
1276 demux.get_stream_mut(stream_index).add_dst(frame_sender);
1277 demux.connect_stream(stream_index);
1278 mux.register_stream_source(output_stream_index, demux_idx, stream_index, true);
1279 let input_stream = demux.get_stream(stream_index);
1280 unsafe {
1281 rescale_duration(
1282 input_stream.duration,
1283 input_stream.time_base,
1284 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
1285 );
1286 }
1287 } else {
1288 error!("Error selecting an encoder(subtitle)");
1289 return Err(OpenOutputError::from(AVERROR_ENCODER_NOT_FOUND).into());
1290 }
1291 }
1292 break;
1293 }
1294
1295 Ok(())
1296}
1297
1298#[cfg(feature = "docs-rs")]
1299unsafe fn map_auto_data(
1300 mux: &mut Muxer,
1301 demuxs: &mut Vec<Demuxer>,
1302 oformat: *const AVOutputFormat,
1303 auto_disable: i32,
1304) -> Result<()> {
1305 Ok(())
1306}
1307
1308#[cfg(not(feature = "docs-rs"))]
1309unsafe fn map_auto_data(
1310 mux: &mut Muxer,
1311 demuxs: &mut Vec<Demuxer>,
1312 oformat: *const AVOutputFormat,
1313 auto_disable: i32,
1314) -> Result<()> {
1315 if auto_disable & (1 << AVMEDIA_TYPE_DATA as i32) != 0 {
1316 return Ok(());
1317 }
1318
1319 let codec_id = av_guess_codec(
1321 oformat,
1322 null(),
1323 (*mux.out_fmt_ctx).url,
1324 null(),
1325 AVMEDIA_TYPE_DATA,
1326 );
1327
1328 if codec_id == AV_CODEC_ID_NONE {
1329 return Ok(());
1330 }
1331
1332 for (demux_idx, demux) in demuxs.iter_mut().enumerate() {
1333 let option = demux
1334 .get_streams()
1335 .iter()
1336 .enumerate()
1337 .find_map(|(index, input_stream)| {
1338 if input_stream.codec_type == AVMEDIA_TYPE_DATA
1339 && (*input_stream.codec_parameters).codec_id == codec_id
1340 {
1341 Some(index)
1342 } else {
1343 None
1344 }
1345 });
1346
1347 if option.is_none() {
1348 continue;
1349 }
1350
1351 let stream_index = option.unwrap();
1352 let option = choose_encoder(mux, AVMEDIA_TYPE_DATA)?;
1353
1354 if let Some((_codec_id, enc)) = option {
1355 let (frame_sender, output_stream_index) =
1356 mux.add_enc_stream(AVMEDIA_TYPE_DATA, enc, demux.node.clone())?;
1357 demux.get_stream_mut(stream_index).add_dst(frame_sender);
1358 demux.connect_stream(stream_index);
1359 mux.register_stream_source(output_stream_index, demux_idx, stream_index, true);
1360 let input_stream = demux.get_stream(stream_index);
1361 unsafe {
1362 rescale_duration(
1363 input_stream.duration,
1364 input_stream.time_base,
1365 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
1366 );
1367 }
1368 } else {
1369 error!("Error selecting an encoder(data)");
1370 return Err(OpenOutputError::from(AVERROR_ENCODER_NOT_FOUND).into());
1371 }
1372
1373 break;
1374 }
1375
1376 Ok(())
1377}
1378
1379#[cfg(feature = "docs-rs")]
1380unsafe fn map_auto_stream(
1381 mux_index: usize,
1382 mux: &mut Muxer,
1383 demuxs: &mut Vec<Demuxer>,
1384 oformat: *const AVOutputFormat,
1385 media_type: AVMediaType,
1386 filter_graphs: &mut Vec<FilterGraph>,
1387 auto_disable: i32,
1388) -> Result<()> {
1389 Ok(())
1390}
1391
1392#[cfg(not(feature = "docs-rs"))]
1393unsafe fn map_auto_stream(
1394 mux_index: usize,
1395 mux: &mut Muxer,
1396 demuxs: &mut Vec<Demuxer>,
1397 oformat: *const AVOutputFormat,
1398 media_type: AVMediaType,
1399 filter_graphs: &mut Vec<FilterGraph>,
1400 auto_disable: i32,
1401) -> Result<()> {
1402 if auto_disable & (1 << media_type as i32) != 0 {
1403 return Ok(());
1404 }
1405 if (media_type == AVMEDIA_TYPE_VIDEO
1406 || media_type == AVMEDIA_TYPE_AUDIO
1407 || media_type == AVMEDIA_TYPE_DATA)
1408 && av_guess_codec(oformat, null(), (*mux.out_fmt_ctx).url, null(), media_type)
1409 == AV_CODEC_ID_NONE
1410 {
1411 return Ok(());
1412 }
1413
1414 for (demux_idx, demux) in demuxs.iter_mut().enumerate() {
1415 let option = demux
1416 .get_streams()
1417 .iter()
1418 .enumerate()
1419 .find_map(|(index, input_stream)| {
1420 if input_stream.codec_type == media_type {
1421 Some(index)
1422 } else {
1423 None
1424 }
1425 });
1426
1427 if option.is_none() {
1428 continue;
1429 }
1430
1431 let stream_index = option.unwrap();
1432 let input_file_idx = demux_idx;
1433 let option = choose_encoder(mux, media_type)?;
1434
1435 if let Some((codec_id, enc)) = option {
1436 if media_type == AVMEDIA_TYPE_VIDEO || media_type == AVMEDIA_TYPE_AUDIO {
1437 init_simple_filtergraph(
1438 demux,
1439 stream_index,
1440 codec_id,
1441 enc,
1442 mux_index,
1443 mux,
1444 filter_graphs,
1445 input_file_idx,
1446 )?;
1447 } else {
1448 let (frame_sender, output_stream_index) =
1449 mux.add_enc_stream(media_type, enc, demux.node.clone())?;
1450 demux.get_stream_mut(stream_index).add_dst(frame_sender);
1451 demux.connect_stream(stream_index);
1452 mux.register_stream_source(output_stream_index, input_file_idx, stream_index, true);
1453 let input_stream = demux.get_stream(stream_index);
1454 unsafe {
1455 rescale_duration(
1456 input_stream.duration,
1457 input_stream.time_base,
1458 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
1459 );
1460 }
1461 }
1462
1463 return Ok(());
1464 }
1465
1466 let input_stream = demux.get_stream(stream_index);
1468 let input_stream_duration = input_stream.duration;
1469 let input_stream_time_base = input_stream.time_base;
1470
1471 let (packet_sender, _st, output_stream_index) = mux.new_stream(demux.node.clone())?;
1472 demux.add_packet_dst(packet_sender, stream_index, output_stream_index);
1473 mux.register_stream_source(output_stream_index, input_file_idx, stream_index, false);
1474
1475 unsafe {
1476 streamcopy_init(
1477 mux,
1478 *(*demux.in_fmt_ctx).streams.add(stream_index),
1479 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
1480 demux.framerate,
1481 )?;
1482 rescale_duration(
1483 input_stream_duration,
1484 input_stream_time_base,
1485 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
1486 );
1487 mux.stream_ready()
1488 }
1489 }
1490
1491 Ok(())
1492}
1493
1494fn init_simple_filtergraph(
1495 demux: &mut Demuxer,
1496 stream_index: usize,
1497 codec_id: AVCodecID,
1498 enc: *const AVCodec,
1499 mux_index: usize,
1500 mux: &mut Muxer,
1501 filter_graphs: &mut Vec<FilterGraph>,
1502 input_file_idx: usize,
1503) -> Result<()> {
1504 let codec_type = demux.get_stream(stream_index).codec_type;
1505
1506 let filter_desc = if codec_type == AVMEDIA_TYPE_VIDEO {
1507 "null"
1508 } else {
1509 "anull"
1510 };
1511 let mut filter_graph = init_filter_graph(filter_graphs.len(), filter_desc, None)?;
1512
1513 ifilter_bind_ist(&mut filter_graph, 0, stream_index, demux)?;
1517 ofilter_bind_ost(
1518 mux_index,
1519 mux,
1520 &mut filter_graph,
1521 0,
1522 codec_id,
1523 enc,
1524 Some((input_file_idx, stream_index)),
1525 )?;
1526
1527 filter_graphs.push(filter_graph);
1528
1529 Ok(())
1530}
1531
1532unsafe fn rescale_duration(src_duration: i64, src_time_base: AVRational, stream: *mut AVStream) {
1533 (*stream).duration = av_rescale_q(src_duration, src_time_base, (*stream).time_base);
1534}
1535
1536#[cfg(feature = "docs-rs")]
1537fn streamcopy_init(
1538 mux: &mut Muxer,
1539 input_stream: *mut AVStream,
1540 output_stream: *mut AVStream,
1541 input_framerate: AVRational,
1542) -> Result<()> {
1543 Ok(())
1544}
1545
1546#[cfg(not(feature = "docs-rs"))]
1547fn streamcopy_init(
1548 mux: &mut Muxer,
1549 input_stream: *mut AVStream,
1550 output_stream: *mut AVStream,
1551 input_framerate: AVRational,
1552) -> Result<()> {
1553 unsafe {
1554 let codec_ctx = avcodec_alloc_context3(null_mut());
1555 if codec_ctx.is_null() {
1556 return Err(OpenOutputError::OutOfMemory.into());
1557 }
1558 let _codec_context = CodecContext::new(codec_ctx);
1559
1560 let mut ret = avcodec_parameters_to_context(codec_ctx, (*input_stream).codecpar);
1561 if ret < 0 {
1562 error!("Error setting up codec context options.");
1563 return Err(OpenOutputError::from(ret).into());
1564 }
1565
1566 ret = avcodec_parameters_from_context((*output_stream).codecpar, codec_ctx);
1567 if ret < 0 {
1568 error!("Error getting reference codec parameters.");
1569 return Err(OpenOutputError::from(ret).into());
1570 }
1571
1572 let mut codec_tag = (*(*output_stream).codecpar).codec_tag;
1573 if codec_tag == 0 {
1574 let ct = (*(*mux.out_fmt_ctx).oformat).codec_tag;
1575 let mut codec_tag_tmp = 0;
1576 if ct.is_null()
1577 || av_codec_get_id(ct, (*(*output_stream).codecpar).codec_tag)
1578 == (*(*output_stream).codecpar).codec_id
1579 || av_codec_get_tag2(
1580 ct,
1581 (*(*output_stream).codecpar).codec_id,
1582 &mut codec_tag_tmp,
1583 ) == 0
1584 {
1585 codec_tag = (*(*output_stream).codecpar).codec_tag;
1586 }
1587 }
1588 (*(*output_stream).codecpar).codec_tag = codec_tag;
1589
1590 let codec_type = (*(*output_stream).codecpar).codec_type;
1596 let mut fr = AVRational { num: 0, den: 0 };
1597 if codec_type == AVMEDIA_TYPE_VIDEO {
1598 fr = mux.framerate.unwrap_or(AVRational { num: 0, den: 0 });
1599 if fr.num == 0 {
1600 fr = input_framerate;
1601 }
1602 }
1603
1604 if fr.num != 0 {
1605 (*output_stream).avg_frame_rate = fr;
1606 } else {
1607 (*output_stream).avg_frame_rate = (*input_stream).avg_frame_rate;
1608 }
1609
1610 if (*output_stream).time_base.num <= 0 || (*output_stream).time_base.den <= 0 {
1612 if fr.num != 0 {
1613 (*output_stream).time_base = av_inv_q(fr);
1614 } else {
1615 (*output_stream).time_base =
1616 av_add_q((*input_stream).time_base, AVRational { num: 0, den: 1 });
1617 }
1618 }
1619
1620 for i in 0..(*(*input_stream).codecpar).nb_coded_side_data {
1621 let sd_src = (*(*input_stream).codecpar)
1622 .coded_side_data
1623 .offset(i as isize);
1624
1625 let sd_dst = av_packet_side_data_new(
1626 &mut (*(*output_stream).codecpar).coded_side_data,
1627 &mut (*(*output_stream).codecpar).nb_coded_side_data,
1628 (*sd_src).type_,
1629 (*sd_src).size,
1630 0,
1631 );
1632 if sd_dst.is_null() {
1633 return Err(OpenOutputError::OutOfMemory.into());
1634 }
1635 std::ptr::copy_nonoverlapping(
1636 (*sd_src).data as *const u8,
1637 (*sd_dst).data,
1638 (*sd_src).size,
1639 );
1640 }
1641
1642 match (*(*output_stream).codecpar).codec_type {
1643 AVMEDIA_TYPE_AUDIO => {
1644 if ((*(*output_stream).codecpar).block_align == 1
1645 || (*(*output_stream).codecpar).block_align == 1152
1646 || (*(*output_stream).codecpar).block_align == 576)
1647 && (*(*output_stream).codecpar).codec_id == AV_CODEC_ID_MP3
1648 {
1649 (*(*output_stream).codecpar).block_align = 0;
1650 }
1651 if (*(*output_stream).codecpar).codec_id == AV_CODEC_ID_AC3 {
1652 (*(*output_stream).codecpar).block_align = 0;
1653 }
1654 }
1655 AVMEDIA_TYPE_VIDEO => {
1656 let sar = if (*input_stream).sample_aspect_ratio.num != 0 {
1657 (*input_stream).sample_aspect_ratio
1658 } else {
1659 (*(*output_stream).codecpar).sample_aspect_ratio
1660 };
1661 (*output_stream).sample_aspect_ratio = sar;
1662 (*(*output_stream).codecpar).sample_aspect_ratio = sar;
1663 (*output_stream).r_frame_rate = (*input_stream).r_frame_rate;
1664 }
1665 _ => {}
1666 }
1667 };
1668 Ok(())
1669}
1670
1671fn output_bind_by_unlabeled_filter(
1672 index: usize,
1673 mux: &mut Muxer,
1674 filter_graphs: &mut Vec<FilterGraph>,
1675 auto_disable: &mut i32,
1676) -> Result<()> {
1677 let fg_len = filter_graphs.len();
1678
1679 for i in 0..fg_len {
1680 let filter_graph = &mut filter_graphs[i];
1681
1682 for i in 0..filter_graph.outputs.len() {
1683 let option = {
1684 let output_filter = &filter_graph.outputs[i];
1685 if (!output_filter.linklabel.is_empty() && output_filter.linklabel != "out")
1686 || output_filter.has_dst()
1687 {
1688 continue;
1689 }
1690
1691 choose_encoder(mux, output_filter.media_type)?
1692 };
1693
1694 let media_type = filter_graph.outputs[i].media_type;
1695
1696 match option {
1697 None => {
1698 warn!(
1699 "An unexpected media_type {:?} appears in output_filter",
1700 media_type
1701 );
1702 }
1703 Some((codec_id, enc)) => {
1704 *auto_disable |= 1 << media_type as i32;
1705 ofilter_bind_ost(index, mux, filter_graph, i, codec_id, enc, None)?;
1706 }
1707 }
1708 }
1709 }
1710
1711 Ok(())
1712}
1713
1714fn ofilter_bind_ost(
1715 index: usize,
1716 mux: &mut Muxer,
1717 filter_graph: &mut FilterGraph,
1718 output_filter_index: usize,
1719 codec_id: AVCodecID,
1720 enc: *const AVCodec,
1721 stream_source: Option<(usize, usize)>,
1722) -> Result<usize> {
1723 let output_filter = &mut filter_graph.outputs[output_filter_index];
1724 let (frame_sender, output_stream_index) =
1725 mux.add_enc_stream(output_filter.media_type, enc, filter_graph.node.clone())?;
1726 output_filter.set_dst(frame_sender);
1727
1728 if let Some((file_idx, stream_idx)) = stream_source {
1729 mux.register_stream_source(output_stream_index, file_idx, stream_idx, true);
1730 }
1731
1732 configure_output_filter_opts(
1733 index,
1734 mux,
1735 output_filter,
1736 codec_id,
1737 enc,
1738 output_stream_index,
1739 )?;
1740 Ok(output_stream_index)
1741}
1742
1743fn choose_encoder(
1744 mux: &Muxer,
1745 media_type: AVMediaType,
1746) -> Result<Option<(AVCodecID, *const AVCodec)>> {
1747 let media_codec = match media_type {
1748 AVMEDIA_TYPE_VIDEO => mux.video_codec.clone(),
1749 AVMEDIA_TYPE_AUDIO => mux.audio_codec.clone(),
1750 AVMEDIA_TYPE_SUBTITLE => mux.subtitle_codec.clone(),
1751 _ => return Ok(None),
1752 };
1753
1754 match media_codec {
1755 None => {
1756 let url = CString::new(&*mux.url).unwrap();
1757 unsafe {
1758 let codec_id = av_guess_codec(
1759 (*mux.out_fmt_ctx).oformat,
1760 null(),
1761 url.as_ptr(),
1762 null(),
1763 media_type,
1764 );
1765 let enc = avcodec_find_encoder(codec_id);
1766 if enc.is_null() {
1767 let format_name = (*(*mux.out_fmt_ctx).oformat).name;
1768 let format_name = CStr::from_ptr(format_name).to_str();
1769 let codec_name = avcodec_get_name(codec_id);
1770 let codec_name = CStr::from_ptr(codec_name).to_str();
1771 if let (Ok(format_name), Ok(codec_name)) = (format_name, codec_name) {
1772 error!("Automatic encoder selection failed Default encoder for format {format_name} (codec {codec_name}) is probably disabled. Please choose an encoder manually.");
1773 }
1774 return Err(OpenOutputError::from(AVERROR_ENCODER_NOT_FOUND).into());
1775 }
1776
1777 return Ok(Some((codec_id, enc)));
1778 }
1779 }
1780 Some(media_codec) if media_codec != "copy" => unsafe {
1781 let media_codec_cstr = CString::new(media_codec.clone())?;
1782
1783 let mut enc = avcodec_find_encoder_by_name(media_codec_cstr.as_ptr());
1784 let desc = avcodec_descriptor_get_by_name(media_codec_cstr.as_ptr());
1785
1786 if enc.is_null() && !desc.is_null() {
1787 enc = avcodec_find_encoder((*desc).id);
1788 if !enc.is_null() {
1789 let codec_name = (*enc).name;
1790 let codec_name = CStr::from_ptr(codec_name).to_str();
1791 let desc_name = (*desc).name;
1792 let desc_name = CStr::from_ptr(desc_name).to_str();
1793 if let (Ok(codec_name), Ok(desc_name)) = (codec_name, desc_name) {
1794 debug!("Matched encoder '{codec_name}' for codec '{desc_name}'.");
1795 }
1796 }
1797 }
1798
1799 if enc.is_null() {
1800 error!("Unknown encoder '{media_codec}'");
1801 return Err(OpenOutputError::from(AVERROR_ENCODER_NOT_FOUND).into());
1802 }
1803
1804 if (*enc).type_ != media_type {
1805 error!("Invalid encoder type '{media_codec}'");
1806 return Err(OpenOutputError::InvalidArgument.into());
1807 }
1808 let codec_id = (*enc).id;
1809 return Ok(Some((codec_id, enc)));
1810 },
1811 _ => {}
1812 };
1813
1814 Ok(None)
1815}
1816
1817fn check_duplicate_inputs_outputs(inputs: &[Input], outputs: &[Output]) -> Result<()> {
1818 for output in outputs {
1819 if let Some(output_url) = &output.url {
1820 for input in inputs {
1821 if let Some(input_url) = &input.url {
1822 if input_url == output_url {
1823 return Err(FileSameAsInput(input_url.clone()));
1824 }
1825 }
1826 }
1827 }
1828 }
1829 Ok(())
1830}
1831
1832fn open_output_files(outputs: &mut Vec<Output>, copy_ts: bool) -> Result<Vec<Muxer>> {
1833 let mut muxs = Vec::new();
1834
1835 for (i, output) in outputs.iter_mut().enumerate() {
1836 unsafe {
1837 let result = open_output_file(i, output, copy_ts);
1838 if let Err(e) = result {
1839 free_output_av_format_context(muxs);
1840 return Err(e);
1841 }
1842 let mux = result.unwrap();
1843 muxs.push(mux)
1844 }
1845 }
1846 Ok(muxs)
1847}
1848
1849unsafe fn free_output_av_format_context(muxs: Vec<Muxer>) {
1850 for mut mux in muxs {
1851 avformat_close_input(&mut mux.out_fmt_ctx);
1852 }
1853}
1854
1855#[cfg(feature = "docs-rs")]
1856unsafe fn open_output_file(index: usize, output: &mut Output, copy_ts: bool) -> Result<Muxer> {
1857 Err(Error::Bug)
1858}
1859
1860#[cfg(not(feature = "docs-rs"))]
1861unsafe fn open_output_file(index: usize, output: &mut Output, copy_ts: bool) -> Result<Muxer> {
1862 let mut out_fmt_ctx = null_mut();
1863 let format = get_format(&output.format)?;
1864 match &output.url {
1865 None => {
1866 if output.write_callback.is_none() {
1867 error!("input url and write_callback is none.");
1868 return Err(OpenOutputError::InvalidSink.into());
1869 }
1870
1871 let write_callback = output.write_callback.take().unwrap();
1872
1873 let avio_ctx_buffer_size = 1024 * 64;
1874 let mut avio_ctx_buffer = av_malloc(avio_ctx_buffer_size);
1875 if avio_ctx_buffer.is_null() {
1876 return Err(OpenOutputError::OutOfMemory.into());
1877 }
1878
1879 let have_seek_callback = output.seek_callback.is_some();
1880 let input_opaque = Box::new(OutputOpaque {
1881 write: write_callback,
1882 seek: output.seek_callback.take(),
1883 });
1884 let opaque = Box::into_raw(input_opaque) as *mut libc::c_void;
1885
1886 let mut avio_ctx = avio_alloc_context(
1887 avio_ctx_buffer as *mut libc::c_uchar,
1888 avio_ctx_buffer_size as i32,
1889 1,
1890 opaque,
1891 None,
1892 Some(write_packet_wrapper),
1893 if have_seek_callback {
1894 Some(seek_packet_wrapper)
1895 } else {
1896 None
1897 },
1898 );
1899 if avio_ctx.is_null() {
1900 av_freep(&mut avio_ctx_buffer as *mut _ as *mut c_void);
1901 return Err(OpenOutputError::OutOfMemory.into());
1902 }
1903
1904 let ret = avformat_alloc_output_context2(&mut out_fmt_ctx, format, null(), null());
1905 if out_fmt_ctx.is_null() {
1906 warn!("Error initializing the muxer for write_callback");
1907 av_freep(&mut (*avio_ctx).buffer as *mut _ as *mut c_void);
1908 avio_context_free(&mut avio_ctx);
1909 return Err(AllocOutputContextError::from(ret).into());
1910 }
1911
1912 if !have_seek_callback && output_requires_seek(out_fmt_ctx) {
1913 av_freep(&mut (*avio_ctx).buffer as *mut _ as *mut c_void);
1914 avio_context_free(&mut avio_ctx);
1915 avformat_free_context(out_fmt_ctx);
1916 warn!("The output format supports seeking, but no seek callback is provided. This may cause issues.");
1917 return Err(OpenOutputError::SeekFunctionMissing.into());
1918 }
1919
1920 (*out_fmt_ctx).pb = avio_ctx;
1921 (*out_fmt_ctx).flags |= AVFMT_FLAG_CUSTOM_IO;
1922 }
1923 Some(url) => {
1924 let url_cstr = if url == "-" {
1925 CString::new("pipe:")?
1926 } else {
1927 CString::new(url.as_str())?
1928 };
1929 let ret =
1930 avformat_alloc_output_context2(&mut out_fmt_ctx, format, null(), url_cstr.as_ptr());
1931 if out_fmt_ctx.is_null() {
1932 warn!("Error initializing the muxer for {url}");
1933 return Err(AllocOutputContextError::from(ret).into());
1934 }
1935
1936 let output_format = (*out_fmt_ctx).oformat;
1937 if (*output_format).flags & AVFMT_NOFILE == 0 {
1938 let ret = avio_open(&mut (*out_fmt_ctx).pb, url_cstr.as_ptr(), AVIO_FLAG_WRITE);
1939 if ret < 0 {
1940 warn!("Error opening output {url}");
1941 return Err(OpenOutputError::from(ret).into());
1942 }
1943 }
1944 }
1945 }
1946
1947 let recording_time_us = match output.stop_time_us {
1948 None => output.recording_time_us,
1949 Some(stop_time_us) => {
1950 let start_time_us = output.start_time_us.unwrap_or(0);
1951 if stop_time_us <= start_time_us {
1952 error!("stop_time_us value smaller than start_time_us; aborting.");
1953 return Err(OpenOutputError::InvalidArgument.into());
1954 } else {
1955 Some(stop_time_us - start_time_us)
1956 }
1957 }
1958 };
1959
1960 let url = output
1961 .url
1962 .clone()
1963 .unwrap_or_else(|| format!("write_callback[{index}]"));
1964
1965 let video_codec_opts = convert_options(output.video_codec_opts.clone())?;
1966 let audio_codec_opts = convert_options(output.audio_codec_opts.clone())?;
1967 let subtitle_codec_opts = convert_options(output.subtitle_codec_opts.clone())?;
1968 let format_opts = convert_options(output.format_opts.clone())?;
1969
1970 let mux = Muxer::new(
1971 url,
1972 output.url.is_none(),
1973 out_fmt_ctx,
1974 output.frame_pipelines.take(),
1975 output.stream_map_specs.clone(),
1976 output.stream_maps.clone(),
1977 output.video_codec.clone(),
1978 output.audio_codec.clone(),
1979 output.subtitle_codec.clone(),
1980 output.start_time_us,
1981 recording_time_us,
1982 output.framerate,
1983 output.vsync_method,
1984 output.bits_per_raw_sample,
1985 output.audio_sample_rate,
1986 output.audio_channels,
1987 output.audio_sample_fmt,
1988 output.video_qscale,
1989 output.audio_qscale,
1990 output.max_video_frames,
1991 output.max_audio_frames,
1992 output.max_subtitle_frames,
1993 video_codec_opts,
1994 audio_codec_opts,
1995 subtitle_codec_opts,
1996 format_opts,
1997 copy_ts,
1998 output.global_metadata.clone(),
1999 output.stream_metadata.clone(),
2000 output.chapter_metadata.clone(),
2001 output.program_metadata.clone(),
2002 output.metadata_map.clone(),
2003 output.auto_copy_metadata,
2004 );
2005
2006 Ok(mux)
2007}
2008
2009fn get_format(format_option: &Option<String>) -> Result<*const AVOutputFormat> {
2010 match format_option {
2011 None => Ok(null()),
2012 Some(format_str) => unsafe {
2013 let mut format_cstr = CString::new(format_str.to_string())?;
2014 let mut format = av_guess_format(format_cstr.as_ptr(), null(), null());
2015 if format.is_null() {
2016 format_cstr = CString::new(format!("tmp.{format_str}"))?;
2017 format = av_guess_format(null(), format_cstr.as_ptr(), null());
2018 }
2019 if format.is_null() {
2020 return Err(OpenOutputError::FormatUnsupported(format_str.to_string()).into());
2021 }
2022 Ok(format)
2023 },
2024 }
2025}
2026
2027unsafe fn output_requires_seek(fmt_ctx: *mut AVFormatContext) -> bool {
2028 if fmt_ctx.is_null() {
2029 return false;
2030 }
2031
2032 let mut format_name = "unknown".to_string();
2033
2034 if !(*fmt_ctx).oformat.is_null() {
2035 let oformat = (*fmt_ctx).oformat;
2036 format_name = CStr::from_ptr((*oformat).name)
2037 .to_string_lossy()
2038 .into_owned();
2039 let flags = (*oformat).flags;
2040 let no_file = flags & AVFMT_NOFILE != 0;
2041 let global_header = flags & AVFMT_GLOBALHEADER != 0;
2042
2043 log::debug!(
2044 "Output format '{format_name}' - No file: {}, Global header: {}",
2045 if no_file { "True" } else { "False" },
2046 if global_header { "True" } else { "False" }
2047 );
2048
2049 let format_names: Vec<&str> = format_name.split(',').collect();
2051 if format_names
2052 .iter()
2053 .any(|&f| matches!(f, "mp4" | "mov" | "mkv" | "avi" | "flac" | "ogg" | "webm"))
2054 {
2055 log::debug!("Output format '{format_name}' typically requires seeking.");
2056 return true;
2057 }
2058
2059 if format_names.iter().any(|&f| {
2061 matches!(
2062 f,
2063 "mpegts" | "hls" | "m3u8" | "udp" | "rtp" | "rtp_mpegts" | "http" | "srt"
2064 )
2065 }) {
2066 log::debug!("Output format '{format_name}' does not typically require seeking.");
2067 return false;
2068 }
2069
2070 if format_name == "flv" {
2072 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'.");
2073 return false;
2074 }
2075
2076 if no_file {
2078 log::debug!(
2079 "Output format '{format_name}' uses AVFMT_NOFILE. Seeking is likely unnecessary."
2080 );
2081 return false;
2082 }
2083
2084 if global_header {
2086 log::debug!(
2087 "Output format '{format_name}' uses AVFMT_GLOBALHEADER. Seeking may be required."
2088 );
2089 return true;
2090 }
2091 } else {
2092 log::debug!("Output format is null. Cannot determine if seeking is required.");
2093 }
2094
2095 log::debug!("Output format '{format_name}' does not match any known rules. Assuming seeking is not required.");
2097 false
2098}
2099
2100struct InputOpaque {
2101 read: Box<dyn FnMut(&mut [u8]) -> i32>,
2102 seek: Option<Box<dyn FnMut(i64, i32) -> i64>>,
2103}
2104
2105#[allow(dead_code)]
2106struct OutputOpaque {
2107 write: Box<dyn FnMut(&[u8]) -> i32>,
2108 seek: Option<Box<dyn FnMut(i64, i32) -> i64>>,
2109}
2110
2111unsafe extern "C" fn write_packet_wrapper(
2112 opaque: *mut libc::c_void,
2113 buf: *const u8,
2114 buf_size: libc::c_int,
2115) -> libc::c_int {
2116 if buf.is_null() {
2117 return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO);
2118 }
2119 let closure = &mut *(opaque as *mut Box<dyn FnMut(&[u8]) -> i32>);
2120
2121 let slice = std::slice::from_raw_parts(buf, buf_size as usize);
2122
2123 (*closure)(slice)
2124}
2125
2126unsafe extern "C" fn read_packet_wrapper(
2127 opaque: *mut libc::c_void,
2128 buf: *mut u8,
2129 buf_size: libc::c_int,
2130) -> libc::c_int {
2131 if buf.is_null() {
2132 return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO);
2133 }
2134
2135 let context = &mut *(opaque as *mut InputOpaque);
2136
2137 let slice = std::slice::from_raw_parts_mut(buf, buf_size as usize);
2138
2139 (context.read)(slice)
2140}
2141
2142unsafe extern "C" fn seek_packet_wrapper(
2143 opaque: *mut libc::c_void,
2144 offset: i64,
2145 whence: libc::c_int,
2146) -> i64 {
2147 let context = &mut *(opaque as *mut InputOpaque);
2148
2149 if let Some(seek_func) = &mut context.seek {
2150 (*seek_func)(offset, whence)
2151 } else {
2152 ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE) as i64
2153 }
2154}
2155
2156fn fg_bind_inputs(filter_graphs: &mut Vec<FilterGraph>, demuxs: &mut Vec<Demuxer>) -> Result<()> {
2157 if filter_graphs.is_empty() {
2158 return Ok(());
2159 }
2160 bind_fg_inputs_by_fg(filter_graphs)?;
2161
2162 for filter_graph in filter_graphs.iter_mut() {
2163 for i in 0..filter_graph.inputs.len() {
2164 fg_complex_bind_input(filter_graph, i, demuxs)?;
2165 }
2166 }
2167
2168 Ok(())
2169}
2170
2171struct FilterLabel {
2172 linklabel: String,
2173 media_type: AVMediaType,
2174}
2175
2176fn bind_fg_inputs_by_fg(filter_graphs: &mut Vec<FilterGraph>) -> Result<()> {
2177 let fg_labels = filter_graphs
2178 .iter()
2179 .map(|filter_graph| {
2180 let inputs = filter_graph
2181 .inputs
2182 .iter()
2183 .map(|input| FilterLabel {
2184 linklabel: input.linklabel.clone(),
2185 media_type: input.media_type,
2186 })
2187 .collect::<Vec<_>>();
2188 let outputs = filter_graph
2189 .outputs
2190 .iter()
2191 .map(|output| FilterLabel {
2192 linklabel: output.linklabel.clone(),
2193 media_type: output.media_type,
2194 })
2195 .collect::<Vec<_>>();
2196 (inputs, outputs)
2197 })
2198 .collect::<Vec<_>>();
2199
2200 for (i, (inputs, _outputs)) in fg_labels.iter().enumerate() {
2201 for input_filter_label in inputs.iter() {
2202 if input_filter_label.linklabel.is_empty() {
2203 continue;
2204 }
2205
2206 'outer: for (j, (_inputs, outputs)) in fg_labels.iter().enumerate() {
2207 if i == j {
2208 continue;
2209 }
2210
2211 for (output_idx, output_filter_label) in outputs.iter().enumerate() {
2212 if output_filter_label.linklabel != input_filter_label.linklabel {
2213 continue;
2214 }
2215 if output_filter_label.media_type != input_filter_label.media_type {
2216 warn!(
2217 "Tried to connect {:?} output to {:?} input",
2218 output_filter_label.media_type, input_filter_label.media_type
2219 );
2220 return Err(FilterGraphParseError::InvalidArgument.into());
2221 }
2222
2223 {
2224 let filter_graph = &filter_graphs[j];
2225 let output_filter = &filter_graph.outputs[output_idx];
2226 if output_filter.has_dst() {
2227 continue;
2228 }
2229 }
2230
2231 let (sender, finished_flag_list) = {
2232 let filter_graph = &mut filter_graphs[i];
2233 filter_graph.get_src_sender()
2234 };
2235
2236 {
2237 let filter_graph = &mut filter_graphs[j];
2238 filter_graph.outputs[output_idx].set_dst(sender);
2239 filter_graph.outputs[output_idx].fg_input_index = i;
2240 filter_graph.outputs[output_idx].finished_flag_list = finished_flag_list;
2241 }
2242
2243 break 'outer;
2244 }
2245 }
2246 }
2247 }
2248 Ok(())
2249}
2250
2251fn fg_complex_bind_input(
2252 filter_graph: &mut FilterGraph,
2253 input_filter_index: usize,
2254 demuxs: &mut Vec<Demuxer>,
2255) -> Result<()> {
2256 let graph_desc = &filter_graph.graph_desc;
2257 let input_filter = &mut filter_graph.inputs[input_filter_index];
2258 let (demux_idx, stream_idx) = if !input_filter.linklabel.is_empty()
2259 && input_filter.linklabel != "in"
2260 {
2261 let (demux_idx, stream_idx) = fg_find_input_idx_by_linklabel(
2262 &input_filter.linklabel,
2263 input_filter.media_type,
2264 demuxs,
2265 graph_desc,
2266 )?;
2267
2268 info!(
2269 "Binding filter input with label '{}' to input stream {stream_idx}:{demux_idx}",
2270 input_filter.linklabel
2271 );
2272 (demux_idx, stream_idx)
2273 } else {
2274 let mut demux_idx = -1i32;
2275 let mut stream_idx = 0;
2276 for (d_idx, demux) in demuxs.iter().enumerate() {
2277 for (st_idx, intput_stream) in demux.get_streams().iter().enumerate() {
2278 if intput_stream.is_used() {
2279 continue;
2280 }
2281 if intput_stream.codec_type == input_filter.media_type {
2282 demux_idx = d_idx as i32;
2283 stream_idx = st_idx;
2284 break;
2285 }
2286 }
2287 if demux_idx >= 0 {
2288 break;
2289 }
2290 }
2291
2292 if demux_idx < 0 {
2293 warn!(
2294 "Cannot find a matching stream for unlabeled input pad {}",
2295 input_filter.name
2296 );
2297 return Err(FilterGraphParseError::InvalidArgument.into());
2298 }
2299
2300 debug!("FilterGraph binding unlabeled input {input_filter_index} to input stream {stream_idx}:{demux_idx}");
2301
2302 (demux_idx as usize, stream_idx)
2303 };
2304
2305 let demux = &mut demuxs[demux_idx];
2306
2307 ifilter_bind_ist(filter_graph, input_filter_index, stream_idx, demux)
2308}
2309
2310#[cfg(feature = "docs-rs")]
2311fn ifilter_bind_ist(
2312 filter_graph: &mut FilterGraph,
2313 input_index: usize,
2314 stream_idx: usize,
2315 demux: &mut Demuxer,
2316) -> Result<()> {
2317 Ok(())
2318}
2319
2320#[cfg(not(feature = "docs-rs"))]
2321fn ifilter_bind_ist(
2322 filter_graph: &mut FilterGraph,
2323 input_index: usize,
2324 stream_idx: usize,
2325 demux: &mut Demuxer,
2326) -> Result<()> {
2327 unsafe {
2328 let input_filter = &mut filter_graph.inputs[input_index];
2329 let ist = *(*demux.in_fmt_ctx).streams.add(stream_idx);
2330 let par = (*ist).codecpar;
2331 if (*par).codec_type == AVMEDIA_TYPE_VIDEO {
2332 let framerate = av_guess_frame_rate(demux.in_fmt_ctx, ist, null_mut());
2333 input_filter.opts.framerate = framerate;
2334 } else if (*par).codec_type == AVMEDIA_TYPE_SUBTITLE {
2335 input_filter.opts.sub2video_width = (*par).width;
2336 input_filter.opts.sub2video_height = (*par).height;
2337
2338 if input_filter.opts.sub2video_width <= 0 || input_filter.opts.sub2video_height <= 0 {
2339 let nb_streams = (*demux.in_fmt_ctx).nb_streams;
2340 for j in 0..nb_streams {
2341 let par1 = (**(*demux.in_fmt_ctx).streams.add(j as usize)).codecpar;
2342 if (*par1).codec_type == AVMEDIA_TYPE_VIDEO {
2343 input_filter.opts.sub2video_width =
2344 std::cmp::max(input_filter.opts.sub2video_width, (*par1).width);
2345 input_filter.opts.sub2video_height =
2346 std::cmp::max(input_filter.opts.sub2video_height, (*par1).height);
2347 }
2348 }
2349 }
2350
2351 if input_filter.opts.sub2video_width <= 0 || input_filter.opts.sub2video_height <= 0 {
2352 input_filter.opts.sub2video_width =
2353 std::cmp::max(input_filter.opts.sub2video_width, 720);
2354 input_filter.opts.sub2video_height =
2355 std::cmp::max(input_filter.opts.sub2video_height, 576);
2356 }
2357
2358 demux.get_stream_mut(stream_idx).have_sub2video = true;
2359 }
2360
2361 let dec_ctx = {
2362 let input_stream = demux.get_stream_mut(stream_idx);
2363 avcodec_alloc_context3(input_stream.codec.as_ptr())
2364 };
2365 if dec_ctx.is_null() {
2366 return Err(FilterGraphParseError::OutOfMemory.into());
2367 }
2368 let _codec_ctx = CodecContext::new(dec_ctx);
2369
2370 let fallback = input_filter.opts.fallback.as_mut_ptr();
2371 if (*dec_ctx).codec_type == AVMEDIA_TYPE_AUDIO {
2372 (*fallback).format = (*dec_ctx).sample_fmt as i32;
2373 (*fallback).sample_rate = (*dec_ctx).sample_rate;
2374
2375 let ret = av_channel_layout_copy(&mut (*fallback).ch_layout, &(*dec_ctx).ch_layout);
2376 if ret < 0 {
2377 return Err(FilterGraphParseError::from(ret).into());
2378 }
2379 } else if (*dec_ctx).codec_type == AVMEDIA_TYPE_VIDEO {
2380 (*fallback).format = (*dec_ctx).pix_fmt as i32;
2381 (*fallback).width = (*dec_ctx).width;
2382 (*fallback).height = (*dec_ctx).height;
2383 (*fallback).sample_aspect_ratio = (*dec_ctx).sample_aspect_ratio;
2384 (*fallback).colorspace = (*dec_ctx).colorspace;
2385 (*fallback).color_range = (*dec_ctx).color_range;
2386 }
2387 (*fallback).time_base = (*dec_ctx).pkt_timebase;
2388
2389 if demux.autorotate {
2392 input_filter.opts.flags |= IFILTER_FLAG_AUTOROTATE;
2393 }
2394
2395 let tsoffset = if demux.copy_ts {
2396 let mut tsoffset = if demux.start_time_us.is_some() {
2397 demux.start_time_us.unwrap()
2398 } else {
2399 0
2400 };
2401 if (*demux.in_fmt_ctx).start_time != ffmpeg_sys_next::AV_NOPTS_VALUE {
2402 tsoffset += (*demux.in_fmt_ctx).start_time
2403 }
2404 tsoffset
2405 } else {
2406 0
2407 };
2408 if demux.start_time_us.is_some() {
2409 input_filter.opts.trim_start_us = Some(tsoffset);
2410 }
2411 input_filter.opts.trim_end_us = demux.recording_time_us;
2412
2413 let (sender, finished_flag_list) = filter_graph.get_src_sender();
2414 {
2415 let input_stream = demux.get_stream_mut(stream_idx);
2416 input_stream.add_fg_dst(sender, input_index, finished_flag_list);
2417 };
2418
2419 let node = Arc::make_mut(&mut filter_graph.node);
2420 let SchNode::Filter { inputs, .. } = node else {
2421 unreachable!()
2422 };
2423 inputs.insert(input_index, demux.node.clone());
2424
2425 demux.connect_stream(stream_idx);
2426 Ok(())
2427 }
2428}
2429
2430fn fg_find_input_idx_by_linklabel(
2434 linklabel: &str,
2435 filter_media_type: AVMediaType,
2436 demuxs: &mut Vec<Demuxer>,
2437 desc: &str,
2438) -> Result<(usize, usize)> {
2439 let new_linklabel = if linklabel.starts_with("[") && linklabel.ends_with("]") {
2441 if linklabel.len() <= 2 {
2442 warn!("Filter linklabel is empty");
2443 return Err(InvalidFilterSpecifier(desc.to_string()).into());
2444 } else {
2445 &linklabel[1..linklabel.len() - 1]
2446 }
2447 } else {
2448 linklabel
2449 };
2450
2451 let (file_idx, remainder) = strtol(new_linklabel).map_err(|_| {
2453 FilterGraphParseError::InvalidArgument
2454 })?;
2455
2456 if file_idx < 0 || file_idx as usize >= demuxs.len() {
2457 return Err(InvalidFileIndexInFg(file_idx as usize, desc.to_string()).into());
2458 }
2459 let file_idx = file_idx as usize;
2460
2461 let spec_str = if remainder.is_empty() {
2463 ""
2465 } else if remainder.starts_with(':') {
2466 &remainder[1..]
2467 } else {
2468 remainder
2469 };
2470
2471 let stream_spec = if spec_str.is_empty() {
2472 let mut spec = StreamSpecifier::default();
2474 spec.media_type = Some(filter_media_type);
2475 spec
2476 } else {
2477 StreamSpecifier::parse(spec_str).map_err(|e| {
2479 warn!("Invalid stream specifier in filter linklabel '{}': {}", linklabel, e);
2480 FilterGraphParseError::InvalidArgument
2481 })?
2482 };
2483
2484 let demux = &demuxs[file_idx];
2486 unsafe {
2487 let fmt_ctx = demux.in_fmt_ctx;
2488
2489 for (idx, _) in demux.get_streams().iter().enumerate() {
2490 let avstream = *(*fmt_ctx).streams.add(idx);
2491
2492 if stream_spec.matches(fmt_ctx, avstream) {
2493 let codec_type = (*avstream).codecpar.as_ref().unwrap().codec_type;
2495 if codec_type == filter_media_type {
2496 return Ok((file_idx, idx));
2497 }
2498 }
2499 }
2500 }
2501
2502 warn!(
2504 "Stream specifier '{}' in filtergraph description {} matches no streams.",
2505 remainder, desc
2506 );
2507 Err(FilterGraphParseError::InvalidArgument.into())
2508}
2509
2510fn strtol(input: &str) -> Result<(i64, &str)> {
2514 let mut chars = input.chars().peekable();
2515 let mut negative = false;
2516
2517 if let Some(&ch) = chars.peek() {
2518 if ch == '-' {
2519 negative = true;
2520 chars.next();
2521 } else if !ch.is_ascii_digit() {
2522 return Err(ParseInteger);
2523 }
2524 }
2525
2526 let number_start = input.len() - chars.clone().collect::<String>().len();
2527
2528 let number_str: String = chars.by_ref().take_while(|ch| ch.is_ascii_digit()).collect();
2529
2530 if number_str.is_empty() {
2531 return Err(ParseInteger);
2532 }
2533
2534 let number: i64 = number_str.parse().map_err(|_| ParseInteger)?;
2535
2536 let remainder_index = number_start + number_str.len();
2537 let remainder = &input[remainder_index..];
2538
2539 if negative {
2540 Ok((-number, remainder))
2541 } else {
2542 Ok((number, remainder))
2543 }
2544}
2545
2546fn init_filter_graphs(filter_complexs: Vec<FilterComplex>) -> Result<Vec<FilterGraph>> {
2547 let mut filter_graphs = Vec::with_capacity(filter_complexs.len());
2548 for (i, filter) in filter_complexs.iter().enumerate() {
2549 let filter_graph = init_filter_graph(i, &filter.filter_descs, filter.hw_device.clone())?;
2550 filter_graphs.push(filter_graph);
2551 }
2552 Ok(filter_graphs)
2553}
2554
2555#[cfg(feature = "docs-rs")]
2556fn init_filter_graph(
2557 fg_index: usize,
2558 filter_desc: &str,
2559 hw_device: Option<String>,
2560) -> Result<FilterGraph> {
2561 Err(Error::Bug)
2562}
2563
2564#[cfg(not(feature = "docs-rs"))]
2565fn init_filter_graph(
2566 fg_index: usize,
2567 filter_desc: &str,
2568 hw_device: Option<String>,
2569) -> Result<FilterGraph> {
2570 let desc_cstr = CString::new(filter_desc)?;
2571
2572 unsafe {
2573 let mut graph = avfilter_graph_alloc();
2576 (*graph).nb_threads = 1;
2577
2578 let mut seg = null_mut();
2579 let mut ret = avfilter_graph_segment_parse(graph, desc_cstr.as_ptr(), 0, &mut seg);
2580 if ret < 0 {
2581 avfilter_graph_free(&mut graph);
2582 return Err(FilterGraphParseError::from(ret).into());
2583 }
2584
2585 ret = avfilter_graph_segment_create_filters(seg, 0);
2586 if ret < 0 {
2587 avfilter_graph_free(&mut graph);
2588 avfilter_graph_segment_free(&mut seg);
2589 return Err(FilterGraphParseError::from(ret).into());
2590 }
2591
2592 #[cfg(not(feature = "docs-rs"))]
2593 {
2594 ret = graph_opts_apply(seg);
2595 }
2596 if ret < 0 {
2597 avfilter_graph_segment_free(&mut seg);
2598 avfilter_graph_free(&mut graph);
2599 return Err(FilterGraphParseError::from(ret).into());
2600 }
2601
2602 let mut inputs = null_mut();
2603 let mut outputs = null_mut();
2604 ret = avfilter_graph_segment_apply(seg, 0, &mut inputs, &mut outputs);
2605 avfilter_graph_segment_free(&mut seg);
2606
2607 if ret < 0 {
2608 avfilter_inout_free(&mut inputs);
2609 avfilter_inout_free(&mut outputs);
2610 avfilter_graph_free(&mut graph);
2611 return Err(FilterGraphParseError::from(ret).into());
2612 }
2613
2614 let input_filters = inouts_to_input_filters(fg_index, inputs)?;
2615 let output_filters = inouts_to_output_filters(outputs)?;
2616
2617 if output_filters.is_empty() {
2618 avfilter_inout_free(&mut inputs);
2619 avfilter_inout_free(&mut outputs);
2620 avfilter_graph_free(&mut graph);
2621 return Err(FilterZeroOutputs);
2622 }
2623
2624 let filter_graph = FilterGraph::new(
2625 filter_desc.to_string(),
2626 hw_device,
2627 input_filters,
2628 output_filters,
2629 );
2630
2631 avfilter_inout_free(&mut inputs);
2632 avfilter_inout_free(&mut outputs);
2633 avfilter_graph_free(&mut graph);
2634
2635 Ok(filter_graph)
2636 }
2637}
2638
2639unsafe fn inouts_to_input_filters(
2640 fg_index: usize,
2641 inouts: *mut AVFilterInOut,
2642) -> Result<Vec<InputFilter>> {
2643 let mut cur = inouts;
2644 let mut filterinouts = Vec::new();
2645 let mut filter_index = 0;
2646 while !cur.is_null() {
2647 let linklabel = if (*cur).name.is_null() {
2648 ""
2649 } else {
2650 let linklabel = CStr::from_ptr((*cur).name);
2651 let result = linklabel.to_str();
2652 if result.is_err() {
2653 return Err(FilterDescUtf8);
2654 }
2655 result.unwrap()
2656 };
2657
2658 let filter_ctx = (*cur).filter_ctx;
2659 let media_type = avfilter_pad_get_type((*filter_ctx).input_pads, (*cur).pad_idx);
2660
2661 let pads = (*filter_ctx).input_pads;
2662 let nb_pads = (*filter_ctx).nb_inputs;
2663
2664 let name = describe_filter_link(cur, filter_ctx, pads, nb_pads)?;
2665
2666 let fallback = frame_alloc()?;
2667
2668 let mut filter = InputFilter::new(linklabel.to_string(), media_type, name, fallback);
2669 filter.opts.name = format!("fg:{fg_index}:{filter_index}");
2670 filterinouts.push(filter);
2671
2672 cur = (*cur).next;
2673 filter_index += 1;
2674 }
2675 Ok(filterinouts)
2676}
2677
2678unsafe fn inouts_to_output_filters(inouts: *mut AVFilterInOut) -> Result<Vec<OutputFilter>> {
2679 let mut cur = inouts;
2680 let mut output_filters = Vec::new();
2681 while !cur.is_null() {
2682 let linklabel = if (*cur).name.is_null() {
2683 ""
2684 } else {
2685 let linklabel = CStr::from_ptr((*cur).name);
2686 let result = linklabel.to_str();
2687 if result.is_err() {
2688 return Err(FilterDescUtf8);
2689 }
2690 result.unwrap()
2691 };
2692
2693 let filter_ctx = (*cur).filter_ctx;
2694 let media_type = avfilter_pad_get_type((*filter_ctx).output_pads, (*cur).pad_idx);
2695
2696 let pads = (*filter_ctx).output_pads;
2697 let nb_pads = (*filter_ctx).nb_outputs;
2698
2699 let name = describe_filter_link(cur, filter_ctx, pads, nb_pads)?;
2700
2701 let filter = OutputFilter::new(linklabel.to_string(), media_type, name);
2702 output_filters.push(filter);
2703
2704 cur = (*cur).next;
2705 }
2706 Ok(output_filters)
2707}
2708
2709unsafe fn describe_filter_link(
2710 cur: *mut AVFilterInOut,
2711 filter_ctx: *mut AVFilterContext,
2712 pads: *mut AVFilterPad,
2713 nb_pads: c_uint,
2714) -> Result<String> {
2715 let filter = (*filter_ctx).filter;
2716 let name = (*filter).name;
2717 let name = CStr::from_ptr(name);
2718 let result = name.to_str();
2719 if result.is_err() {
2720 return Err(FilterNameUtf8);
2721 }
2722 let name = result.unwrap();
2723
2724 let name = if nb_pads > 1 {
2725 name.to_string()
2726 } else {
2727 let pad_name = avfilter_pad_get_name(pads, (*cur).pad_idx);
2728 let pad_name = CStr::from_ptr(pad_name);
2729 let result = pad_name.to_str();
2730 if result.is_err() {
2731 return Err(FilterNameUtf8);
2732 }
2733 let pad_name = result.unwrap();
2734 format!("{name}:{pad_name}")
2735 };
2736 Ok(name)
2737}
2738
2739fn open_input_files(inputs: &mut Vec<Input>, copy_ts: bool) -> Result<Vec<Demuxer>> {
2740 let mut demuxs = Vec::new();
2741 for (i, input) in inputs.iter_mut().enumerate() {
2742 unsafe {
2743 let result = open_input_file(i, input, copy_ts);
2744 if let Err(e) = result {
2745 free_input_av_format_context(demuxs);
2746 return Err(e);
2747 }
2748 let demux = result.unwrap();
2749 demuxs.push(demux)
2750 }
2751 }
2752 Ok(demuxs)
2753}
2754
2755unsafe fn free_input_av_format_context(demuxs: Vec<Demuxer>) {
2756 for mut demux in demuxs {
2757 avformat_close_input(&mut demux.in_fmt_ctx);
2758 }
2759}
2760
2761#[cfg(feature = "docs-rs")]
2762unsafe fn open_input_file(index: usize, input: &mut Input, copy_ts: bool) -> Result<Demuxer> {
2763 Err(Error::Bug)
2764}
2765
2766#[cfg(not(feature = "docs-rs"))]
2767unsafe fn open_input_file(index: usize, input: &mut Input, copy_ts: bool) -> Result<Demuxer> {
2768 let mut in_fmt_ctx = avformat_alloc_context();
2769 if in_fmt_ctx.is_null() {
2770 return Err(OpenInputError::OutOfMemory.into());
2771 }
2772
2773 let recording_time_us = match input.stop_time_us {
2774 None => input.recording_time_us,
2775 Some(stop_time_us) => {
2776 let start_time_us = input.start_time_us.unwrap_or(0);
2777 if stop_time_us <= start_time_us {
2778 error!("stop_time_us value smaller than start_time_us; aborting.");
2779 return Err(OpenOutputError::InvalidArgument.into());
2780 } else {
2781 Some(stop_time_us - start_time_us)
2782 }
2783 }
2784 };
2785
2786 let file_iformat = if let Some(format) = &input.format {
2787 let format_cstr = CString::new(format.clone())?;
2788
2789 let file_iformat = ffmpeg_sys_next::av_find_input_format(format_cstr.as_ptr());
2790 if file_iformat.is_null() {
2791 error!("Unknown input format: '{format}'");
2792 return Err(OpenInputError::InvalidFormat(format.clone()).into());
2793 }
2794 file_iformat
2795 } else {
2796 null()
2797 };
2798
2799 let input_opts = convert_options(input.input_opts.clone())?;
2800 let mut input_opts = hashmap_to_avdictionary(&input_opts);
2801
2802 match &input.url {
2803 None => {
2804 if input.read_callback.is_none() {
2805 error!("input url and read_callback is none.");
2806 return Err(OpenInputError::InvalidSource.into());
2807 }
2808
2809 let avio_ctx_buffer_size = 1024 * 64;
2810 let mut avio_ctx_buffer = av_malloc(avio_ctx_buffer_size);
2811 if avio_ctx_buffer.is_null() {
2812 avformat_close_input(&mut in_fmt_ctx);
2813 return Err(OpenInputError::OutOfMemory.into());
2814 }
2815
2816 let have_seek_callback = input.seek_callback.is_some();
2817 let input_opaque = Box::new(InputOpaque {
2818 read: input.read_callback.take().unwrap(),
2819 seek: input.seek_callback.take(),
2820 });
2821 let opaque = Box::into_raw(input_opaque) as *mut libc::c_void;
2822
2823 let mut avio_ctx = avio_alloc_context(
2824 avio_ctx_buffer as *mut libc::c_uchar,
2825 avio_ctx_buffer_size as i32,
2826 0,
2827 opaque,
2828 Some(read_packet_wrapper),
2829 None,
2830 if have_seek_callback {
2831 Some(seek_packet_wrapper)
2832 } else {
2833 None
2834 },
2835 );
2836 if avio_ctx.is_null() {
2837 av_freep(&mut avio_ctx_buffer as *mut _ as *mut c_void);
2838 avformat_close_input(&mut in_fmt_ctx);
2839 return Err(OpenInputError::OutOfMemory.into());
2840 }
2841
2842 (*in_fmt_ctx).pb = avio_ctx;
2843 (*in_fmt_ctx).flags = AVFMT_FLAG_CUSTOM_IO;
2844
2845 let ret = avformat_open_input(&mut in_fmt_ctx, null(), file_iformat, &mut input_opts);
2846 if ret < 0 {
2847 av_freep(&mut (*avio_ctx).buffer as *mut _ as *mut c_void);
2848 avio_context_free(&mut avio_ctx);
2849 avformat_close_input(&mut in_fmt_ctx);
2850 return Err(OpenInputError::from(ret).into());
2851 }
2852
2853 let ret = avformat_find_stream_info(in_fmt_ctx, null_mut());
2854 if ret < 0 {
2855 av_freep(&mut (*avio_ctx).buffer as *mut _ as *mut c_void);
2856 avio_context_free(&mut avio_ctx);
2857 avformat_close_input(&mut in_fmt_ctx);
2858 return Err(FindStreamError::from(ret).into());
2859 }
2860
2861 if !have_seek_callback && input_requires_seek(in_fmt_ctx) {
2862 av_freep(&mut (*avio_ctx).buffer as *mut _ as *mut c_void);
2863 avio_context_free(&mut avio_ctx);
2864 avformat_close_input(&mut in_fmt_ctx);
2865 warn!("The input format supports seeking, but no seek callback is provided. This may cause issues.");
2866 return Err(OpenInputError::SeekFunctionMissing.into());
2867 }
2868 }
2869 Some(url) => {
2870 let url_cstr = CString::new(url.as_str())?;
2871
2872 let scan_all_pmts_key = CString::new("scan_all_pmts")?;
2873 if ffmpeg_sys_next::av_dict_get(
2874 input_opts,
2875 scan_all_pmts_key.as_ptr(),
2876 null(),
2877 ffmpeg_sys_next::AV_DICT_MATCH_CASE,
2878 )
2879 .is_null()
2880 {
2881 let scan_all_pmts_value = CString::new("1")?;
2882 ffmpeg_sys_next::av_dict_set(
2883 &mut input_opts,
2884 scan_all_pmts_key.as_ptr(),
2885 scan_all_pmts_value.as_ptr(),
2886 ffmpeg_sys_next::AV_DICT_DONT_OVERWRITE,
2887 );
2888 };
2889 (*in_fmt_ctx).flags |= ffmpeg_sys_next::AVFMT_FLAG_NONBLOCK;
2890
2891 let mut ret = avformat_open_input(
2892 &mut in_fmt_ctx,
2893 url_cstr.as_ptr(),
2894 file_iformat,
2895 &mut input_opts,
2896 );
2897 av_dict_free(&mut input_opts);
2898 if ret < 0 {
2899 avformat_close_input(&mut in_fmt_ctx);
2900 return Err(OpenInputError::from(ret).into());
2901 }
2902
2903 ret = avformat_find_stream_info(in_fmt_ctx, null_mut());
2904 if ret < 0 {
2905 avformat_close_input(&mut in_fmt_ctx);
2906 return Err(FindStreamError::from(ret).into());
2907 }
2908 }
2909 }
2910
2911 let mut timestamp = input.start_time_us.unwrap_or(0);
2912 if (*in_fmt_ctx).start_time != ffmpeg_sys_next::AV_NOPTS_VALUE {
2914 timestamp += (*in_fmt_ctx).start_time;
2915 }
2916
2917 if let Some(start_time_us) = input.start_time_us {
2919 let mut seek_timestamp = timestamp;
2920
2921 if (*(*in_fmt_ctx).iformat).flags & ffmpeg_sys_next::AVFMT_SEEK_TO_PTS == 0 {
2922 let mut dts_heuristic = false;
2923 let stream_count = (*in_fmt_ctx).nb_streams;
2924
2925 for i in 0..stream_count {
2926 let stream = *(*in_fmt_ctx).streams.add(i as usize);
2927 let par = (*stream).codecpar;
2928 if (*par).video_delay != 0 {
2929 dts_heuristic = true;
2930 break;
2931 }
2932 }
2933 if dts_heuristic {
2934 seek_timestamp -= 3 * AV_TIME_BASE as i64 / 23;
2935 }
2936 }
2937 let ret = ffmpeg_sys_next::avformat_seek_file(
2938 in_fmt_ctx,
2939 -1,
2940 i64::MIN,
2941 seek_timestamp,
2942 seek_timestamp,
2943 0,
2944 );
2945 if ret < 0 {
2946 warn!(
2947 "could not seek to position {:.3}",
2948 start_time_us as f64 / AV_TIME_BASE as f64
2949 );
2950 }
2951 }
2952
2953 let url = input
2954 .url
2955 .clone()
2956 .unwrap_or_else(|| format!("read_callback[{index}]"));
2957
2958 let demux = Demuxer::new(
2959 url,
2960 input.url.is_none(),
2961 in_fmt_ctx,
2962 0 - if copy_ts { 0 } else { timestamp },
2963 input.frame_pipelines.take(),
2964 input.video_codec.clone(),
2965 input.audio_codec.clone(),
2966 input.subtitle_codec.clone(),
2967 input.readrate,
2968 input.start_time_us,
2969 recording_time_us,
2970 input.exit_on_error,
2971 input.stream_loop,
2972 input.hwaccel.clone(),
2973 input.hwaccel_device.clone(),
2974 input.hwaccel_output_format.clone(),
2975 copy_ts,
2976 input.autorotate.unwrap_or(true), input.ts_scale.unwrap_or(1.0), match input.framerate { Some((num, den)) => AVRational { num, den },
2980 None => AVRational { num: 0, den: 0 },
2981 },
2982 )?;
2983
2984 Ok(demux)
2985}
2986
2987fn convert_options(
2988 opts: Option<HashMap<String, String>>,
2989) -> Result<Option<HashMap<CString, CString>>> {
2990 if opts.is_none() {
2991 return Ok(None);
2992 }
2993
2994 let converted = opts.map(|map| {
2995 map.into_iter()
2996 .map(|(k, v)| Ok((CString::new(k)?, CString::new(v)?)))
2997 .collect::<Result<HashMap<CString, CString>, _>>() });
2999
3000 converted.transpose() }
3002
3003unsafe fn input_requires_seek(fmt_ctx: *mut AVFormatContext) -> bool {
3004 if fmt_ctx.is_null() {
3005 return false;
3006 }
3007
3008 let mut format_name = "unknown".to_string();
3009 let mut format_names: Vec<&str> = Vec::with_capacity(0);
3010
3011 if !(*fmt_ctx).iformat.is_null() {
3012 let iformat = (*fmt_ctx).iformat;
3013 format_name = CStr::from_ptr((*iformat).name)
3014 .to_string_lossy()
3015 .into_owned();
3016 let flags = (*iformat).flags;
3017 let no_binsearch = flags & AVFMT_NOBINSEARCH != 0;
3018 let no_gensearch = flags & AVFMT_NOGENSEARCH != 0;
3019
3020 log::debug!(
3021 "Input format '{format_name}' - Binary search: {}, Generic search: {}",
3022 if no_binsearch { "Disabled" } else { "Enabled" },
3023 if no_gensearch { "Disabled" } else { "Enabled" }
3024 );
3025
3026 format_names = format_name.split(',').collect();
3027
3028 if format_names.iter().any(|&f| {
3029 matches!(
3030 f,
3031 "mp4" | "mkv" | "avi" | "mov" | "flac" | "wav" | "aac" | "ogg" | "mp3" | "webm"
3032 )
3033 })
3034 && !no_binsearch && !no_gensearch {
3035 return true;
3036 }
3037
3038 if format_names.iter().any(|&f| {
3039 matches!(
3040 f,
3041 "hls" | "m3u8" | "mpegts" | "mms" | "udp" | "rtp" | "rtp_mpegts" | "http" | "srt"
3042 )
3043 }) {
3044 log::debug!("Live stream detected ({format_name}). Seeking is not possible.");
3045 return false;
3046 }
3047
3048 if no_binsearch && no_gensearch {
3049 log::debug!("Input format '{format_name}' has both NOBINSEARCH and NOGENSEARCH set. Seeking is likely restricted.");
3050 }
3051 }
3052
3053 let format_duration = (*fmt_ctx).duration;
3054
3055 if format_names.contains(&"flv") {
3056 if format_duration <= 0 {
3057 log::debug!(
3058 "Input format 'flv' detected with no valid duration. Seeking is not possible."
3059 );
3060 } else {
3061 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.");
3062 }
3063 return false;
3064 }
3065
3066 if format_duration > 0 {
3067 log::debug!("Format '{format_name}' has a duration of {format_duration}. Seeking is likely possible.");
3068 return true;
3069 }
3070
3071 let mut video_stream_index = -1;
3072 for i in 0..(*fmt_ctx).nb_streams {
3073 let stream = *(*fmt_ctx).streams.offset(i as isize);
3074 if (*stream).codecpar.is_null() {
3075 continue;
3076 }
3077 if (*(*stream).codecpar).codec_type == AVMEDIA_TYPE_VIDEO {
3078 video_stream_index = i as i32;
3079 break;
3080 }
3081 }
3082
3083 let stream_index = if video_stream_index >= 0 {
3084 video_stream_index
3085 } else {
3086 -1
3087 };
3088
3089 let original_pos = if !(*fmt_ctx).pb.is_null() {
3090 (*(*fmt_ctx).pb).pos
3091 } else {
3092 -1
3093 };
3094
3095 if original_pos >= 0 {
3096 let seek_target = AV_TIME_BASE as i64;
3097 let seek_result = av_seek_frame(fmt_ctx, stream_index, seek_target, AVSEEK_FLAG_BACKWARD);
3098
3099 if seek_result >= 0 {
3100 log::debug!("Seek test successful.");
3101
3102 (*(*fmt_ctx).pb).pos = original_pos;
3103 avformat_flush(fmt_ctx);
3104 log::debug!("Restored fmt_ctx.pb.pos to {original_pos} and flushed format context.",);
3105 return true;
3106 } else {
3107 log::debug!("Seek test failed (return code {seek_result}). This format likely does not support seeking.");
3108 }
3109 }
3110
3111 false
3112}
3113
3114#[cfg(test)]
3115mod tests {
3116 use std::ffi::{CStr, CString};
3117 use std::ptr::null_mut;
3118
3119 use crate::core::context::ffmpeg_context::{strtol, FfmpegContext, Output};
3120 use ffmpeg_sys_next::{
3121 avfilter_graph_alloc, avfilter_graph_free, avfilter_graph_parse_ptr, avfilter_inout_free,
3122 };
3123
3124 #[test]
3125 fn test_filter() {
3126 let desc_cstr = CString::new("[1:v][2:v]concat=n=2:v=1:a=0[vout]").unwrap();
3127 unsafe {
3130 let mut graph = avfilter_graph_alloc();
3131 let mut inputs = null_mut();
3132 let mut outputs = null_mut();
3133
3134 let ret = avfilter_graph_parse_ptr(
3135 graph,
3136 desc_cstr.as_ptr(),
3137 &mut inputs,
3138 &mut outputs,
3139 null_mut(),
3140 );
3141 if ret < 0 {
3142 avfilter_inout_free(&mut inputs);
3143 avfilter_inout_free(&mut outputs);
3144 avfilter_graph_free(&mut graph);
3145 println!("err ret:{}", crate::util::ffmpeg_utils::av_err2str(ret));
3146 return;
3147 }
3148
3149 println!("inputs.is_null:{}", inputs.is_null());
3150 println!("outputs.is_null:{}", outputs.is_null());
3151
3152 let mut cur = inputs;
3153 while !cur.is_null() {
3154 let input_name = CStr::from_ptr((*cur).name);
3155 println!("Input name: {}", input_name.to_str().unwrap());
3156 cur = (*cur).next;
3157 }
3158
3159 let output_name = CStr::from_ptr((*outputs).name);
3160 println!("Output name: {}", output_name.to_str().unwrap());
3161
3162 let filter_ctx = (*outputs).filter_ctx;
3163 avfilter_inout_free(&mut outputs);
3164 println!("filter_ctx.is_null:{}", filter_ctx.is_null());
3165 }
3166 }
3167
3168 #[test]
3169 fn test_new() {
3170 let _ = env_logger::builder()
3171 .filter_level(log::LevelFilter::Debug)
3172 .is_test(true)
3173 .try_init();
3174 let _ffmpeg_context = FfmpegContext::new(
3175 vec!["test.mp4".to_string().into()],
3176 vec!["hue=s=0".to_string().into()],
3177 vec!["output.mp4".to_string().into()],
3178 )
3179 .unwrap();
3180 let _ffmpeg_context = FfmpegContext::new(
3181 vec!["test.mp4".into()],
3182 vec!["[0:v]hue=s=0".into()],
3183 vec!["output.mp4".to_string().into()],
3184 )
3185 .unwrap();
3186 let _ffmpeg_context = FfmpegContext::new(
3187 vec!["test.mp4".into()],
3188 vec!["hue=s=0[my-out]".into()],
3189 vec![Output::from("output.mp4").add_stream_map("my-out")],
3190 )
3191 .unwrap();
3192 let result = FfmpegContext::new(
3193 vec!["test.mp4".into()],
3194 vec!["hue=s=0".into()],
3195 vec![Output::from("output.mp4").add_stream_map("0:v?")],
3196 );
3197 assert!(result.is_err());
3198 let result = FfmpegContext::new(
3199 vec!["test.mp4".into()],
3200 vec!["hue=s=0".into()],
3201 vec![Output::from("output.mp4").add_stream_map_with_copy("1:v?")],
3202 );
3203 assert!(result.is_err());
3204 let result = FfmpegContext::new(
3205 vec!["test.mp4".into()],
3206 vec!["hue=s=0[fg-out]".into()],
3207 vec![
3208 Output::from("output.mp4").add_stream_map("my-out?"),
3209 Output::from("output.mp4").add_stream_map("fg-out"),
3210 ],
3211 );
3212 assert!(result.is_err());
3213 let result = FfmpegContext::new(
3215 vec!["test.mp4".into()],
3216 vec!["hue=s=0".into()],
3217 vec![Output::from("output.mp4").add_stream_map_with_copy("1:v")],
3218 );
3219 assert!(result.is_err());
3220 let result = FfmpegContext::new(
3221 vec!["test.mp4".into()],
3222 vec!["hue=s=0[fg-out]".into()],
3223 vec![Output::from("output.mp4").add_stream_map("fg-out?")],
3224 );
3225 assert!(result.is_err());
3226 }
3227
3228 #[test]
3229 fn test_builder() {
3230 let _ = env_logger::builder()
3231 .filter_level(log::LevelFilter::Debug)
3232 .is_test(true)
3233 .try_init();
3234
3235 let _context1 = FfmpegContext::builder()
3236 .input("test.mp4")
3237 .filter_desc("hue=s=0")
3238 .output("output.mp4")
3239 .build()
3240 .unwrap();
3241
3242 let _context2 = FfmpegContext::builder()
3243 .inputs(vec!["test.mp4"])
3244 .filter_descs(vec!["hue=s=0"])
3245 .outputs(vec!["output.mp4"])
3246 .build()
3247 .unwrap();
3248 }
3249
3250 #[test]
3251 fn test_strtol() {
3252 let input = "-123---abc";
3253 let result = strtol(input);
3254 assert_eq!(result.unwrap(), (-123, "---abc"));
3255
3256 let input = "123---abc";
3257 let result = strtol(input);
3258 assert_eq!(result.unwrap(), (123, "---abc"));
3259
3260 let input = "-123aa";
3261 let result = strtol(input);
3262 assert_eq!(result.unwrap(), (-123, "aa"));
3263
3264 let input = "-aa";
3265 let result = strtol(input);
3266 assert!(result.is_err());
3267
3268 let input = "abc";
3269 let result = strtol(input);
3270 assert!(result.is_err())
3271 }
3272}