JPEG原理分析及JPEG解码器的解析

JPEG原理分析及JPEG解码器的调试

原理分析

JPEG编解码流程图

JPEG编解码流程

  1. 将传入的图像进行零电平偏置,其实就是将所有像素的数值减去128,将其范围从[0,255]变成[-128,127]。
  2. 进行8×8的DCT变换,实现能量集中和去相关,便于去除图像的空间冗余度。
  3. 使用量化表进行量化。利用根据人眼视觉特性设计的量化的量化矩阵进行量化,低频细量化,高频粗量化,进而减少视觉冗余。
  4. 最后对量化后的直流系数进行差分和VLC编码;对交流系数进行zig-zag扫描和游程编码最后再进行VLC编码。从而减少数据冗余。
DC系数编码

由于直流系数 F(0,0)反映了该子图像中包含的直流成分,通常较大,又由于两个相邻的子图像的直流系数通常具有较大的相关性,所以对 DC 系数采用差值脉冲编码(DPCM),即对本像素块直流系数与前一像素块直流系数的差值进行无损编码。

AC系数编码

首先,进行游程编码(RLC),并在最后加上块结束码(EOB);然后,系数序列分组,将非零系数和它前面的相邻的全部零系数分在一组内;每组用两个符号表示[(Run,Size),(Amplitude)]
其中Amplitude表示非零系数的幅度值;Run:表示零的游程即零的个数;Size:
表示非零系数的幅度值的编码位数;

JPEG文件格式

Segment组织形式

JPEG 在文件中以 Segment 的形式组织,它具有以下特点:

  • 均以 0xFF 开始,后跟 1 byte 的 Marker 和 2 byte 的 Segment length(该长度包含Length本身所占用的 2 byte,指的是length及其后的数据长度)
  • 采用 Motorola 序(相对于 Intel 序),即保存时高位在前,低位在后;
  • Data 部分中,0xFF 后若为 0x00,则跳过此字节不予处理;
JPEG 的 Segment Marker
non-hierarchical Huffman coding
SymbolCode Assignment(0xFF+Marker)Description
S O F 0 SOF_0 SOF00xFFC0Baseline DCT
S O F 1 SOF_1 SOF10xFFC1Extended sequential DCT
S O F 2 SOF_2 SOF20xFFC2Progressive DCT
S O F 3 SOF_3 SOF30xFFC3Spatial (sequential) lossless
hierarchical Huffman coding
SymbolCode Assignment(0xFF+Marker)Description
S O F 5 SOF_5 SOF50xFFC5Differential sequential DCT
S O F 6 SOF_6 SOF60xFFC6Differential progressive DCT
S O F 7 SOF_7 SOF70xFFC7Differential spatial lossless
non-hierarchical arithmetic coding
SymbolCode Assignment(0xFF+Marker)Description
JPG0xFFC8Reserved for JPEG extensions
S O F 9 SOF_9 SOF90xFFC9Extended sequential DCT
S O F 10 SOF_{10} SOF100xFFCAProgressive DCT
S O F 11 SOF_{11} SOF110xFFCBSpatial (sequential) Lossless
hierarchical arithmetic coding
SymbolCode Assignment(0xFF+Marker)Description
S O F 1 3 SOF_13 SOF130xFFCDDifferential sequential DCT
S O F 1 4 SOF_14 SOF140xFFCEDifferential progressive DCT
S O F 15 SOF_{15} SOF150xFFCFDifferential progressive DCT
Huffman table specification
SymbolCode Assignment(0xFF+Marker)Description
DHT0xFFC4Define Huffman table(s)
arithmetic coding conditioning specification
SymbolCode Assignment(0xFF+Marker)Description
DAC0xFFCCDefine arithmetic conditioning table
Restart interval termination
SymbolCode Assignment(0xFF+Marker)Description
RSTm0xFFD0~0xFFD7Restart with modulo 8 counter m
Other marker
SymbolCode Assignment(0xFF+Marker)Description
SOI0xFFD8Start of image
EOI0xFFD9End of image
SOS0xFFDAStart of scan
DQT0xFFDBDefine quantization table(s)
DNL0xFFDCDefine number of lines
DRI0xFFDDDefine restart interval
DHP0xFFDEDefine hierarchical progression
EXP0xFFDFExpand reference image(s)
A P P n APP_n APPn0xFFE0~0xFFEFReserved for application use
J P G n JPG_n JPGn0xFFF0~0xFFFDReserved for JPEG extension
COM0xFFFEComment
Reserved markers
SymbolCode Assignment(0xFF+Marker)Description
TEM0xFF01For temporary use in arithmetic coding
RES0xFF02~0xFFBFReserved
Segment详细信息
SOI & EOI
标志字节数含义
SOI2字节Start of Image,图像开始
EOI2字节End of Image,图像结束
APP0

共有9个字段

字段字节数含义
数据长度2字节1-9共9个字段的总长度
标识符5字节固定值0x4A46494600,即字符串“JFIF0”
版本号2字节一般是0x0102,表示JFIF的版本号1.2
X和Y的密度单位1字节只有三个值可选
0:无单位;1:点数/英寸;2:点数/厘米
X方向像素密度2字节X方向像素密度
Y方向像素密度2字节Y方向像素密度
缩略图水平像素数目1字节缩略图水平像素数
缩略图垂直像素数目1字节缩略图垂直像素数
缩略图RGB位图长度可能是3的倍数缩略图RGB位图数据
DQT

即定义量化表。

字段字节数含义
数据长度2字节所有字段的总长度
量化表×n数据长度-2字节详细内容见下表

量化表中字段的含义:

字段字节数含义
量化精度及量化表ID1字节高四位精度,0表示8位,1表示16位
低四位为量化表ID,取值范围为0-3
表项(64×(精度+1))字节表格的具体内容

本标记段中,量化表可以重复出现,表示有多个量化表,但最多只能出现4次。

SOF0

即帧图像开始 。

字段字节数含义
数据长度2字节所有字段的总长度
精度1字节每个数据样本的位数
通常是8位,一般软件都不支持 12位和16位
图像高度2字节图像高度(单位:像素)
图像宽度2字节图像宽度(单位:像素)
颜色分量数1字节只有3个数值可选
1:灰度图;
3:YCrCb或YIQ;
4:CMYK
而JFIF中使用YCrCb,故这里颜色分量数恒为3
颜色分量信息颜色分量数×3字节
(通常为9字节)
颜色分量ID 1字节
水平/垂直采样因子 1字节 高四位为水平采样因子,低四位为垂直采样因子
量化表 1字节 当前分量所使用量化表的ID
DHT

即定义哈夫曼表。

字段字节数含义
数据长度2字节所有字段的总长度
Huffman表×n数据长度-2字节详细内容见下表

Huffman表中的内容:

字段字节数含义
表ID和表类型1字节高四位表示类型,0表示直流,1表示交流
低四位为Huffman表ID,DC表和AC表分开编码
不同位数的码字数量16字节不同位数码字的数量
编码内容16个不同位数的码字数量之和(字节)编码的具体内容

Huffman表可以重复出现,但最多4次。对于Huffman表的存储方式在下面会有介绍。

SOS

即扫描开始。

字段字节数含义
数据长度2字节所有字段的总长度
颜色分量数1字节应该和SOF中的相应字段值相同
1:灰度图是;3: YCrCb或YIQ;4:CMYK
颜色分量信息2字节其中包括
颜色分量ID 1字节
直流/交流系数表号 1字节 高四位:直流分量使用的哈夫曼树编号;低四位交流分量使用的哈夫曼树编号。
压缩图像数据2字节谱选择开始 1字节 固定值0x00
谱选择结束 1字节 固定值0x3F
谱选择 1字节 在基本JPEG中总为00

Huffman表存储方式说明

在标记段DHT内,包含了一个或者多个的哈夫曼表。对于单一个哈夫曼表,应该包括了三部分

  • 哈夫曼表ID和表类型
    • 这个字节的值为一般只有四个0x00、0x01、0x10、0x11。
      • 0x00表示DC直流0号表
      • 0x01表示DC直流1号表
      • 0x10表示AC交流0号表
      • 0x11表示AC交流1号表
  • 不同位数的码字数量
  • 编码内容
    • 第一字段:JPEG文件的哈夫曼编码最长只有16位。所以这个字段的16个字节分别表示1-16位的编码码字在哈夫曼树中的个数。
    • 第二字段:这个字段记录了哈夫曼树中各个叶子结点的权。所以,上一字段(不同位数的码字数量)的16个数值之和就应该是本字段的长度,也就是哈夫曼树中叶子结点个数。

举个例子:FF C4 00 3E 00 00 03 01 01 01 01 01 01 01 00 00 00 00 00 00 04 05 06 03 02 01 00 09 07 08

其中

  • 红色部分表示哈夫曼表的ID和表的类型,本例中0x00表示此部分数据描述的是DC交流0号表。
  • 蓝色部分表示为不同码长的码字的数量。本例中,即码长为1的码字有0个,码长为2的码字有3个,码长为3的码字有1个,以此类推。
  • 绿色部分的字节数等于蓝色部分的数值相加。此处为10字节。表示每个叶子节点从小到大(所谓从小到大实际上是指的码长和出现顺序)排序后,对应的权值(权值对直流和交流系数的含义不同)。
建立huffman树/表

Huffman码表的建立方式比较简单,

具体方法为:

  • 第一个码字必定为0
    • 如果第一个码字位数为1,则码字为0;
    • 如果第一个码字位数为2,则码字为00;
    • 以此类推
  • 从第二个码字开始
    • 如果它和它前面的码字位数相同,则当前码字为它前面的码字加1;
    • 如果它的位数比它前面的码字位数大,则当前码字是前面的码字加1后再在后边添若干个0,直至满足位数长度为止。

按照上述方法,可以根据上一个例子建立一个霍夫曼码表:

序号码长码字权值
12004
22015
32106
431103
5411102
65111101
761111100
8711111109
98111111107
1091111111108
代码解析

具体实现为:

/*
 * Takes two array of bits, and build the huffman table for size, and code
 * 使用两个关于bits的数组构建出来huffmantable
 * lookup will return the symbol if the code is less or equal than HUFFMAN_HASH_NBITS.
 * lookup表会返回编码后的码字,如果编码长度小于等于HUFFMAN_HASH_NBITS
 * code_size will be used to known how many bits this symbol is encoded.
 * 编码大小会被用来表示这个符号的长度为多少bit
 * slowtable will be used when the first lookup didn't give the result.
 * 当上述查找表无法使用给出答案的时候,使用slowtable
 */
static void build_huffman_table(const unsigned char* bits, const unsigned char* vals, struct huffman_table* table)
{
	//bits为每个长度出现的频次,vals是权值,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;

	/*
	 * Build a temp array
	 *   huffsize[X] => numbers of bits to write vals[X]
	 *   huffsize[x]表示用多少bit去表示vals[X]
	 * bits数组表示该huffman编码长度的出现的次数
	 * 下列代码将所有子节点初始化
	 */
	hz = huffsize;
	for (i = 1; i <= 16; i++)
	{
		for (j = 1; j <= bits[i]; j++)
			*hz++ = i;
	}
	*hz = 0;

	//初始化lookup表,全部赋值为最大值。初始化slowtable全为0
	memset(table->lookup, 0xff, sizeof(table->lookup));
	for (i = 0; i < (16 - HUFFMAN_HASH_NBITS); i++)
		table->slowtable[i][0] = 0;

	/* Build a temp array
	 *   huffcode[X] => code used to write vals[X]
	 *   huffcode[X] 表示用来表示vals[X]的编码
	 */
	code = 0;
	hc = huffcode;
	hz = huffsize;
	nbits = *hz;
	while (*hz)
	{
		//当长度没有变化的时候下一个编码为上一个+1
		while (*hz == nbits)
		{
			*hc++ = code++;
			hz++;
		}
		//当长度发生变换的时候则是+1补零
		code <<= 1;
		nbits++;
	}

	/*
	 * Build the lookup table, and the slowtable if needed.
	 * 建立慢速查找表,如果有需要
	 */
	next_free_entry = -1;
	//如果 huffsize[i]不为0,则执行循环
	for (i = 0; huffsize[i]; i++)
	{
		//获取值,编码,和码长
		val = vals[i];
		code = huffcode[i];
		code_size = huffsize[i];
		//建立权值和码长的关系
		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
			 * 如果code_size小于HUFFMAN_HASH_NBITS,即查找表的固定长度
			 * 就重复 1UL << (HUFFMAN_HASH_NBITS - code_size) 次
			 * 就相当于把表格中码字后边的所有位数补全,并把它的值全赋值成val
			 */
			int repeat = 1UL << (HUFFMAN_HASH_NBITS - code_size);
			code <<= HUFFMAN_HASH_NBITS - code_size;
			while (repeat--)
				table->lookup[code++] = val;

		}
		else
		{
			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 */
		}

	}
}
JPEG中Huffman解码过程
代码解析

直接用代码进行说明吧:

static int get_next_huffman_code(struct jdec_private* priv, struct huffman_table* huffman_table)
{
	int value, hcode;
	unsigned int extra_nbits, nbits;
	uint16_t* slowtable;
	//读取HUFFMAN_HASH_NBITS比特,并赋值给hcode
	look_nbits(priv->reservoir, priv->nbits_in_reservoir, priv->stream, HUFFMAN_HASH_NBITS, hcode);
	//在霍夫曼码表中找到对应的权值
	value = huffman_table->lookup[hcode];
	if (__likely(value >= 0))
	{
		unsigned int code_size = huffman_table->code_size[value];
		//跳过code_size比特
		skip_nbits(priv->reservoir, priv->nbits_in_reservoir, priv->stream, code_size);
		//返回权值
		return value;
	}

	/* Decode more bits each time ...每次读取更多bit */
	for (extra_nbits = 0; extra_nbits < 16 - HUFFMAN_HASH_NBITS; extra_nbits++)
	{
		nbits = HUFFMAN_HASH_NBITS + 1 + extra_nbits;
		//读取n比特
		look_nbits(priv->reservoir, priv->nbits_in_reservoir, priv->stream, nbits, hcode);
		slowtable = huffman_table->slowtable[extra_nbits];
		/* Search if the code is in this array */
		while (slowtable[0]) {
			if (slowtable[0] == hcode) {
				//如果找到,那么将当前的bit数跳过并返回权值
				skip_nbits(priv->reservoir, priv->nbits_in_reservoir, priv->stream, nbits);
				return slowtable[1];
			}
			slowtable += 2;//查找下一个位置有没有
		}
	}
	return 0;
}
直流

对于直流来说,权值是解码时需要额外读入的bit位数。这个再次读入的数通过查表来得到真正的码值。

比如比特流:0110101011

由于前缀码的特性,读入01后码字结束,查表可以得到额外比特数为5,所以需要额外读5位数,此处为10101,译码可以得到21。所以直流系数为21.要注意的是,直流系数是差分编码之后得到的。

实际中的过程是把所有的颜色分量单元按颜色分量(Y、Cr、Cb)分类。每一种颜色分量内,相邻的两个颜色分量单元的直流变量是以差分来编码的。也就是说,通过Huffman码表解码出来的直流变量数值只是当前颜色分量单元的实际直流变量减去前一个颜色分量单元的实际直流变量。也就是说,当前直流变量要通过前一个颜色分量单元的实际(非解码)直流分量来校正,即 D C n = D C n − 1 + D i f f DC_n=DC_{n-1}+Diff DCn=DCn1+Diff,其中Diff为差分校正变量,也就是直接解码出来的直流系数。但如果当前颜色分量单元是第一个单元,则解码出来的直流数值就是真正的直流变量。

交流

对于交流系数,用交流哈夫曼树/表查得该码字对应的权值。权值的高4位表示当前数值前面有多少个连续的零低4位表示该交流分量数值的二进制位数,也就是接下来需要读入的位数。

例如,权值0X31可以表示为(3,1)。表明交流系数前面有3个0,此外交流系数的具体值还需要再读入1bit的码字才能得到。

JPEG 的解码流程

  1. 读取文件

  2. 解析 Segment Marker

    1. 解析 SOI

    2. 解析 APP0

      • 检查标识“JFIF”及版本

      • 得到一些参数

    3. 解析 DQT

      • 得到量化表长度(可能包含多张量化表)
      • 得到量化表的精度
      • 得到及检查量化表的序号(只能是 0 ——3)
      • 得到量化表内容(64 个数据)
    4. 解析 SOF0

      • 得到每个 sample 的比特数、长宽、颜色分量数

      • 得到每个颜色分量的 ID、水平采样因子、垂直采样因子、使用的量化表序号(与 DQT 中序号对应)

      • 解析 DHT

    5. 解析 DHT

      • 得到 Huffman 表的类型(AC、DC)、序号
      • 依据数据重建 Huffman 表
    6. 解析 SOS

      • 得到解析每个颜色分量的 DC、AC 值所使用的 Huffman 表序号(与 DHT 中序号对应)
  3. 依据每个分量的水平、垂直采样因子计算 MCU 的大小,并得到每个 MCU 中 8*8宏块的个数

  4. 对每个 MCU 解码(依照各分量水平、垂直采样因子对 MCU中每个分量宏块解码)

    1. 对每个宏块进行 Huffman 解码,得到 DCT 系数
    2. 对每个宏块的 DCT 系数进行 IDCT,得到 Y、Cb、Cr
    3. 遇到 Segment Marker RST 时,清空之前的 DC DCT 系数
  5. 解析到 EOI,解码结束

  6. 将 Y、Cb、Cr 转化为需要的色彩空间并保存。

部分代码解释

convert_one_image

加载一个jpeg图像,对其进行解压缩,并存储结果

/**
 * Load one jpeg image, and decompress it, and save the result.
 * 加载一个jpeg图像,对其进行解压缩,并存储结果
 */
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];

  /* Load the Jpeg into memory */
  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);
  fclose(fp);

  /* Decompress it 初始化解码器*/
  jdec = tinyjpeg_init();
  if (jdec == NULL)
    exitmessage("Not enough memory to alloc the structure need for decompressing\n");
  //对header进行解码
  if (tinyjpeg_parse_header(jdec, buf, length_of_file)<0)
    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 (tinyjpeg_decode(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);

  /* 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;
   }
  //释放内存
  /* Only called this if the buffers were allocated by tinyjpeg_decode() */
  tinyjpeg_free(jdec);
  /* else called just free(jdec); */

  free(buf);
  return 0;
}
tinyjpeg_decode

对图片进行解码。

/**
 * Decode and convert the jpeg image into @pixfmt@ image
 *
 * Note: components will be automaticaly allocated if no memory is attached.
 */
int tinyjpeg_decode(struct jdec_private* priv, int pixfmt)
{
	unsigned int x, y, xstride_by_mcu, ystride_by_mcu;
	unsigned int bytes_per_blocklines[3], bytes_per_mcu[3];
	decode_MCU_fct decode_MCU;
	const decode_MCU_fct* decode_mcu_table;
	const convert_colorspace_fct* colorspace_array_conv;
	convert_colorspace_fct convert_to_pixfmt;

	if (setjmp(priv->jump_state))
		return -1;

	/* To keep gcc happy initialize some array */
	bytes_per_mcu[1] = 0;
	bytes_per_mcu[2] = 0;
	bytes_per_blocklines[1] = 0;
	bytes_per_blocklines[2] = 0;

	decode_mcu_table = decode_mcu_3comp_table;
	switch (pixfmt) {
	case TINYJPEG_FMT_YUV420P:
		colorspace_array_conv = convert_colorspace_yuv420p;
		if (priv->components[0] == NULL)
			priv->components[0] = (uint8_t*)malloc(priv->width * priv->height);
		if (priv->components[1] == NULL)
			priv->components[1] = (uint8_t*)malloc(priv->width * priv->height / 4);
		if (priv->components[2] == NULL)
			priv->components[2] = (uint8_t*)malloc(priv->width * priv->height / 4);
		bytes_per_blocklines[0] = priv->width;
		bytes_per_blocklines[1] = priv->width / 4;
		bytes_per_blocklines[2] = priv->width / 4;
		bytes_per_mcu[0] = 8;
		bytes_per_mcu[1] = 4;
		bytes_per_mcu[2] = 4;
		break;

	case TINYJPEG_FMT_RGB24:
		colorspace_array_conv = convert_colorspace_rgb24;
		if (priv->components[0] == NULL)
			priv->components[0] = (uint8_t*)malloc(priv->width * priv->height * 3);
		bytes_per_blocklines[0] = priv->width * 3;
		bytes_per_mcu[0] = 3 * 8;
		break;

	case TINYJPEG_FMT_BGR24:
		colorspace_array_conv = convert_colorspace_bgr24;
		if (priv->components[0] == NULL)
			priv->components[0] = (uint8_t*)malloc(priv->width * priv->height * 3);
		bytes_per_blocklines[0] = priv->width * 3;
		bytes_per_mcu[0] = 3 * 8;
		break;

	case TINYJPEG_FMT_GREY:
		decode_mcu_table = decode_mcu_1comp_table;
		colorspace_array_conv = convert_colorspace_grey;
		if (priv->components[0] == NULL)
			priv->components[0] = (uint8_t*)malloc(priv->width * priv->height);
		bytes_per_blocklines[0] = priv->width;
		bytes_per_mcu[0] = 8;
		break;

	default:
#if TRACE
		fprintf(p_trace, "Bad pixel format\n");
		fflush(p_trace);
#endif
		return -1;
	}
	//选择合适的采样方式来进行解码
	xstride_by_mcu = ystride_by_mcu = 8;
	if ((priv->component_infos[cY].Hfactor | priv->component_infos[cY].Vfactor) == 1) {
		decode_MCU = decode_mcu_table[0];
		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) {
		decode_MCU = decode_mcu_table[1];
		convert_to_pixfmt = colorspace_array_conv[1];
		ystride_by_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) {
		decode_MCU = decode_mcu_table[3];
		convert_to_pixfmt = colorspace_array_conv[3];
		xstride_by_mcu = 16;
		ystride_by_mcu = 16;
#if TRACE 
		fprintf(p_trace, "Use decode 2x2 sampling\n");
		fflush(p_trace);
#endif
	}
	else {
		decode_MCU = decode_mcu_table[2];
		convert_to_pixfmt = colorspace_array_conv[2];
		xstride_by_mcu = 16;
#if TRACE
		fprintf(p_trace, "Use decode 2x1 sampling\n");
		fflush(p_trace);
#endif
	}

	resync(priv);

	/* Don't forget to that block can be either 8 or 16 lines */
	bytes_per_blocklines[0] *= ystride_by_mcu;
	bytes_per_blocklines[1] *= ystride_by_mcu;
	bytes_per_blocklines[2] *= ystride_by_mcu;

	bytes_per_mcu[0] *= xstride_by_mcu / 8;
	bytes_per_mcu[1] *= xstride_by_mcu / 8;
	bytes_per_mcu[2] *= xstride_by_mcu / 8;

	/* Just the decode the image by macroblock (size is 8x8, 8x16, or 16x16) */
	//对每个块进行解码
	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);
			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);
					if (find_next_rst_marker(priv) < 0)
						return -1;
				}
			}
		}
	}
#if TRACE
	fprintf(p_trace, "Input file size: %d\n", priv->stream_length + 2);
	fprintf(p_trace, "Input bytes actually read: %d\n", priv->stream - priv->stream_begin + 2);
	fflush(p_trace);
#endif

	return 0;
}
YCrCB_to_YUV420P_1x1

将YCrCb换成YUV4:2:0格式。还有类似的函数起到相同的功能,一般看名字就可以理解。

static void YCrCB_to_YUV420P_1x1(struct jdec_private* priv)
{
	const unsigned char* s, * y;
	unsigned char* p;
	int i, j;

	p = priv->plane[0];
	y = priv->Y;
	for (i = 0; i < 8; i++)
	{
		memcpy(p, y, 8);
		p += priv->width;
		y += 8;
	}

	p = priv->plane[1];
	s = priv->Cb;
	for (i = 0; i < 8; i += 2)
	{
		for (j = 0; j < 8; j += 2, s += 2)
			*p++ = *s;
		s += 8; /* Skip one line */
		p += priv->width / 2 - 4;
	}

	p = priv->plane[2];
	s = priv->Cr;
	for (i = 0; i < 8; i += 2)
	{
		for (j = 0; j < 8; j += 2, s += 2)
			*p++ = *s;
		s += 8; /* Skip one line */
		p += priv->width / 2 - 4;
	}
}
resync

初始化jdec_private。

static void resync(struct jdec_private* priv)
{
	int i;

	/* Init DC coefficients 初始化DC系数*/
	for (i = 0; i < COMPONENTS; i++)
		priv->component_infos[i].previous_DC = 0;

	priv->reservoir = 0;
	priv->nbits_in_reservoir = 0;
	if (priv->restart_interval > 0)
		priv->restarts_to_go = priv->restart_interval;
	else
		priv->restarts_to_go = -1;
}
decode_MCU_1x1_3planes

即对每个通道进行解码。类似的函数还有多个。

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);
}
process_Huffman_data_unit
/**
 *
 * Decode a single block that contains the DCT coefficients.
 * 解码出单个DCT系数的块
 * The table coefficients is already dezigzaged at the end of the operation.
 * 表格的系数已经在操作最后进行了反zig-zag变换
 */
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;

	//获取该component的信息
	struct component* c = &priv->component_infos[component];
	short int DCT[64];


	/* Initialize the DCT coef table 初始化DCT系数*/
	memset(DCT, 0, sizeof(DCT));

	/* DC coefficient decoding 直流系数解码*/
	huff_code = get_next_huffman_code(priv, c->DC_table); //对霍夫曼编码进行解码
	//trace("+ %x\n", huff_code);
	if (huff_code) {	//如果权值不为零
		//读取额外的bit
		get_nbits(priv->reservoir, priv->nbits_in_reservoir, priv->stream, huff_code, DCT[0]);
		//由于直流是差分编码,所以需要与前一个值相加才能获得真是值
		DCT[0] += c->previous_DC;
		c->previous_DC = DCT[0];
	}
	else {
		DCT[0] = c->previous_DC;
	}

	/* AC coefficient decoding 交流系数解码*/
	j = 1;
	while (j < 64)
	{
		huff_code = get_next_huffman_code(priv, c->AC_table);
		//trace("- %x\n", huff_code);
		//前四位为需要读取的额外bit,后四位是需要跳过的0的个数
		size_val = huff_code & 0xF;
		count_0 = huff_code >> 4;

		if (size_val == 0)
		{ /* RLE */
			if (count_0 == 0)
				break;	/* EOB found, go out EOB块结尾*/
			else if (count_0 == 0xF)
				j += 16;	/* skip 16 zeros */
		}
		else
		{
			j += count_0;	/* skip count_0 zeroes */
			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]);
			j++;
		}
	}

	for (j = 0; j < 64; j++)
		c->DCT[j] = DCT[zigzag[j]]; //进行反DCT变换

实验步骤

  1. 逐步调试JPEG解码器程序。将输入的JPG文件进行解码,将输出文件保存为可供YUVViewer观看的YUV文件。
  2. 程序调试过程中,做到
    • 理解程序设计的整体框架
    • 理解三个结构体的设计目的
      • struct huffman_table
      • struct component
      • struct jdec_private
    • 理解在视音频编解码调试中TRACE的目的和含义
      • 会打开和关闭TRACE
      • 会根据自己的要求修改TRACE
  3. 以txt文件输出所有的量化矩阵和所有的HUFFMAN码表。
  4. 输出DC图像并统计其概率分布。
  5. 输出某一个AC值图像并统计其概率分布。

逐步调试JPEG解码器程序。将输入的JPG文件进行解码,将输出文件保存为可供YUVViewer观看的YUV文件。

由于使用的块是8×8的,所以当图像不够一整个块的时候会填充一些额外字节。所以所选图像的宽最好是8的倍数。
过程中用到的图片,大小320×256:
football
根据程序,输入如下命令行参数:
在这里插入图片描述
为了将输出文件保存为可供YUVViewer观看的YUV文件,找到保存yuv文件的函数:

static void write_yuv(const char *filename, int width, int height, unsigned char **components)

进行如下修改:

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

  snprintf(temp, 1024, "%s.Y", filename);
  F = fopen(temp, "wb");
  fwrite(components[0], width, height, F);
  fclose(F);
  snprintf(temp, 1024, "%s.U", filename);
  F = fopen(temp, "wb");
  fwrite(components[1], width*height/4, 1, F);
  fclose(F);
  snprintf(temp, 1024, "%s.V", filename);
  F = fopen(temp, "wb");
  fwrite(components[2], width*height/4, 1, F);
  fclose(F);

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

使用YUVviewer打开:
在这里插入图片描述
可见成功输出了yuv文件。

代码理解

程序设计的整体框架

本次程序大致分为三部分:

  • 异常处理,包括trace记录
  • 文件读写
  • 解码
    • 读取Segment信息
    • 针对每个Segment进行相应的解码

解码过程则是按照文章最开始的流程图进行的。

结构体设计的目的
huffman_table

创立一个快速查找表用于快速解码。另外如果查找失败的话则使用慢速查找表。
最主要的目的就是加速解码的过程。

struct huffman_table
{
  /* Fast look up table, using HUFFMAN_HASH_NBITS bits we can have directly the symbol,
   * 快速查找表,使用HUFFMAN_HASH_NBITS个比特可以快速找到符号
   * if the symbol is <0, then we need to look into the tree table 
   * 如果符号小于0,那么需要在慢速查找表中查找,lookup中返回的是权值*/
  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];
};
component

componet主要是用来保存一个MCU块的信息。每处理一个新的MCU块后,信息都会更新。用它来保存一些解码过程中需要用到的信息,如量化表,前一个直流的值,和当前MCU的DCT块从而使解码更加简单。

struct component 
{
  unsigned int Hfactor; //水平采样情况
  unsigned int Vfactor; //垂直采样情况
  float *Q_table;		/* Pointer to the quantisation table to use 指向量化表*/
  struct huffman_table *AC_table; //指向直流霍夫曼查找表
  struct huffman_table *DC_table; //指向交流霍夫曼查找表
  short int previous_DC;	/* Previous DC coefficient 前一个直流系数*/
  short int DCT[64];		/* DCT coef DCT系数*/
#if SANITY_CHECK
  unsigned int cid;
#endif
};

jdec_private

该结构体使用最为广泛,是解码过程中频繁用到的东西,起到整合基本信息、连接各个部分的作用。他保存着图像的基本信息和各通道的信息。包括了前两个结构体的内容。由于是采用指针方式,并不占用额外的空间。

struct jdec_private
{
  /* Public variables */
  uint8_t *components[COMPONENTS]; 
  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];		/* quantization tables 三个通道的量化表*/
  struct huffman_table HTDC[HUFFMAN_TABLES];	/* DC huffman tables  交流霍夫曼表 */
  struct huffman_table HTAC[HUFFMAN_TABLES];	/* AC huffman tables   直流霍夫曼表*/
  int default_huffman_table_initialized;        //是否使用默认初始化
  int restart_interval;
  int restarts_to_go;				/* MCUs left in this restart interval 剩余的MCU*/
  int last_rst_marker_seen;			/* Rst marker is incremented each time 上一个看到的marker*/

  /* Temp space used after the IDCT to store each components */
  uint8_t Y[64*4], Cr[64], Cb[64]; //解码后各个分量的值

  jmp_buf jump_state;
  /* Internal Pointer use for colorspace conversion, do not modify it !!! 用于色彩空间的变换*/
  uint8_t *plane[COMPONENTS];

};
trace的目的和含义

trace的目的主要是输出中间过程中的某些变量,或者错误信息。方便理解程序运行的过程以及哪部分可能会出错等。
trace还可以顺便理解程序运行的顺序和方式。

打开和关闭trace

本代码中trace的打开和关闭非常简单.

#define  snprintf _snprintf//add by nxn
#define TRACE 1//add by nxn
#define  TRACEFILE "trace_jpeg.txt"//add by nxn

发现有TRACE字段,将TRACE设置为1为打开,设置为0为关闭。

按照需求修改trace

如需要在trace记录中输出直流系数,只需要在process_Huffman_data_unit函数中添加

#if TRACE
	fprintf(p_trace, "%d ", DCT[0]);
#endif

即可。
完整代码如下:
为了方便,还可以继续输出一些标志,在解码前添加:
在这里插入图片描述
输出结果:
在这里插入图片描述

添加额外代码解决后续问题

tinyjpeg.h中添加如下声明:

FILE* p_trace;//add by nxn
FILE* DC_coff, DC_freq;
FILE* AC_coff, AC_freq;
FILE* Huffman_code;
FILE* quantization_table;
int* AC_buffer[3], * DC_buffer[3];
int AC_pos[3], DC_pos[3];
int AC_max[3], DC_max[3], AC_min[3], DC_min[3];

在主程序中为霍夫曼表和量化矩阵分配文件指针。

#if TRACE
  p_trace=fopen(TRACEFILE,"w");
  if (p_trace==NULL)
  {
	  printf("trace file open error!");
  }
  
  DC_coff = fopen("DC_coff.yuv", "wb+");
  if (DC_coff == NULL)
  {
      printf("DC file open error!");
  }
  AC_coff = fopen("AC_coff.yuv", "wb+");
  if (DC_coff == NULL)
  {
      printf("AC file open error!");
  }
  Huffman_code = fopen("Huffman_code.txt", "w+");
  if (Huffman_code == NULL)
  {
      printf("Huffman_code file open error!");
  }
  quantization_table = fopen("quantization_table.txt", "w+");
  if (DC_coff == NULL)
  {
      printf("quantization_table file open error!");
  }
#endif

tinyjpeg.c中实现保存即获得概率分布的函数。

//保存直流或交流的系数
void save_DC_AC(struct jdec_private* priv, int component)
{
	int DC = (priv->component_infos[component]).DCT[0], AC = priv->component_infos[component].DCT[1];
	DC_buffer[component][DC_pos[component] ++] = DC;
	DC_max[component] = DC_max[component] < DC ? DC : DC_max[component];
	DC_min[component] = DC_min[component] < DC ? DC_min[component] : DC;
	AC_buffer[component][AC_pos[component] ++] = AC;
	AC_max[component] = DC_max[component] < AC ? AC : DC_max[component];
	AC_min[component] = DC_min[component] < AC ? DC_min[component] : AC;
}
//初始话
void save_Init(struct jdec_private* priv)
{
	for (int i = 0; i < 3; ++i)
	{
		AC_buffer[i] = (int*)malloc(priv->width * priv->height / 16);
		DC_buffer[i] = (int*)malloc(priv->width * priv->height / 16);
		AC_pos[i] = 0; DC_pos[i] = 0;
		AC_max[i] = -1E9; AC_min[i] = 1E9;
		DC_max[i] = -1E9; DC_min[i] = 1E9;

	}
}
//输出直流和交流的频率分布
void get_freq()
{
	double* AC_fre[3], * DC_fre[3];
	for (int i = 0; i < 3; ++i)
	{
		AC_fre[i] = (int*)malloc((AC_max[i] - AC_min[i] + 1) * sizeof(double) );
		DC_fre[i] = (int*)malloc((DC_max[i] - DC_min[i] + 1) * sizeof(double));
		memset(AC_fre[i], 0, (AC_max[i] - AC_min[i] + 1) * sizeof(double));
		memset(DC_fre[i], 0, (DC_max[i] - DC_min[i] + 1) * sizeof(double));
	}
	for (int i = 0; i < 3; ++i)
	{
		for (int j = 0; j < DC_pos[i]; ++j)
		{
			AC_fre[i][AC_buffer[i][j] - AC_min[i]] ++;
			DC_fre[i][DC_buffer[i][j] - DC_min[i]] ++;
		}

	}
	char AC_file_name[] = "freq_DC_x.csv";
	char DC_file_name[] = "freq_AC_x.csv";
	for (int i = 0; i < 3; ++i)
	{
		DC_file_name[8] = i + '0';
		AC_file_name[8] = i + '0';
		FILE* DC = fopen(DC_file_name, "w+"), * AC = fopen(AC_file_name, "w+");
		fprintf(DC, "value, frequency\n"); fprintf(AC, "value, frequency\n");
		for (int j = 0; j < DC_max[i] - DC_min[i] + 1; ++j)
			fprintf(DC, "%d, %lf\n", j + DC_min[i], (1.0 * DC_fre[i][j]) / DC_pos[i]);
		for (int j = 0; j < AC_max[i] - AC_min[i] + 1; ++j)
			fprintf(AC, "%d, %lf\n", j + AC_min[i], (1.0 * AC_fre[i][j]) / AC_pos[i]);
		fclose(DC); fclose(AC);
	}
}

保存DC和直流分量
如果有其他解码方式,也可以用类似的方法来获得相对应的系数。

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

	// Cr
	process_Huffman_data_unit(priv, cCr);
#if  TRACE
	save_DC_AC(priv, cCr);
#endif //  TRACE
	IDCT(&priv->component_infos[cCr], priv->Cr, 8);
}

tinyjpeg_decoder中添加如下代码:

int tinyjpeg_decode(struct jdec_private* priv, int pixfmt)
{
    ...
#if TRACE
	fprintf(p_trace, "\nInput file size: %d\n", priv->stream_length + 2);
	fprintf(p_trace, "Input bytes actually read: %d\n", priv->stream - priv->stream_begin + 2);
	fflush(p_trace);
	//输出直流和交流的各个分量
	for (int comp = 0; comp < 3; ++comp)
	{
		for (int i = 0; i < AC_pos[comp]; ++i)
		{
			unsigned char t_AC = 1.0 * (AC_buffer[comp][i] - DC_min[comp]) / (AC_max[comp] - AC_min[comp]) * 255;
			unsigned char t_DC = 1.0 * (DC_buffer[comp][i] - AC_min[comp]) / (DC_max[comp] - DC_min[comp]) * 255;
			fwrite(&t_DC, 1, 1, DC_coff);
			fwrite(&t_AC, 1, 1, AC_coff);
		}
	}
	//获取频率分布
	get_freq();
#endif
	return 0;
}
以txt文件输出所有的量化矩阵和所有的HUFFMAN码表。

Huffman表
在parse_DHT中添加如下代码:

static int parse_DHT(struct jdec_private* priv, const unsigned char* stream)
{
	...
#if TRACE
		fprintf(p_trace, "Huffman table %s[%d] length=%d\n", (index & 0xf0) ? "AC" : "DC", index & 0xf, count);
		fprintf(Huffman_code, "Huffman table %s[%d] length=%d\n", (index & 0xf0) ? "AC" : "DC", index & 0xf, count);
		fflush(p_trace);
#endif


		if (index & 0xf0)
			build_huffman_table(huff_bits, stream, &priv->HTAC[index & 0xf]);
		else
			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;
}

build_huffman_table中添加:

static void build_huffman_table(const unsigned char* bits, const unsigned char* vals, struct huffman_table* table)
{
	...
	for (i = 0; huffsize[i]; i++)
	{
		//获取值,编码,和码长
		val = vals[i];
		code = huffcode[i];
		code_size = huffsize[i];
#if TRACE
		fprintf(p_trace, "val=%2.2x \tcode=%8.8x \tcodesize=%2.2d\n", val, code, code_size);
		fprintf(Huffman_code, "val=%2.2x \tcode=%8.8x \tcodesize=%2.2d\n", val, code, code_size);
		fflush(p_trace);
#endif
		//建立权值和码长的关系
		table->code_size[val] = code_size;
		...
}

结果
其中一部分如下:
在这里插入图片描述
量化矩阵
在parse_DQT添加如下代码:

static int parse_DQT(struct jdec_private* priv, const unsigned char* stream)
{
	...
	while (stream < dqt_block_end)
	{
		qi = *stream++;
#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
#if TRACE
		fprintf(quantization_table, "original quantization table [%d]\n", qi);
#endif
		table = priv->Q_tables[qi];
		build_quantization_table(table, stream);
		stream += 64;
#if TRACE
		fprintf(quantization_table, "scaled quantization table [%d]\n", qi);
		for (int i = 0; i < 8; ++i)
			for (int j = 0; j < 8; ++j)
				if (j == 7) fprintf(quantization_table, "%10.3f\n", table[i * 8 + j]);
				else fprintf(quantization_table, "%10.3f\t", table[i * 8 + j]);
#endif
	...
	}

在build_quantization_table中添加:

static void build_quantization_table(float* qtable, const unsigned char* ref_table)
{
	...
	for (i = 0; i < 8; i++) {
		for (j = 0; j < 8; j++) {
			*qtable++ = ref_table[*zz++] * aanscalefactor[i] * aanscalefactor[j];
		}
	}
#if TRACE
	for (int i = 0; i < 8; ++i)
		for (int j = 0; j < 8; ++j)
			if (j == 7) fprintf(quantization_table, "%10d\n", (int)ref_table[zigzag[i * 8 + j]]);
			else fprintf(quantization_table, "%10d\t", (int)ref_table[zigzag[i * 8 + j]]);
#endif
}

结果
在这里插入图片描述

输出某一个DC值图像并统计其概率分布

由于橄榄球的图像过小,仅保留直流或交流分量后,图片显示不清晰,于是采用下面的两个图片:

  1. 长和宽均为1024像素。
    在这里插入图片描述
  2. 分辨率为1440×1080
    在这里插入图片描述
    利用上文中的代码,可以得到DC系数图像:
图片1图片2
在这里插入图片描述在这里插入图片描述

如果仅看Y分量的DC系数,则是:

图片1图片2
在这里插入图片描述在这里插入图片描述

概率分布为:
图1

Y Y Y C b C_b Cb C r C_r Cr
在这里插入图片描述在这里插入图片描述在这里插入图片描述

图2

Y Y Y C b C_b Cb C r C_r Cr
在这里插入图片描述在这里插入图片描述在这里插入图片描述
输出某一个AC值图像并统计其概率分布

该交流系数为DCT表中第一行第2个数。

利用上文中的代码,可以得到AC系数图像:

图片1图片2
在这里插入图片描述在这里插入图片描述

如果仅看Y分量的AC系数,则是:

图片1图片2
在这里插入图片描述在这里插入图片描述

概率分布为:
图1

Y Y Y C b C_b Cb C r C_r Cr
在这里插入图片描述在这里插入图片描述在这里插入图片描述

图2

Y Y Y C b C_b Cb C r C_r Cr
在这里插入图片描述在这里插入图片描述在这里插入图片描述
  • 1
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C知道:要开发Xilinx的JPEG2000编解码器,你可以使用Xilinx的开发工具和IP核来实现。以下是一个简单的开发流程: 1. 确定需求:首先,你需要明确你的JPEG2000编解码器的功能和性能要求。例如,你希望实现哪些JPEG2000的功能,如压缩、解压缩、编码参数调整等。 2. 选择开发工具:Xilinx提供了一系列开发工具,如Vivado和Vitis,用于FPGA设计和开发。选择适合你需求的工具,并确保你已经安装和配置好了相应的软件。 3. 寻找IP核:在Xilinx的IP库中寻找适合的JPEG2000 IP核。Xilinx提供了一些IP核,可以加速JPEG2000编解码的实现。你可以通过Xilinx官方网站或者软件开发平台来获取这些IP核。 4. 配置和连接IP核:将选定的IP核添加到你的设计中,并根据需要进行相应的配置。连接IP核与其他必要的模块(如存储器、接口等),以完成整个JPEG2000编解码器的设计。 5. 进行综合和实现:使用Vivado工具对你的设计进行逻辑综合和实现。这个过程将会生成一个比特流文件,可用于烧录到目标FPGA设备上。 6. 进行验证和调试:将生成的比特流文件加载到目标FPGA设备上,并进行验证和调试。确保JPEG2000编解码器的功能和性能满足你的需求。 这只是一个大致的开发流程,具体的实现方式可能因项目需求和工具版本而有所不同。建议你参考Xilinx官方文档和相关教程,以获取更详细的开发指导和示例代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值