Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
6769618
perf(enc-ffmpeg): skip BufferedResampler when conversion is identity
richiemcilroy May 8, 2026
0efa98f
perf(rendering): add rgba_to_nv12_fast CPU conversion
richiemcilroy May 8, 2026
7496ff1
perf(rendering): parallelize segment meta load and add first_camera_d…
richiemcilroy May 8, 2026
ea44510
test(rendering): verify lazy zoom precompute matches full precompute
richiemcilroy May 8, 2026
cc7684c
perf(rendering): cache clip offsets and accelerate NV12 readback conv…
richiemcilroy May 8, 2026
5f5d2a3
fix(rendering): order readback wait after submit in finish_encoder paths
richiemcilroy May 8, 2026
963db80
fix(audio): handle resampler flush loops and propagate decode EOF errors
richiemcilroy May 8, 2026
fec7de5
perf(audio): batch stereo mix using prepared linear gains
richiemcilroy May 8, 2026
3dab4a4
perf(editor): render audio in-place with cached clip offsets
richiemcilroy May 8, 2026
f8a3094
perf(editor): load segment audio concurrently with decoder setup
richiemcilroy May 8, 2026
fc33541
refactor(editor): use first_camera_duration for recording length bound
richiemcilroy May 8, 2026
e9b712a
chore(editor): re-export AudioSegment from crate root
richiemcilroy May 8, 2026
2e780d0
build(export): add cap-audio dev dependency for benchmarks
richiemcilroy May 8, 2026
5db7637
perf(export): parallelize renderer setup with segment loading
richiemcilroy May 8, 2026
76e2a15
perf(export): speed NV12 pipeline and widen export frame channel
richiemcilroy May 8, 2026
a424daa
chore(export): add CPU startup profiling example binary
richiemcilroy May 8, 2026
0598c11
chore(export): add encoder benchmark example binary
richiemcilroy May 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 17 additions & 66 deletions crates/audio/src/audio_data.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
use ffmpeg::{
ChannelLayout, codec as avcodec,
format::{self as avformat},
};
use ffmpeg::{ChannelLayout, codec as avcodec, format as avformat, frame};
use std::path::Path;

use crate::cast_bytes_to_f32_slice;

// F32 Packed 48kHz audio
pub struct AudioData {
samples: Vec<f32>,
channels: u16,
}

fn append_frame_samples(frame: &frame::Audio, samples: &mut Vec<f32>) {
let slice = &frame.data(0)[0..frame.samples() * 4 * frame.channels() as usize];
samples.extend(unsafe { cast_bytes_to_f32_slice(slice) });
}

impl AudioData {
pub const SAMPLE_FORMAT: avformat::Sample =
avformat::Sample::F32(avformat::sample::Type::Packed);
pub const SAMPLE_RATE: u32 = 48_000;

#[cfg(test)]
pub(crate) fn from_samples(samples: Vec<f32>, channels: u16) -> Self {
Self { samples, channels }
}

pub fn from_file(path: impl AsRef<Path>) -> Result<Self, String> {
fn inner(path: &Path) -> Result<AudioData, String> {
let mut input_ctx =
Expand Down Expand Up @@ -53,7 +59,6 @@ impl AudioData {
let mut decoded_frame = ffmpeg::frame::Audio::empty();
let mut resampled_frame = ffmpeg::frame::Audio::empty();

// let mut resampled_frames = 0;
let mut samples: Vec<f32> = vec![];

for (stream, packet) in input_ctx.packets() {
Expand All @@ -66,84 +71,30 @@ impl AudioData {
.map_err(|e| format!("Send Packet / {e}"))?;

while decoder.receive_frame(&mut decoded_frame).is_ok() {
let resample_delay = resampler
resampler
.run(&decoded_frame, &mut resampled_frame)
.map_err(|e| format!("Run Resampler / {e:?}"))?;

let slice = &resampled_frame.data(0)
[0..resampled_frame.samples() * 4 * resampled_frame.channels() as usize];
samples.extend(unsafe { cast_bytes_to_f32_slice(slice) });

if resample_delay.is_some() {
loop {
let resample_delay = resampler
.flush(&mut resampled_frame)
.map_err(|e| format!("Flush Resampler / {e}"))?;

let slice = &resampled_frame.data(0)[0..resampled_frame.samples()
* 4
* resampled_frame.channels() as usize];
samples.extend(unsafe { cast_bytes_to_f32_slice(slice) });

if resample_delay.is_none() {
break;
}
}
}
}

loop {
let resample_delay = resampler
.flush(&mut resampled_frame)
.map_err(|e| format!("Flush Resampler / {e}"))?;

let slice = &resampled_frame.data(0)
[0..resampled_frame.samples() * 4 * resampled_frame.channels() as usize];
samples.extend(unsafe { cast_bytes_to_f32_slice(slice) });

if resample_delay.is_none() {
break;
}
append_frame_samples(&resampled_frame, &mut samples);
}
}

decoder.send_eof().unwrap();
decoder.send_eof().map_err(|e| format!("Send EOF / {e}"))?;

while decoder.receive_frame(&mut decoded_frame).is_ok() {
let resample_delay = resampler
resampler
.run(&decoded_frame, &mut resampled_frame)
.map_err(|e| format!("Run Resampler / {e}"))?;

let slice = &resampled_frame.data(0)
[0..resampled_frame.samples() * 4 * resampled_frame.channels() as usize];
samples.extend(unsafe { cast_bytes_to_f32_slice(slice) });

if resample_delay.is_some() {
loop {
let resample_delay = resampler
.flush(&mut resampled_frame)
.map_err(|e| format!("Flush Resampler / {e}"))?;

let slice = &resampled_frame.data(0)[0..resampled_frame.samples()
* 4
* resampled_frame.channels() as usize];
samples.extend(unsafe { cast_bytes_to_f32_slice(slice) });

if resample_delay.is_none() {
break;
}
}
}
append_frame_samples(&resampled_frame, &mut samples);
}

loop {
let resample_delay = resampler
.flush(&mut resampled_frame)
.map_err(|e| format!("Flush Resampler / {e}"))?;

let slice = &resampled_frame.data(0)
[0..resampled_frame.samples() * 4 * resampled_frame.channels() as usize];
samples.extend(unsafe { cast_bytes_to_f32_slice(slice) });
append_frame_samples(&resampled_frame, &mut samples);

if resample_delay.is_none() {
break;
Expand Down
Loading
Loading