數據壓縮實驗五 JPEG原理分析JPEG解碼器的調試

一、實驗原理

1.JPEG原理及編碼流程

  JPEG是常見的一種圖像格式,由ISO與CCITT建立並開發,是一個國際數字圖像壓縮標準。JPEG文件的擴展名爲.jpg或.jpeg,用有損方式去除冗餘的圖像與彩色數據,在獲取極高的壓縮率同時能展現十分生動的靜態圖像,JPEG被認爲是目前壓縮比最高的靜態圖像,它被廣泛地應用於多媒體與網絡程序中。
  根據人眼視覺特性:眼睛對亮度的敏感程度要大於對色彩的敏感程度。在圖像中,爲了利用人類的種視角特性,從而降低數據量,通常將RGB空間表示的彩色圖像變換到YCbCr顏色空間中。由於人眼對亮度Y的敏感度大於色差CrCb,因此可以在適當程度上對CrCb進行削弱以達到壓縮的目的。由於原始圖像是由很多獨立的像素組成的,其實人眼對於每個細微像素的分辨能力很弱,只有衆多像素集合一塊,才能呈現出顏色連續變化的圖像,因此圖像中相鄰兩像素點,其彩色分量在很大程度上是接近的。在一幅圖像內,包含了各種頻率的分量,但大多數分量都屬於低頻信號,只在佔圖像區域比例很小的圖像邊緣的像素才含有高頻信號。因此在對圖像編碼的時候,在圖像質量不出現可察覺損失的情況下,對包含信息量大的低頻譜區分配較多比特數,對包含信息量較低的高頻譜區域分配較少的比特數,就能達到數據壓縮目的。
  DCT變換是將圖像的色彩空間域轉換到頻譜域。DCT過程並不產生壓縮作用,其作用是將圖像數據去相關化、能量集中,去除圖像數據內部的相關性後,以便在對這些圖像數據分類處理——即對不同的頻路部分進行不同的量化。
  量化編碼是JPEG編碼中產生信息損失的根源,也是圖像質量下降的最主要原因。簡單的說,就是將頻譜領域中的每個值,除以量化表中對應的常數,且四捨五入取最接近的整數,這樣會把很多高頻的成分四捨五入爲0。量化後左上角的值較小,右下角的值較大,這樣就保持低頻分量、抑制高頻分量的目的。這一步在實現的時候會對Y進行細量化,對Cr、Cb採用粗量化,依次來提高壓縮比。因此存在兩張不同的表。
  經過DCT變換後,圖像中的低頻分量會集中在左上角,而右下角有較多的0值,因此採用Z字形編排。JPEG算法使用了差分脈衝編碼(DPCM)技術,對相鄰圖像塊之間量化DC洗漱的差值進行單獨編碼,從而再次利用相鄰特性簡化數據。並對剩餘的63個交流(AC)係數進行遊程編碼。
  爲了進一步提高壓縮比例,JPEG算法對DPCM編碼後的直流係數與遊程編碼後的交流係數使用Huffman熵編碼。使用huffman碼錶可以簡單的查表進行編碼。對於AC與DC所採用的碼錶是不同的,對於色差和亮度的霍夫曼碼錶也不同。因此應該有四個霍夫曼碼錶。
JPEG編碼流程:


這裏寫圖片描述
JPEG編碼流程如圖,解碼爲逆過程

(1)零偏置(level offset)
  對於灰度級是2n的像素,通過減去2n-1,將無符號的整數值變成有符號數;對於n=8,即將0~255的值域,通過減去128,轉換爲值域在-128~127之間的值。這樣做的目的是: 使像素的絕對值出現3位10進制的概率大大減少。
(2)8x8 DCT變換
  DCT變換是指對每個單獨的彩色圖像分量,把整個分量圖像分成8×8的圖像塊,再以8x8的圖像塊爲一個單位進行量化和編碼處理。我們可以利用DCT變換去相關的特性,去除冗餘信息,提高編碼效率。
(3)量化
  我們可以通過量化減少數據的編碼位數,提高編碼效率;因爲人眼對亮度信號比對色差信號更敏感,因此使用了兩種量化表:亮度量化值和色差量化值;根據人眼的視覺特性(對低頻敏感,對高頻不太敏感)對低頻分量採取較細的量化,對高頻分量採取較粗的量化。
(4)DC係數差分編碼
  8×8圖像塊經過DCT變換之後得到的DC直流係數有兩個特點:係數的數值比較大和相鄰8×8圖像塊的DC係數值變化不大:冗餘;根據這個特點, JPEG算法使用了差分脈衝調製編碼(DPCM)技術,對相鄰圖像塊之間量化DC係數的差值DIFF進行編碼:DIFFk=DCKDCK1 ,再對DIFF進行Huffman編碼。
(5)AC係數的之字形掃描與遊程編碼
  由於經DCT變換後,係數大多數集中在左上角,即低頻分量區,因此採用Z字形按頻率的高低順序讀出,可以出現很多連零的機會。可以使用遊程編碼。尤其在最後,如果都是零,給出EOB (End of Block)即可。zigzag掃描如下圖:
這裏寫圖片描述
  在經過之字形掃描排序後的AC係數,存在很多連0。爲了進一步提高編碼效率,因此對AC係數進行遊程編碼(RLC)處理之後,再進一步進行Huffman編碼。
(6)AC和DC係數分別進行Huffman編碼
  JPEG中共採用了四張Huffman碼錶:亮度DC、亮度AC、色度DC、色度AC,即分別對圖像的亮度和色度,直流和交流數據進行編碼處理。

2.JPEG文件格式分析

  JPEG文件的存儲格式有很多種,但最常用的是JFIF格式,即JPEG File Interchange Format。JPEG文件大體可以分爲兩個部分:

(1)標記碼:由兩個字節構成,其中,前一個字節是固定值0XFF代表了一個標記碼的開始,後一個字節不同的值代表着不同的含義。需要提醒的是,連續的多個0XFF可以理解爲一個0XFF,並表示一個標記碼的開始。另外,標記碼在文件中一般是以標記代碼的形式出現的。例如,SOI的標記代碼是0XFFD8,即,如果JPEG文件中出現了0XFFD8,則代表此處是一個SOI標記。

(2)壓縮數據:一個完整的兩字節標記碼的後面,就是該標記碼對應的壓縮數據了,它記錄了關於文件的若干信息。


這裏寫圖片描述
常見的標記碼

3.JPEG解碼流程

1 .讀取文件
2. 解析 Segment Marker
  解析 SOI
  解析 APP0:檢查標識“ JFIF”及版本,得到一些參數
  解析 DQT: 得到量化表長度(可能包含多張量化表);
       得到量化表的精度;
       得到及檢查量化表的序號(只能是 0 —— 3);
       得到量化表內容( 64 個數據)
  解析 SOF0:得到每個 sample 的比特數、長寬、顏色分量數
       得到每個顏色分量的 ID、水平採樣因子、垂直採樣因子、使用的量化表序號(與 DQT 中序號對應)
  解析 DHT:得到 Huffman 表的類型( AC、 DC)、序號,依據數據重建 Huffman 表
  解析 SOS:得到解析每個顏色分量的 DC、 AC 值所使用的 Huffman 表序號(與 DHT中序號對應)
3. 依據每個分量的水平、垂直採樣因子計算 MCU 的大小,並得到每個 MCU 中 8*8宏塊的個數
4. 對每個 MCU 解碼(依照各分量水平、垂直採樣因子對 MCU 中每個分量宏塊解碼)
  對每個宏塊進行 Huffman 解碼,得到 DCT 係數
  對每個宏塊的 DCT 係數進行 IDCT,得到 Y、 Cb、 Cr
  遇到 Segment Marker RST 時,清空之前的 DC DCT 係數
5. 解析到 EOI,解碼結束
6. 將 Y、 Cb、 Cr 轉化爲需要的色彩空間並保存。

二、關鍵代碼分析

JPEG解碼程序工程文件目錄如下:
這裏寫圖片描述
在tinyjpeg_internal文件中定義了三個結構體:


struct huffman_table(Huffman碼錶結構體)

struct huffman_table
{
  /* Fast look up table, using HUFFMAN_HASH_NBITS bits we can have directly the symbol,
   * if the symbol is <0, then we need to look into the tree table */
  short int lookup[HUFFMAN_HASH_SIZE];//快速查找到權值對應的碼字
  /* code size: give the number of bits of a symbol is encoded */
  unsigned char code_size[HUFFMAN_HASH_SIZE];//碼長對應的權值
  /* some place to store value that is not encoded in the lookup table
   * FIXME: Calculate if 256 value is enough to store all values
   */
  uint16_t slowtable[16-HUFFMAN_HASH_NBITS][256];
};


struct component (8*8宏塊結構體)

<span style="font-weight: normal;">struct component 

{
  unsigned int Hfactor;//水平採樣因子
  unsigned int Vfactor;//垂直採樣因子
  float *Q_table; //指向該宏塊使用的量化表
  struct huffman_table *AC_table;//指向該宏塊直流係數的Huffman碼錶
  struct huffman_table *DC_table;//指向該宏塊交流係數的Huffman碼錶
  short int previous_DC; /* Previous DC coefficient *///前一個塊的DC係數
  short int DCT[64]; /* DCT coef *///該塊的DCT係數,其中DCT[0]爲該塊直流,其他爲交流
#if SANITY_CHECK
  unsigned int cid;
#endif
};</span>


struct jdec_private(文件解碼信息結構體)

struct jdec_private(文件解碼信息結構體)

{
  /* Public variables */
  uint8_t *components[COMPONENTS];//分別指向YUV分量結構體的指針數組
  unsigned int width, height; /* Size of the image *///圖像的寬高
  unsigned int flags;

  /* Private variables */
  const unsigned char *stream_begin, *stream_end;//文件流的開始和結束
  unsigned int stream_length;//文件流的長度

  const unsigned char *stream; /* Pointer to the current stream *///指向當前文件流的指針
  unsigned int reservoir, nbits_in_reservoir;

  struct component component_infos[COMPONENTS];//
  float Q_tables[COMPONENTS][64]; //對YUV進行量化的量化表
  struct huffman_table HTDC[HUFFMAN_TABLES]; //DC係數編碼的Huffman碼錶
  struct huffman_table HTAC[HUFFMAN_TABLES]; //AC係數編碼的Huffman碼錶
  int default_huffman_table_initialized;
  int restart_interval;
  int restarts_to_go; /* MCUs left in this restart interval */
  int last_rst_marker_seen; /* Rst marker is incremented each time *///固定增長

  /* Temp space used after the IDCT to store each components */
  uint8_t Y[64*4], Cr[64], Cb[64];//反DCT之後存三個分量的數組

  jmp_buf jump_state;
  /* Internal Pointer use for colorspace conversion, do not modify it !!! */
  uint8_t *plane[COMPONENTS];

};

1.讀取文件

在主函數main函數中,我們打開對輸入輸出文件,並解析了輸出格式:

int main(int argc, char *argv[])
{
  int output_format = TINYJPEG_FMT_YUV420P;//將輸出格式初始化爲yuv420P
  char *output_filename, *input_filename;//定義輸入文件和輸出文件指針
  clock_t start_time, finish_time;
  unsigned int duration;
  int current_argument;
  int benchmark_mode = 0;

#if TRACE//TRACE=1,則中間代碼會編譯,TRACE=0,則會忽略
  p_trace=fopen(TRACEFILE,"w");
  if (p_trace==NULL)
  {
      printf("trace file open error!");
  }
#endif
  if (argc < 3)
    usage();

  current_argument = 1;
  while (1)
   {
     if (strcmp(argv[current_argument], "--benchmark")==0)//字符比較,若輸入了基準模式benchmark,則加1
       benchmark_mode = 1;
     else
       break;//否則跳出
     current_argument++;
   }

  if (argc < current_argument+2)
    usage();

  input_filename = argv[current_argument];//輸入文件指針指向第一個文件
  if (strcmp(argv[current_argument+1],"yuv420p")==0)
    output_format = TINYJPEG_FMT_YUV420P;
  else if (strcmp(argv[current_argument+1],"rgb24")==0)
    output_format = TINYJPEG_FMT_RGB24;
  else if (strcmp(argv[current_argument+1],"bgr24")==0)
    output_format = TINYJPEG_FMT_BGR24;
  else if (strcmp(argv[current_argument+1],"grey")==0)
    output_format = TINYJPEG_FMT_GREY;//確認文件輸出格式
  /*add by yangyulan*/
  else if (strcmp(argv[current_argument+1],"yuvone")==0)
    output_format =TINYJPEG_FMT_YUV420one;
  /*end by yangyulan*/
  else
    exitmessage("Bad format: need to be one of yuv420p, rgb24, bgr24, grey,yuvone\n");
  output_filename = argv[current_argument+2];//輸出文件指針指向第三個文件

整個JPEG解碼過程,都是由下面的covert_one函數實現:

 if (benchmark_mode)
    load_multiple_times(input_filename, output_filename, output_format);
  else
    convert_one_image(input_filename, output_filename, output_format);//調用convert函數

在convert_one_image函數中,進行解碼處理:

int convert_one_image(const char *infilename, const char *outfilename, int output_format)
{
  FILE *fp;//定義了一個文件指針
  unsigned int length_of_file;//保存文件大小
  unsigned int width, height;//保存圖像寬高
  unsigned char *buf;//緩衝區
  struct jdec_private *jdec;
  unsigned char *components[3];//定義三個字符數組

  /* 把文件中的數據讀如緩存中*/
  fp = fopen(infilename, "rb");//以只讀的形式讀取輸入文件
  if (fp == NULL)
    exitmessage("Cannot open filename\n");
  length_of_file = filesize(fp);//得到文件大小
  buf = (unsigned char *)malloc(length_of_file + 4);//爲存文件數據申請內存
  if (buf == NULL)
    exitmessage("Not enough memory for loading file\n");
  fread(buf, length_of_file, 1, fp);//將文件裏面的jpg數據讀到buf中
  fclose(fp);//關閉文件指針

  /* 解壓縮*/
  jdec = tinyjpeg_init();//初始化解壓縮成一塊表和數組的結構體
  if (jdec == NULL)
    exitmessage("Not enough memory to alloc the structure need for decompressing\n");

  if (<strong><span style="color:#ff0000;">tinyjpeg_parse_header(jdec, buf, length_of_file</span></strong>)<0)//tinyjpeg_parse_header函數在tinyjpeg.c中
    exitmessage(tinyjpeg_get_errorstring(jdec));

  /* Get the size of the image */
  tinyjpeg_get_size(jdec, &width, &height);//獲取圖像的大小

  snprintf(error_string, sizeof(error_string),"Decoding JPEG image...\n");
  if (<strong><span style="color:#ff0000;">tinyjpeg_decode</span></strong>(jdec, output_format) < 0)
    exitmessage(tinyjpeg_get_errorstring(jdec));

  /* 
   * Get address for each plane (not only max 3 planes is supported), and
   * depending of the output mode, only some components will be filled 
   * RGB: 1 plane, YUV420P: 3 planes, GREY: 1 plane
   */
  tinyjpeg_get_components(jdec, components);

  /* 按所要求的個數輸出文件*/
  switch (output_format)
   {
    case TINYJPEG_FMT_RGB24:
    case TINYJPEG_FMT_BGR24:
      write_tga(outfilename, output_format, width, height, components);
      break;
    case TINYJPEG_FMT_YUV420P:
      write_yuv(outfilename, width, height, components);
      break;
    case TINYJPEG_FMT_GREY:
      write_pgm(outfilename, width, height, components);
      break;
   }

  /* Only called this if the buffers were allocated by tinyjpeg_decode() */
  tinyjpeg_free(jdec);
  /* else called just free(jdec); */

  free(buf);
  return 0;
}  

在main函數中,條件判斷屬於哪一格式輸出:

 if (strcmp(argv[current_argument+1],"yuv420p")==0)
    output_format = TINYJPEG_FMT_YUV420P;
  else if (strcmp(argv[current_argument+1],"rgb24")==0)
    output_format = TINYJPEG_FMT_RGB24;
  else if (strcmp(argv[current_argument+1],"bgr24")==0)
    output_format = TINYJPEG_FMT_BGR24;
  else if (strcmp(argv[current_argument+1],"grey")==0)
    output_format = TINYJPEG_FMT_GREY;//確認文件輸出格式

在convert_one_image函數中,進行格式判斷,並調用相應的輸出函數:

  /* Save it */
  switch (output_format)
   {
    case TINYJPEG_FMT_RGB24:
    case TINYJPEG_FMT_BGR24:
      write_tga(outfilename, output_format, width, height, components);
      break;
    case TINYJPEG_FMT_YUV420P:
      write_yuv(outfilename, width, height, components);
      break;
    case TINYJPEG_FMT_GREY:
      write_pgm(outfilename, width, height, components);
      break;
   }

輸出爲yuv,則調用write_yuv函數:

static void write_yuv(const char *filename, int width, int height, unsigned char **components)
{
 FILE *F;
 char temp[1024];

 snprintf(temp, 1024, "%s.yuv", filename);
 F = fopen(temp, "ab");
 fwrite(components[0], width, height, F);
 fclose(F);
 snprintf(temp, 1024, "%s.yuv", filename);
 F = fopen(temp, "ab"); 
 fwrite(components[1], width*height/4, 1, F);
 fclose(F);
 snprintf(temp, 1024, "%s.yuv", filename);
 F = fopen(temp, "ab");
 fwrite(components[2], width*height/4, 1, F);
 fclose(F);
 printf("ok");
}

2.解析 Segment Marker(tinyjpeg_parse_header中)

解析文件頭:

int tinyjpeg_parse_header(struct jdec_private *priv, const unsigned char *buf, unsigned int size)
{
  int ret;

  /* Identify the file */
  //解析SOI:
  if ((buf[0] != 0xFF) || (buf[1] != SOI))//文件開頭是0xFF,D8即SOI文件開始標誌,開始不是FFD8則報錯
    snprintf(error_string, sizeof(error_string),"Not a JPG file ?\n");

  priv->stream_begin = buf+2;//沒錯則將文件流的開始向後移兩個字節
  priv->stream_length = size-2;//將剩餘長度也減兩個字節
  priv->stream_end = priv->stream_begin + priv->stream_length;//定位到文件最後

  ret = <strong><span style="color:#ff0000;">parse_JFIF</span></strong>(priv, priv->stream_begin);

  return ret;
}
static int parse_JFIF(struct jdec_private *priv, const unsigned char *stream)//解析JFIF
{
  int chuck_len;
  int marker;
  int sos_marker_found = 0;
  int dht_marker_found = 0;
  const unsigned char *next_chunck;


  /* Parse marker */
  while (!sos_marker_found)//循環一直讀到掃描開始,即編碼數據塊
   {
     if (*stream++ != 0xff)
       goto bogus_jpeg_format;
     /* Skip any padding ff byte (this is normal) */
     while (*stream == 0xff)
       stream++;

     marker = *stream++;//E0賦值給marker,E0==APP0
     chuck_len = be16_to_cpu(stream);
     next_chunck = stream + chuck_len;
     switch (marker)
      {
       case SOF:
     if (<strong><span style="color:#ff0000;">parse_SOF</span></strong>(priv, stream) < 0)
       return -1;
     break;
       case DQT:
     if (<strong><span style="color:#ff0000;">parse_DQT</span></strong>(priv, stream) < 0)
       return -1;
     break;
       case SOS:
     if (<span style="color:#ff0000;"><strong>parse_SOS</strong></span>(priv, stream) < 0)
       return -1;
     sos_marker_found = 1;
     break;
       case DHT:
     if (<span style="color:#ff0000;"><strong>parse_DHT</strong></span>(priv, stream) < 0)
       return -1;
     dht_marker_found = 1;
     break;
       case DRI:
     if (parse_DRI(priv, stream) < 0)
       return -1;
     break;
       default:
#if TRACE
    fprintf(p_trace,"> Unknown marker %2.2x\n", marker);
    fflush(p_trace);
#endif
     break;
      }

     stream = next_chunck;//跳到下一個數據塊,再判斷
   }

  if (!dht_marker_found) {
#if TRACE
      fprintf(p_trace,"No Huffman table loaded, using the default one\n");
      fflush(p_trace);
#endif
    build_default_huffman_tables(priv);
  }

#ifdef SANITY_CHECK
  if (   (priv->component_infos[cY].Hfactor < priv->component_infos[cCb].Hfactor)
      || (priv->component_infos[cY].Hfactor < priv->component_infos[cCr].Hfactor))
    snprintf(error_string, sizeof(error_string),"Horizontal sampling factor for Y should be greater than horitontal sampling factor for Cb or Cr\n");
  if (   (priv->component_infos[cY].Vfactor < priv->component_infos[cCb].Vfactor)
      || (priv->component_infos[cY].Vfactor < priv->component_infos[cCr].Vfactor))
    snprintf(error_string, sizeof(error_string),"Vertical sampling factor for Y should be greater than vertical sampling factor for Cb or Cr\n");
  if (   (priv->component_infos[cCb].Hfactor!=1) 
      || (priv->component_infos[cCr].Hfactor!=1)
      || (priv->component_infos[cCb].Vfactor!=1)
      || (priv->component_infos[cCr].Vfactor!=1))
    snprintf(error_string, sizeof(error_string),"Sampling other than 1x1 for Cr and Cb is not supported");
#endif

  return 0;
bogus_jpeg_format:
#if TRACE
  fprintf(p_trace,"Bogus jpeg format\n");
  fflush(p_trace);
#endif
  return -1;
}

解析DQT:

//解析量化表
static int parse_DQT(struct jdec_private *priv, const unsigned char *stream)
{
  int qi;
  /*add by yangyulan*/

  /*end by yangyulan*/
  float *table;//定義了用於指向量化表的指針
  const unsigned char *dqt_block_end;//指向量化表的結束地址
#if TRACE
  fprintf(p_trace,"> DQT marker\n");
  fflush(p_trace);
#endif
  dqt_block_end = stream + be16_to_cpu(stream);//量化塊結束的位置
  stream += 2;  /* Skip length */  //跳過兩字節的存儲長度,如00 43

  while (stream < dqt_block_end)//當還在表內
   {
     qi = *stream++;//將量化表中的值逐個賦給qi
#if SANITY_CHECK
     if (qi>>4)
       snprintf(error_string, sizeof(error_string),"16 bits quantization table is not supported\n");
     if (qi>4)
       snprintf(error_string, sizeof(error_string),"No more 4 quantization table is supported (got %d)\n", qi);
#endif
     table = priv->Q_tables[qi];//初始化量化表
     build_quantization_table(table, stream);//得到量化表內容,將文檔數據流賦值給量化表
     stream += 64;//指向下一塊
   }

build_quantization_table函數:

static void build_quantization_table(float *qtable, const unsigned char *ref_table)
{
  int i, j;
  static const double aanscalefactor[8] = {//比例因子
     1.0, 1.387039845, 1.306562965, 1.175875602,
     1.0, 0.785694958, 0.541196100, 0.275899379
  };
  const unsigned char *zz = zigzag;//zigzag爲之字形掃描順序係數
  for (i=0; i<8; i++) {
     for (j=0; j<8; j++) {
       *qtable++ = ref_table[*zz++] * aanscalefactor[i] * aanscalefactor[j];
     }
   }
}

Zigzag數組:

static const unsigned char zigzag[64] = //定義之字形掃描順序
{
   0,  1,  5,  6, 14, 15, 27, 28,
   2,  4,  7, 13, 16, 26, 29, 42,
   3,  8, 12, 17, 25, 30, 41, 43,
   9, 11, 18, 24, 31, 40, 44, 53,
  10, 19, 23, 32, 39, 45, 52, 54,
  20, 22, 33, 38, 46, 51, 55, 60,
  21, 34, 37, 47, 50, 56, 59, 61,
  35, 36, 48, 49, 57, 58, 62, 63
};

解析SOF:

static int parse_SOF(struct jdec_private *priv, const unsigned char *stream)//基線餘弦變換
{
  int i, width, height, nr_components, cid, sampling_factor;
  int Q_table;
  struct component *c;
#if TRACE
  fprintf(p_trace,"> SOF marker\n");
  fflush(p_trace);
#endif
  print_SOF(stream);//打印SOF,即獲得圖像寬高和圖像精度,並打印出來

  height = be16_to_cpu(stream+3);//獲得圖像高度
  width  = be16_to_cpu(stream+5);//獲得圖像寬度
  nr_components = stream[7];//獲得圖像精度
#if SANITY_CHECK
  if (stream[2] != 8)
    snprintf(error_string, sizeof(error_string),"Precision other than 8 is not supported\n");
  if (width>JPEG_MAX_WIDTH || height>JPEG_MAX_HEIGHT)
    snprintf(error_string, sizeof(error_string),"Width and Height (%dx%d) seems suspicious\n", width, height);
  if (nr_components != 3)
    snprintf(error_string, sizeof(error_string),"We only support YUV images\n");
  if (height%16)
    snprintf(error_string, sizeof(error_string),"Height need to be a multiple of 16 (current height is %d)\n", height);
  if (width%16)
    snprintf(error_string, sizeof(error_string),"Width need to be a multiple of 16 (current Width is %d)\n", width);
#endif
  stream += 8;//分別解析YUV分量
  for (i=0; i<nr_components; i++) {
     cid = *stream++;//該分量ID
     sampling_factor = *stream++;//該分量的採樣率
     Q_table = *stream++;//該分量的量化表
     c = &priv->component_infos[i];//指向該分量的結構體指針
#if SANITY_CHECK
     c->cid = cid;
     if (Q_table >= COMPONENTS)
       snprintf(error_string, sizeof(error_string),"Bad Quantization table index (got %d, max allowed %d)\n", Q_table, COMPONENTS-1);
#endif
     c->Vfactor = sampling_factor&0xf;//該分量的垂直採樣率
     c->Hfactor = sampling_factor>>4;//水平採樣率
     c->Q_table = priv->Q_tables[Q_table];//該分量使用的量化表
#if TRACE
     fprintf(p_trace,"Component:%d  factor:%dx%d  Quantization table:%d\n",
           cid, c->Hfactor, c->Hfactor, Q_table );
     fflush(p_trace);
#endif

  }
  priv->width = width;//寬高的信息
  priv->height = height;
#if TRACE
  fprintf(p_trace,"< SOF marker\n");
  fflush(p_trace);
#endif

  return 0;
}

解析DHT:

static int parse_DHT(struct jdec_private *priv, const unsigned char *stream)//解析Huffman碼錶
{
  unsigned int count, i;
  unsigned char huff_bits[17];//碼長從1到16的數目數組
  int length, index;
  /*add by yangyulan*/
  FILE *hufftable;
  hufftable=fopen("huffmantable_file.txt","ab"); 
  /*end by yangyulan*/

  length = be16_to_cpu(stream) - 2;//得到碼長(可能包含多張表)
  stream += 2;  /* Skip length */
#if TRACE
  fprintf(p_trace,"> DHT marker (length=%d)\n", length);
  fflush(p_trace);
#endif

  while (length>0) {//如果碼長大於0
     index = *stream++;//把該塊賦值給index

     /* We need to calculate the number of bytes 'vals' will takes */
     huff_bits[0] = 0;//碼長爲0的爲0個,下標與碼長相對應
     count = 0;//總碼字數
     for (i=1; i<17; i++) {
    huff_bits[i] = *stream++;//各碼長的個數分別賦值
    count += huff_bits[i];//總的碼字數
     }
#if SANITY_CHECK
     if (count >= HUFFMAN_BITS_SIZE)
       snprintf(error_string, sizeof(error_string),"No more than %d bytes is allowed to describe a huffman table", HUFFMAN_BITS_SIZE);
     if ( (index &0xf) >= HUFFMAN_TABLES)
       snprintf(error_string, sizeof(error_string),"No more than %d Huffman tables is supported (got %d)\n", HUFFMAN_TABLES, index&0xf);
#if TRACE
     fprintf(p_trace,"Huffman table %s[%d] length=%d\n", (index&0xf0)?"AC":"DC", index&0xf, count);
     fflush(p_trace);
     /*add by yangyulan*/
     fprintf(hufftable,"Huffman table %s[%d] length=%d\n", (index&0xf0)?"AC":"DC", index&0xf, count);
     fflush(hufftable);
     /*end by yangyulan*/

#endif
#endif

     if (index & 0xf0 )//高位爲1則爲AC表
     {  
         build_huffman_table(huff_bits, stream, &priv->HTAC[index&0xf]);
     }

     else//否則爲DC表
     {
         build_huffman_table(huff_bits, stream, &priv->HTDC[index&0xf]);

     }

     length -= 1;
     length -= 16;
     length -= count;
     stream += count;
  }
#if TRACE
  fprintf(p_trace,"< DHT marker\n");
  fflush(p_trace);
#endif
  return 0;
}

重建 Huffman 表 :

static void build_huffman_table(const unsigned char *bits, const unsigned char *vals, struct huffman_table *table)//創建碼錶
{
  unsigned int i, j, code, code_size, val, nbits;
  unsigned char huffsize[HUFFMAN_BITS_SIZE+1], *hz;
  unsigned int huffcode[HUFFMAN_BITS_SIZE+1], *hc;
  int next_free_entry;
  /*add by yangyulan*/
    FILE *hufftable;
     hufftable=fopen("huffmantable_file.txt","ab"); 
  /*end by yangyulan*/

  /*
   * Build a temp array 
   *   huffsize[X] => numbers of bits to write vals[X]
   */
  hz = huffsize;
  for (i=1; i<=16; i++)//碼長爲1~16
   {
     for (j=1; j<=bits[i]; j++)//碼長爲1~16的個數
       *hz++ = i;//第1~bits[1]的碼長都爲1...
   }
  *hz = 0;//最後碼長賦爲0

  memset(table->lookup, 0xff, sizeof(table->lookup));
  for (i=0; i<(16-HUFFMAN_HASH_NBITS); i++)
    table->slowtable[i][0] = 0;//都初始化爲0

  /* Build a temp array
   *   huffcode[X] => code used to write vals[X]
   */
  code = 0;
  hc = huffcode;//指向碼字
  hz = huffsize;//重新指向
  nbits = *hz;//從第一個開始,碼長賦值
  while (*hz)//碼長大於0 時
   {
     while (*hz == nbits)//碼長未改變時
      {
    *hc++ = code++;//碼字加1
    hz++;//指向下一個碼字
      }
     code <<= 1;//否則碼字加1補0
     nbits++;
   }

  /*
   * Build the lookup table, and the slowtable if needed.
   */
  next_free_entry = -1;
  for (i=0; huffsize[i]; i++)//當各碼長碼字數不爲0 時
   {
     val = vals[i];//vals[i]表示i碼長碼字個數
     code = huffcode[i];//碼字
     code_size = huffsize[i];///碼長
    #if TRACE
     fprintf(p_trace,"val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size);
     fflush(p_trace);
     /*add by yangyulan*/
      fprintf(hufftable,"val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size);
      fflush(hufftable);
     /*end by yangyulan*/

    #endif

     table->code_size[val] = code_size;
     if (code_size <= HUFFMAN_HASH_NBITS)
      {
    /*
     * Good: val can be put in the lookup table, so fill all value of this
     * column with value val 
     */
    int repeat = 1UL<<(HUFFMAN_HASH_NBITS - code_size);
    code <<= HUFFMAN_HASH_NBITS - code_size;
    while ( repeat-- )
      table->lookup[code++] = val;

      }
     else
      {
    /* Perhaps sorting the array will be an optimization */
    uint16_t *slowtable = table->slowtable[code_size-HUFFMAN_HASH_NBITS-1];
    while(slowtable[0])
      slowtable+=2;
    slowtable[0] = code;
    slowtable[1] = val;
    slowtable[2] = 0;
    /* TODO: NEED TO CHECK FOR AN OVERFLOW OF THE TABLE */
      }

   }

}

解析SOS:

static int parse_SOS(struct jdec_private *priv, const unsigned char *stream)
{
  unsigned int i, cid, table;
  unsigned int nr_components = stream[2];//獲得分量數
#if TRACE
  fprintf(p_trace,"> SOS marker\n");
  fflush(p_trace);
#endif

#if SANITY_CHECK
  if (nr_components != 3)
    snprintf(error_string, sizeof(error_string),"We only support YCbCr image\n");
#endif

  stream += 3;//指向Y分量ID
  for (i=0;i<nr_components;i++) {
     cid = *stream++;//ID賦值給cid
     table = *stream++;//對應的量化和Huffman碼錶
#if SANITY_CHECK
     if ((table&0xf)>=4)
    snprintf(error_string, sizeof(error_string),"We do not support more than 2 AC Huffman table\n");
     if ((table>>4)>=4)
    snprintf(error_string, sizeof(error_string),"We do not support more than 2 DC Huffman table\n");
     if (cid != priv->component_infos[i].cid)
        snprintf(error_string, sizeof(error_string),"SOS cid order (%d:%d) isn't compatible with the SOF marker (%d:%d)\n",
          i, cid, i, priv->component_infos[i].cid);
#if TRACE
     fprintf(p_trace,"ComponentId:%d  tableAC:%d tableDC:%d\n", cid, table&0xf, table>>4);
     fflush(p_trace);
#endif
#endif
     priv->component_infos[i].AC_table = &priv->HTAC[table&0xf];//得到每個顏色分量的ACHuffman碼錶
     priv->component_infos[i].DC_table = &priv->HTDC[table>>4];//得到每個顏色分量的DCHuffman碼錶
  }
  priv->stream = stream+3;//指向熵編碼數據流的開始
#if TRACE
  fprintf(p_trace,"< SOS marker\n");
  fflush(p_trace);
#endif
  return 0;
}

3.依據每個分量的水平、垂直採樣因子計算 MCU 的大小,得到每個 MCU 中 8*8宏塊個數

 xstride_by_mcu = ystride_by_mcu = 8;//初始化爲4:4:4的情況,即MCU的寬和高都爲8像素
  if ((priv->component_infos[cY].Hfactor | priv->component_infos[cY].Vfactor) == 1) {//Y分量的垂直和水平採樣因子相等
     decode_MCU = decode_mcu_table[0];//每個MCU就包括1個Y分量
     convert_to_pixfmt = colorspace_array_conv[0];
#if TRACE
     fprintf(p_trace,"Use decode 1x1 sampling\n");
     fflush(p_trace);
#endif
  } else if (priv->component_infos[cY].Hfactor == 1) {//如果水平採樣因子爲1,垂直爲2,
     decode_MCU = decode_mcu_table[1];//每個MCU 包含2個Y分量
     convert_to_pixfmt = colorspace_array_conv[1];
     ystride_by_mcu = 16;//一個MCU的高爲16像素
#if TRACE
     fprintf(p_trace,"Use decode 1x2 sampling (not supported)\n");
     fflush(p_trace);
#endif
  } else if (priv->component_infos[cY].Vfactor == 2) {//如果水平採樣因子爲2,垂直爲2,
     decode_MCU = decode_mcu_table[3];//每個MCU 包含4個Y分量
     convert_to_pixfmt = colorspace_array_conv[3];
     xstride_by_mcu = 16;//一個mcu的寬爲16像素
     ystride_by_mcu = 16;//一個mcu的高爲16像素
#if TRACE 
     fprintf(p_trace,"Use decode 2x2 sampling\n");
     fflush(p_trace);
#endif
  } else {//如果水平採樣因子爲2,垂直爲1
     decode_MCU = decode_mcu_table[2];//每個MCU 包含2個Y分量
     convert_to_pixfmt = colorspace_array_conv[2];
     xstride_by_mcu = 16;//一個mcu的寬爲16
#if TRACE
     fprintf(p_trace,"Use decode 2x1 sampling\n");
     fflush(p_trace);
#endif
  }

4.對每個 MCU 解碼(依照各分量水平、垂直採樣因子對 MCU 中每個分量宏塊解碼)

/*
 * Decode all the 3 components for 1x1 
 */
static void decode_MCU_1x1_3planes(struct jdec_private *priv)
{
  // Y
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y, 8);

  // Cb
  process_Huffman_data_unit(priv, cCb);
  IDCT(&priv->component_infos[cCb], priv->Cb, 8);

  // Cr
  process_Huffman_data_unit(priv, cCr);
  IDCT(&priv->component_infos[cCr], priv->Cr, 8);
}

/*
 * Decode a 1x1 directly in 1 color
 */
static void decode_MCU_1x1_1plane(struct jdec_private *priv)//採樣格式爲1:1:1
{
  // Y
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y, 8);

  // Cb
  process_Huffman_data_unit(priv, cCb);
  IDCT(&priv->component_infos[cCb], priv->Cb, 8);

  // Cr
  process_Huffman_data_unit(priv, cCr);
  IDCT(&priv->component_infos[cCr], priv->Cr, 8);
}


/*
 * Decode a 2x1
 *  .-------.
 *  | 1 | 2 |
 *  `-------'
 */
static void decode_MCU_2x1_3planes(struct jdec_private *priv)//採樣格式爲2:1:1
{
  // Y
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y, 16);
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y+8, 16);

  // Cb
  process_Huffman_data_unit(priv, cCb);
  IDCT(&priv->component_infos[cCb], priv->Cb, 8);

  // Cr
  process_Huffman_data_unit(priv, cCr);
  IDCT(&priv->component_infos[cCr], priv->Cr, 8);
}

/*
 * Decode a 2x1
 *  .-------.
 *  | 1 | 2 |
 *  `-------'
 */
static void decode_MCU_2x1_1plane(struct jdec_private *priv)
{
  // Y
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y, 16);
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y+8, 16);

  // Cb
  process_Huffman_data_unit(priv, cCb);

  // Cr
  process_Huffman_data_unit(priv, cCr);
}


/*
 * Decode a 2x2
 *  .-------.
 *  | 1 | 2 |
 *  |---+---|
 *  | 3 | 4 |
 *  `-------'
 */
static void decode_MCU_2x2_3planes(struct jdec_private *priv)
{
  // Y
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y, 16);
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y+8, 16);
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y+64*2, 16);
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y+64*2+8, 16);

  // Cb
  process_Huffman_data_unit(priv, cCb);
  IDCT(&priv->component_infos[cCb], priv->Cb, 8);

  // Cr
  process_Huffman_data_unit(priv, cCr);
  IDCT(&priv->component_infos[cCr], priv->Cr, 8);
}

/*
 * Decode a 2x2 directly in GREY format (8bits)
 *  .-------.
 *  | 1 | 2 |
 *  |---+---|
 *  | 3 | 4 |
 *  `-------'
 */
static void decode_MCU_2x2_1plane(struct jdec_private *priv)
{
  // Y
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y, 16);
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y+8, 16);
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y+64*2, 16);
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y+64*2+8, 16);

  // Cb
  process_Huffman_data_unit(priv, cCb);

  // Cr
  process_Huffman_data_unit(priv, cCr);
}

/*
 * Decode a 1x2 mcu
 *  .---.
 *  | 1 |
 *  |---|
 *  | 2 |
 *  `---'
 */
static void decode_MCU_1x2_3planes(struct jdec_private *priv)
{
  // Y
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y, 8);
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y+64, 8);

  // Cb
  process_Huffman_data_unit(priv, cCb);
  IDCT(&priv->component_infos[cCb], priv->Cb, 8);

  // Cr
  process_Huffman_data_unit(priv, cCr);
  IDCT(&priv->component_infos[cCr], priv->Cr, 8);
}

/*
 * Decode a 1x2 mcu
 *  .---.
 *  | 1 |
 *  |---|
 *  | 2 |
 *  `---'
 */
static void decode_MCU_1x2_1plane(struct jdec_private *priv)
{
  // Y
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y, 8);
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y+64, 8);

  // Cb
  process_Huffman_data_unit(priv, cCb);

  // Cr
  process_Huffman_data_unit(priv, cCr);
}

對一個8*8的彩色分量單元進行解碼:

static void process_Huffman_data_unit(struct jdec_private *priv, int component)
{
  unsigned char j;
  unsigned int huff_code;
  unsigned char size_val, count_0;


  struct component *c = &priv->component_infos[component];
  short int DCT[64];


  /* 初始化DCT係數表*/
  memset(DCT, 0, sizeof(DCT));

  /* DC係數解碼*/
  huff_code = get_next_huffman_code(priv, c->DC_table);
  //trace("+ %x\n", huff_code);
  if (huff_code) {
     get_nbits(priv->reservoir, priv->nbits_in_reservoir, priv->stream, huff_code, DCT[0]);// 查表的 DC DCT 係數(殘值)<span style="text-align: -webkit-auto;"><br style="orphans: 2; text-align: -webkit-auto; widows: 2;"></span>
     DCT[0] += c->previous_DC;
     c->previous_DC = DCT[0];// DC 係數採用差分編碼, 恢復原值<span style="font-family:宋體;color:#008000;"><span style="font-size: 10pt; text-align: -webkit-auto;"><br style="orphans: 2; text-align: -webkit-auto; widows: 2;"></span></span>
  } else {
     DCT[0] = c->previous_DC;
  }


  /* AC係數解碼 */
  j = 1;
  while (j<64)
   {
     huff_code = get_next_huffman_code(priv, c->AC_table);
     //trace("- %x\n", huff_code);

     size_val = huff_code & 0xF;// Amplitude 幅度
     count_0 = huff_code >> 4;// 零遊程長度
     if (size_val == 0)// 0 不是一個有效的 Amplitude 值,這裏做零遊程標誌
      { /* 零遊程 */
    if (count_0 == 0)
      break;    /* EOB found, go out */
    else if (count_0 == 0xF)
      j += 16;  /* skip 16 zeros */
      }
     else
      {
    j += count_0;   /* 忽略零遊程 */
    if (__unlikely(j >= 64))//出錯了
     {
       snprintf(error_string, sizeof(error_string), "Bad huffman data (buffer overflow)");
       break;
     }
    get_nbits(priv->reservoir, priv->nbits_in_reservoir, priv->stream, size_val, DCT[j]);// 查表得到 AC DCT 係數
    j++;
      }
   }

  for (j = 0; j < 64; j++)
    c->DCT[j] = DCT[zigzag[j]];
}     

5.解完所有 MCU,解碼結束

for (y=0; y < priv->height/ystride_by_mcu; y++) // 行循環
{
//trace("Decoding row %d\n", y);
    priv->plane[0] = priv->components[0] + (y * bytes_per_blocklines[0]);
    priv->plane[1] = priv->components[1] + (y * bytes_per_blocklines[1]);
    priv->plane[2] = priv->components[2] + (y * bytes_per_blocklines[2]);
    for (x=0; x < priv->width; x+=xstride_by_mcu) // 列循環
    {
        decode_MCU(priv); // 解碼( Huffman 解碼 + IDCT)
        convert_to_pixfmt(priv);
        priv->plane[0] += bytes_per_mcu[0];
        priv->plane[1] += bytes_per_mcu[1];
        priv->plane[2] += bytes_per_mcu[2];
        if (priv->restarts_to_go>0)
        {
            priv->restarts_to_go--;
            if (priv->restarts_to_go == 0)
            {
                priv->stream -= (priv->nbits_in_reservoir/8);
                resync(priv); // 清空 preDC(所有顏色分量)
                if (find_next_rst_marker(priv) < 0) // 查找 RST 標記
                return -1;
            }
        }
    }
}

四:實驗結果

1.yuv文件輸出:

這裏寫圖片描述

2.量化表與Huffman碼錶:

這裏寫圖片描述這裏寫圖片描述

3.輸出DC、AC圖像並經過huff_run.exe統計概率分佈

這裏寫圖片描述這裏寫圖片描述
這裏寫圖片描述這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章