libx264 编码参数调整--流媒体

在标题加了个后缀-流媒体。所以主要是借libx264来研究一下其中关于在流媒体场景中(低延时)偏向的设置, 关于编码器 码率,图像效果等等这里面有好多参数,指不定有相互影响的参数,理解的不多,这里借鉴各处博客以及x264官方wiki和libx264源码,官方网站https://www.videolan.org/developers/x264.html (vlc 一家组织,去vlc官网即可,还可以找到wiki文档))记录下试水libx264-流媒体低延时编码的参数设置
环境:
        5代corei7 cpu ,ubuntu 系统。
        libx264  配置 --disable-asm 关闭,这个可能会降低效率,实际使用需要开启,这里只是研究测试。
libx264 ,configure + make 之后默认生成x264可执行文件,不过我们这里并不打算使用x264工具来研究,其源码目录下有一个 example.c 文件,可以作为api 调用的参考。需要我们 执行
#make example
生成example 可执行文件,本片参考这个example 源码来写自己的demo, 
1.0 设置参数。 2.0 输入一帧yuv420数据。 3.0 编码取出数据

Q1: 怎么设置编码器让其不生成B帧?

param.i_bframe=0 //  /* how many b-frame between 2 references pictures */ 源码注释

Q2:怎么设置GOP值

param.i_keyint_max=xx /* Force an IDR keyframe at this interval */

Q3:在每一个I帧前面加上sps pps信息

param.b_repeat_headers=1 /* put SPS/PPS before each keyframe */

Q4:关键点,怎么让编码器不缓存,期望它是送入一帧数据,编码完就出一帧数据?

x264_param_default_preset( &param, "ultrafast", "zerolatency" )
第二个参数,zerolatency。 设置为这个之后,就是进一帧出一帧的顺序了。在编码存储本地文件的时候可能不需要这么做,但是事实性要求高的肯定要这么设置了,不然始终有delay。
第一个参数,实际上设置的是包含一系列参数的设置组合,根据你选择的速度来帮忙设置一些列参数,比如上面的param.i_bframe在选择 ultrafast的时候就会被设置为0,在源码 base.c ::param_apply_preset( x264_param_t *param, const char *preset ) 函数中可以看到详细信息,可选的值:{"ultrafast","superfast","veryfast","faster","fast","medium","slow","slower","veryslow","placebo"}
看名字也就知道意思了。

其他的一些参数:

   param.i_bitdepth = 8;
    param.i_csp = X264_CSP_I420;//颜色空间 color space?
    param.i_width  = width;
    param.i_height = height;
    param.b_vfr_input = 0; //设置为1将控制时间戳?
    param.b_annexb = 1;//每一个帧携带启始码00 00 00 01,如果设置为0,则不添加00 00 00 01,而是存储这一帧的数据大小值。(占4个字节)

对比结果

不设置"zerolatency",即使第一个参数设置的是 "ultrafast",也是有缓存,延时输出:
libx264 编码参数调整--流媒体libx264 编码参数调整--流媒体

比如上面,一直到输入12帧之后才输出。
开启 "zerolatency" 之后:
libx264 编码参数调整--流媒体
这才是我们流媒体实时性要求的。
可以看到我这样的硬件环境,ultrafast 设置编码一帧的速度在 4ms以内,(这里原yuv数据分辨率为640x480,yuv420sp,libx264没有开启asm 汇编级别的平台优化). 并且基本上编码 I帧的耗时比P帧高,上面的 nal->i_type ==7为I帧。(原本以为P帧信息冗余更少,要帧参考,可能耗时会更大,看来不是。)

放上调试demo :

//canok 20210804

#include <stdint.h>
#include <stdio.h>
#include <sys/time.h>
#include <x264.h>

#define FAIL_IF_ERROR( cond, ... )\
do\
{\
    if( cond )\
    {\
        fprintf( stderr, __VA_ARGS__ );\
        goto fail;\
    }\
} while( 0 )


int64_t getNowUs(){
    struct timeval tv;
    gettimeofday(&tv, 0);
    return (int64_t)tv.tv_sec * 1000000 + (int64_t)tv.tv_usec;
}

int main( int argc, char **argv )
{//https://www.cnblogs.com/wainiwann/p/5647521.html
    int width, height;
    x264_param_t param;
    x264_picture_t pic;
    x264_picture_t pic_out;
    x264_t *h;
    int i_frame = 0;
    int out_frame=0;
    int i_frame_size;
    x264_nal_t *nal;
    int i_nal;

    //FAIL_IF_ERROR( !(argc > 1), "Example usage: example 352x288 <input.yuv >output.h264\n" );
    if(argc !=3){
        printf("usage: my_encoder WxH inputfile.yuv\n");
        return -1;
    }

    FILE *fp_in=fopen(argv[2],"r");
    if(fp_in==NULL){
        printf("[%s%d] fopen err:%s\n",__FUNCTION__,__LINE__,argv[2]);
        return -1;
    }

    FILE *fp_out=fopen("out.h264","w+");
    if(fp_out==NULL){
        printf("[%s%d] fopen err:out.h264 \n",__FUNCTION__,__LINE__);
        return -1;
    }

    FAIL_IF_ERROR( 2 != sscanf( argv[1], "%dx%d", &width, &height ), "resolution not specified or incorrect\n" );



    /* Get default params for preset/tuning */
    #if 0
    if( x264_param_default_preset( &param, "medium", NULL ) < 0 )
        goto fail;
    #else
    //设置到最快
    //具体参数可以看base.c::param_apply_preset
    //zerolatency不延迟这个设置之后,就进一帧出一帧了,不延迟
    //char speed[][16]={"ultrafast","superfast","veryfast","faster","fast","medium","slow","slower","veryslow","placebo"};
    if( x264_param_default_preset( &param, "ultrafast", "zerolatency" ) < 0 )
    //if( x264_param_default_preset( &param, "ultrafast", NULL ) < 0 )
        goto fail;
    #endif
    /* Configure non-default params */
    param.i_bitdepth = 8;
    param.i_csp = X264_CSP_I420;//颜色空间
    param.i_width  = width;
    param.i_height = height;
    param.b_vfr_input = 0; //设置为1将控制时间戳?
    param.b_repeat_headers = 1; //在每一个I帧前面加上sps pps
    param.b_annexb = 1;//每一个帧携带启始码00 00 00 01,如果设置为0,则不添加00 00 00 01,而是存储这一帧的数据大小值。(占4个字节)

    param.i_bframe =0; // 不使用B帧/* how many b-frame between 2 references pictures */

    //param.i_keyint_min =
    param.i_keyint_max = 5; //gop

//https://blog.csdn.net/bingqingsuimeng/article/details/79197901
    param.rc.b_mb_tree = 0;//实时编码是强烈建议为0

    /* Apply profile restrictions. */
    if( x264_param_apply_profile( &param, "high" ) < 0 )
        goto fail;

    if( x264_picture_alloc( &pic, param.i_csp, param.i_width, param.i_height ) < 0 )
        goto fail;
#undef fail
#define fail fail2

    h = x264_encoder_open( &param );
    if( !h )
        goto fail;
#undef fail
#define fail fail3

    int luma_size = width * height;
    int chroma_size = luma_size / 4;
    /* Encode frames */
    for( ;; i_frame++ )
    {
        /* Read input frame */
        if( fread( pic.img.plane[0], 1, luma_size, fp_in ) != (unsigned)luma_size )
            break;
        if( fread( pic.img.plane[1], 1, chroma_size, fp_in ) != (unsigned)chroma_size )
            break;
        if( fread( pic.img.plane[2], 1, chroma_size, fp_in ) != (unsigned)chroma_size )
            break;

        pic.i_pts = i_frame;
        int64_t t1=getNowUs();
        
        printf("[%s%d]in:%d %ld\n",__FUNCTION__,__LINE__,i_frame, t1);
        i_frame_size = x264_encoder_encode( h, &nal, &i_nal, &pic, &pic_out );
        if( i_frame_size < 0 )
            goto fail;
        else if( i_frame_size )
        {
            int64_t t2=getNowUs();
            printf("[%s%d]out:%d %ld\n",__FUNCTION__,__LINE__,out_frame, t2);
            //SLICE_TYPE_P SLICE_TYPE_B SLICE_TYPE_I 
            //nal->i_type nal_unit_type_e
            /*enum sei_payload_type_e
            {
                SEI_BUFFERING_PERIOD       = 0,
                SEI_PIC_TIMING             = 1,
                SEI_PAN_SCAN_RECT          = 2,
                SEI_FILLER                 = 3,
                SEI_USER_DATA_REGISTERED   = 4,
                SEI_USER_DATA_UNREGISTERED = 5,
                SEI_RECOVERY_POINT         = 6,
                SEI_DEC_REF_PIC_MARKING    = 7,
                SEI_FRAME_PACKING          = 45,
                SEI_ALTERNATIVE_TRANSFER   = 147,
            };*/
            printf("[%s%d]in:%d---%dout,taketimes:%ld,nal->i_type:%d\n",__FUNCTION__,__LINE__,i_frame,out_frame,t2-t1,nal->i_type);
            out_frame++;
            if( !fwrite( nal->p_payload, i_frame_size, 1, fp_out ) )
                goto fail;
        }
    }
    /* Flush delayed frames */
    //设置 "zerolatency" 之后就不会有delayed_frames了。
    while( x264_encoder_delayed_frames( h ) )
    {
        i_frame_size = x264_encoder_encode( h, &nal, &i_nal, NULL, &pic_out );
        if( i_frame_size < 0 )
            goto fail;
        else if( i_frame_size )
        {
            int64_t t2=getNowUs();
            printf("[%s%d]out-delayframe:%d %ld\n",__FUNCTION__,__LINE__,out_frame, t2);
            out_frame++;
            if( !fwrite( nal->p_payload, i_frame_size, 1, fp_out ) )
                goto fail;
        }
    }

    x264_encoder_close( h );
    x264_picture_clean( &pic );
    return 0;

#undef fail
fail3:
    x264_encoder_close( h );
fail2:
    x264_picture_clean( &pic );
fail:
    fclose(fp_in);
    fclose(fp_out);
    return -1;
}

 makefile: 参照make example 时的输出copy一个就行

all:
	gcc -Wno-maybe-uninitialized -Wshadow -O3 -ffast-math -m64  -Wall -I. -I../x264-master/my_install/include -std=gnu99 -D_GNU_SOURCE -mpreferred-stack-boundary=6 -fomit-frame-pointer -fno-tree-vectorize -fvisibility=hidden  -c my_encoder.c -o my_encoder.o
	gcc -o my_encoder  my_encoder.o ../x264-master/my_install/lib/libx264.a -m64  -lm -lpthread -ldl

#my_encoder.c  demo文件
#../x264-master/my_install/lib/libx264.a lib264库

上一篇:HDU 3688 Searchlights(并查集)


下一篇:IBatis.Net XML文件配置