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