@@ -3,7 +3,9 @@ use cap_editor::{AudioRenderer, get_audio_segments};
33use cap_enc_ffmpeg:: { AudioEncoder , aac:: AACEncoder , h264:: H264Encoder , mp4:: * } ;
44use cap_media_info:: { RawVideoFormat , VideoInfo } ;
55use cap_project:: XY ;
6- use cap_rendering:: { GpuOutputFormat , Nv12RenderedFrame , ProjectUniforms , RenderSegment } ;
6+ use cap_rendering:: {
7+ GpuOutputFormat , Nv12RenderedFrame , ProjectUniforms , RenderSegment , SharedNv12Buffer ,
8+ } ;
79use futures:: FutureExt ;
810use image:: ImageBuffer ;
911use serde:: Deserialize ;
@@ -330,15 +332,15 @@ impl Mp4ExportSettings {
330332}
331333
332334struct 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
340342struct 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) ]
500502struct 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(
562564use cap_project:: { ProjectConfiguration , RecordingMeta , StudioRecordingMeta } ;
563565use 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) ]
566572async 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