diff --git a/src/lib_ccx/avc_functions.c b/src/lib_ccx/avc_functions.c index e6b451336..315b313d8 100644 --- a/src/lib_ccx/avc_functions.c +++ b/src/lib_ccx/avc_functions.c @@ -48,6 +48,7 @@ struct avc_ctx *init_avc(void) ctx->cc_databufsize = 1024; ctx->cc_buffer_saved = CCX_TRUE; // Was the CC buffer saved after it was last updated? + ctx->is_hevc = 0; ctx->got_seq_para = 0; ctx->nal_ref_idc = 0; ctx->seq_parameter_set_id = 0; @@ -87,16 +88,43 @@ struct avc_ctx *init_avc(void) return ctx; } +// HEVC NAL unit types for SEI messages +#define HEVC_NAL_PREFIX_SEI 39 +#define HEVC_NAL_SUFFIX_SEI 40 +#define HEVC_NAL_VPS 32 +#define HEVC_NAL_SPS 33 +#define HEVC_NAL_PPS 34 + void do_NAL(struct encoder_ctx *enc_ctx, struct lib_cc_decode *dec_ctx, unsigned char *NAL_start, LLONG NAL_length, struct cc_subtitle *sub) { unsigned char *NAL_stop; - enum ccx_avc_nal_types nal_unit_type = *NAL_start & 0x1F; + int nal_unit_type; + int nal_header_size; + unsigned char *payload_start; + + // Determine if this is HEVC or H.264 based on NAL header + // H.264 NAL header: 1 byte, type in bits [4:0] + // HEVC NAL header: 2 bytes, type in bits [6:1] of first byte + if (dec_ctx->avc_ctx->is_hevc) + { + // HEVC: NAL type is in bits [6:1] of byte 0 + nal_unit_type = (NAL_start[0] >> 1) & 0x3F; + nal_header_size = 2; + } + else + { + // H.264: NAL type is in bits [4:0] of byte 0 + nal_unit_type = NAL_start[0] & 0x1F; + nal_header_size = 1; + } NAL_stop = NAL_length + NAL_start; - NAL_stop = remove_03emu(NAL_start + 1, NAL_stop); // Add +1 to NAL_stop for TS, without it for MP4. Still don't know why + NAL_stop = remove_03emu(NAL_start + nal_header_size, NAL_stop); + payload_start = NAL_start + nal_header_size; - dvprint("BEGIN NAL unit type: %d length %d ref_idc: %d - Buffered captions before: %d\n", - nal_unit_type, NAL_stop - NAL_start - 1, dec_ctx->avc_ctx->nal_ref_idc, !dec_ctx->avc_ctx->cc_buffer_saved); + dvprint("BEGIN NAL unit type: %d length %d ref_idc: %d - Buffered captions before: %d (HEVC: %d)\n", + nal_unit_type, NAL_stop - NAL_start - nal_header_size, dec_ctx->avc_ctx->nal_ref_idc, + !dec_ctx->avc_ctx->cc_buffer_saved, dec_ctx->avc_ctx->is_hevc); if (NAL_stop == NULL) // remove_03emu failed. { @@ -104,45 +132,64 @@ void do_NAL(struct encoder_ctx *enc_ctx, struct lib_cc_decode *dec_ctx, unsigned return; } - if (nal_unit_type == CCX_NAL_TYPE_ACCESS_UNIT_DELIMITER_9) - { - // Found Access Unit Delimiter - } - else if (nal_unit_type == CCX_NAL_TYPE_SEQUENCE_PARAMETER_SET_7) - { - // Found sequence parameter set - // We need this to parse NAL type 1 (CCX_NAL_TYPE_CODED_SLICE_NON_IDR_PICTURE_1) - dec_ctx->avc_ctx->num_nal_unit_type_7++; - seq_parameter_set_rbsp(dec_ctx->avc_ctx, NAL_start + 1, NAL_stop); - dec_ctx->avc_ctx->got_seq_para = 1; - } - else if (dec_ctx->avc_ctx->got_seq_para && (nal_unit_type == CCX_NAL_TYPE_CODED_SLICE_NON_IDR_PICTURE_1 || - nal_unit_type == CCX_NAL_TYPE_CODED_SLICE_IDR_PICTURE)) // Only if nal_unit_type=1 + if (dec_ctx->avc_ctx->is_hevc) { - // Found coded slice of a non-IDR picture - // We only need the slice header data, no need to implement - // slice_layer_without_partitioning_rbsp( ); - slice_header(enc_ctx, dec_ctx, NAL_start + 1, NAL_stop, nal_unit_type, sub); - } - else if (dec_ctx->avc_ctx->got_seq_para && nal_unit_type == CCX_NAL_TYPE_SEI) - { - // Found SEI (used for subtitles) - // set_fts(ctx->timing); // FIXME - check this!!! - sei_rbsp(dec_ctx->avc_ctx, NAL_start + 1, NAL_stop); + // HEVC NAL unit processing + if (nal_unit_type == HEVC_NAL_VPS || nal_unit_type == HEVC_NAL_SPS || nal_unit_type == HEVC_NAL_PPS) + { + // Found HEVC parameter set - mark as having sequence params + // We don't parse HEVC SPS fully, but we need to enable SEI processing + dec_ctx->avc_ctx->got_seq_para = 1; + } + else if (nal_unit_type == HEVC_NAL_PREFIX_SEI || nal_unit_type == HEVC_NAL_SUFFIX_SEI) + { + // Found HEVC SEI (used for subtitles) + // SEI payload format is similar to H.264 + sei_rbsp(dec_ctx->avc_ctx, payload_start, NAL_stop); + } } - else if (dec_ctx->avc_ctx->got_seq_para && nal_unit_type == CCX_NAL_TYPE_PICTURE_PARAMETER_SET) + else { - // Found Picture parameter set + // H.264 NAL unit processing (original code) + if (nal_unit_type == CCX_NAL_TYPE_ACCESS_UNIT_DELIMITER_9) + { + // Found Access Unit Delimiter + } + else if (nal_unit_type == CCX_NAL_TYPE_SEQUENCE_PARAMETER_SET_7) + { + // Found sequence parameter set + // We need this to parse NAL type 1 (CCX_NAL_TYPE_CODED_SLICE_NON_IDR_PICTURE_1) + dec_ctx->avc_ctx->num_nal_unit_type_7++; + seq_parameter_set_rbsp(dec_ctx->avc_ctx, payload_start, NAL_stop); + dec_ctx->avc_ctx->got_seq_para = 1; + } + else if (dec_ctx->avc_ctx->got_seq_para && (nal_unit_type == CCX_NAL_TYPE_CODED_SLICE_NON_IDR_PICTURE_1 || + nal_unit_type == CCX_NAL_TYPE_CODED_SLICE_IDR_PICTURE)) + { + // Found coded slice of a non-IDR picture + // We only need the slice header data + slice_header(enc_ctx, dec_ctx, payload_start, NAL_stop, nal_unit_type, sub); + } + else if (dec_ctx->avc_ctx->got_seq_para && nal_unit_type == CCX_NAL_TYPE_SEI) + { + // Found SEI (used for subtitles) + sei_rbsp(dec_ctx->avc_ctx, payload_start, NAL_stop); + } + else if (dec_ctx->avc_ctx->got_seq_para && nal_unit_type == CCX_NAL_TYPE_PICTURE_PARAMETER_SET) + { + // Found Picture parameter set + } } + if (temp_debug) { - int len = NAL_stop - (NAL_start + 1); + int len = NAL_stop - payload_start; dbg_print(CCX_DMT_VIDES, "\n After decoding, the actual thing was (length =%d)\n", len); - dump(CCX_DMT_VIDES, NAL_start + 1, len > 160 ? 160 : len, 0, 0); + dump(CCX_DMT_VIDES, payload_start, len > 160 ? 160 : len, 0, 0); } dvprint("END NAL unit type: %d length %d ref_idc: %d - Buffered captions after: %d\n", - nal_unit_type, NAL_stop - NAL_start - 1, dec_ctx->avc_ctx->nal_ref_idc, !dec_ctx->avc_ctx->cc_buffer_saved); + nal_unit_type, NAL_stop - NAL_start - nal_header_size, dec_ctx->avc_ctx->nal_ref_idc, !dec_ctx->avc_ctx->cc_buffer_saved); } // Process inbuf bytes in buffer holding and AVC (H.264) video stream. diff --git a/src/lib_ccx/avc_functions.h b/src/lib_ccx/avc_functions.h index 0fc945ee8..b3ddd63c8 100644 --- a/src/lib_ccx/avc_functions.h +++ b/src/lib_ccx/avc_functions.h @@ -9,6 +9,7 @@ struct avc_ctx long cc_databufsize; int cc_buffer_saved; // Was the CC buffer saved after it was last updated? + int is_hevc; // Flag to indicate HEVC (H.265) mode vs H.264 int got_seq_para; unsigned nal_ref_idc; LLONG seq_parameter_set_id; diff --git a/src/lib_ccx/ccx_common_constants.h b/src/lib_ccx/ccx_common_constants.h index 9e554b0f9..bce9b2678 100644 --- a/src/lib_ccx/ccx_common_constants.h +++ b/src/lib_ccx/ccx_common_constants.h @@ -237,7 +237,8 @@ enum ccx_bufferdata_type CCX_ISDB_SUBTITLE = 8, /* BUffer where cc data contain 3 byte cc_valid ccdata 1 ccdata 2 */ CCX_RAW_TYPE = 9, - CCX_DVD_SUBTITLE = 10 + CCX_DVD_SUBTITLE = 10, + CCX_HEVC = 11 }; enum ccx_frame_type diff --git a/src/lib_ccx/general_loop.c b/src/lib_ccx/general_loop.c index 6ac92ed15..ced6b39df 100644 --- a/src/lib_ccx/general_loop.c +++ b/src/lib_ccx/general_loop.c @@ -779,6 +779,13 @@ int process_data(struct encoder_ctx *enc_ctx, struct lib_cc_decode *dec_ctx, str else if (data_node->bufferdatatype == CCX_H264) // H.264 data from TS file { dec_ctx->in_bufferdatatype = CCX_H264; + dec_ctx->avc_ctx->is_hevc = 0; + got = process_avc(enc_ctx, dec_ctx, data_node->buffer, data_node->len, dec_sub); + } + else if (data_node->bufferdatatype == CCX_HEVC) // HEVC data from TS file + { + dec_ctx->in_bufferdatatype = CCX_H264; // Use same internal type for NAL processing + dec_ctx->avc_ctx->is_hevc = 1; got = process_avc(enc_ctx, dec_ctx, data_node->buffer, data_node->len, dec_sub); } else if (data_node->bufferdatatype == CCX_RAW_TYPE) diff --git a/src/lib_ccx/ts_functions.c b/src/lib_ccx/ts_functions.c index 14a82c93f..e896a0c31 100644 --- a/src/lib_ccx/ts_functions.c +++ b/src/lib_ccx/ts_functions.c @@ -135,7 +135,7 @@ enum ccx_bufferdata_type get_buffer_type(struct cap_info *cinfo) } else if (cinfo->stream == CCX_STREAM_TYPE_VIDEO_HEVC) { - return CCX_H264; // HEVC uses same buffer type as H264 + return CCX_HEVC; } else if (cinfo->stream == CCX_STREAM_TYPE_PRIVATE_MPEG2 && cinfo->codec == CCX_CODEC_DVB) { @@ -390,15 +390,26 @@ void look_for_caption_data(struct ccx_demuxer *ctx, struct ts_payload *payload) // Check for H.264/H.265 NAL start codes if (es_data[0] == 0x00 && es_data[1] == 0x00 && es_data[2] == 0x00 && es_data[3] == 0x01) { - // Could be H.264 or H.265 - check NAL type - unsigned char nal_type = es_data[4] & 0x1F; - if (nal_type == 7 || nal_type == 8) // SPS or PPS + // Check for H.264 NAL types first (1-byte header, type in bits 4:0) + unsigned char h264_nal_type = es_data[4] & 0x1F; + if (h264_nal_type == 7 || h264_nal_type == 8) // H.264 SPS or PPS stream_type = CCX_STREAM_TYPE_VIDEO_H264; + else + { + // Check for HEVC NAL types (2-byte header, type in bits 6:1 of first byte) + unsigned char hevc_nal_type = (es_data[4] >> 1) & 0x3F; + // HEVC VPS=32, SPS=33, PPS=34, PREFIX_SEI=39, SUFFIX_SEI=40 + // Also check for IDR (19, 20) and CRA (21) which are common first NALs + if (hevc_nal_type == 32 || hevc_nal_type == 33 || hevc_nal_type == 34 || + hevc_nal_type == 39 || hevc_nal_type == 40 || + hevc_nal_type == 19 || hevc_nal_type == 20 || hevc_nal_type == 21) + stream_type = CCX_STREAM_TYPE_VIDEO_HEVC; + } } mprint("PID %u detected as video stream (no PAT/PMT) - assuming %s.\n", payload->pid, - stream_type == CCX_STREAM_TYPE_VIDEO_H264 ? "H.264" : "MPEG-2"); + stream_type == CCX_STREAM_TYPE_VIDEO_H264 ? "H.264" : (stream_type == CCX_STREAM_TYPE_VIDEO_HEVC ? "HEVC" : "MPEG-2")); // Register this PID as a video stream that may contain captions update_capinfo(ctx, payload->pid, stream_type, CCX_CODEC_ATSC_CC, 0, NULL); @@ -661,7 +672,10 @@ int copy_payload_to_capbuf(struct cap_info *cinfo, struct ts_payload *payload) int newcapbuflen; if (cinfo->ignore == CCX_TRUE && - (cinfo->stream != CCX_STREAM_TYPE_VIDEO_MPEG2 || !ccx_options.analyze_video_stream)) + ((cinfo->stream != CCX_STREAM_TYPE_VIDEO_MPEG2 && + cinfo->stream != CCX_STREAM_TYPE_VIDEO_H264 && + cinfo->stream != CCX_STREAM_TYPE_VIDEO_HEVC) || + !ccx_options.analyze_video_stream)) { return CCX_OK; } @@ -948,7 +962,10 @@ long ts_readstream(struct ccx_demuxer *ctx, struct demuxer_data **data) continue; } else if (cinfo->ignore == CCX_TRUE && - (cinfo->stream != CCX_STREAM_TYPE_VIDEO_MPEG2 || !ccx_options.analyze_video_stream)) + ((cinfo->stream != CCX_STREAM_TYPE_VIDEO_MPEG2 && + cinfo->stream != CCX_STREAM_TYPE_VIDEO_H264 && + cinfo->stream != CCX_STREAM_TYPE_VIDEO_HEVC) || + !ccx_options.analyze_video_stream)) { if (cinfo->codec_private_data) { diff --git a/src/lib_ccx/ts_info.c b/src/lib_ccx/ts_info.c index 639f2f212..5f894e09a 100644 --- a/src/lib_ccx/ts_info.c +++ b/src/lib_ccx/ts_info.c @@ -102,7 +102,9 @@ int get_video_stream(struct ccx_demuxer *ctx) struct cap_info *iter; list_for_each_entry(iter, &ctx->cinfo_tree.all_stream, all_stream, struct cap_info) { - if (iter->stream == CCX_STREAM_TYPE_VIDEO_MPEG2) + if (iter->stream == CCX_STREAM_TYPE_VIDEO_MPEG2 || + iter->stream == CCX_STREAM_TYPE_VIDEO_H264 || + iter->stream == CCX_STREAM_TYPE_VIDEO_HEVC) return iter->pid; } return -1; diff --git a/src/rust/src/avc/common_types.rs b/src/rust/src/avc/common_types.rs index 8955cf8a5..4fcb4e5ea 100644 --- a/src/rust/src/avc/common_types.rs +++ b/src/rust/src/avc/common_types.rs @@ -9,6 +9,7 @@ pub struct AvcContextRust { pub cc_databufsize: usize, pub cc_buffer_saved: bool, + pub is_hevc: bool, pub got_seq_para: bool, pub nal_ref_idc: u32, pub seq_parameter_set_id: i64, @@ -55,6 +56,7 @@ impl Default for AvcContextRust { cc_databufsize: 1024, cc_buffer_saved: true, + is_hevc: false, got_seq_para: false, nal_ref_idc: 0, seq_parameter_set_id: 0, diff --git a/src/rust/src/avc/core.rs b/src/rust/src/avc/core.rs index e06526458..845555ac8 100644 --- a/src/rust/src/avc/core.rs +++ b/src/rust/src/avc/core.rs @@ -4,6 +4,7 @@ use crate::avc::sei::*; use crate::bindings::{cc_subtitle, encoder_ctx, lib_cc_decode, realloc}; use crate::ctorust::FromCType; use crate::libccxr_exports::time::ccxr_set_fts; +use crate::{anchor_hdcc, current_fps, process_hdcc, store_hdcc, MPEG_CLOCK_FREQ}; use lib_ccxr::common::AvcNalType; use lib_ccxr::util::log::DebugMessageFlag; use lib_ccxr::{debug, info}; @@ -17,6 +18,50 @@ pub fn round_portable(x: f64) -> f64 { (x + 0.5).floor() } +// HEVC NAL unit types (defined for completeness, not all currently used) +#[allow(dead_code)] +const HEVC_NAL_TRAIL_N: u8 = 0; +#[allow(dead_code)] +const HEVC_NAL_TRAIL_R: u8 = 1; +#[allow(dead_code)] +const HEVC_NAL_TSA_N: u8 = 2; +#[allow(dead_code)] +const HEVC_NAL_TSA_R: u8 = 3; +#[allow(dead_code)] +const HEVC_NAL_STSA_N: u8 = 4; +#[allow(dead_code)] +const HEVC_NAL_STSA_R: u8 = 5; +#[allow(dead_code)] +const HEVC_NAL_RADL_N: u8 = 6; +#[allow(dead_code)] +const HEVC_NAL_RADL_R: u8 = 7; +#[allow(dead_code)] +const HEVC_NAL_RASL_N: u8 = 8; +#[allow(dead_code)] +const HEVC_NAL_RASL_R: u8 = 9; +#[allow(dead_code)] +const HEVC_NAL_BLA_W_LP: u8 = 16; +#[allow(dead_code)] +const HEVC_NAL_BLA_W_RADL: u8 = 17; +#[allow(dead_code)] +const HEVC_NAL_BLA_N_LP: u8 = 18; +const HEVC_NAL_IDR_W_RADL: u8 = 19; +const HEVC_NAL_IDR_N_LP: u8 = 20; +#[allow(dead_code)] +const HEVC_NAL_CRA_NUT: u8 = 21; +const HEVC_NAL_VPS: u8 = 32; +const HEVC_NAL_SPS: u8 = 33; +const HEVC_NAL_PPS: u8 = 34; +const HEVC_NAL_PREFIX_SEI: u8 = 39; +const HEVC_NAL_SUFFIX_SEI: u8 = 40; + +/// Helper to check if HEVC NAL is a VCL (Video Coding Layer) type +fn is_hevc_vcl_nal(nal_type: u8) -> bool { + // VCL NAL types are 0-31 + // Most common are 0-21 (slice types) + nal_type <= 21 +} + /// Process NAL unit data /// # Safety /// This function is unsafe because it processes raw NAL data @@ -31,8 +76,17 @@ pub unsafe fn do_nal( return Ok(()); } + let is_hevc = (*dec_ctx.avc_ctx).is_hevc != 0; + let (nal_unit_type_raw, nal_header_size): (u8, usize) = if is_hevc { + // HEVC: NAL type is in bits [6:1] of byte 0, header is 2 bytes + ((nal_start[0] >> 1) & 0x3F, 2) + } else { + // H.264: NAL type is in bits [4:0] of byte 0, header is 1 byte + (nal_start[0] & 0x1F, 1) + }; + let nal_unit_type = - AvcNalType::from_ctype(nal_start[0] & 0x1F).unwrap_or(AvcNalType::Unspecified0); + AvcNalType::from_ctype(nal_unit_type_raw).unwrap_or(AvcNalType::Unspecified0); // Calculate NAL_stop pointer equivalent let original_length = nal_length as usize; @@ -45,15 +99,18 @@ pub unsafe fn do_nal( return Ok(()); } - // Get the working slice (skip first byte for remove_03emu) - let mut working_buffer = nal_start[1..original_length].to_vec(); + // Get the working slice (skip header bytes for remove_03emu) + if original_length <= nal_header_size { + return Ok(()); + } + let mut working_buffer = nal_start[nal_header_size..original_length].to_vec(); let processed_length = match remove_03emu(&mut working_buffer) { Some(len) => len, None => { info!( - "Notice: NAL of type {:?} had to be skipped because remove_03emu failed.", - nal_unit_type + "Notice: NAL of type {} had to be skipped because remove_03emu failed. (HEVC: {})", + nal_unit_type_raw, is_hevc ); return Ok(()); } @@ -63,100 +120,218 @@ pub unsafe fn do_nal( working_buffer.truncate(processed_length); debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; - "BEGIN NAL unit type: {:?} length {} ref_idc: {} - Buffered captions before: {}", - nal_unit_type, + "BEGIN NAL unit type: {} length {} ref_idc: {} - Buffered captions before: {} (HEVC: {})", + nal_unit_type_raw, working_buffer.len(), (*dec_ctx.avc_ctx).nal_ref_idc, - if (*dec_ctx.avc_ctx).cc_buffer_saved != 0 { 0 } else { 1 } + if (*dec_ctx.avc_ctx).cc_buffer_saved != 0 { 0 } else { 1 }, + is_hevc ); - match nal_unit_type { - AvcNalType::AccessUnitDelimiter9 => { - // Found Access Unit Delimiter - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "Found Access Unit Delimiter"); - } + if is_hevc { + // HEVC NAL unit processing + match nal_unit_type_raw { + HEVC_NAL_VPS | HEVC_NAL_SPS | HEVC_NAL_PPS => { + // Found HEVC parameter set - mark as having sequence params + // We don't parse HEVC SPS fully, but we need to enable SEI processing + (*dec_ctx.avc_ctx).got_seq_para = 1; + } + HEVC_NAL_PREFIX_SEI | HEVC_NAL_SUFFIX_SEI if (*dec_ctx.avc_ctx).got_seq_para != 0 => { + // Found HEVC SEI (used for subtitles) + // SEI payload format is similar to H.264 + ccxr_set_fts(enc_ctx.timing); + + // Load current context to preserve SPS info needed by sei_rbsp + let mut ctx_rust = AvcContextRust::from_ctype(*dec_ctx.avc_ctx).unwrap(); + + // Reset CC count for this SEI (don't accumulate across SEIs) + // IMPORTANT: Don't clear cc_data vector - copy_ccdata_to_buffer checks + // cc_data.len(), which becomes 0 if we clear(). Just reset cc_count. + ctx_rust.cc_count = 0; + + sei_rbsp(&mut ctx_rust, &working_buffer); + + // If this SEI contained CC data, process it immediately + if ctx_rust.cc_count > 0 { + let required_size = (ctx_rust.cc_count as usize * 3) + 1; + if required_size > (*dec_ctx.avc_ctx).cc_databufsize as usize { + let new_size = required_size * 2; + let new_ptr = + realloc((*dec_ctx.avc_ctx).cc_data as *mut c_void, new_size as _) + as *mut u8; + if new_ptr.is_null() { + return Err("Failed to realloc cc_data".into()); + } + (*dec_ctx.avc_ctx).cc_data = new_ptr; + (*dec_ctx.avc_ctx).cc_databufsize = new_size as _; + } - AvcNalType::SequenceParameterSet7 => { - // Found sequence parameter set - // We need this to parse NAL type 1 (CodedSliceNonIdrPicture1) - (*dec_ctx.avc_ctx).num_nal_unit_type_7 += 1; + if !(*dec_ctx.avc_ctx).cc_data.is_null() { + let c_data = slice::from_raw_parts_mut( + (*dec_ctx.avc_ctx).cc_data, + (*dec_ctx.avc_ctx).cc_databufsize as usize, + ); + // Only copy the actual data (cc_count * 3 + 1 for 0xFF marker), not the full vector + let copy_len = required_size.min(c_data.len()).min(ctx_rust.cc_data.len()); + c_data[..copy_len].copy_from_slice(&ctx_rust.cc_data[..copy_len]); + } - let mut ctx_rust = AvcContextRust::from_ctype(*dec_ctx.avc_ctx).unwrap(); - seq_parameter_set_rbsp(&mut ctx_rust, &working_buffer)?; + (*dec_ctx.avc_ctx).cc_count = ctx_rust.cc_count; + (*dec_ctx.avc_ctx).ccblocks_in_avc_total += ctx_rust.ccblocks_in_avc_total; + (*dec_ctx.avc_ctx).ccblocks_in_avc_lost += ctx_rust.ccblocks_in_avc_lost; - (*dec_ctx.avc_ctx).seq_parameter_set_id = ctx_rust.seq_parameter_set_id; - (*dec_ctx.avc_ctx).log2_max_frame_num = ctx_rust.log2_max_frame_num; - (*dec_ctx.avc_ctx).pic_order_cnt_type = ctx_rust.pic_order_cnt_type; - (*dec_ctx.avc_ctx).log2_max_pic_order_cnt_lsb = ctx_rust.log2_max_pic_order_cnt_lsb; - (*dec_ctx.avc_ctx).frame_mbs_only_flag = - if ctx_rust.frame_mbs_only_flag { 1 } else { 0 }; - (*dec_ctx.avc_ctx).num_nal_hrd = ctx_rust.num_nal_hrd as _; - (*dec_ctx.avc_ctx).num_vcl_hrd = ctx_rust.num_vcl_hrd as _; + // Calculate PTS-based sequence number for proper B-frame reordering + // This is similar to H.264's PTS ordering mode - (*dec_ctx.avc_ctx).got_seq_para = 1; - } + // Initialize currefpts on first frame if it hasn't been set yet + if (*dec_ctx.avc_ctx).currefpts == 0 && (*dec_ctx.timing).current_pts > 0 { + (*dec_ctx.avc_ctx).currefpts = (*dec_ctx.timing).current_pts; + anchor_hdcc(dec_ctx, 0); + } - AvcNalType::CodedSliceNonIdrPicture1 | AvcNalType::CodedSliceIdrPicture - if (*dec_ctx.avc_ctx).got_seq_para != 0 => - { - // Found coded slice of a non-IDR picture or IDR picture - // We only need the slice header data, no need to implement - // slice_layer_without_partitioning_rbsp( ); - slice_header(enc_ctx, dec_ctx, &mut working_buffer, &nal_unit_type, sub)?; + // Calculate sequence index from PTS difference + let pts_diff = (*dec_ctx.timing).current_pts - (*dec_ctx.avc_ctx).currefpts; + let fps_factor = MPEG_CLOCK_FREQ as f64 / current_fps; + let calculated_index = + round_portable(2.0 * pts_diff as f64 / fps_factor) as i32; + + // If the calculated index is out of range, flush buffer and reset + let current_index = 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); + } + // Reset the reference point to current PTS + (*dec_ctx.avc_ctx).currefpts = (*dec_ctx.timing).current_pts; + anchor_hdcc(dec_ctx, 0); + 0 + } else { + calculated_index + }; + + store_hdcc( + enc_ctx, + dec_ctx, + (*dec_ctx.avc_ctx).cc_data, + ctx_rust.cc_count as i32, + current_index, + (*dec_ctx.timing).fts_now, + sub, + ); + (*dec_ctx.avc_ctx).cc_buffer_saved = 1; + } + } + // HEVC VCL NAL units (slice data) + // Only flush on IDR frames (NAL types 19, 20) to allow B-frame reordering + HEVC_NAL_IDR_W_RADL | HEVC_NAL_IDR_N_LP if (*dec_ctx.avc_ctx).got_seq_para != 0 => { + // Found HEVC IDR frame - flush buffered CC data and reset anchor + if dec_ctx.has_ccdata_buffered != 0 { + process_hdcc(enc_ctx, dec_ctx, sub); + } + // Reset reference point for new GOP + (*dec_ctx.avc_ctx).currefpts = (*dec_ctx.timing).current_pts; + anchor_hdcc(dec_ctx, 0); + } + // Other VCL NAL types - don't flush, let reordering work + nal_type if is_hevc_vcl_nal(nal_type) && (*dec_ctx.avc_ctx).got_seq_para != 0 => { + // Non-IDR slice - don't flush to allow B-frame reordering + } + _ => { + // Other HEVC NAL types - ignore + } } + } else { + // H.264 NAL unit processing (original code) + match nal_unit_type { + AvcNalType::AccessUnitDelimiter9 => { + // Found Access Unit Delimiter + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "Found Access Unit Delimiter"); + } + + AvcNalType::SequenceParameterSet7 => { + // Found sequence parameter set + // We need this to parse NAL type 1 (CodedSliceNonIdrPicture1) + (*dec_ctx.avc_ctx).num_nal_unit_type_7 += 1; + + let mut ctx_rust = AvcContextRust::from_ctype(*dec_ctx.avc_ctx).unwrap(); + seq_parameter_set_rbsp(&mut ctx_rust, &working_buffer)?; + + (*dec_ctx.avc_ctx).seq_parameter_set_id = ctx_rust.seq_parameter_set_id; + (*dec_ctx.avc_ctx).log2_max_frame_num = ctx_rust.log2_max_frame_num; + (*dec_ctx.avc_ctx).pic_order_cnt_type = ctx_rust.pic_order_cnt_type; + (*dec_ctx.avc_ctx).log2_max_pic_order_cnt_lsb = ctx_rust.log2_max_pic_order_cnt_lsb; + (*dec_ctx.avc_ctx).frame_mbs_only_flag = + if ctx_rust.frame_mbs_only_flag { 1 } else { 0 }; + (*dec_ctx.avc_ctx).num_nal_hrd = ctx_rust.num_nal_hrd as _; + (*dec_ctx.avc_ctx).num_vcl_hrd = ctx_rust.num_vcl_hrd as _; + + (*dec_ctx.avc_ctx).got_seq_para = 1; + } + + AvcNalType::CodedSliceNonIdrPicture1 | AvcNalType::CodedSliceIdrPicture + if (*dec_ctx.avc_ctx).got_seq_para != 0 => + { + // Found coded slice of a non-IDR picture or IDR picture + // We only need the slice header data, no need to implement + // slice_layer_without_partitioning_rbsp( ); + slice_header(enc_ctx, dec_ctx, &mut working_buffer, &nal_unit_type, sub)?; + } - AvcNalType::Sei if (*dec_ctx.avc_ctx).got_seq_para != 0 => { - // Found SEI (used for subtitles) - ccxr_set_fts(enc_ctx.timing); + AvcNalType::Sei if (*dec_ctx.avc_ctx).got_seq_para != 0 => { + // Found SEI (used for subtitles) + ccxr_set_fts(enc_ctx.timing); - let mut ctx_rust = AvcContextRust::from_ctype(*dec_ctx.avc_ctx).unwrap(); - let old_cc_count = ctx_rust.cc_count; + let mut ctx_rust = AvcContextRust::from_ctype(*dec_ctx.avc_ctx).unwrap(); + let old_cc_count = ctx_rust.cc_count; - sei_rbsp(&mut ctx_rust, &working_buffer); + sei_rbsp(&mut ctx_rust, &working_buffer); - if ctx_rust.cc_count > old_cc_count { - let required_size = (ctx_rust.cc_count as usize * 3) + 1; - if required_size > (*dec_ctx.avc_ctx).cc_databufsize as usize { - let new_size = required_size * 2; // Some headroom - let new_ptr = realloc((*dec_ctx.avc_ctx).cc_data as *mut c_void, new_size as _) - as *mut u8; - if new_ptr.is_null() { - return Err("Failed to realloc cc_data".into()); + if ctx_rust.cc_count > old_cc_count { + let required_size = (ctx_rust.cc_count as usize * 3) + 1; + if required_size > (*dec_ctx.avc_ctx).cc_databufsize as usize { + let new_size = required_size * 2; // Some headroom + let new_ptr = + realloc((*dec_ctx.avc_ctx).cc_data as *mut c_void, new_size as _) + as *mut u8; + if new_ptr.is_null() { + return Err("Failed to realloc cc_data".into()); + } + (*dec_ctx.avc_ctx).cc_data = new_ptr; + (*dec_ctx.avc_ctx).cc_databufsize = new_size as _; } - (*dec_ctx.avc_ctx).cc_data = new_ptr; - (*dec_ctx.avc_ctx).cc_databufsize = new_size as _; - } - if !(*dec_ctx.avc_ctx).cc_data.is_null() { - let c_data = slice::from_raw_parts_mut( - (*dec_ctx.avc_ctx).cc_data, - (*dec_ctx.avc_ctx).cc_databufsize as usize, - ); - let rust_data_len = ctx_rust.cc_data.len().min(c_data.len()); - c_data[..rust_data_len].copy_from_slice(&ctx_rust.cc_data[..rust_data_len]); - } + if !(*dec_ctx.avc_ctx).cc_data.is_null() { + let c_data = slice::from_raw_parts_mut( + (*dec_ctx.avc_ctx).cc_data, + (*dec_ctx.avc_ctx).cc_databufsize as usize, + ); + let rust_data_len = ctx_rust.cc_data.len().min(c_data.len()); + c_data[..rust_data_len].copy_from_slice(&ctx_rust.cc_data[..rust_data_len]); + } - // Update the essential fields - (*dec_ctx.avc_ctx).cc_count = ctx_rust.cc_count; - (*dec_ctx.avc_ctx).cc_buffer_saved = if ctx_rust.cc_buffer_saved { 1 } else { 0 }; - (*dec_ctx.avc_ctx).ccblocks_in_avc_total = ctx_rust.ccblocks_in_avc_total; - (*dec_ctx.avc_ctx).ccblocks_in_avc_lost = ctx_rust.ccblocks_in_avc_lost; + // Update the essential fields + (*dec_ctx.avc_ctx).cc_count = ctx_rust.cc_count; + (*dec_ctx.avc_ctx).cc_buffer_saved = + if ctx_rust.cc_buffer_saved { 1 } else { 0 }; + (*dec_ctx.avc_ctx).ccblocks_in_avc_total = ctx_rust.ccblocks_in_avc_total; + (*dec_ctx.avc_ctx).ccblocks_in_avc_lost = ctx_rust.ccblocks_in_avc_lost; + } } - } - AvcNalType::PictureParameterSet if (*dec_ctx.avc_ctx).got_seq_para != 0 => { - // Found Picture parameter set - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "Found Picture parameter set"); - } + AvcNalType::PictureParameterSet if (*dec_ctx.avc_ctx).got_seq_para != 0 => { + // Found Picture parameter set + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "Found Picture parameter set"); + } - _ => { - // Handle other NAL unit types or do nothing + _ => { + // Handle other NAL unit types or do nothing + } } } debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; - "END NAL unit type: {:?} length {} ref_idc: {} - Buffered captions after: {}", - nal_unit_type, + "END NAL unit type: {} length {} ref_idc: {} - Buffered captions after: {}", + nal_unit_type_raw, working_buffer.len(), (*dec_ctx.avc_ctx).nal_ref_idc, if (*dec_ctx.avc_ctx).cc_buffer_saved != 0 { 0 } else { 1 } diff --git a/src/rust/src/common.rs b/src/rust/src/common.rs index 9aef909a3..e3d6e1b63 100755 --- a/src/rust/src/common.rs +++ b/src/rust/src/common.rs @@ -1136,6 +1136,7 @@ impl CType for AvcContextRust { cc_databufsize: self.cc_databufsize as _, cc_buffer_saved: if self.cc_buffer_saved { 1 } else { 0 }, + is_hevc: if self.is_hevc { 1 } else { 0 }, got_seq_para: if self.got_seq_para { 1 } else { 0 }, nal_ref_idc: self.nal_ref_idc, seq_parameter_set_id: self.seq_parameter_set_id, diff --git a/src/rust/src/ctorust.rs b/src/rust/src/ctorust.rs index 8c3c7c662..02bd94363 100755 --- a/src/rust/src/ctorust.rs +++ b/src/rust/src/ctorust.rs @@ -811,6 +811,7 @@ impl FromCType for AvcContextRust { cc_databufsize: ctx.cc_databufsize as usize, cc_buffer_saved: ctx.cc_buffer_saved != 0, + is_hevc: ctx.is_hevc != 0, got_seq_para: ctx.got_seq_para != 0, nal_ref_idc: ctx.nal_ref_idc, seq_parameter_set_id: ctx.seq_parameter_set_id,