缩写 | 名称 | 说明 | 标记代码 | 字节数 |
SOI | Start of Image | 图像开始 | 固定值0xFFD8 | 2(标记代码) |
EOI | End of Image | 图像结束 | 固定值0xFFD9 | 2(标记代码) |
APP0 | Application | 应用程序保留标记 | 固定值0xFFE0 | variable |
DQT | Define Quantization Table | 定义量化表 | 固定值0xFFDB | variable |
SOF0 | Start of Frame | 帧图像开始 | 固定值0xFFC0 | variable |
DHT | Define Huffman Table | 定义哈夫曼表 | 固定值0xFFC4 | variable |
SOS | Start of Scan | 扫描开始 | 固定值0xFFDA | variable |
JPEG解码过程:
1、读入文件
进入load_multiple_times函数进行解码,先后进入tinyjpeg_parse_header()解析头函数和tinyjpeg_decode()解码函数;
tinyjpeg_parse_header();
int tinyjpeg_parse_header(struct jdec_private *priv, const unsigned char *buf, unsigned int size)
{
int ret;
/* Identify the file */
if ((buf[0] != 0xFF) || (buf[1] != SOI))
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 = parse_JFIF(priv, priv->stream_begin);//解析JFIF
return ret;
}
在解析函数parse_JFIF()中,分别进行对下述7个segment marker的解析。
static int parse_JFIF(struct jdec_private *priv, const unsigned char *stream)
{
……
marker = *stream++;
chuck_len = be16_to_cpu(stream);
next_chunck = stream + chuck_len;
switch (marker)
{
case SOF:
if (parse_SOF(priv, stream) < 0)
return -1;
break;
case DQT:
if (parse_DQT(priv, stream) < 0)
return -1;
break;
case SOS:
if (parse_SOS(priv, stream) < 0)
return -1;
sos_marker_found = 1;
break;
case DHT:
if (parse_DHT(priv, stream) < 0)
return -1;
dht_marker_found = 1;
break;
case DRI:
if (parse_DRI(priv, stream) < 0)
return -1;
break;
default:
……
}
2、解析segment marker
1)解析SOI
2)解析APP0
检查表示JFIF以及版本
得到一些参数
3)解析DQT(获取与量化表相关信息)
量化表长度(可能有多长量化表)
量化表精度
得到并检查量化表序号(0-3)
量化表内容
static int parse_DQT(struct jdec_private *priv, const unsigned char *stream) { int qi,i,j; 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 */ while (stream < dqt_block_end)//check是否还有表,若有则进入循环 { 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 table = priv->Q_tables[qi]; build_quantization_table(table, stream);//得到量化表 /////////////*added by xy*///////////增添了输出量化表的代码 for(i=0; i<8;i++) { for(j=0;j<8;j++) { fprintf(Q_TABLE, " %f", table[i*8+j]); } fprintf(Q_TABLE, "\n"); } fprintf(Q_TABLE, "\n"); /////////////////////////////////////// stream += 64; } #if TRACE fprintf(p_trace,"< DQT marker\n"); fflush(p_trace); #endif return 0; }
4)解析SOF0
得到每个采样的比特数/width/height/颜色分量数
得到每个颜色分量的ID/水平采样因子/垂直采样因子/所采用的量化表的序号(DQT中对应)
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; …… print_SOF(stream); height = be16_to_cpu(stream+3);//获得图像宽高 width = be16_to_cpu(stream+5); nr_components = stream[7];//颜色分量数 …… stream += 8; for (i=0; i
component_infos[i]; …… c->Vfactor = sampling_factor&0xf;//水平垂直采样因子 c->Hfactor = sampling_factor>>4; c->Q_table = priv->Q_tables[Q_table]; …… } priv->width = width; priv->height = height; #if TRACE fprintf(p_trace,"< SOF marker\n"); fflush(p_trace); #endif return 0; }
5)解析DHT
得到Huffman表的类型及序号(AC/DC)
依据数据重建Huffman表
static int parse_DHT(struct jdec_private *priv, const unsigned char *stream) { unsigned int count, i; unsigned char huff_bits[17]; int length, index; 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) { index = *stream++; /* We need to calculate the number of bytes 'vals' will takes */ huff_bits[0] = 0; count = 0; for (i=1; i<17; i++) { huff_bits[i] = *stream++; count += huff_bits[i]; } …… if (index & 0xf0 ) {//AC表 //added by xy #if TABLES fprintf(AC_TABLE, "Huffman table AC[%d] length=%d\n", index & 0xf, count); fflush(AC_TABLE); #endif aqi=0; build_huffman_table(huff_bits, stream, &priv->HTAC[index&0xf]); } else {//DC表 #if TABLES fprintf(DC_TABLE, "Huffman table DC[%d] length=%d\n", index & 0xf, count); fflush(DC_TABLE); #endif aqi=1; 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; }
6)解析SOS
得到解析每个颜色分量的DC/AC值所用的Huffman表序号(与DHT中的序号要对应)
static int parse_SOS(struct jdec_private *priv, const unsigned char *stream) { unsigned int i, cid, table; unsigned int nr_components = stream[2];//颜色分量数 …… stream += 3; for (i=0;i
component_infos[i].AC_table = &priv->HTAC[table&0xf]; priv->component_infos[i].DC_table = &priv->HTDC[table>>4]; } priv->stream = stream+3; #if TRACE fprintf(p_trace,"< SOS marker\n"); fflush(p_trace); #endif return 0; }
3、根据每个分昂的垂直和水平采样因子计算CMU的大小,并计算CMU中8*8宏块的数量
在tinyjpeg_decode中实现:
xstride_by_mcu = ystride_by_mcu = 8;//初始化每个MCU的宽、高为8,即采样格式4;4;4
if ((priv->component_infos[cY].Hfactor | priv->component_infos[cY].Vfactor) == 1) //水平:垂直=1: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) //水平:垂直=1:2
{
decode_MCU = decode_mcu_table[1];//每个MCU1*2个Y分量块
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) //水平:垂直=2: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 //水平:垂直=2:1
{
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
4、对每个CMU解码
1)对每个宏块进行Huffman解码,得到DCT系数
2)对每个宏块的DCT系数进行IDCT,得到Y/Cb/Cr
3)遇到Segment Marker RST时,清空之前的DC DCT系数
表示为一系列decode_MCU_nxn_nplanes()函数,不一一列举。
static void decode_MCU_1x1_1plane(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); }
5、解析到EOI,解码结束
6、将Y、Cb、Cr转换并输出。
通过添加输出格式将其合并成一个yuv文件输出,首先定义输出类型,添加yuv类型:
enum tinyjpeg_fmt {
TINYJPEG_FMT_GREY = 1,
TINYJPEG_FMT_BGR24,
TINYJPEG_FMT_YUV420P,
TINYJPEG_FMT_RGB24, //*added by xy*//
};
TINYJPEG_FMT_YUV,
在load_multiple_times函数中添加当输出格式为yuv时的case:
此处我们需要自己添加write_YUV函数,参考write_yuv可以得到:
///////*added by xy*////////
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);
fwrite(components[1], width*height/4, 1, F);
F = fopen(temp, "wb"); fwrite(components[0], width, height, F);
//////////////////////////////////////////
fwrite(components[2], width*height/4, 1, F); fclose(F);
}
此处可知,当第二个命令行参数为“yuv”时,文件输出格式会置为TINYJPEG_FMT_YUV,文件会以yuv格式输出。
在VS2010下运行时可能报错,“此源文件已更改,不再于以下版本的文件匹配"和"源文件与模块生成时的文件不同”
解决方法:点击“生成”选项卡,选择里面的选项弹出下面的对话框,把对勾去掉即可。
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 */
/* code size: give the number of bits of a symbol is encoded */
short int lookup[HUFFMAN_HASH_SIZE];//快速查表,找到对应symbol unsigned char code_size[HUFFMAN_HASH_SIZE];//码长
* FIXME: Calculate if 256 value is enough to store all values
/* some place to store value that is not encoded in the lookup table */ uint16_t slowtable[16-HUFFMAN_HASH_NBITS][256];//存编码前符号
};
2,宏块结构体component:
struct jdec_private
{
/* Public variables */
uint8_t *components[COMPONENTS];//一个MCU指向它包含的块
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 */
struct component component_infos[COMPONENTS];//一个mcu包含的component的结构体
unsigned int reservoir, nbits_in_reservoir;//用于存储bit float Q_tables[COMPONENTS][64]; /* 量化表quantization tables */
int default_huffman_table_initialized;
struct huffman_table HTDC[HUFFMAN_TABLES]; /* DC huffman tables */ struct huffman_table HTAC[HUFFMAN_TABLES]; /* AC huffman tables */ int restart_interval;
/* Temp space used after the IDCT to store each components */
int restarts_to_go; /* MCUs left in this restart interval */ int last_rst_marker_seen; /* Rst marker is incremented each time */ 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];
以上三个结构体为层层包括,即,jdec_private表示一个MCU包含component 结构体,而在component的结构体定义中又包含一个huffman_table结构体。
2)TRACE的打开和关闭
TRACE随着文件的解析,输出中间信息,输出到txt文件。txt文件在主函数中实现打开和关闭,输出信息的过程在tinyjpeg.c中。
关闭TRACE,则需要在tinyjpeg.h中加一条定义:#define TRACE 0 即可实现关闭TRACE.
文件的解析:
static int parse_JFIF(struct jdec_private *priv, const unsigned char *stream)
{ int chuck_len; int marker; int sos_marker_found = 0;
/* Parse marker */
int dht_marker_found = 0; const unsigned char *next_chunck;
goto bogus_jpeg_format;
while (!sos_marker_found) { if (*stream++ != 0xff)
stream++;
/* Skip any padding ff byte (this is normal) */ while (*stream == 0xff) marker = *stream++;
case SOF:
chuck_len = be16_to_cpu(stream); next_chunck = stream + chuck_len; switch (marker) {
return -1;
if (parse_SOF(priv, stream) < 0)//解析SOF,此过程TRACE会随之输出中间信息,下同。 return -1; break; case DQT: if (parse_DQT(priv, stream) < 0) break; case SOS:
dht_marker_found = 1;
if (parse_SOS(priv, stream) < 0) return -1; sos_marker_found = 1; break; case DHT: if (parse_DHT(priv, stream) < 0) return -1; break; case DRI:
stream = next_chunck;
if (parse_DRI(priv, stream) < 0) return -1; break; default: #if TRACE//TRACE fprintf(p_trace,"> Unknown marker %2.2x\n", marker); fflush(p_trace); #endif break; } }
if ( (priv->component_infos[cY].Hfactor < priv->component_infos[cCb].Hfactor)
if (!dht_marker_found) { #if TRACE//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
snprintf(error_string, sizeof(error_string),"Horizontal sampling factor for Y should be greater than horitontal sampling factor for Cb or Cr\n");
|| (priv->component_infos[cY].Hfactor < priv->component_infos[cCr].Hfactor)) if ( (priv->component_infos[cY].Vfactor < priv->component_infos[cCb].Vfactor) || (priv->component_infos[cY].Vfactor < priv->component_infos[cCr].Vfactor))
|| (priv->component_infos[cCb].Vfactor!=1)
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[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//TRACE fprintf(p_trace,"Bogus jpeg format\n"); fflush(p_trace); #endif
return -1;
运行一次之后会自动产生txt文件,输出的中间信息如下图。
FILE *p_trace;//add by nxn
FILE *Q_TABLE, *DC_TABLE, *AC_TABLE;
2、在主函数中打开文件(在文件末尾记得关闭!)
3、在解析DQT函数中加入for循环输出量化表,函数在tinyjpeg.c文件中。
static int parse_DQT(struct jdec_private *priv, const unsigned char *stream)
{ …… while (stream < dqt_block_end) { qi = *stream++;
snprintf(error_string, sizeof(error_string),"16 bits quantization table is not supported\n");
#if SANITY_CHECK if (qi>>4) 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); /////////////*added by xy*/////////// for(i=0; i<8;i++) {
stream += 64;
for(j=0;j<8;j++) { fprintf(Q_TABLE, "%c%f"," ", table[i*8+j]); } fprintf(Q_TABLE, "\n"); } /////////////////////////////////////// } ……
}
return 0;
量化表的输出如图所示:
2)AC/DC码表的输出
1、定义文件指针,AC和DC分量分两个table分别输出,因此在tinyjpeg.h中定义了一个全局变量以控制程序分别向AC_TABLE和DC_TABLE中写入huffman码表。
tinyjpeg.h:
FILE *Q_TABLE, *DC_TABLE, *AC_TABLE;//added by xy
int aqi;//added by xy
#define snprintf _snprintf//add by nxn
#define TRACE 1//add by nxn
#define TABLES 1 //用于输出,同打开和关闭trace同理
//#define TRACE 0//added by xy用于关闭TRACE
#define TRACEFILE "trace_jpeg.txt"//add by nxn
2、在tinyjpeg.c中的parse_DHT函数里增加写出huffman表头信息的输出代码:
static int parse_DHT(struct jdec_private *priv, const unsigned char *stream)
{ …… if (index & 0xf0 ) { //added by xy #if TABLES
fprintf(AC_TABLE, "Huffman table AC[%d] length=%d\n", index & 0xf, count);
fflush(AC_TABLE);
#endif
aqi=0; //全局变量,为1则输出AC到DC_TABLE
build_huffman_table(huff_bits, stream, &priv->HTAC[index&0xf]);
} else { #if TABLES
fprintf(DC_TABLE, "Huffman table DC[%d] length=%d\n", index & 0xf, count);
fflush(DC_TABLE);
#endif
aqi=1;//全局变量,为0则输出DC到DC_TABLE
build_huffman_table(huff_bits, stream, &priv->HTDC[index&0xf]);
} …… return 0;
}
3、调用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];
fprintf(p_trace,"val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size);
code_size = huffsize[i]; #if TRACE fflush(p_trace); #endif /////////////////////added by xy
fprintf(AC_TABLE, "val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size);
#if TABLES if(aqi==0)//通过全局变量来控制输出的文件 { fflush(AC_TABLE); } else {
#endif
fprintf(DC_TABLE, "val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size); fflush(DC_TABLE); } ////////////////////////// …… }
}
做如上修改后,运行程序应生成码表和量化表如下:
DC_TABLE如下:
AC_TABLE如下:
TASK 4 : 输出DC图像和某个AC值图像
输出DC/AC图像:
在成功解码后,加入输出的代码即可,首先定义三个数组,作为从DCT[0]/[1]/[2]/[3]输出的中转站。
unsigned char DCimage[1],ACimage[1];
float DC[1];
随后在tinyjpeg_decode当中解析每一个CMU后输出相应的值
int tinyjpeg_decode(struct jdec_private *priv, int pixfmt)
{
unsigned char DCimage[1],ACimage[1];
float DC[1];
……
/* Just the decode the image by macroblock (size is 8x8, 8x16, or 16x16) */
for (y=0; y < priv->height/ystride_by_mcu; y++)
{
……
for (x=0; x < priv->width; x+=xstride_by_mcu)
{
decode_MCU(priv);
……
//////*added by xy*////////
DC[0]=(priv->component_infos->DCT[0]+512.0)/4;//将DCT[0]的范围压缩到0-255
DCimage[0]=(unsigned char)(DC[0]+0.5);
fwrite(DCimage,1,1,DC_FILE);//输出到DC_FILE文件中,每解析一块MCU,就输出一个分量值。原图1024,每个MCU8*8,所以输出图像128*128
ACimage[0]=(unsigned char)(priv->component_infos->DCT[1]+128);//AC分量有正负,幅度很小,集中在0附近。为了方便看,将分量抬高128
fwrite(ACimage,1,1,AC_FILE);
//write_DCAC(priv);
////////////////////////////
}
}
……
return 0;
}
输出图像:
直流DC:
交流AC(DCT[1])