Skip to content

Commit a8412fb

Browse files
committed
add timestamp on video frame option
1 parent 91283fa commit a8412fb

1 file changed

Lines changed: 29 additions & 12 deletions

File tree

examples/local_video/src/publisher.rs

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ use std::sync::{
2424
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
2525
use yuv_sys;
2626

27+
mod timestamp_burn;
28+
29+
use timestamp_burn::TimestampOverlay;
30+
2731
#[derive(Parser, Debug)]
2832
#[command(author, version, about, long_about = None)]
2933
struct Args {
@@ -83,6 +87,10 @@ struct Args {
8387
#[arg(long, default_value_t = false)]
8488
attach_timestamp: bool,
8589

90+
/// Burn the attached timestamp into each video frame; does nothing unless --attach-timestamp is also enabled
91+
#[arg(long, default_value_t = false)]
92+
burn_timestamp: bool,
93+
8694
/// Shared encryption key for E2EE (enables AES-GCM end-to-end encryption when set)
8795
#[arg(long)]
8896
e2ee_key: Option<String>,
@@ -118,8 +126,8 @@ struct PublisherTimingSummary {
118126
camera_capture_ms: RollingMs,
119127
decode_ms: RollingMs,
120128
buffer_convert_ms: RollingMs,
129+
frame_draw_ms: RollingMs,
121130
buffer_prepare_ms: RollingMs,
122-
webrtc_capture_ms: RollingMs,
123131
capture_to_webrtc_capture_ms: RollingMs,
124132
}
125133

@@ -128,8 +136,8 @@ impl PublisherTimingSummary {
128136
self.camera_capture_ms.reset();
129137
self.decode_ms.reset();
130138
self.buffer_convert_ms.reset();
139+
self.frame_draw_ms.reset();
131140
self.buffer_prepare_ms.reset();
132-
self.webrtc_capture_ms.reset();
133141
self.capture_to_webrtc_capture_ms.reset();
134142
}
135143
}
@@ -146,11 +154,10 @@ fn format_timing_line(timings: &PublisherTimingSummary) -> String {
146154
"convert_to_i420 {:.2}",
147155
timings.buffer_convert_ms.average().unwrap_or_default()
148156
));
157+
if let Some(frame_draw_ms) = timings.frame_draw_ms.average() {
158+
parts.push(format!("frame_draw {:.2}", frame_draw_ms));
159+
}
149160
parts.push(format!("buffer {:.2}", timings.buffer_prepare_ms.average().unwrap_or_default()));
150-
parts.push(format!(
151-
"webrtc_capture {:.2}",
152-
timings.webrtc_capture_ms.average().unwrap_or_default()
153-
));
154161
parts.push(format!(
155162
"capture_to_webrtc {:.2}",
156163
timings.capture_to_webrtc_capture_ms.average().unwrap_or_default()
@@ -381,14 +388,14 @@ async fn run(args: Args, ctrl_c_received: Arc<AtomicBool>) -> Result<()> {
381388
let mut timings = PublisherTimingSummary::default();
382389
let mut logged_mjpeg_fallback = false;
383390
let mut frame_counter: u32 = 0;
391+
let mut timestamp_overlay = (args.attach_timestamp && args.burn_timestamp)
392+
.then(|| TimestampOverlay::new(width, height));
384393
loop {
385394
if ctrl_c_received.load(Ordering::Acquire) {
386395
break;
387396
}
388397
// Wait until the scheduled next frame time
389-
let wait_start = Instant::now();
390398
ticker.tick().await;
391-
let iter_start = Instant::now();
392399

393400
// Capture the frame as early as possible so the attached timestamp is
394401
// close to the camera acquisition point.
@@ -398,7 +405,8 @@ async fn run(args: Args, ctrl_c_received: Arc<AtomicBool>) -> Result<()> {
398405
let camera_frame_acquired_at = Instant::now();
399406
let (stride_y, stride_u, stride_v) = frame.buffer.strides();
400407
let (data_y, data_u, data_v) = frame.buffer.data_mut();
401-
let (decode_finished_at, buffer_ready_at, used_decode_path) = if is_yuyv {
408+
let stride_y_usize = stride_y as usize;
409+
let (decode_finished_at, mut buffer_ready_at, used_decode_path) = if is_yuyv {
402410
// Fast path for YUYV: convert directly to I420 via libyuv
403411
let src = frame_buf.buffer();
404412
let src_bytes = src.as_ref();
@@ -513,6 +521,15 @@ async fn run(args: Args, ctrl_c_received: Arc<AtomicBool>) -> Result<()> {
513521
}
514522
};
515523

524+
let mut frame_draw_ms = None;
525+
if let Some(overlay) = timestamp_overlay.as_mut() {
526+
let overlay_started_at = Instant::now();
527+
overlay.draw(data_y, stride_y_usize, capture_wall_time_us);
528+
let overlay_finished_at = Instant::now();
529+
frame_draw_ms = Some((overlay_finished_at - overlay_started_at).as_secs_f64() * 1000.0);
530+
buffer_ready_at = overlay_finished_at;
531+
}
532+
516533
// Update RTP timestamp (monotonic, microseconds since start)
517534
frame.timestamp_us = start_ts.elapsed().as_micros() as i64;
518535
// Optionally attach wall-clock time as user timestamp and frame_id
@@ -541,12 +558,12 @@ async fn run(args: Args, ctrl_c_received: Arc<AtomicBool>) -> Result<()> {
541558
timings
542559
.buffer_convert_ms
543560
.record((buffer_ready_at - decode_finished_at).as_secs_f64() * 1000.0);
561+
if let Some(frame_draw_ms) = frame_draw_ms {
562+
timings.frame_draw_ms.record(frame_draw_ms);
563+
}
544564
timings
545565
.buffer_prepare_ms
546566
.record((buffer_ready_at - camera_frame_acquired_at).as_secs_f64() * 1000.0);
547-
timings
548-
.webrtc_capture_ms
549-
.record((webrtc_capture_finished_at - buffer_ready_at).as_secs_f64() * 1000.0);
550567
timings.capture_to_webrtc_capture_ms.record(
551568
(webrtc_capture_finished_at - camera_capture_started_at).as_secs_f64() * 1000.0,
552569
);

0 commit comments

Comments
 (0)