scuffle_ffmpeg/
resampler.rs

1use rusty_ffmpeg::ffi::{SwrContext, swr_alloc_set_opts2, swr_convert_frame, swr_free, swr_init};
2
3use crate::enums::AVSampleFormat;
4use crate::error::{FfmpegError, FfmpegErrorCode};
5use crate::frame::{AudioChannelLayout, AudioFrame, GenericFrame};
6use crate::smart_object::SmartPtr;
7
8/// A wrapper around an [`SwrContext`]. Which is used to resample and convert [`AudioFrame`]s.
9pub struct Resampler {
10    ptr: SmartPtr<SwrContext>,
11    channel_layout: AudioChannelLayout,
12    sample_fmt: AVSampleFormat,
13    sample_rate: i32,
14}
15
16impl Resampler {
17    /// Create a new [`Resampler`] instance
18    pub fn new(
19        input_ch_layout: AudioChannelLayout,
20        input_sample_fmt: AVSampleFormat,
21        input_sample_rate: i32,
22        output_ch_layout: AudioChannelLayout,
23        output_sample_fmt: AVSampleFormat,
24        output_sample_rate: i32,
25    ) -> Result<Self, FfmpegError> {
26        let mut ptr = core::ptr::null_mut::<SwrContext>();
27
28        // Safety: swr_alloc_set_opts2 is safe to call
29        FfmpegErrorCode(unsafe {
30            swr_alloc_set_opts2(
31                &mut ptr,
32                output_ch_layout.as_ptr(),
33                output_sample_fmt.into(),
34                output_sample_rate,
35                input_ch_layout.as_ptr(),
36                input_sample_fmt.into(),
37                input_sample_rate,
38                0,
39                core::ptr::null::<core::ffi::c_void>() as _,
40            )
41        })
42        .result()?;
43
44        let destructor = |ctx: &mut *mut SwrContext| {
45            // Safety: swr_free is safe to call
46            unsafe { swr_free(ctx) };
47        };
48
49        // Safety: this is safe to call
50        let mut ptr = unsafe { SmartPtr::wrap_non_null(ptr, destructor).ok_or(FfmpegError::Alloc) }?;
51
52        // Safety: ptr is initialized, swr_init is safe to call
53        FfmpegErrorCode(unsafe { swr_init(ptr.as_mut_ptr()) }).result()?;
54
55        Ok(Self {
56            ptr,
57            channel_layout: output_ch_layout,
58            sample_fmt: output_sample_fmt,
59            sample_rate: output_sample_rate,
60        })
61    }
62
63    /// Process an [`AudioFrame`] thought the resampler
64    pub fn process(&mut self, input: &AudioFrame) -> Result<AudioFrame, FfmpegError> {
65        let mut out = GenericFrame::new()?;
66
67        // Safety: the GenericFrame is allocated
68        let inner = unsafe { out.as_mut_ptr().as_mut() }.expect("inner pointer of GenericFrame was invalid");
69        inner.ch_layout = self.channel_layout().copy()?.into_inner();
70        inner.format = self.sample_format().into();
71        inner.sample_rate = self.sample_rate();
72
73        // Safety: self.ptr is initialized and valid, data buffers of out get initialized here, swr_convert_frame is safe to call
74        FfmpegErrorCode(unsafe { swr_convert_frame(self.ptr.as_mut_ptr(), out.as_mut_ptr(), input.as_ptr()) }).result()?;
75
76        // Safety: swr_convert_frame was successful, the pointer is valid;
77        Ok(out.audio())
78    }
79
80    /// The output channel layout
81    pub const fn channel_layout(&self) -> &AudioChannelLayout {
82        &self.channel_layout
83    }
84
85    /// The output sample format
86    pub const fn sample_format(&self) -> AVSampleFormat {
87        self.sample_fmt
88    }
89
90    /// The output sample rate
91    pub const fn sample_rate(&self) -> i32 {
92        self.sample_rate
93    }
94}
95
96#[cfg(test)]
97#[cfg_attr(all(test, coverage_nightly), coverage(off))]
98mod tests {
99    use rand::{Rng, rng};
100    use rusty_ffmpeg::ffi::swr_is_initialized;
101
102    use super::Resampler;
103    use crate::AVSampleFormat;
104    use crate::frame::{AudioChannelLayout, AudioFrame};
105
106    #[test]
107    fn test_resampler_new() {
108        let input_layout = AudioChannelLayout::new(1).expect("Failed to create new AudioChannelLayout");
109        let input_format = AVSampleFormat::S16;
110        let input_sample_rate = 44100;
111
112        let output_layout = AudioChannelLayout::new(2).expect("Failed to create new AudioChannelLayout");
113        let output_format = AVSampleFormat::S16p;
114        let output_sample_rate = 48000;
115
116        let mut resampler = Resampler::new(
117            input_layout,
118            input_format,
119            input_sample_rate,
120            output_layout,
121            output_format,
122            output_sample_rate,
123        )
124        .expect("Failed to create new Resampler");
125
126        // Safety: swr_is_initialized is safe to call
127        let is_init = unsafe { swr_is_initialized(resampler.ptr.as_mut_ptr()) };
128
129        assert!(is_init.is_positive() && is_init != 0, "Resampler is not initialized")
130    }
131
132    #[test]
133    fn test_resampler_process() {
134        let input_layout = AudioChannelLayout::new(1).expect("Failed to create new AudioChannelLayout");
135        let input_format = AVSampleFormat::S16;
136        let input_sample_rate = 44100;
137
138        let output_layout = AudioChannelLayout::new(2).expect("Failed to create new AudioChannelLayout");
139        let output_format = AVSampleFormat::S16p;
140        let output_sample_rate = 48000;
141
142        let mut resampler = Resampler::new(
143            input_layout.copy().unwrap(),
144            input_format,
145            input_sample_rate,
146            output_layout,
147            output_format,
148            output_sample_rate,
149        )
150        .expect("Failed to create new Resampler");
151
152        let mut input_frame = AudioFrame::builder()
153            .nb_samples(1024)
154            .channel_layout(input_layout)
155            .sample_fmt(input_format)
156            .sample_rate(44100)
157            .build()
158            .expect("Failed to create input AudioFrame");
159
160        let input_data = input_frame.data_mut(0).expect("Data buffer of input frame was invalid");
161        rng().fill(input_data);
162
163        let output = resampler.process(&input_frame).expect("Failed to process frame");
164
165        assert_eq!(output.channel_count(), 2, "Output channel count should be 2");
166        assert!(output.data(0).is_some(), "First data buffer of output frame is None");
167        assert!(output.data(1).is_some(), "Second data buffer of output frame is None");
168        assert_eq!(output.sample_rate(), 48000, "Output sample rate was not 48000");
169    }
170}