在标题加了个后缀-流媒体。所以主要是借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( ¶m, "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",也是有缓存,延时输出:
比如上面,一直到输入12帧之后才输出。
开启 "zerolatency" 之后:
这才是我们流媒体实时性要求的。
可以看到我这样的硬件环境,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( ¶m, "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( ¶m, "ultrafast", "zerolatency" ) < 0 )
//if( x264_param_default_preset( ¶m, "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( ¶m, "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( ¶m );
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库