x264命令行工具(x264cli)源码分析

目录

  1. 参考
  2. 使用说明
  3. x264cli框架
  4. x264cli内部流程
  5. x264cli中的一些结构体

1. 参考

2. 使用说明

x264的使用方式可以通过帮助文档来了解,x264的help信息的一部分如下,省略了很多参数说明的部分。

x264 core:155
Syntax: x264 [options] -o outfile infile

Infile can be raw (in which case resolution is required),
  or YUV4MPEG (*.y4m),
  or Avisynth if compiled with support (yes).
  or libav* formats if compiled with lavf support (no) or ffms support (no).
Outfile type is selected by filename:
 .264 -> Raw bytestream
 .mkv -> Matroska
 .flv -> Flash Video
 .mp4 -> MP4 if compiled with GPAC or L-SMASH support (no)
Output bit depth: 8/10
.
Options:

  -h, --help                  List basic options
      --longhelp              List more options
      --fullhelp              List all options

Example usage:

      Constant quality mode:
            x264 --crf 24 -o <output> <input>

      Two-pass with a bitrate of 1000kbps:
            x264 --pass 1 --bitrate 1000 -o <output> <input>
            x264 --pass 2 --bitrate 1000 -o <output> <input>

      Lossless:
            x264 --qp 0 -o <output> <input>

      Maximum PSNR at the cost of speed and visual quality:
            x264 --preset placebo --tune psnr -o <output> <input>

      Constant bitrate at 1000kbps with a 2 second-buffer:
            x264 --vbv-bufsize 2000 --bitrate 1000 -o <output> <input>
......

语法很简单:x264 [options] -o outfile infile
示例:

x264 --input-res 1280x720 -o julin_3s_x264.flv julin_3s.yuv

支持输入文件的格式:

  1. yuv的原始数据格式,需要指定视频的宽/高,如--input-res 1280x720
  2. YUV4MPEG (*.y4m)格式。
  3. Avisynth(avs),如果编译支持。之前没听过这个软件,不太了解。
  4. libav*支持的format,如果编译有lavf或者ffms的支持。lavf指的是FFmpeg,ffms是一个基于FFmpeg和Avisynth/VapourSynth插件的库,方便对帧的精确访问 [2]。

支持输出文件的格式:.h264.mkv.flv.mp4(需要引入外部库GPAC[4]或L-SMASH[5])

帮助文档中提供的几个使用的示例。各参数的使用具体看各参数的说明。

  • [options]可以分为两种,一种是带参数值的,一种是不带参数值的。

3. x264命令行工具(x264_cli)框架

简要画了一下x264命令行工具(x264_cli)的框架,大致如下图。


x264_cli.png
  • main:程序的主流程,主要包括参数解析、解封装、编码、封装和其他一些信息的输出流程。
  • opt:解析命令行参数的工具。
  • help:打印帮助信息。
  • version:打印版本信息。
  • demuxer:解封装器,支持的输入格式的解封装。
  • muxer:封装器,编码后的H.264封装到相应的输入格式。
  • filter:对输入数据应用的filter,比如crop(裁剪)等。
  • libx264:进行H.264编码。

demuxer

x264_demuxer.png

  • 内部支持yuv和y4m的输入格式。
  • 其他的输入格式可编译加入外部模块如libav、libavsynth和libffms2来支持,lavf、avs、ffms为对应外部模块的一个包装。

muxer

x264_muxer.png

  • 内部支持h264裸流、flv、mkv的输入格式。
  • mp4格式可编译加入外部模块libgpac[4]或liblsmash[5]来支持,mp4为对应外部模块的包装。

4. x264_cli内部流程

内部流程可分三个阶段:

x264_main.png
  1. parse:对应x264.c中的parse()函数,解析参数和打开输入输出文件。
  2. encode:对应x264.c的encode()的函数,H.264编码的阶段。
  3. close:关闭/释放打开的一些资源。

4.1 parse阶段

parse阶段的主要流程见下面的时序图:


x264_cli_parse.png

说明:

  • 虚线箭头表示在某些情况下会执行。
  • x264_param_default():设置默认参数。
  • getopt_long():解析输入的参数。根据解析的参数会对x264_param_tcli_input_opt_tcli_output_opt_tcli_opt_t这些结构体进行设置。
  • x264_param_default_preset():根据参数preset或tune,设置对应的编码参数,在设置其他传入的编码参数之前。
    • 先根据preset设置各种调谐好的编码参数(preset分各种速度档位,用于调节编码速度和质量的平衡)。
    • 再根据tune设置各种调谐好的编码参数(tune为不同视频类型和视觉优化的参数)。
  • x264_param_apply_fastfirstpass():如果设置了first-pass模式(rc.b_stat_read == 0, rc.b_stat_write == 1),修改编码器设置,以禁用通常在第一趟编码无用的选项。
  • x264_param_apply_profile():根据profile设置对应配置的编码参数。
  • select_output():根据文件的扩展名和编译支持的三方库选择对应的封装器(使用结构体cli_output_t定义)。
  • cli_output_t.openfile(): 使用选择的封装器打开输出文件。封装器的上下文信息保存在cli_opt_t.hout,由于不同的封装器的上下文信息不一样,这是一个(void *)类型。
  • select_input():根据文件的扩展名和和编译支持的三方库选择对应的解封装器(结构体cli_input_t定义)。
  • cli_input_t.openfile(): 使用选择的解封装器打开输出文件,读取header信息,获取的一些信息会保存在video_info_t结构体中。解封装器的上下文信息保存在cli_opt_t.hin
  • init_vid_filters():初始化filter,其中有名为"source"的filter,里面包装了上面已经选择好的解封装器。这些filter用于对输入数据做一些处理。

4.2 encode阶段

parse阶段的主要流程见下面的时序图:


x264_cli_encode.png

说明:

  • x264_encoder_open():打开x264编码器,编码参数由x264_param_t传入,编码器内部会拷贝这些参数,编码器内部使用的参数集与打开编码器时传入的x264_param_t可能会有不同。
  • x264_encoder_parameters():获得编码器内部使用的参数集x264_param_t。
  • cli_output_t.set_param():封装器根据x264_param_t设定封装格式需要的一些参数。
  • x264_encoder_headers():适用于mp4、flv等封装格式中的AVCC格式H.264码流。编码SPS/PPS/SEI的NALU,添加到整个H.264码流前面。
  • 进入一个循环中进行一帧一帧的将YUV编码为H.264。
    1. cli_vid_filter_t.get_frame():从输入的获取一帧YUV数据,存放在cli_pic_t结构体。
    2. x264_picture_init():初始化x264_picture_t结构体。
    3. 设置待编码数据buffer信息和pts:把cli_pic_t待编码数据buffer的地址复制给x264_picture_t,计算pts然后设置给x264_picture_t
    4. x264_encoder_encode():完成编码工作。
    5. cli_output_t.write_frame():封装器写入一帧数据。
    6. cli_vid_filter_t.release_frame():释放刚才存储YUV数据的buffer。
  • fush encoder:flush编码器,获取内部缓存的编码数据。
  • x264_encoder_close:关闭x264编码器。
  • cli_output_t.close_file: 一些封装器会根据传入的largest_pts和second_largest_pts 来修改total_duration信息。

4.3 close阶段

close阶段很简单

    if( filter.free )
        filter.free( opt.hin );
    else if( opt.hin )
        cli_input.close_file( opt.hin );
    if( opt.hout )
        cli_output.close_file( opt.hout, 0, 0 );
    if( opt.tcfile_out )
        fclose( opt.tcfile_out );
    if( opt.qpfile )
        fclose( opt.qpfile );
  • 关闭各种打开的资源。
  • cli_output.close_file:当parse出现错误没有进入encode阶段时会走到这里。

5. x264cli中的一些结构体

libx264中定义的:

x264_param_t //配置编码器使用的各种参数
x264_picture_t  //存放待编码的YUV数据
x264_nal_t  //存放已编码的H.264的NAL数据

x264cli中定义的:

cli_opt_t //x264cli的一些上下文信息
cli_pic_t  //保存从输入获取的YUV数据
cli_input_t  //处理输入的解封装器
cli_output_t  //处理输出的封装器
cli_vid_filter_t  //对输入数据做一些处理如crop的filter
video_info_t  //保存从解封装获取到的一些视频信息,filter中会使用。

5.1 cli_opt_t

typedef struct {
    int b_progress; //是否显示编码进度。默认开启,使用--no-progress来关闭。
    int i_seek;  //第一个要编码的帧,使用--seek <integer>设置。
    hnd_t hin; //输入使用的解封装器的上下文信息。
    hnd_t hout;  //输出使用的封装器的上下文信息。
    FILE *qpfile; //对某些或所有帧强制帧类型和QP值的配置文件,使用说明参看--qpfile。
    FILE *tcfile_out;//输出每一帧的pts到tcfile_out文件,使用--tcfile-out <string>指定文件。
    double timebase_convert_multiplier; //timebase转换倍数,使用--timebase选项指定timebase时使用。
    int i_pulldown; //使用--pulldown <string>选项来改变帧率时使用。
} cli_opt_t;

5.2 cli_pic_t

/* image data type used by x264cli */
typedef struct
{
    int     csp;       /* colorspace */
    int     width;     /* width of the picture */
    int     height;    /* height of the picture */
    int     planes;    /* number of planes */
    uint8_t *plane[4]; /* pointers for each plane */
    int     stride[4]; /* strides for each plane */
} cli_image_t;

typedef struct
{
    cli_image_t img;
    int64_t pts;       /* input pts */
    int64_t duration;  /* frame duration - used for vfr */
    void    *opaque;   /* opaque handle */
} cli_pic_t;

5.3 cli_input_t

typedef struct
{
    int (*open_file)( char *psz_filename, hnd_t *p_handle, video_info_t *info, cli_input_opt_t *opt );
    int (*picture_alloc)( cli_pic_t *pic, hnd_t handle, int csp, int width, int height );
    int (*read_frame)( cli_pic_t *pic, hnd_t handle, int i_frame );
    int (*release_frame)( cli_pic_t *pic, hnd_t handle );
    void (*picture_clean)( cli_pic_t *pic, hnd_t handle );
    int (*close_file)( hnd_t handle );
} cli_input_t;
  • 输入使用的解装器的接口定义。
  • open_file/close_file:输入文件的打开/关闭。此外,open_file中进行一些初始化的工作和获取一些header信息,close_file中进行一些销毁工作。
  • picture_alloc/picture_clean:分配/释放存储待编码的视频数据的数据结构cli_pic_t(有个地方没明白,为什么lavf、ffms和avs中的picture_clean函数没有去释放cli_pic_t的内存,只是把cli_pic_t设置为了0)
  • read_frame:读取一帧YUV数据。
  • release_frame:在cli_pic_t使用mmap映射的内存和avs中需要做一些释放操作。

5.4 cli_output_t

typedef struct
{
    int use_dts_compress;
} cli_output_opt_t;

typedef struct
{
    int (*open_file)( char *psz_filename, hnd_t *p_handle, cli_output_opt_t *opt );
    int (*set_param)( hnd_t handle, x264_param_t *p_param );
    int (*write_headers)( hnd_t handle, x264_nal_t *p_nal );
    int (*write_frame)( hnd_t handle, uint8_t *p_nal, int i_size, x264_picture_t *p_picture );
    int (*close_file)( hnd_t handle, int64_t largest_pts, int64_t second_largest_pts );
} cli_output_t;
  • 输出使用的封装器的接口定义。
  • open_file/close_file:输出文件的打开/关闭。此外,open_file中进行一些初始化工作,close_file中写入一些元数据和进行一些销毁工作。
  • picture_alloc/picture_clean:分配/释放存储待编码的视频数据的数据结构cli_pic_t(有个地方没明白,为什么lavf、ffms和avs中的picture_clean函数没有去释放cli_pic_t的内存,只是把cli_pic_t设置为了0)
  • set_param:告知封装器编码器使用的参数信息,用于设置一些视频元数据。
  • write_headers:写H.264的头信息(SPS/PPS/SEI的NALU数据),适用于mp4、flv等封装格式中的AVCC格式H.264码流。
  • write_frame:写一帧已编码的数据。

5.5 cli_vid_filter_t

struct cli_vid_filter_t
{
    /* name of the filter */
    const char *name;
    /* help: a short message on what the filter does and how to use it.
     * this should only be implemented by filters directly accessible by the user */
    void (*help)( int longhelp );
    /* init: initializes the filter given the input clip properties and parameter to adjust them as necessary
     * with the given options provided by the user.
     * returns 0 on success, nonzero on error. */
    int (*init)( hnd_t *handle, cli_vid_filter_t *filter, video_info_t *info, x264_param_t *param, char *opt_string );
    /* get_frame: given the storage for the output frame and desired frame number, generate the frame accordingly.
     * the image data returned by get_frame should be treated as const and not be altered.
     * returns 0 on success, nonzero on error. */
    int (*get_frame)( hnd_t handle, cli_pic_t *output, int frame );
    /* release_frame: frame is done being used and is signaled for cleanup.
     * returns 0 on succeess, nonzero on error. */
    int (*release_frame)( hnd_t handle, cli_pic_t *pic, int frame );
    /* free: run filter cleanup procedures. */
    void (*free)( hnd_t handle );
    /* next registered filter, unused by filters themselves */
    cli_vid_filter_t *next;
};
  • init:使用输入的剪辑属性和参数初始化filter。
  • get_frame:给定输出帧的存储空间和期望的帧序号,相应地生成帧。返回的图像数据应该作为const处理,不应该被修改。
  • release_frame:帧使用完成之后出清除信号。
  • free:执行filter的清理程序。

5.6 video_info_t

/* properties of the source given by the demuxer */
typedef struct
{
    int csp;         /* colorspace of the input */
    uint32_t fps_num;
    uint32_t fps_den;
    int fullrange;   /* has 2^bit_depth-1 instead of 219*2^(bit_depth-8) ranges (YUV only) */
    int width;
    int height;
    int interlaced;
    int num_frames;
    uint32_t sar_width;
    uint32_t sar_height;
    int tff;
    int thread_safe; /* demuxer is thread_input safe */
    uint32_t timebase_num;
    uint32_t timebase_den;
    int vfr;
} video_info_t;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353