【x264视频编码器应用与实现】九. x264编码一帧图像API:x264_encoder_encode(二)


摘要:

本文作为 “x264视频编码器应用与实现” 系列博文的第九篇,继续讨论x264编码器实例的图像编码API的实现。

x264编码一帧图像API:x264_encoder_encode(二)


首先给出一章函数调用关系图:

一、获取待编码的图像帧

在开始编码图像帧之前,首先从图像帧列表中取出保存的图像数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
int     x264_encoder_encode( x264_t *h,
x264_nal_t **pp_nal, int *pi_nal,
x264_picture_t *pic_in,
x264_picture_t *pic_out )
{
// ......
/* ------------------- Get frame to be encoded ------------------------- */
/* 4: get picture to encode */
h->fenc = x264_frame_shift( h->frames.current );//从h->frames.current获取最前面的一帧;

/* If applicable, wait for previous frame reconstruction to finish */
if( h->param.b_sliced_threads )
if( threadpool_wait_all( h ) < 0 )
return -1;

if( h->i_frame == 0 )
h->i_reordered_pts_delay = h->fenc->i_reordered_pts;
if( h->reconfig )
{
x264_encoder_reconfig_apply( h, &h->reconfig_h->param );
h->reconfig = 0;
}
if( h->fenc->param )
{
x264_encoder_reconfig_apply( h, h->fenc->param );
if( h->fenc->param->param_free )
{
h->fenc->param->param_free( h->fenc->param );
h->fenc->param = NULL;
}
}
x264_ratecontrol_zone_init( h );// 初始化码率控制相关信息

// ok to call this before encoding any frames, since the initial values of fdec have b_kept_as_ref=0
if( reference_update( h ) ) // 更新参考帧列表
return -1;
h->fdec->i_lines_completed = -1;

if( !IS_X264_TYPE_I( h->fenc->i_type ) )
{
// 统计缓存中有效的参考帧数量
int valid_refs_left = 0;
for( int i = 0; h->frames.reference[i]; i++ )
if( !h->frames.reference[i]->b_corrupt )
valid_refs_left++;
/* No valid reference frames left: force an IDR. */
// 如果当前缓存中没有有效的参考帧,则当前帧强制设为关键帧编码
if( !valid_refs_left )
{
h->fenc->b_keyframe = 1;
h->fenc->i_type = X264_TYPE_IDR;
}
}

// 设定关键帧的部分参数
if( h->fenc->b_keyframe )
{
h->frames.i_last_keyframe = h->fenc->i_frame;
if( h->fenc->i_type == X264_TYPE_IDR )
{
h->i_frame_num = 0; // 每遇到一个IDR,frame_num 归0
h->frames.i_last_idr = h->fenc->i_frame;
}
}
h->sh.i_mmco_command_count =
h->sh.i_mmco_remove_from_end = 0;
h->b_ref_reorder[0] =
h->b_ref_reorder[1] = 0;
h->fdec->i_poc =
h->fenc->i_poc = 2 * ( h->fenc->i_frame - X264_MAX( h->frames.i_last_idr, 0 ) );
// ......
}

二、设置编码帧的上下文信息

在编码之前,设置该帧的上下文信息根据帧类型的不同而不同,具体实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
int     x264_encoder_encode( x264_t *h,
x264_nal_t **pp_nal, int *pi_nal,
x264_picture_t *pic_in,
x264_picture_t *pic_out )
{
// ......
/* ------------------- Setup frame context ----------------------------- */
/* 5: Init data dependent of frame type */
if( h->fenc->i_type == X264_TYPE_IDR )
{
/* reset ref pictures */
i_nal_type = NAL_SLICE_IDR; // 设置 nal 类型为 IDR
i_nal_ref_idc = NAL_PRIORITY_HIGHEST; // 设置 ref_idc,IDR帧最重要所以优先级最高
h->sh.i_type = SLICE_TYPE_I; // 设置 slice_type,IDR帧一定为I slice
reference_reset( h );
h->frames.i_poc_last_open_gop = -1;
}
else if( h->fenc->i_type == X264_TYPE_I )
{
i_nal_type = NAL_SLICE; // 设置 nal 类型为 非IDR
// 设置 ref_idc,I帧可能作为参考帧因此优先级设为高
i_nal_ref_idc = NAL_PRIORITY_HIGH; /* Not completely true but for now it is (as all I/P are kept as ref)*/
h->sh.i_type = SLICE_TYPE_I;
reference_hierarchy_reset( h );
if( h->param.b_open_gop )
h->frames.i_poc_last_open_gop = h->fenc->b_keyframe ? h->fenc->i_poc : -1;
}
else if( h->fenc->i_type == X264_TYPE_P )
{
i_nal_type = NAL_SLICE; // 设置 nal 类型为 非IDR
// 设置 ref_idc,P帧可能作为参考帧因此优先级设为高
i_nal_ref_idc = NAL_PRIORITY_HIGH; /* Not completely true but for now it is (as all I/P are kept as ref)*/
h->sh.i_type = SLICE_TYPE_P;
reference_hierarchy_reset( h );
h->frames.i_poc_last_open_gop = -1;
}
else if( h->fenc->i_type == X264_TYPE_BREF )
{
i_nal_type = NAL_SLICE; // 设置 nal 类型为 非IDR
// 设置 ref_idc,当B帧作为参考帧开启时优先级设为高,否则设为低;
i_nal_ref_idc = h->param.i_bframe_pyramid == X264_B_PYRAMID_STRICT ? NAL_PRIORITY_LOW : NAL_PRIORITY_HIGH;
h->sh.i_type = SLICE_TYPE_B;
reference_hierarchy_reset( h );
}
else /* B frame */
{
i_nal_type = NAL_SLICE; // 设置 nal 类型为 非IDR
i_nal_ref_idc = NAL_PRIORITY_DISPOSABLE; // 设置 ref_idc,普通B帧可丢弃
h->sh.i_type = SLICE_TYPE_B;
}

h->fdec->i_type = h->fenc->i_type;
h->fdec->i_frame = h->fenc->i_frame;
h->fenc->b_kept_as_ref =
h->fdec->b_kept_as_ref = i_nal_ref_idc != NAL_PRIORITY_DISPOSABLE && h->param.i_keyint_max > 1;

h->fdec->mb_info = h->fenc->mb_info;
h->fdec->mb_info_free = h->fenc->mb_info_free;
h->fenc->mb_info = NULL;
h->fenc->mb_info_free = NULL;

h->fdec->i_pts = h->fenc->i_pts;
if( h->frames.i_bframe_delay )
{
int64_t *prev_reordered_pts = thread_current->frames.i_prev_reordered_pts;
h->fdec->i_dts = h->i_frame > h->frames.i_bframe_delay
? prev_reordered_pts[ (h->i_frame - h->frames.i_bframe_delay) % h->frames.i_bframe_delay ]
: h->fenc->i_reordered_pts - h->frames.i_bframe_delay_time;
prev_reordered_pts[ h->i_frame % h->frames.i_bframe_delay ] = h->fenc->i_reordered_pts;
}
else
h->fdec->i_dts = h->fenc->i_reordered_pts;
if( h->fenc->i_type == X264_TYPE_IDR )
h->i_last_idr_pts = h->fdec->i_pts;
// ......
}

从上述代码中可知,针对IDR、I、P和BREF帧,都调用了以下两个函数之一:

  • reference_reset;
  • reference_hierarchy_reset;

这两个函数的作用是重置参考帧列表,其实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
static inline void reference_reset( x264_t *h )
{
// 清理h->frames.reference中的所有参考帧
while( h->frames.reference[0] )
x264_frame_push_unused( h, x264_frame_pop( h->frames.reference ) );
h->fdec->i_poc =
h->fenc->i_poc = 0; // 重置当前帧的poc值;
}

static inline void reference_hierarchy_reset( x264_t *h )
{
int ref;
int b_hasdelayframe = 0;

/* look for delay frames -- chain must only contain frames that are disposable */
for( int i = 0; h->frames.current[i] && IS_DISPOSABLE( h->frames.current[i]->i_type ); i++ )
b_hasdelayframe |= h->frames.current[i]->i_coded
!= h->frames.current[i]->i_frame + h->sps->vui.i_num_reorder_frames;

/* This function must handle b-pyramid and clear frames for open-gop */
if( h->param.i_bframe_pyramid != X264_B_PYRAMID_STRICT && !b_hasdelayframe && h->frames.i_poc_last_open_gop == -1 )
return;

/* Remove last BREF. There will never be old BREFs in the
* dpb during a BREF decode when pyramid == STRICT */
for( ref = 0; h->frames.reference[ref]; ref++ )
{
if( ( h->param.i_bframe_pyramid == X264_B_PYRAMID_STRICT
&& h->frames.reference[ref]->i_type == X264_TYPE_BREF )
|| ( h->frames.reference[ref]->i_poc < h->frames.i_poc_last_open_gop
&& h->sh.i_type != SLICE_TYPE_B ) )
{
int diff = h->i_frame_num - h->frames.reference[ref]->i_frame_num;
h->sh.mmco[h->sh.i_mmco_command_count].i_difference_of_pic_nums = diff;
h->sh.mmco[h->sh.i_mmco_command_count++].i_poc = h->frames.reference[ref]->i_poc;
x264_frame_push_unused( h, x264_frame_shift( &h->frames.reference[ref] ) );
h->b_ref_reorder[0] = 1;
ref--;
}
}

/* Prepare room in the dpb for the delayed display time of the later b-frame's */
if( h->param.i_bframe_pyramid )
h->sh.i_mmco_remove_from_end = X264_MAX( ref + 2 - h->frames.i_max_dpb, 0 );
}

三、参考帧列表初始化

x264_encoder_encode调用了reference_build_list来构建参考帧列表:

1
2
3
4
5
6
7
8
9
10
11
int     x264_encoder_encode( x264_t *h,
x264_nal_t **pp_nal, int *pi_nal,
x264_picture_t *pic_in,
x264_picture_t *pic_out )
{
// ......
/* ------------------- Init ----------------------------- */
/* build ref list 0/1 */
reference_build_list( h, h->fdec->i_poc );
// ......
}

reference_build_list需要输入两个参数,其中之一为x264的句柄h,另一个为当前帧的poc。其实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
static inline void reference_build_list( x264_t *h, int i_poc )
{
int b_ok;

/* build ref list 0/1 */
// 初始化参考帧索引值
h->mb.pic.i_fref[0] = h->i_ref[0] = 0;
h->mb.pic.i_fref[1] = h->i_ref[1] = 0;
if( h->sh.i_type == SLICE_TYPE_I )
return;

// 从h->frames.reference选出合适的帧放入两个参考帧列表h->fref[0]和h->fref[1]中,以i_poc为界
for( int i = 0; h->frames.reference[i]; i++ )
{
if( h->frames.reference[i]->b_corrupt )
continue;
if( h->frames.reference[i]->i_poc < i_poc )
h->fref[0][h->i_ref[0]++] = h->frames.reference[i];
else if( h->frames.reference[i]->i_poc > i_poc )
h->fref[1][h->i_ref[1]++] = h->frames.reference[i];
}

if( h->sh.i_mmco_remove_from_end )
{
/* Order ref0 for MMCO remove */
do
{
b_ok = 1;
for( int i = 0; i < h->i_ref[0] - 1; i++ )
{
if( h->fref[0][i]->i_frame < h->fref[0][i+1]->i_frame )
{
XCHG( x264_frame_t*, h->fref[0][i], h->fref[0][i+1] );
b_ok = 0;
break;
}
}
} while( !b_ok );

for( int i = h->i_ref[0]-1; i >= h->i_ref[0] - h->sh.i_mmco_remove_from_end; i-- )
{
int diff = h->i_frame_num - h->fref[0][i]->i_frame_num;
h->sh.mmco[h->sh.i_mmco_command_count].i_poc = h->fref[0][i]->i_poc;
h->sh.mmco[h->sh.i_mmco_command_count++].i_difference_of_pic_nums = diff;
}
}

/* Order reference lists by distance from the current frame. */
// 对参考帧列表中的帧按照与当前帧的距离排序
for( int list = 0; list < 2; list++ )
{
h->fref_nearest[list] = h->fref[list][0];
do
{
b_ok = 1;
for( int i = 0; i < h->i_ref[list] - 1; i++ )
{
if( list ? h->fref[list][i+1]->i_poc < h->fref_nearest[list]->i_poc
: h->fref[list][i+1]->i_poc > h->fref_nearest[list]->i_poc )
h->fref_nearest[list] = h->fref[list][i+1];
if( reference_distance( h, h->fref[list][i] ) > reference_distance( h, h->fref[list][i+1] ) )
{
XCHG( x264_frame_t*, h->fref[list][i], h->fref[list][i+1] );
b_ok = 0;
break;
}
}
} while( !b_ok );
}

// 检查参考帧列表中poc和frame_num差值,判断是否需要对参考帧列表重拍
reference_check_reorder( h );

h->i_ref[1] = X264_MIN( h->i_ref[1], h->frames.i_max_ref1 );
h->i_ref[0] = X264_MIN( h->i_ref[0], h->frames.i_max_ref0 );
h->i_ref[0] = X264_MIN( h->i_ref[0], h->param.i_frame_reference ); // if reconfig() has lowered the limit

/* For Blu-ray compliance, don't reference frames outside of the minigop. */
if( IS_X264_TYPE_B( h->fenc->i_type ) && h->param.b_bluray_compat )
h->i_ref[0] = X264_MIN( h->i_ref[0], IS_X264_TYPE_B( h->fref[0][0]->i_type ) + 1 );

/* add duplicates */
if( h->fenc->i_type == X264_TYPE_P )
{
int idx = -1;
if( h->param.analyse.i_weighted_pred >= X264_WEIGHTP_SIMPLE ) // 加权预测相关
{
x264_weight_t w[3];
w[1].weightfn = w[2].weightfn = NULL;
if( h->param.rc.b_stat_read )
x264_ratecontrol_set_weights( h, h->fenc );

if( !h->fenc->weight[0][0].weightfn )
{
h->fenc->weight[0][0].i_denom = 0;
SET_WEIGHT( w[0], 1, 1, 0, -1 );
idx = weighted_reference_duplicate( h, 0, w );
}
else
{
if( h->fenc->weight[0][0].i_scale == 1<<h->fenc->weight[0][0].i_denom )
{
SET_WEIGHT( h->fenc->weight[0][0], 1, 1, 0, h->fenc->weight[0][0].i_offset );
}
weighted_reference_duplicate( h, 0, x264_weight_none );
if( h->fenc->weight[0][0].i_offset > -128 )
{
w[0] = h->fenc->weight[0][0];
w[0].i_offset--;
h->mc.weight_cache( h, &w[0] );
idx = weighted_reference_duplicate( h, 0, w );
}
}
}
h->mb.ref_blind_dupe = idx;
}

assert( h->i_ref[0] + h->i_ref[1] <= X264_REF_MAX );
h->mb.pic.i_fref[0] = h->i_ref[0];
h->mb.pic.i_fref[1] = h->i_ref[1];
}

四、写出码流

4.1 初始化

写出到码流部分是encoder_encode函数中的重要部分。首先是初始化部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* ---------------------- Write the bitstream -------------------------- */
/* Init bitstream context */
if( h->param.b_sliced_threads )
{
for( int i = 0; i < h->param.i_threads; i++ )
{
bs_init( &h->thread[i]->out.bs, h->thread[i]->out.p_bitstream, h->thread[i]->out.i_bitstream );
h->thread[i]->out.i_nal = 0;
}
}
else
{
bs_init( &h->out.bs, h->out.p_bitstream, h->out.i_bitstream );
h->out.i_nal = 0;
}

其中所用到的函数bs_init,其输入的参数类型为 bs_t,为 struct out的一部分。struct out 作为 x264_t 结构的一部分,实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct x264_t
{
// ......

/* bitstream output */
struct
{
int i_nal;
int i_nals_allocated;
x264_nal_t *nal;
int i_bitstream; /* size of p_bitstream */
uint8_t *p_bitstream; /* will hold data for all nal */
bs_t bs;
} out;

// ......
}

bs_t的定义如下:

1
2
3
4
5
6
7
8
9
10
typedef struct bs_s
{
uint8_t *p_start;
uint8_t *p;
uint8_t *p_end;

uintptr_t cur_bits;
int i_left; /* i_count number of available bits */
int i_bits_encoded; /* RD only */
} bs_t;

bs_init利用了上述两个结构类型,其实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static inline void bs_init( bs_t *s, void *p_data, int i_data )
{
int offset = ((intptr_t)p_data & 3);
s->p = s->p_start = (uint8_t*)p_data - offset;
s->p_end = (uint8_t*)p_data + i_data;
s->i_left = (WORD_SIZE - offset)*8;// 64位机上 WORD_SIZE == 8,因此码流区长度为 64 bit
if( offset )
{
s->cur_bits = endian_fix32( M32(s->p) );
s->cur_bits >>= (4-offset)*8;
}
else
s->cur_bits = 0;
}

该函数的作用是利用 out 中的 p_bitstream 和 i_bitstream 的数据对码流结构进行初始化。

4.2 设置SPS/PPS

当参数 param.b_repeat_headers 设置为 true 时,每一个关键帧前都将插入一组SPS和PPS。实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
if( h->fenc->b_keyframe )
{
/* Write SPS and PPS */
if( h->param.b_repeat_headers )
{
/* generate sequence parameters */
nal_start( h, NAL_SPS, NAL_PRIORITY_HIGHEST );// 将 nal_type 和 nal_ref_idx 写入到 h->out 中对应的nal结构中
x264_sps_write( &h->out.bs, h->sps );
if( nal_end( h ) )
return -1;
/* Pad AUD/SPS to 256 bytes like Panasonic */
if( h->param.i_avcintra_class )
h->out.nal[h->out.i_nal-1].i_padding = 256 - bs_pos( &h->out.bs ) / 8 - 2*NALU_OVERHEAD;
overhead += h->out.nal[h->out.i_nal-1].i_payload + h->out.nal[h->out.i_nal-1].i_padding + NALU_OVERHEAD;

/* generate picture parameters */
nal_start( h, NAL_PPS, NAL_PRIORITY_HIGHEST );
x264_pps_write( &h->out.bs, h->sps, h->pps );
if( nal_end( h ) )
return -1;
if( h->param.i_avcintra_class )
{
int total_len = 256;
/* Sony XAVC uses an oversized PPS instead of SEI padding */
if( h->param.i_avcintra_flavor == X264_AVCINTRA_FLAVOR_SONY )
total_len += h->param.i_height == 1080 ? 18*512 : 10*512;
h->out.nal[h->out.i_nal-1].i_padding = total_len - h->out.nal[h->out.i_nal-1].i_payload - NALU_OVERHEAD;
}
overhead += h->out.nal[h->out.i_nal-1].i_payload + h->out.nal[h->out.i_nal-1].i_padding + NALU_OVERHEAD;
}

/* when frame threading is used, buffering period sei is written in encoder_frame_end */
if( h->i_thread_frames == 1 && h->sps->vui.b_nal_hrd_parameters_present )
{
x264_hrd_fullness( h );
nal_start( h, NAL_SEI, NAL_PRIORITY_DISPOSABLE );
x264_sei_buffering_period_write( h, &h->out.bs );
if( nal_end( h ) )
return -1;
overhead += h->out.nal[h->out.i_nal-1].i_payload + SEI_OVERHEAD;
}
}

其中,调用x264_sps_write实现将sps结构写入码流中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
void x264_sps_write( bs_t *s, x264_sps_t *sps )
{
bs_realign( s );
bs_write( s, 8, sps->i_profile_idc );
bs_write1( s, sps->b_constraint_set0 );
bs_write1( s, sps->b_constraint_set1 );
bs_write1( s, sps->b_constraint_set2 );
bs_write1( s, sps->b_constraint_set3 );
bs_write1( s, sps->b_constraint_set4 );
bs_write1( s, sps->b_constraint_set5 );

bs_write( s, 2, 0 ); /* reserved */

bs_write( s, 8, sps->i_level_idc );

bs_write_ue( s, sps->i_id );

if( sps->i_profile_idc >= PROFILE_HIGH )
{
bs_write_ue( s, sps->i_chroma_format_idc );
if( sps->i_chroma_format_idc == CHROMA_444 )
bs_write1( s, 0 ); // separate_colour_plane_flag
bs_write_ue( s, BIT_DEPTH-8 ); // bit_depth_luma_minus8
bs_write_ue( s, BIT_DEPTH-8 ); // bit_depth_chroma_minus8
bs_write1( s, sps->b_qpprime_y_zero_transform_bypass );
/* Exactly match the AVC-Intra bitstream */
bs_write1( s, sps->b_avcintra ); // seq_scaling_matrix_present_flag
if( sps->b_avcintra )
{
scaling_list_write( s, sps, CQM_4IY );
scaling_list_write( s, sps, CQM_4IC );
scaling_list_write( s, sps, CQM_4IC );
bs_write1( s, 0 ); // no inter
bs_write1( s, 0 ); // no inter
bs_write1( s, 0 ); // no inter
scaling_list_write( s, sps, CQM_8IY+4 );
bs_write1( s, 0 ); // no inter
if( sps->i_chroma_format_idc == CHROMA_444 )
{
scaling_list_write( s, sps, CQM_8IC+4 );
bs_write1( s, 0 ); // no inter
scaling_list_write( s, sps, CQM_8IC+4 );
bs_write1( s, 0 ); // no inter
}
}
}

bs_write_ue( s, sps->i_log2_max_frame_num - 4 );
bs_write_ue( s, sps->i_poc_type );
if( sps->i_poc_type == 0 )
bs_write_ue( s, sps->i_log2_max_poc_lsb - 4 );
bs_write_ue( s, sps->i_num_ref_frames );
bs_write1( s, sps->b_gaps_in_frame_num_value_allowed );
bs_write_ue( s, sps->i_mb_width - 1 );
bs_write_ue( s, (sps->i_mb_height >> !sps->b_frame_mbs_only) - 1);
bs_write1( s, sps->b_frame_mbs_only );
if( !sps->b_frame_mbs_only )
bs_write1( s, sps->b_mb_adaptive_frame_field );
bs_write1( s, sps->b_direct8x8_inference );

bs_write1( s, sps->b_crop );
if( sps->b_crop )
{
int h_shift = sps->i_chroma_format_idc == CHROMA_420 || sps->i_chroma_format_idc == CHROMA_422;
int v_shift = (sps->i_chroma_format_idc == CHROMA_420) + !sps->b_frame_mbs_only;
bs_write_ue( s, sps->crop.i_left >> h_shift );
bs_write_ue( s, sps->crop.i_right >> h_shift );
bs_write_ue( s, sps->crop.i_top >> v_shift );
bs_write_ue( s, sps->crop.i_bottom >> v_shift );
}

bs_write1( s, sps->b_vui );
if( sps->b_vui )
{
bs_write1( s, sps->vui.b_aspect_ratio_info_present );
if( sps->vui.b_aspect_ratio_info_present )
{
int i;
static const struct { uint8_t w, h, sar; } sar[] =
{
// aspect_ratio_idc = 0 -> unspecified
{ 1, 1, 1 }, { 12, 11, 2 }, { 10, 11, 3 }, { 16, 11, 4 },
{ 40, 33, 5 }, { 24, 11, 6 }, { 20, 11, 7 }, { 32, 11, 8 },
{ 80, 33, 9 }, { 18, 11, 10}, { 15, 11, 11}, { 64, 33, 12},
{160, 99, 13}, { 4, 3, 14}, { 3, 2, 15}, { 2, 1, 16},
// aspect_ratio_idc = [17..254] -> reserved
{ 0, 0, 255 }
};
for( i = 0; sar[i].sar != 255; i++ )
{
if( sar[i].w == sps->vui.i_sar_width &&
sar[i].h == sps->vui.i_sar_height )
break;
}
bs_write( s, 8, sar[i].sar );
if( sar[i].sar == 255 ) /* aspect_ratio_idc (extended) */
{
bs_write( s, 16, sps->vui.i_sar_width );
bs_write( s, 16, sps->vui.i_sar_height );
}
}

bs_write1( s, sps->vui.b_overscan_info_present );
if( sps->vui.b_overscan_info_present )
bs_write1( s, sps->vui.b_overscan_info );

bs_write1( s, sps->vui.b_signal_type_present );
if( sps->vui.b_signal_type_present )
{
bs_write( s, 3, sps->vui.i_vidformat );
bs_write1( s, sps->vui.b_fullrange );
bs_write1( s, sps->vui.b_color_description_present );
if( sps->vui.b_color_description_present )
{
bs_write( s, 8, sps->vui.i_colorprim );
bs_write( s, 8, sps->vui.i_transfer );
bs_write( s, 8, sps->vui.i_colmatrix );
}
}

bs_write1( s, sps->vui.b_chroma_loc_info_present );
if( sps->vui.b_chroma_loc_info_present )
{
bs_write_ue( s, sps->vui.i_chroma_loc_top );
bs_write_ue( s, sps->vui.i_chroma_loc_bottom );
}

bs_write1( s, sps->vui.b_timing_info_present );
if( sps->vui.b_timing_info_present )
{
bs_write32( s, sps->vui.i_num_units_in_tick );
bs_write32( s, sps->vui.i_time_scale );
bs_write1( s, sps->vui.b_fixed_frame_rate );
}

bs_write1( s, sps->vui.b_nal_hrd_parameters_present );
if( sps->vui.b_nal_hrd_parameters_present )
{
bs_write_ue( s, sps->vui.hrd.i_cpb_cnt - 1 );
bs_write( s, 4, sps->vui.hrd.i_bit_rate_scale );
bs_write( s, 4, sps->vui.hrd.i_cpb_size_scale );

bs_write_ue( s, sps->vui.hrd.i_bit_rate_value - 1 );
bs_write_ue( s, sps->vui.hrd.i_cpb_size_value - 1 );

bs_write1( s, sps->vui.hrd.b_cbr_hrd );

bs_write( s, 5, sps->vui.hrd.i_initial_cpb_removal_delay_length - 1 );
bs_write( s, 5, sps->vui.hrd.i_cpb_removal_delay_length - 1 );
bs_write( s, 5, sps->vui.hrd.i_dpb_output_delay_length - 1 );
bs_write( s, 5, sps->vui.hrd.i_time_offset_length );
}

bs_write1( s, sps->vui.b_vcl_hrd_parameters_present );

if( sps->vui.b_nal_hrd_parameters_present || sps->vui.b_vcl_hrd_parameters_present )
bs_write1( s, 0 ); /* low_delay_hrd_flag */

bs_write1( s, sps->vui.b_pic_struct_present );
bs_write1( s, sps->vui.b_bitstream_restriction );
if( sps->vui.b_bitstream_restriction )
{
bs_write1( s, sps->vui.b_motion_vectors_over_pic_boundaries );
bs_write_ue( s, sps->vui.i_max_bytes_per_pic_denom );
bs_write_ue( s, sps->vui.i_max_bits_per_mb_denom );
bs_write_ue( s, sps->vui.i_log2_max_mv_length_horizontal );
bs_write_ue( s, sps->vui.i_log2_max_mv_length_vertical );
bs_write_ue( s, sps->vui.i_num_reorder_frames );
bs_write_ue( s, sps->vui.i_max_dec_frame_buffering );
}
}

bs_rbsp_trailing( s );
bs_flush( s );
}

写入pps的方式类似:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
void x264_pps_write( bs_t *s, x264_sps_t *sps, x264_pps_t *pps )
{
bs_realign( s );
bs_write_ue( s, pps->i_id );
bs_write_ue( s, pps->i_sps_id );

bs_write1( s, pps->b_cabac );
bs_write1( s, pps->b_pic_order );
bs_write_ue( s, pps->i_num_slice_groups - 1 );

bs_write_ue( s, pps->i_num_ref_idx_l0_default_active - 1 );
bs_write_ue( s, pps->i_num_ref_idx_l1_default_active - 1 );
bs_write1( s, pps->b_weighted_pred );
bs_write( s, 2, pps->b_weighted_bipred );

bs_write_se( s, pps->i_pic_init_qp - 26 - QP_BD_OFFSET );
bs_write_se( s, pps->i_pic_init_qs - 26 - QP_BD_OFFSET );
bs_write_se( s, pps->i_chroma_qp_index_offset );

bs_write1( s, pps->b_deblocking_filter_control );
bs_write1( s, pps->b_constrained_intra_pred );
bs_write1( s, pps->b_redundant_pic_cnt );

int b_scaling_list = !sps->b_avcintra && sps->i_cqm_preset != X264_CQM_FLAT;
if( pps->b_transform_8x8_mode || b_scaling_list )
{
bs_write1( s, pps->b_transform_8x8_mode );
bs_write1( s, b_scaling_list );
if( b_scaling_list )
{
scaling_list_write( s, sps, CQM_4IY );
scaling_list_write( s, sps, CQM_4IC );
bs_write1( s, 0 ); // Cr = Cb
scaling_list_write( s, sps, CQM_4PY );
scaling_list_write( s, sps, CQM_4PC );
bs_write1( s, 0 ); // Cr = Cb
if( pps->b_transform_8x8_mode )
{
scaling_list_write( s, sps, CQM_8IY+4 );
scaling_list_write( s, sps, CQM_8PY+4 );
if( sps->i_chroma_format_idc == CHROMA_444 )
{
scaling_list_write( s, sps, CQM_8IC+4 );
scaling_list_write( s, sps, CQM_8PC+4 );
bs_write1( s, 0 ); // Cr = Cb
bs_write1( s, 0 ); // Cr = Cb
}
}
}
bs_write_se( s, pps->i_chroma_qp_index_offset );
}

bs_rbsp_trailing( s );
bs_flush( s );
}

4.3 写出SEI信息

SEI信息中保存了 H.264 的一些附加信息,如编码器版本或以字符形式保存编码参数等。通常在H.264的码流中,以一个专门的 NAL Unit 保存 SEI 信息。在 x264 中的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
if( h->fenc->b_keyframe )
{
/* Avid's decoder strictly wants two SEIs for AVC-Intra so we can't insert the x264 SEI */
if( h->param.b_repeat_headers && h->fenc->i_frame == 0 && !h->param.i_avcintra_class )
{
/* identify ourself */
nal_start( h, NAL_SEI, NAL_PRIORITY_DISPOSABLE );
if( x264_sei_version_write( h, &h->out.bs ) )
return -1;
if( nal_end( h ) )
return -1;
overhead += h->out.nal[h->out.i_nal-1].i_payload + SEI_OVERHEAD;
}

if( h->fenc->i_type != X264_TYPE_IDR )
{
int time_to_recovery = h->param.b_open_gop ? 0 : X264_MIN( h->mb.i_mb_width - 1, h->param.i_keyint_max ) + h->param.i_bframe - 1;
nal_start( h, NAL_SEI, NAL_PRIORITY_DISPOSABLE );
x264_sei_recovery_point_write( h, &h->out.bs, time_to_recovery );
if( nal_end( h ) )
return -1;
overhead += h->out.nal[h->out.i_nal-1].i_payload + SEI_OVERHEAD;
}
}

其中,x264_sei_version_write 的实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
int x264_sei_version_write( x264_t *h, bs_t *s )
{
// random ID number generated according to ISO-11578
static const uint8_t uuid[16] =
{
0xdc, 0x45, 0xe9, 0xbd, 0xe6, 0xd9, 0x48, 0xb7,
0x96, 0x2c, 0xd8, 0x20, 0xd9, 0x23, 0xee, 0xef
};
char *opts = x264_param2string( &h->param, 0 );
char *payload;
int length;

if( !opts )
return -1;
CHECKED_MALLOC( payload, 200 + strlen( opts ) );

memcpy( payload, uuid, 16 );
sprintf( payload+16, "x264 - core %d%s - H.264/MPEG-4 AVC codec - "
"Copy%s 2003-2019 - http://www.videolan.org/x264.html - options: %s",
X264_BUILD, X264_VERSION, HAVE_GPL?"left":"right", opts );
length = strlen(payload)+1;

x264_sei_write( s, (uint8_t *)payload, length, SEI_USER_DATA_UNREGISTERED );

x264_free( opts );
x264_free( payload );
return 0;
fail:
x264_free( opts );
return -1;
}

从上述实现可知,x264 在 SEI 中写入了以下信息:

  • uuid;
  • x264的版本号和说明信息;
  • 配置选项;

通过修改该函数的实现,可以对 SEI 包含的信息进行自定义配置。


Author: Yin Wenjie
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source Yin Wenjie !
 Current
【x264视频编码器应用与实现】九. x264编码一帧图像API:x264_encoder_encode(二) 【x264视频编码器应用与实现】九. x264编码一帧图像API:x264_encoder_encode(二)
摘要:本文作为 “x264视频编码器应用与实现” 系列博文的第九篇,继续讨论x264编码器实例的图像编码API的实现。
2022-09-29 Yin Wenjie
Next 
【x264视频编码器应用与实现】八. x264编码一帧图像API:x264_encoder_encode(一) 【x264视频编码器应用与实现】八. x264编码一帧图像API:x264_encoder_encode(一)
摘要:本文作为 “x264视频编码器应用与实现” 系列博文的第八篇,主要讨论x264编码器实例的图像编码API的实现。
2022-06-01 Yin Wenjie
  TOC