精华内容
下载资源
问答
  • 对一幅BMP格式的图像进行二元霍夫曼和费诺编码和译码。
  • Huffman编码解码

    万次阅读 多人点赞 2016-06-05 14:06:49
    Huffman编码解码霍夫曼(Huffman)编码问题也就是最优编码问题,通过比较权值逐步构建一颗Huffman树,再由Huffman树进行编码、解码。其步骤是先构建一个包含所有节点的线性表,每次选取最小权值的两个节点,生成一个...

    Huffman编码解码

    霍夫曼(Huffman)编码问题也就是最优编码问题,通过比较权值逐步构建一颗Huffman树,再由Huffman树进行编码、解码。

    其步骤是先构建一个包含所有节点的线性表,每次选取最小权值的两个节点,生成一个父亲节点,该父亲节点的权值等于两节点权值之和,然后将该父亲节点加入到该线性表中,再重复上述步骤,直至构成一个二叉树,注意已经使用过的节点不参与。

    Huffman编码贪心原理

    编码原理

    把每个字符看作一个单节点子树放在一个树集合中,每棵子树的权值等于相应字符的频率。每次取权值最小的两棵子树合成一棵新树,并重新放到集合中。新树的权值等于两棵子树权值之和。

    贪心选择性

    x y是频率最小的两个字符,则存在前缀码使得 x y具有相同码长,且仅有最后一位编码不同。换句话说,贪心选择保留了最优解。

    优化子结构

    T 是加权字符集C的最优编码树, x y是树 T 中两个叶子,且互为兄弟结点,z是它们的父结点。若把 z 看成具有频率f(z)=f(x)+f(y)的字符,则树 T=T{x,y} 是字符集 C=C{x,y}{z} 的一棵最优编码树。换句话说,原问题的最优解包含子问题的最优解。

    举例说明

    编码表

    字符abcdef
    频率4513121695
    编码010110011111011100

    Huffman编码

    下面将解释为什么是这样编码,在解释之前先说明一个概念:

    • 前缀码:任何一个编码都不是另一个编码的前缀(prefix)。

    如果Huffman编码符合前缀码的要求的话,那么绝不会出现编码二义性的问题。而且通过权值这一参考量,构成了最优编码。

    原理图

    原始节点


    构建Huffman树的步骤


    Huffman编码

    Huffman编码解码算法实现

    节点信息结构

    // 节点信息结构
    struct Node {
        // 值
        string value;
        // 权值
        float weight;
        // 父节点
        int parent;
        // 左子节点
        int lchild;
        // 右子节点
        int rchild;
    };

    编码信息结构

    // 编码信息结构
    struct Code {
        // 编码字符
        int bit[maxBit];
        // 开始位置
        int start;
        // 值
        string value;
    };

    全局常量和全局变量

    const int INF = 1000000000;
    const int maxBit = 1 << 5;
    const int maxNode = 1 << 10;
    const int maxCode = 1 << 10;
    
    // 节点数组
    Node huffman[maxNode];
    // 编码数组
    Code huffmanCode[maxCode];
    // n个字符串
    int n;

    初始化Huffman树

    // 初始化Huffman树
    void initHuffmanTree() {
        for(int i = 0; i < (2 * n) - 1; i++) {
            huffman[i].weight = 0;
            huffman[i].value = "";
            huffman[i].parent = -1;
            huffman[i].lchild = -1;
            huffman[i].rchild = -1;
        }
    }

    构造Huffman树

    // 贪心法
    // 构造Huffman树
    void huffmanTree() {
        // 循环构建Huffman树
        for(int i = 0; i < n - 1; i++) {
            // m1,m2存放所有节点中权值最小的两个节点权值
            int m1 = INF;
            int m2 = INF;
            // x1,x2存放所有节点中权值最小的两个节点下标
            int x1 = 0;
            int x2 = 0;
            for(int j = 0; j < n + i; j++) {
                if(huffman[j].weight < m1 && huffman[j].parent == -1) {
                    m2 = m1;
                    x2 = x1;
                    m1 = huffman[j].weight;
                    x1 = j;
                } else if(huffman[j].weight < m2 && huffman[j].parent == -1) {
                    m2 = huffman[j].weight;
                    x2 = j;
                }
            }
            // 设置找到的两个节点的x1,x2的父节点信息
            huffman[x1].parent = n + i;
            huffman[x2].parent = n + i;
            huffman[n + i].weight = huffman[x1].weight + huffman[x2].weight;
            huffman[n + i].lchild = x1;
            huffman[n + i].rchild = x2;
        }
    }

    Huffman编码

    // huffman编码
    void huffmanEncoding() {
        // 临时结构
        Code cd;
        int child, parent;
        for(int i = 0; i < n; i++) {
            cd.value = huffman[i].value;
            cd.start = n - 1;
            child = i;
            parent = huffman[child].parent;
            // 未到根节点
            while(parent != -1) {
                // 左孩子
                if(huffman[parent].lchild == child) {
                    cd.bit[cd.start] = 0;
                } else {
                    // 右孩子
                    cd.bit[cd.start] = 1;
                }
                cd.start--;
                // 设置下一循环条件
                child = parent;
                parent = huffman[child].parent;
            }
    
            // 保存求出的每个叶子节点的Huffman编码结构
            for(int j = cd.start + 1; j < n; j++) {
                huffmanCode[i].bit[j] = cd.bit[j];
            }
            huffmanCode[i].start = cd.start;
            huffmanCode[i].value = cd.value;
        }
    }

    打印Huffman编码信息

    // 打印每个叶节点的Huffman编码和编码起始值
    void printHuffmanCode() {
        for(int i = 0; i < n; i++) {
            cout << "第" << i + 1 << "个字符 " << huffmanCode[i].value << " 的Huffman编码为:";
            for(int j = huffmanCode[i].start + 1; j < n; j++) {
                cout << huffmanCode[i].bit[j];
            }
            cout << " 编码起始值为:" << huffmanCode[i].start << endl;
        }
        cout << endl;
    }

    解码Huffman编码

    // 解码Huffman编码
    void HuffmanDecoding(string s) {
        vector<string> v;
        // 标识位
        int ok = 1;
        for(int i = 0; i < s.length();) {
            // 根节点
            int x = (2 * n) - 1 - 1;
            // 不为叶子节点
            while(huffman[x].lchild != -1 && huffman[x].rchild != -1) {
                // 左子树
                if(s[i] == '0') {
                    x = huffman[x].lchild;
                } else {
                    // 右子树
                    x = huffman[x].rchild;
                }
                i++;
                // 处理0,1序列有误
                // 这种情况一般是结尾0,1序列少了,导致最后一个字符串解码失败
                if(i == s.length() && huffman[x].lchild != -1) {
                    ok = 0;
                    break;
                }
            }
    
            if(ok) {
                v.push_back(huffman[x].value);
            }
        }
        if(ok) {
            for(int i = 0; i < v.size(); i++) {
                cout << v[i];
            }
            cout << endl << endl;
        } else {
            cout << "解码有误。" << endl << endl;
        }
    }

    主函数

    int main() {
        while(true) {
            // 初始化
    
            // 输入数据
            cout << "请输入字符串个数(0退出):";
            cin >> n;
            if(!n) {
                break;
            }
    
            // 初始化Huffman树
            initHuffmanTree();
    
            for(int i = 0; i < n; i++) {
                cout << "一共" << n << "个字符串,请输入第" << i + 1 << "个字符串及其权值:";
                cin >> huffman[i].value;
                cin >> huffman[i].weight;
            }
    
            // 构造Huffman树
            huffmanTree();
    
            // huffman编码
            huffmanEncoding();
    
            // 打印每个叶节点的Huffman编码和编码起始值
            printHuffmanCode();
    
            while(true) {
                cout << "请输入一段符合上述编码的0,1序列(q进入下一次编码解码):";
                string s;
                cin >> s;
                if(s[0] == 'q') {
                    cout << endl;
                    break;
                }
                cout << "原始0,1序列为:" << s << endl;
                cout << "解码后为:";
                // 解码
                HuffmanDecoding(s);
            }
        }
        return 0;
    }

    测试主程序

    #include <iostream>
    #include <vector>
    #include <string>
    
    using namespace std;
    
    const int INF = 1000000000;
    const int maxBit = 1 << 5;
    const int maxNode = 1 << 10;
    const int maxCode = 1 << 10;
    
    // 节点信息结构
    struct Node {
        // 值
        string value;
        // 权值
        float weight;
        // 父节点
        int parent;
        // 左子节点
        int lchild;
        // 右子节点
        int rchild;
    };
    
    // 编码信息结构
    struct Code {
        // 编码字符
        int bit[maxBit];
        // 开始位置
        int start;
        // 值
        string value;
    };
    
    // 节点数组
    Node huffman[maxNode];
    // 编码数组
    Code huffmanCode[maxCode];
    
    // n个字符串
    int n;
    
    // 初始化Huffman树
    void initHuffmanTree() {
        for(int i = 0; i < (2 * n) - 1; i++) {
            huffman[i].weight = 0;
            huffman[i].value = "";
            huffman[i].parent = -1;
            huffman[i].lchild = -1;
            huffman[i].rchild = -1;
        }
    }
    
    
    // 贪心法
    // 构造Huffman树
    void huffmanTree() {
        // 循环构建Huffman树
        for(int i = 0; i < n - 1; i++) {
            // m1,m2存放所有节点中权值最小的两个节点权值
            int m1 = INF;
            int m2 = INF;
            // x1,x2存放所有节点中权值最小的两个节点下标
            int x1 = 0;
            int x2 = 0;
            for(int j = 0; j < n + i; j++) {
                if(huffman[j].weight < m1 && huffman[j].parent == -1) {
                    m2 = m1;
                    x2 = x1;
                    m1 = huffman[j].weight;
                    x1 = j;
                } else if(huffman[j].weight < m2 && huffman[j].parent == -1) {
                    m2 = huffman[j].weight;
                    x2 = j;
                }
            }
            // 设置找到的两个节点的x1,x2的父节点信息
            huffman[x1].parent = n + i;
            huffman[x2].parent = n + i;
            huffman[n + i].weight = huffman[x1].weight + huffman[x2].weight;
            huffman[n + i].lchild = x1;
            huffman[n + i].rchild = x2;
        }
    }
    
    // huffman编码
    void huffmanEncoding() {
        // 临时结构
        Code cd;
        int child, parent;
        for(int i = 0; i < n; i++) {
            cd.value = huffman[i].value;
            cd.start = n - 1;
            child = i;
            parent = huffman[child].parent;
            // 未到根节点
            while(parent != -1) {
                // 左孩子
                if(huffman[parent].lchild == child) {
                    cd.bit[cd.start] = 0;
                } else {
                    // 右孩子
                    cd.bit[cd.start] = 1;
                }
                cd.start--;
                // 设置下一循环条件
                child = parent;
                parent = huffman[child].parent;
            }
    
            // 保存求出的每个叶子节点的Huffman编码结构
            for(int j = cd.start + 1; j < n; j++) {
                huffmanCode[i].bit[j] = cd.bit[j];
            }
            huffmanCode[i].start = cd.start;
            huffmanCode[i].value = cd.value;
        }
    }
    
    // 打印每个叶节点的Huffman编码和编码起始值
    void printHuffmanCode() {
        for(int i = 0; i < n; i++) {
            cout << "第" << i + 1 << "个字符 " << huffmanCode[i].value << " 的Huffman编码为:";
            for(int j = huffmanCode[i].start + 1; j < n; j++) {
                cout << huffmanCode[i].bit[j];
            }
            cout << " 编码起始值为:" << huffmanCode[i].start << endl;
        }
        cout << endl;
    }
    
    // 解码Huffman编码
    void HuffmanDecoding(string s) {
        vector<string> v;
        // 标识位
        int ok = 1;
        for(int i = 0; i < s.length();) {
            // 根节点
            int x = (2 * n) - 1 - 1;
            // 不为叶子节点
            while(huffman[x].lchild != -1 && huffman[x].rchild != -1) {
                // 左子树
                if(s[i] == '0') {
                    x = huffman[x].lchild;
                } else {
                    // 右子树
                    x = huffman[x].rchild;
                }
                i++;
                // 处理0,1序列有误
                // 这种情况一般是结尾0,1序列少了,导致最后一个字符串解码失败
                if(i == s.length() && huffman[x].lchild != -1) {
                    ok = 0;
                    break;
                }
            }
    
            if(ok) {
                v.push_back(huffman[x].value);
            }
        }
        if(ok) {
            for(int i = 0; i < v.size(); i++) {
                cout << v[i];
            }
            cout << endl << endl;
        } else {
            cout << "解码有误。" << endl << endl;
        }
    }
    
    int main() {
        while(true) {
            // 初始化
    
            // 输入数据
            cout << "请输入字符串个数(0退出):";
            cin >> n;
            if(!n) {
                break;
            }
    
            // 初始化Huffman树
            initHuffmanTree();
    
            for(int i = 0; i < n; i++) {
                cout << "一共" << n << "个字符串,请输入第" << i + 1 << "个字符串及其权值:";
                cin >> huffman[i].value;
                cin >> huffman[i].weight;
            }
    
            // 构造Huffman树
            huffmanTree();
    
            // huffman编码
            huffmanEncoding();
    
            // 打印每个叶节点的Huffman编码和编码起始值
            printHuffmanCode();
    
            while(true) {
                cout << "请输入一段符合上述编码的0,1序列(q进入下一次编码解码):";
                string s;
                cin >> s;
                if(s[0] == 'q') {
                    cout << endl;
                    break;
                }
                cout << "原始0,1序列为:" << s << endl;
                cout << "解码后为:";
                // 解码
                HuffmanDecoding(s);
            }
        }
        return 0;
    }

    输出数据

    请输入字符串个数(0退出):6
    一共6个字符串,请输入第1个字符串及其权值:a 45
    一共6个字符串,请输入第2个字符串及其权值:b 13
    一共6个字符串,请输入第3个字符串及其权值:c 12
    一共6个字符串,请输入第4个字符串及其权值:d 16
    一共6个字符串,请输入第5个字符串及其权值:e 9
    一共6个字符串,请输入第6个字符串及其权值:f 51个字符 a 的Huffman编码为:0 编码起始值为:42个字符 b 的Huffman编码为:101 编码起始值为:23个字符 c 的Huffman编码为:100 编码起始值为:24个字符 d 的Huffman编码为:111 编码起始值为:25个字符 e 的Huffman编码为:1101 编码起始值为:16个字符 f 的Huffman编码为:1100 编码起始值为:1
    
    请输入一段符合上述编码的0,1序列(q进入下一次编码解码):010011110111011100
    原始0,1序列为:010011110111011100
    解码后为:acdbef
    
    请输入一段符合上述编码的0,1序列(q进入下一次编码解码):00010010110010111011101110011001111110101
    原始0,1序列为:00010010110010111011101110011001111110101
    解码后为:aaacbcbeeffddab
    
    请输入一段符合上述编码的0,1序列(q进入下一次编码解码):010110011
    原始0,1序列为:010110011
    解码后为:解码有误。
    
    请输入一段符合上述编码的0,1序列(q进入下一次编码解码):q
    
    请输入字符串个数(0退出):0
    
    Process returned 0 (0x0)   execution time : 16.174 s
    Press any key to continue.
    展开全文
  • MPEG4 H.264 编码 解码 源代码 MPEG4 H.264 编码 解码 源代码 MPEG4 H.264 编码 解码 源代码 MPEG4 H.264 编码 解码 源代码 MPEG4 H.264 编码 解码 源代码 MPEG4 H.264 编码 解码 源代码 MPEG4 H.264 编码 解码 源...
  • 这里把ExchangeCodec和DubboCodec放一起来讲解dubbo传输的底层协议组成以及它的编码解码过程。   传输协议 协议格式 协议头 :header 是16个字节的定长数据  = 2 //short类型的MAGIC = (short) 0xdabb + 1 //一个...

    这里把ExchangeCodec和DubboCodec放一起来讲解dubbo传输的底层协议组成以及它的编码解码过程。

     

    传输协议

    协议格式<header><bodydata>

    协议头 :header 是16个字节的定长数据

       =  2 //short类型的MAGIC = (short) 0xdabb

    + 1 //一个字节的消息标志位,用来表示消息是request还是//response,twoway还是oneway,是心跳还是正常请求以及采用//的序列化反序列化协议

     + 1 //状态位, 消息类型为response时,设置请求响应状态

     + 8 //设置消息的id long类型

     + 4 //设置消息体body长度 int类型

     

     

    Body data:body是消息传递的真正内容,body的占用的字节大小由协议头后四位保存。Body的内容:

    Request.getData()得到dubbo请求消息传输的body对象RpcInvocation,对于请求对象dubbo其实是知道需要传输哪些信息的,所以并没有把整个RpcInvocation对象序列化传输,而是序列化传输必要字段信息,下面列举出具体信息:

    1. dubbo的版本信息

    2. 服务接口名如:com.zhanqiu.DemoService

    3. 服务的版本号

    4. 调服务的方法名

    5. 调服务的方法的参数描述符如:[int.class, boolean[].class,Object.class] => "I[ZLjava/lang/Object;"

    6. 遍历传输的参数值逐个序列化

    7. 将整个附属信息map对象attachments序列化

    下图是序列化Request的body的具体代码:

     

    Response.getResult()获取Result对象。根据Result对象是否有异常对象序列里化异常对象, 如果没有获取result.getValue()此对象为真正返回的业务对象消息的的body数据,整体序列化result.getValue()返回的对象。

     

     

    编码整体流程:

    1. 判断消息类型是Request, Resonse如果不是调父类(可能是string telnet类型)

    2. 获取序列化方式, 可以同URL指定,没有默认为hessian方式

    3. 构建存储header的字节数组,大小是16

    4. Header数组前两位写入dubbo协议魔数(short)0xdabb

    5. Header数组第三位, 一个字节4位与或方式存储,

    1)     哪种序列化方式

    2)     请求还是响应消息

    3)     请求时twoway还是oneway

    4)     是心跳,还是正常消息

    5)     如果是response, 响应的状态

    6. 获取buffer的写入位置writerIndex, 加上消息头长度16,重新设置buffer的写入位置,这里是消息body的写入位置, 因为后面是先写body,要把header的位置空出来

    7. 序列化消息body, (request, response参考前面的)写入buffer

    8. 计算消息体大小writerIndex – startIndex

    9. 检查消息体是否超过限制大小

    10.      重置writeIndex就是第6点获取的值

    11.      给消息头数组最后四位写入消息消body长度值int类型

    12.      向buffer写入消息头数据

    13.      Buffer设置writerIndex=savedWriteIndex+ HEADER_LENGTH + len

     

     

    解码整体流程:

    1. 从channle获取可读取的字节数readable

    2. readable跟header_length取小构建字节数组header[]

    readable < header_length说明不是一个全的dubbo协议消息(所以后面要判断消息头魔数),或者只是一个telnet消息

    3. 如果判断header[]的第一个和第二个字节不是dubbo协议魔数

    a)  如果可读取字节readable大于header_length, 重新构建header[], 读取全部可读取readable数据到header

    b)  遍历header中的字节判断中间是否有dubbo协议的魔数0xdabb, 如果有说明魔数开始的是dubbo协议的消息。

    重新设置buffer的读取位置从魔数开始

    截取header[]中从头开始到魔术位置的数据

    c)  调父类解码,可能就是telnet字符串解码

    4. 如果是dubbo协议的消息 readable < header_length 说明读取header[]数据不全, 返回NEED_MORE_INPUT说明需要读取更多数据

    5. 读取header[]最后四位一个int的数据,bodydata的长度len

    6. 计算总消息大小tt = len + body_length

    7. 如果可读取数据readable < tt, 数据不够返回NEED_MORE_INPUT

    8. 由buffer和len构建ChannelBufferInputStream, 后面序列化工具会使用

    9. 下面是解码消息体body data过程

    9.1 header[2] 第三位获取标志位

    9.2 从标志位判断采用哪种序列化方式,获取具体的序列化工具

    9.3 读取header[4]开始的8位, 获取消息的id

    9.4 根据标志位判读消息为响应类型

    a)构建resonse对象 new Response(id)

    b)根据标志位如果是心跳,给response对象设置Event类型

    c)从header[3]获取消息响应状态,给 response对象设置消息状态

    d) 如果是事件消息直接利用反序列化工具读取对象

    e) 如果不是构建消息接口DecodeableRpcResult result 序列化工具读取请求结果并设置到result的value属性上

    f)如果响应状态不是ok, 反序列化errMessage并设置给response

    9.5 根据标志位判断消息为请求类型

    a) 根据id构建Request(id)

    b) 根据状态位设置请求模式 twoway还是oneway

    c) 根据状态位设置请求是否是事件类型

    d) 如果是事件类型直接通过饭序列化工具读取

    e) 如果不是事件请求,构建DecodeableRpcInvocation

          通过反序列化工具

          读取dubbo版本,设置到invocation的map中

          读取path服务名,设置到invocation的map中

          读取服务版本,设置到invocation的map中

          读取调用服务的方法, 设置到invocation的methodName属性上

          读取方法描述符, 得到入参类型

          遍历读取入参参数值

          读取map对象不为空添加到invocation的map中
    展开全文
  • netty系列之:netty中的懒人编码解码

    千次阅读 2021-08-20 17:28:41
    netty之所以强大,是因为它内置了很多非常有用的编码解码器,通过使用这些编码解码器可以很方便的搭建出非常强大的应用程序,今天给大家讲讲netty中最基本的内置编码解码器。

    简介

    netty之所以强大,是因为它内置了很多非常有用的编码解码器,通过使用这些编码解码器可以很方便的搭建出非常强大的应用程序,今天给大家讲讲netty中最基本的内置编码解码器。

    netty中的内置编码器

    在对netty的包进行引入的时候,我们可以看到netty有很多以netty-codec开头的artifactId,统计一下,有这么多个:

    netty-codec
    netty-codec-http
    netty-codec-http2
    netty-codec-memcache
    netty-codec-redis
    netty-codec-socks
    netty-codec-stomp
    netty-codec-mqtt
    netty-codec-haproxy
    netty-codec-dns
    

    总共10个codec包,其中netty-codec是最基础的一个,其他的9个是对不同的协议包进行的扩展和适配,可以看到netty支持常用的和流行的协议格式,非常的强大。因为codec的内容非常多,要讲解他们也不是很容易,本文将会以netty-codec做一个例子,讲解其中最基本的也是最通用的编码解码器。

    使用codec要注意的问题

    虽然netty提供了很方便的codec编码解码器,但是正如我们在前一篇文章中提到的,有些codec是需要和Frame detection一起配合使用的,先使用Frame detection将ByteBuf拆分成一个个代表真实数据的ByteBuf,再交由netty内置的codec或者自定义的codec进行处理,这样才能起到应有的效果。

    netty内置的基本codec

    netty中基本的codec有base64、bytes、compression、json、marshalling、protobuf、serialization、string和xml这几种。

    下面将会一一进行讲解。

    base64

    这个codec是负责ByteBuf和base64过后的ByteBuf之间的转换。虽然都是从ByteBuf到ByteBuf,但是其中的内容发生了变化。

    有两个关键的类,分别是Base64Encoder和Base64Decoder。因为Base64Decoder是一个MessageToMessageDecoder,所以需要使用一个DelimiterBasedFrameDecoder提前进行处理,常用的例子如下:

       ChannelPipeline pipeline = ...;
      
       // Decoders
       pipeline.addLast("frameDecoder", new DelimiterBasedFrameDecoder(80, Delimiters.nulDelimiter()));
       pipeline.addLast("base64Decoder", new Base64Decoder());
      
       // Encoder
       pipeline.addLast("base64Encoder", new Base64Encoder());
    

    bytes

    bytes是将bytes数组和ByteBuf之间进行转换,同样的在decode之前,也需要使用FrameDecoder,通常的使用方式如下:

       ChannelPipeline pipeline = ...;
      
       // Decoders
       pipeline.addLast("frameDecoder",
                        new LengthFieldBasedFrameDecoder(1048576, 0, 4, 0, 4));
       pipeline.addLast("bytesDecoder",
                        new ByteArrayDecoder());
      
       // Encoder
       pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
       pipeline.addLast("bytesEncoder", new ByteArrayEncoder());
       
    

    compression

    compression这个包的内容就比较丰富了,主要是对数据的压缩和解压缩服务。其支持的算法如下:

    brotli
    Bzip2
    FastLZ
    JdkZlib
    Lz4
    Lzf
    Snappy
    Zlib
    Zstandard
    

    compression对于大数据量的传输特别有帮助,通过压缩可以节省传输的数据量,从而提高传输速度。

    但是压缩是使用特定的算法来计算的,所以它是一个高CPU的操作,我们在使用的时候需要兼顾网络速度和CPU性能,并从中得到平衡。

    json

    json这个包里面只有一个JsonObjectDecoder类,主要负责将Byte流的JSON对象或者数组转换成JSON对象和数组。

    JsonObjectDecoder直接就是一个ByteToMessageDecoder的子类,所以它不需要FrameDecoder,它是根据括号的匹配来判断Byte数组的起始位置,从而区分哪些Byte数据是属于同一个Json对象或者数组。

    我们如果希望使用JSON来传输数据的话,这个类就非常有用了。

    marshalling

    Marshalling的全称叫做JBoss Marshalling,它是JBoss出品的一个对象序列化的方式,但是JBoss Marshalling的最新API还是在2011-04-27,已经有10年没更新了,是不是已经被废弃了?

    所以这里我们不详细介绍这个序列化的内容,感兴趣的小伙伴可以自行探索。

    protobuf

    protobuf大家应该都很熟悉了,它是google出品的一种信息交换格式,可以将其看做是一种序列化的方式。它是语言中立、平台中立、可扩展的结构化数据序列化机制,和XML类似,但是比XML更小、更快、更简单。

    netty对protobuf的支持在于可以将protobuf中的message和MessageLite对象跟ByteBuf进行转换。

    protobuf的两个编码器也是message到message直接的转换,所以也需要使用frame detection。当然你也可以使用其他的frame detection比如LengthFieldPrepender和LengthFieldBasedFrameDecoder如下所示:

       ChannelPipeline pipeline = ...;
      
       // Decoders
       pipeline.addLast("frameDecoder",
                        new LengthFieldBasedFrameDecoder(1048576, 0, 4, 0, 4));
       pipeline.addLast("protobufDecoder",
                        new ProtobufDecoder(MyMessage.getDefaultInstance()));
      
       // Encoder
       pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
       pipeline.addLast("protobufEncoder", new ProtobufEncoder());
    

    其中LengthFieldPrepender会自动给字段前面加上一个长度字段:

    之前:
       +----------------+
       | "HELLO, WORLD" |
       +----------------+
    
    之后:
       +--------+----------------+
       + 0x000C | "HELLO, WORLD" |
       +--------+----------------+
    

    当然netty为protobuf准备了两个专门的frame detection,他们是ProtobufVarint32FrameDecoder和ProtobufVarint32LengthFieldPrepender。在讲解这两个类之前,我们需要了解一下protobuf中的Base 128 Varints。

    什么叫Varints呢?就是序列化整数的时候,占用的空间大小是不一样的,小的整数占用的空间小,大的整数占用的空间大,这样不用固定一个具体的长度,可以减少数据的长度,但是会带来解析的复杂度。

    那么怎么知道这个数据到底需要几个byte呢?在protobuf中,每个byte的最高位是一个判断位,如果这个位被置位1,则表示后面一个byte和该byte是一起的,表示同一个数,如果这个位被置位0,则表示后面一个byte和该byte没有关系,数据到这个byte就结束了。

    举个例子,一个byte是8位,如果表示的是整数1,那么可以用下面的byte来表示:

    0000 0001
    

    如果一个byte装不下的整数,那么就需要使用多个byte来进行连接操作,比如下面的数据表示的是300:

    1010 1100 0000 0010
    

    为什么是300呢?首先看第一个byte,它的首位是1,表示后面还有一个byte。再看第二个byte,它的首位是0,表示到此就结束了。我们把判断位去掉,变成下面的数字:

    010 1100 000 0010
    

    这时候还不能计算数据的值,因为在protobuf中,byte的位数是反过来的,所以我们需要把上面的两个byte交换一下位置:

    000 0010 010 1100 
    

    也就是:

    10 010 1100 
    

    =256 + 32 + 8 + 4 = 300

    在protobuf中一般使用Varint作为字段的长度位,所以netty提供了ProtobufVarint32LengthFieldPrepender和ProtobufVarint32FrameDecoder对ByteBuf进行转换。

    比如为ByteBuf添加varint的length:

       BEFORE ENCODE (300 bytes)       AFTER ENCODE (302 bytes)
       +---------------+               +--------+---------------+
       | Protobuf Data |-------------->| Length | Protobuf Data |
       |  (300 bytes)  |               | 0xAC02 |  (300 bytes)  |
       +---------------+               +--------+---------------+
    

    解码的时候删除varint的length字段:

       BEFORE DECODE (302 bytes)       AFTER DECODE (300 bytes)
       +--------+---------------+      +---------------+
       | Length | Protobuf Data |----->| Protobuf Data |
       | 0xAC02 |  (300 bytes)  |      |  (300 bytes)  |
       +--------+---------------+      +---------------+
    

    serialization

    序列化就是把对象转换成二进制数据,事实上所有的codec都可以成为序列化。他们提供了对象和byte之间的转换方法。

    netty也提供了两个对象的转换方法:ObjectDecoder和ObjectEncoder。

    要注意的是,这两个对象和JDK自带的ObjectInputStream和ObjectOutputStream,是不兼容的,如果要兼容,可以使用CompactObjectInputStream、CompactObjectOutputStream和CompatibleObjectEncoder。

    string

    String是我们最常使用到的对象,netty为string提供了StringDecoder和StringEncoder。

    同样的,在使用这两个类之前,需要将消息进行转换,通常使用的是 LineBasedFrameDecoder按行进行转换:

       ChannelPipeline pipeline = ...;
      
       // Decoders
       pipeline.addLast("frameDecoder", new LineBasedFrameDecoder(80));
       pipeline.addLast("stringDecoder", new StringDecoder(CharsetUtil.UTF_8));
      
       // Encoder
       pipeline.addLast("stringEncoder", new StringEncoder(CharsetUtil.UTF_8));
    

    xml

    xml也是一个非常常用的格式,但是它的体积会比较大,现在应该用的比较少了。netty提供了一个XmlFrameDecoder来进行解析。

    因为xml有自己的开始和结束符,所以不需要再做frame detection,直接转换即可,如:

       +-----+-----+-----------+
       | <an | Xml | Element/> |
       +-----+-----+-----------+
    转换成:
       +-----------------+
       | <anXmlElement/> |
       +-----------------+
    
       +-----+-----+-----------+-----+----------------------------------+
       | <an | Xml | Element/> | <ro | ot><child>content</child></root> |
       +-----+-----+-----------+-----+----------------------------------+
       转换成:
       +-----------------+-------------------------------------+
       | <anXmlElement/> | <root><child>content</child></root> |
       +-----------------+-------------------------------------+
    

    都是可以的。

    总结

    netty提供了很多优秀的codec来适配各种应用协议,大家可以多用用,找找不同协议的不同之处。

    本文已收录于 http://www.flydean.com/16-netty-buildin-codec-common/

    最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

    欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

    展开全文
  • 视频编码解码基本知识

    千次阅读 2018-05-24 11:24:28
    最近在做摄像头视频的数据处理,得到了网络相机的.h264格式的数据,需要做解码处理,由于对视频的编码解码原理不太了解,因此查找资料总结一下 为什么要进行视频编码? 视频编码又称为视频压缩,伴随着用户对高清...

    最近在做摄像头视频的数据处理,得到了网络相机的.h264格式的数据,需要做解码处理,由于对视频的编码解码原理不太了解,因此查找资料总结一下

    为什么要进行视频编码?

    视频编码又称为视频压缩,伴随着用户对高清视频的需求量的增加,视频多媒体的视频数据量也在不断加大。如果未经压缩,这些视频很难应用于实际的存储和传输。而视频中是有很多冗余信息的,以记录数字视频的YUV分量格式为例,YUV分别代表亮度与两个色差信号。例如对于现有的PAL制电视系统,其亮度信号采样频率为13.5MHz;色度信号的频带通常为亮度信号的一半或更少,为6.75MHz或3.375MHz。以4:2:2的采样频率为例,Y信号采用13.5MHz,色度信号U和V采用6.75MHz采样,采样信号以8bit量化,则可以计算出数字视频的码率为:13.5*8 + 6.75*8 + 6.75*8= 216Mbit/s。如此大的数据量如果直接进行存储或传输将会遇到很大困难,因此必须采用压缩技术以减少码率。数字化后的视频信号能进行压缩主要依据两个基本条件:

    • 数据冗余。例如如空间冗余、时间冗余、结构冗余、信息熵冗余等,即图像的各像素之间存在着很强的相关性。消除这些冗余并不会导致信息损失,属于无损压缩
    • 视觉冗余。人眼的一些特性比如亮度辨别阈值,视觉阈值,对亮度和色度的敏感度不同,使得在编码的时候引入适量的误差,也不会被察觉出来。可以利用人眼的视觉特性,以一定的客观失真换取数据压缩。这种压缩属于有损压缩

    数字视频信号的压缩正是基于上述两种条件,有效地去除视频数据中冗余信息,实现视频数据在互联网中快速传输和离线的存储。使得视频数据量得以极大的压缩,有利于传输和存储。

    主流的编解码标准的压缩对象都是YUV图像

    常见的编码格式

    在过去的几十年中,一系列的视频编码标准被广泛的应用。目前已有的视频压缩标准有很多种,包括国际标准化组织(International Organization for Standardization, ISO)/国际电工技术委员会(International Electrotechnical Commission, IEC)制定的MPEG-1、MPEG-2、MPEG-4标准;国际电信联盟电信标准化部门(International Telecommunication Union-Telecom, ITU-T)制定的H.261、H.263。

    2003年3月,ITU-T和ISO/IEC 正式公布了H.264/MPEG-4 AVC视频压缩标准。H.264作为目前应用最为广泛的视频编码标准,在提高编码效率和灵活性方面取得了巨大成功,使得数字视频有效地应用在各种各样的网络类型和工程领域。为了在关键技术上不受国外牵制,同时也不用交大量的专利费用,中国也制定了AVS系列标准,可以提供与H.264/AVC相当的编码效率。

    近年来随着用户体验的不断升级,高清(1920x1080)和超高清(3840x2160)视频的应用越来越广泛。相对于标清视频,高清视频分辨率更大也更清晰,但是相应的数据量也随之增加。在存储空间和网络带宽有限的情况下,现有的视频压缩技术已经不能满足现实的应用需求。为了解决高清及超高清视频急剧增长的数据率给网络传输和数据存储带来的冲击,ITU-T和ISO/IEC联合制定了具有更高压缩效率的新一代视频压缩标准HEVC(High Efficiency Video Coding)。

    视频文件生成方式

    一个完整的视频文件是由音频和视频2 部分组成的。常见的AVI、RMVB、MKV、ASF、WMV、MP4、3GP、FLV等文件其实只能算是一种封装标准。

    H264、Xvid等就是视频编码格式,MP3、AAC等就是音频编码格式。例如:将一个Xvid视频编码文件和一个MP3视频编码文件按AVI封装标准封装以后,就得到一个AVI后缀的视频文件,这个就是我们常见的AVI视频文件了。由于很多种视频编码文件、音频编码文件都符合AVI封装要求,则意味着即使是AVI后缀,也可能里面的具体编码格式不同。因此出现在一些设备上,同是AVI后缀文件,一些能正常播放,还有一些就无法播放。

    同样的情况也存在于其他容器格式。即使RMVB、WMV等也不例外。部分技术先进的容器还可以同时封装多个视频、音频编码文件,甚至同时封装进字幕,如MKV封装格式。MKV文件可以做到一个文件包括多语种发音、多语种字幕,适合不同人的需要。例如:MKV文件只要制作的时候同时加入国语和粤语发音的音轨和对应的简体、繁体字幕,播放的时候,你可以独立选择国语或粤语发音,并根据自己需要选择简体或繁体字幕,也可以选择不显示字幕。相当方便。

    因此,视频转换需要设置的本质就是:A设置需要的视频编码、B设置需要的音频编码、C选择需要的容器封装。一个完整的视频转换设置都至少包括了上面3个步骤。常见的组合方式如表所示

    常见的组合方式

    封装容器

    视频流编码格式

    音频流编码格式

    AVI

    Xvid

    MP3

    AVI

    Divx

    MP3

    Matroska(后缀就是MKV)

    Xvid

    MP3

    Matroska(后缀就是MKV)

    Xvid

    AAC

    Matroska(后缀就是MKV)

    H264

    AAC

    MP4

    Xvid

    MP3

    MP4

    H264

    AAC

    3GP

    H.263

    AAC

    视频编解码关键技术

    MPEG-4/H.264等编解码算法的工作机制基本都是混合编码,主要处理模块包括:预测、变换、量化和熵编码等。工作原理大同小异,图像帧的编码模式主要有帧内和帧间两种方式。帧内包括预测、变换、量化和熵编码等,帧间编码包括运动估计、运动补偿、变换、量化和熵编码等。 

    • 预测:通过帧内预测和帧间预测降低视频图像的空间冗余和时间冗余。
    • 变换:通过从时域到频域的变换,去除相邻数据之间的相关性,即去除空间冗余。
    • 量化:通过用更粗糙的数据表示精细的数据来降低编码的数据量,或者通过去除人眼不敏感的信息来降低编码数据量。
    • 扫描:将二维变换量化数据重新组织成一维的数据序列。
    • 熵编码:根据待编码数据的概率特性减少编码冗余。

    展开全文
  • QRCode编码解码的dll

    热门讨论 2011-01-07 11:57:17
    QRCode编码解码的dll QRCode是一种快速反应二维码,编码信息量大,解码快速
  • 编码解码 GStreamer

    千次阅读 2009-11-16 20:01:00
    编码解码 今天听了关于GStreamer的一个报告,讲到了GLib,讲到了编码解码,讲到了关于音频与视频上的很多东西,现在整理一下。 GStreamer,它是一种通用的多媒体处理库,可以使得多媒体处理更加容易。多媒体库,...
  • Get请求与URL编码解码

    千次阅读 2018-03-20 18:11:37
    Get请求传参,与编码解码 @1 Get请求流程: 在get请求中,参数直接添加在了url后面,同url一起提交到服务器。 常用的url参数的格式为: http://ip:port/path/file?参数1=值1&amp;参数2=值2…. 多个参数之间...
  • Huffman图像编码解码实现 matlab

    千次阅读 2019-04-18 20:54:43
    哈夫曼编码是一种可变长无损编码,应用范围广。这里介绍利用matalb实现哈夫曼编码方法。matalb中带有相关函,下面一一介绍: ...DECO = huffmandeco(COMP, DICT) :哈夫曼解码函数,COMP为哈夫曼编码向量,...
  • PDU短信编码解码

    千次阅读 2018-08-13 11:27:58
    PDU短信编解码格式说明 共有三种方式来发送和接收SMS信息:Block Mode, Text Mode和PDU Mode。其中PDU Mode被所有手机支持,可以使用任何字符集,这也是手机默认的编码方式。   PDU模式不仅支持中文短信,也能...
  • Chunked 编码 解码

    千次阅读 2012-04-09 17:20:30
    表示Body将用Chunked编码传输内容 Chunked编码使用若干个Chunk串连而成,由一个标明长度为0的chunk标示结束; 每个Chunk分为头部和正文两部分:  头部内容指定下一段正文的字符总数(十六进制的数字)和数量单位...
  • 基础|URL编码解码原理及示例

    千次阅读 2015-12-01 16:53:42
    一:URL编码解码原理 什么是编码解码? 编码:是把字符转化为字节数组。 解码:是把字节数组转化为字符。 URL编码解码过程? 但有一种特殊的URL编码方式是把字符转化为字符,具体的规则是: 如果字符是西文字符,则...
  • 多种语言实现Base64编码解码

    千次阅读 2016-02-13 13:35:01
    由于长度限制,上篇《网络信息传输编码解码》只介绍了编码解码的原理,本篇将把c#,vc,vb,delphi,php,python,asm 等语言的程序代码列于此,有些是从网上搜来的,供大家参考:   一。系统自带函数: 1.c# ...
  • 手机短信编码解码

    万次阅读 2010-04-25 14:32:00
    这个任务落到了我的脑袋上,所以需要做出一套通用的收发短信的东西,周五做了一套同步收发的工具,但是一直苦恼于短信的解码问题,因此,周末在家自己研究了一下关于手机的编码解码问题。0891683108501905F0040D...
  • java编码解码过程

    千次阅读 2015-12-26 12:35:15
     所以总的来说,编码的原因可以总结为:计算机中存储信息的最小单元是一个字节,即8个bit,所以能表示的字符范围是0-255个;人类要表示的符号太多,无法用一个字节来完全表示。 要解决这个矛盾必须要有一个新的...
  • HDMI_FPGA实现4K60TMDS编码解码之一

    千次阅读 2019-07-25 18:29:59
    hdmi1.4 tmds编码解码原理 一个HDMI包括三个TMDS 数据通道和一个 TMDS 时钟通道(像素时钟),如图所示 每一个 TMDS 时钟周期内,TMDS 数据通道上会发送一个 10 位的字符信息。 信号源编码逻辑的输入数据包括控制...
  • JPEG图像编码解码

    万次阅读 多人点赞 2014-09-23 15:24:01
    JPEG图像压缩编解码的设计与实现 ——程序设计实践报告 目录   一.工作总结 ..............................................................................................................................
  • 这是一个很好的Base64编-解码工具.转换很方便,支持中文,支持UTF-8,... 解码: 在下面的窗口输入base64的编码(最多可输入25000个字符,软件可以自动过滤掉非base64编码),同时即可在上面的窗口输出解码后的信息
  • Python编码解码问题 encode decode

    千次阅读 2014-12-28 20:05:11
    首先要搞清楚,字符串在Python内部的表示是unicode编码,因此,在做编码转换时,通常需要以unicode作为中间编码,即先将其他编码的字符串解码(decode)成unicode,再从unicode编码(encode)成另一种编码。...
  • JSP页面请求响应过程中的编码解码

    千次阅读 2017-02-19 17:35:51
    该编简要讲述:JSP页面传输过程中,浏览器与服务器的编码解码以及HTTP协议对URL进行的编码解码。无意间在网络上看到这样一个问题,让我不得其解。查询了诸多资料,稍有眉目。问题如下:所有的JSP页面的编码都是UTF-8...
  • MediaCodec 编码解码踩坑记录

    千次阅读 2019-01-17 15:18:50
    也就是说在初始化完成以后MediaCodec就会吐出2byte字节,这2字节记录了当前编码器的一些信息编码格式、编码位数,频道数等,是重要的信息不可扔掉。 ByteBuffer outBuf = mMediaCodec.getOutputBuffer(outIndex...
  • 编码解码以及码表介绍

    千次阅读 2017-01-30 19:24:16
    计算机在存储这些信息的时候,根据一个有规则的编号,当用户输入a 有a对映的编号,就将这个编号存进计算机中这就是编码。 计算机只能识别二进制数据。  为了方便应用计算机,让它可以识别各个国家的文字。就将...
  • H265编码解码/推流

    2018-09-20 16:03:26
    随着高清显示设备的发展和高清数字视频内容的日益增多,人们对于视频观看体验的要求也越来越高。新一代视频编码技术H.265/HEVC应运而生,作为新一代视频编码技术
  • python模块之email: 电子邮件编码解码 (一、解码邮件) python自带的email模块是个很有意思的东西,它可以对邮件编码解码,用来处理邮件非常好用。 处理邮件是一个很细致的工作,尤其是解码邮件,因为它的格式...
  • BER 编码解码规范与事例

    千次阅读 2012-04-10 14:45:19
    BER 编码解码规范与事例 1.1.1 概述 BER(Basic Encoding Rules)是ASN.1中最早定义的编码规则,在讨论详细编码规则时,我们是基于正确的抽象描述上。BER传输语法的格式一直是TLV三元组, Length, Value>
  • 红外遥控器快速编码解码(NEC)

    千次阅读 2020-06-29 10:46:22
    红外遥控器快速编码解码 NEC编解码模块 红外遥控简介NEC编码红外编解码模块接线说明串口查看数据插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建...
  • 数据结构哈夫曼编码解码课程设计

    千次阅读 多人点赞 2018-12-17 20:13:07
    数据结构课程设计 课题名称:哈夫曼编码/译码器 专业:班级: 姓名:(组长)学号:成绩: 姓名:(组员)学号:成绩: 姓名:(组员)学号:成绩: 指导教师: ...
  • 简单的来说,MediaCodec可以把摄像头采集的数据流编码为H264格式,这个过程是压缩过程。也可以把H264格式解码在surface类的控件上显示。我们先来看一下Android系统中解码器的命名,软解码器通常是以OMX.google开头的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 194,196
精华内容 77,678
热门标签
关键字:

信息的编码解码