AVS3编码过程全理解

前言

本博客基于笔者对于AVS3-HPM15.5代码的个人理解整理而得,内容如有不妥之处,欢迎批评指正。
tips: 内容比较多,会不断更新

若干结构体的理解

编码上下文ENC_CTX

Function: 编码过程最核心的结构体,编码过程所用的上下文(context),其中记录了编码过程所需的所有内容
编码上下文和编码器实例是两个等价的概念
逻辑: ENC_CTX ctx记录了当前编码器所需要用的所有内容,并会实时更新。
ctx的内容先由用户输入的命令参数以及配置文件进行初始化设置。
在编码一帧之前,编码器会调用enc_pic_prepare函数对ctx的内容进行初始化、更新,将其中记录的内容更新为当前帧的内容,如有必要,会将ctx中的相应内容赋值给一些局部变量(e.g. pic_header)

struct _ENC_CTX
{
    COM_INFO              info; //* 关于当前图像的公共信息(common info) 结构体,记录了一些基本的信息(e.g. 序列头,图像头,图像的宽度、高度)
    /* address of current input picture, ref_picture  buffer structure */
    ENC_PICO            *pico_buf[ENC_MAX_INBUF_CNT]; //* pico_buf 表示所有原始图像的缓冲区,pico_buf[i]表示第i个原始图像的缓冲区结构体
    /* address of current input picture buffer structure */
    ENC_PICO            *pico; //* pico 表示当前帧,指向一个具体的原始图像缓冲区结构体
    /* index of current input picture buffer in pico_buf[] */
    u32                    pico_idx; //* 当前帧在pico_buf数组中的索引,由pic_cnt通过一定的方式计算得到
    int                    pico_max_cnt; //* 会被初始化为: ctx->pico_max_cnt = 1 + (ctx->param.max_b_frames << 1); 且初始化完成之后就不再发生变化,理解成常值
    /* magic code */
    u32                    magic;
    /* ENC identifier */
    ENC                  id;
    /* address of core structure */
    ENC_CORE             *core;
    /* address indicating original picture and reconstructed picture */
    COM_PIC              *pic[PIC_BUF_NUM]; //* pic用于存储当前帧的原始图像或者重建图像
    /* reference picture (0: foward, 1: backward) */
    COM_REFP              refp[MAX_NUM_REF_PICS][REFP_NUM];
    /* encoding parameter */
    ENC_PARAM             param; //* 编码参数,这一部分参数是由用户输入的,基本上在整个编码过程中保持不变
    /* bitstream structure */
    COM_BSW                bs; //* 编码器的比特流结构体
    /* bitstream structure for RDO */
    COM_BSW                bs_temp;

    /* reference picture manager */
    COM_PM                 rpm; //* 编码器的参考帧管理对象rpm

    /* current encoding picture count(This is not PicNum or FrameNum.
    Just count of encoded picture correctly) */
    u32                    pic_cnt; //* 表示当前帧的编码顺序索引(i.e. 解码顺序索引DOI), pic_cnt的值每次加一
    /* current picture input count (only update when CTX0) */
    int                    pic_icnt; //* 表示当前帧的编码顺序索引(i.e. 解码顺序索引DOI), 是由pic_cnt简单计算得到的
    /* total input picture count (only used for bumping process) */
    u32                    pic_ticnt;
    /* remaining pictures is encoded to p or b slice (only used for bumping process) */
    u8                     force_slice;
    /* ignored pictures for force slice count (unavailable pictures cnt in gop,\
    only used for bumping process) */
    int                     force_ignored_cnt;
    /* initial frame return number(delayed input count) due to B picture or Forecast */
    int                    frm_rnum;
    /* current encoding slice number in one picture */
    int                    slice_num;
    /* first mb number of current encoding slice in one picture */
    int                    sl_first_mb;
    /* current slice type */
    u8                     slice_type;
    /* slice depth for current picture */
    u8                     slice_depth;
    /* current picture POC number */
    int                    poc; //* 当前帧的POI/POC值(显示顺序索引),由pic_cnt通过一定的方式计算得到

AVS3码流结构

注意: AVS3使用了REPEAT_SEQ_HEADER(重复序列头)技术,在每一个I帧之前,都需要有一个序列头.
单视点码流结构:码流结构: [序列头, 第0帧图像头,第0帧内容,第1帧图像头,第1帧内容,…, 第x帧图像头,第x帧内容,序列结束符]

多视点码流结构: [视点0的序列头, 视点0的第0帧图像头,视点0的第0帧内容,视点1的序列头,视点1的第0帧的图像头,视点1的第0帧内容,视点2的序列头,视点2的第0帧的图像头,视点2的第0帧的内容,视点0的第1帧图像头,视点0的第1帧内容,视点1的第1帧的图像头,视点1的第1帧内容,…, 视点0的第x帧图像头,视点0的第x帧内容,视点0的序列结束符,视点1的第x帧图像头,视点1的第x帧内容,视点1的序列结束符,视点2的第x帧图像头,视点2的第x帧内容,视点2的序列结束符,]

编码器状态

在整个编码过程中,定义了几个编码器所在的状态:

  • STATE_ENCODING: 编码状态/读取状态,表示此时需要从输入视频文件中读取当前帧
  • STATE_BUMPING:输出状态,此时所有帧都已经被成功读取了,接下来要按照编码顺序依次对每一个帧进行编码
  • STATE_SKIPPING: 跳帧状态
  • STATE_LIB_SKIPPING: 知识图像跳帧状态

在编码器的主函数中,会维护一个状态变量state, 表示当时编码器所处在的状态.

编码过程

编码过程的整体流程

首先,先从用户的输入中初始化编码器的参数,全都存放在ctx中。然后从输入视频文件中依次读取每一帧(此顺序即为"显示顺序"POI/POC),由于B帧的存在,并不是每读取一帧就可以立刻对当前帧进行编码的。因此要先将一些已经读取到了但还不能编码的帧存下来,存放在ctx->pico_buf这个数组中。将视频文件中所有的帧全都读取完毕之后,然后按照一定的顺序(编码顺序/解码顺序DOI)对于这些帧依次进行编码,每编码一帧,就输出这个编码帧以及其对应的重建帧的一些信息(e.g. PSNR值)

编码过程的详细流程

编码器的输入: 用户输入的命令行参数+配置文件cfg
参数解析: 先用com_args_parse_all函数读取用户输入的命令行参数+配置文件cfg,来设定基本的编码参数,存放到options中
通过get_conf(&param_input),将options中的编码参数赋值给param_input.
编码器实例/编码上下文的创建: id = enc_create(&param_input, NULL),根据编码器结构体参数param_input创建一个编码器上下文 ctx, 用于记录编码过程所需要的所有内容,并将这个ctx作为编码器实例返回.
至此,id 表示一个编码器实例,也即一个编码上下文ctx.
输出配置信息: print_config(id, param_input); //* 向stdout输出一些基本的配置信息, print_stat_init(); //向stdout输出一些基本的统计信息
位流缓冲区初始化: 接着给比特流缓冲区结构体bitb进行简单的初始化,先申请bs_buf, bs_buf2都是一段长为MAX_BS_BUF的空间,让bitb指向bs_buf, bs_buf2
初始化序列头: 用 ret = init_seq_header( (ENC_CTX *)id, &bitb); //初始化序列头,对编码器上下文ctx中的ctx->info.sqh进行初始化,设置一些基本的初始值
依次编码每一个帧: 下面执行一个while(1)的死循环, 这里先简单介绍这个while(1)死循环的高层次逻辑:

先根据当前编码器所处在的状态state进行相应处理:
● state是跳跃状态时的处理方案
● state是编码状态/读取状态时的处理方案
然后执行ret=enc_encode()函数根据编码器当前的状态判断当前编码器的编码顺序时间戳ctx->pic_icnt所指向的那个帧是否可以被编码,如果可以编码,就完成对于当前帧的编码。
根据返回值ret来判断下一步的处理方案(e.g. 状态切换,退出循环,输出当前帧的psnr值 .etc)

退出这个while(1) 死循环后,至此,编码器已经完成了所有帧的编码工作, 之后代码是用于输出一些统计信息
释放申请的空间

enc_encode函数

作用: 编码一帧,并将这一帧的编码内容写入到比特流ctx->bs中+更新统计信息
先用ret = enc_pic_prepare(ctx, bitb); // 在编码一帧图像之前对编码上下文ctx和比特流bitb做一下基本的初始化工作,具体来说,先为重建帧申请空间,然后确定用于确定当前帧是谁,以及当前帧的类型(I帧还是B帧),当前帧是ctx->pico,也是ctx->pic[0],ctx->pico_idx是当前帧在pico_buf数组中的索引, 初始化比特流结构体对象 ctx->bs
enc_pic_prepare
PIC_REC(ctx) = com_picman_get_empty_pic(&ctx->rpm, &ret);//* 为解码一帧图像申请缓冲区
//* 至此,ctx->pic[1] 可以用于存放当前帧的重建帧(尽管还没成功重建,但是先把空间给开辟出来了)
然后用decide_slice_type(ctx); //* 用于确定当前帧是谁,以及当前帧的类型(I帧还是B帧);当前帧是ctx->pico,也是ctx->pic[0],ctx->pico_idx是当前帧在pico_buf数组中的索引
ctx->lcu_cnt = ctx->info.f_lcu; //* 记录当前帧中待编码的LCU的数目
com_bsw_init(&ctx->bs, bitb->addr, bitb->addr2, bitb->bsize, NULL); //* 对比特流结构体对象 bs 进行一些初始化, 将bs中的缓冲器buffer清空,bs中比特流的起始地址为bitb->addr, 终止地址为bitb->addr+bitb->bsize-1.
//* 至此,当前帧是ctx->pic[0], ctx->pico_idx是当前帧在pico_buf数组中的索引,
然后调用ret = enc_pic(ctx, bitb, stat);编码一帧,并将这一帧的编码内容写入到比特流ctx->bs中
然后,enc_pic_finish(ctx, bitb, stat); //* 结束一帧图像的编码,更新ctc,bitb,stat中的一些信息,并将当前帧添加到参考帧管理结构体ctx->rpm->pic(DPB)中
然后就结束enc_encode函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值