22//!
33//! SPDX-License-Identifier: GPL-3.0-or-later
44
5- use std:: time:: Instant ;
5+ use std:: time:: { Instant , SystemTime , UNIX_EPOCH } ;
66use std:: {
77 collections:: VecDeque ,
88 io,
@@ -22,7 +22,7 @@ use crate::{
2222 traits:: { Camera , CodecParameters } ,
2323 write_box,
2424} ;
25- use anyhow:: { Context , Error } ;
25+ use anyhow:: { Error } ;
2626use bytes:: { BufMut , BytesMut } ;
2727use crossbeam_channel:: unbounded;
2828use image:: RgbImage ;
@@ -51,15 +51,15 @@ pub enum VideoFrameKind {
5151pub struct VideoFrame {
5252 pub data : Vec < u8 > ,
5353 pub kind : VideoFrameKind ,
54- pub timestamp : Instant ,
54+ pub timestamp : SystemTime ,
5555}
5656
5757impl VideoFrame {
5858 pub fn new ( data : Vec < u8 > , kind : VideoFrameKind ) -> Self {
5959 Self {
6060 data,
6161 kind,
62- timestamp : Instant :: now ( ) ,
62+ timestamp : SystemTime :: now ( ) ,
6363 }
6464 }
6565}
@@ -223,11 +223,19 @@ impl RaspberryPiCamera {
223223 let ts = frame_count * ticks_per_frame;
224224
225225 let avcc = Self :: annexb_to_avcc_frame ( & frame. data , /*strip_aud*/ true , /*strip_ps*/ true ) ;
226+
227+ // Prepend per-frame SEI carrying capture time
228+ let sei = Self :: make_sei_unreg_avcc ( frame. timestamp . duration_since ( UNIX_EPOCH ) . unwrap ( ) . as_millis ( ) as u64 ) ;
229+ let mut sample = Vec :: with_capacity ( sei. len ( ) + avcc. len ( ) ) ;
230+ sample. extend_from_slice ( & sei) ;
231+ sample. extend_from_slice ( & avcc) ;
232+
226233 mp4. video (
227- & avcc ,
234+ & sample ,
228235 ts,
229236 frame. kind == VideoFrameKind :: IFrame ,
230237 ) . await ?;
238+
231239 frame_count += 1 ;
232240 samples_in_fragment += 1 ;
233241 }
@@ -285,6 +293,63 @@ impl RaspberryPiCamera {
285293 Ok ( ( ) )
286294 }
287295
296+
297+ fn make_sei_unreg_avcc ( epoch_ms : u64 ) -> Vec < u8 > {
298+ const NAL_TYPE_SEI : u8 = 6 ;
299+ const PAYLOAD_TYPE_UNREG : u8 = 5 ;
300+
301+ // 16-byte UUID
302+ const UUID : & [ u8 ; 16 ] = b"SECLUSO_LATENCY_" ;
303+
304+ // Build raw RBSP (before EPB insertion)
305+ // payload = UUID(16) + timestamp(8, big-endian)
306+ let mut payload = Vec :: with_capacity ( 24 ) ;
307+ payload. extend_from_slice ( UUID ) ;
308+ payload. extend_from_slice ( & epoch_ms. to_be_bytes ( ) ) ; // 8B BE
309+
310+ // payloadType (5) using 255-extension coding
311+ // payloadSize (24) using 255-extension coding
312+ let mut rbsp = Vec :: with_capacity ( 1 + 1 + payload. len ( ) + 1 ) ;
313+ rbsp. push ( PAYLOAD_TYPE_UNREG ) ;
314+
315+ let mut size = payload. len ( ) as u32 ; // must be 24
316+ while size >= 255 {
317+ rbsp. push ( 255 ) ;
318+ size -= 255 ;
319+ }
320+ rbsp. push ( size as u8 ) ;
321+
322+ rbsp. extend_from_slice ( & payload) ;
323+
324+ // rbsp_trailing_bits: 1 bit '1' then pad with zeros to byte boundary
325+ rbsp. push ( 0x80 ) ;
326+
327+ // Emulation-prevention bytes
328+ // Scan rbsp and after any 0x00 0x00 {00..03} pattern, insert 0x03 before that
329+ let mut rbsp_epb = Vec :: with_capacity ( rbsp. len ( ) + 8 ) ;
330+ let mut zero_run = 0usize ;
331+ for & b in & rbsp {
332+ if zero_run >= 2 && b <= 0x03 {
333+ rbsp_epb. push ( 0x03 ) ;
334+ zero_run = 0 ; // reset after insertion
335+ }
336+ rbsp_epb. push ( b) ;
337+ if b == 0x00 { zero_run += 1 } else { zero_run = 0 }
338+ }
339+
340+ // Assemble the NAL (header + rbsp_epb)
341+ // forbidden_zero_bit=0, nal_ref_idc=0, nal_unit_type=6 (SEI)
342+ let mut nal = Vec :: with_capacity ( 1 + rbsp_epb. len ( ) ) ;
343+ nal. push ( 0x00 | NAL_TYPE_SEI ) ;
344+ nal. extend_from_slice ( & rbsp_epb) ;
345+
346+ // AVCC length-prefixed output
347+ let mut out = Vec :: with_capacity ( 4 + nal. len ( ) ) ;
348+ out. extend_from_slice ( & ( nal. len ( ) as u32 ) . to_be_bytes ( ) ) ;
349+ out. extend_from_slice ( & nal) ;
350+ out
351+ }
352+
288353 /// Streams fmp4 video.
289354 async fn write_fmp4 (
290355 livestream_writer : LivestreamWriter ,
0 commit comments