Skip to content

Commit 54bc97a

Browse files
authored
fix(hevc): Add HEVC/H.265 caption extraction support with B-frame reordering
2 parents 3d7c534 + 1bdd9ab commit 54bc97a

File tree

10 files changed

+371
-117
lines changed

10 files changed

+371
-117
lines changed

src/lib_ccx/avc_functions.c

Lines changed: 80 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ struct avc_ctx *init_avc(void)
4848
ctx->cc_databufsize = 1024;
4949
ctx->cc_buffer_saved = CCX_TRUE; // Was the CC buffer saved after it was last updated?
5050

51+
ctx->is_hevc = 0;
5152
ctx->got_seq_para = 0;
5253
ctx->nal_ref_idc = 0;
5354
ctx->seq_parameter_set_id = 0;
@@ -87,62 +88,108 @@ struct avc_ctx *init_avc(void)
8788
return ctx;
8889
}
8990

91+
// HEVC NAL unit types for SEI messages
92+
#define HEVC_NAL_PREFIX_SEI 39
93+
#define HEVC_NAL_SUFFIX_SEI 40
94+
#define HEVC_NAL_VPS 32
95+
#define HEVC_NAL_SPS 33
96+
#define HEVC_NAL_PPS 34
97+
9098
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)
9199
{
92100
unsigned char *NAL_stop;
93-
enum ccx_avc_nal_types nal_unit_type = *NAL_start & 0x1F;
101+
int nal_unit_type;
102+
int nal_header_size;
103+
unsigned char *payload_start;
104+
105+
// Determine if this is HEVC or H.264 based on NAL header
106+
// H.264 NAL header: 1 byte, type in bits [4:0]
107+
// HEVC NAL header: 2 bytes, type in bits [6:1] of first byte
108+
if (dec_ctx->avc_ctx->is_hevc)
109+
{
110+
// HEVC: NAL type is in bits [6:1] of byte 0
111+
nal_unit_type = (NAL_start[0] >> 1) & 0x3F;
112+
nal_header_size = 2;
113+
}
114+
else
115+
{
116+
// H.264: NAL type is in bits [4:0] of byte 0
117+
nal_unit_type = NAL_start[0] & 0x1F;
118+
nal_header_size = 1;
119+
}
94120

95121
NAL_stop = NAL_length + NAL_start;
96-
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
122+
NAL_stop = remove_03emu(NAL_start + nal_header_size, NAL_stop);
123+
payload_start = NAL_start + nal_header_size;
97124

98-
dvprint("BEGIN NAL unit type: %d length %d ref_idc: %d - Buffered captions before: %d\n",
99-
nal_unit_type, NAL_stop - NAL_start - 1, dec_ctx->avc_ctx->nal_ref_idc, !dec_ctx->avc_ctx->cc_buffer_saved);
125+
dvprint("BEGIN NAL unit type: %d length %d ref_idc: %d - Buffered captions before: %d (HEVC: %d)\n",
126+
nal_unit_type, NAL_stop - NAL_start - nal_header_size, dec_ctx->avc_ctx->nal_ref_idc,
127+
!dec_ctx->avc_ctx->cc_buffer_saved, dec_ctx->avc_ctx->is_hevc);
100128

101129
if (NAL_stop == NULL) // remove_03emu failed.
102130
{
103131
mprint("\rNotice: NAL of type %u had to be skipped because remove_03emu failed.\n", nal_unit_type);
104132
return;
105133
}
106134

107-
if (nal_unit_type == CCX_NAL_TYPE_ACCESS_UNIT_DELIMITER_9)
108-
{
109-
// Found Access Unit Delimiter
110-
}
111-
else if (nal_unit_type == CCX_NAL_TYPE_SEQUENCE_PARAMETER_SET_7)
112-
{
113-
// Found sequence parameter set
114-
// We need this to parse NAL type 1 (CCX_NAL_TYPE_CODED_SLICE_NON_IDR_PICTURE_1)
115-
dec_ctx->avc_ctx->num_nal_unit_type_7++;
116-
seq_parameter_set_rbsp(dec_ctx->avc_ctx, NAL_start + 1, NAL_stop);
117-
dec_ctx->avc_ctx->got_seq_para = 1;
118-
}
119-
else if (dec_ctx->avc_ctx->got_seq_para && (nal_unit_type == CCX_NAL_TYPE_CODED_SLICE_NON_IDR_PICTURE_1 ||
120-
nal_unit_type == CCX_NAL_TYPE_CODED_SLICE_IDR_PICTURE)) // Only if nal_unit_type=1
135+
if (dec_ctx->avc_ctx->is_hevc)
121136
{
122-
// Found coded slice of a non-IDR picture
123-
// We only need the slice header data, no need to implement
124-
// slice_layer_without_partitioning_rbsp( );
125-
slice_header(enc_ctx, dec_ctx, NAL_start + 1, NAL_stop, nal_unit_type, sub);
126-
}
127-
else if (dec_ctx->avc_ctx->got_seq_para && nal_unit_type == CCX_NAL_TYPE_SEI)
128-
{
129-
// Found SEI (used for subtitles)
130-
// set_fts(ctx->timing); // FIXME - check this!!!
131-
sei_rbsp(dec_ctx->avc_ctx, NAL_start + 1, NAL_stop);
137+
// HEVC NAL unit processing
138+
if (nal_unit_type == HEVC_NAL_VPS || nal_unit_type == HEVC_NAL_SPS || nal_unit_type == HEVC_NAL_PPS)
139+
{
140+
// Found HEVC parameter set - mark as having sequence params
141+
// We don't parse HEVC SPS fully, but we need to enable SEI processing
142+
dec_ctx->avc_ctx->got_seq_para = 1;
143+
}
144+
else if (nal_unit_type == HEVC_NAL_PREFIX_SEI || nal_unit_type == HEVC_NAL_SUFFIX_SEI)
145+
{
146+
// Found HEVC SEI (used for subtitles)
147+
// SEI payload format is similar to H.264
148+
sei_rbsp(dec_ctx->avc_ctx, payload_start, NAL_stop);
149+
}
132150
}
133-
else if (dec_ctx->avc_ctx->got_seq_para && nal_unit_type == CCX_NAL_TYPE_PICTURE_PARAMETER_SET)
151+
else
134152
{
135-
// Found Picture parameter set
153+
// H.264 NAL unit processing (original code)
154+
if (nal_unit_type == CCX_NAL_TYPE_ACCESS_UNIT_DELIMITER_9)
155+
{
156+
// Found Access Unit Delimiter
157+
}
158+
else if (nal_unit_type == CCX_NAL_TYPE_SEQUENCE_PARAMETER_SET_7)
159+
{
160+
// Found sequence parameter set
161+
// We need this to parse NAL type 1 (CCX_NAL_TYPE_CODED_SLICE_NON_IDR_PICTURE_1)
162+
dec_ctx->avc_ctx->num_nal_unit_type_7++;
163+
seq_parameter_set_rbsp(dec_ctx->avc_ctx, payload_start, NAL_stop);
164+
dec_ctx->avc_ctx->got_seq_para = 1;
165+
}
166+
else if (dec_ctx->avc_ctx->got_seq_para && (nal_unit_type == CCX_NAL_TYPE_CODED_SLICE_NON_IDR_PICTURE_1 ||
167+
nal_unit_type == CCX_NAL_TYPE_CODED_SLICE_IDR_PICTURE))
168+
{
169+
// Found coded slice of a non-IDR picture
170+
// We only need the slice header data
171+
slice_header(enc_ctx, dec_ctx, payload_start, NAL_stop, nal_unit_type, sub);
172+
}
173+
else if (dec_ctx->avc_ctx->got_seq_para && nal_unit_type == CCX_NAL_TYPE_SEI)
174+
{
175+
// Found SEI (used for subtitles)
176+
sei_rbsp(dec_ctx->avc_ctx, payload_start, NAL_stop);
177+
}
178+
else if (dec_ctx->avc_ctx->got_seq_para && nal_unit_type == CCX_NAL_TYPE_PICTURE_PARAMETER_SET)
179+
{
180+
// Found Picture parameter set
181+
}
136182
}
183+
137184
if (temp_debug)
138185
{
139-
int len = NAL_stop - (NAL_start + 1);
186+
int len = NAL_stop - payload_start;
140187
dbg_print(CCX_DMT_VIDES, "\n After decoding, the actual thing was (length =%d)\n", len);
141-
dump(CCX_DMT_VIDES, NAL_start + 1, len > 160 ? 160 : len, 0, 0);
188+
dump(CCX_DMT_VIDES, payload_start, len > 160 ? 160 : len, 0, 0);
142189
}
143190

144191
dvprint("END NAL unit type: %d length %d ref_idc: %d - Buffered captions after: %d\n",
145-
nal_unit_type, NAL_stop - NAL_start - 1, dec_ctx->avc_ctx->nal_ref_idc, !dec_ctx->avc_ctx->cc_buffer_saved);
192+
nal_unit_type, NAL_stop - NAL_start - nal_header_size, dec_ctx->avc_ctx->nal_ref_idc, !dec_ctx->avc_ctx->cc_buffer_saved);
146193
}
147194

148195
// Process inbuf bytes in buffer holding and AVC (H.264) video stream.

src/lib_ccx/avc_functions.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ struct avc_ctx
99
long cc_databufsize;
1010
int cc_buffer_saved; // Was the CC buffer saved after it was last updated?
1111

12+
int is_hevc; // Flag to indicate HEVC (H.265) mode vs H.264
1213
int got_seq_para;
1314
unsigned nal_ref_idc;
1415
LLONG seq_parameter_set_id;

src/lib_ccx/ccx_common_constants.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,8 @@ enum ccx_bufferdata_type
237237
CCX_ISDB_SUBTITLE = 8,
238238
/* BUffer where cc data contain 3 byte cc_valid ccdata 1 ccdata 2 */
239239
CCX_RAW_TYPE = 9,
240-
CCX_DVD_SUBTITLE = 10
240+
CCX_DVD_SUBTITLE = 10,
241+
CCX_HEVC = 11
241242
};
242243

243244
enum ccx_frame_type

src/lib_ccx/general_loop.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,13 @@ int process_data(struct encoder_ctx *enc_ctx, struct lib_cc_decode *dec_ctx, str
779779
else if (data_node->bufferdatatype == CCX_H264) // H.264 data from TS file
780780
{
781781
dec_ctx->in_bufferdatatype = CCX_H264;
782+
dec_ctx->avc_ctx->is_hevc = 0;
783+
got = process_avc(enc_ctx, dec_ctx, data_node->buffer, data_node->len, dec_sub);
784+
}
785+
else if (data_node->bufferdatatype == CCX_HEVC) // HEVC data from TS file
786+
{
787+
dec_ctx->in_bufferdatatype = CCX_H264; // Use same internal type for NAL processing
788+
dec_ctx->avc_ctx->is_hevc = 1;
782789
got = process_avc(enc_ctx, dec_ctx, data_node->buffer, data_node->len, dec_sub);
783790
}
784791
else if (data_node->bufferdatatype == CCX_RAW_TYPE)

src/lib_ccx/ts_functions.c

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ enum ccx_bufferdata_type get_buffer_type(struct cap_info *cinfo)
135135
}
136136
else if (cinfo->stream == CCX_STREAM_TYPE_VIDEO_HEVC)
137137
{
138-
return CCX_H264; // HEVC uses same buffer type as H264
138+
return CCX_HEVC;
139139
}
140140
else if (cinfo->stream == CCX_STREAM_TYPE_PRIVATE_MPEG2 && cinfo->codec == CCX_CODEC_DVB)
141141
{
@@ -390,15 +390,26 @@ void look_for_caption_data(struct ccx_demuxer *ctx, struct ts_payload *payload)
390390
// Check for H.264/H.265 NAL start codes
391391
if (es_data[0] == 0x00 && es_data[1] == 0x00 && es_data[2] == 0x00 && es_data[3] == 0x01)
392392
{
393-
// Could be H.264 or H.265 - check NAL type
394-
unsigned char nal_type = es_data[4] & 0x1F;
395-
if (nal_type == 7 || nal_type == 8) // SPS or PPS
393+
// Check for H.264 NAL types first (1-byte header, type in bits 4:0)
394+
unsigned char h264_nal_type = es_data[4] & 0x1F;
395+
if (h264_nal_type == 7 || h264_nal_type == 8) // H.264 SPS or PPS
396396
stream_type = CCX_STREAM_TYPE_VIDEO_H264;
397+
else
398+
{
399+
// Check for HEVC NAL types (2-byte header, type in bits 6:1 of first byte)
400+
unsigned char hevc_nal_type = (es_data[4] >> 1) & 0x3F;
401+
// HEVC VPS=32, SPS=33, PPS=34, PREFIX_SEI=39, SUFFIX_SEI=40
402+
// Also check for IDR (19, 20) and CRA (21) which are common first NALs
403+
if (hevc_nal_type == 32 || hevc_nal_type == 33 || hevc_nal_type == 34 ||
404+
hevc_nal_type == 39 || hevc_nal_type == 40 ||
405+
hevc_nal_type == 19 || hevc_nal_type == 20 || hevc_nal_type == 21)
406+
stream_type = CCX_STREAM_TYPE_VIDEO_HEVC;
407+
}
397408
}
398409

399410
mprint("PID %u detected as video stream (no PAT/PMT) - assuming %s.\n",
400411
payload->pid,
401-
stream_type == CCX_STREAM_TYPE_VIDEO_H264 ? "H.264" : "MPEG-2");
412+
stream_type == CCX_STREAM_TYPE_VIDEO_H264 ? "H.264" : (stream_type == CCX_STREAM_TYPE_VIDEO_HEVC ? "HEVC" : "MPEG-2"));
402413

403414
// Register this PID as a video stream that may contain captions
404415
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)
661672
int newcapbuflen;
662673

663674
if (cinfo->ignore == CCX_TRUE &&
664-
(cinfo->stream != CCX_STREAM_TYPE_VIDEO_MPEG2 || !ccx_options.analyze_video_stream))
675+
((cinfo->stream != CCX_STREAM_TYPE_VIDEO_MPEG2 &&
676+
cinfo->stream != CCX_STREAM_TYPE_VIDEO_H264 &&
677+
cinfo->stream != CCX_STREAM_TYPE_VIDEO_HEVC) ||
678+
!ccx_options.analyze_video_stream))
665679
{
666680
return CCX_OK;
667681
}
@@ -948,7 +962,10 @@ long ts_readstream(struct ccx_demuxer *ctx, struct demuxer_data **data)
948962
continue;
949963
}
950964
else if (cinfo->ignore == CCX_TRUE &&
951-
(cinfo->stream != CCX_STREAM_TYPE_VIDEO_MPEG2 || !ccx_options.analyze_video_stream))
965+
((cinfo->stream != CCX_STREAM_TYPE_VIDEO_MPEG2 &&
966+
cinfo->stream != CCX_STREAM_TYPE_VIDEO_H264 &&
967+
cinfo->stream != CCX_STREAM_TYPE_VIDEO_HEVC) ||
968+
!ccx_options.analyze_video_stream))
952969
{
953970
if (cinfo->codec_private_data)
954971
{

src/lib_ccx/ts_info.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,9 @@ int get_video_stream(struct ccx_demuxer *ctx)
102102
struct cap_info *iter;
103103
list_for_each_entry(iter, &ctx->cinfo_tree.all_stream, all_stream, struct cap_info)
104104
{
105-
if (iter->stream == CCX_STREAM_TYPE_VIDEO_MPEG2)
105+
if (iter->stream == CCX_STREAM_TYPE_VIDEO_MPEG2 ||
106+
iter->stream == CCX_STREAM_TYPE_VIDEO_H264 ||
107+
iter->stream == CCX_STREAM_TYPE_VIDEO_HEVC)
106108
return iter->pid;
107109
}
108110
return -1;

src/rust/src/avc/common_types.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub struct AvcContextRust {
99
pub cc_databufsize: usize,
1010
pub cc_buffer_saved: bool,
1111

12+
pub is_hevc: bool,
1213
pub got_seq_para: bool,
1314
pub nal_ref_idc: u32,
1415
pub seq_parameter_set_id: i64,
@@ -55,6 +56,7 @@ impl Default for AvcContextRust {
5556
cc_databufsize: 1024,
5657
cc_buffer_saved: true,
5758

59+
is_hevc: false,
5860
got_seq_para: false,
5961
nal_ref_idc: 0,
6062
seq_parameter_set_id: 0,

0 commit comments

Comments
 (0)