use core::{fmt, slice};
use ffmpeg_next::ffi::{AVBufferRef, av_buffer_ref, av_buffer_unref};
pub struct FfmpegBuffer {
inner: *mut AVBufferRef,
offset: usize,
len: usize,
}
unsafe impl Send for FfmpegBuffer {}
impl FfmpegBuffer {
#[inline]
pub unsafe fn from_ref(buf: *mut AVBufferRef) -> Option<Self> {
if buf.is_null() {
return None;
}
let new_ref = unsafe { av_buffer_ref(buf) };
if new_ref.is_null() {
return None;
}
let len = unsafe { (*new_ref).size as usize };
Some(Self {
inner: new_ref,
offset: 0,
len,
})
}
#[inline]
pub unsafe fn from_ref_view(buf: *mut AVBufferRef, offset: usize, len: usize) -> Option<Self> {
if buf.is_null() {
return None;
}
let buf_size = unsafe { (*buf).size };
let end = offset.checked_add(len)?;
if end > buf_size {
return None;
}
let new_ref = unsafe { av_buffer_ref(buf) };
if new_ref.is_null() {
return None;
}
Some(Self {
inner: new_ref,
offset,
len,
})
}
#[inline]
pub fn empty() -> Self {
Self::try_empty().expect("FfmpegBuffer::empty: av_buffer_alloc returned null (OOM)")
}
#[inline]
pub fn try_empty() -> Option<Self> {
use ffmpeg_next::ffi::av_buffer_alloc;
let raw = unsafe { av_buffer_alloc(1) };
if raw.is_null() {
return None;
}
let mut buf = unsafe { Self::take(raw) }?;
buf.len = 0;
Some(buf)
}
#[inline]
pub fn from_packet(packet: &ffmpeg_next::Packet) -> Option<Self> {
use ffmpeg_next::packet::Ref;
let buf_ptr = unsafe { (*packet.as_ptr()).buf };
if buf_ptr.is_null() {
return None;
}
let data_ptr = unsafe { (*packet.as_ptr()).data };
let size_raw = unsafe { (*packet.as_ptr()).size };
if data_ptr.is_null() || size_raw <= 0 {
return None;
}
let payload_len = size_raw as usize;
let buf_data = unsafe { (*buf_ptr).data };
if buf_data.is_null() {
return None;
}
let offset = (data_ptr as usize).wrapping_sub(buf_data as usize);
unsafe { Self::from_ref_view(buf_ptr, offset, payload_len) }
}
#[inline]
pub fn from_frame_plane(frame: &ffmpeg_next::Frame, plane_idx: usize) -> Option<Self> {
if plane_idx >= 8 {
return None;
}
let buf_ptr = unsafe { (*frame.as_ptr()).buf[plane_idx] };
unsafe { Self::from_ref(buf_ptr) }
}
#[inline]
pub fn copy_from_slice(bytes: &[u8]) -> Option<Self> {
use ffmpeg_next::ffi::av_buffer_alloc;
let len = bytes.len();
let alloc_size = len.max(1);
let raw = unsafe { av_buffer_alloc(alloc_size as _) };
if raw.is_null() {
return None;
}
if len > 0 {
unsafe {
core::ptr::copy_nonoverlapping(bytes.as_ptr(), (*raw).data, len);
}
}
Some(Self {
inner: raw,
offset: 0,
len,
})
}
#[inline]
pub unsafe fn take(buf: *mut AVBufferRef) -> Option<Self> {
if buf.is_null() {
return None;
}
let len = unsafe { (*buf).size };
Some(Self {
inner: buf,
offset: 0,
len,
})
}
#[inline]
pub fn len(&self) -> usize {
self.len
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
pub fn as_ptr(&self) -> *const u8 {
unsafe {
let data = (*self.inner).data;
if data.is_null() {
return core::ptr::NonNull::<u8>::dangling().as_ptr();
}
(data as *const u8).add(self.offset)
}
}
#[inline]
pub fn as_av_buffer_ref(&self) -> *const AVBufferRef {
self.inner as *const _
}
#[inline]
pub fn offset(&self) -> usize {
self.offset
}
#[inline]
pub fn try_clone(&self) -> Option<Self> {
let new_ref = unsafe { av_buffer_ref(self.inner) };
if new_ref.is_null() {
return None;
}
Some(Self {
inner: new_ref,
offset: self.offset,
len: self.len,
})
}
}
impl Clone for FfmpegBuffer {
fn clone(&self) -> Self {
self
.try_clone()
.expect("FfmpegBuffer::clone: av_buffer_ref returned null (OOM)")
}
}
impl Drop for FfmpegBuffer {
fn drop(&mut self) {
unsafe { av_buffer_unref(&mut self.inner) };
}
}
impl AsRef<[u8]> for FfmpegBuffer {
#[inline]
fn as_ref(&self) -> &[u8] {
unsafe {
let data = (*self.inner).data as *const u8;
if data.is_null() || self.len == 0 {
return &[];
}
slice::from_raw_parts(data.add(self.offset), self.len)
}
}
}
impl fmt::Debug for FfmpegBuffer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FfmpegBuffer")
.field("len", &self.len())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use ffmpeg_next::ffi::av_buffer_alloc;
fn make_buffer(size: usize, fill: u8) -> FfmpegBuffer {
let raw = unsafe { av_buffer_alloc(size as _) };
assert!(!raw.is_null(), "av_buffer_alloc failed");
unsafe {
let data = (*raw).data;
core::ptr::write_bytes(data, fill, size);
}
unsafe { FfmpegBuffer::take(raw) }.expect("non-null take")
}
#[test]
fn null_take_returns_none() {
assert!(unsafe { FfmpegBuffer::take(core::ptr::null_mut()) }.is_none());
}
#[test]
fn null_from_ref_returns_none() {
assert!(unsafe { FfmpegBuffer::from_ref(core::ptr::null_mut()) }.is_none());
}
#[test]
fn allocated_buffer_round_trips_bytes() {
let buf = make_buffer(16, 0xAB);
assert_eq!(buf.len(), 16);
assert!(!buf.is_empty());
let slice = buf.as_ref();
assert_eq!(slice.len(), 16);
assert!(slice.iter().all(|&b| b == 0xAB));
}
#[test]
fn clone_bumps_refcount_and_keeps_data_alive() {
let original = make_buffer(8, 0x5A);
let cloned = original.clone();
assert_eq!(original.as_ref(), cloned.as_ref());
assert_eq!(original.as_ptr(), cloned.as_ptr());
drop(original);
assert_eq!(cloned.len(), 8);
assert!(cloned.as_ref().iter().all(|&b| b == 0x5A));
}
#[test]
fn debug_shows_length() {
let buf = make_buffer(42, 0);
let s = format!("{buf:?}");
assert!(s.contains("len: 42"), "got {s}");
}
#[test]
fn from_ref_view_carves_out_subregion() {
let raw = unsafe { av_buffer_alloc(24) };
assert!(!raw.is_null());
unsafe {
let data = (*raw).data;
core::ptr::write_bytes(data, 0xAA, 8);
core::ptr::write_bytes(data.add(8), 0xBB, 8);
core::ptr::write_bytes(data.add(16), 0xCC, 8);
}
let view_a = unsafe { FfmpegBuffer::from_ref_view(raw, 0, 8) }.expect("view_a");
let view_b = unsafe { FfmpegBuffer::from_ref_view(raw, 8, 8) }.expect("view_b");
let view_c = unsafe { FfmpegBuffer::from_ref_view(raw, 16, 8) }.expect("view_c");
assert!(view_a.as_ref().iter().all(|&b| b == 0xAA));
assert!(view_b.as_ref().iter().all(|&b| b == 0xBB));
assert!(view_c.as_ref().iter().all(|&b| b == 0xCC));
assert_eq!(view_a.offset(), 0);
assert_eq!(view_b.offset(), 8);
assert_eq!(view_c.offset(), 16);
assert_eq!(view_a.len(), 8);
unsafe { av_buffer_unref(&mut { raw }) };
let _ = (view_a, view_b, view_c);
}
#[test]
fn from_ref_view_rejects_out_of_bounds() {
let raw = unsafe { av_buffer_alloc(16) };
assert!(!raw.is_null());
assert!(unsafe { FfmpegBuffer::from_ref_view(raw, 10, 8) }.is_none());
assert!(unsafe { FfmpegBuffer::from_ref_view(raw, usize::MAX, 1) }.is_none());
unsafe { av_buffer_unref(&mut { raw }) };
}
#[test]
fn empty_buffer_returns_empty_slice() {
let raw = unsafe { av_buffer_alloc(0) };
if raw.is_null() {
return;
}
let buf = unsafe { FfmpegBuffer::take(raw) }.expect("non-null take");
assert_eq!(buf.len(), 0);
assert!(buf.is_empty());
assert_eq!(buf.as_ref(), &[] as &[u8]);
}
}