Skip to content

Commit

Permalink
Hardware acceleration support (#169)
Browse files Browse the repository at this point in the history
* Hwaccel support

* Add `vaapi_encode`
  • Loading branch information
ldm0 authored Apr 4, 2024
1 parent 9d1926a commit c78469a
Show file tree
Hide file tree
Showing 8 changed files with 3,076 additions and 1 deletion.
19 changes: 18 additions & 1 deletion src/avcodec/codec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ use std::{

use crate::{
avcodec::{AVCodecID, AVCodecParameters, AVPacket},
avutil::{AVChannelLayoutRef, AVDictionary, AVFrame, AVPixelFormat, AVRational},
avutil::{
AVChannelLayoutRef, AVDictionary, AVFrame, AVHWFramesContext, AVHWFramesContextMut,
AVHWFramesContextRef, AVPixelFormat, AVRational,
},
error::{Result, RsmpegError},
ffi,
shared::*,
Expand Down Expand Up @@ -342,6 +345,20 @@ impl AVCodecContext {
unsafe { AVChannelLayoutRef::from_raw(inner) }
}

pub fn hw_frames_ctx(&self) -> Option<AVHWFramesContextRef> {
let hw_frame_ctx = NonNull::new(self.hw_frames_ctx)?;
Some(unsafe { AVHWFramesContextRef::from_raw(hw_frame_ctx) })
}

pub fn hw_frames_ctx_mut(&mut self) -> Option<AVHWFramesContextMut> {
let hw_frame_ctx = NonNull::new(self.hw_frames_ctx)?;
Some(unsafe { AVHWFramesContextMut::from_raw(hw_frame_ctx) })
}

pub fn set_hw_frames_ctx(&mut self, hw_frames_ctx: AVHWFramesContext) {
unsafe { self.deref_mut().hw_frames_ctx = hw_frames_ctx.buffer_ref.into_raw().as_ptr() };
}

/// Is hardware accelaration enabled in this codec context.
pub fn is_hwaccel(&self) -> bool {
// We doesn't expose the `AVHWAccel` because the documentation states:
Expand Down
27 changes: 27 additions & 0 deletions src/avutil/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,33 @@ impl AVFrame {
Err(e) => Err(RsmpegError::AVError(e)),
}
}

/// Copy data to or from a hw surface. At least one of self/src must have an
/// AVHWFramesContext attached.
///
/// If src has an AVHWFramesContext attached, then the format of dst (if set)
/// must use one of the formats returned by av_hwframe_transfer_get_formats(src,
/// AV_HWFRAME_TRANSFER_DIRECTION_FROM).
/// If dst has an AVHWFramesContext attached, then the format of src must use one
/// of the formats returned by av_hwframe_transfer_get_formats(dst,
/// AV_HWFRAME_TRANSFER_DIRECTION_TO)
///
/// dst may be "clean" (i.e. with data/buf pointers unset), in which case the
/// data buffers will be allocated by this function using av_frame_get_buffer().
/// If dst->format is set, then this format will be used, otherwise (when
/// dst->format is AV_PIX_FMT_NONE) the first acceptable format will be chosen.
///
/// The two frames must have matching allocated dimensions (i.e. equal to
/// AVHWFramesContext.width/height), since not all device types support
/// transferring a sub-rectangle of the whole surface. The display dimensions
/// (i.e. AVFrame.width/height) may be smaller than the allocated dimensions, but
/// also have to be equal for both frames. When the display dimensions are
/// smaller than the allocated dimensions, the content of the padding in the
/// destination frame is unspecified.
pub fn hwframe_transfer_data(&mut self, src: &AVFrame) -> Result<()> {
unsafe { ffi::av_hwframe_transfer_data(self.as_mut_ptr(), src.as_ptr(), 0) }.upgrade()?;
Ok(())
}
}

impl Clone for AVFrame {
Expand Down
224 changes: 224 additions & 0 deletions src/avutil/hwcontext.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
use super::{AVBufferRef, AVDictionary, AVFrame};
use crate::{
error::Result,
ffi,
shared::{PointerUpgrade, RetUpgrade},
};
use std::{
ffi::CStr,
ops::{Deref, DerefMut},
os::raw::c_int,
ptr::{self, NonNull},
};

#[repr(transparent)]
pub struct AVHWDeviceContext {
buffer_ref: AVBufferRef,
}

impl AVHWDeviceContext {
/// Allocate an [`AVHWDeviceContext`] for a given hardware type.
pub fn alloc(r#type: ffi::AVHWDeviceType) -> Self {
let buffer_ref = unsafe { ffi::av_hwdevice_ctx_alloc(r#type) };
// this only panic on OOM
let buffer_ref = buffer_ref.upgrade().unwrap();
Self {
buffer_ref: unsafe { AVBufferRef::from_raw(buffer_ref) },
}
}

/// Finalize the device context before use. This function must be called after
/// the context is filled with all the required information and before it is
/// used in any way.
pub fn init(&mut self) -> Result<()> {
unsafe { ffi::av_hwdevice_ctx_init(self.buffer_ref.as_mut_ptr()) }.upgrade()?;
Ok(())
}

/// Open a device of the specified type and create an [`AVHWDeviceContext`] for it.
///
/// This is a convenience function intended to cover the simple cases. Callers
/// who need to fine-tune device creation/management should open the device
/// manually and then wrap it in an [`AVHWDeviceContext`] using
/// [`Self::alloc()`] / [`Self::init()`].
///
/// The returned context is already initialized and ready for use, the caller
/// should not call [`Self::init()`] on it. The user_opaque/free fields of
/// the created [`AVHWDeviceContext`] are set by this function and should not be
/// touched by the caller.
pub fn create(
r#type: ffi::AVHWDeviceType,
device: Option<&CStr>,
opts: Option<&AVDictionary>,
flags: c_int,
) -> Result<Self> {
let mut ptr = ptr::null_mut();
let opts = opts.map(|opts| opts.as_ptr()).unwrap_or_else(ptr::null);
let device = device
.map(|device| device.as_ptr())
.unwrap_or_else(ptr::null);
unsafe { ffi::av_hwdevice_ctx_create(&mut ptr, r#type, device, opts as *mut _, flags) }
.upgrade()?;
// this won't panic since av_hwdevice_ctx_create ensures it's non-null if successful.
let ptr = ptr.upgrade().unwrap();
let buffer_ref = unsafe { AVBufferRef::from_raw(ptr) };
Ok(Self { buffer_ref })
}

/// Create a new device of the specified type from an existing device.
///
/// If the source device is a device of the target type or was originally
/// derived from such a device (possibly through one or more intermediate
/// devices of other types), then this will return a reference to the
/// existing device of the same type as is requested.
///
/// Otherwise, it will attempt to derive a new device from the given source
/// device. If direct derivation to the new type is not implemented, it will
/// attempt the same derivation from each ancestor of the source device in
/// turn looking for an implemented derivation method.
pub fn create_derived(&self, r#type: ffi::AVHWDeviceType) -> Result<Self> {
let mut ptr = ptr::null_mut();
// `flags` parameter of av_hwdevice_ctx_create_derived is unused and need to be set to 0
unsafe {
ffi::av_hwdevice_ctx_create_derived(&mut ptr, r#type, self.as_ptr() as *mut _, 0)
}
.upgrade()?;
// this won't panic since av_hwdevice_ctx_create_derived ensures it's non-null if successful.
let ptr = ptr.upgrade().unwrap();
Ok(Self {
buffer_ref: unsafe { AVBufferRef::from_raw(ptr) },
})
}

/// Create a new device of the specified type from an existing device.
///
/// This function performs the same action as av_hwdevice_ctx_create_derived,
/// however, it is able to set options for the new device to be derived.
pub fn create_derived_opts(
&self,
r#type: ffi::AVHWDeviceType,
options: Option<&AVDictionary>,
) -> Result<Self> {
let mut ptr = ptr::null_mut();
let options = options.map(|opts| opts.as_ptr()).unwrap_or_else(ptr::null);
// `flags` parameter of av_hwdevice_ctx_create_derived is unused and need to be set to 0
unsafe {
ffi::av_hwdevice_ctx_create_derived_opts(
&mut ptr,
r#type,
self.as_ptr() as *mut _,
options as *mut _,
0,
)
}
.upgrade()?;
// this won't panic since av_hwdevice_ctx_create_derived_opts ensures it's non-null if successful.
let ptr = ptr.upgrade().unwrap();
Ok(Self {
buffer_ref: unsafe { AVBufferRef::from_raw(ptr) },
})
}

/// Allocate an [`AVHWFramesContext`] tied to a given device context.
pub fn hwframe_ctx_alloc(&self) -> AVHWFramesContext {
let buffer_ref = unsafe {
ffi::av_hwframe_ctx_alloc(self.as_ptr() as *mut _)
.upgrade()
.unwrap()
};
AVHWFramesContext {
buffer_ref: unsafe { AVBufferRef::from_raw(buffer_ref) },
}
}

/// Consume self and get the underlying buffer ref.
pub fn into_inner(self) -> AVBufferRef {
self.buffer_ref
}
}

impl Deref for AVHWDeviceContext {
type Target = AVBufferRef;

fn deref(&self) -> &Self::Target {
&self.buffer_ref
}
}

impl DerefMut for AVHWDeviceContext {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.buffer_ref
}
}

/// This struct describes a set or pool of "hardware" frames (i.e. those with
/// data not located in normal system memory). All the frames in the pool are
/// assumed to be allocated in the same way and interchangeable.
///
/// This struct is reference-counted with the AVBuffer mechanism and tied to a
/// given AVHWDeviceContext instance. The av_hwframe_ctx_alloc() constructor
/// yields a reference, whose data field points to the actual AVHWFramesContext
/// struct.
#[derive(Clone)]
#[repr(transparent)]
pub struct AVHWFramesContext {
pub(crate) buffer_ref: AVBufferRef,
}

// Here we manually use `wrap_ref_pure` and `wrap_mut_pure` for owned-reference type which can be used by `AVCodecContext::hw_frames_ctx*`.
//
// We want type safety, which means we cannot add methods of AVHWFramesContext to AVBufferRef directly.
wrap_ref_pure!((AVHWFramesContext, AVHWFramesContextRef): ffi::AVBufferRef);
wrap_mut_pure!((AVHWFramesContext, AVHWFramesContextMut): ffi::AVBufferRef);

impl AVHWFramesContext {
/// Finalize the context before use. This function must be called after the
/// context is filled with all the required information and before it is attached
/// to any frames.
pub fn init(&mut self) -> Result<()> {
unsafe { ffi::av_hwframe_ctx_init(self.buffer_ref.as_mut_ptr()) }.upgrade()?;
Ok(())
}

/// Return the mutable reference of the underlying AVHWFramesContext.
pub fn data(&mut self) -> &mut ffi::AVHWFramesContext {
unsafe { &mut *(self.buffer_ref.data as *mut ffi::AVHWFramesContext) }
}

/// Allocate a new frame attached to the current AVHWFramesContext.
///
/// `frame`: an empty (freshly allocated or unreffed) frame to be filled with newly allocated buffers.
pub fn get_buffer(&mut self, frame: &mut AVFrame) -> Result<()> {
unsafe { ffi::av_hwframe_get_buffer(self.buffer_ref.as_mut_ptr(), frame.as_mut_ptr(), 0) }
.upgrade()?;
Ok(())
}

/// # Safety
///
/// This function is only save when given `raw` points to a valid AVHWFramesContext.
pub unsafe fn from_raw(raw: NonNull<ffi::AVBufferRef>) -> Self {
Self {
buffer_ref: unsafe { AVBufferRef::from_raw(raw) },
}
}

/// Consume self and get the underlying buffer ref.
pub fn into_inner(self) -> AVBufferRef {
self.buffer_ref
}
}

impl Deref for AVHWFramesContext {
type Target = AVBufferRef;

fn deref(&self) -> &Self::Target {
&self.buffer_ref
}
}

impl DerefMut for AVHWFramesContext {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.buffer_ref
}
}
2 changes: 2 additions & 0 deletions src/avutil/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod dict;
mod error;
mod file;
mod frame;
mod hwcontext;
mod imgutils;
mod media_type;
mod mem;
Expand All @@ -25,6 +26,7 @@ pub use dict::*;
pub use error::*;
pub use file::*;
pub use frame::*;
pub use hwcontext::*;
pub use imgutils::*;
pub use media_type::*;
pub use mem::*;
Expand Down
2,612 changes: 2,612 additions & 0 deletions tests/assets/vids/bear.yuv

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions tests/ffmpeg_examples/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ mod extract_mvs;
mod remux;
mod transcode;
mod transcode_aac;
mod vaapi_encode;
Loading

0 comments on commit c78469a

Please sign in to comment.