1use std::ffi::CString;
2use std::ptr::NonNull;
3
4use crate::error::{FfmpegError, FfmpegErrorCode};
5use crate::ffi::*;
6use crate::frame::GenericFrame;
7use crate::smart_object::SmartPtr;
8
9pub struct FilterGraph(SmartPtr<AVFilterGraph>);
11
12unsafe impl Send for FilterGraph {}
14
15impl FilterGraph {
16 pub fn new() -> Result<Self, FfmpegError> {
18 let ptr = unsafe { avfilter_graph_alloc() };
20 unsafe { Self::wrap(ptr) }.ok_or(FfmpegError::Alloc)
22 }
23
24 const unsafe fn wrap(ptr: *mut AVFilterGraph) -> Option<Self> {
26 let destructor = |ptr: &mut *mut AVFilterGraph| {
27 unsafe { avfilter_graph_free(ptr) };
29 };
30
31 if ptr.is_null() {
32 return None;
33 }
34
35 Some(Self(unsafe { SmartPtr::wrap(ptr, destructor) }))
37 }
38
39 pub const fn as_ptr(&self) -> *const AVFilterGraph {
41 self.0.as_ptr()
42 }
43
44 pub const fn as_mut_ptr(&mut self) -> *mut AVFilterGraph {
46 self.0.as_mut_ptr()
47 }
48
49 pub fn add(&mut self, filter: Filter, name: &str, args: &str) -> Result<FilterContext<'_>, FfmpegError> {
51 let name = CString::new(name).or(Err(FfmpegError::Arguments("name must be non-empty")))?;
52 let args = CString::new(args).or(Err(FfmpegError::Arguments("args must be non-empty")))?;
53
54 let mut filter_context = std::ptr::null_mut();
55
56 FfmpegErrorCode(unsafe {
59 avfilter_graph_create_filter(
60 &mut filter_context,
61 filter.as_ptr(),
62 name.as_ptr(),
63 args.as_ptr(),
64 std::ptr::null_mut(),
65 self.as_mut_ptr(),
66 )
67 })
68 .result()?;
69
70 Ok(FilterContext(unsafe {
72 NonNull::new(filter_context).ok_or(FfmpegError::Alloc)?.as_mut()
73 }))
74 }
75
76 pub fn get(&mut self, name: &str) -> Option<FilterContext<'_>> {
78 let name = CString::new(name).ok()?;
79
80 let mut ptr = NonNull::new(unsafe { avfilter_graph_get_filter(self.as_mut_ptr(), name.as_ptr()) })?;
83 Some(FilterContext(unsafe { ptr.as_mut() }))
85 }
86
87 pub fn validate(&mut self) -> Result<(), FfmpegError> {
89 FfmpegErrorCode(unsafe { avfilter_graph_config(self.as_mut_ptr(), std::ptr::null_mut()) }).result()?;
91 Ok(())
92 }
93
94 pub fn dump(&mut self) -> Option<String> {
96 let dump = unsafe { avfilter_graph_dump(self.as_mut_ptr(), std::ptr::null_mut()) };
98 let destructor = |ptr: &mut *mut libc::c_char| {
99 unsafe { av_free(*ptr as *mut libc::c_void) };
101 *ptr = std::ptr::null_mut();
102 };
103
104 let c_str = unsafe { SmartPtr::wrap_non_null(dump, destructor)? };
106
107 let c_str = unsafe { std::ffi::CStr::from_ptr(c_str.as_ptr()) };
109
110 Some(c_str.to_str().ok()?.to_owned())
111 }
112
113 pub const fn set_thread_count(&mut self, threads: i32) {
115 self.0.as_deref_mut_except().nb_threads = threads;
116 }
117
118 pub fn input(&mut self, name: &str, pad: i32) -> Result<FilterGraphParser<'_>, FfmpegError> {
120 FilterGraphParser::new(self).input(name, pad)
121 }
122
123 pub fn output(&mut self, name: &str, pad: i32) -> Result<FilterGraphParser<'_>, FfmpegError> {
125 FilterGraphParser::new(self).output(name, pad)
126 }
127}
128
129pub struct FilterGraphParser<'a> {
131 graph: &'a mut FilterGraph,
132 inputs: SmartPtr<AVFilterInOut>,
133 outputs: SmartPtr<AVFilterInOut>,
134}
135
136unsafe impl Send for FilterGraphParser<'_> {}
138
139impl<'a> FilterGraphParser<'a> {
140 fn new(graph: &'a mut FilterGraph) -> Self {
142 Self {
143 graph,
144 inputs: SmartPtr::null(|ptr| {
146 unsafe { avfilter_inout_free(ptr) };
148 }),
149 outputs: SmartPtr::null(|ptr| {
151 unsafe { avfilter_inout_free(ptr) };
153 }),
154 }
155 }
156
157 pub fn input(self, name: &str, pad: i32) -> Result<Self, FfmpegError> {
159 self.inout_impl(name, pad, false)
160 }
161
162 pub fn output(self, name: &str, pad: i32) -> Result<Self, FfmpegError> {
164 self.inout_impl(name, pad, true)
165 }
166
167 pub fn parse(mut self, spec: &str) -> Result<(), FfmpegError> {
169 let spec = CString::new(spec).unwrap();
170
171 FfmpegErrorCode(unsafe {
174 avfilter_graph_parse_ptr(
175 self.graph.as_mut_ptr(),
176 spec.as_ptr(),
177 self.inputs.as_mut(),
178 self.outputs.as_mut(),
179 std::ptr::null_mut(),
180 )
181 })
182 .result()?;
183
184 Ok(())
185 }
186
187 fn inout_impl(mut self, name: &str, pad: i32, output: bool) -> Result<Self, FfmpegError> {
188 let context = self.graph.get(name).ok_or(FfmpegError::Arguments("unknown name"))?;
189
190 let destructor = |ptr: &mut *mut AVFilterInOut| {
191 unsafe { avfilter_inout_free(ptr) };
193 };
194
195 let inout = unsafe { avfilter_inout_alloc() };
197
198 let mut inout = unsafe { SmartPtr::wrap_non_null(inout, destructor) }.ok_or(FfmpegError::Alloc)?;
201
202 let name = CString::new(name).map_err(|_| FfmpegError::Arguments("name must be non-empty"))?;
203
204 inout.as_deref_mut_except().name = unsafe { av_strdup(name.as_ptr()) };
208 inout.as_deref_mut_except().filter_ctx = context.0;
209 inout.as_deref_mut_except().pad_idx = pad;
210
211 if output {
212 inout.as_deref_mut_except().next = self.outputs.into_inner();
213 self.outputs = inout;
214 } else {
215 inout.as_deref_mut_except().next = self.inputs.into_inner();
216 self.inputs = inout;
217 }
218
219 Ok(self)
220 }
221}
222
223#[derive(Clone, Copy, PartialEq, Eq)]
225pub struct Filter(*const AVFilter);
226
227impl Filter {
228 pub fn get(name: &str) -> Option<Self> {
230 let name = std::ffi::CString::new(name).ok()?;
231
232 let filter = unsafe { avfilter_get_by_name(name.as_ptr()) };
235
236 if filter.is_null() { None } else { Some(Self(filter)) }
237 }
238
239 pub const fn as_ptr(&self) -> *const AVFilter {
241 self.0
242 }
243
244 pub const unsafe fn wrap(ptr: *const AVFilter) -> Self {
247 Self(ptr)
248 }
249}
250
251unsafe impl Send for Filter {}
253
254pub struct FilterContext<'a>(&'a mut AVFilterContext);
256
257unsafe impl Send for FilterContext<'_> {}
259
260impl<'a> FilterContext<'a> {
261 pub const fn source(self) -> FilterContextSource<'a> {
263 FilterContextSource(self.0)
264 }
265
266 pub const fn sink(self) -> FilterContextSink<'a> {
268 FilterContextSink(self.0)
269 }
270}
271
272pub struct FilterContextSource<'a>(&'a mut AVFilterContext);
274
275unsafe impl Send for FilterContextSource<'_> {}
277
278impl FilterContextSource<'_> {
279 pub fn send_frame(&mut self, frame: &GenericFrame) -> Result<(), FfmpegError> {
281 FfmpegErrorCode(unsafe { av_buffersrc_write_frame(self.0, frame.as_ptr()) }).result()?;
283 Ok(())
284 }
285
286 pub fn send_eof(&mut self, pts: Option<i64>) -> Result<(), FfmpegError> {
288 if let Some(pts) = pts {
289 FfmpegErrorCode(unsafe { av_buffersrc_close(self.0, pts, 0) }).result()?;
291 } else {
292 FfmpegErrorCode(unsafe { av_buffersrc_write_frame(self.0, std::ptr::null()) }).result()?;
294 }
295
296 Ok(())
297 }
298}
299
300pub struct FilterContextSink<'a>(&'a mut AVFilterContext);
302
303unsafe impl Send for FilterContextSink<'_> {}
305
306impl FilterContextSink<'_> {
307 pub fn receive_frame(&mut self) -> Result<Option<GenericFrame>, FfmpegError> {
309 let mut frame = GenericFrame::new()?;
310
311 match FfmpegErrorCode(unsafe { av_buffersink_get_frame(self.0, frame.as_mut_ptr()) }) {
313 code if code.is_success() => Ok(Some(frame)),
314 FfmpegErrorCode::Eagain | FfmpegErrorCode::Eof => Ok(None),
315 code => Err(FfmpegError::Code(code)),
316 }
317 }
318}
319
320#[cfg(test)]
321#[cfg_attr(all(test, coverage_nightly), coverage(off))]
322mod tests {
323 use std::ffi::CString;
324
325 use crate::AVSampleFormat;
326 use crate::ffi::avfilter_get_by_name;
327 use crate::filter_graph::{Filter, FilterGraph, FilterGraphParser};
328 use crate::frame::{AudioChannelLayout, AudioFrame, GenericFrame};
329
330 #[test]
331 fn test_filter_graph_new() {
332 let filter_graph = FilterGraph::new();
333 assert!(filter_graph.is_ok(), "FilterGraph::new should create a valid filter graph");
334
335 if let Ok(graph) = filter_graph {
336 assert!(!graph.as_ptr().is_null(), "FilterGraph pointer should not be null");
337 }
338 }
339
340 #[test]
341 fn test_filter_graph_as_mut_ptr() {
342 let mut filter_graph = FilterGraph::new().expect("Failed to create filter graph");
343 let raw_ptr = filter_graph.as_mut_ptr();
344
345 assert!(!raw_ptr.is_null(), "FilterGraph::as_mut_ptr should return a valid pointer");
346 }
347
348 #[test]
349 fn test_filter_graph_add() {
350 let mut filter_graph = FilterGraph::new().expect("Failed to create filter graph");
351 let filter_name = "buffer";
352 let filter_ptr = unsafe { avfilter_get_by_name(CString::new(filter_name).unwrap().as_ptr()) };
354 assert!(
355 !filter_ptr.is_null(),
356 "avfilter_get_by_name should return a valid pointer for filter '{filter_name}'"
357 );
358
359 let filter = unsafe { Filter::wrap(filter_ptr) };
361 let name = "buffer_filter";
362 let args = "width=1920:height=1080:pix_fmt=0:time_base=1/30";
363 let result = filter_graph.add(filter, name, args);
364
365 assert!(
366 result.is_ok(),
367 "FilterGraph::add should successfully add a filter to the graph"
368 );
369
370 if let Ok(context) = result {
371 assert!(
372 !context.0.filter.is_null(),
373 "The filter context should have a valid filter pointer"
374 );
375 }
376 }
377
378 #[test]
379 fn test_filter_graph_get() {
380 let mut filter_graph = FilterGraph::new().expect("Failed to create filter graph");
381 let filter_name = "buffer";
382 let filter_ptr = unsafe { avfilter_get_by_name(CString::new(filter_name).unwrap().as_ptr()) };
384 assert!(
385 !filter_ptr.is_null(),
386 "avfilter_get_by_name should return a valid pointer for filter '{filter_name}'"
387 );
388
389 let filter = unsafe { Filter::wrap(filter_ptr) };
391 let name = "buffer_filter";
392 let args = "width=1920:height=1080:pix_fmt=0:time_base=1/30";
393 filter_graph
394 .add(filter, name, args)
395 .expect("Failed to add filter to the graph");
396
397 let result = filter_graph.get(name);
398 assert!(
399 result.is_some(),
400 "FilterGraph::get should return Some(FilterContext) for an existing filter"
401 );
402
403 if let Some(filter_context) = result {
404 assert!(
405 !filter_context.0.filter.is_null(),
406 "The retrieved FilterContext should have a valid filter pointer"
407 );
408 }
409
410 let non_existent = filter_graph.get("non_existent_filter");
411 assert!(
412 non_existent.is_none(),
413 "FilterGraph::get should return None for a non-existent filter"
414 );
415 }
416
417 #[test]
418 fn test_filter_graph_validate_and_dump() {
419 let mut filter_graph = FilterGraph::new().expect("Failed to create filter graph");
420 let filter_spec = "anullsrc=sample_rate=44100:channel_layout=stereo [out0]; [out0] anullsink";
421 FilterGraphParser::new(&mut filter_graph)
422 .parse(filter_spec)
423 .expect("Failed to parse filter graph spec");
424
425 filter_graph.validate().expect("FilterGraph::validate should succeed");
426 let dump_output = filter_graph.dump().expect("Failed to dump the filter graph");
427
428 assert!(
429 dump_output.contains("anullsrc"),
430 "Dump output should include the 'anullsrc' filter type"
431 );
432 assert!(
433 dump_output.contains("anullsink"),
434 "Dump output should include the 'anullsink' filter type"
435 );
436 }
437
438 #[test]
439 fn test_filter_graph_set_thread_count() {
440 let mut filter_graph = FilterGraph::new().expect("Failed to create filter graph");
441 filter_graph.set_thread_count(4);
442 assert_eq!(
443 unsafe { (*filter_graph.as_mut_ptr()).nb_threads },
445 4,
446 "Thread count should be set to 4"
447 );
448
449 filter_graph.set_thread_count(8);
450 assert_eq!(
451 unsafe { (*filter_graph.as_mut_ptr()).nb_threads },
453 8,
454 "Thread count should be set to 8"
455 );
456 }
457
458 #[test]
459 fn test_filter_graph_input() {
460 let mut filter_graph = FilterGraph::new().expect("Failed to create filter graph");
461 let anullsrc = Filter::get("anullsrc").expect("Failed to get 'anullsrc' filter");
462 filter_graph
463 .add(anullsrc, "src", "sample_rate=44100:channel_layout=stereo")
464 .expect("Failed to add 'anullsrc' filter");
465 let input_parser = filter_graph
466 .input("src", 0)
467 .expect("Failed to set input for the filter graph");
468
469 assert!(
470 std::ptr::eq(input_parser.graph.as_ptr(), filter_graph.as_ptr()),
471 "Input parser should belong to the same filter graph"
472 );
473 }
474
475 #[test]
476 fn test_filter_graph_output() {
477 let mut filter_graph = FilterGraph::new().expect("Failed to create filter graph");
478 let anullsink = Filter::get("anullsink").expect("Failed to get 'anullsink' filter");
479 filter_graph
480 .add(anullsink, "sink", "")
481 .expect("Failed to add 'anullsink' filter");
482 let output_parser = filter_graph
483 .output("sink", 0)
484 .expect("Failed to set output for the filter graph");
485
486 assert!(
487 std::ptr::eq(output_parser.graph.as_ptr(), filter_graph.as_ptr()),
488 "Output parser should belong to the same filter graph"
489 );
490 }
491
492 #[test]
493 fn test_filter_context_source() {
494 let mut filter_graph = FilterGraph::new().expect("Failed to create filter graph");
495 let anullsrc = Filter::get("anullsrc").expect("Failed to get 'anullsrc' filter");
496 filter_graph
497 .add(anullsrc, "src", "sample_rate=44100:channel_layout=stereo")
498 .expect("Failed to add 'anullsrc' filter");
499 let filter_context = filter_graph.get("src").expect("Failed to retrieve 'src' filter context");
500 let source_context = filter_context.source();
501
502 assert!(
503 std::ptr::eq(source_context.0, filter_graph.get("src").unwrap().0),
504 "Source context should wrap the same filter as the original filter context"
505 );
506 }
507
508 #[test]
509 fn test_filter_context_sink() {
510 let mut filter_graph = FilterGraph::new().expect("Failed to create filter graph");
511 let anullsink = Filter::get("anullsink").expect("Failed to get 'anullsink' filter");
512 filter_graph
513 .add(anullsink, "sink", "")
514 .expect("Failed to add 'anullsink' filter");
515 let filter_context = filter_graph.get("sink").expect("Failed to retrieve 'sink' filter context");
516 let sink_context = filter_context.sink();
517
518 assert!(
519 std::ptr::eq(sink_context.0, filter_graph.get("sink").unwrap().0),
520 "Sink context should wrap the same filter as the original filter context"
521 );
522 }
523
524 #[test]
525 fn test_filter_context_source_send_and_receive_frame() {
526 let mut filter_graph = FilterGraph::new().expect("Failed to create filter graph");
527 let filter_spec = "\
528 abuffer=sample_rate=44100:sample_fmt=s16:channel_layout=stereo:time_base=1/44100 \
529 [out]; \
530 [out] abuffersink";
531 FilterGraphParser::new(&mut filter_graph)
532 .parse(filter_spec)
533 .expect("Failed to parse filter graph spec");
534 filter_graph.validate().expect("Failed to validate filter graph");
535
536 let source_context_name = "Parsed_abuffer_0";
537 let sink_context_name = "Parsed_abuffersink_1";
538
539 let frame = AudioFrame::builder()
540 .sample_fmt(AVSampleFormat::S16)
541 .nb_samples(1024)
542 .sample_rate(44100)
543 .channel_layout(AudioChannelLayout::new(2).expect("Failed to create a new AudioChannelLayout"))
544 .build()
545 .expect("Failed to create a new AudioFrame");
546
547 let mut source_context = filter_graph
548 .get(source_context_name)
549 .expect("Failed to retrieve source filter context")
550 .source();
551
552 let result = source_context.send_frame(&frame);
553 assert!(result.is_ok(), "send_frame should succeed when sending a valid frame");
554
555 let mut sink_context = filter_graph
556 .get(sink_context_name)
557 .expect("Failed to retrieve sink filter context")
558 .sink();
559 let received_frame = sink_context
560 .receive_frame()
561 .expect("Failed to receive frame from sink context");
562
563 assert!(received_frame.is_some(), "No frame received from sink context");
564
565 insta::assert_debug_snapshot!(received_frame.unwrap(), @r"
566 GenericFrame {
567 pts: None,
568 dts: None,
569 duration: Some(
570 1024,
571 ),
572 best_effort_timestamp: None,
573 time_base: Rational {
574 numerator: 0,
575 denominator: 1,
576 },
577 format: 1,
578 is_audio: true,
579 is_video: false,
580 }
581 ");
582 }
583
584 #[test]
585 fn test_filter_context_source_send_frame_error() {
586 let mut filter_graph = FilterGraph::new().expect("Failed to create filter graph");
587 let filter_spec = "\
588 abuffer=sample_rate=44100:sample_fmt=s16:channel_layout=stereo:time_base=1/44100 \
589 [out]; \
590 [out] anullsink";
591 FilterGraphParser::new(&mut filter_graph)
592 .parse(filter_spec)
593 .expect("Failed to parse filter graph spec");
594 filter_graph.validate().expect("Failed to validate filter graph");
595
596 let mut source_context = filter_graph
597 .get("Parsed_abuffer_0")
598 .expect("Failed to retrieve 'Parsed_abuffer_0' filter context")
599 .source();
600
601 let mut frame = GenericFrame::new().expect("Failed to create frame");
603 unsafe { frame.as_mut_ptr().as_mut().unwrap().format = AVSampleFormat::Fltp.into() };
605 let result = source_context.send_frame(&frame);
606
607 assert!(result.is_err(), "send_frame should fail when sending an invalid frame");
608 }
609
610 #[test]
611 fn test_filter_context_source_send_and_receive_eof() {
612 let mut filter_graph = FilterGraph::new().expect("Failed to create filter graph");
613 let filter_spec = "\
614 abuffer=sample_rate=44100:sample_fmt=s16:channel_layout=stereo:time_base=1/44100 \
615 [out]; \
616 [out] abuffersink";
617 FilterGraphParser::new(&mut filter_graph)
618 .parse(filter_spec)
619 .expect("Failed to parse filter graph spec");
620 filter_graph.validate().expect("Failed to validate filter graph");
621
622 let source_context_name = "Parsed_abuffer_0";
623 let sink_context_name = "Parsed_abuffersink_1";
624
625 {
626 let mut source_context = filter_graph
627 .get(source_context_name)
628 .expect("Failed to retrieve source filter context")
629 .source();
630 let eof_result_with_pts = source_context.send_eof(Some(12345));
631 assert!(eof_result_with_pts.is_ok(), "send_eof with PTS should succeed");
632
633 let eof_result_without_pts = source_context.send_eof(None);
634 assert!(eof_result_without_pts.is_ok(), "send_eof without PTS should succeed");
635 }
636
637 {
638 let mut sink_context = filter_graph
639 .get(sink_context_name)
640 .expect("Failed to retrieve sink filter context")
641 .sink();
642 let received_frame = sink_context.receive_frame();
643 assert!(received_frame.is_ok(), "receive_frame should succeed after EOF is sent");
644 assert!(received_frame.unwrap().is_none(), "No frame should be received after EOF");
645 }
646 }
647}