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