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