Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/lib_ccx/ccx_common_timing.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ unsigned pts_big_change;

// PTS timing related stuff

double current_fps = (double)30000.0 / 1001; /* 29.97 */ // TODO: Get from framerates_values[] instead
double current_fps = (double)30000.0 / 1001; /* 29.97 default, updated at runtime from stream data */

int frames_since_ref_time = 0;
unsigned total_frames_count;
Expand Down Expand Up @@ -122,7 +122,7 @@ size_t print_scc_time(struct ccx_boundary_time time, char *buf)
// Format produces "HH:MM:SS;FF" = 11 chars + null, use 32 for safety
const size_t max_time_len = 32;

frame = ((double)(time.time_in_ms - 1000 * (time.ss + 60 * (time.mm + 60 * time.hh))) * 29.97 / 1000);
frame = ((double)(time.time_in_ms - 1000 * (time.ss + 60 * (time.mm + 60 * time.hh))) * current_fps / 1000);

return (size_t)snprintf(buf + time.set, max_time_len, fmt, time.hh, time.mm, time.ss, (unsigned)frame);
}
Expand Down
33 changes: 30 additions & 3 deletions src/rust/lib_ccxr/src/time/units.rs
Original file line number Diff line number Diff line change
Expand Up @@ -389,12 +389,21 @@ impl Timestamp {
Ok(s)
}

/// SCC time formatting
pub fn to_scc_time(&self) -> Result<String, TimestampError> {
/// SCC time formatting using `fps` to compute the frame number.
///
/// # Examples
/// ```rust
/// # use lib_ccxr::time::units::Timestamp;
/// let timestamp = Timestamp::from_millis(500);
/// assert_eq!(timestamp.to_scc_time(25.0).unwrap(), "00:00:00;12");
/// ```
pub fn to_scc_time(&self, fps: f64) -> Result<String, TimestampError> {
let mut result = String::new();

let (h, m, s, _) = self.as_hms_millis()?;
let frame = (self.millis - 1000 * (s + 60 * (m + 60 * h)) as i64) as f64 * 29.97 / 1000.0;
let secs = s as i64 + 60 * (m as i64 + 60 * h as i64);
let frame = (self.millis - 1000 * secs) as f64 * fps / 1000.0;
let frame = frame as u32;

write!(result, "{h:02}:{m:02}:{s:02};{frame:02}")?;

Expand Down Expand Up @@ -742,6 +751,24 @@ mod tests {
assert!(ts.to_vtt_time().is_err());
}

#[test]
fn test_timestamp_to_scc_time() {
let ts = Timestamp::from_millis(5445500);
assert_eq!(ts.to_scc_time(29.97).unwrap(), "01:30:45;14");

// The frame number follows the given fps.
let ts = Timestamp::from_millis(960);
assert_eq!(ts.to_scc_time(25.0).unwrap(), "00:00:00;24");
assert_eq!(ts.to_scc_time(29.97).unwrap(), "00:00:00;28");

// Frame numbers are truncated, never fractional.
let ts = Timestamp::from_millis(500);
assert_eq!(ts.to_scc_time(25.0).unwrap(), "00:00:00;12");

let ts = Timestamp::from_millis(-1);
assert!(ts.to_scc_time(29.97).is_err());
}

#[test]
fn test_timestamp_to_hms_millis_time() {
let ts = Timestamp::from_millis(5445500);
Expand Down
22 changes: 20 additions & 2 deletions src/rust/src/decoder/timing.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Utilty functions to get timing for captions

use crate::{bindings::*, cb_708, cb_field1, cb_field2};
use crate::{bindings::*, cb_708, cb_field1, cb_field2, current_fps};

use log::{debug, error};

Expand Down Expand Up @@ -88,10 +88,11 @@ impl PartialEq for ccx_boundary_time {
/// Returns a hh:mm:ss;frame string of time for SCC format
pub fn get_scc_time_str(time: ccx_boundary_time) -> String {
// Feel sorry for formatting:(
let fps = unsafe { current_fps };
let frame: u8 = (((time.time_in_ms
- 1000 * ((time.ss as i64) + 60 * ((time.mm as i64) + 60 * (time.hh as i64))))
as f64)
* 29.97
* fps
/ 1000.0) as u8;
format!("{:02}:{:02}:{:02};{:02}", time.hh, time.mm, time.ss, frame)
}
Expand Down Expand Up @@ -246,6 +247,23 @@ mod test {
);
}

#[test]
fn test_get_scc_time_str_frame_number() {
// In test builds current_fps is mocked to 30.0 (see crate::lib.rs),
// so 500 ms into a second is frame 15. The old hardcoded 29.97 would
// truncate to frame 14.
assert_eq!(
get_scc_time_str(ccx_boundary_time {
hh: 0,
mm: 0,
ss: 0,
time_in_ms: 500,
set: Default::default()
}),
"00:00:00;15"
);
}

fn set_tmp_cb_values() {
unsafe {
cb_708 = 10;
Expand Down
57 changes: 54 additions & 3 deletions src/rust/src/decoder/tv_screen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use super::timing::{get_scc_time_str, get_time_str};
use super::{CCX_DTVCC_SCREENGRID_COLUMNS, CCX_DTVCC_SCREENGRID_ROWS};
use crate::{
bindings::*,
current_fps,
utils::{is_false, is_true},
};
use encoding_rs::Encoding;
Expand Down Expand Up @@ -492,12 +493,14 @@ impl dtvcc_tv_screen {
let mut buf = String::new();
let mut time_show = ccx_boundary_time::get_time(self.time_ms_show);
let time_end = ccx_boundary_time::get_time(self.time_ms_hide);
// One frame duration in milliseconds (truncated like the C path)
let one_frame_ms = unsafe { (1000.0 / current_fps) as i64 };

// Caption overlapping situation
match writer.old_cc_time_end.cmp(&(time_show.time_in_ms as i32)) {
Ordering::Greater => {
// Correct the frame delay
time_show.time_in_ms -= 1000 / 29.97 as i64;
time_show.time_in_ms -= one_frame_ms;
buf.push_str(&(get_scc_time_str(time_show) + "\t942c 942c ").to_owned());
// Clear the buffer and start pop on caption
buf.push_str("94ae 94ae 9420 9420");
Expand All @@ -507,12 +510,12 @@ impl dtvcc_tv_screen {
let time_to_display = ccx_boundary_time::get_time(writer.old_cc_time_end as i64);
buf.push_str(&(get_scc_time_str(time_to_display) + "\t942c 942c \n\n").to_owned());
// Correct the frame delay
time_show.time_in_ms -= 1000 / 29.97 as i64;
time_show.time_in_ms -= one_frame_ms;
// Clear the buffer and start pop on caption in new time
buf.push_str(&(get_scc_time_str(time_show) + "\t94ae 94ae 9420 9420").to_owned());
}
Ordering::Equal => {
time_show.time_in_ms -= 1000 / 29.97 as i64;
time_show.time_in_ms -= one_frame_ms;
buf.push_str(
&(get_scc_time_str(time_show) + "\t942c 942c 94ae 94ae 9420 9420").to_owned(),
);
Expand Down Expand Up @@ -781,6 +784,54 @@ mod test {
assert!(screen.is_row_empty(1));
}

#[test]
#[cfg(unix)]
fn test_write_scc_timecode_uses_current_fps() {
// In test builds current_fps is mocked to 30.0 (see crate::lib.rs).
use std::io::{Read, Seek, SeekFrom};
use std::os::unix::io::AsRawFd;
use tempfile::tempfile;

let mut screen = get_zero_allocated_obj::<dtvcc_tv_screen>();
screen.time_ms_show = 500;
screen.time_ms_hide = 2000;
screen.chars[0][0] = dtvcc_symbol::new(0x41);

let mut writer_ctx = get_zero_allocated_obj::<dtvcc_writer_ctx>();
let transcript_settings = get_zero_allocated_obj::<ccx_encoders_transcript_format>();
let mut temp_file = tempfile().expect("Failed to create temp file");
writer_ctx.fd = temp_file.as_raw_fd();
writer_ctx.filename = std::ptr::null_mut();

let mut counter = 0u32;
let mut writer = Writer::new(
&mut counter,
0,
ccx_output_format::CCX_OF_SCC,
&mut writer_ctx,
0,
&transcript_settings,
0,
b"\r\n",
);

screen
.write_scc(&mut writer)
.expect("write_scc should succeed");

temp_file.seek(SeekFrom::Start(0)).unwrap();
let mut output = String::new();
temp_file.read_to_string(&mut output).unwrap();

// 500 ms minus one frame (33 ms at 30 fps) leaves 467 ms => frame 14.
// The old hardcoded 29.97 math would produce frame 13 for both steps.
assert!(
output.starts_with("00:00:00;14"),
"unexpected SCC timecode: {:?}",
output
);
}

#[test]
#[cfg(unix)]
fn test_writer_output_with_valid_fd() {
Expand Down
Loading