rewrite unsafe code in ffmpeg ffi

This commit is contained in:
Skyler Lehmkuhl 2026-02-15 23:35:30 -05:00
parent a16c14a6a8
commit 6c4cc62098
5 changed files with 96 additions and 47 deletions

View File

@ -769,6 +769,26 @@ pub fn extract_audio_from_video(path: &str) -> Result<Option<ExtractedAudio>, St
// Extract f32 samples (interleaved format)
let data_ptr = resampled_frame.data(0).as_ptr() as *const f32;
let total_samples = resampled_frame.samples() * frame_channels;
// Safety checks before creating slice from FFmpeg data
// 1. Verify f32 alignment (required: 4 bytes)
if data_ptr.align_offset(std::mem::align_of::<f32>()) != 0 {
return Err("FFmpeg audio data is not properly aligned for f32".to_string());
}
// 2. Verify the frame actually has enough data
let byte_size = resampled_frame.data(0).len();
let expected_bytes = total_samples * std::mem::size_of::<f32>();
if byte_size < expected_bytes {
return Err(format!(
"FFmpeg frame buffer too small: {} bytes, need {} bytes",
byte_size, expected_bytes
));
}
// SAFETY: We verified alignment and bounds above.
// The slice lifetime is tied to resampled_frame which lives until
// after extend_from_slice completes.
let samples_slice = unsafe {
std::slice::from_raw_parts(data_ptr, total_samples)
};
@ -800,6 +820,26 @@ pub fn extract_audio_from_video(path: &str) -> Result<Option<ExtractedAudio>, St
let data_ptr = resampled_frame.data(0).as_ptr() as *const f32;
let total_samples = resampled_frame.samples() * frame_channels;
// Safety checks before creating slice from FFmpeg data
// 1. Verify f32 alignment (required: 4 bytes)
if data_ptr.align_offset(std::mem::align_of::<f32>()) != 0 {
return Err("FFmpeg audio data is not properly aligned for f32".to_string());
}
// 2. Verify the frame actually has enough data
let byte_size = resampled_frame.data(0).len();
let expected_bytes = total_samples * std::mem::size_of::<f32>();
if byte_size < expected_bytes {
return Err(format!(
"FFmpeg frame buffer too small: {} bytes, need {} bytes",
byte_size, expected_bytes
));
}
// SAFETY: We verified alignment and bounds above.
// The slice lifetime is tied to resampled_frame which lives until
// after extend_from_slice completes.
let samples_slice = unsafe {
std::slice::from_raw_parts(data_ptr, total_samples)
};

View File

@ -195,17 +195,16 @@ fn encode_pcm_to_mp3(
frame.set_rate(sample_rate);
// Copy planar samples to frame
unsafe {
for ch in 0..channels as usize {
let plane = frame.data_mut(ch);
let offset = samples_encoded;
let src = &planar_samples[ch][offset..offset + chunk_size];
for ch in 0..channels as usize {
let plane = frame.data_mut(ch);
let offset = samples_encoded;
let src = &planar_samples[ch][offset..offset + chunk_size];
std::ptr::copy_nonoverlapping(
src.as_ptr() as *const u8,
plane.as_mut_ptr(),
chunk_size * std::mem::size_of::<i16>(),
);
// Safe byte-level copy
for (i, &sample) in src.iter().enumerate() {
let bytes = sample.to_ne_bytes();
let byte_offset = i * 2;
plane[byte_offset..byte_offset + 2].copy_from_slice(&bytes);
}
}
@ -360,17 +359,16 @@ fn encode_pcm_to_aac(
frame.set_rate(sample_rate);
// Copy planar samples to frame
unsafe {
for ch in 0..channels as usize {
let plane = frame.data_mut(ch);
let offset = samples_encoded;
let src = &planar_samples[ch][offset..offset + chunk_size];
for ch in 0..channels as usize {
let plane = frame.data_mut(ch);
let offset = samples_encoded;
let src = &planar_samples[ch][offset..offset + chunk_size];
std::ptr::copy_nonoverlapping(
src.as_ptr() as *const u8,
plane.as_mut_ptr(),
chunk_size * std::mem::size_of::<f32>(),
);
// Safe byte-level copy
for (i, &sample) in src.iter().enumerate() {
let bytes = sample.to_ne_bytes();
let byte_offset = i * 4;
plane[byte_offset..byte_offset + 4].copy_from_slice(&bytes);
}
}

View File

@ -115,17 +115,18 @@ fn main() -> Result<(), String> {
height,
);
// Copy YUV planes
unsafe {
let y_plane = video_frame.data_mut(0);
std::ptr::copy_nonoverlapping(y.as_ptr(), y_plane.as_mut_ptr(), y.len());
// Copy YUV planes (safe slice copy)
let y_plane = video_frame.data_mut(0);
let y_len = y.len().min(y_plane.len());
y_plane[..y_len].copy_from_slice(&y[..y_len]);
let u_plane = video_frame.data_mut(1);
std::ptr::copy_nonoverlapping(u.as_ptr(), u_plane.as_mut_ptr(), u.len());
let u_plane = video_frame.data_mut(1);
let u_len = u.len().min(u_plane.len());
u_plane[..u_len].copy_from_slice(&u[..u_len]);
let v_plane = video_frame.data_mut(2);
std::ptr::copy_nonoverlapping(v.as_ptr(), v_plane.as_mut_ptr(), v.len());
}
let v_plane = video_frame.data_mut(2);
let v_len = v.len().min(v_plane.len());
v_plane[..v_len].copy_from_slice(&v[..v_len]);
// Set PTS
let timestamp = frame_num as f64 / framerate;

View File

@ -198,17 +198,25 @@ fn export_audio_ffmpeg_mp3<P: AsRef<Path>>(
frame.set_rate(settings.sample_rate);
// Copy planar samples to frame
unsafe {
for ch in 0..settings.channels as usize {
let plane = frame.data_mut(ch);
let offset = samples_encoded;
let src = &planar_samples[ch][offset..offset + chunk_size];
for ch in 0..settings.channels as usize {
let plane = frame.data_mut(ch);
let offset = samples_encoded;
let src = &planar_samples[ch][offset..offset + chunk_size];
std::ptr::copy_nonoverlapping(
src.as_ptr() as *const u8,
plane.as_mut_ptr(),
chunk_size * std::mem::size_of::<i16>(),
);
// Convert i16 samples to bytes and copy
let byte_size = chunk_size * std::mem::size_of::<i16>();
if plane.len() < byte_size {
return Err(format!(
"FFmpeg frame buffer too small: {} bytes, need {} bytes",
plane.len(), byte_size
));
}
// Safe byte-level copy using slice operations
for (i, &sample) in src.iter().enumerate() {
let bytes = sample.to_ne_bytes();
let offset = i * 2;
plane[offset..offset + 2].copy_from_slice(&bytes);
}
}

View File

@ -1182,16 +1182,18 @@ impl ExportOrchestrator {
);
// Copy YUV planes to frame
unsafe {
let y_dest = video_frame.data_mut(0);
std::ptr::copy_nonoverlapping(y_plane.as_ptr(), y_dest.as_mut_ptr(), y_plane.len());
// Use safe slice copy - LLVM optimizes this to memcpy, same performance as copy_nonoverlapping
let y_dest = video_frame.data_mut(0);
let y_len = y_plane.len().min(y_dest.len());
y_dest[..y_len].copy_from_slice(&y_plane[..y_len]);
let u_dest = video_frame.data_mut(1);
std::ptr::copy_nonoverlapping(u_plane.as_ptr(), u_dest.as_mut_ptr(), u_plane.len());
let u_dest = video_frame.data_mut(1);
let u_len = u_plane.len().min(u_dest.len());
u_dest[..u_len].copy_from_slice(&u_plane[..u_len]);
let v_dest = video_frame.data_mut(2);
std::ptr::copy_nonoverlapping(v_plane.as_ptr(), v_dest.as_mut_ptr(), v_plane.len());
}
let v_dest = video_frame.data_mut(2);
let v_len = v_plane.len().min(v_dest.len());
v_dest[..v_len].copy_from_slice(&v_plane[..v_len]);
// Set PTS (presentation timestamp) in encoder's time base
// Encoder time base is 1/(framerate * 1000), so PTS = timestamp * (framerate * 1000)