JPEG解码——(3)文件头解析

JPEG解码——(3)⽂件头解析
  与具体的编码数据空间相⽐,jpeg⽂件头占据⾮常⼩乃⾄可以忽略不计的⼤⼩。
  仍然拿中的《animal park》这张图⽚来举例,从跳过SOS(FF DA)的TAG开始——offset=0x153,
就真正进⼊了编码数据区域,如下图所⽰:
  其占据的⽐例为:0x153/0x9721 = 339/38689 = 0.876%,还不到1%,其他jpeg图⽚也是类似情况。
  但是,就是这么⼩的数据区域,却是⾄关重要的地⽅,某些关键的地⽅⼀个字节出错了的话,解码就会出错(例如huffman table
中数据),或者重建出的yuv图像异常(例如quantization table中数据)!
  本篇是该系列的第三篇,主要介绍jpeg头信息解析,其中除了huffman table重建较复杂外,其他TAG的解析都⽐较容易。
1. APP0——FF EO
  先贴出这段区域:
  从ASCII值可以看出,保存了JFIF——JPEG File Interchange Format(JPEG⽂件交换格式),后⾯的⼏个字节应该是version信
息吧,没深究。
2. DQT——FF DB
  量化表有两个,上⾯贴图只⾼亮了其中⼀个表。
  从offset=0x16开始的两个字节(0x00 43)为这段区域的size=67,后⾯的⼀个字节为表的ID——0x00=0(可以看到第⼆张表中对应位置offset=0x5D处为0x1)。
  跳过前⾯三字节从offset=0x19处开始的64字节,即为量化表中量化值。其中需要说明的是,量化值是固定为64字节的,因为按8X8进⾏DCT变换的。
  ⼯具解析的结果如下:
  需要补充两点:
  A.亮度信号的Y分量使⽤DQT表⼀,UV分量使⽤表⼆。
  B.亮度信号通常采⽤细量化(量化值较⼩),对应位置处,表⼀通常⽐表⼆值要⼩。此量化原因是⼈眼对亮度信号⽐较敏感,采⽤颗粒度较细来量化,细量化引⼊的⼀个问题会消耗更多的数据空间。
3. SOF——FF C0
  在该JPEG解码系列中第⼀篇已经详细介绍过了,不再赘述。⼯具解析如下:
4. DHT——FF C4
  共有四张表,上⾯只贴出第⼀张表。
低频标签  DHT表的重建有些复杂,涉及底层更多关于数据压缩领域的知识,可以参考“范式霍夫曼编码”相关材料,本博⽂不再做介绍该编码原理。但会针对具体个例进⾏说明,如果重建霍夫曼表。这是⾄关重要的⼀环,因为关系着后⾯霍夫曼解码,如果表有误,后⾯会解码异常。
  4.1 表分类
    重建霍夫曼表。⼀般分为四个表:DC0,DC1,AC0,AC1,因为Y分量使⽤两个表:DC0+AC0,⽽UV分量也使⽤两个表:
DC1+AC1。
  4.2 ⼏个名词及解释
    这个⼏个名词是个⼈按照⾃⼰的理解来定义的,读者需按照这个来解读,因为我的解码⼯具就是按照这个来使⽤的。
    例如,parseDHT显⽰的下图:
    ⼏个名词:序号(SequenceNum)、码字长度(CodeWidth)、码字(Code)、信源值/权值(CodeVal)。
    SequenceNum:序号,依次递增,从0到totalCodeCnt-1。totalCodeCnt值不确定,取决于编码端编码出的数量。
            上⾯图⽰的第⼀列数字,是依次递增的,多⼀个编码数据就多⼀⾏。
    CodeWidth:  编码数据的宽度(码字宽度——⼆进制数据的bit位宽),宽度都是从2开始,最⼤为16。当某个码字Code的宽度为16时,表⽰⽤16位的
          编码数据来表⽰某个像素值(确切讲并不是像素值,⽽是RLE的值!),当然,其出现的概率⾮常低,否则会出现编码数据量⼤于信源数据量了。
            另外,码字宽度必须是依次递增的,中间不可能有跳变,因为霍夫曼编码理论上会尽量⽤较窄的码字来表⽰信源。-->也有可能产⽣跳变!
          但⼀般概率较低,曾经遇到过。
            相同码字宽度的若⼲码字,其码字依次递增。例如,图⽰第3-5⾏,码字宽度为3,其对应的码字为0x4,0x5,0x6,即⼆进制:100,101,110。风管抗震支吊架
          Question:每当码字宽度加⼀时,码字如何变化?
            上⼀个码字值加1后,末尾再补⼀个零(即——加1右移)。当宽度增加⼆时,先将上⼀个码字值加1后再补两个零。增加三时类似,但出现
          概率极低。例如上图中,从CodeWidth中2->3过渡,Code值变化为:01 -> 100;从CodeWidth的3 -> 4过渡,Code值变化为:110 ->1110。
            值得注意的⼀点:码字宽度,不⼀定都是依次递增,有可能产⽣跳变,⽬的是使后⾯的码字不溢出,也就是补两个或多个零的情况。
    Code: 码字,全部码字要求各不相等。因其是编码数据,⽽霍夫曼编码要求读取的完整的n位⽐特位的码字Code,不能与其他码字Code的前n位相等,
       因此宽度值从2位的00开始。
    例如,上⾯码字Code中间的四⾏分别为:0x5,0x6,0xe,0x1e,(⼆进制表⽰:101,110,1110,11110)的编码数据,其真正代表的CodeVal信源
      值为4,3,5,6。由此也可以看出,信源值4出现的频率/概率最⾼(如果仅仅这四个做⽐较的话是这样(再极端情况是:
P4=P3>P5>P6),如果通盘⽐较,
      当然是第⼀⾏0x1出现的概率最⼤),因为要⽤最⼩(最窄)的编码数据来表⽰频率最⾼的信源值。这是huffman编码理论中的⼀个核⼼概念——出现概率最⼤
      的值的编码宽度最窄,这样最利于压缩数据。
    CodeVal:信源值(应该是接近信源的值,不是量化后的值,其值是RLE⾏程编码值,由两部分组成,⾼四位和低四位)。
       即编码内容,也是霍夫曼树叶⼦节点权值,在解码时需要⽤Code来恢复出这种值。
       该值的宽度由量化精度决定,通常为8位,代表yuv图形单个像素值采样精度为8位,该值是唯⼀的,不能重复。——》描述错误,不是这种情况,后⾯再解释。
  4.3 重建步骤
    以例⼦来展⽰,不使⽤《animal park》,使⽤如下这⼀串值(红⾊ 号分割不同意义的值,我⾃⼰添加的):
    FF C4 00 1D 00 00 03 01 01 01 01 01 01 01 00 00 00 00 00 00 00 +04 05 06 03 02 01 00 09 07 08
    step1. 剔除掉表⽰size的00 1D以及表⽰table_id的00,剩余:00 03 01 01 01 01 01 01 01 00 00 00 00 00 00 00+04 05 06 03 02 01 00 09 07 08
      其中,前16个数值表⽰含义————码字宽度(CodeWidth)为n的码字(Code)的数量,其中n从1递增到16(可以表⽰为该位置的index,但是其是从1开始递增),
    因为最⼩宽度为1,最⼤宽度为16。
      通常,宽度为1的码字不会使⽤,⽽编码是从2位开始,例如第⼀个码字通常为0b00,来表⽰出现频率最⾼的那个信源值。有些位置上的值为0,表⽰该码字宽度⽆
    对应的码字,像第⼀个位置和最后7个位置的0,就没有对应的码字。
    从上⾯分析,可以得到结论:总共使⽤码字的数量————16个位置上的数值之和,也即是
totalCodeCnt=10(3+1+1+1+1+1+1+1),也是霍夫曼树中叶⼦节点的个数。
    step2. 前16个字节后⾯的若⼲个字节数据:04 05 06 03 02 01 00 09 07 08
      其表⽰码字宽度依次递增时所对应的信源值(CodeVal),其数量必然等于totalCodeCnt,因为⼀个有效码字(前16Byte不为0的)对应⼀个信源值。
    step3. 对应关系⽣成
      前16Bytes的第2个位置的03,代表码字宽度为2的码字数量为3,那么其分别为:0b00,0b01,0b10,其对应的信源值分别为后⾯的0x04,0x05,0x06
。。。。。。3。。。。01。。。。。。。。3。。。。。。1 。。。。。。。0b110。。。。。。。。。。。。。。。。。。。。0x03
      。。。。。。4。。。。  01。。。。。。。。4。。。。。。1。。。。。。。。 0b1110。。。。。。。。。。。。。。。。。。。0x02
      以此类推,直到最后⼀个码字宽度为9的码字0x1fe,以及其代表的信源值0x08。
  4.4 重建算法
    本⼈⼯具提供了⼀个重建huffman表的算法,感兴趣的可以参考。写的不是太简洁,但能正常重建DHT。
裹尸袋
1//rebuild huffman table
2int parseDHT(ABitReader* abr, struct jpegParam* param)
3 {
4    printf("(%s : %d), DHT offset:%#x\n", __func__, __LINE__, abr->getOffset());
5int len = abr->getBits(16);
6    len -= 2;
7while (len>0)
8    {
9        uint8_t idx = abr->getBits(8);
10        uint8_t idx_high = idx>>4;
11        uint8_t idx_low = idx & 0x0f;
12
13//idx_hight represent DC or AC: 0-DC, 1-AC
14//idx_low represent color id: 0-Y, 1-uv
15//[0][x] -- DC table, [0][0]:DC0, [0][1]:DC1
16//[1][x] -- AC table, [1][0]:AC0, [1][1]:AC1
17//generate pHTCodeCnt[idx_high][idx_low]
18        uint8_t *pCodeCnt = (uint8_t*)malloc(16);
19int i, j;
20int total_code_cnt = 0;
21        printf("\ttable id: [%d][%d]--[%s%d], dump more \n", idx_high, idx_low, idx_high==0?"DC":"AC", idx_low);
22        printf("\tCodeCntOfNBits:\t");
23for (i=0; i<16; i++) {
24int code_cnt = abr->getBits(8);
25            pCodeCnt[i] = code_cnt;
26            total_code_cnt += code_cnt;
27            printf("%2d  ", code_cnt);
28        }
29        printf("\n\ttotal code cnt: %d\n", total_code_cnt);
30        param->HTCodeRealCnt[idx_high][idx_low] = total_code_cnt;
31        param->pHTCodeCnt[idx_high][idx_low] = pCodeCnt;
32
33        uint8_t *pWidth = (uint8_t *)malloc(total_code_cnt);
34        param->pHTCodeWidth[idx_high][idx_low] = pWidth;
35        printf("\tValidCodeWidth:\t");
36for (i=0, j=0; i<16; i++, j=0) {
37while (j++ < pCodeCnt[i]) {
38                uint8_t tmp = *pWidth++ = i+1;
39                printf("%2d  ", tmp);
40            }
41        }
42        puts("");
43
44        pWidth = param->pHTCodeWidth[idx_high][idx_low];
45
46//generate pHTCode[idx_high][idx_low]
47        uint16_t *pCode = (uint16_t*)malloc(2*total_code_cnt);  //huffman code width: 2~16 bits -> may 1 bits! but HuffmanDecode3 can not handle this!
48        param->pHTCode[idx_high][idx_low] = pCode;
49bool init_flag = false;
50for (i=0; i<16; i++) {
51int j = 0;
52            uint16_t tmp;
53while (j++ < pCodeCnt[i]) {
54if ((i==1 || i==0) && (j==1) && (init_flag==false)) {
55                    *pCode = 0;            //init val
开关柜测温装置56                    init_flag = true;
57                } else if (j == 1) {        //first add x bits
58int k = i;
59int shift_bits = 1;
60while(pCodeCnt[--k] == 0) {
61                        shift_bits++;
62                    }
63                    tmp = (*pCode+1)<<shift_bits;
64                    *++pCode = tmp;
65                } else {
66                    tmp = *pCode + 1;
67                    *++pCode = tmp;
68                }
69//printf("i:%d, j:%d, (%d , %d) => %#x\n", i, j, pCodeCnt[i], pWidth[i], *pCode);
70            }
71        }
72
73//generate pHTCodeVal[idx_high][idx_low]
74        uint8_t *pCodeVal = (uint8_t*)malloc(total_code_cnt);  //huffman code width: 2~16 bits
75        param->pHTCodeVal[idx_high][idx_low] = pCodeVal;
76for (i=0; i<total_code_cnt; i++) {
77            *pCodeVal++ = abr->getBits(8);
78        }
79
80        printf("\t-----------------huffman table: [%d][%d]---------------------\n", idx_high, idx_low);
81
82        pWidth = param->pHTCodeWidth[idx_high][idx_low];
83        pCode = param->pHTCode[idx_high][idx_low];
84        pCodeVal = param->pHTCodeVal[idx_high][idx_low];
85        puts("\t[SequenceNum] (CodeWidth,  Code) -> CodeVal");
86for (i=0; i<total_code_cnt; i++) {
87            printf("\t[%11d] (%9d, %#6x) -> %#7x\n", i, *pWidth++, *pCode++, *pCodeVal++);
88        }
89        len -= (17+total_code_cnt);
90        puts("\t---------------------------------------------------------------------------------------------");
91    }
92return0;
93 }
View Code
5. SOS——FF DA
  SOS主要描述了分量号与⼏个DHT表的对应关系,以及编码profile。
2mc  其含义如代码所⽰:
1//map comp_id to huffman_table
2int parseSOS(ABitReader* abr, struct jpegParam* param)
3 {
4    printf("(%s : %d), SOS offset:%#x\n", __func__, __LINE__, abr->getOffset());
5int len = abr->getBits(16);
6int comp_cnt = abr->getBits(8);
7int i;
8for (i=0; i<comp_cnt; i++) {
9        param->ht_comp_id[i] = abr->getBits(8);
10        CHECK_EQ(param->ht_comp_id[i], i+1);
11int ht_idx = abr->getBits(8);
12        param->ht_idx[i] = ht_idx;
13int dc_idx = ht_idx>>4;
14int ac_idx = ht_idx & 0x0f;
15        printf("\tcolor_id[%d] use DC_table[%d], AC_table[%d]\n", param->ht_comp_id[i], dc_idx, ac_idx);
16    }
17    uint32_t baseline_flag = abr->getBits(24);
18    puts("\tonly support baseline profile! should pass for most cases!");
19    CHECK_EQ(baseline_flag, 0x003f00);
20
21return0;
22 }
天线制作
  需要注意的是,最后3Bytes是描述所⽤的profile,但是基本上都是使⽤的baseline,这个值是固定的。  ⼯具解析结果如下:

本文发布于:2024-09-24 03:21:16,感谢您对本站的认可!

本文链接:https://www.17tex.com/tex/4/219135.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:码字   宽度   编码   量化   信源   对应   递增
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议