[FEAT] Add timing module in lib_ccxr (#1640)
Some checks failed
Build CCExtractor on Linux / build_shell (push) Has been cancelled
Build CCExtractor on Linux / build_autoconf (push) Has been cancelled
Build CCExtractor on Linux / cmake (push) Has been cancelled
Build CCExtractor on Linux / cmake_ocr_hardsubx (push) Has been cancelled
Build CCExtractor on Linux / build_rust (push) Has been cancelled
Build CCExtractor on Mac / build_shell (push) Has been cancelled
Build CCExtractor on Mac / build_autoconf (push) Has been cancelled
Build CCExtractor on Mac / cmake (push) Has been cancelled
Build CCExtractor on Mac / build_rust (push) Has been cancelled
Build CCExtractor on Windows / build_release (push) Has been cancelled
Build CCExtractor on Windows / build_debug (push) Has been cancelled

* feat: Add new module for timings functionality

* feat: Add timing functionality in `timing.rs` module

* feat: List all module & function conversion

* chore: Clippy fixes

* feat: Equivalent `ccx_common_timing.h` functions in rust module

* feat: Add static constants & include struct in `build.rs`

* feat: Add extern C functions

* feat: Include & use rust extern functions in C

* fix: Windows build

* fix: Windows build

---------

Co-authored-by: Prateek Sunal <prtksunal@gmail.com>
This commit is contained in:
Ishan Grover 2024-09-14 15:20:21 +05:30 committed by GitHub
parent 349020ece9
commit cbd8e27fe3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 1150 additions and 12 deletions

View File

@ -30,6 +30,18 @@ int gop_rollover = 0;
struct ccx_common_timing_settings_t ccx_common_timing_settings;
#ifndef DISABLE_RUST
void ccxr_add_current_pts(struct ccx_common_timing_ctx *ctx, LLONG pts);
void ccxr_set_current_pts(struct ccx_common_timing_ctx *ctx, LLONG pts);
int ccxr_set_fts(struct ccx_common_timing_ctx *ctx);
LLONG ccxr_get_fts(struct ccx_common_timing_ctx *ctx, int current_field);
LLONG ccxr_get_fts_max(struct ccx_common_timing_ctx *ctx);
char *ccxr_print_mstime_static(LLONG mstime, char *buf);
void ccxr_print_debug_timing(struct ccx_common_timing_ctx *ctx);
void ccxr_calculate_ms_gop_time(struct gop_time_code *g);
int ccxr_gop_accepted(struct gop_time_code *g);
#endif
void ccx_common_timing_init(LLONG *file_position, int no_sync)
{
ccx_common_timing_settings.disable_sync_check = 0;
@ -73,11 +85,18 @@ struct ccx_common_timing_ctx *init_timing_ctx(struct ccx_common_timing_settings_
void add_current_pts(struct ccx_common_timing_ctx *ctx, LLONG pts)
{
#ifndef DISABLE_RUST
return ccxr_add_current_pts(ctx, pts);
#endif
set_current_pts(ctx, ctx->current_pts + pts);
}
void set_current_pts(struct ccx_common_timing_ctx *ctx, LLONG pts)
{
#ifndef DISABLE_RUST
return ccxr_set_current_pts(ctx, pts);
#endif
LLONG prev_pts = ctx->current_pts;
ctx->current_pts = pts;
if (ctx->pts_set == 0)
@ -95,6 +114,9 @@ void set_current_pts(struct ccx_common_timing_ctx *ctx, LLONG pts)
int set_fts(struct ccx_common_timing_ctx *ctx)
{
#ifndef DISABLE_RUST
return ccxr_set_fts(ctx);
#endif
int pts_jump = 0;
// ES don't have PTS unless GOP timing is used
@ -266,6 +288,10 @@ int set_fts(struct ccx_common_timing_ctx *ctx)
LLONG get_fts(struct ccx_common_timing_ctx *ctx, int current_field)
{
#ifndef DISABLE_RUST
return ccxr_get_fts(ctx, current_field);
#endif
LLONG fts;
switch (current_field)
@ -290,6 +316,10 @@ LLONG get_fts(struct ccx_common_timing_ctx *ctx, int current_field)
LLONG get_fts_max(struct ccx_common_timing_ctx *ctx)
{
#ifndef DISABLE_RUST
return ccxr_get_fts_max(ctx);
#endif
// This returns the maximum FTS that belonged to a frame. Caption block
// counters are not applicable.
return ctx->fts_max + ctx->fts_global;
@ -350,6 +380,11 @@ size_t print_mstime_buff(LLONG mstime, char *fmt, char *buf)
char *print_mstime_static(LLONG mstime)
{
static char buf[15]; // 14 should be long enough
#ifndef DISABLE_RUST
return ccxr_print_mstime_static(mstime, buf);
#endif
print_mstime_buff(mstime, "%02u:%02u:%02u:%03u", buf);
return buf;
}
@ -357,6 +392,10 @@ char *print_mstime_static(LLONG mstime)
/* Helper function for to display debug timing info. */
void print_debug_timing(struct ccx_common_timing_ctx *ctx)
{
#ifndef DISABLE_RUST
return ccxr_print_debug_timing(ctx);
#endif
// Avoid wrong "Calc. difference" and "Asynchronous by" numbers
// for uninitialized min_pts
LLONG tempmin_pts = (ctx->min_pts == 0x01FFFFFFFFLL ? ctx->sync_pts : ctx->min_pts);
@ -383,6 +422,10 @@ void print_debug_timing(struct ccx_common_timing_ctx *ctx)
void calculate_ms_gop_time(struct gop_time_code *g)
{
#ifndef DISABLE_RUST
return ccxr_calculate_ms_gop_time(g);
#endif
int seconds = (g->time_code_hours * 3600) + (g->time_code_minutes * 60) + g->time_code_seconds;
g->ms = (LLONG)(1000 * (seconds + g->time_code_pictures / current_fps));
if (gop_rollover)
@ -391,6 +434,10 @@ void calculate_ms_gop_time(struct gop_time_code *g)
int gop_accepted(struct gop_time_code *g)
{
#ifndef DISABLE_RUST
return ccxr_gop_accepted(g);
#endif
if (!((g->time_code_hours <= 23) && (g->time_code_minutes <= 59) && (g->time_code_seconds <= 59) && (g->time_code_pictures <= 59)))
return 0;

View File

@ -31,6 +31,7 @@ fn main() {
"ccx_output_format",
"ccx_boundary_time",
"gop_time_code",
"ccx_common_timing_settings_t",
"ccx_s_options",
"ccx_s_teletext_config",
"ccx_output_date_format",

View File

@ -38,7 +38,7 @@ impl Default for EncodersTranscriptFormat {
}
}
#[derive(Debug, Default, Copy, Clone)]
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub enum FrameType {
#[default]
ResetOrUnknown = 0,

View File

@ -0,0 +1,61 @@
//! Provides Rust equivalent for functions in C. Uses Rust-native types as input and output.
use super::*;
/// Rust equivalent for `add_current_pts` function in C. Uses Rust-native types as input and output.
pub fn add_current_pts(ctx: &mut TimingContext, pts: MpegClockTick) {
ctx.add_current_pts(pts)
}
/// Rust equivalent for `set_current_pts` function in C. Uses Rust-native types as input and output.
pub fn set_current_pts(ctx: &mut TimingContext, pts: MpegClockTick) {
ctx.set_current_pts(pts)
}
/// Rust equivalent for `set_fts` function in C. Uses Rust-native types as input and output.
pub fn set_fts(ctx: &mut TimingContext) -> bool {
ctx.set_fts()
}
/// Rust equivalent for `get_fts` function in C. Uses Rust-native types as input and output.
pub fn get_fts(ctx: &mut TimingContext, current_field: CaptionField) -> Timestamp {
ctx.get_fts(current_field)
}
/// Rust equivalent for `get_fts_max` function in C. Uses Rust-native types as input and output.
pub fn get_fts_max(ctx: &mut TimingContext) -> Timestamp {
ctx.get_fts_max()
}
/// Rust equivalent for `print_mstime_static` function in C. Uses Rust-native types as input and output.
pub fn print_mstime_static(mstime: Timestamp, sep: char) -> String {
mstime.to_hms_millis_time(sep).unwrap()
}
/// Rust equivalent for `print_debug_timing` function in C. Uses Rust-native types as input and output.
pub fn print_debug_timing(ctx: &mut TimingContext) {
ctx.print_debug_timing()
}
/// Rust equivalent for `calculate_ms_gop_time` function in C. Uses Rust-native types as input and output.
pub fn calculate_ms_gop_time(g: GopTimeCode) -> Timestamp {
g.timestamp()
}
/// Rust equivalent for `gop_accepted` function in C. Uses Rust-native types as input and output.
pub fn gop_accepted(g: GopTimeCode) -> bool {
let mut timing_info = GLOBAL_TIMING_INFO.write().unwrap();
let gop_time = if let Some(gt) = timing_info.gop_time {
gt
} else {
return true;
};
if g.did_rollover(&gop_time) {
timing_info.gop_rollover = true;
true
} else {
gop_time.timestamp() <= g.timestamp()
}
}

View File

@ -1,4 +1,4 @@
//! Provide types for storing time in different formats
//! Provide types for storing time in different formats and manage timing information while decoding.
//!
//! Time can be represented in one of following formats:
//! - [`Timestamp`] as number of milliseconds
@ -6,6 +6,9 @@
//! - [`FrameCount`] as number of frames
//! - [`GopTimeCode`] as a GOP time code (as defined in the MPEG standard)
//!
//! [`GLOBAL_TIMING_INFO`] and [`TimingContext`] are used for managing time-related information
//! during the deocoding process.
//!
//! # Conversion Guide
//!
//! | From | To |
@ -16,8 +19,27 @@
//! | any pts | [`MpegClockTick`] |
//! | any frame count | [`FrameCount`] |
//! | `gop_time_code` | [`GopTimeCode`] |
//! | `current_field` | [`CaptionField`] |
//! | `ccx_common_timing_ctx.pts_set` | [`PtsSet`] |
//! | `ccx_common_timing_settings_t` | [`TimingSettings`] |
//! | `ccx_common_timing_ctx` | [`TimingContext`] |
//! | `init_timing_ctx` | [`TimingContext::new`] |
//! | `add_current_pts` | [`TimingContext::add_current_pts`] |
//! | `set_current_pts` | [`TimingContext::set_current_pts`] |
//! | `set_fts` | [`TimingContext::set_fts`] |
//! | `get_fts` | [`TimingContext::get_fts`] |
//! | `get_fts_max` | [`TimingContext::get_fts_max`] |
//! | `print_mstime_buff` | [`Timestamp::write_hms_millis_time`] |
//! | `print_mstime_static` | [`Timestamp::to_hms_millis_time`] |
//! | `print_scc_time` | [`Timestamp::to_scc_time`] |
//! | `print_debug_timing` | [`TimingContext::print_debug_timing`] |
//! | `gop_accepted` | [`GopTimeCode::did_rollover`] + some additional logic |
//! | `calculate_ms_gop_time` | [`GopTimeCode::new`], [`GopTimeCode::timestamp`] |
//! | `cb_708`, `cb_field1`, `cb_field2`, `pts_big_change`, `current_fps`, `frames_since_ref_time`, `total_frames_count`, `gop_time`, `first_gop_time`, `fts_at_gop_start`, `gop_rollover`, `ccx_common_timing_settings` | [`GlobalTimingInfo`], [`GLOBAL_TIMING_INFO`] |
pub mod c_functions;
pub mod timing;
pub mod units;
pub use timing::*;
pub use units::*;

View File

@ -0,0 +1,559 @@
use crate::common::{FrameType, FRAMERATES_VALUES};
use crate::time::units::{FrameCount, GopTimeCode, MpegClockTick, Timestamp};
use crate::util::log::{debug, info, DebugMessageFlag};
use std::sync::RwLock;
/// The maximum allowed difference between [`TimingContext::current_pts`] and [`TimingContext::sync_pts`] in seconds.
///
/// If the difference crosses this value, a PTS jump has occured and is handled accordingly.
const MAX_DIF: i64 = 5;
/// A unique global instance of [`GlobalTimingInfo`] to be used throughout the program.
pub static GLOBAL_TIMING_INFO: RwLock<GlobalTimingInfo> = RwLock::new(GlobalTimingInfo::new());
pub const DEFAULT_FRAME_RATE: f64 = 30000.0 / 1001.0;
/// Represents the status of [`TimingContext::current_pts`] and [`TimingContext::min_pts`]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum PtsSet {
No = 0,
Received = 1,
MinPtsSet = 2,
}
/// Represent the field of 608 or 708 caption.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum CaptionField {
Field1,
Field2,
Cea708,
}
/// A collective struct for storing timing-related information when decoding a file.
///
/// [`GlobalTimingInfo`] serves a similar purpose. The only difference is that its lifetime is
/// global.
#[derive(Debug)]
pub struct TimingContext {
pub pts_set: PtsSet,
/// if true then don't adjust again.
min_pts_adjusted: bool,
pub current_pts: MpegClockTick,
pub current_picture_coding_type: FrameType,
/// Store temporal reference of current frame.
pub current_tref: FrameCount,
pub min_pts: MpegClockTick,
pub sync_pts: MpegClockTick,
/// No screen should start before this FTS
pub minimum_fts: Timestamp,
/// Time stamp of current file (w/ fts_offset, w/o fts_global).
pub fts_now: Timestamp,
/// Time before first sync_pts.
pub fts_offset: Timestamp,
/// Time before first GOP.
pub fts_fc_offset: Timestamp,
/// Remember the maximum fts that we saw in current file.
pub fts_max: Timestamp,
/// Duration of previous files (-ve mode).
pub fts_global: Timestamp,
pub sync_pts2fts_set: bool,
pub sync_pts2fts_fts: Timestamp,
pub sync_pts2fts_pts: MpegClockTick,
/// PTS resets when current_pts is lower than prev.
pts_reset: bool,
}
/// Settings for overall timing functionality in [`TimingContext`].
#[derive(Debug)]
pub struct TimingSettings {
/// If true, timeline jumps will be ignored. This is important in several input formats that are assumed to have correct timing, no matter what.
pub disable_sync_check: bool,
/// If true, there will be no sync at all. Mostly useful for debugging.
pub no_sync: bool,
// Needs to be set, as it's used in set_fts.
pub is_elementary_stream: bool,
}
/// A collective struct to store global timing-related information while decoding a file.
///
/// [`TimingContext`] serves a similar purpose. The only difference is that its lifetime is not
/// global and its information could be reset while execution of program.
#[derive(Debug)]
pub struct GlobalTimingInfo {
// Count 608 (per field) and 708 blocks since last set_fts() call
pub cb_field1: u64,
pub cb_field2: u64,
pub cb_708: u64,
pub pts_big_change: bool,
pub current_fps: f64,
pub frames_since_ref_time: FrameCount,
pub total_frames_count: FrameCount,
pub gop_time: Option<GopTimeCode>,
pub first_gop_time: Option<GopTimeCode>,
pub fts_at_gop_start: Timestamp,
pub gop_rollover: bool,
pub timing_settings: TimingSettings,
}
impl TimingContext {
/// Create a new [`TimingContext`].
pub fn new() -> TimingContext {
TimingContext {
pts_set: PtsSet::No,
min_pts_adjusted: false,
current_pts: MpegClockTick::new(0),
current_picture_coding_type: FrameType::ResetOrUnknown,
current_tref: FrameCount::new(0),
min_pts: MpegClockTick::new(0x01FFFFFFFF),
sync_pts: MpegClockTick::new(0),
minimum_fts: Timestamp::from_millis(0),
fts_now: Timestamp::from_millis(0),
fts_offset: Timestamp::from_millis(0),
fts_fc_offset: Timestamp::from_millis(0),
fts_max: Timestamp::from_millis(0),
fts_global: Timestamp::from_millis(0),
sync_pts2fts_set: false,
sync_pts2fts_fts: Timestamp::from_millis(0),
sync_pts2fts_pts: MpegClockTick::new(0),
pts_reset: false,
}
}
/// Add `pts` to `TimingContext::current_pts`.
///
/// It also checks for PTS resets.
pub fn add_current_pts(&mut self, pts: MpegClockTick) {
self.set_current_pts(self.current_pts + pts)
}
/// Set `TimingContext::current_pts` to `pts`.
///
/// It also checks for PTS resets.
pub fn set_current_pts(&mut self, pts: MpegClockTick) {
let prev_pts = self.current_pts;
self.current_pts = pts;
if self.pts_set == PtsSet::No {
self.pts_set = PtsSet::Received
}
debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "PTS: {} ({:8})", self.current_pts.as_timestamp().to_hms_millis_time(':').unwrap(), self.current_pts.as_i64());
debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; " FTS: {} \n", self.fts_now.to_hms_millis_time(':').unwrap());
// Check if PTS reset
if self.current_pts < prev_pts {
self.pts_reset = true;
}
}
pub fn set_fts(&mut self) -> bool {
let mut timing_info = GLOBAL_TIMING_INFO.write().unwrap();
let mut pts_jump = false;
// ES don't have PTS unless GOP timing is used
if self.pts_set == PtsSet::No && timing_info.timing_settings.is_elementary_stream {
return true;
}
// First check for timeline jump (only when min_pts was set (implies sync_pts)).
if self.pts_set == PtsSet::MinPtsSet {
let dif = if timing_info.timing_settings.disable_sync_check {
// Disables sync check. Used for several input formats.
0
} else {
(self.current_pts - self.sync_pts).as_timestamp().seconds()
};
// Previously in C equivalent code, its -0.2
if !(0..=MAX_DIF).contains(&dif) {
// ATSC specs: More than 3501 ms means missing component
info!("\nWarning: Reference clock has changed abruptly ({} seconds filepos={}), attempting to synchronize\n", dif, "Unable to get file position"); // TODO: get the file position somehow
info!("Last sync PTS value: {}\n", self.sync_pts.as_i64());
info!("Current PTS value: {}\n", self.current_pts.as_i64());
info!("Note: You can disable this behavior by adding -ignoreptsjumps to the parameters.\n");
pts_jump = true;
timing_info.pts_big_change = true;
// Discard the gap if it is not on an I-frame or temporal reference zero.
if self.current_tref.as_u64() != 0
&& self.current_picture_coding_type != FrameType::IFrame
{
self.fts_now = self.fts_max;
info!("Change did not occur on first frame - probably a broken GOP\n");
return true;
}
}
}
// If min_pts was set just before a rollover we compensate by "roll-oving" it too
if self.pts_set == PtsSet::MinPtsSet && !self.min_pts_adjusted {
// We want to be aware of the upcoming rollover, not after it happened, so we don't take
// the 3 most significant bits but the 3 next ones
let min_pts_big_bits = (self.min_pts.as_i64() >> 30) & 7;
let cur_pts_big_bits = (self.current_pts.as_i64() >> 30) & 7;
if cur_pts_big_bits == 7 && min_pts_big_bits == 0 {
// Huge difference possibly means the first min_pts was actually just over the boundary
// Take the current_pts (smaller, accounting for the rollover) instead
self.min_pts = self.current_pts;
self.min_pts_adjusted = true;
} else if (1..=6).contains(&cur_pts_big_bits) {
// Far enough from the boundary
// Prevent the eventual difference with min_pts to make a bad adjustment
self.min_pts_adjusted = true;
}
}
// Set min_pts, fts_offset
if self.pts_set != PtsSet::No {
self.pts_set = PtsSet::MinPtsSet;
// Use this part only the first time min_pts is set. Later treat
// it as a reference clock change
if self.current_pts < self.min_pts && !pts_jump {
// If this is the first GOP, and seq 0 was not encountered yet
// we might reset min_pts/fts_offset again
self.min_pts = self.current_pts;
// Avoid next async test
self.sync_pts = self.current_pts
- self
.current_tref
.as_mpeg_clock_tick(timing_info.current_fps);
if self.current_tref.as_u64() == 0
|| (timing_info.total_frames_count - timing_info.frames_since_ref_time).as_u64()
== 0
{
// Earliest time in GOP.
// OR
// If this is the first frame (PES) there cannot be an offset.
// This part is also reached for dvr-ms/NTSC (RAW) as
// total_frames_count = frames_since_ref_time = 0 when
// this is called for the first time.
self.fts_offset = Timestamp::from_millis(0);
} else {
// It needs to be "+1" because the current frame is
// not yet counted.
self.fts_offset = (timing_info.total_frames_count
- timing_info.frames_since_ref_time
+ FrameCount::new(1))
.as_timestamp(timing_info.current_fps);
}
debug!(
msg_type = DebugMessageFlag::TIME;
"\nFirst sync time PTS: {} {:+}ms (time before this PTS)\n",
self.min_pts.as_timestamp().to_hms_millis_time(':').unwrap(),
self.fts_offset.millis()
);
debug!(
msg_type = DebugMessageFlag::TIME;
"Total_frames_count {} frames_since_ref_time {}\n",
timing_info.total_frames_count.as_u64(),
timing_info.frames_since_ref_time.as_u64()
);
}
// -nosync disables syncing
if pts_jump && !timing_info.timing_settings.no_sync {
// The current time in the old time base is calculated using
// sync_pts (set at the beginning of the last GOP) plus the
// time of the frames since then.
self.fts_offset = self.fts_offset
+ (self.sync_pts - self.min_pts).as_timestamp()
+ timing_info
.frames_since_ref_time
.as_timestamp(timing_info.current_fps);
self.fts_max = self.fts_offset;
// Start counting again from here
self.pts_set = PtsSet::Received; // Force min to be set again
self.sync_pts2fts_set = false; // Make note of the new conversion values
// Avoid next async test - the gap might have occured on
// current_tref != 0.
self.sync_pts = self.current_pts
- self
.current_tref
.as_mpeg_clock_tick(timing_info.current_fps);
// Set min_pts = sync_pts as this is used for fts_now
self.min_pts = self.sync_pts;
debug!(
msg_type = DebugMessageFlag::TIME;
"\nNew min PTS time: {} {:+}ms (time before this PTS)\n",
self.min_pts.as_timestamp().to_hms_millis_time(':').unwrap(),
self.fts_offset.millis()
);
}
}
// Set sync_pts, fts_offset
if self.current_tref.as_u64() == 0 {
self.sync_pts = self.current_pts;
}
// Reset counters
timing_info.cb_field1 = 0;
timing_info.cb_field2 = 0;
timing_info.cb_708 = 0;
// Avoid wrong "Calc. difference" and "Asynchronous by" numbers
// for uninitialized min_pts
// CFS: Remove or think decent condition
if self.pts_set != PtsSet::No {
// If pts_set is TRUE we have min_pts
self.fts_now = (self.current_pts - self.min_pts).as_timestamp() + self.fts_offset;
if !self.sync_pts2fts_set {
self.sync_pts2fts_pts = self.current_pts;
self.sync_pts2fts_fts = self.fts_now;
self.sync_pts2fts_set = true;
}
} else {
// No PTS info at all!!
info!("Set PTS called without any global timestamp set\n");
return false;
}
if self.fts_now > self.fts_max {
self.fts_max = self.fts_now;
}
// If PTS resets, then fix minimum_fts and fts_max
if self.pts_reset {
self.minimum_fts = Timestamp::from_millis(0);
self.fts_max = self.fts_now;
self.pts_reset = false;
}
true
}
/// Returns the total FTS.
///
/// Caption block counters are included.
pub fn get_fts(&self, current_field: CaptionField) -> Timestamp {
let timing_info = GLOBAL_TIMING_INFO.read().unwrap();
let count = match current_field {
CaptionField::Field1 => timing_info.cb_field1,
CaptionField::Field2 => timing_info.cb_field2,
CaptionField::Cea708 => timing_info.cb_708,
};
self.fts_now + self.fts_global + Timestamp::from_millis((count as i64) * 1001 / 30)
}
/// This returns the maximum FTS that belonged to a frame.
///
/// Caption block counters are not applicable.
pub fn get_fts_max(&self) -> Timestamp {
self.fts_max + self.fts_global
}
/// Log FTS and PTS information for debugging purpose.
pub fn print_debug_timing(&self) {
let zero = Timestamp::from_millis(0);
let timing_info = GLOBAL_TIMING_INFO.read().unwrap();
let gop_time = timing_info.gop_time.map(|x| x.timestamp()).unwrap_or(zero);
let first_gop_time = timing_info
.first_gop_time
.map(|x| x.timestamp())
.unwrap_or(zero);
// Avoid wrong "Calc. difference" and "Asynchronous by" numbers
// for uninitialized min_pts
let tempmin_pts = if self.min_pts.as_i64() == 0x01FFFFFFFF {
self.sync_pts
} else {
self.min_pts
};
info!(
"Sync time stamps: PTS: {} ",
self.sync_pts
.as_timestamp()
.to_hms_millis_time(':')
.unwrap()
);
info!(
" GOP start FTS: {}\n",
gop_time.to_hms_millis_time(':').unwrap()
);
// Length first GOP to last GOP
let goplenms = gop_time - first_gop_time;
// Length at last sync point
let ptslenms = (self.sync_pts - tempmin_pts).as_timestamp() + self.fts_offset;
info!(
"Last FTS: {}",
self.get_fts_max().to_hms_millis_time(':').unwrap()
);
info!(
" GOP start FTS: {}\n",
timing_info
.fts_at_gop_start
.to_hms_millis_time(':')
.unwrap()
);
let one_frame = FrameCount::new(1).as_timestamp(timing_info.current_fps);
// Times are based on last GOP and/or sync time
info!(
"Max FTS diff. to PTS: {:6}ms GOP: {:6}ms\n\n",
(self.get_fts_max() + one_frame - ptslenms)
.to_hms_millis_time(':')
.unwrap(),
(self.get_fts_max() + one_frame - goplenms)
.to_hms_millis_time(':')
.unwrap()
);
}
/// Constructs a [`TimingContext`] from its individual fields.
///
/// # Safety
///
/// Make sure that [`TimingContext`] is in a valid state.
#[allow(clippy::too_many_arguments)]
pub unsafe fn from_raw_parts(
pts_set: PtsSet,
min_pts_adjusted: bool,
current_pts: MpegClockTick,
current_picture_coding_type: FrameType,
current_tref: FrameCount,
min_pts: MpegClockTick,
sync_pts: MpegClockTick,
minimum_fts: Timestamp,
fts_now: Timestamp,
fts_offset: Timestamp,
fts_fc_offset: Timestamp,
fts_max: Timestamp,
fts_global: Timestamp,
sync_pts2fts_set: bool,
sync_pts2fts_fts: Timestamp,
sync_pts2fts_pts: MpegClockTick,
pts_reset: bool,
) -> TimingContext {
TimingContext {
pts_set,
min_pts_adjusted,
current_pts,
current_picture_coding_type,
current_tref,
min_pts,
sync_pts,
minimum_fts,
fts_now,
fts_offset,
fts_fc_offset,
fts_max,
fts_global,
sync_pts2fts_set,
sync_pts2fts_fts,
sync_pts2fts_pts,
pts_reset,
}
}
/// Returns the individual fields of a [`TimingContext`].
///
/// # Safety
///
/// Certain fields are supposed to be private.
#[allow(clippy::type_complexity)]
pub unsafe fn as_raw_parts(
&self,
) -> (
PtsSet,
bool,
MpegClockTick,
FrameType,
FrameCount,
MpegClockTick,
MpegClockTick,
Timestamp,
Timestamp,
Timestamp,
Timestamp,
Timestamp,
Timestamp,
bool,
Timestamp,
MpegClockTick,
bool,
) {
let TimingContext {
pts_set,
min_pts_adjusted,
current_pts,
current_picture_coding_type,
current_tref,
min_pts,
sync_pts,
minimum_fts,
fts_now,
fts_offset,
fts_fc_offset,
fts_max,
fts_global,
sync_pts2fts_set,
sync_pts2fts_fts,
sync_pts2fts_pts,
pts_reset,
} = *self;
(
pts_set,
min_pts_adjusted,
current_pts,
current_picture_coding_type,
current_tref,
min_pts,
sync_pts,
minimum_fts,
fts_now,
fts_offset,
fts_fc_offset,
fts_max,
fts_global,
sync_pts2fts_set,
sync_pts2fts_fts,
sync_pts2fts_pts,
pts_reset,
)
}
}
impl GlobalTimingInfo {
/// Create a new instance of [`GlobalTimingInfo`].
const fn new() -> GlobalTimingInfo {
GlobalTimingInfo {
cb_field1: 0,
cb_field2: 0,
cb_708: 0,
pts_big_change: false,
current_fps: FRAMERATES_VALUES[4], // 29.97
frames_since_ref_time: FrameCount::new(0),
total_frames_count: FrameCount::new(0),
gop_time: None,
first_gop_time: None,
fts_at_gop_start: Timestamp::from_millis(0),
gop_rollover: false,
timing_settings: TimingSettings {
disable_sync_check: false,
no_sync: false,
is_elementary_stream: false,
},
}
}
}
impl Default for TimingContext {
fn default() -> Self {
Self::new()
}
}

View File

@ -1,7 +1,7 @@
use std::convert::TryInto;
use std::ffi::c_int;
use std::fmt::Write;
use std::num::TryFromIntError;
use std::os::raw::c_int;
use std::time::{SystemTime, UNIX_EPOCH};
use derive_more::{Add, Neg, Sub};
@ -397,6 +397,18 @@ impl Timestamp {
Ok(s)
}
/// SCC time formatting
pub fn to_scc_time(&self) -> 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;
write!(result, "{:02}:{:02}:{:02};{:02}", h, m, s, frame)?;
Ok(result)
}
/// Returns a formatted [`Timestamp`] using ctime's format.
///
/// # Examples

View File

@ -289,7 +289,7 @@ mod test {
assert_eq!(decoder.packet, vec![0xC2, 0x23, 0x45, 0x67, 0x01, 0x02]);
assert_eq!(decoder.packet_length, 6);
assert_eq!(decoder.is_header_parsed, true);
assert!(decoder.is_header_parsed);
}
#[test]

View File

@ -1385,7 +1385,7 @@ mod test {
decoder.windows[0].col_count = 4;
decoder.windows[0].attribs.print_direction =
dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT as i32;
let block = ['a' as u8, 'b' as u8] as [c_uchar; 2];
let block = [b'a', b'b'] as [c_uchar; 2];
decoder.process_p16(&block);

View File

@ -24,7 +24,6 @@ pub mod utils;
#[cfg(windows)]
use std::os::windows::io::{FromRawHandle, RawHandle};
use std::{io::Write, os::raw::c_char, os::raw::c_int};
use args::Args;
use bindings::*;
@ -37,7 +36,11 @@ use utils::is_true;
use env_logger::{builder, Target};
use log::{warn, LevelFilter};
use std::ffi::CStr;
use std::{
ffi::CStr,
io::Write,
os::raw::{c_char, c_double, c_int, c_long, c_uint},
};
#[cfg(test)]
static mut cb_708: c_int = 0;
@ -60,6 +63,15 @@ extern "C" {
static mut MPEG_CLOCK_FREQ: c_int;
static mut tlt_config: ccx_s_teletext_config;
static mut ccx_options: ccx_s_options;
static mut pts_big_change: c_uint;
static mut current_fps: c_double;
static mut frames_since_ref_time: c_int;
static mut total_frames_count: c_uint;
static mut gop_time: gop_time_code;
static mut first_gop_time: gop_time_code;
static mut fts_at_gop_start: c_long;
static mut gop_rollover: c_int;
static mut ccx_common_timing_settings: ccx_common_timing_settings_t;
static mut capitalization_list: word_list;
static mut profane: word_list;
}

View File

@ -6,7 +6,7 @@ use lib_ccxr::util::log::*;
use lib_ccxr::util::{bits::*, levenshtein::*};
use std::convert::TryInto;
use std::os::raw::{c_char, c_int, c_uint};
use std::ffi::{c_char, c_int, c_uint};
/// Initializes the logger at the rust side.
///

View File

@ -1,9 +1,16 @@
use crate::bindings::*;
#![allow(clippy::useless_conversion)]
use std::ffi::CStr;
use std::os::raw::{c_char, c_int};
use std::convert::TryInto;
use std::ffi::{c_char, c_int, c_long, CStr};
use lib_ccxr::time::units::*;
use crate::{
bindings::*, cb_708, cb_field1, cb_field2, ccx_common_timing_settings as timing_settings,
current_fps, first_gop_time, frames_since_ref_time, fts_at_gop_start, gop_rollover, gop_time,
pts_big_change, total_frames_count,
};
use lib_ccxr::common::FrameType;
use lib_ccxr::time::{c_functions as c, *};
use lib_ccxr::util::{time::*, write_string_into_pointer};
/// Rust equivalent for `timestamp_to_srttime` function in C. Uses C-native types as input and
@ -122,3 +129,420 @@ pub unsafe extern "C" fn ccxr_millis_to_time(
*seconds = seconds_value;
*ms = ms_value;
}
/// Construct a [`TimingContext`] from a pointer of `ccx_common_timing_ctx`.
///
/// It is used to move data of [`TimingContext`] from C to Rust.
///
/// # Safety
///
/// `ctx` should not be null.
unsafe fn generate_timing_context(ctx: *const ccx_common_timing_ctx) -> TimingContext {
let pts_set = match (*ctx).pts_set {
0 => PtsSet::No,
1 => PtsSet::Received,
2 => PtsSet::MinPtsSet,
_ => panic!("incorrect value for pts_set"),
};
let min_pts_adjusted = (*ctx).min_pts_adjusted != 0;
let current_pts = MpegClockTick::new((*ctx).current_pts);
let current_picture_coding_type = match (*ctx).current_picture_coding_type {
ccx_frame_type_CCX_FRAME_TYPE_RESET_OR_UNKNOWN => FrameType::ResetOrUnknown,
ccx_frame_type_CCX_FRAME_TYPE_I_FRAME => FrameType::IFrame,
ccx_frame_type_CCX_FRAME_TYPE_P_FRAME => FrameType::PFrame,
ccx_frame_type_CCX_FRAME_TYPE_B_FRAME => FrameType::BFrame,
ccx_frame_type_CCX_FRAME_TYPE_D_FRAME => FrameType::DFrame,
_ => panic!("Incorrect value for current_picture_coding_type"),
};
let current_tref = FrameCount::new((*ctx).current_tref.try_into().unwrap());
let min_pts = MpegClockTick::new((*ctx).min_pts);
let sync_pts = MpegClockTick::new((*ctx).sync_pts);
let minimum_fts = Timestamp::from_millis((*ctx).minimum_fts);
let fts_now = Timestamp::from_millis((*ctx).fts_now);
let fts_offset = Timestamp::from_millis((*ctx).fts_offset);
let fts_fc_offset = Timestamp::from_millis((*ctx).fts_fc_offset);
let fts_max = Timestamp::from_millis((*ctx).fts_max);
let fts_global = Timestamp::from_millis((*ctx).fts_global);
let sync_pts2fts_set = (*ctx).sync_pts2fts_set != 0;
let sync_pts2fts_fts = Timestamp::from_millis((*ctx).sync_pts2fts_fts);
let sync_pts2fts_pts = MpegClockTick::new((*ctx).sync_pts2fts_pts);
let pts_reset = (*ctx).pts_reset != 0;
TimingContext::from_raw_parts(
pts_set,
min_pts_adjusted,
current_pts,
current_picture_coding_type,
current_tref,
min_pts,
sync_pts,
minimum_fts,
fts_now,
fts_offset,
fts_fc_offset,
fts_max,
fts_global,
sync_pts2fts_set,
sync_pts2fts_fts,
sync_pts2fts_pts,
pts_reset,
)
}
/// Copy the contents [`TimingContext`] to a `ccx_common_timing_ctx`.
///
/// It is used to move data of [`TimingContext`] from Rust to C.
///
/// # Safety
///
/// `ctx` should not be null.
unsafe fn write_back_to_common_timing_ctx(
ctx: *mut ccx_common_timing_ctx,
timing_ctx: &TimingContext,
) {
let (
pts_set,
min_pts_adjusted,
current_pts,
current_picture_coding_type,
current_tref,
min_pts,
sync_pts,
minimum_fts,
fts_now,
fts_offset,
fts_fc_offset,
fts_max,
fts_global,
sync_pts2fts_set,
sync_pts2fts_fts,
sync_pts2fts_pts,
pts_reset,
) = timing_ctx.as_raw_parts();
(*ctx).pts_set = match pts_set {
PtsSet::No => 0,
PtsSet::Received => 1,
PtsSet::MinPtsSet => 2,
};
(*ctx).min_pts_adjusted = if min_pts_adjusted { 1 } else { 0 };
(*ctx).current_pts = current_pts.as_i64();
(*ctx).current_picture_coding_type = match current_picture_coding_type {
FrameType::ResetOrUnknown => ccx_frame_type_CCX_FRAME_TYPE_RESET_OR_UNKNOWN,
FrameType::IFrame => ccx_frame_type_CCX_FRAME_TYPE_I_FRAME,
FrameType::PFrame => ccx_frame_type_CCX_FRAME_TYPE_P_FRAME,
FrameType::BFrame => ccx_frame_type_CCX_FRAME_TYPE_B_FRAME,
FrameType::DFrame => ccx_frame_type_CCX_FRAME_TYPE_D_FRAME,
};
(*ctx).current_tref = current_tref.as_u64().try_into().unwrap();
(*ctx).min_pts = min_pts.as_i64();
(*ctx).sync_pts = sync_pts.as_i64();
(*ctx).minimum_fts = minimum_fts.millis();
(*ctx).fts_now = fts_now.millis();
(*ctx).fts_offset = fts_offset.millis();
(*ctx).fts_fc_offset = fts_fc_offset.millis();
(*ctx).fts_max = fts_max.millis();
(*ctx).fts_global = fts_global.millis();
(*ctx).sync_pts2fts_set = if sync_pts2fts_set { 1 } else { 0 };
(*ctx).sync_pts2fts_fts = sync_pts2fts_fts.millis();
(*ctx).sync_pts2fts_pts = sync_pts2fts_pts.as_i64();
(*ctx).pts_reset = if pts_reset { 1 } else { 0 };
}
/// Write to [`GLOBAL_TIMING_INFO`] from the equivalent static variables in C.
///
/// It is used to move data of [`GLOBAL_TIMING_INFO`] from C to Rust.
///
/// # Safety
///
/// All the static variables should be initialized and in valid state.
unsafe fn apply_timing_info() {
let mut timing_info = GLOBAL_TIMING_INFO.write().unwrap();
timing_info.cb_field1 = cb_field1.try_into().unwrap();
timing_info.cb_field2 = cb_field2.try_into().unwrap();
timing_info.cb_708 = cb_708.try_into().unwrap();
timing_info.pts_big_change = pts_big_change != 0;
timing_info.current_fps = current_fps;
timing_info.frames_since_ref_time = FrameCount::new(frames_since_ref_time.try_into().unwrap());
timing_info.total_frames_count = FrameCount::new(total_frames_count.into());
timing_info.gop_time = generate_gop_time_code(gop_time);
timing_info.first_gop_time = generate_gop_time_code(first_gop_time);
timing_info.fts_at_gop_start = Timestamp::from_millis(fts_at_gop_start.into());
timing_info.gop_rollover = gop_rollover != 0;
timing_info.timing_settings.disable_sync_check = timing_settings.disable_sync_check != 0;
timing_info.timing_settings.no_sync = timing_settings.no_sync != 0;
timing_info.timing_settings.is_elementary_stream = timing_settings.is_elementary_stream != 0;
}
/// Write from [`GLOBAL_TIMING_INFO`] to the equivalent static variables in C.
///
/// It is used to move data of [`GLOBAL_TIMING_INFO`] from Rust to C.
///
/// # Safety
///
/// All the static variables should be initialized and in valid state.
unsafe fn write_back_from_timing_info() {
let timing_info = GLOBAL_TIMING_INFO.read().unwrap();
cb_field1 = timing_info.cb_field1.try_into().unwrap();
cb_field2 = timing_info.cb_field2.try_into().unwrap();
cb_708 = timing_info.cb_708.try_into().unwrap();
pts_big_change = if timing_info.pts_big_change { 1 } else { 0 };
current_fps = timing_info.current_fps;
frames_since_ref_time = timing_info
.frames_since_ref_time
.as_u64()
.try_into()
.unwrap();
total_frames_count = timing_info.total_frames_count.as_u64().try_into().unwrap();
gop_time = write_gop_time_code(timing_info.gop_time);
first_gop_time = write_gop_time_code(timing_info.first_gop_time);
fts_at_gop_start = timing_info.fts_at_gop_start.millis() as c_long;
gop_rollover = if timing_info.gop_rollover { 1 } else { 0 };
timing_settings.disable_sync_check = if timing_info.timing_settings.disable_sync_check {
1
} else {
0
};
timing_settings.no_sync = if timing_info.timing_settings.no_sync {
1
} else {
0
};
timing_settings.is_elementary_stream = if timing_info.timing_settings.is_elementary_stream {
1
} else {
0
};
}
/// Construct a [`GopTimeCode`] from `gop_time_code`.
unsafe fn generate_gop_time_code(g: gop_time_code) -> Option<GopTimeCode> {
if g.inited == 0 {
None
} else {
Some(GopTimeCode::from_raw_parts(
g.drop_frame_flag != 0,
g.time_code_hours.try_into().unwrap(),
g.time_code_minutes.try_into().unwrap(),
g.time_code_seconds.try_into().unwrap(),
g.time_code_pictures.try_into().unwrap(),
Timestamp::from_millis(g.ms),
))
}
}
/// Construct a `gop_time_code` from [`GopTimeCode`].
unsafe fn write_gop_time_code(g: Option<GopTimeCode>) -> gop_time_code {
if let Some(gop) = g {
let (
drop_frame,
time_code_hours,
time_code_minutes,
time_code_seconds,
time_code_pictures,
timestamp,
) = gop.as_raw_parts();
gop_time_code {
drop_frame_flag: if drop_frame { 1 } else { 0 },
time_code_hours: time_code_hours.into(),
time_code_minutes: time_code_minutes.into(),
marker_bit: 0,
time_code_seconds: time_code_seconds.into(),
time_code_pictures: time_code_pictures.into(),
inited: 1,
ms: timestamp.millis(),
}
} else {
gop_time_code {
drop_frame_flag: 0,
time_code_hours: 0,
time_code_minutes: 0,
marker_bit: 0,
time_code_seconds: 0,
time_code_pictures: 0,
inited: 0,
ms: 0,
}
}
}
/// Rust equivalent for `add_current_pts` function in C. Uses C-native types as input and output.
///
/// # Safety
///
/// `ctx` must not be null.
#[no_mangle]
pub unsafe extern "C" fn ccxr_add_current_pts(ctx: *mut ccx_common_timing_ctx, pts: c_long) {
apply_timing_info();
let mut context = generate_timing_context(ctx);
c::add_current_pts(&mut context, MpegClockTick::new(pts.into()));
write_back_to_common_timing_ctx(ctx, &context);
write_back_from_timing_info();
}
/// Rust equivalent for `set_current_pts` function in C. Uses C-native types as input and output.
///
/// # Safety
///
/// `ctx` must not be null.
#[no_mangle]
pub unsafe extern "C" fn ccxr_set_current_pts(ctx: *mut ccx_common_timing_ctx, pts: c_long) {
apply_timing_info();
let mut context = generate_timing_context(ctx);
c::set_current_pts(&mut context, MpegClockTick::new(pts.into()));
write_back_to_common_timing_ctx(ctx, &context);
write_back_from_timing_info();
}
/// Rust equivalent for `set_fts` function in C. Uses C-native types as input and output.
///
/// # Safety
///
/// `ctx` must not be null.
#[no_mangle]
pub unsafe extern "C" fn ccxr_set_fts(ctx: *mut ccx_common_timing_ctx) -> c_int {
apply_timing_info();
let mut context = generate_timing_context(ctx);
let ans = c::set_fts(&mut context);
write_back_to_common_timing_ctx(ctx, &context);
write_back_from_timing_info();
if ans {
1
} else {
0
}
}
/// Rust equivalent for `get_fts` function in C. Uses C-native types as input and output.
///
/// # Safety
///
/// `ctx` must not be null. `current_field` must be 1, 2 or 3.
#[no_mangle]
pub unsafe extern "C" fn ccxr_get_fts(
ctx: *mut ccx_common_timing_ctx,
current_field: c_int,
) -> c_long {
apply_timing_info();
let mut context = generate_timing_context(ctx);
let caption_field = match current_field {
1 => CaptionField::Field1,
2 => CaptionField::Field2,
3 => CaptionField::Cea708,
_ => panic!("incorrect value for caption field"),
};
let ans = c::get_fts(&mut context, caption_field);
write_back_to_common_timing_ctx(ctx, &context);
write_back_from_timing_info();
ans.millis().try_into().unwrap()
}
/// Rust equivalent for `get_fts_max` function in C. Uses C-native types as input and output.
///
/// # Safety
///
/// `ctx` must not be null.
#[no_mangle]
pub unsafe extern "C" fn ccxr_get_fts_max(ctx: *mut ccx_common_timing_ctx) -> c_long {
apply_timing_info();
let mut context = generate_timing_context(ctx);
let ans = c::get_fts_max(&mut context);
write_back_to_common_timing_ctx(ctx, &context);
write_back_from_timing_info();
ans.millis().try_into().unwrap()
}
/// Rust equivalent for `print_mstime_static` function in C. Uses C-native types as input and output.
///
/// # Safety
///
/// `buf` must not be null. It must have sufficient length to hold the time in string form.
#[no_mangle]
pub unsafe extern "C" fn ccxr_print_mstime_static(mstime: c_long, buf: *mut c_char) -> *mut c_char {
let time = Timestamp::from_millis(mstime.into());
let ans = c::print_mstime_static(time, ':');
write_string_into_pointer(buf, &ans);
buf
}
/// Rust equivalent for `print_debug_timing` function in C. Uses C-native types as input and output.
///
/// # Safety
///
/// `ctx` must not be null.
#[no_mangle]
pub unsafe extern "C" fn ccxr_print_debug_timing(ctx: *mut ccx_common_timing_ctx) {
apply_timing_info();
let mut context = generate_timing_context(ctx);
c::print_debug_timing(&mut context);
write_back_to_common_timing_ctx(ctx, &context);
write_back_from_timing_info();
}
/// Rust equivalent for `calculate_ms_gop_time` function in C. Uses C-native types as input and output.
///
/// # Safety
///
/// `g` must not be null.
#[no_mangle]
pub unsafe extern "C" fn ccxr_calculate_ms_gop_time(g: *mut gop_time_code) {
apply_timing_info();
let timing_info = GLOBAL_TIMING_INFO.read().unwrap();
(*g).ms = GopTimeCode::new(
(*g).drop_frame_flag != 0,
(*g).time_code_hours.try_into().unwrap(),
(*g).time_code_minutes.try_into().unwrap(),
(*g).time_code_seconds.try_into().unwrap(),
(*g).time_code_pictures.try_into().unwrap(),
timing_info.current_fps,
timing_info.gop_rollover,
)
.unwrap()
.timestamp()
.millis()
}
/// Rust equivalent for `gop_accepted` function in C. Uses C-native types as input and output.
///
/// # Safety
///
/// `g` must not be null.
#[no_mangle]
pub unsafe extern "C" fn ccxr_gop_accepted(g: *mut gop_time_code) -> c_int {
if let Some(gop) = generate_gop_time_code(*g) {
let ans = c::gop_accepted(gop);
if ans {
1
} else {
0
}
} else {
0
}
}