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_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_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_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(feature = "docs-rs")]
495fn configure_output_filter_opts(
496 index: usize,
497 mux: &mut Muxer,
498 output_filter: &mut OutputFilter,
499 codec_id: AVCodecID,
500 enc: *const AVCodec,
501 output_stream_index: usize,
502) -> Result<()> {
503 Ok(())
504}
505
506#[cfg(not(feature = "docs-rs"))]
507fn configure_output_filter_opts(
508 index: usize,
509 mux: &mut Muxer,
510 output_filter: &mut OutputFilter,
511 codec_id: AVCodecID,
512 enc: *const AVCodec,
513 output_stream_index: usize,
514) -> Result<()> {
515 unsafe {
516 output_filter.opts.name = format!("#{index}:{output_stream_index}");
517 output_filter.opts.enc = enc;
518 output_filter.opts.trim_start_us = mux.start_time_us;
519 output_filter.opts.trim_duration_us = mux.recording_time_us;
520 output_filter.opts.ts_offset = mux.start_time_us;
521
522 output_filter.opts.flags = OFILTER_FLAG_DISABLE_CONVERT
523 | OFILTER_FLAG_AUTOSCALE
524 | if av_get_exact_bits_per_sample(codec_id) == 24 {
525 OFILTER_FLAG_AUDIO_24BIT
526 } else {
527 0
528 };
529
530 let enc_ctx = avcodec_alloc_context3(enc);
531 if enc_ctx.is_null() {
532 return Err(OpenOutputError::OutOfMemory.into());
533 }
534 let _codec_ctx = CodecContext::new(enc_ctx);
535
536 (*enc_ctx).thread_count = 0;
537
538 if output_filter.media_type == AVMEDIA_TYPE_VIDEO {
539 let mut formats: *const AVPixelFormat = null();
541 let mut ret = avcodec_get_supported_config(
542 enc_ctx,
543 null(),
544 AV_CODEC_CONFIG_PIX_FORMAT,
545 0,
546 &mut formats as *mut _ as *mut *const libc::c_void,
547 null_mut(),
548 );
549 if ret < 0 {
550 return Err(OpenOutputError::from(ret).into());
551 }
552
553 let mut current = formats;
554 let mut format_list = Vec::new();
555 while !current.is_null() && *current != AV_PIX_FMT_NONE {
556 format_list.push(*current);
557 current = current.add(1);
558 }
559 output_filter.opts.formats = Some(format_list);
560
561 let mut framerates: *const AVRational = null();
563 ret = avcodec_get_supported_config(
564 enc_ctx,
565 null(),
566 AV_CODEC_CONFIG_FRAME_RATE,
567 0,
568 &mut framerates as *mut _ as *mut *const libc::c_void,
569 null_mut(),
570 );
571 if ret < 0 {
572 return Err(OpenOutputError::from(ret).into());
573 }
574 let mut framerate_list = Vec::new();
575 let mut current = framerates;
576 while !current.is_null() && (*current).num != 0 && (*current).den != 0 {
577 framerate_list.push(*current);
578 current = current.add(1);
579 }
580 output_filter.opts.framerates = Some(framerate_list);
581
582 if let Some(framerate) = mux.framerate {
583 output_filter.opts.framerate = framerate;
584 }
585
586 let mut color_spaces: *const AVColorSpace = null();
588 ret = avcodec_get_supported_config(
589 enc_ctx,
590 null(),
591 AV_CODEC_CONFIG_COLOR_SPACE,
592 0,
593 &mut color_spaces as *mut _ as *mut *const libc::c_void,
594 null_mut(),
595 );
596 if ret < 0 {
597 return Err(OpenOutputError::from(ret).into());
598 }
599 let mut color_space_list = Vec::new();
600 let mut current = color_spaces;
601 while !current.is_null() && *current != AVCOL_SPC_UNSPECIFIED {
602 color_space_list.push(*current);
603 current = current.add(1);
604 }
605 output_filter.opts.color_spaces = Some(color_space_list);
606
607 let mut color_ranges: *const AVColorRange = null();
609 ret = avcodec_get_supported_config(
610 enc_ctx,
611 null(),
612 AV_CODEC_CONFIG_COLOR_RANGE,
613 0,
614 &mut color_ranges as *mut _ as *mut *const libc::c_void,
615 null_mut(),
616 );
617 if ret < 0 {
618 return Err(OpenOutputError::from(ret).into());
619 }
620 let mut color_range_list = Vec::new();
621 let mut current = color_ranges;
622 while !current.is_null() && *current != AVCOL_RANGE_UNSPECIFIED {
623 color_range_list.push(*current);
624 current = current.add(1);
625 }
626 output_filter.opts.color_ranges = Some(color_range_list);
627
628 let stream = &mux.get_streams()[output_stream_index];
629 output_filter.opts.vsync_method = stream.vsync_method;
630 } else {
631 if let Some(sample_fmt) = &mux.audio_sample_fmt {
632 output_filter.opts.audio_format = *sample_fmt;
633 }
634 let mut audio_formats: *const AVSampleFormat = null();
636 let mut ret = avcodec_get_supported_config(
637 enc_ctx,
638 null(),
639 AV_CODEC_CONFIG_SAMPLE_FORMAT,
640 0,
641 &mut audio_formats as *mut _ as *mut _,
642 null_mut(),
643 );
644 if ret < 0 {
645 return Err(OpenOutputError::from(ret).into());
646 }
647
648 let mut current = audio_formats;
649 let mut audio_format_list = Vec::new();
650 while !current.is_null() && *current != AV_SAMPLE_FMT_NONE {
651 audio_format_list.push(*current);
652 current = current.add(1);
653 }
654 output_filter.opts.audio_formats = Some(audio_format_list);
655
656
657 if let Some(audio_sample_rate) = &mux.audio_sample_rate {
658 output_filter.opts.sample_rate = *audio_sample_rate;
659 }
660 let mut rates: *const i32 = null();
662 ret = avcodec_get_supported_config(
663 enc_ctx,
664 null(),
665 AV_CODEC_CONFIG_SAMPLE_RATE,
666 0,
667 &mut rates as *mut _ as *mut _,
668 null_mut(),
669 );
670 if ret < 0 {
671 return Err(OpenOutputError::from(ret).into());
672 }
673 let mut rate_list = Vec::new();
674 let mut current = rates;
675 while !current.is_null() && *current != 0 {
676 rate_list.push(*current);
677 current = current.add(1);
678 }
679 output_filter.opts.sample_rates = Some(rate_list);
680
681
682 if let Some(channels) = &mux.audio_channels {
683 output_filter.opts.ch_layout.nb_channels = *channels;
684 }
685 let mut layouts: *const AVChannelLayout = null();
687 ret = avcodec_get_supported_config(
688 enc_ctx,
689 null(),
690 AV_CODEC_CONFIG_CHANNEL_LAYOUT,
691 0,
692 &mut layouts as *mut _ as *mut _,
693 null_mut(),
694 );
695 if ret < 0 {
696 return Err(OpenOutputError::from(ret).into());
697 }
698 let mut layout_list = Vec::new();
699 let mut current = layouts;
700 while !current.is_null() && (*current).order != AV_CHANNEL_ORDER_UNSPEC {
701 layout_list.push(*current);
702 current = current.add(1);
703 }
704 output_filter.opts.ch_layouts = Some(layout_list);
705 }
706 };
707 Ok(())
708}
709
710fn output_find_input_idx_by_linklabel(
711 linklabel: &str,
712 demuxs: &mut Vec<Demuxer>,
713 desc: &str,
714) -> Result<Option<(usize, usize, AVMediaType)>> {
715 let new_linklabel = if linklabel.starts_with("[") && linklabel.ends_with("]") {
716 if linklabel.len() <= 2 {
717 warn!("Output linklabel is empty");
718 return Err(OpenOutputError::InvalidArgument.into());
719 } else {
720 &linklabel[1..linklabel.len() - 1]
721 }
722 } else {
723 linklabel
724 };
725
726 let (file_idx, remainder) = strtol(new_linklabel)?;
727 if file_idx < 0 || file_idx as usize >= demuxs.len() {
728 return Err(InvalidFileIndexInIntput(file_idx as usize, desc.to_string()).into());
729 }
730
731 let (media_type, allow_unused) = stream_specifier_parse(remainder)?;
732
733 let demux = &demuxs[file_idx as usize];
734
735 let mut stream_idx = -1i32;
736
737 for (idx, dec_stream) in demux.get_streams().iter().enumerate() {
738 if (*dec_stream).codec_type == media_type {
739 stream_idx = idx as i32;
740 break;
741 }
742 }
743
744 if stream_idx < 0 {
745 if allow_unused {
746 return Ok(None);
747 }
748
749 warn!("Stream specifier '{remainder}' in output {desc} matches no streams.");
750 return Err(OpenOutputError::MatchesNoStreams(linklabel.to_string()).into());
751 }
752 Ok(Some((file_idx as usize, stream_idx as usize, media_type)))
753}
754
755fn map_auto_streams(
756 mux_index: usize,
757 mux: &mut Muxer,
758 demuxs: &mut Vec<Demuxer>,
759 filter_graphs: &mut Vec<FilterGraph>,
760 auto_disable: i32,
761) -> Result<()> {
762 unsafe {
763 let oformat = (*mux.out_fmt_ctx).oformat;
764 map_auto_stream(
765 mux_index,
766 mux,
767 demuxs,
768 oformat,
769 AVMEDIA_TYPE_VIDEO,
770 filter_graphs,
771 auto_disable,
772 )?;
773 map_auto_stream(
774 mux_index,
775 mux,
776 demuxs,
777 oformat,
778 AVMEDIA_TYPE_AUDIO,
779 filter_graphs,
780 auto_disable,
781 )?;
782 map_auto_stream(
783 mux_index,
784 mux,
785 demuxs,
786 oformat,
787 AVMEDIA_TYPE_SUBTITLE,
788 filter_graphs,
789 auto_disable,
790 )?;
791 map_auto_stream(
792 mux_index,
793 mux,
794 demuxs,
795 oformat,
796 AVMEDIA_TYPE_DATA,
797 filter_graphs,
798 auto_disable,
799 )?;
800 }
801 Ok(())
802}
803
804
805#[cfg(feature = "docs-rs")]
806unsafe fn map_auto_stream(
807 mux_index: usize,
808 mux: &mut Muxer,
809 demuxs: &mut Vec<Demuxer>,
810 oformat: *const AVOutputFormat,
811 media_type: AVMediaType,
812 filter_graphs: &mut Vec<FilterGraph>,
813 auto_disable: i32,
814) -> Result<()> {
815 Ok(())
816}
817
818#[cfg(not(feature = "docs-rs"))]
819unsafe fn map_auto_stream(
820 mux_index: usize,
821 mux: &mut Muxer,
822 demuxs: &mut Vec<Demuxer>,
823 oformat: *const AVOutputFormat,
824 media_type: AVMediaType,
825 filter_graphs: &mut Vec<FilterGraph>,
826 auto_disable: i32,
827) -> Result<()> {
828 if auto_disable & (1 << media_type as i32) != 0 {
829 return Ok(());
830 }
831 if media_type == AVMEDIA_TYPE_VIDEO
832 || media_type == AVMEDIA_TYPE_AUDIO
833 || media_type == AVMEDIA_TYPE_DATA
834 {
835 if av_guess_codec(oformat, null(), (*mux.out_fmt_ctx).url, null(), media_type)
836 == AV_CODEC_ID_NONE
837 {
838 return Ok(());
839 }
840 }
841
842 for demux in demuxs {
843 let option = demux
844 .get_streams()
845 .iter()
846 .enumerate()
847 .find_map(|(index, input_stream)| {
848 if input_stream.codec_type == media_type {
849 Some(index)
850 } else {
851 None
852 }
853 });
854
855 if option.is_none() {
856 continue;
857 }
858
859 let stream_index = option.unwrap();
860 let option = choose_encoder(mux, media_type)?;
861
862 if let Some((codec_id, enc)) = option {
863 if media_type == AVMEDIA_TYPE_VIDEO || media_type == AVMEDIA_TYPE_AUDIO {
864 init_simple_filtergraph(
865 demux,
866 stream_index,
867 codec_id,
868 enc,
869 mux_index,
870 mux,
871 filter_graphs,
872 )?;
873 } else {
874 let (frame_sender, output_stream_index) =
875 mux.add_enc_stream(media_type, enc, demux.node.clone())?;
876 demux.get_stream_mut(stream_index).add_dst(frame_sender);
877 demux.connect_stream(stream_index);
878 let input_stream = demux.get_stream(stream_index);
879 unsafe {
880 rescale_duration(
881 input_stream.duration,
882 input_stream.time_base,
883 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
884 );
885 }
886 }
887
888 return Ok(());
889 }
890
891 let input_stream = demux.get_stream(stream_index);
893 let input_stream_duration = input_stream.duration;
894 let input_stream_time_base = input_stream.time_base;
895
896 let (packet_sender, _st, output_stream_index) = mux.new_stream(demux.node.clone())?;
897 demux.add_packet_dst(packet_sender, stream_index, output_stream_index);
898
899 unsafe {
900 streamcopy_init(
901 mux,
902 *(*demux.in_fmt_ctx).streams.add(stream_index),
903 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
904 )?;
905 rescale_duration(
906 input_stream_duration,
907 input_stream_time_base,
908 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
909 );
910 mux.stream_ready()
911 }
912 }
913
914 Ok(())
915}
916
917fn init_simple_filtergraph(
918 demux: &mut Demuxer,
919 stream_index: usize,
920 codec_id: AVCodecID,
921 enc: *const AVCodec,
922 mux_index: usize,
923 mux: &mut Muxer,
924 filter_graphs: &mut Vec<FilterGraph>,
925) -> Result<()> {
926 let codec_type = demux.get_stream(stream_index).codec_type;
927
928 let filter_desc = if codec_type == AVMEDIA_TYPE_VIDEO {
929 "null"
930 } else {
931 "anull"
932 };
933 let mut filter_graph = init_filter_graph(filter_graphs.len(), filter_desc, None)?;
934
935 ifilter_bind_ist(&mut filter_graph, 0, stream_index, demux)?;
939 ofilter_bind_ost(
940 mux_index,
941 mux,
942 &mut filter_graph,
943 0,
944 codec_id,
945 enc,
946 )?;
947
948 filter_graphs.push(filter_graph);
949
950 Ok(())
951}
952
953unsafe fn rescale_duration(src_duration: i64, src_time_base: AVRational, stream: *mut AVStream) {
954 (*stream).duration = av_rescale_q(src_duration, src_time_base, (*stream).time_base);
955}
956
957#[cfg(feature = "docs-rs")]
958fn streamcopy_init(
959 mux: &mut Muxer,
960 input_stream: *mut AVStream,
961 output_stream: *mut AVStream,
962) -> Result<()> {
963 Ok(())
964}
965
966#[cfg(not(feature = "docs-rs"))]
967fn streamcopy_init(
968 mux: &mut Muxer,
969 input_stream: *mut AVStream,
970 output_stream: *mut AVStream,
971) -> Result<()> {
972 unsafe {
973 let codec_ctx = avcodec_alloc_context3(null_mut());
974 if codec_ctx.is_null() {
975 return Err(OpenOutputError::OutOfMemory.into());
976 }
977 let _codec_context = CodecContext::new(codec_ctx);
978
979 let mut ret = avcodec_parameters_to_context(codec_ctx, (*input_stream).codecpar);
980 if ret < 0 {
981 error!("Error setting up codec context options.");
982 return Err(OpenOutputError::from(ret).into());
983 }
984
985 ret = avcodec_parameters_from_context((*output_stream).codecpar, codec_ctx);
986 if ret < 0 {
987 error!("Error getting reference codec parameters.");
988 return Err(OpenOutputError::from(ret).into());
989 }
990
991 let mut codec_tag = (*(*output_stream).codecpar).codec_tag;
992 if codec_tag == 0 {
993 let ct = (*(*mux.out_fmt_ctx).oformat).codec_tag;
994 let mut codec_tag_tmp = 0;
995 if ct.is_null()
996 || av_codec_get_id(ct, (*(*output_stream).codecpar).codec_tag)
997 == (*(*output_stream).codecpar).codec_id
998 || av_codec_get_tag2(
999 ct,
1000 (*(*output_stream).codecpar).codec_id,
1001 &mut codec_tag_tmp,
1002 ) == 0
1003 {
1004 codec_tag = (*(*output_stream).codecpar).codec_tag;
1005 }
1006 }
1007 (*(*output_stream).codecpar).codec_tag = codec_tag;
1008
1009 let mut fr = (*output_stream).r_frame_rate;
1010 if fr.num == 0 {
1011 fr = (*input_stream).r_frame_rate;
1012 }
1013
1014 if fr.num != 0 {
1015 (*output_stream).avg_frame_rate = fr;
1016 } else {
1017 (*output_stream).avg_frame_rate = (*input_stream).avg_frame_rate;
1018 }
1019
1020 if (*output_stream).time_base.num <= 0 || (*output_stream).time_base.den <= 0 {
1022 if fr.num != 0 {
1023 (*output_stream).time_base = av_inv_q(fr);
1024 } else {
1025 (*output_stream).time_base =
1026 av_add_q((*input_stream).time_base, AVRational { num: 0, den: 1 });
1027 }
1028 }
1029
1030 for i in 0..(*(*input_stream).codecpar).nb_coded_side_data {
1031 let sd_src = (*(*input_stream).codecpar)
1032 .coded_side_data
1033 .offset(i as isize);
1034
1035 let sd_dst = av_packet_side_data_new(
1036 &mut (*(*output_stream).codecpar).coded_side_data,
1037 &mut (*(*output_stream).codecpar).nb_coded_side_data,
1038 (*sd_src).type_,
1039 (*sd_src).size,
1040 0,
1041 );
1042 if sd_dst.is_null() {
1043 return Err(OpenOutputError::OutOfMemory.into());
1044 }
1045 std::ptr::copy_nonoverlapping(
1046 (*sd_src).data as *const u8,
1047 (*sd_dst).data,
1048 (*sd_src).size,
1049 );
1050 }
1051
1052 match (*(*output_stream).codecpar).codec_type {
1053 AVMEDIA_TYPE_AUDIO => {
1054 if ((*(*output_stream).codecpar).block_align == 1
1055 || (*(*output_stream).codecpar).block_align == 1152
1056 || (*(*output_stream).codecpar).block_align == 576)
1057 && (*(*output_stream).codecpar).codec_id == AV_CODEC_ID_MP3
1058 {
1059 (*(*output_stream).codecpar).block_align = 0;
1060 }
1061 if (*(*output_stream).codecpar).codec_id == AV_CODEC_ID_AC3 {
1062 (*(*output_stream).codecpar).block_align = 0;
1063 }
1064 }
1065 AVMEDIA_TYPE_VIDEO => {
1066 let sar = if (*input_stream).sample_aspect_ratio.num != 0 {
1067 (*input_stream).sample_aspect_ratio
1068 } else {
1069 (*(*output_stream).codecpar).sample_aspect_ratio
1070 };
1071 (*output_stream).sample_aspect_ratio = sar;
1072 (*(*output_stream).codecpar).sample_aspect_ratio = sar;
1073 (*output_stream).r_frame_rate = (*input_stream).r_frame_rate;
1074 }
1075 _ => {}
1076 }
1077 };
1078 Ok(())
1079}
1080
1081fn output_bind_by_unlabeled_filter(
1082 index: usize,
1083 mux: &mut Muxer,
1084 filter_graphs: &mut Vec<FilterGraph>,
1085 auto_disable: &mut i32,
1086) -> Result<()> {
1087 let fg_len = filter_graphs.len();
1088
1089 for i in 0..fg_len {
1090 let filter_graph = &mut filter_graphs[i];
1091
1092 for i in 0..filter_graph.outputs.len() {
1093 let option = {
1094 let output_filter = &filter_graph.outputs[i];
1095 if (!output_filter.linklabel.is_empty() && output_filter.linklabel != "out")
1096 || output_filter.has_dst()
1097 {
1098 continue;
1099 }
1100
1101 choose_encoder(mux, output_filter.media_type)?
1102 };
1103
1104 let media_type = filter_graph.outputs[i].media_type;
1105
1106 match option {
1107 None => {
1108 warn!("An unexpected media_type {:?} appears in output_filter", media_type);
1109 }
1110 Some((codec_id, enc)) => {
1111 *auto_disable |= 1 << media_type as i32;
1112 ofilter_bind_ost(index, mux, filter_graph, i, codec_id, enc)?;
1113 }
1114 }
1115 }
1116 }
1117
1118 Ok(())
1119}
1120
1121fn ofilter_bind_ost(
1122 index: usize,
1123 mux: &mut Muxer,
1124 filter_graph: &mut FilterGraph,
1125 output_filter_index: usize,
1126 codec_id: AVCodecID,
1127 enc: *const AVCodec,
1128) -> Result<()> {
1129 let output_filter = &mut filter_graph.outputs[output_filter_index];
1130 let (frame_sender, output_stream_index) =
1131 mux.add_enc_stream(output_filter.media_type, enc, filter_graph.node.clone())?;
1132 output_filter.set_dst(frame_sender);
1133
1134 configure_output_filter_opts(
1135 index,
1136 mux,
1137 output_filter,
1138 codec_id,
1139 enc,
1140 output_stream_index,
1141 )?;
1142 Ok(())
1143}
1144
1145fn choose_encoder(
1146 mux: &Muxer,
1147 media_type: AVMediaType,
1148) -> Result<Option<(AVCodecID, *const AVCodec)>> {
1149 let media_codec = match media_type {
1150 AVMEDIA_TYPE_VIDEO => mux.video_codec.clone(),
1151 AVMEDIA_TYPE_AUDIO => mux.audio_codec.clone(),
1152 AVMEDIA_TYPE_SUBTITLE => mux.subtitle_codec.clone(),
1153 _ => return Ok(None),
1154 };
1155
1156 match media_codec {
1157 None => {
1158 let url = CString::new(&*mux.url).unwrap();
1159 unsafe {
1160 let codec_id = av_guess_codec(
1161 (*mux.out_fmt_ctx).oformat,
1162 null(),
1163 url.as_ptr(),
1164 null(),
1165 media_type,
1166 );
1167 let enc = avcodec_find_encoder(codec_id);
1168 if enc.is_null() {
1169 let format_name = (*(*mux.out_fmt_ctx).oformat).name;
1170 let format_name = CStr::from_ptr(format_name).to_str();
1171 let codec_name = avcodec_get_name(codec_id);
1172 let codec_name = CStr::from_ptr(codec_name).to_str();
1173 if let (Ok(format_name), Ok(codec_name)) = (format_name, codec_name) {
1174 error!("Automatic encoder selection failed Default encoder for format {format_name} (codec {codec_name}) is probably disabled. Please choose an encoder manually.");
1175 }
1176 return Err(OpenOutputError::from(AVERROR_ENCODER_NOT_FOUND).into());
1177 }
1178
1179 return Ok(Some((codec_id, enc)));
1180 }
1181 }
1182 Some(media_codec) if media_codec != "copy" => unsafe {
1183 let media_codec_cstr = CString::new(media_codec.clone())?;
1184
1185 let mut enc = avcodec_find_encoder_by_name(media_codec_cstr.as_ptr());
1186 let desc = avcodec_descriptor_get_by_name(media_codec_cstr.as_ptr());
1187
1188 if enc.is_null() && !desc.is_null() {
1189 enc = avcodec_find_encoder((*desc).id);
1190 if !enc.is_null() {
1191 let codec_name = (*enc).name;
1192 let codec_name = CStr::from_ptr(codec_name).to_str();
1193 let desc_name = (*desc).name;
1194 let desc_name = CStr::from_ptr(desc_name).to_str();
1195 if let (Ok(codec_name), Ok(desc_name)) = (codec_name, desc_name) {
1196 debug!("Matched encoder '{codec_name}' for codec '{desc_name}'.");
1197 }
1198 }
1199 }
1200
1201 if enc.is_null() {
1202 error!("Unknown encoder '{media_codec}'");
1203 return Err(OpenOutputError::from(AVERROR_ENCODER_NOT_FOUND).into());
1204 }
1205
1206 if (*enc).type_ != media_type {
1207 error!("Invalid encoder type '{media_codec}'");
1208 return Err(OpenOutputError::InvalidArgument.into());
1209 }
1210 let codec_id = (*enc).id;
1211 return Ok(Some((codec_id, enc)));
1212 },
1213 _ => {}
1214 };
1215
1216 Ok(None)
1217}
1218
1219fn check_duplicate_inputs_outputs(inputs: &[Input], outputs: &[Output]) -> Result<()> {
1220 for output in outputs {
1221 if let Some(output_url) = &output.url {
1222 for input in inputs {
1223 if let Some(input_url) = &input.url {
1224 if input_url == output_url {
1225 return Err(FileSameAsInput(input_url.clone()));
1226 }
1227 }
1228 }
1229 }
1230 }
1231 Ok(())
1232}
1233
1234fn open_output_files(outputs: &mut Vec<Output>, copy_ts: bool) -> Result<Vec<Muxer>> {
1235 let mut muxs = Vec::new();
1236
1237 for (i, output) in outputs.iter_mut().enumerate() {
1238 unsafe {
1239 let result = open_output_file(i, output, copy_ts);
1240 if let Err(e) = result {
1241 free_output_av_format_context(muxs);
1242 return Err(e);
1243 }
1244 let mux = result.unwrap();
1245 muxs.push(mux)
1246 }
1247 }
1248 Ok(muxs)
1249}
1250
1251unsafe fn free_output_av_format_context(muxs: Vec<Muxer>) {
1252 for mut mux in muxs {
1253 avformat_close_input(&mut mux.out_fmt_ctx);
1254 }
1255}
1256
1257#[cfg(feature = "docs-rs")]
1258unsafe fn open_output_file(index: usize, output: &mut Output) -> Result<Muxer> {
1259 Err(Bug)
1260}
1261
1262#[cfg(not(feature = "docs-rs"))]
1263unsafe fn open_output_file(index: usize, output: &mut Output, copy_ts: bool) -> Result<Muxer> {
1264 let mut out_fmt_ctx = null_mut();
1265 let format = get_format(&output.format)?;
1266 match &output.url {
1267 None => {
1268 if output.write_callback.is_none() {
1269 error!("input url and write_callback is none.");
1270 return Err(OpenOutputError::InvalidSink.into());
1271 }
1272
1273 let write_callback = output.write_callback.take().unwrap();
1274
1275 let avio_ctx_buffer_size = 1024 * 64;
1276 let mut avio_ctx_buffer = av_malloc(avio_ctx_buffer_size);
1277 if avio_ctx_buffer.is_null() {
1278 return Err(OpenOutputError::OutOfMemory.into());
1279 }
1280
1281 let have_seek_callback = output.seek_callback.is_some();
1282 let input_opaque = Box::new(OutputOpaque {
1283 write: write_callback,
1284 seek: output.seek_callback.take(),
1285 });
1286 let opaque = Box::into_raw(input_opaque) as *mut libc::c_void;
1287
1288 let mut avio_ctx = avio_alloc_context(
1289 avio_ctx_buffer as *mut libc::c_uchar,
1290 avio_ctx_buffer_size as i32,
1291 1,
1292 opaque,
1293 None,
1294 Some(write_packet_wrapper),
1295 if have_seek_callback {
1296 Some(seek_packet_wrapper)
1297 } else {
1298 None
1299 },
1300 );
1301 if avio_ctx.is_null() {
1302 av_freep(&mut avio_ctx_buffer as *mut _ as *mut c_void);
1303 return Err(OpenOutputError::OutOfMemory.into());
1304 }
1305
1306 let ret = avformat_alloc_output_context2(&mut out_fmt_ctx, format, null(), null());
1307 if out_fmt_ctx.is_null() {
1308 warn!("Error initializing the muxer for write_callback");
1309 av_freep(&mut (*avio_ctx).buffer as *mut _ as *mut c_void);
1310 avio_context_free(&mut avio_ctx);
1311 return Err(AllocOutputContextError::from(ret).into());
1312 }
1313
1314 if !have_seek_callback && output_requires_seek(out_fmt_ctx) {
1315 av_freep(&mut (*avio_ctx).buffer as *mut _ as *mut c_void);
1316 avio_context_free(&mut avio_ctx);
1317 avformat_free_context(out_fmt_ctx);
1318 warn!("The output format supports seeking, but no seek callback is provided. This may cause issues.");
1319 return Err(OpenOutputError::SeekFunctionMissing.into());
1320 }
1321
1322 (*out_fmt_ctx).pb = avio_ctx;
1323 (*out_fmt_ctx).flags |= AVFMT_FLAG_CUSTOM_IO;
1324 }
1325 Some(url) => {
1326 let url_cstr = if url == "-" {
1327 CString::new("pipe:")?
1328 } else {
1329 CString::new(url.as_str())?
1330 };
1331 let ret =
1332 avformat_alloc_output_context2(&mut out_fmt_ctx, format, null(), url_cstr.as_ptr());
1333 if out_fmt_ctx.is_null() {
1334 warn!("Error initializing the muxer for {url}");
1335 return Err(AllocOutputContextError::from(ret).into());
1336 }
1337
1338 let output_format = (*out_fmt_ctx).oformat;
1339 if (*output_format).flags & AVFMT_NOFILE == 0 {
1340 let ret = avio_open(&mut (*out_fmt_ctx).pb, url_cstr.as_ptr(), AVIO_FLAG_WRITE);
1341 if ret < 0 {
1342 warn!("Error opening output {url}");
1343 return Err(OpenOutputError::from(ret).into());
1344 }
1345 }
1346 }
1347 }
1348
1349 let recording_time_us = match output.stop_time_us {
1350 None => output.recording_time_us,
1351 Some(stop_time_us) => {
1352 let start_time_us = output.start_time_us.unwrap_or_else(|| 0);
1353 if stop_time_us <= start_time_us {
1354 error!("stop_time_us value smaller than start_time_us; aborting.");
1355 return Err(OpenOutputError::InvalidArgument.into());
1356 } else {
1357 Some(stop_time_us - start_time_us)
1358 }
1359 }
1360 };
1361
1362 let url = output
1363 .url
1364 .clone()
1365 .unwrap_or_else(|| format!("write_callback[{index}]"));
1366
1367 let video_codec_opts = convert_options(output.video_codec_opts.clone())?;
1368 let audio_codec_opts = convert_options(output.audio_codec_opts.clone())?;
1369 let subtitle_codec_opts = convert_options(output.subtitle_codec_opts.clone())?;
1370 let format_opts = convert_options(output.format_opts.clone())?;
1371
1372 let mux = Muxer::new(
1373 url,
1374 output.url.is_none(),
1375 out_fmt_ctx,
1376 output.frame_pipelines.take(),
1377 output.stream_maps.clone(),
1378 output.video_codec.clone(),
1379 output.audio_codec.clone(),
1380 output.subtitle_codec.clone(),
1381 output.start_time_us,
1382 recording_time_us,
1383 output.framerate,
1384 output.vsync_method,
1385 output.bits_per_raw_sample,
1386 output.audio_sample_rate,
1387 output.audio_channels,
1388 output.audio_sample_fmt,
1389 output.video_qscale,
1390 output.audio_qscale,
1391 output.max_video_frames,
1392 output.max_audio_frames,
1393 output.max_subtitle_frames,
1394 video_codec_opts,
1395 audio_codec_opts,
1396 subtitle_codec_opts,
1397 format_opts,
1398 copy_ts
1399 );
1400
1401 Ok(mux)
1402}
1403
1404fn get_format(format_option: &Option<String>) -> Result<*const AVOutputFormat> {
1405 match format_option {
1406 None => Ok(null()),
1407 Some(format_str) => unsafe {
1408 let mut format_cstr = CString::new(format_str.to_string())?;
1409 let mut format = av_guess_format(format_cstr.as_ptr(), null(), null());
1410 if format.is_null() {
1411 format_cstr = CString::new(format!("tmp.{format_str}"))?;
1412 format = av_guess_format(null(), format_cstr.as_ptr(), null());
1413 }
1414 if format.is_null() {
1415 return Err(OpenOutputError::FormatUnsupported(format_str.to_string()).into());
1416 }
1417 Ok(format)
1418 },
1419 }
1420}
1421
1422unsafe fn output_requires_seek(fmt_ctx: *mut AVFormatContext) -> bool {
1423 if fmt_ctx.is_null() {
1424 return false;
1425 }
1426
1427 let mut format_name = "unknown".to_string();
1428
1429 if !(*fmt_ctx).oformat.is_null() {
1430 let oformat = (*fmt_ctx).oformat;
1431 format_name = CStr::from_ptr((*oformat).name)
1432 .to_string_lossy()
1433 .into_owned();
1434 let flags = (*oformat).flags;
1435 let no_file = flags & AVFMT_NOFILE as i32 != 0;
1436 let global_header = flags & AVFMT_GLOBALHEADER as i32 != 0;
1437
1438 log::debug!(
1439 "Output format '{format_name}' - No file: {}, Global header: {}",
1440 if no_file { "True" } else { "False" },
1441 if global_header { "True" } else { "False" }
1442 );
1443
1444 let format_names: Vec<&str> = format_name.split(',').collect();
1446 if format_names
1447 .iter()
1448 .any(|&f| matches!(f, "mp4" | "mov" | "mkv" | "avi" | "flac" | "ogg" | "webm"))
1449 {
1450 log::debug!("Output format '{format_name}' typically requires seeking.");
1451 return true;
1452 }
1453
1454 if format_names.iter().any(|&f| {
1456 matches!(
1457 f,
1458 "mpegts" | "hls" | "m3u8" | "udp" | "rtp" | "rtp_mpegts" | "http" | "srt"
1459 )
1460 }) {
1461 log::debug!("Output format '{format_name}' does not typically require seeking.");
1462 return false;
1463 }
1464
1465 if format_name == "flv" {
1467 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'.");
1468 return false;
1469 }
1470
1471 if no_file {
1473 log::debug!(
1474 "Output format '{format_name}' uses AVFMT_NOFILE. Seeking is likely unnecessary."
1475 );
1476 return false;
1477 }
1478
1479 if global_header {
1481 log::debug!(
1482 "Output format '{format_name}' uses AVFMT_GLOBALHEADER. Seeking may be required."
1483 );
1484 return true;
1485 }
1486 } else {
1487 log::debug!("Output format is null. Cannot determine if seeking is required.");
1488 }
1489
1490 log::debug!("Output format '{format_name}' does not match any known rules. Assuming seeking is not required.");
1492 false
1493}
1494
1495struct InputOpaque {
1496 read: Box<dyn FnMut(&mut [u8]) -> i32>,
1497 seek: Option<Box<dyn FnMut(i64, i32) -> i64>>,
1498}
1499
1500#[allow(dead_code)]
1501struct OutputOpaque {
1502 write: Box<dyn FnMut(&[u8]) -> i32>,
1503 seek: Option<Box<dyn FnMut(i64, i32) -> i64>>,
1504}
1505
1506unsafe extern "C" fn write_packet_wrapper(
1507 opaque: *mut libc::c_void,
1508 buf: *const u8,
1509 buf_size: libc::c_int,
1510) -> libc::c_int {
1511 if buf.is_null() {
1512 return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO);
1513 }
1514 let closure = &mut *(opaque as *mut Box<dyn FnMut(&[u8]) -> i32>);
1515
1516 let slice = std::slice::from_raw_parts(buf, buf_size as usize);
1517
1518 (*closure)(slice)
1519}
1520
1521unsafe extern "C" fn read_packet_wrapper(
1522 opaque: *mut libc::c_void,
1523 buf: *mut u8,
1524 buf_size: libc::c_int,
1525) -> libc::c_int {
1526 if buf.is_null() {
1527 return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO);
1528 }
1529
1530 let context = &mut *(opaque as *mut InputOpaque);
1531
1532 let slice = std::slice::from_raw_parts_mut(buf, buf_size as usize);
1533
1534 (context.read)(slice)
1535}
1536
1537unsafe extern "C" fn seek_packet_wrapper(
1538 opaque: *mut libc::c_void,
1539 offset: i64,
1540 whence: libc::c_int,
1541) -> i64 {
1542 let context = &mut *(opaque as *mut InputOpaque);
1543
1544 if let Some(seek_func) = &mut context.seek {
1545 (*seek_func)(offset, whence)
1546 } else {
1547 ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE) as i64
1548 }
1549}
1550
1551fn fg_bind_inputs(filter_graphs: &mut Vec<FilterGraph>, demuxs: &mut Vec<Demuxer>) -> Result<()> {
1552 if filter_graphs.is_empty() {
1553 return Ok(());
1554 }
1555 bind_fg_inputs_by_fg(filter_graphs)?;
1556
1557 for filter_graph in filter_graphs.iter_mut() {
1558 for i in 0..filter_graph.inputs.len() {
1559 fg_complex_bind_input(
1560 filter_graph,
1561 i,
1562 demuxs,
1563 )?;
1564 }
1565 }
1566
1567 Ok(())
1568}
1569
1570struct FilterLabel {
1571 linklabel: String,
1572 media_type: AVMediaType,
1573}
1574
1575fn bind_fg_inputs_by_fg(filter_graphs: &mut Vec<FilterGraph>) -> Result<()> {
1576 let fg_labels = filter_graphs
1577 .iter()
1578 .map(|filter_graph| {
1579 let inputs = filter_graph
1580 .inputs
1581 .iter()
1582 .map(|input| FilterLabel {
1583 linklabel: input.linklabel.clone(),
1584 media_type: input.media_type,
1585 })
1586 .collect::<Vec<_>>();
1587 let outputs = filter_graph
1588 .outputs
1589 .iter()
1590 .map(|output| FilterLabel {
1591 linklabel: output.linklabel.clone(),
1592 media_type: output.media_type,
1593 })
1594 .collect::<Vec<_>>();
1595 (inputs, outputs)
1596 })
1597 .collect::<Vec<_>>();
1598
1599 for (i, (inputs, _outputs)) in fg_labels.iter().enumerate() {
1600 for input_filter_label in inputs.iter() {
1601 if input_filter_label.linklabel.is_empty() {
1602 continue;
1603 }
1604
1605 'outer: for (j, (_inputs, outputs)) in fg_labels.iter().enumerate() {
1606 if i == j {
1607 continue;
1608 }
1609
1610 for (output_idx, output_filter_label) in outputs.iter().enumerate() {
1611 if output_filter_label.linklabel != input_filter_label.linklabel {
1612 continue;
1613 }
1614 if output_filter_label.media_type != input_filter_label.media_type {
1615 warn!(
1616 "Tried to connect {:?} output to {:?} input",
1617 output_filter_label.media_type, input_filter_label.media_type
1618 );
1619 return Err(FilterGraphParseError::InvalidArgument.into());
1620 }
1621
1622 {
1623 let filter_graph = &filter_graphs[j];
1624 let output_filter = &filter_graph.outputs[output_idx];
1625 if output_filter.has_dst() {
1626 continue;
1627 }
1628
1629 }
1630
1631 let (sender, finished_flag_list) = {
1632 let filter_graph = &mut filter_graphs[i];
1633 filter_graph.get_src_sender()
1634 };
1635
1636 {
1637 let filter_graph = &mut filter_graphs[j];
1638 filter_graph.outputs[output_idx].set_dst(sender);
1639 filter_graph.outputs[output_idx].fg_input_index = i;
1640 filter_graph.outputs[output_idx].finished_flag_list = finished_flag_list;
1641 }
1642
1643 break 'outer;
1644 }
1645 }
1646 }
1647 }
1648 Ok(())
1649}
1650
1651fn fg_complex_bind_input(
1652 filter_graph: &mut FilterGraph,
1653 input_filter_index: usize,
1654 demuxs: &mut Vec<Demuxer>,
1655) -> Result<()> {
1656 let graph_desc = &filter_graph.graph_desc;
1657 let input_filter = &mut filter_graph.inputs[input_filter_index];
1658 let (demux_idx, stream_idx) =
1659 if !input_filter.linklabel.is_empty() && input_filter.linklabel != "in" {
1660 let (demux_idx, stream_idx) = fg_find_input_idx_by_linklabel(
1661 &input_filter.linklabel,
1662 input_filter.media_type,
1663 demuxs,
1664 graph_desc,
1665 )?;
1666
1667 info!(
1668 "Binding filter input with label '{}' to input stream {stream_idx}:{demux_idx}",
1669 input_filter.linklabel
1670 );
1671 (demux_idx, stream_idx)
1672 } else {
1673 let mut demux_idx = -1i32;
1674 let mut stream_idx = 0;
1675 for (d_idx, demux) in demuxs.iter().enumerate() {
1676 for (st_idx, intput_stream) in demux.get_streams().iter().enumerate() {
1677 if intput_stream.is_used() {
1678 continue;
1679 }
1680 if intput_stream.codec_type == input_filter.media_type {
1681 demux_idx = d_idx as i32;
1682 stream_idx = st_idx;
1683 break;
1684 }
1685 }
1686 if demux_idx >= 0 {
1687 break;
1688 }
1689 }
1690
1691 if demux_idx < 0 {
1692 warn!("Cannot find a matching stream for unlabeled input pad {}", input_filter.name);
1693 return Err(FilterGraphParseError::InvalidArgument.into());
1694 }
1695
1696 debug!("FilterGraph binding unlabeled input {input_filter_index} to input stream {stream_idx}:{demux_idx}");
1697
1698 (demux_idx as usize, stream_idx)
1699 };
1700
1701 let demux = &mut demuxs[demux_idx];
1702
1703 ifilter_bind_ist(filter_graph, input_filter_index, stream_idx, demux)
1704}
1705
1706#[cfg(feature = "docs-rs")]
1707fn ifilter_bind_ist(
1708 filter_graph: &mut FilterGraph,
1709 input_index: usize,
1710 stream_idx: usize,
1711 demux: &mut Demuxer,
1712) -> Result<()> {
1713 Ok(())
1714}
1715
1716#[cfg(not(feature = "docs-rs"))]
1717fn ifilter_bind_ist(
1718 filter_graph: &mut FilterGraph,
1719 input_index: usize,
1720 stream_idx: usize,
1721 demux: &mut Demuxer,
1722) -> Result<()> {
1723 unsafe {
1724 let input_filter = &mut filter_graph.inputs[input_index];
1725 let ist = *(*demux.in_fmt_ctx).streams.add(stream_idx);
1726 let par = (*ist).codecpar;
1727 if (*par).codec_type == AVMEDIA_TYPE_VIDEO {
1728 let framerate = av_guess_frame_rate(demux.in_fmt_ctx, ist, null_mut());
1729 input_filter.opts.framerate = framerate;
1730 } else if (*par).codec_type == AVMEDIA_TYPE_SUBTITLE {
1731 input_filter.opts.sub2video_width = (*par).width;
1732 input_filter.opts.sub2video_height = (*par).height;
1733
1734 if input_filter.opts.sub2video_width <= 0 || input_filter.opts.sub2video_height <= 0 {
1735 let nb_streams = (*demux.in_fmt_ctx).nb_streams;
1736 for j in 0..nb_streams {
1737 let par1 = (**(*demux.in_fmt_ctx).streams.add(j as usize)).codecpar;
1738 if (*par1).codec_type == AVMEDIA_TYPE_VIDEO {
1739 input_filter.opts.sub2video_width =
1740 std::cmp::max(input_filter.opts.sub2video_width, (*par1).width);
1741 input_filter.opts.sub2video_height =
1742 std::cmp::max(input_filter.opts.sub2video_height, (*par1).height);
1743 }
1744 }
1745 }
1746
1747 if input_filter.opts.sub2video_width <= 0 || input_filter.opts.sub2video_height <= 0 {
1748 input_filter.opts.sub2video_width =
1749 std::cmp::max(input_filter.opts.sub2video_width, 720);
1750 input_filter.opts.sub2video_height =
1751 std::cmp::max(input_filter.opts.sub2video_height, 576);
1752 }
1753
1754 demux.get_stream_mut(stream_idx).have_sub2video = true;
1755 }
1756
1757 let dec_ctx = {
1758 let input_stream = demux.get_stream_mut(stream_idx);
1759 avcodec_alloc_context3(input_stream.codec.as_ptr())
1760 };
1761 if dec_ctx.is_null() {
1762 return Err(FilterGraphParseError::OutOfMemory.into());
1763 }
1764 let _codec_ctx = CodecContext::new(dec_ctx);
1765
1766 let fallback = input_filter.opts.fallback.as_mut_ptr();
1767 if (*dec_ctx).codec_type == AVMEDIA_TYPE_AUDIO {
1768 (*fallback).format = (*dec_ctx).sample_fmt as i32;
1769 (*fallback).sample_rate = (*dec_ctx).sample_rate;
1770
1771 let ret = av_channel_layout_copy(&mut (*fallback).ch_layout, &(*dec_ctx).ch_layout);
1772 if ret < 0 {
1773 return Err(FilterGraphParseError::from(ret).into());
1774 }
1775 } else if (*dec_ctx).codec_type == AVMEDIA_TYPE_VIDEO {
1776 (*fallback).format = (*dec_ctx).pix_fmt as i32;
1777 (*fallback).width = (*dec_ctx).width;
1778 (*fallback).height = (*dec_ctx).height;
1779 (*fallback).sample_aspect_ratio = (*dec_ctx).sample_aspect_ratio;
1780 (*fallback).colorspace = (*dec_ctx).colorspace;
1781 (*fallback).color_range = (*dec_ctx).color_range;
1782 }
1783 (*fallback).time_base = (*dec_ctx).pkt_timebase;
1784
1785 input_filter.opts.flags |= IFILTER_FLAG_AUTOROTATE;
1787
1788 let tsoffset = if demux.copy_ts {
1789 let mut tsoffset = if demux.start_time_us.is_some() {
1790 demux.start_time_us.unwrap()
1791 } else {
1792 0
1793 };
1794 if (*demux.in_fmt_ctx).start_time != ffmpeg_sys_next::AV_NOPTS_VALUE {
1795 tsoffset += (*demux.in_fmt_ctx).start_time
1796 }
1797 tsoffset
1798 } else {
1799 0
1800 };
1801 if (*demux.in_fmt_ctx).start_time != ffmpeg_sys_next::AV_NOPTS_VALUE {
1802 input_filter.opts.trim_start_us = Some(tsoffset);
1803 }
1804 input_filter.opts.trim_end_us = demux.recording_time_us;
1805
1806 let (sender, finished_flag_list) = filter_graph.get_src_sender();
1807 {
1808 let input_stream = demux.get_stream_mut(stream_idx);
1809 input_stream.add_fg_dst(sender, input_index, finished_flag_list);
1810 };
1811
1812 let node = Arc::make_mut(&mut filter_graph.node);
1813 let SchNode::Filter { inputs, .. } = node else {
1814 unreachable!()
1815 };
1816 inputs.insert(input_index, demux.node.clone());
1817
1818
1819 demux.connect_stream(stream_idx);
1820 Ok(())
1821 }
1822}
1823
1824fn fg_find_input_idx_by_linklabel(
1825 linklabel: &str,
1826 filter_media_type: AVMediaType,
1827 demuxs: &mut Vec<Demuxer>,
1828 desc: &str,
1829) -> Result<(usize, usize)> {
1830 let new_linklabel = if linklabel.starts_with("[") && linklabel.ends_with("]") {
1831 if linklabel.len() <= 2 {
1832 warn!("Filter linklabel is empty");
1833 return Err(InvalidFilterSpecifier(desc.to_string()).into());
1834 } else {
1835 &linklabel[1..linklabel.len() - 1]
1836 }
1837 } else {
1838 linklabel
1839 };
1840
1841 let (file_idx, remainder) = strtol(new_linklabel)?;
1842 if file_idx < 0 || file_idx as usize >= demuxs.len() {
1843 return Err(InvalidFileIndexInFg(file_idx as usize, desc.to_string()).into());
1844 }
1845
1846 let (media_type, _allow_unused) = stream_specifier_parse(remainder)?;
1847
1848 if media_type != filter_media_type {
1849 warn!("Invalid stream label: {linklabel}");
1850 return Err(FilterGraphParseError::InvalidArgument.into());
1851 }
1852
1853 let demux = &demuxs[file_idx as usize];
1854
1855 let mut stream_idx = -1i32;
1856
1857 for (idx, dec_stream) in demux.get_streams().iter().enumerate() {
1858 if (*dec_stream).codec_type == media_type {
1859 stream_idx = idx as i32;
1860 break;
1861 }
1862 }
1863
1864 if stream_idx < 0 {
1865 warn!(
1866 "Stream specifier '{remainder}' in filtergraph description {desc} matches no streams."
1867 );
1868 return Err(FilterGraphParseError::InvalidArgument.into());
1869 }
1870 Ok((file_idx as usize, stream_idx as usize))
1871}
1872
1873fn stream_specifier_parse(specifier: &str) -> Result<(AVMediaType, bool)> {
1874 let specifier = if specifier.starts_with(':') {
1875 &specifier[1..]
1876 } else {
1877 specifier
1878 };
1879
1880 match specifier {
1881 "v" => Ok((AVMEDIA_TYPE_VIDEO, false)),
1882 "a" => Ok((AVMEDIA_TYPE_AUDIO, false)),
1883 "s" => Ok((AVMEDIA_TYPE_SUBTITLE, false)),
1884 "d" => Ok((AVMEDIA_TYPE_DATA, false)),
1885 "t" => Ok((AVMEDIA_TYPE_ATTACHMENT, false)),
1886 "v?" => Ok((AVMEDIA_TYPE_VIDEO, true)),
1887 "a?" => Ok((AVMEDIA_TYPE_AUDIO, true)),
1888 "s?" => Ok((AVMEDIA_TYPE_SUBTITLE, true)),
1889 "d?" => Ok((AVMEDIA_TYPE_DATA, true)),
1890 "t?" => Ok((AVMEDIA_TYPE_ATTACHMENT, true)),
1891 _ => Err(InvalidFilterSpecifier(specifier.to_string()).into()),
1893 }
1894}
1895
1896fn strtol(input: &str) -> Result<(i64, &str)> {
1898 let mut chars = input.chars().peekable();
1899 let mut negative = false;
1900
1901 if let Some(&ch) = chars.peek() {
1902 if ch == '-' {
1903 negative = true;
1904 chars.next();
1905 } else if !ch.is_digit(10) {
1906 return Err(ParseInteger);
1907 }
1908 }
1909
1910 let number_start = input.len() - chars.clone().collect::<String>().len();
1911
1912 let number_str: String = chars.by_ref().take_while(|ch| ch.is_digit(10)).collect();
1913
1914 if number_str.is_empty() {
1915 return Err(ParseInteger);
1916 }
1917
1918 let number: i64 = number_str.parse().map_err(|_| ParseInteger)?;
1919
1920 let remainder_index = number_start + number_str.len();
1921 let remainder = &input[remainder_index..];
1922
1923 if negative {
1924 Ok((-number, remainder))
1925 } else {
1926 Ok((number, remainder))
1927 }
1928}
1929
1930fn init_filter_graphs(filter_complexs: Vec<FilterComplex>) -> Result<Vec<FilterGraph>> {
1931 let mut filter_graphs = Vec::with_capacity(filter_complexs.len());
1932 for (i, filter) in filter_complexs.iter().enumerate() {
1933 let filter_graph = init_filter_graph(i, &filter.filter_descs, filter.hw_device.clone())?;
1934 filter_graphs.push(filter_graph);
1935 }
1936 Ok(filter_graphs)
1937}
1938
1939
1940#[cfg(feature = "docs-rs")]
1941fn init_filter_graph(
1942 fg_index: usize,
1943 filter_desc: &str,
1944 hw_device: Option<String>,
1945) -> Result<FilterGraph> {
1946 Err(Bug)
1947}
1948
1949#[cfg(not(feature = "docs-rs"))]
1950fn init_filter_graph(
1951 fg_index: usize,
1952 filter_desc: &str,
1953 hw_device: Option<String>,
1954) -> Result<FilterGraph> {
1955 let desc_cstr = CString::new(filter_desc)?;
1956
1957 unsafe {
1958 let mut graph = avfilter_graph_alloc();
1961 (*graph).nb_threads = 1;
1962
1963 let mut seg = null_mut();
1964 let mut ret = avfilter_graph_segment_parse(graph, desc_cstr.as_ptr(), 0, &mut seg);
1965 if ret < 0 {
1966 avfilter_graph_free(&mut graph);
1967 return Err(FilterGraphParseError::from(ret).into());
1968 }
1969
1970 ret = avfilter_graph_segment_create_filters(seg, 0);
1971 if ret < 0 {
1972 avfilter_graph_free(&mut graph);
1973 avfilter_graph_segment_free(&mut seg);
1974 return Err(FilterGraphParseError::from(ret).into());
1975 }
1976
1977 #[cfg(not(feature = "docs-rs"))]
1978 {
1979 ret = graph_opts_apply(seg);
1980 }
1981 if ret < 0 {
1982 avfilter_graph_segment_free(&mut seg);
1983 avfilter_graph_free(&mut graph);
1984 return Err(FilterGraphParseError::from(ret).into());
1985 }
1986
1987 let mut inputs = null_mut();
1988 let mut outputs = null_mut();
1989 ret = avfilter_graph_segment_apply(seg, 0, &mut inputs, &mut outputs);
1990 avfilter_graph_segment_free(&mut seg);
1991
1992 if ret < 0 {
1993 avfilter_inout_free(&mut inputs);
1994 avfilter_inout_free(&mut outputs);
1995 avfilter_graph_free(&mut graph);
1996 return Err(FilterGraphParseError::from(ret).into());
1997 }
1998
1999 let input_filters = inouts_to_input_filters(fg_index, inputs)?;
2000 let output_filters = inouts_to_output_filters(outputs)?;
2001
2002 if output_filters.len() == 0 {
2003 avfilter_inout_free(&mut inputs);
2004 avfilter_inout_free(&mut outputs);
2005 avfilter_graph_free(&mut graph);
2006 return Err(FilterZeroOutputs);
2007 }
2008
2009 let filter_graph = FilterGraph::new(
2010 filter_desc.to_string(),
2011 hw_device,
2012 input_filters,
2013 output_filters,
2014 );
2015
2016 avfilter_inout_free(&mut inputs);
2017 avfilter_inout_free(&mut outputs);
2018 avfilter_graph_free(&mut graph);
2019
2020 Ok(filter_graph)
2021 }
2022}
2023
2024unsafe fn inouts_to_input_filters(
2025 fg_index: usize,
2026 inouts: *mut AVFilterInOut,
2027) -> Result<Vec<InputFilter>> {
2028 let mut cur = inouts;
2029 let mut filterinouts = Vec::new();
2030 let mut filter_index = 0;
2031 while !cur.is_null() {
2032 let linklabel = if (*cur).name.is_null() {
2033 ""
2034 } else {
2035 let linklabel = CStr::from_ptr((*cur).name);
2036 let result = linklabel.to_str();
2037 if let Err(_) = result {
2038 return Err(FilterDescUtf8);
2039 }
2040 result.unwrap()
2041 };
2042
2043 let filter_ctx = (*cur).filter_ctx;
2044 let media_type = avfilter_pad_get_type((*filter_ctx).input_pads, (*cur).pad_idx);
2045
2046 let pads = (*filter_ctx).input_pads;
2047 let nb_pads = (*filter_ctx).nb_inputs;
2048
2049 let name = describe_filter_link(cur, filter_ctx, pads, nb_pads)?;
2050
2051 let fallback = frame_alloc()?;
2052
2053 let mut filter = InputFilter::new(linklabel.to_string(), media_type, name, fallback);
2054 filter.opts.name = format!("fg:{fg_index}:{filter_index}");
2055 filterinouts.push(filter);
2056
2057 cur = (*cur).next;
2058 filter_index += 1;
2059 }
2060 Ok(filterinouts)
2061}
2062
2063unsafe fn inouts_to_output_filters(inouts: *mut AVFilterInOut) -> Result<Vec<OutputFilter>> {
2064 let mut cur = inouts;
2065 let mut output_filters = Vec::new();
2066 while !cur.is_null() {
2067 let linklabel = if (*cur).name.is_null() {
2068 ""
2069 } else {
2070 let linklabel = CStr::from_ptr((*cur).name);
2071 let result = linklabel.to_str();
2072 if let Err(_) = result {
2073 return Err(FilterDescUtf8);
2074 }
2075 result.unwrap()
2076 };
2077
2078 let filter_ctx = (*cur).filter_ctx;
2079 let media_type = avfilter_pad_get_type((*filter_ctx).output_pads, (*cur).pad_idx);
2080
2081 let pads = (*filter_ctx).output_pads;
2082 let nb_pads = (*filter_ctx).nb_outputs;
2083
2084 let name = describe_filter_link(cur, filter_ctx, pads, nb_pads)?;
2085
2086 let filter = OutputFilter::new(linklabel.to_string(), media_type, name);
2087 output_filters.push(filter);
2088
2089 cur = (*cur).next;
2090 }
2091 Ok(output_filters)
2092}
2093
2094unsafe fn describe_filter_link(
2095 cur: *mut AVFilterInOut,
2096 filter_ctx: *mut AVFilterContext,
2097 pads: *mut AVFilterPad,
2098 nb_pads: c_uint,
2099) -> Result<String> {
2100 let filter = (*filter_ctx).filter;
2101 let name = (*filter).name;
2102 let name = CStr::from_ptr(name);
2103 let result = name.to_str();
2104 if let Err(_) = result {
2105 return Err(FilterNameUtf8);
2106 }
2107 let name = result.unwrap();
2108
2109 let name = if nb_pads > 1 {
2110 name.to_string()
2111 } else {
2112 let pad_name = avfilter_pad_get_name(pads, (*cur).pad_idx);
2113 let pad_name = CStr::from_ptr(pad_name);
2114 let result = pad_name.to_str();
2115 if let Err(_) = result {
2116 return Err(FilterNameUtf8);
2117 }
2118 let pad_name = result.unwrap();
2119 format!("{name}:{pad_name}")
2120 };
2121 Ok(name)
2122}
2123
2124fn open_input_files(inputs: &mut Vec<Input>, copy_ts: bool) -> Result<Vec<Demuxer>> {
2125 let mut demuxs = Vec::new();
2126 for (i, input) in inputs.iter_mut().enumerate() {
2127 unsafe {
2128 let result = open_input_file(i, input, copy_ts);
2129 if let Err(e) = result {
2130 free_input_av_format_context(demuxs);
2131 return Err(e);
2132 }
2133 let demux = result.unwrap();
2134 demuxs.push(demux)
2135 }
2136 }
2137 Ok(demuxs)
2138}
2139
2140unsafe fn free_input_av_format_context(demuxs: Vec<Demuxer>) {
2141 for mut demux in demuxs {
2142 avformat_close_input(&mut demux.in_fmt_ctx);
2143 }
2144}
2145
2146#[cfg(feature = "docs-rs")]
2147unsafe fn open_input_file(
2148 index: usize,
2149 input: &mut Input,
2150) -> Result<Demuxer> {
2151 Err(Bug)
2152}
2153
2154#[cfg(not(feature = "docs-rs"))]
2155unsafe fn open_input_file(
2156 index: usize,
2157 input: &mut Input,
2158 copy_ts: bool
2159) -> Result<Demuxer> {
2160 let mut in_fmt_ctx = avformat_alloc_context();
2161 if in_fmt_ctx.is_null() {
2162 return Err(OpenInputError::OutOfMemory.into());
2163 }
2164
2165 let recording_time_us = match input.stop_time_us {
2166 None => input.recording_time_us,
2167 Some(stop_time_us) => {
2168 let start_time_us = input.start_time_us.unwrap_or_else(|| 0);
2169 if stop_time_us <= start_time_us {
2170 error!("stop_time_us value smaller than start_time_us; aborting.");
2171 return Err(OpenOutputError::InvalidArgument.into());
2172 } else {
2173 Some(stop_time_us - start_time_us)
2174 }
2175 }
2176 };
2177
2178 match &input.url {
2179 None => {
2180 if input.read_callback.is_none() {
2181 error!("input url and read_callback is none.");
2182 return Err(OpenInputError::InvalidSource.into());
2183 }
2184
2185 let avio_ctx_buffer_size = 1024 * 64;
2186 let mut avio_ctx_buffer = av_malloc(avio_ctx_buffer_size);
2187 if avio_ctx_buffer.is_null() {
2188 avformat_close_input(&mut in_fmt_ctx);
2189 return Err(OpenInputError::OutOfMemory.into());
2190 }
2191
2192 let have_seek_callback = input.seek_callback.is_some();
2193 let input_opaque = Box::new(InputOpaque {
2194 read: input.read_callback.take().unwrap(),
2195 seek: input.seek_callback.take(),
2196 });
2197 let opaque = Box::into_raw(input_opaque) as *mut libc::c_void;
2198
2199 let mut avio_ctx = avio_alloc_context(
2200 avio_ctx_buffer as *mut libc::c_uchar,
2201 avio_ctx_buffer_size as i32,
2202 0,
2203 opaque,
2204 Some(read_packet_wrapper),
2205 None,
2206 if have_seek_callback {
2207 Some(seek_packet_wrapper)
2208 } else {
2209 None
2210 },
2211 );
2212 if avio_ctx.is_null() {
2213 av_freep(&mut avio_ctx_buffer as *mut _ as *mut c_void);
2214 avformat_close_input(&mut in_fmt_ctx);
2215 return Err(OpenInputError::OutOfMemory.into());
2216 }
2217
2218 (*in_fmt_ctx).pb = avio_ctx;
2219 (*in_fmt_ctx).flags = AVFMT_FLAG_CUSTOM_IO;
2220
2221 let ret = avformat_open_input(&mut in_fmt_ctx, null(), null(), null_mut());
2222 if ret < 0 {
2223 av_freep(&mut (*avio_ctx).buffer as *mut _ as *mut c_void);
2224 avio_context_free(&mut avio_ctx);
2225 avformat_close_input(&mut in_fmt_ctx);
2226 return Err(OpenInputError::from(ret).into());
2227 }
2228
2229 let ret = avformat_find_stream_info(in_fmt_ctx, null_mut());
2230 if ret < 0 {
2231 av_freep(&mut (*avio_ctx).buffer as *mut _ as *mut c_void);
2232 avio_context_free(&mut avio_ctx);
2233 avformat_close_input(&mut in_fmt_ctx);
2234 return Err(FindStreamError::from(ret).into());
2235 }
2236
2237 if !have_seek_callback && input_requires_seek(in_fmt_ctx) {
2238 av_freep(&mut (*avio_ctx).buffer as *mut _ as *mut c_void);
2239 avio_context_free(&mut avio_ctx);
2240 avformat_close_input(&mut in_fmt_ctx);
2241 warn!("The input format supports seeking, but no seek callback is provided. This may cause issues.");
2242 return Err(OpenInputError::SeekFunctionMissing.into());
2243 }
2244 }
2245 Some(url) => {
2246 let file_iformat = if let Some(format) = &input.format {
2247 let format_cstr = CString::new(format.clone())?;
2248
2249 let file_iformat = ffmpeg_sys_next::av_find_input_format(format_cstr.as_ptr());
2250 if file_iformat.is_null() {
2251 error!("Unknown input format: '{format}'");
2252 return Err(OpenInputError::InvalidFormat(format.clone()).into());
2253 }
2254 file_iformat
2255 } else {
2256 null()
2257 };
2258
2259 let url_cstr = CString::new(url.as_str())?;
2260
2261 let format_opts = convert_options(input.format_opts.clone())?;
2262 let mut format_opts = hashmap_to_avdictionary(&format_opts);
2263 let scan_all_pmts_key = CString::new("scan_all_pmts")?;
2264 if ffmpeg_sys_next::av_dict_get(format_opts, scan_all_pmts_key.as_ptr(), null(), ffmpeg_sys_next::AV_DICT_MATCH_CASE).is_null() {
2265 let scan_all_pmts_value = CString::new("1")?;
2266 ffmpeg_sys_next::av_dict_set(&mut format_opts, scan_all_pmts_key.as_ptr(), scan_all_pmts_value.as_ptr(), ffmpeg_sys_next::AV_DICT_DONT_OVERWRITE);
2267 };
2268 (*in_fmt_ctx).flags |= ffmpeg_sys_next::AVFMT_FLAG_NONBLOCK;
2269
2270 let mut ret =
2271 avformat_open_input(&mut in_fmt_ctx, url_cstr.as_ptr(), file_iformat, &mut format_opts);
2272 av_dict_free(&mut format_opts);
2273 if ret < 0 {
2274 avformat_close_input(&mut in_fmt_ctx);
2275 return Err(OpenInputError::from(ret).into());
2276 }
2277
2278 ret = avformat_find_stream_info(in_fmt_ctx, null_mut());
2279 if ret < 0 {
2280 avformat_close_input(&mut in_fmt_ctx);
2281 return Err(FindStreamError::from(ret).into());
2282 }
2283 }
2284 }
2285
2286 let mut timestamp = input.start_time_us.unwrap_or(0);
2287 if (*in_fmt_ctx).start_time != ffmpeg_sys_next::AV_NOPTS_VALUE {
2289 timestamp += (*in_fmt_ctx).start_time;
2290 }
2291
2292 if let Some(start_time_us) = input.start_time_us {
2294 let mut seek_timestamp = timestamp;
2295
2296 if (*(*in_fmt_ctx).iformat).flags & ffmpeg_sys_next::AVFMT_SEEK_TO_PTS == 0 {
2297 let mut dts_heuristic = false;
2298 let stream_count = (*in_fmt_ctx).nb_streams;
2299
2300 for i in 0..stream_count {
2301 let stream = *(*in_fmt_ctx).streams.add(i as usize);
2302 let par = (*stream).codecpar;
2303 if (*par).video_delay != 0 {
2304 dts_heuristic = true;
2305 break;
2306 }
2307 }
2308 if dts_heuristic {
2309 seek_timestamp -= 3 * AV_TIME_BASE as i64 / 23;
2310 }
2311 }
2312 let ret = ffmpeg_sys_next::avformat_seek_file(in_fmt_ctx, -1, i64::MIN, seek_timestamp, seek_timestamp, 0);
2313 if ret < 0 {
2314 warn!("could not seek to position {:.3}", start_time_us as f64 / AV_TIME_BASE as f64);
2315 }
2316 }
2317
2318 let url = input
2319 .url
2320 .clone()
2321 .unwrap_or_else(|| format!("read_callback[{index}]"));
2322
2323
2324 let demux = Demuxer::new(
2325 url,
2326 input.url.is_none(),
2327 in_fmt_ctx,
2328 0 - if copy_ts { 0 } else { timestamp },
2329 input.frame_pipelines.take(),
2330 input.video_codec.clone(),
2331 input.audio_codec.clone(),
2332 input.subtitle_codec.clone(),
2333 input.readrate,
2334 input.start_time_us,
2335 recording_time_us,
2336 input.exit_on_error,
2337 input.stream_loop,
2338 input.hwaccel.clone(),
2339 input.hwaccel_device.clone(),
2340 input.hwaccel_output_format.clone(),
2341 copy_ts
2342 )?;
2343
2344 Ok(demux)
2345}
2346
2347fn convert_options(
2348 opts: Option<HashMap<String, String>>,
2349) -> Result<Option<HashMap<CString, CString>>> {
2350 if opts.is_none() {
2351 return Ok(None);
2352 }
2353
2354 let converted = opts.map(|map| {
2355 map.into_iter()
2356 .map(|(k, v)| Ok((CString::new(k)?, CString::new(v)?)))
2357 .collect::<Result<HashMap<CString, CString>, _>>() });
2359
2360 converted.transpose() }
2362
2363unsafe fn input_requires_seek(fmt_ctx: *mut AVFormatContext) -> bool {
2364 if fmt_ctx.is_null() {
2365 return false;
2366 }
2367
2368 let mut format_name = "unknown".to_string();
2369 let mut format_names: Vec<&str> = Vec::with_capacity(0);
2370
2371 if !(*fmt_ctx).iformat.is_null() {
2372 let iformat = (*fmt_ctx).iformat;
2373 format_name = CStr::from_ptr((*iformat).name)
2374 .to_string_lossy()
2375 .into_owned();
2376 let flags = (*iformat).flags;
2377 let no_binsearch = flags & AVFMT_NOBINSEARCH as i32 != 0;
2378 let no_gensearch = flags & AVFMT_NOGENSEARCH as i32 != 0;
2379
2380 log::debug!(
2381 "Input format '{format_name}' - Binary search: {}, Generic search: {}",
2382 if no_binsearch { "Disabled" } else { "Enabled" },
2383 if no_gensearch { "Disabled" } else { "Enabled" }
2384 );
2385
2386 format_names = format_name.split(',').collect();
2387
2388 if format_names.iter().any(|&f| {
2389 matches!(
2390 f,
2391 "mp4" | "mkv" | "avi" | "mov" | "flac" | "wav" | "aac" | "ogg" | "mp3" | "webm"
2392 )
2393 }) {
2394 if !no_binsearch && !no_gensearch {
2395 return true;
2396 }
2397 }
2398
2399 if format_names.iter().any(|&f| {
2400 matches!(
2401 f,
2402 "hls" | "m3u8" | "mpegts" | "mms" | "udp" | "rtp" | "rtp_mpegts" | "http" | "srt"
2403 )
2404 }) {
2405 log::debug!("Live stream detected ({format_name}). Seeking is not possible.");
2406 return false;
2407 }
2408
2409 if no_binsearch && no_gensearch {
2410 log::debug!("Input format '{format_name}' has both NOBINSEARCH and NOGENSEARCH set. Seeking is likely restricted.");
2411 }
2412 }
2413
2414 let format_duration = (*fmt_ctx).duration;
2415
2416 if format_names.iter().any(|&f| f == "flv") {
2417 if format_duration <= 0 {
2418 log::debug!(
2419 "Input format 'flv' detected with no valid duration. Seeking is not possible."
2420 );
2421 } else {
2422 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.");
2423 }
2424 return false;
2425 }
2426
2427 if format_duration > 0 {
2428 log::debug!("Format '{format_name}' has a duration of {format_duration}. Seeking is likely possible.");
2429 return true;
2430 }
2431
2432 let mut video_stream_index = -1;
2433 for i in 0..(*fmt_ctx).nb_streams {
2434 let stream = *(*fmt_ctx).streams.offset(i as isize);
2435 if (*stream).codecpar.is_null() {
2436 continue;
2437 }
2438 if (*(*stream).codecpar).codec_type == AVMEDIA_TYPE_VIDEO {
2439 video_stream_index = i as i32;
2440 break;
2441 }
2442 }
2443
2444 let stream_index = if video_stream_index >= 0 {
2445 video_stream_index
2446 } else {
2447 -1
2448 };
2449
2450 let original_pos = if !(*fmt_ctx).pb.is_null() {
2451 (*(*fmt_ctx).pb).pos
2452 } else {
2453 -1
2454 };
2455
2456 if original_pos >= 0 {
2457 let seek_target = 1 * AV_TIME_BASE as i64;
2458 let seek_result = av_seek_frame(fmt_ctx, stream_index, seek_target, AVSEEK_FLAG_BACKWARD);
2459
2460 if seek_result >= 0 {
2461 log::debug!("Seek test successful.");
2462
2463 (*(*fmt_ctx).pb).pos = original_pos;
2464 avformat_flush(fmt_ctx);
2465 log::debug!("Restored fmt_ctx.pb.pos to {original_pos} and flushed format context.",);
2466 return true;
2467 } else {
2468 log::debug!("Seek test failed (return code {seek_result}). This format likely does not support seeking.");
2469 }
2470 }
2471
2472 false
2473}
2474
2475#[cfg(test)]
2476mod tests {
2477 use std::ffi::{CStr, CString};
2478 use std::ptr::null_mut;
2479
2480 use crate::core::context::ffmpeg_context::{strtol, FfmpegContext, Output};
2481 use ffmpeg_sys_next::{
2482 avfilter_graph_alloc, avfilter_graph_free, avfilter_graph_parse_ptr, avfilter_inout_free,
2483 };
2484
2485 #[test]
2486 fn test_filter() {
2487 let desc_cstr = CString::new("[1:v][2:v]concat=n=2:v=1:a=0[vout]").unwrap();
2488 unsafe {
2491 let mut graph = avfilter_graph_alloc();
2492 let mut inputs = null_mut();
2493 let mut outputs = null_mut();
2494
2495 let ret = avfilter_graph_parse_ptr(
2496 graph,
2497 desc_cstr.as_ptr(),
2498 &mut inputs,
2499 &mut outputs,
2500 null_mut(),
2501 );
2502 if ret < 0 {
2503 avfilter_inout_free(&mut inputs);
2504 avfilter_inout_free(&mut outputs);
2505 avfilter_graph_free(&mut graph);
2506 println!("err ret:{}", crate::util::ffmpeg_utils::av_err2str(ret));
2507 return;
2508 }
2509
2510 println!("inputs.is_null:{}", inputs.is_null());
2511 println!("outputs.is_null:{}", outputs.is_null());
2512
2513 let mut cur = inputs;
2514 while !cur.is_null() {
2515 let input_name = CStr::from_ptr((*cur).name);
2516 println!("Input name: {}", input_name.to_str().unwrap());
2517 cur = (*cur).next;
2518 }
2519
2520 let output_name = CStr::from_ptr((*outputs).name);
2521 println!("Output name: {}", output_name.to_str().unwrap());
2522
2523 let filter_ctx = (*outputs).filter_ctx;
2524 avfilter_inout_free(&mut outputs);
2525 println!("filter_ctx.is_null:{}", filter_ctx.is_null());
2526 }
2527 }
2528
2529 #[test]
2530 fn test_new() {
2531 let _ = env_logger::builder()
2532 .filter_level(log::LevelFilter::Debug)
2533 .is_test(true)
2534 .try_init();
2535 let _ffmpeg_context = FfmpegContext::new(
2536 vec!["test.mp4".to_string().into()],
2537 vec!["hue=s=0".to_string().into()],
2538 vec!["output.mp4".to_string().into()],
2539 )
2540 .unwrap();
2541 let _ffmpeg_context = FfmpegContext::new(
2542 vec!["test.mp4".into()],
2543 vec!["[0:v]hue=s=0".into()],
2544 vec!["output.mp4".to_string().into()],
2545 )
2546 .unwrap();
2547 let _ffmpeg_context = FfmpegContext::new(
2548 vec!["test.mp4".into()],
2549 vec!["hue=s=0[my-out]".into()],
2550 vec![Output::from("output.mp4").add_stream_map("my-out")],
2551 )
2552 .unwrap();
2553 let _ffmpeg_context = FfmpegContext::new(
2554 vec!["test.mp4".into()],
2555 vec!["hue=s=0".into()],
2556 vec![Output::from("output.mp4").add_stream_map_with_copy("0:v?")],
2557 )
2558 .unwrap();
2559 let result = FfmpegContext::new(
2560 vec!["test.mp4".into()],
2561 vec!["hue=s=0".into()],
2562 vec![Output::from("output.mp4").add_stream_map_with_copy("1:v?")],
2563 );
2564 assert!(result.is_err());
2565 let result = FfmpegContext::new(
2566 vec!["test.mp4".into()],
2567 vec!["hue=s=0[fg-out]".into()],
2568 vec![
2569 Output::from("output.mp4").add_stream_map("my-out?"),
2570 Output::from("output.mp4").add_stream_map("fg-out"),
2571 ],
2572 );
2573 assert!(result.is_err());
2574 let result = FfmpegContext::new(
2576 vec!["test.mp4".into()],
2577 vec!["hue=s=0".into()],
2578 vec![Output::from("output.mp4").add_stream_map_with_copy("1:v")],
2579 );
2580 assert!(result.is_err());
2581 let result = FfmpegContext::new(
2582 vec!["test.mp4".into()],
2583 vec!["hue=s=0[fg-out]".into()],
2584 vec![Output::from("output.mp4").add_stream_map("fg-out?")],
2585 );
2586 assert!(result.is_err());
2587 }
2588
2589 #[test]
2590 fn test_builder() {
2591 let _ = env_logger::builder()
2592 .filter_level(log::LevelFilter::Debug)
2593 .is_test(true)
2594 .try_init();
2595
2596 let context1 = FfmpegContext::builder()
2597 .input("test.mp4")
2598 .filter_desc("hue=s=0")
2599 .output("output.mp4")
2600 .build()
2601 .unwrap();
2602
2603 let context2 = FfmpegContext::builder()
2604 .inputs(vec!["test.mp4"])
2605 .filter_descs(vec!["hue=s=0"])
2606 .outputs(vec!["output.mp4"])
2607 .build()
2608 .unwrap();
2609 }
2610
2611 #[test]
2612 fn test_strtol() {
2613 let input = "-123---abc";
2614 let result = strtol(input);
2615 assert_eq!(result.unwrap(), (-123, "---abc"));
2616
2617 let input = "123---abc";
2618 let result = strtol(input);
2619 assert_eq!(result.unwrap(), (123, "---abc"));
2620
2621 let input = "-123aa";
2622 let result = strtol(input);
2623 assert_eq!(result.unwrap(), (-123, "aa"));
2624
2625 let input = "-aa";
2626 let result = strtol(input);
2627 assert!(result.is_err());
2628
2629 let input = "abc";
2630 let result = strtol(input);
2631 assert!(result.is_err())
2632 }
2633}