x265笔记_3_并行处理机制

HEVC中的并行处理技术

功能并行和数据并行

此处只讨论多处理器的并行技术,可分为功能并行和数据并行:

  1. 功能并行,指将不同的功能模块划分给不同的运算单元,因此又叫”流水线级并行“,这种方法充分利用了功能模块的时间并行性,适合硬件实现,缺点是容易产生载荷失衡的问题,而且拓展性较差;
  2. 数据并行,把信息划分为相互独立的部分,每一部分交给不同的运算单元完成,该种方法具备较好的拓展性,而且如果保证数据单元数大于运算单元数,比较容易达到负载均衡,但是有时候难以保证数据之间的独立性,不得不要进行核间通信来消除数据单元间的依赖性。

在这里插入图片描述

HEVC解码端的并行处理框架

因为熵解码在Slice、Tile和CTB行的起始处都有可能进行概率模型初始化,而且不同模型的初始化位置不一定相同,正是因为熵解码之间的依赖关系和后续解码模块的依赖关系不一致,所以需要两个功能并行模块,即熵编码和其余模块解码。

在每个功能模块中使用数据并行分为更多并行模块,每个运算单元负责其中一个模块:

在这里插入图片描述

HEVC中编码单元数据之间的依赖关系

HEVC中编码单元数据的依赖关系来源于帧内预测、帧间预测、去方块滤波和样点自适应补偿(SAO)等过程,因为编码/解码当前块必须知道其参考块的编码/解码信息。

四种方式的参考信息如下所示:

在这里插入图片描述

HEVC编解码的并行策略

这里其实是很灵活的,可以分为GOP级别、图像级别、Slice级别、Tile级别 和 CTB级别的并行,主要介绍Tile级别的并行策略和CTB级别的并行策略。

Tile级别的并行策略

Tile是H265新增加的数据单元,将原来的图像分为一个个独立的矩形区域,各区域独立进行编码,不会相互参考,很适合于并行化处理。(补充:Tile与之前的Slice划分相互独立,但是要保证一个Tile不会跨过两个Slice,或一个Slice不会跨过两个Tile。而且当多个Tile共用一个Slice的时候,可以公用同一个Slice头,从而可以节省码率。)

Tile中的CTB是按照光栅扫描的顺序进行扫描的,而Tile之间的排序也是按照光栅扫面的顺序排列的,如下图所示:
在这里插入图片描述

可见与Slice的条状相比,Tile的矩形表达提高了内部像素的相关性,而且减少了由于运动预测所需要的缓冲数量(即要缓存的运动搜索范围相比Slice的实现要更小了),但是过多的Tile势必降低率失真性能,因为Tile边界附近的信息被破坏了,下面的波前并行处理算法很好的解决了这个问题。

值得说明的是,虽然Tile之间是相互独立的,而且某些熵编码会在Tile的结尾进行上下文模型的更新,但是去方块滤波和样点自适应补偿仍然可能会跨过Tile边界,此时还是需要额外的核间通信

CTB级别的并行策略/波前并行处理算法(WPP)

波前并行处理算法,可以允许多个CTB同时处理,而且后一行的处理要比前一行滞后两个CTB,这样可以在不破坏边界相关性的前提下进行并行编码。该算法的处理过程如下图所示:

在这里插入图片描述

人们还提出了 依赖片 的数据单元,即将一个完整的Slice划分为不同的区域,分别封装到不同的独立NAL中,这些区域可能是相互关联的,因此被称为依赖片。

对应到该算法,可以将一段用波前并行处理的 CTB行 数据或者Tile 数据打包到一个单独的NAL中,这个单元就被称为依赖片。如下图所示

在这里插入图片描述

这样的好处是,各个NAL虽然是相互依赖的,但是NAL2不用等待NAL1解码完成再解码,而是可以在NAL1解码的同时进行解码,只需要保证落后两个CTB即可,这样可以大大减少解码时延。

坏处就是,码率会十分不平衡,尤其是当上方的运算单元已经结束,但是下方仍然没有结束,此时码率会逐渐降低,这是我们不希望看到的。于是人们提出"重叠波前并行算法",当上方部分运算单元结束之后,会自动编码下一张图片,而不会继续等待,如下图所示,T1~T4会继续编码之后的帧:

在这里插入图片描述

造成的问题是未编码的像素块的运动矢量要尽可能小,在上面的例子中,运动矢量不能大于4。

x265多线程实现原理

main()函数主要调用了encoder_open()encoder_headers()encoder_encode()encoder_close(),其中

  1. encoder_open()除了打印配置信息,还调用了encoder_create()函数(实际调用了Encoder::create()),完成了 等待线程的初始化,并进入threadMain()函数中触发等待线程m_done.wait(),该事件标志线程初始化;
  2. encoder_encode()如上个博客所说,调用了Encoder::encode()函数,在encoder中进一步调用了FrameEncoder::startCompressFrame(),在startCompressFrame中触发线程m_done.trigger,表示进入threadMain()中。
  3. 该线程接下来调用了compressSliceencodeCTUencodeCUfinishCU完成编码

多线程算法流程图

Encoder::create()

Encoder::create()主要用于检查线程池以及可用的线程数目,若符合线程使用条件则调用threadMain()函数。

调用语句为

for (int i = 0; i < m_param->frameNumThreads; i++)
{
    m_frameEncoder[i]->start();
    // wait for thread to initialize
    m_frameEncoder[i]->m_done.wait(); 
}

FrameEncoder::threadMain()

这里的FrameEncoder::threadMain()相当于线程的main函数,主要功能是线程触发后等待处理的过程,主要调用了compressFrame()函数。

如果当前线程池不为空,调用m_pool->setCurrentThreadAffinity()设置当前线程m_tld,如果线程池不为空,创建新的线程m_tld

触发两个事件:m_done.trigger(),线程已初始化完成的信号;m_enable.wait(),等待Encoder::encoder()唤醒。

总结如下:
在这里插入图片描述

Reference

  1. 万帅《新一代高效视频编码:原理、标准与实现》
  2. x265探索与研究 之 x265多线程

视频编码:原理、标准与实现》
2. x265探索与研究 之 x265多线程

  1. x265的线程模型
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值