Skip to content

Commit 1b50454

Browse files
committed
perf(export): add frame receive timeouts and adopt SharedNv12Buffer
1 parent 8eb228e commit 1b50454

File tree

1 file changed

+61
-20
lines changed

1 file changed

+61
-20
lines changed

crates/export/src/mp4.rs

Lines changed: 61 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ use cap_editor::{AudioRenderer, get_audio_segments};
33
use cap_enc_ffmpeg::{AudioEncoder, aac::AACEncoder, h264::H264Encoder, mp4::*};
44
use cap_media_info::{RawVideoFormat, VideoInfo};
55
use cap_project::XY;
6-
use cap_rendering::{GpuOutputFormat, Nv12RenderedFrame, ProjectUniforms, RenderSegment};
6+
use cap_rendering::{
7+
GpuOutputFormat, Nv12RenderedFrame, ProjectUniforms, RenderSegment, SharedNv12Buffer,
8+
};
79
use futures::FutureExt;
810
use image::ImageBuffer;
911
use serde::Deserialize;
@@ -330,15 +332,15 @@ impl Mp4ExportSettings {
330332
}
331333

332334
struct ExportFrame {
333-
nv12_data: Arc<Vec<u8>>,
335+
nv12_data: SharedNv12Buffer,
334336
width: u32,
335337
height: u32,
336338
y_stride: u32,
337339
frame_number: u32,
338340
}
339341

340342
struct FirstFrameNv12 {
341-
data: Arc<Vec<u8>>,
343+
data: SharedNv12Buffer,
342344
width: u32,
343345
height: u32,
344346
y_stride: u32,
@@ -414,7 +416,7 @@ fn nv12_from_rendered_frame(frame: Nv12RenderedFrame) -> ExportFrame {
414416
}
415417

416418
return ExportFrame {
417-
nv12_data: Arc::new(result),
419+
nv12_data: SharedNv12Buffer::from_vec(result),
418420
width,
419421
height,
420422
y_stride: width,
@@ -428,7 +430,7 @@ fn nv12_from_rendered_frame(frame: Nv12RenderedFrame) -> ExportFrame {
428430
"swscale RGBA to NV12 conversion failed, using zeroed NV12"
429431
);
430432
ExportFrame {
431-
nv12_data: Arc::new(vec![0u8; width as usize * height as usize * 3 / 2]),
433+
nv12_data: SharedNv12Buffer::from_vec(vec![0u8; width as usize * height as usize * 3 / 2]),
432434
width,
433435
height,
434436
y_stride: width,
@@ -498,7 +500,7 @@ fn fill_nv12_frame_direct(
498500

499501
#[cfg(test)]
500502
struct Nv12ExportFrame {
501-
nv12_data: Arc<Vec<u8>>,
503+
nv12_data: SharedNv12Buffer,
502504
width: u32,
503505
height: u32,
504506
y_stride: u32,
@@ -562,6 +564,10 @@ fn save_screenshot_from_nv12(
562564
use cap_project::{ProjectConfiguration, RecordingMeta, StudioRecordingMeta};
563565
use cap_rendering::{ProjectRecordingsMeta, RenderVideoConstants};
564566

567+
const FRAME_RECEIVE_INITIAL_TIMEOUT_SECS: u64 = 120;
568+
const FRAME_RECEIVE_STEADY_TIMEOUT_SECS: u64 = 90;
569+
const MAX_CONSECUTIVE_FRAME_TIMEOUTS: u32 = 3;
570+
565571
#[allow(clippy::too_many_arguments)]
566572
async fn export_render_to_channel(
567573
constants: &RenderVideoConstants,
@@ -600,8 +606,46 @@ async fn export_render_to_channel(
600606
let forward_future = async {
601607
let mut first_frame_data: Option<FirstFrameNv12> = None;
602608
let mut frame_count = 0u32;
609+
let mut consecutive_timeouts = 0u32;
610+
611+
loop {
612+
let timeout_secs = if frame_count == 0 {
613+
FRAME_RECEIVE_INITIAL_TIMEOUT_SECS
614+
} else {
615+
FRAME_RECEIVE_STEADY_TIMEOUT_SECS
616+
};
617+
618+
let Some((frame, _frame_number)) = (match tokio::time::timeout(
619+
Duration::from_secs(timeout_secs),
620+
video_rx.recv(),
621+
)
622+
.await
623+
{
624+
Ok(frame) => {
625+
consecutive_timeouts = 0;
626+
frame
627+
}
628+
Err(_) => {
629+
consecutive_timeouts += 1;
630+
631+
if consecutive_timeouts >= MAX_CONSECUTIVE_FRAME_TIMEOUTS {
632+
return Err(cap_rendering::RenderingError::ImageLoadError(format!(
633+
"Export timed out {MAX_CONSECUTIVE_FRAME_TIMEOUTS} times consecutively after {timeout_secs}s each waiting for frame {frame_count}"
634+
)));
635+
}
636+
637+
warn!(
638+
frame_count = frame_count,
639+
timeout_secs = timeout_secs,
640+
consecutive_timeouts = consecutive_timeouts,
641+
"Timed out waiting for rendered frame"
642+
);
643+
continue;
644+
}
645+
}) else {
646+
break;
647+
};
603648

604-
while let Some((frame, _frame_number)) = video_rx.recv().await {
605649
if !(on_progress)(frame_count) {
606650
return Err(cap_rendering::RenderingError::ImageLoadError(
607651
"Export cancelled".to_string(),
@@ -631,17 +675,14 @@ async fn export_render_to_channel(
631675

632676
if let Some(first) = first_frame_data {
633677
let pp = screenshot_project_path;
634-
tokio::task::spawn(async move {
635-
let _ = tokio::task::spawn_blocking(move || {
636-
save_screenshot_from_nv12(
637-
&first.data,
638-
first.width,
639-
first.height,
640-
first.y_stride,
641-
&pp,
642-
);
643-
})
644-
.await;
678+
let _screenshot_task = tokio::task::spawn_blocking(move || {
679+
save_screenshot_from_nv12(
680+
first.data.as_ref(),
681+
first.width,
682+
first.height,
683+
first.y_stride,
684+
&pp,
685+
);
645686
});
646687
}
647688

@@ -699,7 +740,7 @@ mod tests {
699740
}
700741

701742
let input = Nv12ExportFrame {
702-
nv12_data: Arc::new(nv12_data.clone()),
743+
nv12_data: SharedNv12Buffer::from_vec(nv12_data.clone()),
703744
width,
704745
height,
705746
y_stride: width,
@@ -735,7 +776,7 @@ mod tests {
735776

736777
let data = vec![1u8, 2, 3, 4, 5, 6];
737778
let frame = Nv12RenderedFrame {
738-
data: std::sync::Arc::new(data.clone()),
779+
data: SharedNv12Buffer::from_vec(data.clone()),
739780
width: 4,
740781
height: 2,
741782
y_stride: 4,

0 commit comments

Comments
 (0)