【H.264/AVC视频编解码技术详解】八、 熵编码算法(2):H.264中的熵编码基本方法、指数哥伦布编码

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 《H.264/AVC视频编解码技术详解》视频教程已经在“CSDN学院”上线,视频中详述了H.264的背景、标准协议和实现,并通过一个实战工程的形式对H.

《H.264/AVC视频编解码技术详解》视频教程已经在“CSDN学院”上线,视频中详述了H.264的背景、标准协议和实现,并通过一个实战工程的形式对H.264的标准进行解析和实现,欢迎观看!

“纸上得来终觉浅,绝知此事要躬行”,只有自己按照标准文档以代码的形式操作一遍,才能对视频压缩编码标准的思想和方法有足够深刻的理解和体会!

链接地址:H.264/AVC视频编解码技术详解

GitHub代码地址:点击这里

本节视频免费

1. H.264中的熵编码基本方法

在成功从NAL Unit中获取到语法元素的码流之后,接下来就是对语法元素的码流进行解析。根据我们在前面的博文中所讲述的H.264编码框架图,经过预测、变换量化等步骤后得到的H.264语法元素将通过熵编码器压缩为符合标准的H.264码流。因此,为了还原各个语法元素,必须对码流使用熵编码的解码器进行解码。

在H.264的标准协议中,不同的语法元素指定了不同的熵编码方法。在协议文档中共指定了10种语法元素的描述符,这些描述符表达了码流解析为语法元素值的方法,其中包含了H.264标准所支持的所有熵编码方法:

语法元素描述符 编码方法
b(8) 8位二进制比特位串,用于描述rbsp_byte()
f(n) n位固定模式比特位串,从最左bit开始计算
u(n) 使用n位无符号整数表示,由n位bit换算得到
i(n) 使用n位有符号整数表示,由n位bit换算得到
ue(v) 使用无符号指数哥伦布编码
se(v) 使用有符号指数哥伦布编码
te(v) 使用截断指数哥伦布编码
me(v) 使用映射指数哥伦布编码
ce(v) 上下文自适应的变长编码
ae(v) 上下文自适应的二进制算术编码

2. 指数哥伦布编码

同上篇介绍的哈夫曼编码一样,指数哥伦布编码同样属于变长编码(VLC)的一种。指数哥伦布编码同哈夫曼编码最显著的一点不同在于,哈弗曼编码构建完成后必须在传递的信息中加入码字和码元值的对应关系,也就是编码的码表,而指数哥伦布编码则不需要。

(1). 指数哥伦布编码的分类

如上表指出,常用的指数哥伦布编码通常可以分为四类:

  • ue(v):无符号指数哥伦布编码;
  • se(v):有符号指数哥伦布编码;
  • me(v):映射指数哥伦布编码;
  • te(v):截断指数哥伦布编码;

其中无符号指数哥伦布编码ue(v)是其他编码方式的基础,其余几种方法基本可以由ue(v)推导得出。本文先从ue(v)来讲述指数哥伦布算法的原理,而后再看如何推导至其他编码方法。

(2). 无符号指数哥伦布编码ue(v)

ue(v)的码字可以分为三个部分:[prefix] 1 [surfix]。其中前缀码为n个bit长度的0,后缀码为表示实际数值的信息位,信息位的长度为前缀码中0的个数。指数哥伦布编码中前缀和后缀部分的长度根据码元数值来确定:

0阶指数哥伦布编码模板 适用码元值
1 0
0 1 x 1, 2
0 0 1 x x 3~6
0 0 0 1 x x x 7~14
0 0 0 0 1 x x x x 15~30
0 0 0 0 0 1 x x x x x 31~62
…… ……

在上标中编码模板的后缀部分,xx以二进制的形式表示解码后的数值。前缀0的长度以LeadingZeroBits表示,那么解码后数值为:codeNum = 2^LeadingZeroBits - 1 + (xxx)。(xxx)为二进制数值xxx的10进制表示。因此,指数哥伦布编码的码字与码元值的对应关系如下表:

指数哥伦布编码码字 码元数值
1 0
0 1 0 1
0 1 1 2
0 0 1 0 0 3
0 0 1 0 1 4
0 0 1 1 0 5
0 0 1 1 1 6
0 0 0 1 0 0 0 7
…… ……

例如,当使用指数哥伦布编码来表示数值codeNum = 10,那么其前缀0的长度为prefixLen = floor[log2(codeNum+1)] = 3,因此指数哥伦布码的前缀为 0 0 0。其后缀部分的二进制表示为codeNum+1-2^prefixLen = 11-8 = 3 = b(0 1 1),因此10的指数哥伦布编码码字为0 0 0 1 0 1 1。

又例如,当读取到指数哥伦布码0 0 0 0 1 0 1 0 1时,首先计算前缀0的个数,此处为4,然后越过中间的1,读取后面的0 1 0 1为后缀码。二进制0101表示为十进制为5,因此该指数哥伦布码解码后的数值为2^4-1+5 = 20。

无符号指数哥伦布编码是其余多种变形算法的基础,其余的比如有符号指数哥伦布编码、映射指数哥伦布编码、截断指数哥伦布编码都是由无符号指数哥伦布编码进一步处理得到的。

(3). 有符号指数哥伦布编码

有符号的指数哥伦布编码值是通过无符号的指数哥伦布编码的值通过换算得到的,其语法元素描述符为se(v)。每一个无符号指数哥伦布编码的数值通过固定的换算关系转换为有符号的值,其换算关系为:n = (-1)^(k+1) * Ceil(k/2)。下表表示了有符号和无符号指数哥伦布编码之间的换算关系:

codeNum syntax element value
0 0
1 1
2 -1
3 2
4 -2
5 3
6 -3
k (-1)^(k+1)*Ceil(k/2)

(4). 截断指数哥伦布编码

截断指数哥伦布编码的语法元素描述符为te(v)。当语法元素以te(v)解码时,首先需要判断的是语法元素的取值范围,假定为[0, x], x≥1。根据x的取值情况,语法元素根据下面不同情况进行解析:

  • 若x>1,解析方法同ue(v)相同;
  • 若x=1,语法元素值等同于下一位bit值的取反。

(5). 映射指数哥伦布编码

映射指数哥伦布编码的描述符为me(v),适用于预测模式为Intra_4x4, Intra_8x8或Inter的宏块的coded_block_pattern的编码。me(v)的映射方式并无指定的换算公式,通常由查表的方式进行。下表为H.264 spec文档的表9-4的一部分:

三. 指数哥伦布编码同哈夫曼编码的比较

指数哥伦布编码同前文中提到的哈夫曼编码都遵循了同一规律,即针对不同的码元分配了bit位长度不同的码字,因此各自都属于变长编码的一种。然而二者仍然具有较大的差别,具体如:

  1. 哈夫曼编码在编码过程中考虑了信源各个符号的概率分布特性,根据符号的概率分布进行编码,因此对于不同的信源,即使是相同的符号的哈夫曼编码的结果也是不同的;指数哥伦布编码针对不同的信源采用的编码是统一的,因此无论是什么样的输入,输出的编码后的数据都是一致的。
  2. 由于哈夫曼编码是针对信源特性进行的编码,因此在存储或传输编码后的数据之前必须在前面保存一份码表供解码段重建原始信息使用;而指数哥伦布编码不需要存储任何额外信息就可以进行解码。
  3. 由于未考虑信源的实际特性,指数哥伦布编码的压缩比率通常比较低,对于有些信息甚至完全没有压缩效果,输出数据比原始数据更大,在这一点上哈夫曼编码作为“最优编码”在效率上更高;然而由于哈夫曼编码运算较指数哥伦布编码更为复杂,且必须保存码表信息增加了传输负荷,也对压缩比率造成了不利影响。

实际上,对于视频压缩这样的需求而言,类似于哈夫曼编码所提供的压缩比率的优势远远不够,而且像H.264等编码标准都不会指望靠这样的方式来提高压缩比率。因此在实际的视频编码方法中使用的是指数哥伦布编码,但是只作为少数的辅助语法元素的编码以及多数语法元素的二值化方法。真正贡献了高压缩比还需要后面详述的CAVLC和CABAC等。

四. 0阶无符号指数哥伦布编码的实现Demo

程序代码如下:

// ExpColum.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <assert.h>

typedef unsigned char UINT8;

static int get_bit_at_position(UINT8 *buf, UINT8 &bytePotion, UINT8 &bitPosition)
{
    UINT8 mask = 0, val = 0;

    mask = 1 << (7 - bitPosition);
    val = ((buf[bytePotion] & mask) != 0);
    if (++bitPosition > 7)
    {
        bytePotion++;
        bitPosition = 0;
    }

    return val;
}

static int get_uev_code_num(UINT8 *buf, UINT8 &bytePotion, UINT8 &bitPosition)
{
    assert(bitPosition < 8);
    UINT8 val = 0, prefixZeroCount = 0;
    int prefix = 0, surfix = 0;

    while (true)
    {
        val = get_bit_at_position(buf, bytePotion, bitPosition);
        if (val == 0)
        {
            prefixZeroCount++;
        }
        else
        {
            break;
        }
    }
    prefix = (1 << prefixZeroCount) - 1;
    for (size_t i = 0; i < prefixZeroCount; i++)
    {
        val = get_bit_at_position(buf, bytePotion, bitPosition);
        surfix += val * (1 << (prefixZeroCount - i - 1));
    }

    prefix += surfix;

    return prefix;
}

int _tmain(int argc, _TCHAR* argv[])
{
    UINT8 strArray[6] = { 0xA6, 0x42, 0x98, 0xE2, 0x04, 0x8A };
    UINT8 bytePosition = 0, bitPosition = 0;
    UINT8 dataLengthInBits = sizeof(strArray) * 8;

    int codeNum = 0;
    while ((bytePosition * 8 + bitPosition) < dataLengthInBits)
    {
        codeNum = get_uev_code_num(strArray, bytePosition, bitPosition);
        printf("ExpoColumb codeNum = %d\n", codeNum);
    }

    return 0;
}

该例程的详细解读请观看视频教程:H.264/AVC视频编解码技术详解

目录
相关文章
|
13天前
|
JavaScript 算法 前端开发
JS算法必备之String常用操作方法
这篇文章详细介绍了JavaScript中字符串的基本操作,包括创建字符串、访问特定字符、字符串的拼接、位置查找、大小写转换、模式匹配、以及字符串的迭代和格式化等方法。
JS算法必备之String常用操作方法
|
13天前
|
JavaScript 算法 前端开发
JS算法必备之Array常用操作方法
这篇文章详细介绍了JavaScript中数组的创建、检测、转换、排序、操作方法以及迭代方法等,提供了数组操作的全面指南。
JS算法必备之Array常用操作方法
|
13天前
|
算法 定位技术 vr&ar
一文了解PnP算法,python opencv中的cv2.solvePnP()的使用,以及使用cv2.sovlePnP()方法标定相机和2D激光雷达
一文了解PnP算法,python opencv中的cv2.solvePnP()的使用,以及使用cv2.sovlePnP()方法标定相机和2D激光雷达
75 0
一文了解PnP算法,python opencv中的cv2.solvePnP()的使用,以及使用cv2.sovlePnP()方法标定相机和2D激光雷达
|
21天前
|
存储 人工智能 算法
AI算法的道德与社会影响:探索技术双刃剑的边界
【8月更文挑战第22天】AI算法作为一把双刃剑,在推动社会进步的同时,也带来了诸多道德与社会挑战。面对这些挑战,我们需要以开放的心态、严谨的态度和创新的思维,不断探索技术发展与伦理规范之间的平衡之道,共同构建一个更加美好、更加公正的AI未来。
|
24天前
|
机器学习/深度学习 自然语言处理 负载均衡
揭秘混合专家(MoE)模型的神秘面纱:算法、系统和应用三大视角全面解析,带你领略深度学习领域的前沿技术!
【8月更文挑战第19天】在深度学习领域,混合专家(Mixture of Experts, MoE)模型通过整合多个小型专家网络的输出以实现高性能。从算法视角,MoE利用门控网络分配输入至专家网络,并通过组合机制集成输出。系统视角下,MoE需考虑并行化、通信开销及负载均衡等优化策略。在应用层面,MoE已成功应用于Google的BERT模型、Facebook的推荐系统及Microsoft的语音识别系统等多个场景。这是一种强有力的工具,能够解决复杂问题并提升效率。
39 2
|
28天前
|
机器学习/深度学习 监控 算法
目标检测算法技术
8月更文挑战第11天
|
27天前
|
机器学习/深度学习 算法 Java
算法设计(动态规划应用实验报告)实现基于贪婪技术思想的Prim算法、Dijkstra算法
这篇文章介绍了基于贪婪技术思想的Prim算法和Dijkstra算法,包括它们的伪代码描述、Java源代码实现、时间效率分析,并展示了算法的测试用例结果,使读者对贪婪技术及其应用有了更深入的理解。
算法设计(动态规划应用实验报告)实现基于贪婪技术思想的Prim算法、Dijkstra算法
|
18天前
|
算法
【Azure Developer】完成算法第4版书中,第一节基础编码中的数组函数 histogrm()
【Azure Developer】完成算法第4版书中,第一节基础编码中的数组函数 histogrm()
|
6天前
|
算法 BI Serverless
基于鱼群算法的散热片形状优化matlab仿真
本研究利用浴盆曲线模拟空隙外形,并通过鱼群算法(FSA)优化浴盆曲线参数,以获得最佳孔隙度值及对应的R值。FSA通过模拟鱼群的聚群、避障和觅食行为,实现高效全局搜索。具体步骤包括初始化鱼群、计算适应度值、更新位置及判断终止条件。最终确定散热片的最佳形状参数。仿真结果显示该方法能显著提高优化效率。相关代码使用MATLAB 2022a实现。
|
6天前
|
算法 数据可视化
基于SSA奇异谱分析算法的时间序列趋势线提取matlab仿真
奇异谱分析(SSA)是一种基于奇异值分解(SVD)和轨迹矩阵的非线性、非参数时间序列分析方法,适用于提取趋势、周期性和噪声成分。本项目使用MATLAB 2022a版本实现从强干扰序列中提取趋势线,并通过可视化展示了原时间序列与提取的趋势分量。代码实现了滑动窗口下的奇异值分解和分组重构,适用于非线性和非平稳时间序列分析。此方法在气候变化、金融市场和生物医学信号处理等领域有广泛应用。