mirror of
https://github.com/CCExtractor/ccextractor.git
synced 2024-12-22 02:47:42 +00:00
[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
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:
parent
349020ece9
commit
cbd8e27fe3
@ -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;
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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,
|
||||
|
61
src/rust/lib_ccxr/src/time/c_functions.rs
Normal file
61
src/rust/lib_ccxr/src/time/c_functions.rs
Normal 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()
|
||||
}
|
||||
}
|
@ -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::*;
|
||||
|
559
src/rust/lib_ccxr/src/time/timing.rs
Normal file
559
src/rust/lib_ccxr/src/time/timing.rs
Normal 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()
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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.
|
||||
///
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user