欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

数据压缩实验五 JPEG原理分析JPEG解码器的调试

程序员文章站 2022-07-14 21:55:18
...

一、实验原理

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原理分析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扫描如下图:
数据压缩实验五 JPEG原理分析JPEG解码器的调试
  在经过之字形扫描排序后的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)压缩数据:一个完整的两字节标记码的后面,就是该标记码对应的压缩数据了,它记录了关于文件的若干信息。


数据压缩实验五 JPEG原理分析JPEG解码器的调试
常见的标记码

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解码程序工程文件目录如下:
数据压缩实验五 JPEG原理分析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文件输出:

数据压缩实验五 JPEG原理分析JPEG解码器的调试

2.量化表与Huffman码表:

数据压缩实验五 JPEG原理分析JPEG解码器的调试数据压缩实验五 JPEG原理分析JPEG解码器的调试

3.输出DC、AC图像并经过huff_run.exe统计概率分布

数据压缩实验五 JPEG原理分析JPEG解码器的调试数据压缩实验五 JPEG原理分析JPEG解码器的调试
数据压缩实验五 JPEG原理分析JPEG解码器的调试数据压缩实验五 JPEG原理分析JPEG解码器的调试