Refactor: Introduce FFmpegPacket RAII wrapper

Co-authored-by: ijamespine <ijamespine@me.com>
This commit is contained in:
Cursor Agent
2025-12-15 10:25:00 +00:00
parent f709b8cbf0
commit 9496612afa
3 changed files with 52 additions and 28 deletions

View File

@@ -4,15 +4,14 @@ use crate::{
codec_ctx::FFmpegCodecContext,
error::{Error, FFmpegError},
format_ctx::FFmpegFormatContext,
packet::FFmpegPacket,
utils::from_path,
video_frame::FFmpegFrame,
};
use std::{path::Path, slice};
use ffmpeg_sys_next::{
av_frame_alloc, av_frame_free, av_packet_alloc, av_packet_free, av_packet_unref, av_read_frame,
avcodec_find_decoder, AVFrame, AVMediaType, AVSampleFormat,
};
use ffmpeg_sys_next::{av_read_frame, avcodec_find_decoder, AVFrame, AVMediaType, AVSampleFormat};
/// Extract audio samples from a media file as 16kHz mono f32 PCM
pub fn extract_audio_samples(filename: impl AsRef<Path>) -> Result<Vec<f32>, Error> {
@@ -46,57 +45,44 @@ pub fn extract_audio_samples(filename: impl AsRef<Path>) -> Result<Vec<f32>, Err
codec_ctx.parameters_to_context(codecpar)?;
codec_ctx.open2(decoder)?;
// Allocate packet and frame
let packet = av_packet_alloc();
if packet.is_null() {
return Err(FFmpegError::NullError.into());
}
let frame = av_frame_alloc();
if frame.is_null() {
av_packet_free(&packet as *const _ as *mut _);
return Err(FFmpegError::FrameAllocation.into());
}
// Allocate packet and frame using RAII wrappers for automatic cleanup
let mut packet = FFmpegPacket::new()?;
let mut frame = FFmpegFrame::new()?;
let mut samples = Vec::new();
// Read and decode packets
while av_read_frame(format_ctx.as_mut(), packet) >= 0 {
while av_read_frame(format_ctx.as_mut(), packet.as_ptr()) >= 0 {
let pkt = packet.as_ref().ok_or(FFmpegError::NullError)?;
if pkt.stream_index == audio_stream_index {
// Send packet to decoder
if codec_ctx.send_packet(packet).is_err() {
av_packet_unref(packet);
if codec_ctx.send_packet(packet.as_ptr()).is_err() {
packet.unref();
continue;
}
// Receive decoded frames
loop {
match codec_ctx.receive_frame(frame) {
match codec_ctx.receive_frame(frame.as_mut()) {
Ok(true) => {
let frame_ref = frame.as_ref().ok_or(FFmpegError::NullError)?;
// Extract samples from this frame
let frame_samples = extract_and_convert_frame(frame_ref)?;
let frame_samples = extract_and_convert_frame(frame.as_ref())?;
samples.extend_from_slice(&frame_samples);
}
Ok(false) | Err(FFmpegError::Again) => break,
Err(e) => {
av_packet_unref(packet);
av_frame_free(&frame as *const _ as *mut _);
av_packet_free(&packet as *const _ as *mut _);
// RAII wrappers handle cleanup automatically via Drop
return Err(e.into());
}
}
}
}
av_packet_unref(packet);
packet.unref();
}
// Cleanup
av_frame_free(&frame as *const _ as *mut _);
av_packet_free(&packet as *const _ as *mut _);
// RAII wrappers handle cleanup automatically when they go out of scope
// Now resample to 16kHz mono if needed
let codec_ref = codec_ctx.as_ref();

View File

@@ -41,6 +41,7 @@ mod filter_graph;
mod format_ctx;
mod frame_decoder;
pub mod model;
mod packet;
mod thumbnailer;
mod utils;
mod video_frame;

View File

@@ -0,0 +1,37 @@
use crate::error::FFmpegError;
use ffmpeg_sys_next::{av_packet_alloc, av_packet_free, av_packet_unref, AVPacket};
pub struct FFmpegPacket(*mut AVPacket);
impl FFmpegPacket {
pub(crate) fn new() -> Result<Self, FFmpegError> {
let ptr = unsafe { av_packet_alloc() };
if ptr.is_null() {
return Err(FFmpegError::NullError);
}
Ok(Self(ptr))
}
pub(crate) fn as_ptr(&self) -> *mut AVPacket {
self.0
}
pub(crate) fn as_ref(&self) -> Option<&AVPacket> {
unsafe { self.0.as_ref() }
}
pub(crate) fn unref(&mut self) {
if !self.0.is_null() {
unsafe { av_packet_unref(self.0) };
}
}
}
impl Drop for FFmpegPacket {
fn drop(&mut self) {
if !self.0.is_null() {
unsafe { av_packet_free(&mut self.0) };
self.0 = std::ptr::null_mut();
}
}
}