use crate::Error;
use crate::ffi;
use libc;
use std::any::TypeId;
use std::convert::TryFrom;
use std::ffi::{c_int, c_void};
use std::io::{Read, Seek, SeekFrom, Write};
use std::mem::ManuallyDrop;
const DEFAULT_BUFFER_SIZE: usize = 32768;
pub struct StreamIo {
ptr: *mut ffi::AVIOContext,
drop_opaque: fn(*mut c_void),
flush_opaque: Option<fn(*mut c_void)>,
set_interrupt_opaque: fn(*mut c_void, ffi::AVIOInterruptCB),
stream_type: TypeId,
}
unsafe impl Send for StreamIo {}
struct Opaque<T> {
interrupt: ffi::AVIOInterruptCB,
scratch: Vec<u8>,
stream: T,
}
unsafe fn check_interrupt(cb: &ffi::AVIOInterruptCB) -> bool {
match cb.callback {
Some(f) => unsafe { f(cb.opaque) != 0 },
None => false,
}
}
impl StreamIo {
pub fn from_read<T: Read + Send + 'static>(stream: T) -> Result<Self, Error> {
Self::from_read_with_capacity(stream, DEFAULT_BUFFER_SIZE)
}
pub fn from_read_seek<T: Read + Seek + Send + 'static>(stream: T) -> Result<Self, Error> {
Self::from_read_seek_with_capacity(stream, DEFAULT_BUFFER_SIZE)
}
pub fn from_write<T: Write + Send + 'static>(stream: T) -> Result<Self, Error> {
Self::from_write_with_capacity(stream, DEFAULT_BUFFER_SIZE)
}
pub fn from_write_seek<T: Write + Seek + Send + 'static>(stream: T) -> Result<Self, Error> {
Self::from_write_seek_with_capacity(stream, DEFAULT_BUFFER_SIZE)
}
pub fn from_read_with_capacity<T: Read + Send + 'static>(
stream: T,
capacity: usize,
) -> Result<Self, Error> {
Self::new_impl(stream, capacity, Some(read::<T>), None, None, None)
}
pub fn from_read_seek_with_capacity<T: Read + Seek + Send + 'static>(
stream: T,
capacity: usize,
) -> Result<Self, Error> {
Self::new_impl(
stream,
capacity,
Some(read::<T>),
None,
Some(seek::<T>),
None,
)
}
pub fn from_write_with_capacity<T: Write + Send + 'static>(
stream: T,
capacity: usize,
) -> Result<Self, Error> {
Self::new_impl(
stream,
capacity,
None,
Some(write::<T>),
None,
Some(flush_stream::<T>),
)
}
pub fn from_write_seek_with_capacity<T: Write + Seek + Send + 'static>(
stream: T,
capacity: usize,
) -> Result<Self, Error> {
Self::new_impl(
stream,
capacity,
None,
Some(write::<T>),
Some(seek::<T>),
Some(flush_stream::<T>),
)
}
pub fn is_writable(&self) -> bool {
unsafe { (*self.ptr).write_flag != 0 }
}
fn new_impl<T: Send + 'static>(
stream: T,
capacity: usize,
r: Option<unsafe extern "C" fn(*mut c_void, *mut u8, c_int) -> c_int>,
w: Option<unsafe extern "C" fn(*mut c_void, WriteBufferType, c_int) -> c_int>,
s: Option<unsafe extern "C" fn(*mut c_void, i64, c_int) -> i64>,
flush: Option<fn(*mut c_void)>,
) -> Result<Self, Error> {
if capacity == 0 || capacity > c_int::MAX as usize {
return Err(Error::Other { errno: ffi::EINVAL });
}
let buffer = unsafe { ffi::av_mallocz(capacity) };
if buffer.is_null() {
return Err(Error::Other { errno: ffi::ENOMEM });
}
let stream_box_ptr = Box::into_raw(Box::new(Opaque {
interrupt: ffi::AVIOInterruptCB {
callback: None,
opaque: std::ptr::null_mut(),
},
scratch: Vec::new(),
stream,
})) as *mut c_void;
let ptr = unsafe {
ffi::avio_alloc_context(
buffer as *mut _,
capacity as _,
w.is_some() as _,
stream_box_ptr,
r,
w,
s,
)
};
if ptr.is_null() {
unsafe {
ffi::av_free(buffer);
drop(Box::from_raw(stream_box_ptr as *mut Opaque<T>));
}
return Err(Error::Other { errno: ffi::ENOMEM });
}
Ok(Self {
ptr,
drop_opaque: drop_box::<Opaque<T>>,
flush_opaque: flush,
set_interrupt_opaque: set_interrupt_impl::<T>,
stream_type: TypeId::of::<T>(),
})
}
pub(crate) fn set_interrupt(&mut self, cb: ffi::AVIOInterruptCB) {
(self.set_interrupt_opaque)(unsafe { (*self.ptr).opaque }, cb);
}
pub fn into_inner<T: 'static>(self) -> Result<T, Self> {
if self.stream_type != TypeId::of::<T>() {
return Err(self);
}
let mut this = ManuallyDrop::new(self);
unsafe {
ffi::avio_flush(this.ptr);
let opaque = (*this.ptr).opaque;
ffi::av_freep(&raw mut (*this.ptr).buffer as *mut c_void);
ffi::avio_context_free(&mut this.ptr);
Ok(Box::from_raw(opaque as *mut Opaque<T>).stream)
}
}
pub fn as_mut_ptr(&mut self) -> *mut ffi::AVIOContext {
self.ptr
}
}
impl Drop for StreamIo {
fn drop(&mut self) {
if !self.ptr.is_null() {
unsafe {
let opaque = (*self.ptr).opaque;
if (*self.ptr).write_flag != 0 {
ffi::avio_flush(self.ptr);
if let Some(flush) = self.flush_opaque {
flush(opaque);
}
}
ffi::av_freep(&raw mut (*self.ptr).buffer as *mut c_void);
ffi::avio_context_free(&mut self.ptr);
(self.drop_opaque)(opaque);
}
}
}
}
impl std::fmt::Debug for StreamIo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StreamIo").field("ptr", &self.ptr).finish()
}
}
unsafe extern "C" fn read<T: Read>(opaque: *mut c_void, buf: *mut u8, buf_size: c_int) -> c_int {
if buf_size <= 0 {
return ffi::AVERROR(ffi::EINVAL);
}
let buf_size = buf_size as usize;
let opaque = unsafe { &mut *(opaque as *mut Opaque<T>) };
if opaque.scratch.len() < buf_size {
opaque.scratch.resize(buf_size, 0);
}
loop {
if unsafe { check_interrupt(&opaque.interrupt) } {
return ffi::AVERROR_EXIT;
}
let scratch = &mut opaque.scratch[..buf_size];
return match opaque.stream.read(scratch) {
Ok(0) => ffi::AVERROR_EOF,
Ok(n) if n > scratch.len() => ffi::AVERROR(ffi::EIO),
Ok(n) => {
unsafe { std::ptr::copy_nonoverlapping(scratch.as_ptr(), buf, n) };
n as c_int
}
Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
Err(e) => map_io_error(e),
};
}
}
unsafe extern "C" fn write<T: Write>(
opaque: *mut c_void,
buf: WriteBufferType,
buf_size: c_int,
) -> c_int {
if buf_size < 0 {
return ffi::AVERROR(ffi::EINVAL);
}
let buf = unsafe { std::slice::from_raw_parts(buf, buf_size as usize) };
let opaque = unsafe { &mut *(opaque as *mut Opaque<T>) };
let mut written = 0usize;
while written < buf.len() {
if unsafe { check_interrupt(&opaque.interrupt) } {
return ffi::AVERROR_EXIT;
}
match opaque.stream.write(&buf[written..]) {
Ok(0) => return map_io_error(std::io::ErrorKind::WriteZero.into()),
Ok(n) if n > buf.len() - written => return ffi::AVERROR(ffi::EIO),
Ok(n) => written += n,
Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
Err(e) => return map_io_error(e),
}
}
buf_size
}
unsafe extern "C" fn seek<T: Seek>(opaque: *mut c_void, offset: i64, whence: c_int) -> i64 {
let opaque = unsafe { &mut *(opaque as *mut Opaque<T>) };
let stream = &mut opaque.stream;
let whence = whence & !ffi::AVSEEK_FORCE;
if whence == ffi::AVSEEK_SIZE {
match stream.stream_position().and_then(|cur| {
let end = stream.seek(SeekFrom::End(0))?;
if cur != end {
stream.seek(SeekFrom::Start(cur))?;
}
Ok(end)
}) {
Ok(sz) => return position_to_i64(sz),
Err(e) => return map_io_error(e) as i64,
}
}
let pos = match whence {
0 if offset >= 0 => SeekFrom::Start(offset as u64),
0 => return ffi::AVERROR(ffi::EINVAL) as i64,
1 => SeekFrom::Current(offset),
2 => SeekFrom::End(offset),
_ => return ffi::AVERROR(ffi::EINVAL) as i64,
};
loop {
if unsafe { check_interrupt(&opaque.interrupt) } {
return ffi::AVERROR_EXIT as i64;
}
return match stream.seek(pos) {
Ok(pos) => position_to_i64(pos),
Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
Err(e) => map_io_error(e) as i64,
};
}
}
fn position_to_i64(pos: u64) -> i64 {
i64::try_from(pos).unwrap_or(ffi::AVERROR(libc::EOVERFLOW) as i64)
}
fn flush_stream<T: Write>(opaque: *mut c_void) {
let _ = unsafe { &mut *(opaque as *mut Opaque<T>) }.stream.flush();
}
fn drop_box<T>(opaque: *mut c_void) {
drop(unsafe { Box::from_raw(opaque as *mut T) });
}
fn set_interrupt_impl<T>(opaque: *mut c_void, cb: ffi::AVIOInterruptCB) {
unsafe { (*(opaque as *mut Opaque<T>)).interrupt = cb };
}
fn map_io_error(e: std::io::Error) -> i32 {
use std::io::ErrorKind::*;
#[cfg(unix)]
if let Some(errno) = e.raw_os_error()
&& errno > 0
{
return ffi::AVERROR(errno);
}
match e.kind() {
UnexpectedEof => ffi::AVERROR_EOF,
Interrupted => ffi::AVERROR(libc::EINTR),
WouldBlock => ffi::AVERROR(libc::EAGAIN),
TimedOut => ffi::AVERROR(libc::ETIMEDOUT),
Unsupported => ffi::AVERROR(libc::ENOSYS),
_ => ffi::AVERROR(libc::EIO),
}
}
#[cfg(not(feature = "ffmpeg_7_0"))]
type WriteBufferType = *mut u8;
#[cfg(feature = "ffmpeg_7_0")]
type WriteBufferType = *const u8;