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.max_video_frames,
1280 output.max_audio_frames,
1281 output.max_subtitle_frames,
1282 video_codec_opts,
1283 audio_codec_opts,
1284 subtitle_codec_opts,
1285 format_opts,
1286 );
1287
1288 Ok(mux)
1289}
1290
1291fn get_format(format_option: &Option<String>) -> Result<*const AVOutputFormat> {
1292 match format_option {
1293 None => Ok(null()),
1294 Some(format_str) => unsafe {
1295 let mut format_cstr = CString::new(format_str.to_string())?;
1296 let mut format = av_guess_format(format_cstr.as_ptr(), null(), null());
1297 if format.is_null() {
1298 format_cstr = CString::new(format!("tmp.{format_str}"))?;
1299 format = av_guess_format(null(), format_cstr.as_ptr(), null());
1300 }
1301 if format.is_null() {
1302 return Err(OpenOutputError::FormatUnsupported(format_str.to_string()).into());
1303 }
1304 Ok(format)
1305 },
1306 }
1307}
1308
1309unsafe fn output_requires_seek(fmt_ctx: *mut AVFormatContext) -> bool {
1310 if fmt_ctx.is_null() {
1311 return false;
1312 }
1313
1314 let mut format_name = "unknown".to_string();
1315
1316 if !(*fmt_ctx).oformat.is_null() {
1317 let oformat = (*fmt_ctx).oformat;
1318 format_name = CStr::from_ptr((*oformat).name)
1319 .to_string_lossy()
1320 .into_owned();
1321 let flags = (*oformat).flags;
1322 let no_file = flags & AVFMT_NOFILE as i32 != 0;
1323 let global_header = flags & AVFMT_GLOBALHEADER as i32 != 0;
1324
1325 log::debug!(
1326 "Output format '{format_name}' - No file: {}, Global header: {}",
1327 if no_file { "True" } else { "False" },
1328 if global_header { "True" } else { "False" }
1329 );
1330
1331 let format_names: Vec<&str> = format_name.split(',').collect();
1333 if format_names
1334 .iter()
1335 .any(|&f| matches!(f, "mp4" | "mov" | "mkv" | "avi" | "flac" | "ogg" | "webm"))
1336 {
1337 log::debug!("Output format '{format_name}' typically requires seeking.");
1338 return true;
1339 }
1340
1341 if format_names.iter().any(|&f| {
1343 matches!(
1344 f,
1345 "mpegts" | "hls" | "m3u8" | "udp" | "rtp" | "rtp_mpegts" | "http" | "srt"
1346 )
1347 }) {
1348 log::debug!("Output format '{format_name}' does not typically require seeking.");
1349 return false;
1350 }
1351
1352 if format_name == "flv" {
1354 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'.");
1355 return false;
1356 }
1357
1358 if no_file {
1360 log::debug!(
1361 "Output format '{format_name}' uses AVFMT_NOFILE. Seeking is likely unnecessary."
1362 );
1363 return false;
1364 }
1365
1366 if global_header {
1368 log::debug!(
1369 "Output format '{format_name}' uses AVFMT_GLOBALHEADER. Seeking may be required."
1370 );
1371 return true;
1372 }
1373 } else {
1374 log::debug!("Output format is null. Cannot determine if seeking is required.");
1375 }
1376
1377 log::debug!("Output format '{format_name}' does not match any known rules. Assuming seeking is not required.");
1379 false
1380}
1381
1382struct InputOpaque {
1383 read: Box<dyn FnMut(&mut [u8]) -> i32>,
1384 seek: Option<Box<dyn FnMut(i64, i32) -> i64>>,
1385}
1386
1387#[allow(dead_code)]
1388struct OutputOpaque {
1389 write: Box<dyn FnMut(&[u8]) -> i32>,
1390 seek: Option<Box<dyn FnMut(i64, i32) -> i64>>,
1391}
1392
1393unsafe extern "C" fn write_packet_wrapper(
1394 opaque: *mut libc::c_void,
1395 buf: *const u8,
1396 buf_size: libc::c_int,
1397) -> libc::c_int {
1398 if buf.is_null() {
1399 return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO);
1400 }
1401 let closure = &mut *(opaque as *mut Box<dyn FnMut(&[u8]) -> i32>);
1402
1403 let slice = std::slice::from_raw_parts(buf, buf_size as usize);
1404
1405 (*closure)(slice)
1406}
1407
1408unsafe extern "C" fn read_packet_wrapper(
1409 opaque: *mut libc::c_void,
1410 buf: *mut u8,
1411 buf_size: libc::c_int,
1412) -> libc::c_int {
1413 if buf.is_null() {
1414 return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO);
1415 }
1416
1417 let context = &mut *(opaque as *mut InputOpaque);
1418
1419 let slice = std::slice::from_raw_parts_mut(buf, buf_size as usize);
1420
1421 (context.read)(slice)
1422}
1423
1424unsafe extern "C" fn seek_packet_wrapper(
1425 opaque: *mut libc::c_void,
1426 offset: i64,
1427 whence: libc::c_int,
1428) -> i64 {
1429 let context = &mut *(opaque as *mut InputOpaque);
1430
1431 if let Some(seek_func) = &mut context.seek {
1432 (*seek_func)(offset, whence)
1433 } else {
1434 ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE) as i64
1435 }
1436}
1437
1438fn fg_bind_inputs(filter_graphs: &mut Vec<FilterGraph>, demuxs: &mut Vec<Demuxer>) -> Result<()> {
1439 if filter_graphs.is_empty() {
1440 return Ok(());
1441 }
1442 bind_fg_inputs_by_fg(filter_graphs)?;
1443
1444 for filter_graph in filter_graphs.iter_mut() {
1445 for i in 0..filter_graph.inputs.len() {
1446 fg_complex_bind_input(
1447 filter_graph,
1448 i,
1449 demuxs,
1450 )?;
1451 }
1452 }
1453
1454 Ok(())
1455}
1456
1457struct FilterLabel {
1458 linklabel: String,
1459 media_type: AVMediaType,
1460}
1461
1462fn bind_fg_inputs_by_fg(filter_graphs: &mut Vec<FilterGraph>) -> Result<()> {
1463 let fg_labels = filter_graphs
1464 .iter()
1465 .map(|filter_graph| {
1466 let inputs = filter_graph
1467 .inputs
1468 .iter()
1469 .map(|input| FilterLabel {
1470 linklabel: input.linklabel.clone(),
1471 media_type: input.media_type,
1472 })
1473 .collect::<Vec<_>>();
1474 let outputs = filter_graph
1475 .outputs
1476 .iter()
1477 .map(|output| FilterLabel {
1478 linklabel: output.linklabel.clone(),
1479 media_type: output.media_type,
1480 })
1481 .collect::<Vec<_>>();
1482 (inputs, outputs)
1483 })
1484 .collect::<Vec<_>>();
1485
1486 for (i, (inputs, _outputs)) in fg_labels.iter().enumerate() {
1487 for input_filter_label in inputs.iter() {
1488 if input_filter_label.linklabel.is_empty() {
1489 continue;
1490 }
1491
1492 'outer: for (j, (_inputs, outputs)) in fg_labels.iter().enumerate() {
1493 if i == j {
1494 continue;
1495 }
1496
1497 for (output_idx, output_filter_label) in outputs.iter().enumerate() {
1498 if output_filter_label.linklabel != input_filter_label.linklabel {
1499 continue;
1500 }
1501 if output_filter_label.media_type != input_filter_label.media_type {
1502 warn!(
1503 "Tried to connect {:?} output to {:?} input",
1504 output_filter_label.media_type, input_filter_label.media_type
1505 );
1506 return Err(FilterGraphParseError::InvalidArgument.into());
1507 }
1508
1509 {
1510 let filter_graph = &filter_graphs[j];
1511 let output_filter = &filter_graph.outputs[output_idx];
1512 if output_filter.has_dst() {
1513 continue;
1514 }
1515
1516 }
1517
1518 let sender = {
1519 let filter_graph = &mut filter_graphs[i];
1520 let sender = filter_graph.get_src_sender();
1521 sender
1522 };
1523
1524 {
1525 let filter_graph = &mut filter_graphs[j];
1526 filter_graph.outputs[output_idx].set_dst(sender);
1527 filter_graph.outputs[output_idx].fg_input_index = i;
1528 }
1529
1530 break 'outer;
1531 }
1532 }
1533 }
1534 }
1535 Ok(())
1536}
1537
1538fn fg_complex_bind_input(
1539 filter_graph: &mut FilterGraph,
1540 input_filter_index: usize,
1541 demuxs: &mut Vec<Demuxer>,
1542) -> Result<()> {
1543 let graph_desc = &filter_graph.graph_desc;
1544 let input_filter = &mut filter_graph.inputs[input_filter_index];
1545 let (demux_idx, stream_idx) =
1546 if !input_filter.linklabel.is_empty() && input_filter.linklabel != "in" {
1547 let (demux_idx, stream_idx) = fg_find_input_idx_by_linklabel(
1548 &input_filter.linklabel,
1549 input_filter.media_type,
1550 demuxs,
1551 graph_desc,
1552 )?;
1553
1554 info!(
1555 "Binding filter input with label '{}' to input stream {stream_idx}:{demux_idx}",
1556 input_filter.linklabel
1557 );
1558 (demux_idx, stream_idx)
1559 } else {
1560 let mut demux_idx = -1i32;
1561 let mut stream_idx = 0;
1562 for (d_idx, demux) in demuxs.iter().enumerate() {
1563 for (st_idx, intput_stream) in demux.get_streams().iter().enumerate() {
1564 if intput_stream.is_used() {
1565 continue;
1566 }
1567 if intput_stream.codec_type == input_filter.media_type {
1568 demux_idx = d_idx as i32;
1569 stream_idx = st_idx;
1570 break;
1571 }
1572 }
1573 if demux_idx >= 0 {
1574 break;
1575 }
1576 }
1577
1578 if demux_idx < 0 {
1579 warn!("Cannot find a matching stream for unlabeled input pad {}", input_filter.name);
1580 return Err(FilterGraphParseError::InvalidArgument.into());
1581 }
1582
1583 debug!("FilterGraph binding unlabeled input {input_filter_index} to input stream {stream_idx}:{demux_idx}");
1584
1585 (demux_idx as usize, stream_idx)
1586 };
1587
1588 let demux = &mut demuxs[demux_idx];
1589
1590 ifilter_bind_ist(filter_graph, input_filter_index, stream_idx, demux)
1591}
1592
1593#[cfg(feature = "docs-rs")]
1594fn ifilter_bind_ist(
1595 filter_graph: &mut FilterGraph,
1596 input_index: usize,
1597 stream_idx: usize,
1598 demux: &mut Demuxer,
1599) -> Result<()> {
1600 Ok(())
1601}
1602
1603#[cfg(not(feature = "docs-rs"))]
1604fn ifilter_bind_ist(
1605 filter_graph: &mut FilterGraph,
1606 input_index: usize,
1607 stream_idx: usize,
1608 demux: &mut Demuxer,
1609) -> Result<()> {
1610 unsafe {
1611 let input_filter = &mut filter_graph.inputs[input_index];
1612 let ist = *(*demux.in_fmt_ctx).streams.add(stream_idx);
1613 let par = (*ist).codecpar;
1614 if (*par).codec_type == AVMEDIA_TYPE_VIDEO {
1615 let framerate = av_guess_frame_rate(demux.in_fmt_ctx, ist, null_mut());
1616 input_filter.opts.framerate = framerate;
1617 } else if (*par).codec_type == AVMEDIA_TYPE_SUBTITLE {
1618 input_filter.opts.sub2video_width = (*par).width;
1619 input_filter.opts.sub2video_height = (*par).height;
1620
1621 if input_filter.opts.sub2video_width <= 0 || input_filter.opts.sub2video_height <= 0 {
1622 let nb_streams = (*demux.in_fmt_ctx).nb_streams;
1623 for j in 0..nb_streams {
1624 let par1 = (**(*demux.in_fmt_ctx).streams.add(j as usize)).codecpar;
1625 if (*par1).codec_type == AVMEDIA_TYPE_VIDEO {
1626 input_filter.opts.sub2video_width =
1627 std::cmp::max(input_filter.opts.sub2video_width, (*par1).width);
1628 input_filter.opts.sub2video_height =
1629 std::cmp::max(input_filter.opts.sub2video_height, (*par1).height);
1630 }
1631 }
1632 }
1633
1634 if input_filter.opts.sub2video_width <= 0 || input_filter.opts.sub2video_height <= 0 {
1635 input_filter.opts.sub2video_width =
1636 std::cmp::max(input_filter.opts.sub2video_width, 720);
1637 input_filter.opts.sub2video_height =
1638 std::cmp::max(input_filter.opts.sub2video_height, 576);
1639 }
1640
1641 demux.get_stream_mut(stream_idx).have_sub2video = true;
1642 }
1643
1644 let dec_ctx = {
1645 let input_stream = demux.get_stream_mut(stream_idx);
1646 avcodec_alloc_context3(input_stream.codec.as_ptr())
1647 };
1648 if dec_ctx.is_null() {
1649 return Err(FilterGraphParseError::OutOfMemory.into());
1650 }
1651 let _codec_ctx = CodecContext::new(dec_ctx);
1652
1653 let fallback = input_filter.opts.fallback.as_mut_ptr();
1654 if (*dec_ctx).codec_type == AVMEDIA_TYPE_AUDIO {
1655 (*fallback).format = (*dec_ctx).sample_fmt as i32;
1656 (*fallback).sample_rate = (*dec_ctx).sample_rate;
1657
1658 let ret = av_channel_layout_copy(&mut (*fallback).ch_layout, &(*dec_ctx).ch_layout);
1659 if ret < 0 {
1660 return Err(FilterGraphParseError::from(ret).into());
1661 }
1662 } else if (*dec_ctx).codec_type == AVMEDIA_TYPE_VIDEO {
1663 (*fallback).format = (*dec_ctx).pix_fmt as i32;
1664 (*fallback).width = (*dec_ctx).width;
1665 (*fallback).height = (*dec_ctx).height;
1666 (*fallback).sample_aspect_ratio = (*dec_ctx).sample_aspect_ratio;
1667 (*fallback).colorspace = (*dec_ctx).colorspace;
1668 (*fallback).color_range = (*dec_ctx).color_range;
1669 }
1670 (*fallback).time_base = (*dec_ctx).pkt_timebase;
1671
1672 input_filter.opts.flags |= IFILTER_FLAG_AUTOROTATE;
1674
1675 if demux.start_time_us.is_some() {
1676 input_filter.opts.trim_start_us = Some(0);
1677 }
1678 input_filter.opts.trim_end_us = demux.recording_time_us;
1679
1680 let sender = filter_graph.get_src_sender();
1681 {
1682 let input_stream = demux.get_stream_mut(stream_idx);
1683 input_stream.add_dst(sender);
1684 input_stream.fg_input_index = input_index;
1685 };
1686
1687 let node = Arc::make_mut(&mut filter_graph.node);
1688 let SchNode::Filter { inputs, .. } = node else {
1689 unreachable!()
1690 };
1691 inputs.insert(input_index, demux.node.clone());
1692
1693
1694 demux.connect_stream(stream_idx);
1695 Ok(())
1696 }
1697}
1698
1699fn fg_find_input_idx_by_linklabel(
1700 linklabel: &str,
1701 filter_media_type: AVMediaType,
1702 demuxs: &mut Vec<Demuxer>,
1703 desc: &str,
1704) -> Result<(usize, usize)> {
1705 let new_linklabel = if linklabel.starts_with("[") && linklabel.ends_with("]") {
1706 if linklabel.len() <= 2 {
1707 warn!("Filter linklabel is empty");
1708 return Err(InvalidFilterSpecifier(desc.to_string()).into());
1709 } else {
1710 &linklabel[1..linklabel.len() - 1]
1711 }
1712 } else {
1713 linklabel
1714 };
1715
1716 let (file_idx, remainder) = strtol(new_linklabel)?;
1717 if file_idx < 0 || file_idx as usize >= demuxs.len() {
1718 return Err(InvalidFileIndexInFg(file_idx as usize, desc.to_string()).into());
1719 }
1720
1721 let (media_type, _allow_unused) = stream_specifier_parse(remainder)?;
1722
1723 if media_type != filter_media_type {
1724 warn!("Invalid stream label: {linklabel}");
1725 return Err(FilterGraphParseError::InvalidArgument.into());
1726 }
1727
1728 let demux = &demuxs[file_idx as usize];
1729
1730 let mut stream_idx = -1i32;
1731
1732 for (idx, dec_stream) in demux.get_streams().iter().enumerate() {
1733 if (*dec_stream).codec_type == media_type {
1734 stream_idx = idx as i32;
1735 break;
1736 }
1737 }
1738
1739 if stream_idx < 0 {
1740 warn!(
1741 "Stream specifier '{remainder}' in filtergraph description {desc} matches no streams."
1742 );
1743 return Err(FilterGraphParseError::InvalidArgument.into());
1744 }
1745 Ok((file_idx as usize, stream_idx as usize))
1746}
1747
1748fn stream_specifier_parse(specifier: &str) -> Result<(AVMediaType, bool)> {
1749 let specifier = if specifier.starts_with(':') {
1750 &specifier[1..]
1751 } else {
1752 specifier
1753 };
1754
1755 match specifier {
1756 "v" => Ok((AVMEDIA_TYPE_VIDEO, false)),
1757 "a" => Ok((AVMEDIA_TYPE_AUDIO, false)),
1758 "s" => Ok((AVMEDIA_TYPE_SUBTITLE, false)),
1759 "d" => Ok((AVMEDIA_TYPE_DATA, false)),
1760 "t" => Ok((AVMEDIA_TYPE_ATTACHMENT, false)),
1761 "v?" => Ok((AVMEDIA_TYPE_VIDEO, true)),
1762 "a?" => Ok((AVMEDIA_TYPE_AUDIO, true)),
1763 "s?" => Ok((AVMEDIA_TYPE_SUBTITLE, true)),
1764 "d?" => Ok((AVMEDIA_TYPE_DATA, true)),
1765 "t?" => Ok((AVMEDIA_TYPE_ATTACHMENT, true)),
1766 _ => Err(InvalidFilterSpecifier(specifier.to_string()).into()),
1768 }
1769}
1770
1771fn strtol(input: &str) -> Result<(i64, &str)> {
1773 let mut chars = input.chars().peekable();
1774 let mut negative = false;
1775
1776 if let Some(&ch) = chars.peek() {
1777 if ch == '-' {
1778 negative = true;
1779 chars.next();
1780 } else if !ch.is_digit(10) {
1781 return Err(ParseInteger);
1782 }
1783 }
1784
1785 let number_start = input.len() - chars.clone().collect::<String>().len();
1786
1787 let number_str: String = chars.by_ref().take_while(|ch| ch.is_digit(10)).collect();
1788
1789 if number_str.is_empty() {
1790 return Err(ParseInteger);
1791 }
1792
1793 let number: i64 = number_str.parse().map_err(|_| ParseInteger)?;
1794
1795 let remainder_index = number_start + number_str.len();
1796 let remainder = &input[remainder_index..];
1797
1798 if negative {
1799 Ok((-number, remainder))
1800 } else {
1801 Ok((number, remainder))
1802 }
1803}
1804
1805fn init_filter_graphs(filter_complexs: Vec<FilterComplex>) -> Result<Vec<FilterGraph>> {
1806 let mut filter_graphs = Vec::with_capacity(filter_complexs.len());
1807 for (i, filter) in filter_complexs.iter().enumerate() {
1808 let filter_graph = init_filter_graph(i, &filter.filter_descs, filter.hw_device.clone())?;
1809 filter_graphs.push(filter_graph);
1810 }
1811 Ok(filter_graphs)
1812}
1813
1814
1815#[cfg(feature = "docs-rs")]
1816fn init_filter_graph(
1817 fg_index: usize,
1818 filter_desc: &str,
1819 hw_device: Option<String>,
1820) -> Result<FilterGraph> {
1821 Err(Bug)
1822}
1823
1824#[cfg(not(feature = "docs-rs"))]
1825fn init_filter_graph(
1826 fg_index: usize,
1827 filter_desc: &str,
1828 hw_device: Option<String>,
1829) -> Result<FilterGraph> {
1830 let desc_cstr = CString::new(filter_desc)?;
1831
1832 unsafe {
1833 let mut graph = avfilter_graph_alloc();
1836 (*graph).nb_threads = 1;
1837
1838 let mut seg = null_mut();
1839 let mut ret = avfilter_graph_segment_parse(graph, desc_cstr.as_ptr(), 0, &mut seg);
1840 if ret < 0 {
1841 avfilter_graph_free(&mut graph);
1842 return Err(FilterGraphParseError::from(ret).into());
1843 }
1844
1845 ret = avfilter_graph_segment_create_filters(seg, 0);
1846 if ret < 0 {
1847 avfilter_graph_free(&mut graph);
1848 avfilter_graph_segment_free(&mut seg);
1849 return Err(FilterGraphParseError::from(ret).into());
1850 }
1851
1852 #[cfg(not(feature = "docs-rs"))]
1853 {
1854 ret = graph_opts_apply(seg);
1855 }
1856 if ret < 0 {
1857 avfilter_graph_segment_free(&mut seg);
1858 avfilter_graph_free(&mut graph);
1859 return Err(FilterGraphParseError::from(ret).into());
1860 }
1861
1862 let mut inputs = null_mut();
1863 let mut outputs = null_mut();
1864 ret = avfilter_graph_segment_apply(seg, 0, &mut inputs, &mut outputs);
1865 avfilter_graph_segment_free(&mut seg);
1866
1867 if ret < 0 {
1868 avfilter_inout_free(&mut inputs);
1869 avfilter_inout_free(&mut outputs);
1870 avfilter_graph_free(&mut graph);
1871 return Err(FilterGraphParseError::from(ret).into());
1872 }
1873
1874 let input_filters = inouts_to_input_filters(fg_index, inputs)?;
1875 let output_filters = inouts_to_output_filters(outputs)?;
1876
1877 if output_filters.len() == 0 {
1878 avfilter_inout_free(&mut inputs);
1879 avfilter_inout_free(&mut outputs);
1880 avfilter_graph_free(&mut graph);
1881 return Err(FilterZeroOutputs);
1882 }
1883
1884 let filter_graph = FilterGraph::new(
1885 filter_desc.to_string(),
1886 hw_device,
1887 input_filters,
1888 output_filters,
1889 );
1890
1891 avfilter_inout_free(&mut inputs);
1892 avfilter_inout_free(&mut outputs);
1893 avfilter_graph_free(&mut graph);
1894
1895 Ok(filter_graph)
1896 }
1897}
1898
1899unsafe fn inouts_to_input_filters(
1900 fg_index: usize,
1901 inouts: *mut AVFilterInOut,
1902) -> Result<Vec<InputFilter>> {
1903 let mut cur = inouts;
1904 let mut filterinouts = Vec::new();
1905 let mut filter_index = 0;
1906 while !cur.is_null() {
1907 let linklabel = if (*cur).name.is_null() {
1908 ""
1909 } else {
1910 let linklabel = CStr::from_ptr((*cur).name);
1911 let result = linklabel.to_str();
1912 if let Err(_) = result {
1913 return Err(FilterDescUtf8);
1914 }
1915 result.unwrap()
1916 };
1917
1918 let filter_ctx = (*cur).filter_ctx;
1919 let media_type = avfilter_pad_get_type((*filter_ctx).input_pads, (*cur).pad_idx);
1920
1921 let pads = (*filter_ctx).input_pads;
1922 let nb_pads = (*filter_ctx).nb_inputs;
1923
1924 let name = describe_filter_link(cur, filter_ctx, pads, nb_pads)?;
1925
1926 let fallback = frame_alloc()?;
1927
1928 let mut filter = InputFilter::new(linklabel.to_string(), media_type, name, fallback);
1929 filter.opts.name = format!("fg:{fg_index}:{filter_index}");
1930 filterinouts.push(filter);
1931
1932 cur = (*cur).next;
1933 filter_index += 1;
1934 }
1935 Ok(filterinouts)
1936}
1937
1938unsafe fn inouts_to_output_filters(inouts: *mut AVFilterInOut) -> Result<Vec<OutputFilter>> {
1939 let mut cur = inouts;
1940 let mut output_filters = Vec::new();
1941 while !cur.is_null() {
1942 let linklabel = if (*cur).name.is_null() {
1943 ""
1944 } else {
1945 let linklabel = CStr::from_ptr((*cur).name);
1946 let result = linklabel.to_str();
1947 if let Err(_) = result {
1948 return Err(FilterDescUtf8);
1949 }
1950 result.unwrap()
1951 };
1952
1953 let filter_ctx = (*cur).filter_ctx;
1954 let media_type = avfilter_pad_get_type((*filter_ctx).output_pads, (*cur).pad_idx);
1955
1956 let pads = (*filter_ctx).output_pads;
1957 let nb_pads = (*filter_ctx).nb_outputs;
1958
1959 let name = describe_filter_link(cur, filter_ctx, pads, nb_pads)?;
1960
1961 let filter = OutputFilter::new(linklabel.to_string(), media_type, name);
1962 output_filters.push(filter);
1963
1964 cur = (*cur).next;
1965 }
1966 Ok(output_filters)
1967}
1968
1969unsafe fn describe_filter_link(
1970 cur: *mut AVFilterInOut,
1971 filter_ctx: *mut AVFilterContext,
1972 pads: *mut AVFilterPad,
1973 nb_pads: c_uint,
1974) -> Result<String> {
1975 let filter = (*filter_ctx).filter;
1976 let name = (*filter).name;
1977 let name = CStr::from_ptr(name);
1978 let result = name.to_str();
1979 if let Err(_) = result {
1980 return Err(FilterNameUtf8);
1981 }
1982 let name = result.unwrap();
1983
1984 let name = if nb_pads > 1 {
1985 name.to_string()
1986 } else {
1987 let pad_name = avfilter_pad_get_name(pads, (*cur).pad_idx);
1988 let pad_name = CStr::from_ptr(pad_name);
1989 let result = pad_name.to_str();
1990 if let Err(_) = result {
1991 return Err(FilterNameUtf8);
1992 }
1993 let pad_name = result.unwrap();
1994 format!("{name}:{pad_name}")
1995 };
1996 Ok(name)
1997}
1998
1999fn open_input_files(inputs: &mut Vec<Input>) -> Result<Vec<Demuxer>> {
2000 let mut demuxs = Vec::new();
2001 for (i, input) in inputs.iter_mut().enumerate() {
2002 unsafe {
2003 let result = open_input_file(i, input);
2004 if let Err(e) = result {
2005 free_input_av_format_context(demuxs);
2006 return Err(e);
2007 }
2008 let demux = result.unwrap();
2009 demuxs.push(demux)
2010 }
2011 }
2012 Ok(demuxs)
2013}
2014
2015unsafe fn free_input_av_format_context(demuxs: Vec<Demuxer>) {
2016 for mut demux in demuxs {
2017 avformat_close_input(&mut demux.in_fmt_ctx);
2018 }
2019}
2020
2021#[cfg(feature = "docs-rs")]
2022unsafe fn open_input_file(
2023 index: usize,
2024 input: &mut Input,
2025) -> Result<Demuxer> {
2026 Err(Bug)
2027}
2028
2029#[cfg(not(feature = "docs-rs"))]
2030unsafe fn open_input_file(
2031 index: usize,
2032 input: &mut Input,
2033) -> Result<Demuxer> {
2034 let mut in_fmt_ctx = null_mut();
2035
2036 let input_opts = convert_options(input.input_opts.clone())?;
2037 let mut input_opts = hashmap_to_avdictionary(&input_opts);
2038
2039 let recording_time_us = match input.stop_time_us {
2040 None => input.recording_time_us,
2041 Some(stop_time_us) => {
2042 let start_time_us = input.start_time_us.unwrap_or_else(|| 0);
2043 if stop_time_us <= start_time_us {
2044 error!("stop_time_us value smaller than start_time_us; aborting.");
2045 return Err(OpenOutputError::InvalidArgument.into());
2046 } else {
2047 Some(stop_time_us - start_time_us)
2048 }
2049 }
2050 };
2051
2052 match &input.url {
2053 None => {
2054 if input.read_callback.is_none() {
2055 error!("input url and read_callback is none.");
2056 return Err(OpenInputError::InvalidSource.into());
2057 }
2058
2059 in_fmt_ctx = avformat_alloc_context();
2060 if in_fmt_ctx.is_null() {
2061 return Err(OpenInputError::OutOfMemory.into());
2062 }
2063
2064 let avio_ctx_buffer_size = 1024 * 64;
2065 let mut avio_ctx_buffer = av_malloc(avio_ctx_buffer_size);
2066 if avio_ctx_buffer.is_null() {
2067 avformat_close_input(&mut in_fmt_ctx);
2068 return Err(OpenInputError::OutOfMemory.into());
2069 }
2070
2071 let have_seek_callback = input.seek_callback.is_some();
2072 let input_opaque = Box::new(InputOpaque {
2073 read: input.read_callback.take().unwrap(),
2074 seek: input.seek_callback.take(),
2075 });
2076 let opaque = Box::into_raw(input_opaque) as *mut libc::c_void;
2077
2078 let mut avio_ctx = avio_alloc_context(
2079 avio_ctx_buffer as *mut libc::c_uchar,
2080 avio_ctx_buffer_size as i32,
2081 0,
2082 opaque,
2083 Some(read_packet_wrapper),
2084 None,
2085 if have_seek_callback {
2086 Some(seek_packet_wrapper)
2087 } else {
2088 None
2089 },
2090 );
2091 if avio_ctx.is_null() {
2092 av_freep(&mut avio_ctx_buffer as *mut _ as *mut c_void);
2093 avformat_close_input(&mut in_fmt_ctx);
2094 return Err(OpenInputError::OutOfMemory.into());
2095 }
2096
2097 (*in_fmt_ctx).pb = avio_ctx;
2098 (*in_fmt_ctx).flags = AVFMT_FLAG_CUSTOM_IO;
2099
2100 let ret = avformat_open_input(&mut in_fmt_ctx, null(), null(), &mut input_opts);
2101 if ret < 0 {
2102 av_freep(&mut (*avio_ctx).buffer as *mut _ as *mut c_void);
2103 avio_context_free(&mut avio_ctx);
2104 avformat_close_input(&mut in_fmt_ctx);
2105 return Err(OpenInputError::from(ret).into());
2106 }
2107
2108 let ret = avformat_find_stream_info(in_fmt_ctx, null_mut());
2109 if ret < 0 {
2110 av_freep(&mut (*avio_ctx).buffer as *mut _ as *mut c_void);
2111 avio_context_free(&mut avio_ctx);
2112 avformat_close_input(&mut in_fmt_ctx);
2113 return Err(FindStreamError::from(ret).into());
2114 }
2115
2116 if !have_seek_callback && input_requires_seek(in_fmt_ctx) {
2117 av_freep(&mut (*avio_ctx).buffer as *mut _ as *mut c_void);
2118 avio_context_free(&mut avio_ctx);
2119 avformat_close_input(&mut in_fmt_ctx);
2120 warn!("The input format supports seeking, but no seek callback is provided. This may cause issues.");
2121 return Err(OpenInputError::SeekFunctionMissing.into());
2122 }
2123 }
2124 Some(url) => {
2125 let url_cstr = CString::new(url.as_str())?;
2126
2127 let mut ret =
2128 avformat_open_input(&mut in_fmt_ctx, url_cstr.as_ptr(), null(), &mut input_opts);
2129 if ret < 0 {
2130 avformat_close_input(&mut in_fmt_ctx);
2131 return Err(OpenInputError::from(ret).into());
2132 }
2133
2134 ret = avformat_find_stream_info(in_fmt_ctx, null_mut());
2135 if ret < 0 {
2136 avformat_close_input(&mut in_fmt_ctx);
2137 return Err(FindStreamError::from(ret).into());
2138 }
2139 }
2140 }
2141
2142 if let Some(start_time_us) = input.start_time_us {
2144 let mut seek_timestamp = start_time_us;
2145 if (*in_fmt_ctx).start_time != ffmpeg_sys_next::AV_NOPTS_VALUE {
2147 seek_timestamp += (*in_fmt_ctx).start_time;
2148 }
2149
2150 if (*(*in_fmt_ctx).iformat).flags & ffmpeg_sys_next::AVFMT_SEEK_TO_PTS == 0 {
2151 let mut dts_heuristic = false;
2152 let stream_count = (*in_fmt_ctx).nb_streams;
2153
2154 for i in 0..stream_count {
2155 let stream = *(*in_fmt_ctx).streams.add(i as usize);
2156 let par = (*stream).codecpar;
2157 if (*par).video_delay != 0 {
2158 dts_heuristic = true;
2159 break;
2160 }
2161 }
2162 if dts_heuristic {
2163 seek_timestamp -= 3 * AV_TIME_BASE as i64 / 23;
2164 }
2165 }
2166 let ret = ffmpeg_sys_next::avformat_seek_file(in_fmt_ctx, -1, i64::MIN, seek_timestamp, seek_timestamp, 0);
2167 if ret < 0 {
2168 warn!("could not seek to position {:.3}", start_time_us as f64 / AV_TIME_BASE as f64);
2169 }
2170 }
2171
2172 let url = input
2173 .url
2174 .clone()
2175 .unwrap_or_else(|| format!("read_callback[{index}]"));
2176
2177 let demux = Demuxer::new(
2178 index,
2179 url,
2180 input.url.is_none(),
2181 in_fmt_ctx,
2182 0 - input.start_time_us.unwrap_or(0),
2183 input.frame_pipelines.take(),
2184 input.video_codec.clone(),
2185 input.audio_codec.clone(),
2186 input.subtitle_codec.clone(),
2187 input.readrate,
2188 input.start_time_us,
2189 recording_time_us,
2190 input.exit_on_error,
2191 input.stream_loop,
2192 input.hwaccel.clone(),
2193 input.hwaccel_device.clone(),
2194 input.hwaccel_output_format.clone(),
2195 )?;
2196
2197 Ok(demux)
2198}
2199
2200fn convert_options(
2201 opts: Option<HashMap<String, String>>,
2202) -> Result<Option<HashMap<CString, CString>>> {
2203 if opts.is_none() {
2204 return Ok(None);
2205 }
2206
2207 let converted = opts.map(|map| {
2208 map.into_iter()
2209 .map(|(k, v)| Ok((CString::new(k)?, CString::new(v)?)))
2210 .collect::<Result<HashMap<CString, CString>, _>>() });
2212
2213 converted.transpose() }
2215
2216unsafe fn input_requires_seek(fmt_ctx: *mut AVFormatContext) -> bool {
2217 if fmt_ctx.is_null() {
2218 return false;
2219 }
2220
2221 let mut format_name = "unknown".to_string();
2222 let mut format_names: Vec<&str> = Vec::with_capacity(0);
2223
2224 if !(*fmt_ctx).iformat.is_null() {
2225 let iformat = (*fmt_ctx).iformat;
2226 format_name = CStr::from_ptr((*iformat).name)
2227 .to_string_lossy()
2228 .into_owned();
2229 let flags = (*iformat).flags;
2230 let no_binsearch = flags & AVFMT_NOBINSEARCH as i32 != 0;
2231 let no_gensearch = flags & AVFMT_NOGENSEARCH as i32 != 0;
2232
2233 log::debug!(
2234 "Input format '{format_name}' - Binary search: {}, Generic search: {}",
2235 if no_binsearch { "Disabled" } else { "Enabled" },
2236 if no_gensearch { "Disabled" } else { "Enabled" }
2237 );
2238
2239 format_names = format_name.split(',').collect();
2240
2241 if format_names.iter().any(|&f| {
2242 matches!(
2243 f,
2244 "mp4" | "mkv" | "avi" | "mov" | "flac" | "wav" | "aac" | "ogg" | "mp3" | "webm"
2245 )
2246 }) {
2247 if !no_binsearch && !no_gensearch {
2248 return true;
2249 }
2250 }
2251
2252 if format_names.iter().any(|&f| {
2253 matches!(
2254 f,
2255 "hls" | "m3u8" | "mpegts" | "mms" | "udp" | "rtp" | "rtp_mpegts" | "http" | "srt"
2256 )
2257 }) {
2258 log::debug!("Live stream detected ({format_name}). Seeking is not possible.");
2259 return false;
2260 }
2261
2262 if no_binsearch && no_gensearch {
2263 log::debug!("Input format '{format_name}' has both NOBINSEARCH and NOGENSEARCH set. Seeking is likely restricted.");
2264 }
2265 }
2266
2267 let format_duration = (*fmt_ctx).duration;
2268
2269 if format_names.iter().any(|&f| f == "flv") {
2270 if format_duration <= 0 {
2271 log::debug!(
2272 "Input format 'flv' detected with no valid duration. Seeking is not possible."
2273 );
2274 } else {
2275 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.");
2276 }
2277 return false;
2278 }
2279
2280 if format_duration > 0 {
2281 log::debug!("Format '{format_name}' has a duration of {format_duration}. Seeking is likely possible.");
2282 return true;
2283 }
2284
2285 let mut video_stream_index = -1;
2286 for i in 0..(*fmt_ctx).nb_streams {
2287 let stream = *(*fmt_ctx).streams.offset(i as isize);
2288 if (*stream).codecpar.is_null() {
2289 continue;
2290 }
2291 if (*(*stream).codecpar).codec_type == AVMEDIA_TYPE_VIDEO {
2292 video_stream_index = i as i32;
2293 break;
2294 }
2295 }
2296
2297 let stream_index = if video_stream_index >= 0 {
2298 video_stream_index
2299 } else {
2300 -1
2301 };
2302
2303 let original_pos = if !(*fmt_ctx).pb.is_null() {
2304 (*(*fmt_ctx).pb).pos
2305 } else {
2306 -1
2307 };
2308
2309 if original_pos >= 0 {
2310 let seek_target = 1 * AV_TIME_BASE as i64;
2311 let seek_result = av_seek_frame(fmt_ctx, stream_index, seek_target, AVSEEK_FLAG_BACKWARD);
2312
2313 if seek_result >= 0 {
2314 log::debug!("Seek test successful.");
2315
2316 (*(*fmt_ctx).pb).pos = original_pos;
2317 avformat_flush(fmt_ctx);
2318 log::debug!("Restored fmt_ctx.pb.pos to {original_pos} and flushed format context.",);
2319 return true;
2320 } else {
2321 log::debug!("Seek test failed (return code {seek_result}). This format likely does not support seeking.");
2322 }
2323 }
2324
2325 false
2326}
2327
2328#[cfg(test)]
2329mod tests {
2330 use std::ffi::{CStr, CString};
2331 use std::ptr::null_mut;
2332
2333 use crate::core::context::ffmpeg_context::{strtol, FfmpegContext, Output};
2334 use ffmpeg_sys_next::{
2335 avfilter_graph_alloc, avfilter_graph_free, avfilter_graph_parse_ptr, avfilter_inout_free,
2336 };
2337
2338 #[test]
2339 fn test_filter() {
2340 let desc_cstr = CString::new("[1:v][2:v]concat=n=2:v=1:a=0[vout]").unwrap();
2341 unsafe {
2344 let mut graph = avfilter_graph_alloc();
2345 let mut inputs = null_mut();
2346 let mut outputs = null_mut();
2347
2348 let ret = avfilter_graph_parse_ptr(
2349 graph,
2350 desc_cstr.as_ptr(),
2351 &mut inputs,
2352 &mut outputs,
2353 null_mut(),
2354 );
2355 if ret < 0 {
2356 avfilter_inout_free(&mut inputs);
2357 avfilter_inout_free(&mut outputs);
2358 avfilter_graph_free(&mut graph);
2359 println!("err ret:{ret}");
2360 return;
2361 }
2362
2363 println!("inputs.is_null:{}", inputs.is_null());
2364 println!("outputs.is_null:{}", outputs.is_null());
2365
2366 let mut cur = inputs;
2367 while !cur.is_null() {
2368 let input_name = CStr::from_ptr((*cur).name);
2369 println!("Input name: {}", input_name.to_str().unwrap());
2370 cur = (*cur).next;
2371 }
2372
2373 let output_name = CStr::from_ptr((*outputs).name);
2374 println!("Output name: {}", output_name.to_str().unwrap());
2375
2376 let filter_ctx = (*outputs).filter_ctx;
2377 avfilter_inout_free(&mut outputs);
2378 println!("filter_ctx.is_null:{}", filter_ctx.is_null());
2379 }
2380 }
2381
2382 #[test]
2383 fn test_new() {
2384 let _ = env_logger::builder()
2385 .filter_level(log::LevelFilter::Debug)
2386 .is_test(true)
2387 .try_init();
2388 let ffmpeg_context = FfmpegContext::new(
2389 vec!["test.mp4".to_string().into()],
2390 vec!["hue=s=0".to_string().into()],
2391 vec!["output.mp4".to_string().into()],
2392 )
2393 .unwrap();
2394 let ffmpeg_context = FfmpegContext::new(
2395 vec!["test.mp4".into()],
2396 vec!["[0:v]hue=s=0".into()],
2397 vec!["output.mp4".to_string().into()],
2398 )
2399 .unwrap();
2400 let ffmpeg_context = FfmpegContext::new(
2401 vec!["test.mp4".into()],
2402 vec!["hue=s=0[my-out]".into()],
2403 vec![Output::from("output.mp4").add_stream_map("my-out")],
2404 )
2405 .unwrap();
2406 let ffmpeg_context = FfmpegContext::new(
2407 vec!["test.mp4".into()],
2408 vec!["hue=s=0".into()],
2409 vec![Output::from("output.mp4").add_stream_map_with_copy("0:v?")],
2410 )
2411 .unwrap();
2412 let result = FfmpegContext::new(
2413 vec!["test.mp4".into()],
2414 vec!["hue=s=0".into()],
2415 vec![Output::from("output.mp4").add_stream_map_with_copy("1:v?")],
2416 );
2417 assert!(result.is_err());
2418 let result = FfmpegContext::new(
2419 vec!["test.mp4".into()],
2420 vec!["hue=s=0[fg-out]".into()],
2421 vec![
2422 Output::from("output.mp4").add_stream_map("my-out?"),
2423 Output::from("output.mp4").add_stream_map("fg-out"),
2424 ],
2425 );
2426 assert!(result.is_err());
2427 let result = FfmpegContext::new(
2429 vec!["test.mp4".into()],
2430 vec!["hue=s=0".into()],
2431 vec![Output::from("output.mp4").add_stream_map_with_copy("1:v")],
2432 );
2433 assert!(result.is_err());
2434 let result = FfmpegContext::new(
2435 vec!["test.mp4".into()],
2436 vec!["hue=s=0[fg-out]".into()],
2437 vec![Output::from("output.mp4").add_stream_map("fg-out?")],
2438 );
2439 assert!(result.is_err());
2440 }
2441
2442 #[test]
2443 fn test_builder() {
2444 let _ = env_logger::builder()
2445 .filter_level(log::LevelFilter::Debug)
2446 .is_test(true)
2447 .try_init();
2448
2449 let context1 = FfmpegContext::builder()
2450 .input("test.mp4")
2451 .filter_desc("hue=s=0")
2452 .output("output.mp4")
2453 .build()
2454 .unwrap();
2455
2456 let context2 = FfmpegContext::builder()
2457 .inputs(vec!["test.mp4"])
2458 .filter_descs(vec!["hue=s=0"])
2459 .outputs(vec!["output.mp4"])
2460 .build()
2461 .unwrap();
2462 }
2463
2464 #[test]
2465 fn test_strtol() {
2466 let input = "-123---abc";
2467 let result = strtol(input);
2468 assert_eq!(result.unwrap(), (-123, "---abc"));
2469
2470 let input = "123---abc";
2471 let result = strtol(input);
2472 assert_eq!(result.unwrap(), (123, "---abc"));
2473
2474 let input = "-123aa";
2475 let result = strtol(input);
2476 assert_eq!(result.unwrap(), (-123, "aa"));
2477
2478 let input = "-aa";
2479 let result = strtol(input);
2480 assert!(result.is_err());
2481
2482 let input = "abc";
2483 let result = strtol(input);
2484 assert!(result.is_err())
2485 }
2486}