@@ -2,7 +2,7 @@ use cap_media_info::{AudioInfo, VideoInfo};
22use cidre:: { cm:: SampleTimingInfo , objc:: Obj , * } ;
33use ffmpeg:: frame;
44use std:: { ops:: Sub , path:: PathBuf , time:: Duration } ;
5- use tracing:: { debug, error, info} ;
5+ use tracing:: { debug, error, info, trace } ;
66
77// before pausing at all, subtract 0.
88// on pause, record last frame time.
@@ -24,6 +24,9 @@ pub struct MP4Encoder {
2424 // elapsed_duration: cm::Time,
2525 video_frames_appended : usize ,
2626 audio_frames_appended : usize ,
27+ last_timestamp : Option < Duration > ,
28+ last_video_pts : Option < Duration > ,
29+ last_audio_pts : Option < Duration > ,
2730}
2831
2932#[ derive( thiserror:: Error , Debug ) ]
@@ -204,6 +207,9 @@ impl MP4Encoder {
204207 is_paused : false ,
205208 video_frames_appended : 0 ,
206209 audio_frames_appended : 0 ,
210+ last_timestamp : None ,
211+ last_video_pts : None ,
212+ last_audio_pts : None ,
207213 } )
208214 }
209215
@@ -227,15 +233,41 @@ impl MP4Encoder {
227233 self . most_recent_frame = Some ( ( frame. clone ( ) , timestamp) ) ;
228234
229235 if let Some ( pause_timestamp) = self . pause_timestamp {
230- self . timestamp_offset += timestamp - pause_timestamp;
231- self . pause_timestamp = None ;
236+ if let Some ( gap) = timestamp. checked_sub ( pause_timestamp) {
237+ self . timestamp_offset += gap;
238+ self . pause_timestamp = None ;
239+ }
232240 }
233241
242+ let mut pts_duration = timestamp
243+ . checked_sub ( self . timestamp_offset )
244+ . unwrap_or ( Duration :: ZERO ) ;
245+
246+ if let Some ( last_pts) = self . last_video_pts {
247+ if pts_duration <= last_pts {
248+ let frame_duration = self . video_frame_duration ( ) ;
249+ let adjusted_pts = last_pts + frame_duration;
250+
251+ trace ! (
252+ ?timestamp,
253+ ?last_pts,
254+ adjusted_pts = ?adjusted_pts,
255+ frame_duration_ns = frame_duration. as_nanos( ) ,
256+ "Monotonic video pts correction" ,
257+ ) ;
258+
259+ if let Some ( new_offset) = timestamp. checked_sub ( adjusted_pts) {
260+ self . timestamp_offset = new_offset;
261+ }
262+
263+ pts_duration = adjusted_pts;
264+ }
265+ }
266+
267+ self . last_video_pts = Some ( pts_duration) ;
268+
234269 let mut timing = frame. timing_info ( 0 ) . unwrap ( ) ;
235- timing. pts = cm:: Time :: new (
236- timestamp. sub ( self . timestamp_offset ) . as_millis ( ) as i64 ,
237- 1_000 ,
238- ) ;
270+ timing. pts = cm:: Time :: new ( pts_duration. as_millis ( ) as i64 , 1_000 ) ;
239271 let frame = frame. copy_with_new_timing ( & [ timing] ) . unwrap ( ) ;
240272
241273 self . video_input
@@ -244,6 +276,7 @@ impl MP4Encoder {
244276 . and_then ( |v| v. then_some ( ( ) ) . ok_or ( QueueVideoFrameError :: Failed ) ) ?;
245277
246278 self . video_frames_appended += 1 ;
279+ self . last_timestamp = Some ( timestamp) ;
247280
248281 Ok ( ( ) )
249282 }
@@ -259,6 +292,13 @@ impl MP4Encoder {
259292 return Ok ( ( ) ) ;
260293 }
261294
295+ if let Some ( pause_timestamp) = self . pause_timestamp {
296+ if let Some ( gap) = timestamp. checked_sub ( pause_timestamp) {
297+ self . timestamp_offset += gap;
298+ self . pause_timestamp = None ;
299+ }
300+ }
301+
262302 let Some ( audio_input) = & mut self . audio_input else {
263303 return Err ( QueueAudioFrameError :: NoAudioInput ) ;
264304 } ;
@@ -297,8 +337,37 @@ impl MP4Encoder {
297337 let format_desc =
298338 cm:: AudioFormatDesc :: with_asbd ( & audio_desc) . map_err ( QueueAudioFrameError :: Setup ) ?;
299339
340+ let mut pts_duration = timestamp
341+ . checked_sub ( self . timestamp_offset )
342+ . unwrap_or ( Duration :: ZERO ) ;
343+
344+ if let Some ( last_pts) = self . last_audio_pts {
345+ if pts_duration <= last_pts {
346+ let frame_duration = Self :: audio_frame_duration ( & frame) ;
347+ let adjusted_pts = last_pts + frame_duration;
348+
349+ trace ! (
350+ ?timestamp,
351+ ?last_pts,
352+ adjusted_pts = ?adjusted_pts,
353+ frame_duration_ns = frame_duration. as_nanos( ) ,
354+ samples = frame. samples( ) ,
355+ sample_rate = frame. rate( ) ,
356+ "Monotonic audio pts correction" ,
357+ ) ;
358+
359+ if let Some ( new_offset) = timestamp. checked_sub ( adjusted_pts) {
360+ self . timestamp_offset = new_offset;
361+ }
362+
363+ pts_duration = adjusted_pts;
364+ }
365+ }
366+
367+ self . last_audio_pts = Some ( pts_duration) ;
368+
300369 let pts = cm:: Time :: new (
301- ( timestamp . sub ( self . timestamp_offset ) . as_secs_f64 ( ) * frame. rate ( ) as f64 ) as i64 ,
370+ ( pts_duration . as_secs_f64 ( ) * frame. rate ( ) as f64 ) as i64 ,
302371 frame. rate ( ) as i32 ,
303372 ) ;
304373
@@ -322,16 +391,49 @@ impl MP4Encoder {
322391 . and_then ( |v| v. then_some ( ( ) ) . ok_or ( QueueAudioFrameError :: Failed ) ) ?;
323392
324393 self . audio_frames_appended += 1 ;
394+ self . last_timestamp = Some ( timestamp) ;
325395
326396 Ok ( ( ) )
327397 }
328398
399+ fn video_frame_duration ( & self ) -> Duration {
400+ let fps_num = self . config . frame_rate . 0 ;
401+ let fps_den = self . config . frame_rate . 1 ;
402+
403+ if fps_num <= 0 {
404+ return Duration :: from_millis ( 1 ) ;
405+ }
406+
407+ let numerator = fps_den. unsigned_abs ( ) as u128 * 1_000_000_000u128 ;
408+ let denominator = fps_num as u128 ;
409+ let nanos = ( numerator / denominator) . max ( 1 ) ;
410+
411+ Duration :: from_nanos ( nanos as u64 )
412+ }
413+
414+ fn audio_frame_duration ( frame : & frame:: Audio ) -> Duration {
415+ let rate = frame. rate ( ) ;
416+
417+ if rate == 0 {
418+ return Duration :: from_millis ( 1 ) ;
419+ }
420+
421+ let samples = frame. samples ( ) as u128 ;
422+ if samples == 0 {
423+ return Duration :: from_nanos ( 1 ) ;
424+ }
425+
426+ let nanos = ( samples * 1_000_000_000u128 ) / rate as u128 ;
427+
428+ Duration :: from_nanos ( nanos. max ( 1 ) as u64 )
429+ }
430+
329431 pub fn pause ( & mut self ) {
330432 if self . is_paused || !self . is_writing {
331433 return ;
332434 }
333435
334- let Some ( ( _ , timestamp) ) = self . most_recent_frame else {
436+ let Some ( timestamp) = self . last_timestamp else {
335437 return ;
336438 } ;
337439
0 commit comments