forked from unom/punktfunk
`VaapiDecoder::new` and `VulkanDecoder::new` create a hwdevice and then do several more fallible things with it — resolve `vkWaitSemaphores`, find the decoder, alloc the context, open it — and every one of those branches unref'd the device by hand, with a `Drop` doing it once more. Six hand-written unrefs between them, each one a line somebody has to remember when adding a step. `video_libav::AvBuffer` owns it instead, so an early `bail!` releases it and the branches carry nothing. It is a deliberate second copy of `pf-encode`'s type: the crates do not depend on each other (host encode and client decode share no code path), and this one needs something the host's does not — see below — so hoisting it to a shared crate would mean giving that crate an ffmpeg dependency and both sets of semantics to save about twenty lines. That extra piece is `into_raw`. `pick_vulkan` hands its frames context to the codec (`(*ctx).hw_frames_ctx = fr` — the codec unrefs it when the context closes), so the wrapper must give up ownership rather than drop. Making the transfer explicit is the point: dropping an `AvBuffer` there too would be exactly the double-unref this type exists to prevent. Two `av_buffer_unref` calls survive in `video_vulkan.rs` on purpose. One runs before ownership is taken (the `av_hwdevice_ctx_init` failure, ahead of `from_raw`); the other releases the codec's OWN pre-existing frames ctx before we replace it, which was never ours to model. Field order preserved: `hw_device` stays declared after `ctx`, so it still releases after each `Drop` frees packet/frame/context — the order the hand-written unref had. Verified on .21 (CachyOS, FFmpeg 62): `cargo check --workspace --all-targets` clean at exit 0 with zero errors — the workspace-wide check owed since the AvBuffer commit — plus pf-client-core 34 passed / 0 failed and pf-encode 33 passed / 0 failed. The decoders themselves still need real VAAPI/Vulkan playback to exercise.
60 lines
2.9 KiB
Rust
60 lines
2.9 KiB
Rust
//! Shared libav ownership helpers for the hardware decoders (`video_vaapi`, `video_vulkan`,
|
|
//! `video_d3d11`).
|
|
//!
|
|
//! The host has its own copy of this in `pf-encode`'s `enc/libav.rs`. The two crates do not depend
|
|
//! on each other — host encode and client decode share no code path — and the client's copy needs
|
|
//! something the host's does not ([`AvBuffer::into_raw`], for the decoder contexts that take
|
|
//! ownership of our ref), so a shared crate would have to carry an ffmpeg dependency and both sets
|
|
//! of semantics to save ~20 lines. It is not worth the edge.
|
|
|
|
use ffmpeg_next::ffi;
|
|
|
|
/// An owned `AVBufferRef`, unref'd exactly once when it drops.
|
|
///
|
|
/// Each decoder constructor creates a hwdevice and then does several more fallible things with it
|
|
/// — find the codec, alloc a context, open it — and every one of those failure branches used to
|
|
/// unref the device by hand, alongside a `Drop` doing it once more. Miss a branch and a decoder
|
|
/// device leaks per failed negotiation; double it up and the process aborts. Ownership lives here
|
|
/// instead, so an early `bail!` releases whatever exists and the branches carry no cleanup.
|
|
pub(crate) struct AvBuffer(*mut ffi::AVBufferRef);
|
|
|
|
impl AvBuffer {
|
|
/// Take ownership of a freshly-created `AVBufferRef`, rejecting the null an ffmpeg allocator
|
|
/// returns on failure.
|
|
///
|
|
/// # Safety
|
|
/// `p` must be null, or a live `AVBufferRef` whose ownership passes to the returned value —
|
|
/// nothing else may unref it.
|
|
pub(crate) unsafe fn from_raw(p: *mut ffi::AVBufferRef) -> Option<Self> {
|
|
(!p.is_null()).then_some(AvBuffer(p))
|
|
}
|
|
|
|
/// The borrowed pointer, for calls that read the ref without taking it (e.g. `av_buffer_ref`,
|
|
/// which makes its own).
|
|
pub(crate) fn as_ptr(&self) -> *mut ffi::AVBufferRef {
|
|
self.0
|
|
}
|
|
|
|
/// Give up ownership: the caller becomes responsible for the unref.
|
|
///
|
|
/// This exists for the `get_format` callbacks, which hand a frames context to the codec —
|
|
/// `(*ctx).hw_frames_ctx = fr` means *the codec owns our ref now*, and it unrefs it when the
|
|
/// context closes. Dropping an `AvBuffer` there as well would be the double-unref this type is
|
|
/// meant to prevent, so the transfer is made explicit rather than left implicit.
|
|
pub(crate) fn into_raw(self) -> *mut ffi::AVBufferRef {
|
|
let p = self.0;
|
|
std::mem::forget(self);
|
|
p
|
|
}
|
|
}
|
|
|
|
impl Drop for AvBuffer {
|
|
fn drop(&mut self) {
|
|
// SAFETY: `self.0` is the non-null ref `from_raw` took ownership of, and this type is its
|
|
// sole owner (neither `Clone` nor `Copy`; `as_ptr` only lends, and `into_raw` forgets
|
|
// instead of dropping), so this runs exactly once for that reference. `av_buffer_unref`
|
|
// drops the one reference and nulls the pointer through the `&mut`.
|
|
unsafe { ffi::av_buffer_unref(&mut self.0) };
|
|
}
|
|
}
|