• (转)x264源码分析(1):main、parse、encode、x264_encoder_open函数代码分析


    转自:http://nkwavelet.blog.163.com/blog/static/2277560382013103010312144/

     x264版本:   x264-snapshot-20140226-2245

     1.     首先对主函数进行分析,main函数很简洁,主要有三个步骤,见下图:

    x264源码分析(1) - nkwavelet - 小波的世界

     

     2.   接下来分析一下Parse函数中的主要过程:
    static int parse( int argc, char **argv, x264_param_t *param, cli_opt_t *opt )
    {
            // 设置编码器默认参数,此处并不是真正设置编码器参数,而是设置到变量defaults中,
    // 该变量用于命令行help查看的时候,显示默认的参数值而已。
    // 该函数的详细分析见:
    x264编码器默认参数值
    x264_param_default( &defaults );

    /* Presets are applied before all other options. */
    for( optind = 0;; )
    { // 获取preset 和tune
    int c = getopt_long( argc, argv, short_options, long_options, NULL );
    ... ... ... ...
    }
           
    // 如果preset设置为placebo,此设置获取更大的psnr值,但是速度非常慢,通常不采用此设置
    if ( preset && !strcasecmp( preset, "placebo" ) )
    b_turbo = 0;

    // 首先设置缺省的编码器参数,然后根据给定的preset和tune修改部分编码器参数
    // 该函数代码详细分析见:x264_param_xxxx 系列函数代码分析
    if ( x264_param_default_preset( param, preset, tune ) < 0 )
    return -1;

    /*  解释命令行参数 */
    for( optind = 0;; )
    {      ... ... ... ...     }

    /* If first pass mode is used, apply faster settings. */
    if ( b_turbo )  
    x264_param_apply_fastfirstpass( param );

    /* Apply profile restrictions. 主要用于检测设置的profile与其他的编码器参数是否有冲突 */
    // 该函数代码详细分析见:x264_param_xxxx 系列函数代码分析
    if ( x264_param_apply_profile( param, profile ) < 0 )
    return -1;

    // 根据输出文件名的后缀名选择相应的文件操作函数集合
    if ( select_output( muxer, output_filename, param ) )
    return -1;

    // 打开输出文件,如果输出文件是flv格式,则此处调用的函数
    // 就是 output/flv.c 文件中的open_file 函数
    cli_output.open_file( output_filename, &opt->hout, &output_opt );

    // 选择输入文件打开方式,有avs, y4m, ffms, lavf, raw等方式,
    // 具体方式还依赖编译选项的支持
    if( select_input( demuxer, demuxername, input_filename, &opt->hin, &info, &input_opt ) )
    return -1;

    // 打开输入文件
    cli_input.open_file( input_filename, &opt->hin, &info, &input_opt );

    // 计算宽高比和帧率,即约分数表示
    x264_reduce_fraction( &info.sar_width, &info.sar_height );
    x264_reduce_fraction( &info.fps_num, &info.fps_den );

     // 初始化视频filter参数,该函数定义在x264.c中
    if( init_vid_filters( vid_filters, &opt->hin, &info, param, output_csp ) )
    return -1;

    /* set param flags from the post-filtered video */
    param->b_vfr_input = info.vfr;
    param->i_fps_num = info.fps_num;
    param->i_fps_den = info.fps_den;
    param->i_timebase_num = info.timebase_num;
    param->i_timebase_den = info.timebase_den;
    param->vui.i_sar_width  = info.sar_width;
    param->vui.i_sar_height = info.sar_height;

     /* Automatically reduce reference frame count to match the user's target 
     level if the user didn't explicitly set a reference frame count. */
    param->i_frame_reference = .......
    }
     
    3.  编码函数encode分析如下:
    static int encode( x264_param_t *param, cli_opt_t *opt )
    {
          x264_t *h  =  NULL;         // 结构体x264_t的定义在common/common.h文件中
          x264_picture_t  pic;        // 结构体x264_picture_t的定义在x264.h文件中
          cli_pic_t   cli_pic;            // 结构体cli_pic_t的定义在input/input.h文件中
     
          // 函数x264_encoder_open定义在encoder/encoder.c文件中,这个函数是对不正确的参数进行修改,
          // 并对各结构体参数和cabac编码、预测等需要的参数进行初始化,打开编码器句柄。
          // 通过x264_encoder_parameters得到设置给x264的参数,通过x264_encoder_reconfig更新x264参数
          x264_t *h = x264_encoder_open( param );
     
          // 该函数定义在encoder/encoder.c文件中,该函数只有一句话,将线程中x264_t参数集拷贝到param中
          x264_encoder_parameters( h, param );
     
          /* ticks/frame = ticks/second / frames/second */
          ticks_per_frame = (int64_t)param->i_timebase_den * param->i_fps_den
                                           / param->i_timebase_num  / param->i_fps_num;
          ticks_per_frame = X264_MAX( ticks_per_frame, 1 );
     
          if ( !param->b_repeat_headers )
          {
                  // Write SPS/PPS/SEI
                 x264_nal_t *headers;
                 int i_nal;
     
                 // 该函数定义在encoder/encoder.c文件中
                 x264_encoder_headers( h, &headers, &i_nal );
     
                 // 写文件头,如果是flv文件,则调用output/flv.c 文件中的write_headers函数
                 cli_output.write_headers( opt->hout, headers );
          }
     
          /* Encode frames,循环编码每一帧 */
          for( ; !b_ctrl_c && (i_frame < param->i_frame_total || !param->i_frame_total); i_frame++ )
          {
                 // 从输入文件中获取一帧,存放在cli_pic中
                 if ( filter.get_frame( opt->hin, &cli_pic, i_frame + opt->i_seek ) )
                       break;
                 
                  // 初始化一帧图片,该函数定义在common/common.c文件中
                  x264_picture_init( &pic );
     
                  // 将cli_pic 转换成x264_picture_t格式的pic
                  convert_cli_to_lib_pic( &pic, &cli_pic );
     
                  // 对一帧编码,返回编码后的字节数,该函数定义在x264.c中
                  i_frame_size = encode_frame( h, opt->hout, &pic, &last_dts );
                  // 此处将函数encode_frame展开来分析
                 {
                          x264_picture_t pic_out;
                         x264_nal_t *nal;
                         int i_nal;
                         int i_frame_size = 0;
     
                         // 具体执行编码一帧图片,该函数定义在x264的encoder/encoder.c文件中,执行完之后得到nal 和i_nal的值
                         // 该函数详细分析见:
    x264_encoder_encode 函数代码分析  
                         i_frame_size = x264_encoder_encode( h, &nal, &i_nal, pic, &pic_out );
                        if ( i_frame_size )
                       {          // 将一帧数据写入到输出文件中
                                  i_frame_size = cli_output.write_frame( hout, nal[0].p_payload, i_frame_size, &pic_out );
                                 *last_dts = pic_out.i_dts;
                       } 
                      return i_frame_size;
                }
     
               if( i_frame_size < 0 )
               {    
                         b_ctrl_c = 1;        /* lie to exit the loop */
                         retval = -1;
               }    
              else if ( i_frame_size )
              {    
                        i_file += i_frame_size;
                        i_frame_output++;
                       if( i_frame_output == 1 )
                               first_dts = prev_dts = last_dts;
               }
     
               // 释放一帧
              if ( filter.release_frame( opt->hin, &cli_pic, i_frame + opt->i_seek ) )
                          break;
          }  // 循环编码一帧结束
     
          /* Flush delayed frames */
          while( !b_ctrl_c && x264_encoder_delayed_frames( h ) )
          {
                      i_frame_size = encode_frame( h, opt->hout, NULL, &last_dts );
                      ... ... ... ...
           }
     
           // 关闭编码器
           x264_encoder_close( h );
     
           // 关闭输出文件
           cli_output.close_file( opt->hout, largest_pts, second_largest_pts );
     
           return retval;
    }

    4.  编码函数x264_encoder_open分析如下:
    x264_t* x264_encoder_open( x264_param_t *param )
    {
    /* 结构体x264_t的定义在common/common.h中 */
    x264_t *h;

    /* Create a copy of param, 创建一个param拷贝 */
    memcpy( &h->param, param, sizeof(x264_param_t) );

    /*  x264线程初始化 */
    x264_threading_init();

    /* 该函数位于encoder/encoder.c中,主要用于检查h->param中的参数设置是否有冲突  */
    if( x264_validate_parameters( h, 1 ) < 0 )
    goto fail;

    /* 如果提供了外部量化矩阵文件,则读取分析该量化矩阵文件
     */
    if( h->param.psz_cqm_file )
    if( x264_cqm_parse_file( h, h->param.psz_cqm_file ) < 0 ) 
    goto fail;

    /* 用于2pass模式,保存编码的统计数据 */
    if( h->param.rc.psz_stat_out ) 
    h->param.rc.psz_stat_out = strdup( h->param.rc.psz_stat_out ); 
    if( h->param.rc.psz_stat_in ) 
    h->param.rc.psz_stat_in = strdup( h->param.rc.psz_stat_in );

    /* 将fps和timebase化为既约分数 */
    x264_reduce_fraction( &h->param.i_fps_num, &h->param.i_fps_den ); 
    x264_reduce_fraction( &h->param.i_timebase_num, &h->param.i_timebase_den );

    /* Init x264_t
     */
    h->i_frame = -1;
    h->i_frame_num = 0;
    h->i_idr_pic_id = 0;

    ·/* 设置vui的宽高比,也就是将
    vui.i_sar_width和vui.i_sar_height按比例缩放到65535以内并化为即约分数,通常可以忽略 */
    x264_set_aspect_ratio( h, &h->param, 1 );

    /* 初始化sps和pps,这两个函数位于encoder/set.c中 */
    x264_sps_init( h->sps, h->param.i_sps_id, &h->param ); 
    x264_pps_init( h->pps, h->param.i_sps_id, &h->param, h->sps );

    /* 检查设定的level是否有效,该函数及数组
    x264_levels[] 定义定义在encoder/set.c中  */
    /* 有关各种level的极限值,可参考h264官方协议:Table A-1 – Level limits */
    x264_validate_levels( h, 1 );

    h->chroma_qp_table = i_chroma_qp_table + 12 + h->pps->i_chroma_qp_index_offset;

    /* 初始化量化矩阵  */
    if( x264_cqm_init( h ) < 0 )
    goto fail;

    /* 每行每列及一帧总的宏块数目  */
    h->mb.i_mb_width  = h->sps->i_mb_width; 
    h->mb.i_mb_height = h->sps->i_mb_height; 
    h->mb.i_mb_count  = h->mb.i_mb_width * h->mb.i_mb_height; 

    // 对于csp取值X264_CSP_I420而言,以下两个值都是1
    h->mb.chroma_h_shift = CHROMA_FORMAT == CHROMA_420 || CHROMA_FORMAT == CHROMA_422; 
    h->mb.chroma_v_shift = CHROMA_FORMAT == CHROMA_420;

    /* Adaptive MBAFF and subme 0 are not supported as we require halving motion 
     * vectors during prediction, resulting in hpel mvs. 
     * The chosen solution is to make MBAFF non-adaptive in this case. */
    // 忽略隔行,因此下面变量值通常为0
    h->mb.b_adaptive_mbaff = PARAM_INTERLACED && h->param.analyse.i_subpel_refine;

    /* Init frames. 初始化一些帧设置,包括延迟帧数、参考帧数、关键帧间隔等 */ 
    // 
    i_bframe_adaptive的默认值为X264_B_ADAPT_FAST,rc.b_stat_read通常取值0
    // i_bframe_adaptive可以通过命令行参数--b-adapt设定,可以取值0、1、2,默认值为1,即X264_B_ADAPT_FAST
    // param.i_bframe的值可以通过命令行参数--bframes设定
    if( h->param.i_bframe_adaptive == X264_B_ADAPT_TRELLIS && !h->param.rc.b_stat_read ) 
    h->frames.i_delay = X264_MAX(h->param.i_bframe,3)*4; 
    else 
    h->frames.i_delay = h->param.i_bframe;

    // 
    mb_tree默认是开启的,i_vbv_buffer_size默认值为0,
    // rc.i_lookahead默认值为40,但是对于不同的preset,
    rc.i_lookahead取不同的值
    // rc.i_lookahead的值还可以通过命令行参数--rc-lookahead设定
    if ( h->param.rc.b_mb_tree || h->param.rc.i_vbv_buffer_size ) 
    h->frames.i_delay = X264_MAX( h->frames.i_delay, h->param.rc.i_lookahead ); 

    i_slicetype_length = h->frames.i_delay; 
    h->frames.i_delay += h->i_thread_frames - 1;  // 
    h->i_thread_frames通常取值1
    h->frames.i_delay += h->param.i_sync_lookahead;  // 
    h->param.i_sync_lookahead通常取值0
    h->frames.i_delay += h->param.b_vfr_input;  // 
    h->param.b_vfr_input默认值为1

    // 
    param.i_bframe_pyramid的值可以通过命令行参数--b-pyramid设定,可以取值0、1、2
    // 
    --b-pyramid表示是否允许B帧作为参考帧,默认值为normal=2,none=0表示不允许B帧作为参考帧

    h->frames.i_bframe_delay = h->param.i_bframe ? (h->param.i_bframe_pyramid ? 2 : 1) : 0; 
    // 获取前向、后向最大参考帧数,解码缓冲区最大帧数
    h->frames.i_max_ref0 = h->param.i_frame_reference; 
    h->frames.i_max_ref1 = X264_MIN( h->sps->vui.i_num_reorder_frames, h->param.i_frame_reference ); 
    h->frames.i_max_dpb = h->sps->vui.i_max_dec_frame_buffering; 
    h->frames.b_have_lowres = !h->param.rc.b_stat_read 
     && ( h->param.rc.i_rc_method == X264_RC_ABR 
     || h->param.rc.i_rc_method == X264_RC_CRF 
     || h->param.i_bframe_adaptive 
     || h->param.i_scenecut_threshold 
     || h->param.rc.b_mb_tree 
     || h->param.analyse.i_weighted_pred );  // 
    h->frames.b_have_lowres通常取值1
    h->frames.b_have_lowres |= h->param.rc.b_stat_read && h->param.rc.i_vbv_buffer_size > 0; 
    h->frames.b_have_sub8x8_esa = !!(h->param.analyse.inter & X264_ANALYSE_PSUB8x8); 

    // i_keyint_max默认值250,上一个idr帧和上一个关键帧的序号,初始化为负值
    h->frames.i_last_idr = 
    h->frames.i_last_keyframe = - h->param.i_keyint_max;

    h->frames.i_input = 0; 
    h->frames.i_largest_pts = h->frames.i_second_largest_pts = -1;  // 初始化帧的最大pts和次大pts值为-1
    h->frames.i_poc_last_open_gop = -1;

    /* Allocate room for max refs plus a few extra just in case. */ 
    CHECKED_MALLOCZERO( h->frames.unused[0], (h->frames.i_delay + 3) * sizeof(x264_frame_t *) );
    CHECKED_MALLOCZERO( h->frames.unused[1], (h->i_thread_frames + X264_REF_MAX + 4) * sizeof(x264_frame_t *) );
    CHECKED_MALLOCZERO( h->frames.current, (h->param.i_sync_lookahead + h->param.i_bframe 
     + h->i_thread_frames + 3) * sizeof(x264_frame_t *) ); 
    if ( h->param.analyse.i_weighted_pred > 0 ) 
    // 如果开启了P帧加权预测,则还需要申请额外的帧空间;默认是开启的
    CHECKED_MALLOCZERO( h->frames.blank_unused, h->i_thread_frames * 4 * sizeof(x264_frame_t *) );
    h->i_ref[0] = h->i_ref[1] = 0;  // 初始化前向和后向参考帧数为0
    h->i_cpb_delay = h->i_coded_fields = h->i_disp_fields = 0; 
    h->i_prev_duration = ((uint64_t)h->param.i_fps_den * h->sps->vui.i_time_scale) 
    / ((uint64_t)h->param.i_fps_num * h->sps->vui.i_num_units_in_tick); 
    h->i_disp_fields_last_frame = -1; 

    /* precalculate the cost of coding various combinations of bits in a single context,该函数定义在encoder/rdo.c中 */
    x264_rdo_init();

    /* init CPU functions
     */ 
    x264_predict_16x16_init( h->param.cpu, h->predict_16x16 );  // 该函数定义在common/predict.c 
    x264_predict_8x8c_init( h->param.cpu, h->predict_8x8c ); 
    // 该函数定义在common/predict.c
    x264_predict_8x16c_init( h->param.cpu, h->predict_8x16c ); 
    // 该函数定义在common/predict.c
    x264_predict_8x8_init( h->param.cpu, h->predict_8x8, &h->predict_8x8_filter ); 
    // 该函数定义在common/predict.c
    x264_predict_4x4_init( h->param.cpu, h->predict_4x4 ); 
    // 该函数定义在common/predict.c
    x264_pixel_init( h->param.cpu, &h->pixf );  // 
    该函数定义在common/pixel.c中 
    x264_dct_init( h->param.cpu, &h->dctf );  // 该函数定义在common/dct.c中 
    x264_zigzag_init( h->param.cpu, &h->zigzagf_progressive, &h->zigzagf_interlaced ); 
    // 该函数定义在common/dct.c中 
    memcpy( &h->zigzagf, PARAM_INTERLACED ? &h->zigzagf_interlaced : &h->zigzagf_progressive, sizeof(h->zigzagf) ); 
    x264_mc_init( h->param.cpu, &h->mc, h->param.b_cpu_independent ); 
    // 该函数定义在common/mc.c中 
    x264_quant_init( h, h->param.cpu, &h->quantf ); 
    // 该函数定义在common/quant.c中
    x264_deblock_init( h->param.cpu, &h->loopf, PARAM_INTERLACED );
    // 该函数定义在common/deblock.c中
    x264_bitstream_init( h->param.cpu, &h->bsf ); // 该函数定义在common/bitstream.c中
    if( h->param.b_cabac ) 
    x264_cabac_init( h ); 
    // 该函数定义在common/cabac.c中 
    else 
    // 函数x264_cavlc_init定义在common/vlc.c中 
    x264_stack_align( x264_cavlc_init, h );
    // 该函数定义在common/cpu.h中

    mbcmp_init( h ); 
    // 该函数定义在encoder/encoder.c中 
      chroma_dsp_init( h );
    // 该函数定义在encoder/encoder.c中 

    /*  输出cpu可以利用的多媒体指令集,例如mmx、sse2、sse3等 */
    p = buf + sprintf( buf, "using cpu capabilities:" );
    for( int i = 0; x264_cpu_names[i].flags; i++ ) // 数组
    x264_cpu_names定义在common/cpu.c中
    { ... ... 
    }
    if( !h->param.cpu ) 
    p += sprintf( p, " none!" );
    x264_log( h, X264_LOG_INFO, "%s ", buf );

    /* 生成数组logs,其中logs[0] = 0.718、 logs[i] = 2 * log2(i + 1) + 1.718,
       其中log2表示以2为底的对数,该函数定义在encoder/analyse.c中
      */
    float *logs = x264_analyse_prepare_costs( h );

    /* 初始化不同qp值所对应的代价,
    函数定义在encoder/analyse.c中  */
    for( qp = X264_MIN( h->param.rc.i_qp_min, QP_MAX_SPEC ); qp <= h->param.rc.i_qp_max; qp++ )
    if ( x264_analyse_init_costs( h, logs, qp ) ) 

    goto fail;
    if ( x264_analyse_init_costs( h, logs, X264_LOOKAHEAD_QP ) )
      goto fail;
    x264_free( logs );

     static const uint16_t cost_mv_correct[7] = { 24, 47, 95, 189, 379, 757, 1515 };
    /* Checks for known miscompilation issues. */
    if( h->cost_mv[X264_LOOKAHEAD_QP][2013] != cost_mv_correct[BIT_DEPTH-8] )
    goto fail;

    /* Must be volatile or else GCC will optimize it out. */ 
    volatile int temp = 392;
    if( x264_clz( temp ) != 23 )
    goto fail;

    h->out.i_nal = 0;
    h->out.i_bitstream = X264_MAX( 1000000, h->param.i_width * h->param.i_height * 4
     * ( h->param.rc.i_rc_method == X264_RC_ABR ? pow( 0.95, h->param.rc.i_qp_min )
    : pow( 0.95, h->param.rc.i_qp_constant ) * X264_MAX( 1, h->param.rc.f_ip_factor )));
    h->nal_buffer_size = h->out.i_bitstream * 3/2 + 4 + 64;  /* +4 for startcode, +64 for nal_escape assembly padding */

    // 有关线程池、线程锁定等代码
    ... ...   

    if ( x264_lookahead_init( h, i_slicetype_length ) )
    // 该函数定义在encoder/lookahead.c中
    goto fail;

    for( int i = 0; i < h->param.i_threads; i++ )
    if ( x264_macroblock_thread_allocate( h->thread[i], 0 ) < 0 )
    // 该函数定义在common/macroblock.c中
    goto fail;

    /* 初始化码率控制的一些参数,该函数详细分析见:
    x264_ratecontrol_new 函数代码分析(码率控制) */
    if ( x264_ratecontrol_new( h ) < 0 ) // 该函数定义在encoder/ratecontrol.c
    goto fail;

    /* 输出profile、level等一些日志信息 */
    ... ...

    return h;
    }
  • 相关阅读:
    Thymeleaf中,将字符串作为js函数的参数
    测试开发面试题总结
    013_RomanToInteger
    Python列表中查找某个元素的索引(多个)
    Python“函数式编程”中常用的函数
    009_Palindrome Number
    Python字符串方法总结(一)
    007_Reverse Integer
    002_Add Two Numbers
    pycharm上传代码到github
  • 原文地址:https://www.cnblogs.com/lihaiping/p/4194317.html
Copyright © 2020-2023  润新知