Skip to content
Merged
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
35 changes: 32 additions & 3 deletions src/lib_ccx/avc_functions.c
Original file line number Diff line number Diff line change
Expand Up @@ -1046,7 +1046,12 @@ void slice_header(struct encoder_ctx *enc_ctx, struct lib_cc_decode *dec_ctx, un
}

// if slices are buffered - flush
if (isref)
// For I/P-only streams (like HDHomeRun recordings), flushing on every
// reference frame defeats reordering since all frames are reference frames.
// Only flush and reset on IDR frames (nal_unit_type==5), not P-frames.
// This allows P-frames to accumulate in the buffer and be sorted by PTS.
int is_idr = (nal_unit_type == CCX_NAL_TYPE_CODED_SLICE_IDR_PICTURE);
if (isref && is_idr)
{
dvprint("\nReference pic! [%s]\n", slice_types[slice_type]);
dbg_print(CCX_DMT_TIME, "\nReference pic! [%s] maxrefcnt: %3d\n",
Expand Down Expand Up @@ -1141,8 +1146,32 @@ void slice_header(struct encoder_ctx *enc_ctx, struct lib_cc_decode *dec_ctx, un

if (abs(current_index) >= MAXBFRAMES)
{
// Probably a jump in the timeline. Warn and handle gracefully.
mprint("\nFound large gap(%d) in PTS! Trying to recover ...\n", current_index);
// Large PTS gap detected. This can happen with certain encoders
// (like HDHomeRun) that produce streams where PTS jumps are common.
// Instead of just resetting current_index to 0 (which causes captions
// to pile up at the same buffer slot and become garbled), we need to:
// 1. Flush any buffered captions
// 2. Reset the reference PTS to the current PTS
// 3. Set current_index to 0 for a fresh start
// This ensures subsequent frames use the new reference point.
dbg_print(CCX_DMT_VERBOSE, "\nLarge PTS gap(%d) detected, flushing buffer and resetting reference.\n", current_index);

// Flush any buffered captions before resetting
if (dec_ctx->has_ccdata_buffered)
{
process_hdcc(enc_ctx, dec_ctx, sub);
}

// Reset the reference point to current PTS
dec_ctx->avc_ctx->currefpts = dec_ctx->timing->current_pts;

// Reset tracking variables for the new reference
dec_ctx->avc_ctx->lastmaxidx = -1;
dec_ctx->avc_ctx->maxidx = 0;
dec_ctx->avc_ctx->lastminidx = 10000;
dec_ctx->avc_ctx->minidx = 10000;

// Start with index 0 relative to the new reference
current_index = 0;
}

Expand Down
53 changes: 41 additions & 12 deletions src/rust/src/avc/nal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,12 @@ pub unsafe fn slice_header(
}

// if slices are buffered - flush
if isref == 1 {
// For I/P-only streams (like HDHomeRun recordings), flushing on every
// reference frame defeats reordering since all frames are reference frames.
// Only flush and reset on IDR frames, not P-frames.
// This allows P-frames to accumulate in the buffer and be sorted by PTS.
let is_idr = *nal_unit_type == AvcNalType::CodedSliceIdrPicture;
if isref == 1 && is_idr {
debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "Reference pic! [{}]", SLICE_TYPES[slice_type as usize]);
debug!(msg_type = DebugMessageFlag::TIME; "Reference pic! [{}] maxrefcnt: {:3}",
SLICE_TYPES[slice_type as usize], maxrefcnt);
Expand Down Expand Up @@ -518,7 +523,7 @@ pub unsafe fn slice_header(
anchor_hdcc(dec_ctx, (*dec_ctx.avc_ctx).currref);
}

let mut current_index = if ccx_options.usepicorder != 0 {
let current_index = if ccx_options.usepicorder != 0 {
// Use pic_order_cnt_lsb
// Wrap (add max index value) current_index if needed.
if (*dec_ctx.avc_ctx).currref as i64 - pic_order_cnt_lsb > (maxrefcnt / 2) as i64 {
Expand All @@ -529,21 +534,45 @@ pub unsafe fn slice_header(
} else {
// Use PTS ordering - calculate index position from PTS difference and
// frame rate

// Initialize currefpts on first frame if it hasn't been set yet.
// This avoids huge indices at the start of the stream when currefpts is 0.
if (*dec_ctx.avc_ctx).currefpts == 0 && (*dec_ctx.timing).current_pts > 0 {
(*dec_ctx.avc_ctx).currefpts = (*dec_ctx.timing).current_pts;
}

// The 2* accounts for a discrepancy between current and actual FPS
// seen in some files (CCSample2.mpg)
let pts_diff = (*dec_ctx.timing).current_pts - (*dec_ctx.avc_ctx).currefpts;
let fps_factor = MPEG_CLOCK_FREQ as f64 / current_fps;
round_portable(2.0 * pts_diff as f64 / fps_factor) as i32
};
let calculated_index = round_portable(2.0 * pts_diff as f64 / fps_factor) as i32;

// For some streams (like HDHomeRun recordings), the PTS-based index
// calculation produces unreliable results that cause caption garbling.
// If the calculated index is out of the valid range, this indicates
// the PTS ordering isn't working properly. In such cases, flush the
// buffer and reset the reference point.
if calculated_index.abs() >= MAXBFRAMES {
// Flush any buffered captions before resetting
if dec_ctx.has_ccdata_buffered != 0 {
process_hdcc(enc_ctx, dec_ctx, sub);
}

if !ccx_options.usepicorder != 0 && current_index.abs() >= MAXBFRAMES {
// Probably a jump in the timeline. Warn and handle gracefully.
info!(
"Found large gap({}) in PTS! Trying to recover ...",
current_index
);
current_index = 0;
}
// Reset the reference point to current PTS for future frames
(*dec_ctx.avc_ctx).currefpts = (*dec_ctx.timing).current_pts;

// Reset tracking variables for the new reference
(*dec_ctx.avc_ctx).lastmaxidx = -1;
(*dec_ctx.avc_ctx).maxidx = 0;
(*dec_ctx.avc_ctx).lastminidx = 10000;
(*dec_ctx.avc_ctx).minidx = 10000;

// Use index 0 relative to the new reference
0
} else {
calculated_index
}
};

// Track maximum index for this GOP
if current_index > (*dec_ctx.avc_ctx).maxidx {
Expand Down
Loading