一、实验目的
掌握JPEG编解码系统的基本原理。初步掌握复杂的数据压缩算法实现,并能根据理论分析需要实现所对应数据的输出。
二、实验原理
1.编解码原理
-
编码过程
(1)亮度信号0偏置电平下移
对于灰度级是的像素,通过减去,将无符号的整数值变成有符号数,从而降低亮度信号直流的系数,减少数据量。
(2)做8*8的DCT变换
对图像进行分块,通过DCT变换去除图像数据的相关性,便于量化过程去除图像数据的空间冗余。
(3)对8*8的块系数做量化
使用色度和亮度两种量化表,根据人眼视觉特性对低频分量采取较细的量化,对高频分量采取较粗的量化。
(4)对DC系数做预测编码+VLC编码
由于8*8图象块经过DCT变换后得到的DC直流系数具有系数数值较大、数值变化不大这两个特点,因此对DC系数采用差分脉冲调制编码(DPCM)技术,对相邻图像块之间的DC系数的差值进行编码,再进行熵编码。
(5)对AC系数做之字形扫描+游程编码+VLC编码
由于DCT变换后系数集中在左上角,因此采用之字形扫描降低数据量,再进行熵编码。 解码过程
解码过程是编码的逆过程。
2.文件格式
2.1 Segment的组织形式
JPEG 在文件中以 Segment 的形式组织,它具有以下特点:
- 均以 0xFF 开始,后跟 1 byte 的 Marker 和 2 byte 的 Segment length(包含表示Length 本身所占用的 2 byte,不含“0xFF” + “Marker” 所占用的 2 byte);
- 采用 Motorola 序(相对于 Intel 序),即保存时高位在前,低位在后;
- Data 部分中,0xFF 后若为 0x00,则跳过此字节不予处理;
2.2 JPEG的 Segment Marker
- 非分层霍夫曼编码——帧标记的开始
符号 | 标记代码 | 说明 |
---|---|---|
0XFFC0 | Baseline DCT | |
0XFFC1 | Extended sequential DCT | |
0XFFC2 | Progressive DCT | |
0XFFC3 | Spatial (sequential) lossless |
- 分层霍夫曼编码——帧标记的开始
符号 | 标记代码 | 说明 |
---|---|---|
0XFFC5 | Differential sequential DCT | |
0XFFC6 | Differential progressive DCT | |
0XFFC7 | Differential spatial lossless |
- 非分层算术编码——帧标记的开始
符号 | 标记代码 | 说明 |
---|---|---|
0XFFC8 | Reserved for JPEG extensions | |
0XFFC9 | Extended sequential DCT | |
0XFFCA | Progressive DCT | |
0XFFCB | Spatial (sequential) Lossless |
- 分层算术编码——帧标记的开始
符号 | 标记代码 | 说明 |
---|---|---|
0XFFCD | Differential sequential DCT | |
0XFFCE | Differential progressive DCT | |
0XFFCF | Differential spatial lossless |
2.3 JPEG 的解码流程
(1)读取文件
/* 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);
(2)解析Segment Marker
static int parse_JFIF(struct jdec_private *priv, const unsigned char *stream)
{
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++;
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:
break;
}
stream = next_chunck;
}
if (!dht_marker_found) {
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:
return -1;
}
1. 解析SOI
SOI表示图像开始,标记代码为0xFFD8,2字节。
/* Identify the file */
if ((buf[0] != 0xFF) || (buf[1] != SOI)) /* 必须以FFD8开始 */
snprintf(error_string, sizeof(error_string),"Not a JPG file ?\n");
2.解析APP0
APP0表示应用程序保留标记,标记代码为0xFFE0,2字节。
APPN表示应用细节信息,标记代码范围为0xFFE1~0xFFEF。
具体字段 | 大小 | 说明 |
---|---|---|
数据长度 | 2字节 | 9个字段的总长度 |
标识符 | 5字节 | 固定值0x4A46494600,即字符串JFIF0 |
版本号 | 2字节 | 一般为0x0102,版本号1.2 |
X和Y的密度单位 | 1字节 | 0:无单位 1:点数/英寸 2:点数/厘米 |
X方向像素密度 | 2字节 | 取值范围未知 |
Y方向像素密度 | 2字节 | 取值范围未知 |
缩略图水平像素数目 | 1字节 | 取值范围未知 |
缩略图垂直像素数目 | 1字节 | 取值范围未知 |
缩略图RGB位图 | 可能是3的倍数 | 缩略RGB位图数据 |
- FF D8:图像开始
- FF E0:应用程序保留标记
- 00 10:数据长度16字节
- 4A 46 49 00:标识符JFIF0
- 01 01:版本号1.1
- 00:密度无单位
- 00 01:X方向像素密度1
- 00 01:Y方向像素密度1
- 00 00:无缩略图
3.解析DQT
DQT表示量化表,标记代码为0xFFDB,2字节。
包含数据长度和量化表两个字段。
数据长度2字节,量化表长度是数据长度-2。
量化表字段 | 长度 | 说明 |
---|---|---|
精度及量化表ID | 1字节 | 高4位表示精度,0:8位 1:16位;低4位表示量化表ID,取值0~3 |
表项 | (64*(精度+1))字节 | ---- |
static int parse_DQT(struct jdec_private *priv, const unsigned char *stream)
{
int qi;
float *table;
const unsigned char *dqt_block_end;
dqt_block_end = stream + be16_to_cpu(stream);
stream += 2; /* Skip length */
while (stream < dqt_block_end)
{
qi = *stream++;
#if SANITY_CHECK
if (qi>>4) /* 量化表第一字节高4位只能是0 */
snprintf(error_string, sizeof(error_string),"16 bits quantization table is not supported\n");
if (qi>4) /* 量化表第一字节低4位不能超过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;/* 精度为8,量化表大小为64字节 */
}
return 0;
}
- FF DB:DQT开始
- 00 43:数据长度67字节
- 00 02:精度8位,量化表ID为2
4.解析SOF0
SOF0表示帧图像开始,标记代码为0xFFC0,2字节。
具体字段 | 大小 | 说明 |
---|---|---|
数据长度 | 2字节 | 6个字段的总长度 |
精度 | 1字节 | 每个数据样本的位数 |
图像高度 | 2字节 | 单位为像素 |
图像宽度 | 2字节 | 单位为像素 |
颜色分量数 | 1字节 | 1:灰度图;3:YCrCb或YIQ;4:CMYK |
颜色分量信息 | 颜色分量数×3字节(通常为9字节) | 颜色分量ID:1字节;水平/垂直采样因子:1字节;量化表:1字节 |
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);/* 在trace中输出 */
height = be16_to_cpu(stream+3);
width = be16_to_cpu(stream+5);
nr_components = stream[7];
#if SANITY_CHECK
if (stream[2] != 8) /* 不支持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; /* 开始读颜色分量信息 */
for (i=0; i<nr_components; i++) {
cid = *stream++;/* 颜色分量ID */
sampling_factor = *stream++;/* 采样因子 */
Q_table = *stream++;/* 当前分量使用的量化表ID */
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;/* 高4位 水平采样因子 */
c->Hfactor = sampling_factor>>4;/* 低4位 垂直采样因子 */
c->Q_table = priv->Q_tables[Q_table];
}
priv->width = width;
priv->height = height;
return 0;
}
- FF C0:SOF0,帧图像开始
- 00 11:数据长度17字节
- 08:精度8位
- 04 00:高度1024
- 04 00:宽度1024
- 03:颜色格式为YUV
- 01 11 00: 颜色分量Y,420格式取样,量化表ID为0
- 02 11 01: 颜色分量U,420格式取样,量化表ID为1
- 03 11 01: 颜色分量V,420格式取样,量化表ID为1
5.解析DHT
DHT表示哈夫曼表,标记代码为0xFFC4,2字节。
包含数据长度和哈夫曼表。
数据长度2字节,哈夫曼表为数据长度-2字节。
具体字段 | 大小 | 说明 |
---|---|---|
表ID和表类型 | 1字节 | 高4位:类型,0代表直流,1代表交流 低4位:哈夫曼表ID,直流和交流分开编码 |
不同位数的码字数量 | 16字节 | ---- |
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 */
while (length>0) {
index = *stream++;/* 表ID和表类型 */
/* 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 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);
#endif
if (index & 0xf0 )/* 形成交流DHT表 */
build_huffman_table(huff_bits, stream, &priv->HTAC[index&0xf]);
else/* 形成直流DHT表 */
build_huffman_table(huff_bits, stream, &priv->HTDC[index&0xf]);
length -= 1;
length -= 16;
length -= count;
stream += count;
}
return 0;
}
- 黑色部分:数据长度为35。
- 红色部分:表类型为DC直流,ID为0。
- 绿色部分:没有1位的码字;2位的码字有3个;3位-9位的码字各一个;没有9位及以上的码字。
- 蓝色部分:由绿色部分数据知道,此哈夫曼树有0+3+1+1+1+1+1+1+1=10个叶子结点,即本字段应该有10个字节。这段数据表示10个叶子结点按从小到大排列,其权值依次为04、 05、 06、 03、 02、 01、 00、09、 07、 08 (16进制)。
6.解析SOS
SOS表示扫描开始,标记代码为0xFFDA,2字节。
具体字段 | 大小 | 说明 |
---|---|---|
数据长度 | 2字节 | 6个字段的总长度 |
颜色分量数 | 1字节 | 1:灰度图;3:YCrCb或YIQ;4:CMYK |
颜色分量信息 | ||
颜色分量ID | 1字节 | |
直流/交流系数表号 | 1字节 | 高4位:直流分量使用的哈夫曼树编号; 低4位:交流分量使用的哈夫曼树编号 |
压缩图像数据 | 3字节 | 谱选择开始 1字节 固定值0x00; 谱选择结束 1字节 固定值0x3F; 谱选择 1字节 在基本JPEG中总为00 |
static int parse_SOS(struct jdec_private *priv, const unsigned char *stream)
{
unsigned int i, cid, table;
unsigned int nr_components = stream[2];/* 获取颜色分量数 */
#if SANITY_CHECK
if (nr_components != 3) /* 颜色分量数只能为3 */
snprintf(error_string, sizeof(error_string),"We only support YCbCr image\n");
#endif
stream += 3;/* 获取颜色分量信息:颜色分量和哈夫曼树编号*/
for (i=0;i<nr_components;i++) {
cid = *stream++;
table = *stream++;
#if SANITY_CHECK
if ((table&0xf)>=4)/* 直流和交流的哈夫曼树不能超过2个 */
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)/* 颜色分量应该与SOF部分对应 */
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);
#endif
priv->component_infos[i].AC_table = &priv->HTAC[table&0xf];
priv->component_infos[i].DC_table = &priv->HTDC[table>>4];
}
priv->stream = stream+3;
return 0;
}
- 红色部分:数据长度为12。
- 绿色部分:颜色分量为3。
- 蓝色部分:颜色分量Y,直流和交流的哈夫曼树编号都为0;颜色分量UV,直流和交流的哈夫曼树编号都为1。
- 紫色部分:压缩图像数据。
- 剩余数据为压缩数据。
(3)依据每个分量的水平、垂直采样因子计算 MCU 的大小,并得到每个MCU 中 8*8宏块的个数
xstride_by_mcu = ystride_by_mcu = 8;/* 初始化为4:4:4采样情况 */
if ((priv->component_infos[cY].Hfactor | priv->component_infos[cY].Vfactor) == 1) {
/* 4:4:4采样 */
decode_MCU = decode_mcu_table[0];
convert_to_pixfmt = colorspace_array_conv[0];
} else if (priv->component_infos[cY].Hfactor == 1) {
/* 4:2:2采样 */
decode_MCU = decode_mcu_table[1];
convert_to_pixfmt = colorspace_array_conv[1];
ystride_by_mcu = 16;/* MCU 8*16*/
} else if (priv->component_infos[cY].Vfactor == 2) {
/* 4:2:0采样 */
decode_MCU = decode_mcu_table[3];
convert_to_pixfmt = colorspace_array_conv[3];
xstride_by_mcu = 16;
ystride_by_mcu = 16;/* MCU 16*16*/
} else {
decode_MCU = decode_mcu_table[2];
convert_to_pixfmt = colorspace_array_conv[2];
xstride_by_mcu = 16;
}
(4)对每个 MCU 解码(依照各分量水平、垂直采样因子对 MCU 中每个分量宏块解码)
/* 4:4:4 decode */
static void decode_MCU_1x1_3planes(struct jdec_private *priv)
{
// Y
process_Huffman_data_unit(priv, cY);/* 以 8*8 宏块为单位进行 Huffman 解码 */
IDCT(&priv->component_infos[cY], priv->Y, 8);/* 反DCT变换 */
// 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)//4:2:2
{
// 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 2x2
* .-------.
* | 1 | 2 |
* |---+---|
* | 3 | 4 |
* `-------'
*/
static void decode_MCU_2x2_3planes(struct jdec_private *priv) // 4:2:0
{
// 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);
}
/* 以 8*8 宏块为单位进行 Huffman 解码 */
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];
struct component *c = &priv->component_infos[component];
short int DCT[64];
/* Initialize the DCT coef table */
memset(DCT, 0, sizeof(DCT));
/* DC coefficient decoding */
huff_code = get_next_huffman_code(priv, c->DC_table);
if (huff_code) {
/* 查表得直流的DCT系数 (残差值)*/
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);
size_val = huff_code & 0xF;/* 幅度 */
count_0 = huff_code >> 4;/* 游程长度 */
if (size_val == 0)/* 0 不是一个有效的 Amplitude 值,这里做零游程标志 */
{ /* RLE */
if (count_0 == 0)
break; /* EOB found, go out */
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;
}
/* 查表得交流的DCT系数 */
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]];/* ZigZag序保存 */
}
(5)解析到 EOI,解码结束
for (y=0; y < priv->height/ystride_by_mcu; 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);//清空所有颜色分量
if (find_next_rst_marker(priv) < 0)//查找RST标记
return -1;
}
}
}
}
(6)将 Y、Cb、Cr 转化为需要的色彩空间并保存
/**
* YCrCb -> YUV420P (1x1)
* .---.
* | 1 |
* `---'
*/
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++)//Y
{
memcpy(p, y, 8);
p+=priv->width;
y+=8;
}
p = priv->plane[1];
s = priv->Cb;
for (i=0; i<8; i+=2)//Cb
{
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)//Cr
{
for (j=0; j<8; j+=2, s+=2)
*p++ = *s;
s += 8; /* Skip one line */
p += priv->width/2 - 4;
}
}
/**
* Save a buffer in three files (.Y, .U, .V) useable by yuvsplittoppm
*/
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);
}
三、实验步骤
- 逐步调试JPEG解码器程序。将输入的JPG文件进行解码,将输出文件保存为可供YUVViewer观看的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, "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);
}
-
图片分辨率为1024*1024,用YUVViewer打开的文件如下:
- 程序调试过程中,应做到:
- 理解程序设计的整体框架
本次实验的程序主要分为三部分:- 文件的读写
- 解码过程(如算法框图所示流程)
- 异常处理和trace记录
- 理解三个结构体的设计目的
struct jdec_private
综合结构体,储存了JPEG图片的尺寸信息、量化表、huffman表和每个通道的信息。
该结构体包含了其余两个结构体,解码过程频繁用到该结构体。
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 */
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];
jmp_buf jump_state;
/* Internal Pointer use for colorspace conversion, do not modify it !!! */
uint8_t *plane[COMPONENTS];
};
struct component
用于保存一个MCU(最小编码单元)的信息。
包含了水平、垂直因子,量化表和huffman表的指针,上一个DC系数和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 */
#if SANITY_CHECK
unsigned int cid;
#endif
};
struct huffman_table
创建一个快速查找表用于解码,查找失败启用慢速查找表。
用于加速解码过程。
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];
};
- 理解在视音频编解码调试中TRACE的目的和含义
- TRACE用于输出解码的信息和错误信息,便于理解程序运行的流程和查找错误。
- 打开和关闭trace只需在定义trace的头文件中改变条件值,0表示关闭,1表示打开。
#define snprintf _snprintf//add by nxn #define TRACE 1//add by nxn #define TRACEFILE "trace_jpeg.txt"//add by nxn
- 以txt文件输出所有的量化矩阵和所有的HUFFMAN码表。
- Huffman码表的输出
在解码时将码表输出即可。
#if TRACE
fprintf(p_trace,"Huffman table %s[%d] length=%d\n", (index&0xf0)?"AC":"DC", index&0xf, count);
fflush(p_trace);
#endif
- 量化矩阵的输出
由于编码时采用了之字形扫描,在输出量化矩阵时需要对存储顺序进行一定的处理。
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;
for (i=0; i<8; i++) {
for (j=0; j<8; j++) {
*qtable++ = ref_table[*zz++] * aanscalefactor[i] * aanscalefactor[j];
/* 将量化表写入trace文件 */
#if TRACE
* zz--;
fprintf(p_trace, "%d\t", ref_table[*zz++]);
if (j == 7) {
fprintf(p_trace, "\n");
}
#endif
}
}
}
- 输出DC图像并统计其概率分布。
输出直流和交流图像需要在解码时输出对应文件。
由于在编码时进行了0电平偏置下移,所以输出时需要上抬128。原始图像大小为1024*1024,分成了8*8的小块,输出图像大小为128*128。
- 将DC和AC图像写入文件
/* 头文件 tinyjpeg.h中定义 */
#define OUT 1
#define ac_index 1
#define DC_FILE "test_dc.yuv"
#define AC_FILE "test_ac.yuv"
int tinyjpeg_decode(struct jdec_private *priv, int pixfmt)
{
/* 变量定义 省略*/
#if OUT
int count = 0;
unsigned char* dbuf, * abuf;
dbuf = (unsigned char*)malloc(sizeof(unsigned char));
abuf = (unsigned char*)malloc(sizeof(unsigned char));
FILE* F = fopen(DC_FILE, "wb");
if (F == NULL)
printf("open DC_FILE error!");
FILE* F2 = fopen(AC_FILE, "wb");
if (F2 == NULL)
printf("open AC_FILE error!");
#endif
/* 解码过程 省略*/
decode_MCU(priv);
#if OUT
dbuf = (unsigned char)(priv->component_infos->DCT[0]/4 + 128);
abuf = (unsigned char)(priv->component_infos->DCT[ac_index] + 128);
fwrite(&dbuf, 1, 1, F);
fwrite(&abuf, 1, 1, F2);
count++;
#endif
/* 转换格式操作 省略*/
#if OUT
unsigned char* uv = 128;
for (int i = 0; i < count/2; i++) {
fwrite(&uv, 1, 1, F);
fwrite(&uv, 1, 1, F2);
}
fclose(F);
fclose(F2);
#endif
return 0;
}
- 统计概率分布
#include <stdio.h>
#include <stdlib.h>
#define height 128
#define width 128
int main(int argc,char** argv) {
/* 输入图像 输出概率分布 */
int size = height * width;
FILE* file;
FILE* out_file;
if (fopen_s(&file, argv[1], "rb") != 0) {
printf("open file error!\n");
exit(0);
}
if (fopen_s(&out_file, argv[2], "w") != 0) {
printf("open file error!\n");
exit(0);
}
unsigned char* ybuf, *ubuf, *vbuf;
ybuf = (unsigned char*)malloc(size);
ubuf = (unsigned char*)malloc(size / 4);
vbuf = (unsigned char*)malloc(size / 4);
fread(ybuf, 1, size, file);
fread(ubuf, 1, size/4, file);
fread(vbuf, 1, size/4, file);
double p[256] = { 0 };
for (int i = 0; i < size; i++) {
int k = (int)*(ybuf + i);
p[k]++;
}
for (int i = 0; i < 256; i++) {
p[i] /= (double)size;
fprintf(out_file, "%f\n", p[i]);
}
return 0;
}
- DC图像和概率分布图
图像 | 概率分布 |
---|---|
- 输出某一个AC值图像并统计其概率分布。
AC图像 | 概率分布 |
---|---|
由于对亮度信号进行了零偏置电平下移,本例中0~255的值域转换为了值域-128~127。从直流信号的概率分布中可以看到,偏移后像素绝对值出现3位十进制的概率大大减少。
对于交流系数,它们的分布都近似为拉普拉斯分布,有利于熵编码。此外,交流系数的能量值小于直流系数的能量值,从图片中可以看出,交流图像更多地保留了图像的细节信息。
四、总结
- 采样因子
1.YCbCr 4:4:4
数据流存放顺序YCbCrYCbCr...
,采样因子为1*1
,每个MCU代表着8*8个像素块。
2.YCbCr 4:2:2
数据流存放顺序Y0Y1CbCr...
,采样因子为1*2
,每个MCU代表着16*8个像素块。Y0和Y1分别指向两个8*8像素块,这两个像素块共用一个Cb和Cr通道。
3.YCbCr 4:2:0
数据流存放顺序如下图,采样因子为2*2
,每个MCU代表着16*16个像素块。Y0和Y1分别指向4个8*8像素块,这4个像素块共用一个Cb和Cr通道。