[H264]x264_encoder_headers函数

##x264_encoder_headers
#####H264中我们所说的帧是不包含全部的视频的数据的,它是由网络数据NAL和视频数据VCL组成。这个函数主要用来生成一个帧的NAL数据层,这个函数主要是输出SPS、PPS、SEI等数据封装。
具体来看代码

int x264_encoder_headers( x264_t *h, x264_nal_t **pp_nal, int *pi_nal )
{
    int frame_size = 0;
    /* init bitstream context */
    h->out.i_nal = 0;
    bs_init( &h->out.bs, h->out.p_bitstream, h->out.i_bitstream );
	
    /* Write SEI, SPS and PPS. */
    /* generate sequence parameters */
    x264_nal_start( h, NAL_SPS, NAL_PRIORITY_HIGHEST );
    x264_sps_write( &h->out.bs, h->sps );
    if( x264_nal_end( h ) )
        return -1;

    /* generate picture parameters */
    x264_nal_start( h, NAL_PPS, NAL_PRIORITY_HIGHEST );
    x264_pps_write( &h->out.bs, h->sps, h->pps );
    if( x264_nal_end( h ) )
        return -1;

    /* identify ourselves */
    x264_nal_start( h, NAL_SEI, NAL_PRIORITY_DISPOSABLE );
    if( x264_sei_version_write( h, &h->out.bs ) )
        return -1;
    if( x264_nal_end( h ) )
        return -1;

    frame_size = x264_encoder_encapsulate_nals( h, 0 );
    if( frame_size < 0 )
        return -1;

    /* now set output*/
    *pi_nal = h->out.i_nal;
    *pp_nal = &h->out.nal[0];
    h->out.i_nal = 0;

    return frame_size;
}

进入这个函数后,bs_init()先申请内存用来存放比特流数据的内存块,x264_nal_start接着生成SPS信息并存放在h->out.nal[h->out.inal]中,接着,将SPS信息写入h->sps[0]之中,如下

void x264_sps_write( bs_t *s, x264_sps_t *sps )
{
    bs_realign( s );
	//这个函数将8bit写入s->cur_bits中
	//profile的含义见https://blog.youkuaiyun.com/heanyu/article/details/6204414
    bs_write( s, 8, sps->i_profile_idc );
	//左移写入四bit数据,为0不用遵从附录A2.1的条件
    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 );

	//保留的4位
    bs_write( s, 4, 0 );    /* reserved */

	//写入的水平,代表分辨率,同样见https://blog.youkuaiyun.com/heanyu/article/details/6204414
    bs_write( s, 8, sps->i_level_idc );
	
	//写入SPS的ID号码
    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 );
        bs_write1( s, 0 ); // seq_scaling_matrix_present_flag
    }

    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;
        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 );
}
//初始化编码器 x264_t* init_x264_encoder(int width, int height, int fps) { x264_param_t param; x264_param_default_preset(&param, "ultrafast", "zerolatency"); param.i_width = width; param.i_height = height; param.i_fps_num = fps; param.i_fps_den = 1; param.i_csp = X264_CSP_NV12; param.i_threads = 1; param.rc.i_rc_method = X264_RC_CQP; param.rc.i_qp_constant = 25; param.i_log_level = 0; x264_param_apply_profile(&param, "high"); x264_t* encoder = x264_encoder_open(&param); if (!encoder) { std::cerr << "x264编码器初始化失败!" << std::endl; return nullptr; } return encoder; } //编码 int encode_frame_nv12(x264_t* encoder, const uint8_t* nv12Data, int width, int height, std::string& h264OutString) { if (!encoder || !nv12Data) return -1; // 3️⃣ 分配输入帧结构 x264_picture_t pic_in, pic_out; x264_picture_alloc(&pic_in, X264_CSP_NV12, width, height); // 4️⃣ 拆分 NV12 数据(连续内存:Y + UV) int y_size = width * height; int uv_size = y_size / 2; memcpy(pic_in.img.plane[0], nv12Data, y_size); memcpy(pic_in.img.plane[1], nv12Data + y_size, uv_size); // 5️⃣ 编码 x264_nal_t *nals = nullptr; int i_nals = 0; int frame_size = x264_encoder_encode(encoder, &nals, &i_nals, &pic_in, &pic_out); if (frame_size < 0) { std::cerr << "x264编码失败" << std::endl; x264_picture_clean(&pic_in); x264_encoder_close(encoder); return -3; } // 6️⃣ 输出拼接 h264OutString.clear(); for (int i = 0; i < i_nals; i++) { h264OutString.append(reinterpret_cast<char *>(nals[i].p_payload), nals[i].i_payload); } return 0; } //销毁编码器 void destroy_x264_encoder(x264_t* encoder) { if (encoder) x264_encoder_close(encoder); } 这是编码器相关代码,结合给出的相机采集代码,说明为什么每30帧才有帧头,若我想修改,应该如何修改
最新发布
11-29
<think> 我们注意到用户已经提供了编码器的初始化代码,其中使用了x264库。在x264中,关键帧(I帧)的生成间隔由参数`i_keyint_max`控制。在用户提供的初始化函数`init_x264_encoder`中,并没有显式设置`i_keyint_max`,因此编码器会使用默认值。x264的默认关键帧间隔通常是250帧(如果使用默认参数的话),但实际观察到30帧出现一个关键帧,可能是因为预设"ultrafast"和"zerolatency"的默认设置,或者因为场景变化触发了关键帧。 要理解为什么是30帧出现一个关键帧,我们需要检查x264预设的参数。实际上,在x264中,预设"ultrafast"的默认关键帧间隔是250帧,但可能因为用户没有设置,同时编码器在运行过程中由于场景变化或者参数配置(如设置了IDR间隔)而产生了关键帧。然而,用户代码中没有设置`i_keyint_max`,所以默认值会被使用。 用户希望每帧都是关键帧(即每帧都有帧头,也就是SPS、PPS和I帧数据),那么需要将关键帧间隔设置为1,即每帧都是关键帧。 修改方法: 在编码器初始化函数`init_x264_encoder`中,设置参数`param.i_keyint_max = 1;` 并且设置`param.i_bframe = 0;`(避免B帧,因为B帧是双向预测,可能会依赖前后帧,而关键帧不需要依赖其他帧)。同时,为了确保每帧都是IDR帧(关键帧),我们还需要注意其他相关参数。 具体修改位置: 在函数`init_x264_encoder`中,在调用`x264_param_default_preset`之后,可以设置这些参数。 修改后的代码片段如下: ```c x264_t* init_x264_encoder(int width, int height, int fps) { x264_param_t param; x264_param_default_preset(&param, "ultrafast", "zerolatency"); // 设置关键帧间隔为1,即每帧都是关键帧 param.i_keyint_max = 1; // 禁用B帧,因为关键帧序列中不需要B帧 param.i_bframe = 0; // 以下是原有的参数设置 param.i_width = width; param.i_height = height; param.i_fps_num = fps; param.i_fps_den = 1; param.i_csp = X264_CSP_NV12; param.i_threads = 1; param.rc.i_rc_method = X264_RC_CQP; param.rc.i_qp_constant = 25; param.i_log_level = 0; x264_param_apply_profile(&param, "high"); x264_t* encoder = x264_encoder_open(&param); if (!encoder) { std::cerr << "x264编码器初始化失败!" << std::endl; return nullptr; } return encoder; } ``` 这样修改后,编码器会为每一帧都生成关键帧,因此每一帧都会有帧头(SPS、PPS和I帧数据)。注意:这样会增加码流的大小,因为关键帧的数据量通常比非关键帧大很多。 另外,在编码函数`encode_frame_nv12`中,我们不需要做修改。 但是,请注意:每帧都输出SPS和PPS可能会造成冗余,因为通常这些信息不需要每帧都出现。如果对带宽敏感,可以考虑在编码器中设置参数,让SPS和PPS只在关键帧出现(但按照我们的设置,每帧都是关键帧,所以每帧都会包含)。另外,也可以考虑使用`param.b_repeat_headers=1`来让编码器在每个关键帧都输出SPS和PPS(实际上,当我们设置`i_keyint_max=1`时,编码器默认会在每个关键帧(即每一帧)输出SPS和PPS,因为每个关键帧都是IDR帧)。 如果不想每帧都输出SPS和PPS,可以设置`param.b_repeat_headers=0`,但这样就需要在传输过程中确保接收方一开始就收到SPS和PPS,否则无法解码后续帧。由于我们每帧都是关键帧,所以即使不重复SPS和PPS,接收方也能解码任意一帧(因为每帧都是独立解码的)。但注意,设置`param.b_repeat_headers=0`时,编码器只会在第一帧输出SPS和PPS,后面的帧不再输出。这样,如果接收方错过第一帧,则无法解码后续帧。因此,在实时流传输中,通常需要每帧都携带SPS和PPS,或者周期性发送。 不过,根据用户要求,每帧都要有帧头,所以我们应该保留每帧都有SPS和PPS。默认情况下,当关键帧间隔为1时,编码器会自动在每个关键帧(即每一帧)前面加上SPS和PPS。因此,我们不需要额外设置`b_repeat_headers`,因为x264默认情况下在每次IDR帧都会输出SPS和PPS(通过设置`i_keyint_max`为1,每一帧都是IDR帧)。 总结修改点: 1. 在`init_x264_encoder`函数中,设置`param.i_keyint_max = 1;` 2. 设置`param.i_bframe = 0;`(可选,但推荐,因为B帧在每帧关键帧的情况下没有必要,而且会增加延迟) 这样修改后,每帧都是关键帧,每帧都有帧头。 注意:这种设置会显著增加码率,请根据实际需求考虑。 如果用户希望减少码率,但又需要每帧独立(例如为了低延迟或容错),可以考虑使用低QP值,或者使用其他编码方式(如JPEG)。 最后,请重新编译并测试。 下面,根据规则,我们提出相关问题。 </think> ### 为什么每30帧才有一个帧头? 在您的编码器代码中,每30帧出现一个帧头(关键帧/I帧)的根本原因是 **x264编码器使用了默认的关键帧间隔设置**。具体分析如下: 1. **预设参数分析**: - 您使用了 `x264_param_default_preset(&param, "ultrafast", "zerolatency")` - "ultrafast"预设的默认关键帧间隔 **`i_keyint_max=250`**(250帧) - 实际观察到的30帧间隔可能是由于: - 场景变化触发自适应关键帧 - 编码器内部优化策略 - 可能存在的其他参数隐式影响 2. **GOP结构机制**: ```mermaid graph LR A[I帧] --> B[P帧] B --> C[P帧] C --> D[...] D --> E[I帧] // 间隔由i_keyint_max控制 ``` - I帧携带完整帧头(SPS/PPS + 帧数据) - P帧仅包含运动差异数据(无帧头) 3. **代码验证**: 在您的初始化函数中,**缺少关键参数设置**: ```c // init_x264_encoder 函数中缺失: param.i_keyint_max = ...; // 关键帧间隔(默认250) param.i_bframe = ...; // B帧数量(默认3) ``` ### 修改方案:让每帧都有帧头 只需在**编码器初始化函数**中添加两个参数: #### 修改位置:`init_x264_encoder()` 函数 在 `param.rc.i_qp_constant = 25;` 后添加: ```c x264_t* init_x264_encoder(int width, int height, int fps) { // ... [原有代码] param.rc.i_qp_constant = 25; param.i_log_level = 0; // +++ 新增关键参数 +++ param.i_keyint_max = 1; // 关键帧间隔设为1(每帧都是I帧) param.i_bframe = 0; // 禁用B帧(避免依赖) x264_param_apply_profile(&param, "high"); // ... [后续代码] } ``` #### 修改后效果: | 参数 | 原值 | 新值 | 影响 | |------|------|------|------| | `i_keyint_max` | 250 | 1 | 每帧都是关键帧(含帧头) | | `i_bframe` | 3 | 0 | 消除双向预测依赖 | | 视频体积 | 正常 | 增加30-50% | I帧比P帧大 | | 解码延迟 | 中等 | 极低 | 无帧间依赖 | #### 备选方案:动态强制关键帧 在采集函数中插入强制关键帧命令: ```c int camera_capture(int camera_id, string &photo_string) { // ... [原有代码] // 动态请求关键帧(放在encode_frame_nv12之前) if (camera_x264_t[camera_id]) { x264_encoder_intra_refresh(camera_x264_t[camera_id]); } encode_frame_nv12(camera_x264_t[camera_id], frameData, ...); // ... [后续代码] } ``` ### 注意事项 1. **帧头内容**:关键帧的帧头包含SPS/PPS,修改后每帧都会携带这些数据 2. **带宽影响**:1280x720@30fps视频,修改后带宽可能从2Mbps增至3Mbps 3. **验证方法**: ```bash ffmpeg -i output.h264 -vf "showinfo" -f null - 2>&1 | grep pict_type # 应显示每帧都是I帧 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值