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