use std::ffi::CString;
use std::os::raw::c_int;
use std::path::Path;
use std::ptr;
use std::time::Duration;
use crate::{
AVFormatContext, AVIOContext, AVPacket, av_read_frame as ffi_av_read_frame,
av_seek_frame as ffi_av_seek_frame, av_write_frame as ffi_av_write_frame,
avformat_close_input as ffi_avformat_close_input,
avformat_find_stream_info as ffi_avformat_find_stream_info,
avformat_open_input as ffi_avformat_open_input, avformat_seek_file as ffi_avformat_seek_file,
ensure_initialized,
};
unsafe extern "C" {
fn avio_open(s: *mut *mut AVIOContext, url: *const std::os::raw::c_char, flags: c_int)
-> c_int;
fn avio_closep(s: *mut *mut AVIOContext);
}
pub mod avio_flags {
use std::os::raw::c_int;
pub const READ: c_int = crate::AVIO_FLAG_READ as c_int;
pub const WRITE: c_int = crate::AVIO_FLAG_WRITE as c_int;
pub const READ_WRITE: c_int = crate::AVIO_FLAG_READ_WRITE as c_int;
}
pub mod seek_flags {
pub const BACKWARD: i32 = crate::AVSEEK_FLAG_BACKWARD as i32;
pub const BYTE: i32 = crate::AVSEEK_FLAG_BYTE as i32;
pub const ANY: i32 = crate::AVSEEK_FLAG_ANY as i32;
pub const FRAME: i32 = crate::AVSEEK_FLAG_FRAME as i32;
}
pub unsafe fn open_input(path: &Path) -> Result<*mut AVFormatContext, c_int> {
ensure_initialized();
let path_str = match path.to_str() {
Some(s) => s,
None => return Err(crate::error_codes::EINVAL),
};
let c_path = match CString::new(path_str) {
Ok(s) => s,
Err(_) => return Err(crate::error_codes::EINVAL),
};
let mut ctx: *mut AVFormatContext = ptr::null_mut();
let ret = ffi_avformat_open_input(&mut ctx, c_path.as_ptr(), ptr::null(), ptr::null_mut());
if ret < 0 {
return Err(ret);
}
Ok(ctx)
}
pub unsafe fn open_input_url(
url: &str,
connect_timeout: Duration,
read_timeout: Duration,
) -> Result<*mut AVFormatContext, c_int> {
ensure_initialized();
let c_url = CString::new(url).map_err(|_| crate::error_codes::EINVAL)?;
let mut opts: *mut crate::AVDictionary = ptr::null_mut();
let timeout_key = CString::new("timeout").expect("no null in literal");
let rw_timeout_key = CString::new("rw_timeout").expect("no null in literal");
let timeout_val = CString::new(connect_timeout.as_micros().to_string())
.expect("decimal string has no null bytes");
let rw_timeout_val = CString::new(read_timeout.as_micros().to_string())
.expect("decimal string has no null bytes");
unsafe {
crate::av_dict_set(
ptr::addr_of_mut!(opts),
timeout_key.as_ptr(),
timeout_val.as_ptr(),
0,
);
crate::av_dict_set(
ptr::addr_of_mut!(opts),
rw_timeout_key.as_ptr(),
rw_timeout_val.as_ptr(),
0,
);
if url.starts_with("udp://") {
let buf_key = CString::new("buffer_size").expect("no null in literal");
let buf_val = CString::new("65536").expect("no null in literal");
let overrun_key = CString::new("overrun_nonfatal").expect("no null in literal");
let overrun_val = CString::new("1").expect("no null in literal");
crate::av_dict_set(
ptr::addr_of_mut!(opts),
buf_key.as_ptr(),
buf_val.as_ptr(),
0,
);
crate::av_dict_set(
ptr::addr_of_mut!(opts),
overrun_key.as_ptr(),
overrun_val.as_ptr(),
0,
);
}
}
let mut ctx: *mut AVFormatContext = ptr::null_mut();
let ret = unsafe { ffi_avformat_open_input(&mut ctx, c_url.as_ptr(), ptr::null(), &mut opts) };
if !opts.is_null() {
unsafe { crate::av_dict_free(ptr::addr_of_mut!(opts)) };
}
if ret < 0 {
return Err(ret);
}
Ok(ctx)
}
pub fn srt_available() -> bool {
ensure_initialized();
let name = CString::new("libsrt").unwrap();
let fmt = unsafe { crate::av_find_input_format(name.as_ptr()) };
!fmt.is_null()
}
pub unsafe fn open_input_image_sequence(
path: &Path,
framerate: u32,
) -> Result<*mut AVFormatContext, c_int> {
ensure_initialized();
let path_str = match path.to_str() {
Some(s) => s,
None => return Err(crate::error_codes::EINVAL),
};
let c_path = match CString::new(path_str) {
Ok(s) => s,
Err(_) => return Err(crate::error_codes::EINVAL),
};
let image2_name = CString::new("image2").unwrap();
let input_fmt = crate::av_find_input_format(image2_name.as_ptr());
let mut opts: *mut crate::AVDictionary = ptr::null_mut();
let framerate_key = CString::new("framerate").unwrap();
let framerate_str = CString::new(framerate.to_string()).unwrap();
crate::av_dict_set(
ptr::addr_of_mut!(opts),
framerate_key.as_ptr(),
framerate_str.as_ptr(),
0,
);
let mut ctx: *mut AVFormatContext = ptr::null_mut();
let ret = ffi_avformat_open_input(&mut ctx, c_path.as_ptr(), input_fmt, &mut opts);
if !opts.is_null() {
crate::av_dict_free(ptr::addr_of_mut!(opts));
}
if ret < 0 {
return Err(ret);
}
Ok(ctx)
}
pub unsafe fn close_input(ctx: *mut *mut AVFormatContext) {
if !ctx.is_null() && !(*ctx).is_null() {
ffi_avformat_close_input(ctx);
}
}
pub unsafe fn find_stream_info(ctx: *mut AVFormatContext) -> Result<(), c_int> {
if ctx.is_null() {
return Err(crate::error_codes::EINVAL);
}
let ret = ffi_avformat_find_stream_info(ctx, ptr::null_mut());
if ret < 0 { Err(ret) } else { Ok(()) }
}
pub unsafe fn seek_frame(
ctx: *mut AVFormatContext,
stream_index: c_int,
timestamp: i64,
flags: c_int,
) -> Result<(), c_int> {
if ctx.is_null() {
return Err(crate::error_codes::EINVAL);
}
let ret = ffi_av_seek_frame(ctx, stream_index, timestamp, flags);
if ret < 0 { Err(ret) } else { Ok(()) }
}
pub unsafe fn seek_file(
ctx: *mut AVFormatContext,
stream_index: c_int,
min_ts: i64,
ts: i64,
max_ts: i64,
flags: c_int,
) -> Result<(), c_int> {
if ctx.is_null() {
return Err(crate::error_codes::EINVAL);
}
let ret = ffi_avformat_seek_file(ctx, stream_index, min_ts, ts, max_ts, flags);
if ret < 0 { Err(ret) } else { Ok(()) }
}
pub unsafe fn read_frame(ctx: *mut AVFormatContext, pkt: *mut AVPacket) -> Result<(), c_int> {
if ctx.is_null() || pkt.is_null() {
return Err(crate::error_codes::EINVAL);
}
let ret = ffi_av_read_frame(ctx, pkt);
if ret < 0 { Err(ret) } else { Ok(()) }
}
pub unsafe fn write_frame(ctx: *mut AVFormatContext, pkt: *mut AVPacket) -> Result<(), c_int> {
if ctx.is_null() || pkt.is_null() {
return Err(crate::error_codes::EINVAL);
}
let ret = ffi_av_write_frame(ctx, pkt);
if ret < 0 { Err(ret) } else { Ok(()) }
}
pub unsafe fn open_output(path: &Path, flags: c_int) -> Result<*mut AVIOContext, c_int> {
ensure_initialized();
let path_str = match path.to_str() {
Some(s) => s,
None => return Err(crate::error_codes::EINVAL),
};
let c_path = match CString::new(path_str) {
Ok(s) => s,
Err(_) => return Err(crate::error_codes::EINVAL),
};
let mut pb: *mut AVIOContext = ptr::null_mut();
let ret = avio_open(&mut pb, c_path.as_ptr(), flags);
if ret < 0 {
return Err(ret);
}
Ok(pb)
}
pub unsafe fn close_output(pb: *mut *mut AVIOContext) {
if !pb.is_null() && !(*pb).is_null() {
avio_closep(pb);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_seek_flags() {
assert_eq!(seek_flags::BACKWARD, 1);
assert_eq!(seek_flags::BYTE, 2);
assert_eq!(seek_flags::ANY, 4);
assert_eq!(seek_flags::FRAME, 8);
}
#[test]
fn test_open_input_invalid_path() {
let path = Path::new("/nonexistent/path/to/file.mp4");
unsafe {
let result = open_input(path);
assert!(result.is_err());
}
}
#[test]
fn test_close_input_null_safety() {
unsafe {
close_input(ptr::null_mut());
let mut null_ctx: *mut AVFormatContext = ptr::null_mut();
close_input(&mut null_ctx);
}
}
#[test]
fn test_find_stream_info_null() {
unsafe {
let result = find_stream_info(ptr::null_mut());
assert!(result.is_err());
assert_eq!(result.unwrap_err(), crate::error_codes::EINVAL);
}
}
#[test]
fn test_seek_frame_null() {
unsafe {
let result = seek_frame(ptr::null_mut(), 0, 0, 0);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), crate::error_codes::EINVAL);
}
}
#[test]
fn test_seek_file_null() {
unsafe {
let result = seek_file(ptr::null_mut(), 0, 0, 0, 0, 0);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), crate::error_codes::EINVAL);
}
}
#[test]
fn test_read_frame_null() {
unsafe {
let result = read_frame(ptr::null_mut(), ptr::null_mut());
assert!(result.is_err());
assert_eq!(result.unwrap_err(), crate::error_codes::EINVAL);
}
}
#[test]
fn test_write_frame_null() {
unsafe {
let result = write_frame(ptr::null_mut(), ptr::null_mut());
assert!(result.is_err());
assert_eq!(result.unwrap_err(), crate::error_codes::EINVAL);
}
}
#[test]
fn test_avio_flags() {
assert!(avio_flags::READ >= 0);
assert!(avio_flags::WRITE >= 0);
assert!(avio_flags::READ_WRITE >= 0);
assert!(avio_flags::WRITE > 0);
}
#[test]
fn test_open_output_invalid_path() {
let path = Path::new("/nonexistent/directory/output.mp4");
unsafe {
let result = open_output(path, avio_flags::WRITE);
assert!(result.is_err());
}
}
#[test]
fn test_close_output_null_safety() {
unsafe {
close_output(ptr::null_mut());
let mut null_pb: *mut AVIOContext = ptr::null_mut();
close_output(&mut null_pb);
}
}
}