scuffle_ffmpeg/io/
internal.rs

1use aliasable::boxed::AliasableBox;
2use libc::c_void;
3
4use crate::error::{FfmpegError, FfmpegErrorCode};
5use crate::ffi::*;
6use crate::smart_object::SmartPtr;
7use crate::{AVIOFlag, AVSeekWhence};
8
9const AVERROR_IO: i32 = AVERROR(EIO);
10
11/// Safety: The function must be used with the same type as the one used to
12/// generically create the function pointer
13pub(crate) unsafe extern "C" fn read_packet<T: std::io::Read>(
14    opaque: *mut libc::c_void,
15    buf: *mut u8,
16    buf_size: i32,
17) -> i32 {
18    // Safety: The pointer is valid given the way this function is constructed, the opaque pointer is a pointer to a T.
19    let this = unsafe { &mut *(opaque as *mut T) };
20    // Safety: the buffer has at least `buf_size` bytes.
21    let buffer = unsafe { std::slice::from_raw_parts_mut(buf, buf_size as usize) };
22
23    let ret = this.read(buffer).map(|n| n as i32).unwrap_or(AVERROR_IO);
24
25    if ret == 0 {
26        return AVERROR_EOF;
27    }
28
29    ret
30}
31
32/// Safety: The function must be used with the same type as the one used to
33/// generically create the function pointer
34pub(crate) unsafe extern "C" fn write_packet<T: std::io::Write>(
35    opaque: *mut libc::c_void,
36    buf: *const u8,
37    buf_size: i32,
38) -> i32 {
39    // Safety: The pointer is valid given the way this function is constructed, the opaque pointer is a pointer to a T.
40    let this = unsafe { &mut *(opaque as *mut T) };
41    // Safety: the buffer has at least `buf_size` bytes.
42    let buffer = unsafe { std::slice::from_raw_parts(buf, buf_size as usize) };
43
44    this.write(buffer).map(|n| n as i32).unwrap_or(AVERROR_IO)
45}
46
47/// Safety: The function must be used with the same type as the one used to
48/// generically create the function pointer
49pub(crate) unsafe extern "C" fn seek<T: std::io::Seek>(opaque: *mut libc::c_void, offset: i64, whence: i32) -> i64 {
50    // Safety: The pointer is valid given the way this function is constructed, the opaque pointer is a pointer to a T.
51    let this = unsafe { &mut *(opaque as *mut T) };
52
53    let mut whence = AVSeekWhence(whence);
54
55    let seek_size = whence & AVSeekWhence::Size != 0;
56    if seek_size {
57        whence &= !AVSeekWhence::Size;
58    }
59
60    let seek_force = whence & AVSeekWhence::Force != 0;
61    if seek_force {
62        whence &= !AVSeekWhence::Force;
63    }
64
65    if seek_size {
66        let Ok(pos) = this.stream_position() else {
67            return AVERROR_IO as i64;
68        };
69
70        let Ok(end) = this.seek(std::io::SeekFrom::End(0)) else {
71            return AVERROR_IO as i64;
72        };
73
74        if end != pos {
75            let Ok(_) = this.seek(std::io::SeekFrom::Start(pos)) else {
76                return AVERROR_IO as i64;
77            };
78        }
79
80        return end as i64;
81    }
82
83    let whence = match whence {
84        AVSeekWhence::Start => std::io::SeekFrom::Start(offset as u64),
85        AVSeekWhence::Current => std::io::SeekFrom::Current(offset),
86        AVSeekWhence::End => std::io::SeekFrom::End(offset),
87        _ => return -1,
88    };
89
90    match this.seek(whence) {
91        Ok(pos) => pos as i64,
92        Err(_) => AVERROR_IO as i64,
93    }
94}
95
96pub(crate) struct Inner<T: Send + Sync> {
97    pub(crate) data: Option<AliasableBox<T>>,
98    pub(crate) context: SmartPtr<AVFormatContext>,
99    _io: SmartPtr<AVIOContext>,
100}
101
102pub(crate) struct InnerOptions {
103    pub(crate) buffer_size: usize,
104    pub(crate) read_fn: Option<unsafe extern "C" fn(*mut c_void, *mut u8, i32) -> i32>,
105    pub(crate) write_fn: Option<unsafe extern "C" fn(*mut c_void, *const u8, i32) -> i32>,
106    pub(crate) seek_fn: Option<unsafe extern "C" fn(*mut c_void, i64, i32) -> i64>,
107    pub(crate) output_format: *const AVOutputFormat,
108}
109
110impl Default for InnerOptions {
111    fn default() -> Self {
112        Self {
113            buffer_size: 4096,
114            read_fn: None,
115            write_fn: None,
116            seek_fn: None,
117            output_format: std::ptr::null(),
118        }
119    }
120}
121
122impl<T: Send + Sync> Inner<T> {
123    /// Creates a new `Inner` instance.
124    pub(crate) fn new(data: T, options: InnerOptions) -> Result<Self, FfmpegError> {
125        // Safety: av_malloc is safe to call
126        let buffer = unsafe { av_malloc(options.buffer_size) };
127
128        fn buffer_destructor(ptr: &mut *mut c_void) {
129            // We own this resource so we need to free it
130            // Safety: this buffer was allocated via `av_malloc` so we need to free it.
131            unsafe { av_free(*ptr) };
132            // We clear the old pointer so it doesn't get freed again.
133            *ptr = std::ptr::null_mut();
134        }
135
136        // This is only a temporary smart_ptr because we will change the ownership to be owned by the io context. & freed, by the io context.
137        // Safety: av_malloc gives a valid pointer & the destructor has been setup to free the buffer.
138        let buffer = unsafe { SmartPtr::wrap_non_null(buffer, buffer_destructor) }.ok_or(FfmpegError::Alloc)?;
139
140        let mut data = AliasableBox::from_unique(Box::new(data));
141
142        // Safety: avio_alloc_context is safe to call, and all the function pointers are valid
143        let destructor = |ptr: &mut *mut AVIOContext| {
144            // Safety: the pointer is always valid.
145            let mut_ref = unsafe { ptr.as_mut() };
146            if let Some(ptr) = mut_ref {
147                buffer_destructor(&mut (ptr.buffer as *mut c_void));
148            }
149
150            // Safety: avio_context_free is safe to call & the pointer was allocated by `av_create_io_context`
151            unsafe { avio_context_free(ptr) };
152            *ptr = std::ptr::null_mut();
153        };
154
155        // Safety: avio_alloc_context is safe to call & the destructor has been setup to free the buffer.
156        let io = unsafe {
157            avio_alloc_context(
158                buffer.as_ptr() as *mut u8,
159                options.buffer_size as i32,
160                if options.write_fn.is_some() { 1 } else { 0 },
161                data.as_mut() as *mut _ as *mut c_void,
162                options.read_fn,
163                options.write_fn,
164                options.seek_fn,
165            )
166        };
167
168        // Safety: `avio_alloc_context` returns a valid pointer & the destructor has been setup to free both the buffer & the io context.
169        let mut io = unsafe { SmartPtr::wrap_non_null(io, destructor) }.ok_or(FfmpegError::Alloc)?;
170
171        // The buffer is now owned by the IO context. We need to go into_inner here to prevent the destructor from being called before we use it.
172        buffer.into_inner();
173
174        let mut context = if options.write_fn.is_some() {
175            let mut context = SmartPtr::null(|mut_ref| {
176                let ptr = *mut_ref;
177                // Safety: The pointer here is valid.
178                unsafe { avformat_free_context(ptr) };
179                *mut_ref = std::ptr::null_mut();
180            });
181
182            // Safety: avformat_alloc_output_context2 is safe to call
183            FfmpegErrorCode(unsafe {
184                avformat_alloc_output_context2(
185                    context.as_mut(),
186                    options.output_format,
187                    std::ptr::null(),
188                    std::ptr::null_mut(),
189                )
190            })
191            .result()?;
192
193            if context.as_ptr().is_null() {
194                return Err(FfmpegError::Alloc);
195            }
196
197            context
198        } else {
199            // Safety: avformat_alloc_context is safe to call
200            let context = unsafe { avformat_alloc_context() };
201
202            let destructor = |mut_ref: &mut *mut AVFormatContext| {
203                let ptr = *mut_ref;
204                // Safety: The pointer here is valid and was allocated by `avformat_alloc_context`.
205                unsafe { avformat_free_context(ptr) };
206                *mut_ref = std::ptr::null_mut();
207            };
208
209            // Safety: `avformat_alloc_context` returns a valid pointer & the destructor has been setup to free the context.
210            unsafe { SmartPtr::wrap_non_null(context, destructor) }.ok_or(FfmpegError::Alloc)?
211        };
212
213        // The io context will live as long as the format context
214        context.as_deref_mut().expect("Context is null").pb = io.as_mut_ptr();
215
216        Ok(Self {
217            data: Some(data),
218            context,
219            _io: io,
220        })
221    }
222}
223
224impl Inner<()> {
225    /// Empty context cannot be used until its initialized and setup correctly
226    /// Safety: this function is marked as unsafe because it must be initialized and setup correctltly before returning it to the user.
227    pub(crate) unsafe fn empty() -> Self {
228        Self {
229            data: Some(Box::new(()).into()),
230            context: SmartPtr::null(|mut_ref| {
231                // We own this resource so we need to free it
232                let ptr = *mut_ref;
233                // Safety: The pointer here is valid.
234                unsafe { avformat_free_context(ptr) };
235                *mut_ref = std::ptr::null_mut();
236            }),
237            _io: SmartPtr::null(|_| {}),
238        }
239    }
240
241    /// Opens an output stream to a file path.
242    pub(crate) fn open_output(path: &str) -> Result<Self, FfmpegError> {
243        let path = std::ffi::CString::new(path).expect("Failed to convert path to CString");
244
245        // Safety: We immediately initialize the inner and setup the context.
246        let mut this = unsafe { Self::empty() };
247
248        // Safety: avformat_alloc_output_context2 is safe to call and all arguments are valid
249        FfmpegErrorCode(unsafe {
250            avformat_alloc_output_context2(this.context.as_mut(), std::ptr::null(), std::ptr::null(), path.as_ptr())
251        })
252        .result()?;
253
254        // We are not moving the pointer so this is safe
255        if this.context.as_ptr().is_null() {
256            return Err(FfmpegError::Alloc);
257        }
258
259        // Safety: avio_open is safe to call and all arguments are valid
260        FfmpegErrorCode(unsafe {
261            avio_open(
262                &mut this.context.as_deref_mut_except().pb,
263                path.as_ptr(),
264                AVIOFlag::Write.into(),
265            )
266        })
267        .result()?;
268
269        this.context.set_destructor(|mut_ref| {
270            // We own this resource so we need to free it
271            let ptr = *mut_ref;
272            // Safety: The pointer here is valid.
273            let mut_ptr_ref = unsafe { ptr.as_mut() };
274
275            if let Some(mut_ptr_ref) = mut_ptr_ref {
276                // Safety: The pointer here is valid. And we need to clean this up before we do `avformat_free_context`.
277                unsafe { avio_closep(&mut mut_ptr_ref.pb) };
278            }
279
280            // Safety: The pointer here is valid.
281            unsafe { avformat_free_context(ptr) };
282            *mut_ref = std::ptr::null_mut();
283        });
284
285        Ok(this)
286    }
287}
288
289#[cfg(test)]
290#[cfg_attr(all(test, coverage_nightly), coverage(off))]
291mod tests {
292    use std::ffi::CString;
293    use std::io::Cursor;
294    use std::sync::Once;
295    use std::sync::atomic::{AtomicUsize, Ordering};
296
297    use libc::c_void;
298    use tempfile::Builder;
299
300    use crate::AVSeekWhence;
301    use crate::error::FfmpegError;
302    use crate::ffi::av_guess_format;
303    use crate::io::internal::{AVERROR_EOF, Inner, InnerOptions, read_packet, seek, write_packet};
304
305    #[test]
306    fn test_read_packet_eof() {
307        let mut data: Cursor<Vec<u8>> = Cursor::new(vec![]);
308        let mut buf = [0u8; 10];
309
310        // Safety: The pointer is valid.
311        unsafe {
312            let result =
313                read_packet::<Cursor<Vec<u8>>>((&raw mut data) as *mut libc::c_void, buf.as_mut_ptr(), buf.len() as i32);
314
315            assert_eq!(result, AVERROR_EOF);
316        }
317    }
318
319    #[test]
320    fn test_write_packet_success() {
321        let mut data = Cursor::new(vec![0u8; 10]);
322        let buf = [1u8, 2, 3, 4, 5];
323
324        // Safety: The pointer is valid.
325        unsafe {
326            let result = write_packet::<Cursor<Vec<u8>>>((&raw mut data) as *mut c_void, buf.as_ptr(), buf.len() as i32);
327            assert_eq!(result, buf.len() as i32);
328
329            let written_data = data.get_ref();
330            assert_eq!(&written_data[..buf.len()], &buf);
331        }
332    }
333
334    #[test]
335    fn test_seek_force() {
336        let mut cursor = Cursor::new(vec![0u8; 100]);
337        let opaque = &raw mut cursor as *mut c_void;
338        assert_eq!(cursor.position(), 0);
339        let offset = 10;
340        let mut whence = AVSeekWhence::Current | AVSeekWhence::Force;
341        // Safety: The pointer is valid.
342        let result = unsafe { seek::<Cursor<Vec<u8>>>(opaque, offset, whence.into()) };
343
344        assert_eq!(result, { offset });
345        whence &= !AVSeekWhence::Force;
346        assert_eq!(whence, AVSeekWhence::Current);
347        assert_eq!(cursor.position(), offset as u64);
348    }
349
350    #[test]
351    fn test_seek_seek_end() {
352        let mut cursor = Cursor::new(vec![0u8; 100]);
353        let opaque = &raw mut cursor as *mut libc::c_void;
354        let offset = -10;
355        // Safety: The pointer is valid.
356        let result = unsafe { seek::<Cursor<Vec<u8>>>(opaque, offset, AVSeekWhence::End.into()) };
357
358        assert_eq!(result, 90);
359        assert_eq!(cursor.position(), 90);
360    }
361
362    #[test]
363    fn test_seek_invalid_whence() {
364        let mut cursor = Cursor::new(vec![0u8; 100]);
365        let opaque = &raw mut cursor as *mut libc::c_void;
366        // Safety: The pointer is valid.
367        let result = unsafe { seek::<Cursor<Vec<u8>>>(opaque, 0, 999) };
368
369        assert_eq!(result, -1);
370        assert_eq!(cursor.position(), 0);
371    }
372
373    #[test]
374    fn test_avformat_alloc_output_context2_error() {
375        static BUF_SIZE_TRACKER: AtomicUsize = AtomicUsize::new(0);
376        static CALL_COUNT: AtomicUsize = AtomicUsize::new(0);
377        static INIT: Once = Once::new();
378
379        INIT.call_once(|| {
380            BUF_SIZE_TRACKER.store(0, Ordering::SeqCst);
381            CALL_COUNT.store(0, Ordering::SeqCst);
382        });
383
384        unsafe extern "C" fn dummy_write_fn(_opaque: *mut libc::c_void, _buf: *const u8, _buf_size: i32) -> i32 {
385            CALL_COUNT.fetch_add(1, Ordering::SeqCst);
386            BUF_SIZE_TRACKER.store(_buf_size as usize, Ordering::SeqCst);
387            0 // simulate success
388        }
389
390        let invalid_format = CString::new("invalid_format").expect("Failed to create CString");
391        let options = InnerOptions {
392            buffer_size: 4096,
393            write_fn: Some(dummy_write_fn),
394            // Safety: av_guess_format is safe to call
395            output_format: unsafe { av_guess_format(invalid_format.as_ptr(), std::ptr::null(), std::ptr::null()) },
396            ..Default::default()
397        };
398        let data = ();
399        let result = Inner::new(data, options);
400
401        assert!(result.is_err(), "Expected an error but got Ok");
402
403        let call_count = CALL_COUNT.load(Ordering::SeqCst);
404        assert_eq!(call_count, 0, "Expected dummy_write_fn to not be called.");
405
406        if let Err(error) = result {
407            match error {
408                FfmpegError::Code(_) => {
409                    eprintln!("Expected avformat_alloc_output_context2 error occurred.");
410                }
411                _ => panic!("Unexpected error variant: {error:?}"),
412            }
413        }
414    }
415
416    #[test]
417    fn test_open_output_valid_path() {
418        let temp_file = Builder::new()
419            .suffix(".mp4")
420            .tempfile()
421            .expect("Failed to create a temporary file");
422        let test_path = temp_file.path();
423        let result = Inner::open_output(test_path.to_str().unwrap());
424
425        assert!(result.is_ok(), "Expected success but got error");
426    }
427
428    #[test]
429    fn test_open_output_invalid_path() {
430        let test_path = "";
431        let result = Inner::open_output(test_path);
432
433        assert!(result.is_err(), "Expected Err, got Ok");
434    }
435
436    #[test]
437    fn test_open_output_avformat_alloc_error() {
438        let test_path = tempfile::tempdir().unwrap().path().join("restricted_output.mp4");
439        let test_path_str = test_path.to_str().unwrap();
440        let result = Inner::open_output(test_path_str);
441        if let Err(error) = &result {
442            eprintln!("Function returned an error: {error:?}");
443        }
444
445        assert!(
446            matches!(result, Err(FfmpegError::Code(_))),
447            "Expected FfmpegError::Code but received a different error."
448        );
449    }
450}