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
11pub(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 let this = unsafe { &mut *(opaque as *mut T) };
20 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
32pub(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 let this = unsafe { &mut *(opaque as *mut T) };
41 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
47pub(crate) unsafe extern "C" fn seek<T: std::io::Seek>(opaque: *mut libc::c_void, offset: i64, whence: i32) -> i64 {
50 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 pub(crate) fn new(data: T, options: InnerOptions) -> Result<Self, FfmpegError> {
125 let buffer = unsafe { av_malloc(options.buffer_size) };
127
128 fn buffer_destructor(ptr: &mut *mut c_void) {
129 unsafe { av_free(*ptr) };
132 *ptr = std::ptr::null_mut();
134 }
135
136 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 let destructor = |ptr: &mut *mut AVIOContext| {
144 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 unsafe { avio_context_free(ptr) };
152 *ptr = std::ptr::null_mut();
153 };
154
155 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 let mut io = unsafe { SmartPtr::wrap_non_null(io, destructor) }.ok_or(FfmpegError::Alloc)?;
170
171 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 unsafe { avformat_free_context(ptr) };
179 *mut_ref = std::ptr::null_mut();
180 });
181
182 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 let context = unsafe { avformat_alloc_context() };
201
202 let destructor = |mut_ref: &mut *mut AVFormatContext| {
203 let ptr = *mut_ref;
204 unsafe { avformat_free_context(ptr) };
206 *mut_ref = std::ptr::null_mut();
207 };
208
209 unsafe { SmartPtr::wrap_non_null(context, destructor) }.ok_or(FfmpegError::Alloc)?
211 };
212
213 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 pub(crate) unsafe fn empty() -> Self {
228 Self {
229 data: Some(Box::new(()).into()),
230 context: SmartPtr::null(|mut_ref| {
231 let ptr = *mut_ref;
233 unsafe { avformat_free_context(ptr) };
235 *mut_ref = std::ptr::null_mut();
236 }),
237 _io: SmartPtr::null(|_| {}),
238 }
239 }
240
241 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 let mut this = unsafe { Self::empty() };
247
248 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 if this.context.as_ptr().is_null() {
256 return Err(FfmpegError::Alloc);
257 }
258
259 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 let ptr = *mut_ref;
272 let mut_ptr_ref = unsafe { ptr.as_mut() };
274
275 if let Some(mut_ptr_ref) = mut_ptr_ref {
276 unsafe { avio_closep(&mut mut_ptr_ref.pb) };
278 }
279
280 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 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 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 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 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 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 }
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 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}