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