use crate::error::{FrameWritableError, Result};
use ffmpeg_sys_next::{
av_dict_free, av_dict_get, av_dict_iterate, av_dict_set, av_strerror, AVDictionary, AVRational,
AV_DICT_MATCH_CASE, AV_ERROR_MAX_STRING_SIZE,
};
use std::collections::HashMap;
use std::ffi::{c_char, CStr, CString};
pub(crate) struct DictGuard {
dict: *mut AVDictionary,
}
impl DictGuard {
pub(crate) fn new(dict: *mut AVDictionary) -> Self {
Self { dict }
}
#[inline]
pub(crate) fn as_double_ptr(&mut self) -> *mut *mut AVDictionary {
&mut self.dict
}
#[inline]
pub(crate) fn as_ptr(&self) -> *const AVDictionary {
self.dict as *const _
}
pub(crate) fn leftover_keys(&self) -> Vec<String> {
let mut keys = Vec::new();
let mut entry = std::ptr::null();
unsafe {
loop {
entry = av_dict_iterate(self.dict, entry);
if entry.is_null() {
break;
}
keys.push(CStr::from_ptr((*entry).key).to_string_lossy().into_owned());
}
}
keys
}
pub(crate) fn remove(&mut self, key: &CStr) {
unsafe {
if !av_dict_get(
self.dict,
key.as_ptr(),
std::ptr::null(),
AV_DICT_MATCH_CASE,
)
.is_null()
{
av_dict_set(&mut self.dict, key.as_ptr(), std::ptr::null(), 0);
}
}
}
}
impl Drop for DictGuard {
fn drop(&mut self) {
unsafe {
av_dict_free(&mut self.dict);
}
}
}
pub(crate) fn hashmap_to_avdictionary(
opts: &Option<HashMap<CString, CString>>,
) -> *mut AVDictionary {
let mut av_dict: *mut AVDictionary = std::ptr::null_mut();
if let Some(map) = opts {
for (key, value) in map {
unsafe {
av_dict_set(&mut av_dict, key.as_ptr(), value.as_ptr(), 0);
}
}
}
av_dict
}
#[allow(dead_code)]
pub(crate) fn string_to_cstring(s: &str) -> Result<CString, String> {
CString::new(s).map_err(|e| format!("String contains null byte: {}", e))
}
#[allow(dead_code)]
pub(crate) fn hashmap_to_avdictionary_string(
opts: &Option<HashMap<String, String>>,
) -> Result<*mut AVDictionary, String> {
let mut av_dict: *mut AVDictionary = std::ptr::null_mut();
if let Some(map) = opts {
for (key, value) in map {
let c_key = string_to_cstring(key)?;
let c_value = string_to_cstring(value)?;
unsafe {
av_dict_set(&mut av_dict, c_key.as_ptr(), c_value.as_ptr(), 0);
}
}
}
Ok(av_dict)
}
pub fn av_err2str(err: i32) -> String {
unsafe {
let mut buffer = [0 as c_char; AV_ERROR_MAX_STRING_SIZE];
av_strerror(err, buffer.as_mut_ptr(), AV_ERROR_MAX_STRING_SIZE);
let c_str = CStr::from_ptr(buffer.as_ptr());
match c_str.to_str() {
Ok(s) => s.to_string(),
Err(_) => format!("Unknown error: {}", err),
}
}
}
pub fn frame_is_writable(frame: &ffmpeg_next::Frame) -> bool {
unsafe {
!frame.as_ptr().is_null()
&& ffmpeg_sys_next::av_frame_is_writable(frame.as_ptr() as *mut _) > 0
}
}
pub fn make_frame_writable(frame: &mut ffmpeg_next::Frame) -> Result<()> {
if frame_is_eof_marker(frame) {
return Ok(());
}
unsafe {
let ret = ffmpeg_sys_next::av_frame_make_writable(frame.as_mut_ptr());
if ret < 0 {
return Err(FrameWritableError::from(ret).into());
}
}
Ok(())
}
pub fn frame_is_eof_marker(frame: &ffmpeg_next::Frame) -> bool {
unsafe {
let p = frame.as_ptr();
p.is_null() || ((*p).buf[0].is_null() && (*p).data.iter().all(|d| d.is_null()))
}
}
pub(crate) fn av_rescale_q_rnd(a: i64, bq: AVRational, cq: AVRational, rnd: u32) -> i64 {
let b = bq.num as i64 * cq.den as i64;
let c = cq.num as i64 * bq.den as i64;
av_rescale_rnd(a, b, c, rnd)
}
fn av_rescale_rnd(a: i64, b: i64, c: i64, mut rnd: u32) -> i64 {
const AV_ROUND_PASS_MINMAX: u32 = ffmpeg_sys_next::AVRounding::AV_ROUND_PASS_MINMAX as u32;
const INT_MAX: i64 = i32::MAX as i64;
if c <= 0
|| b < 0
|| !((rnd & !AV_ROUND_PASS_MINMAX) <= 5 && (rnd & !AV_ROUND_PASS_MINMAX) != 4)
{
return i64::MIN;
}
if (rnd & AV_ROUND_PASS_MINMAX) != 0 {
if a == i64::MIN || a == i64::MAX {
return a;
}
rnd -= AV_ROUND_PASS_MINMAX;
}
if a < 0 {
let neg_a = -a.max(-i64::MAX);
let neg_result = av_rescale_rnd(neg_a, b, c, rnd ^ ((rnd >> 1) & 1));
return -((neg_result as u64) as i64);
}
let r = if rnd == ffmpeg_sys_next::AVRounding::AV_ROUND_NEAR_INF as u32 {
c / 2
} else if (rnd & 1) != 0 {
c - 1
} else {
0
};
if b <= INT_MAX && c <= INT_MAX {
if a <= INT_MAX {
return (a * b + r) / c;
} else {
let ad = a / c;
let a2 = (a % c * b + r) / c;
if ad >= INT_MAX && b != 0 && ad > (i64::MAX - a2) / b {
return i64::MIN;
}
return ad * b + a2;
}
}
rescale_large(a, b, c, r)
}
fn rescale_large(a: i64, b: i64, c: i64, r: i64) -> i64 {
let a = a as u128;
let b = b as u128;
let c = c as u128;
let r = r as u128;
let result = (a * b + r) / c;
if result > i64::MAX as u128 {
i64::MIN
} else {
result as i64
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_av_rescale_q_rnd_basic() {
let bq = AVRational { num: 1, den: 1000 };
let cq = AVRational { num: 1, den: 90000 };
let result = av_rescale_q_rnd(1000, bq, cq, 5); assert_eq!(result, 90000);
}
#[test]
fn test_av_rescale_q_rnd_with_pass_minmax_flag() {
let bq = AVRational { num: 1, den: 1000 };
let cq = AVRational { num: 1, den: 90000 };
let result = av_rescale_q_rnd(i64::MIN, bq, cq, 8197);
assert_eq!(result, i64::MIN);
let result = av_rescale_q_rnd(i64::MAX, bq, cq, 8197);
assert_eq!(result, i64::MAX);
}
#[test]
fn test_av_rescale_q_rnd_normal_value_with_pass_minmax() {
let bq = AVRational { num: 1, den: 1000 };
let cq = AVRational { num: 1, den: 90000 };
let result = av_rescale_q_rnd(1000, bq, cq, 8197); assert_eq!(result, 90000);
}
#[test]
fn test_av_rescale_rnd_negative_value() {
let result = av_rescale_rnd(-1000, 90000, 1000, 5);
assert_eq!(result, -90000);
}
#[test]
fn test_av_rescale_rnd_zero() {
let result = av_rescale_rnd(0, 90000, 1000, 5);
assert_eq!(result, 0);
}
#[test]
fn test_av_rescale_rnd_rounding_modes() {
assert_eq!(av_rescale_rnd(7, 3, 5, 0), 4);
assert_eq!(av_rescale_rnd(7, 3, 5, 1), 5);
assert_eq!(av_rescale_rnd(7, 3, 5, 5), 4);
}
#[test]
fn test_av_rescale_rnd_large_values() {
let large_a = i32::MAX as i64 + 1000;
let result = av_rescale_rnd(large_a, 1000, 1, 5);
assert_eq!(result, large_a * 1000);
}
#[test]
fn test_av_rescale_rnd_invalid_params() {
assert_eq!(av_rescale_rnd(100, 100, 0, 5), i64::MIN);
assert_eq!(av_rescale_rnd(100, -1, 100, 5), i64::MIN);
assert_eq!(av_rescale_rnd(100, 100, 100, 4), i64::MIN);
}
#[test]
fn frame_is_eof_marker_classifies_every_buffer_shape() {
use ffmpeg_next::Frame;
use ffmpeg_sys_next::{av_buffer_alloc, av_frame_alloc, av_frame_get_buffer, AVPixelFormat};
unsafe {
assert!(
frame_is_eof_marker(&Frame::wrap(std::ptr::null_mut())),
"null frame is a marker"
);
let marker = av_frame_alloc();
assert!(
frame_is_eof_marker(&Frame::wrap(marker)),
"props-only frame is a marker"
);
let sw = av_frame_alloc();
(*sw).format = AVPixelFormat::AV_PIX_FMT_RGBA as i32;
(*sw).width = 4;
(*sw).height = 4;
assert!(av_frame_get_buffer(sw, 0) >= 0);
assert!(
!frame_is_eof_marker(&Frame::wrap(sw)),
"a normal software frame is not a marker"
);
let hw = av_frame_alloc();
let hw_buf = av_buffer_alloc(16);
(*hw).buf[0] = hw_buf;
(*hw).data[3] = (*hw_buf).data;
assert!(
!frame_is_eof_marker(&Frame::wrap(hw)),
"a hardware frame (data in data[3]) is not a marker"
);
let mut pixels = [0u8; 16];
let nrc = av_frame_alloc();
(*nrc).data[0] = pixels.as_mut_ptr();
let nrc_frame = Frame::wrap(nrc);
let nrc_is_marker = frame_is_eof_marker(&nrc_frame);
drop(nrc_frame);
let _ = &pixels;
assert!(
!nrc_is_marker,
"a non-refcounted software frame (data but no buf) is not a marker"
);
}
}
#[test]
fn make_frame_writable_copies_a_non_refcounted_frame() {
use ffmpeg_next::Frame;
use ffmpeg_sys_next::{av_frame_alloc, AVPixelFormat};
unsafe {
let raw = av_frame_alloc();
(*raw).format = AVPixelFormat::AV_PIX_FMT_GRAY8 as i32;
(*raw).width = 4;
(*raw).height = 4;
(*raw).linesize[0] = 4;
let mut pixels: Vec<u8> = (1..=16).collect();
let src_ptr = pixels.as_mut_ptr();
(*raw).data[0] = src_ptr;
assert!((*raw).buf[0].is_null(), "precondition: non-refcounted");
let mut f = Frame::wrap(raw);
make_frame_writable(&mut f).expect("make_frame_writable must copy, not skip");
let out = f.as_ptr();
assert!(
!(*out).buf[0].is_null(),
"a non-refcounted frame must be given owned buffers"
);
assert_ne!((*out).data[0], src_ptr, "data must point at a new buffer");
let new_ls = (*out).linesize[0] as usize;
for row in 0..4usize {
let copied = std::slice::from_raw_parts((*out).data[0].add(row * new_ls), 4);
assert_eq!(
copied,
&pixels[row * 4..row * 4 + 4],
"row {row} must be copied verbatim"
);
}
*(*out).data[0] = 0xAB;
assert_eq!(pixels[0], 1, "the source must be untouched by an in-place edit");
}
}
}