最近工作中涉及到了图片的编解码,在网上确实找到了不少范例,但没发现
详尽介绍这个库的文章,对直接解码输出YUV进行介绍的文章页没找到。
都是讲输出RGB,然后进行RGB->YUV转换的。而我个人觉得,
既然输入图像就是YCbCr,就完全没必要进行RGB-> YUV的转换,
但这样做,图像是否有问题,那就不得而知了(有待确认)。
要深入理解这个开源库,除了readme所列的文档,参考代码(如example.c)
及网上的范例外,最直接的帮助就是源代码本身了。
本文主要从源代码分析,追根溯源,你将会对诸如为什么out_color_space设置
为YCbCr时,得到的图像是YUV444 packed 有个很清晰的理解。
版权归本人所有,如需转载,请注明出处:http://blog.csdn.net/happy08god/article/details/10198011
libjpeg (解码篇)
1. 我们的参数设定必须在jpeg_start_decompress之前,如果我们不设置参数,就会按照默认的来,
请问: 默认参数的设定是在哪个函数中进行的?
通过调用函数jpeg_read_header来设定,具体是:
jpeg_read_header
-> jpeg_consume_input
-> default_decompress_parms
在函数jpeg_consume_input 中,会将状态改为DSTATE_INHEADER,由于switch语句中此处无break,所以会接着执行
case DSTATE_INHEADER,里面的default_decompress_parms 就是做default设定的。
jpeg_consume_input 部分代码:
switch (cinfo->_state) {
case DSTATE_START:
/* Start-of-datastream actions: reset appropriate modules */
(*cinfo->inputctl->reset_input_controller) (cinfo);
/* Initialize application's data source module */
(*cinfo->src->init_source) (cinfo);
cinfo->_state = DSTATE_INHEADER;
/*FALLTHROUGH*/
case DSTATE_INHEADER:
retcode = (*cinfo->inputctl->consume_input) (cinfo);
if (retcode == JPEG_REACHED_SOS) { /* Found SOS, prepare to decompress */
/* Set up default parameters based on header data */
default_decompress_parms(cinfo);
/* Set state: ready for start_decompress */
cinfo->_state = DSTATE_READY;
}
break;
转载请注明: http://blog.csdn.net/happy08god/article/details/10198011
default_decompress_parms里的部分代码:
if (cinfo->saw_JFIF_marker) {
cinfo->jpeg_color_space = JCS_YCbCr; /* JFIF implies YCbCr */
} else if (cinfo->saw_Adobe_marker) {
switch (cinfo->Adobe_transform) {
case 0:
cinfo->jpeg_color_space = JCS_RGB;
break;
case 1:
cinfo->jpeg_color_space = JCS_YCbCr;
break;
default:
WARNMS1(cinfo, JWRN_ADOBE_XFORM, cinfo->Adobe_transform);
cinfo->jpeg_color_space = JCS_YCbCr; /* assume it's YCbCr */
break;
}
} else {
/* Saw no special markers, try to guess from the component IDs */
int cid0 = cinfo->comp_info[0].component_id;
int cid1 = cinfo->comp_info[1].component_id;
int cid2 = cinfo->comp_info[2].component_id;
if (cid0 == 1 && cid1 == 2 && cid2 == 3)
cinfo->jpeg_color_space = JCS_YCbCr; /* assume JFIF w/out marker */
else if (cid0 == 82 && cid1 == 71 && cid2 == 66)
cinfo->jpeg_color_space = JCS_RGB; /* ASCII 'R', 'G', 'B' */
else {
TRACEMS3(cinfo, 1, JTRC_UNKNOWN_IDS, cid0, cid1, cid2);
cinfo->jpeg_color_space = JCS_YCbCr; /* assume it's YCbCr */
}
}
/* Always guess RGB is proper output colorspace. */
cinfo->out_color_space = JCS_RGB;
break;
以及:
/* Set defaults for other decompression parameters. */
cinfo->scale_num = cinfo->block_size; /* 1:1 scaling */
cinfo->scale_denom = cinfo->block_size;
cinfo->output_gamma = 1.0;
cinfo->buffered_image = FALSE;
cinfo->raw_data_out = FALSE;
cinfo->dct_method = JDCT_DEFAULT;
cinfo->do_fancy_upsampling = TRUE;
cinfo->do_block_smoothing = TRUE;
cinfo->quantize_colors = FALSE;
/* We set these in case application only sets quantize_colors. */
cinfo->dither_mode = JDITHER_FS;
#ifdef QUANT_2PASS_SUPPORTED
cinfo->two_pass_quantize = TRUE;
#else
cinfo->two_pass_quantize = FALSE;
#endif
cinfo->desired_number_of_colors = 256;
cinfo->colormap = NULL;
/* Initialize for no mode change in buffered-image mode. */
cinfo->enable_1pass_quant = FALSE;
cinfo->enable_al_quant = FALSE;
cinfo->enable_2pass_quant = FALSE;
2. 做decompress的时候,最终处理前(输入)的数据是什么样的格式? 输出的又是怎样的?
在指定输入数据的时候,会调用jdatasrc.c里面的jpeg_stdio_src(输入数据是文件)或者
jpeg_mem_src (输入数据是内存数据),会设置cinfo里面的source manager指针成员,那么
之后就可以通过decompressor结构体指针cinfo“获取数据”了。
以下流程针对raw_data_out = FALSE(默认)的情况,如果raw_data_out为TRUE,会稍有不同。
分为两个部分,一个是针对jpeg_start_decompress,另一个针对jpeg_read_scanlines 。
(1) jpeg_read_scanlines
jpeg_start_decompress ( jdapistd.c )
-> jinit_master_decompress
output_pass_setup
jinit_master_decompress
-> master_selection
master_selection
-> jpeg_calc_output_dimensions
prepare_range_limit_table
jinit_color_deconverter
jinit_upsampler
jinit_d_post_controller
jinit_inverse_dct
jinit_huff_decoder
jinit_d_coef_controller
jinit_d_main_controller
output_pass_setup
-> (*cinfo->master->prepare_for_output_pass)
(此处调用的其实是在jinit_master_decompress函数中赋值的:
master->pub.prepare_for_output_pass = prepare_for_output_pass;
所以,此处调用的其实就是 prepare_for_output_pass )
--> 于是乎:
if (! cinfo->raw_data_out) {
if (! master->using_merged_upsample)
(*cinfo->cconvert->start_pass) (cinfo);
(*cinfo->upsample->start_pass) (cinfo);
if (cinfo->quantize_colors)
(*cinfo->cquantize->start_pass) (cinfo, master->pub.is_dummy_pass);
(*cinfo->post->start_pass) (cinfo,
(master->pub.is_dummy_pass ? JBUF_SAVE_AND_PASS : JBUF_PASS_THRU));
(*cinfo->main->start_pass) (cinfo, JBUF_PASS_THRU);
}
上面的几个start_pass函数(quantize_colors为FALSE,所以不调Quantize start pass函数),请分别参考:
cconvert->pub.start_pass = start_pass_dcolor; ( jdcolor.c中的jinit_color_deconverter)
upsample->pub.start_pass = start_pass_upsample; ( jdsample.c中的jinit_upsampler)
post->pub.start_pass = start_pass_dpost; ( jdpostct.c中的 jinit_d_post_controller )
mainp->pub.start_pass = start_pass_main; ( jdmainct.c中的 jinit_d_main_controller )
那么依次看下来:
start_pass_dcolor ( 空函数)
start_pass_upsample : 也只是设置了next_row_out 和 rows_to_go 。
start_pass_dpost (传的是JBUF_PASS_THRU):
只是做了 post->pub.post_process_data = cinfo->upsample->upsample;
start_pass_main :
( cinfo->upsample->need_context_rows为FALSE,所以执行的是:
mainp->pub.process_data = process_data_simple_main;
mainp->buffer_full = FALSE;/* Mark buffer empty */
mainp->rowgroup_ctr = 0;
jpeg_start_decompress ( jdapistd.c )
-> jinit_master_decompress
output_pass_setup
jinit_master_decompress
-> master_selection
master_selection
-> jpeg_calc_output_dimensions
prepare_range_limit_table
jinit_color_deconverter
jinit_upsampler
jinit_d_post_controller
jinit_inverse_dct
jinit_huff_decoder
jinit_d_coef_controller
jinit_d_main_controller
output_pass_setup
-> (*cinfo->master->prepare_for_output_pass)
(此处调用的其实是在jinit_master_decompress函数中赋值的:
master->pub.prepare_for_output_pass = prepare_for_output_pass;
所以,此处调用的其实就是 prepare_for_output_pass )
--> 于是乎:
if (! cinfo->raw_data_out) {
if (! master->using_merged_upsample)
(*cinfo->cconvert->start_pass) (cinfo);
(*cinfo->upsample->start_pass) (cinfo);
if (cinfo->quantize_colors)
(*cinfo->cquantize->start_pass) (cinfo, master->pub.is_dummy_pass);
(*cinfo->post->start_pass) (cinfo,
(master->pub.is_dummy_pass ? JBUF_SAVE_AND_PASS : JBUF_PASS_THRU));
(*cinfo->main->start_pass) (cinfo, JBUF_PASS_THRU);
}
上面的几个start_pass函数(quantize_colors为FALSE,所以不调Quantize start pass函数),请分别参考:
cconvert->pub.start_pass = start_pass_dcolor; ( jdcolor.c中的jinit_color_deconverter)
upsample->pub.start_pass = start_pass_upsample; ( jdsample.c中的jinit_upsampler)
post->pub.start_pass = start_pass_dpost; ( jdpostct.c中的 jinit_d_post_controller )
mainp->pub.start_pass = start_pass_main; ( jdmainct.c中的 jinit_d_main_controller )
那么依次看下来:
start_pass_dcolor ( 空函数)
start_pass_upsample : 也只是设置了next_row_out 和 rows_to_go 。
start_pass_dpost (传的是JBUF_PASS_THRU):
只是做了 post->pub.post_process_data = cinfo->upsample->upsample;
start_pass_main :
( cinfo->upsample->need_context_rows为FALSE,所以执行的是:
mainp->pub.process_data = process_data_simple_main;
mainp->buffer_full = FALSE; /* Mark buffer empty */
mainp->rowgroup_ctr = 0;
转载请注明来源: http://blog.csdn.net/happy08god/article/details/10198011
至此,jpeg_start_decompress分析结束。大家可能觉得很奇怪,怎么还没开始解压的具体操作啊?
对,这个函数主要是设置处理函数和对一些变量的初始化,真正解压出数据,还是在获取数据的
地方(如jpeg_read_scanlines 或 jpeg_read_raw_data)。所以,如果你只是想获取解压图像
的长宽或者一些其他参数,通过调用jpeg_start_decompress 或者 jpeg_calc_output_dimensions)
所花费的时间是比较少的。
(2)jpeg_read_scanlines
从传入的数据可以看到,输出的数据(一行)要放到JSAMPARRAY类型。
progress_monitor 用来监控解压情况(可以获取当前解压的百分比,如同winrar等解压软件一样)
之后会调用(*cinfo->main->process_data) (cinfo, scanlines, &row_ctr, max_lines);来进行解压,
并将解压后的数据存放到scanlines中。
如上面的分析,此处实际调用的是: mainp->pub.process_data = process_data_simple_main;
即 process_data_simple_main 函数 (jdmainct.c)。
(*cinfo->coef->decompress_data) (cinfo, mainp->buffer) 这句就是做的具体解压的动作。
对应的是jdcoefct.c里面的decompress_data (后面的我就不继续说了),得到的输出数据是
JSAMPIMAGE类型的,此类型实际上是将Y,U,V分开了 (所以我个人觉得YUV420P3的输入数据,处理
起来会稍微快些)。
后面进行的是后级处理 ( post-process ), 是通过(*cinfo->post->post_process_data) 调用的。
通过上面的post->pub.post_process_data = cinfo->upsample->upsample; 其实就是jdsample.c
中的sep_upsample
此处会分别调用各个component的upsample函数,如果要做upsample,那么upsample的输出buffer是在
jinit_upsampler中分配的,如下:
if (need_buffer) {
upsample->color_buf[ci] = (*cinfo->mem->alloc_sarray)
((j_common_ptr) cinfo, JPOOL_IMAGE,
(JDIMENSION) jround_up((long) cinfo->output_width,
(long) cinfo->max_h_samp_factor),
(JDIMENSION) cinfo->max_v_samp_factor);
}
之后会调用color_convert进行颜色空间的转换,如果你是YUV -> YUV,此时就不会做转换,
调用对应的null_convert函数;如果你使用默认的out_color_space,即RGB,而你的输入
为YUV,则调用ycc_rgb_convert进行YUV->RGB转换。
在color_convert处理之前,数据是以类似YUV420P3那样的三平面的方式存储的,之后
在调用null_convert的时候,会将数据依次收集起来,进行pack,得到YUV444 Packed
这样格式的数据。
(void)
null_convert (j_decompress_ptr cinfo,
JSAMPIMAGE input_buf, JDIMENSION input_row,
JSAMPARRAY output_buf, int num_rows)
{
int ci;
register int nc = cinfo->num_components;
register JSAMPROW outptr;
register JSAMPROW inptr;
register JDIMENSION col;
JDIMENSION num_cols = cinfo->output_width;
while (--num_rows >= 0) {
for (ci = 0; ci < nc; ci++) {
inptr = input_buf[ci][input_row];
outptr = output_buf[0] + ci;
for (col = 0; col < num_cols; col++) {
*outptr = *inptr++; /* needn't bother with GETJSAMPLE() here */
outptr += nc;
}
}
input_row++;
output_buf++;
}
}
版权归本人所有,如需转载,请注明出处:http://blog.csdn.net/happy08god/article/details/10198011