JPEG解碼——(3)文件頭解析


  與具體的編碼數據空間相比,jpeg文件頭占據非常小乃至可以忽略不計的大小。

  仍然拿JPEG解碼--(1)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
 2 int parseDHT(ABitReader* abr, struct jpegParam* param)
 3 {
 4     printf("(%s : %d), DHT offset:%#x\n", __func__, __LINE__, abr->getOffset());
 5     int len = abr->getBits(16);
 6     len -= 2;
 7     while (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);
19         int i, j;
20         int total_code_cnt = 0;
21         printf("\ttable id: [%d][%d]--[%s%d], dump more detail info...\n", idx_high, idx_low, idx_high==0?"DC":"AC", idx_low);
22         printf("\tCodeCntOfNBits:\t");
23         for (i=0; i<16; i++) {
24             int 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");
36         for (i=0, j=0; i<16; i++, j=0) {
37             while (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;
49         bool init_flag = false;
50         for (i=0; i<16; i++) {
51             int j = 0;
52             uint16_t tmp;
53             while (j++ < pCodeCnt[i]) {
54                 if ((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
58                     int k = i;
59                     int shift_bits = 1;
60                     while(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;
76         for (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");
86         for (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     }
92     return 0;
93 }
View Code

 5. SOS——FF DA

  SOS主要描述了分量號與幾個DHT表的對應關系,以及編碼profile。

  其含義如代碼所示:

 1 //map comp_id to huffman_table
 2 int parseSOS(ABitReader* abr, struct jpegParam* param)
 3 {
 4     printf("(%s : %d), SOS offset:%#x\n", __func__, __LINE__, abr->getOffset());
 5     int len = abr->getBits(16);
 6     int comp_cnt = abr->getBits(8);
 7     int i;
 8     for (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);
11         int ht_idx = abr->getBits(8);
12         param->ht_idx[i] = ht_idx;
13         int dc_idx = ht_idx>>4;
14         int 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 
21     return 0;
22 }

  需要注意的是,最后3Bytes是描述所用的profile,但是基本上都是使用的baseline,這個值是固定的。

  工具解析結果如下:

6. 其他補充

  有些圖片帶縮略圖——thumbnail,其存在的目的是解碼大圖太耗費時間,而如果jpeg圖片中還嵌套着另外一份小圖(縮略圖),則利用該小圖解碼可以縮短解碼大圖時間,

來達到盡快展示給用戶看的目的。

  jpeg文件格式支持這種方式,該小圖其實也是一幅分辨率較小的jpeg圖片,其一般會放在APP1下的Exif段。

  帶thumbnail的圖片,thumbnail的部分,其完整保留了前面幾個小節介紹的各個TAG段。文件中,如果搜索"FF E0",會發現找到兩個,一個是主圖的,另外一個是縮略圖的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM