vp9 segment 详细分析

本文详细介绍了VP9编码器中的Segment特性,它允许对画面进行精细化区域划分,每个Segment可以独立设置QP、Loop Filter强度、参考帧及跳过编码等参数,以适应不同的画质需求和应用场景。通过时域预测和直接编码两种方式更新Segment Map,并在解码过程中动态调整,优化码率控制和画质表现。
摘要由CSDN通过智能技术生成

 

参考资料:

vp9提供了一种新的划分称为segment,tile也好,hevc的slice也好,都是画面内按顺序的区域划分,但segment不是,所以segment的标记是对每个处理块标记一个id,画面最多可被划分为八个segment。每个segment可拥有四种能力:

-SEG_LVL_ALT_Q:设定自己的qp,应用场景如实现ROI,或者进行码率控制等,后面会再提到因为vp9不对每个block设定qp,所以画面内的画质调制啊码率控制啊就可以通过这个功能来实现。

-SEG_LVL_ALT_LF:设定自己的loop filter强度,应用场景如画面中有一部分水面等特别容易blocky的部分,就可以仅对这样的部分增强delocking filter,而不影响其他的部分。

-SEG_LVL_REF_FRAME:设定自己的参考帧,应用场景如视频会议中背景一般是不变的,就可以设定背景区域都去参考固定的参考帧。

-SEG_LVL_SKIP:segment skip(即不传送coeff),应用场景如背景等画面中静止部分,就可以全部skip节省码流,特别是在低码率的情况下比较有帮助。

    所以这种segment的划分并不是为了并行或容错,更多的是为了满足不同应用场景下对画质调试的需求。到qp/loop filter部分会具体提到,hevc的这些部分都是可以对每个处理块来控制的,所以可以实现精细调整。只不过二者实现方式不同,也各有优势吧。

        VP9 引入了一个更加先进的分段( segmentation)的概念,使编码器更加高效。 一帧图像中的每个 16×16 的宏块都有一个段标识( segment_id)来区分所属的段。 位于同一个段的宏块在参考帧、量化参数、滤波强度等参数上的处理是相同的, 这样能更好的避免冗余和应对不同的应用需要。例如在一帧中的背景区域可以采 用较小的量化参数以达到较好的编码质量,这样在编码后续帧时,背景区域通过 帧间预测可以达到更精确的预测效果,从而可以节约出更多的码率用于编码前景。

 

结合代码的理解

1、segment 是vp9中将一帧有相同参考帧、量化参数、滤波强度等参数的宏块进行统一管理的方式,segment最多有8个,每个segment 用segment id进行统一管理更新。每个segment的数据可以在帧级别进行单独更新,如果某一帧没有更新segment的数据,那么他用的就是前一帧保存使用的。除了I帧 全I块的帧,以及跟之前完全没关系的帧,这些的帧的seg 值都重置到默认。

2、segment map 也就是segment id 和参数直接的存储关系,可以通过两种方式进行获取,一种是时域编码,也就是通过上一帧的segmap预测当前的segmap,一种是直接编码,直接在码流中传递segment的信息,这个是通过temporal_update 这个码流元素来区别,为1时采用时域编码方式,否则直接传递。这种在帧级别就定义,也就是一整帧采用的是同一种方式。

3、update_map 是否更新segment map, 为1更新,否则不更新,这个也是在帧级别进行定义的。

4、c last_seg_map cur_seg_map: last_seg_map:上一帧的seg_map, 跟随着解码一直同步进行更新的。 cur_seg_map: 如果没有更新就用上一帧的。

代码中有关segment id的管理

1、 初始context buffer的申请

在每一帧启动解码之前 会去分配一些需要的buffer,在vp9_alloc_context_buffers中

  if (cm->seg_map_alloc_size < cm->mi_rows * cm->mi_cols) {

    // Create the segmentation map structure and set to 0.

    free_seg_map(cm);

    if (alloc_seg_map(cm, cm->mi_rows * cm->mi_cols))

      goto fail;

  }

如果当前cm->mi_rows*cm->mi_cols的大小大于上一帧的,那么释放掉cm->seg_map_array[i],重新申请cm->mi_rows*cm->mi_cols 大小的seg_map_array。其中数组大小总共为2, 其中current_frame_seg_map默认为0, last_frame_seg_map为1。

2、 I帧 和 错误隔离相关的处理

  if (frame_is_intra_only(cm) || cm->error_resilient_mode)

    vp9_setup_past_independence(cm);

其中vp9_setup_past_independence 的处理

  if (cm->last_frame_seg_map && !cm->frame_parallel_decode)

    memset(cm->last_frame_seg_map, 0, (cm->mi_rows * cm->mi_cols));

  if (cm->current_frame_seg_map)

    memset(cm->current_frame_seg_map, 0, (cm->mi_rows * cm->mi_cols));

可以看到在I帧或者是错误隔离的模式时,会重新初始化context、segment id 相关的结构体。segment 相关的是将last 和current 重新初始化为0。 其中>error_resilient_mode容错模式允许独立于先前帧进行当前帧的语法元素的解码

3、 buffer的乒乓

在1 中 申请了两个buffer来存储当前和上一帧的。 在解码结束之后。

  if (!cm->show_existing_frame) {

    cm->last_show_frame = cm->show_frame;

    cm->prev_frame = cm->cur_frame;

    if (cm->seg.enabled && !pbi->frame_parallel_decode)

      vp9_swap_current_and_last_seg_map(cm);

  }

vp9_swap_current_and_last_seg_map中将当前帧指向last, 将last指向当前帧。

4、segmen id的使用和更新

    在read_inter_frame_mode_info 和read_intra_segment_id中会把相关的segmentid从码流中读取处理。

read_intra_segment_id: 中seg->enabled 为0 的时候, segment id返回0, seg->update_map 为 0 时,将上一帧的last segment id 复制到当前的 curent segment id 里面。

但上述的两个标志都为1的时候,从码流中读出 并赋值到current_seg_map里面。

    read_inter_segment_id: 中seg->enabled 为0 的时候, segment id返回0。否则 dec_get_segment_id 将last_seg_map的segment id 取出来(是遍历整个宏块 然后取最小的segmentid 来使用) 放到predicted_segment_id。 这个时候并没有去更新current_seg_map。当seg->update_map 根据不同的预测方式 来更新当前的current_seg_map。

    segment id 读上来之后,就会根据这个segment id  去读取想对应的帧间 帧内预测的一些参数来进行解码。

5、整体的流程

第一帧:

     首先申请存储segmap的数组,分别存放last seg map和 current  seg map。 然后根据码流中的enable和update_map 的标志位  进行current seg map 的更新,当enable 和update_map 都是1 的时候,current_seg_map 更新成码流中的值。 等一帧解码后,判断seg enable 的标志 为1 的情况下, 交换current 和 last的位置,也就是当前帧的current seg map要做为下一帧的last seg map。

第二帧:

      首先判断segmap 的空间要不要重新申请了,如果说图像宽高变大了。那么空间要重新申请, 这个时候上一帧的last_seg_map 就被清空了。没有last_seg_map 可以用。 然后判断是不是I帧和错误相关的,是的话,上一帧的last_seg_map 也要清空了。 都不是的情况下,会判断seg enable 和 seg update_map的标志,如果seg enable 为0,segid 为0, 如果update_map为0 ,segment id 用的是last seg map 中的id。否则用码流中current id 并更新current seg map。

     在libvpx 中, 只要seg是enable的时候,都会swap currnet_seg_map 和 last_seg_map,即使 当前currnet_seg_map没有更新,curent_seg_map没有更新的时候 current_seg_map 是等于last_seg_map的。

     在实际实现的时候,如果说当前curent_seg_map 没有更新,那么不会去swap current 和 last。只有清零 或者current seg map更新的时候才会去swap current 和last。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值