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