Opencv中jpeg编码完整流程分析
来源:互联网 发布:语音输入法软件下载 编辑:程序博客网 时间:2024/05/17 02:07
本文分析了Opencv中jpeg的编码流程,希望能够在加速jpeg编码效率上获得一些启发
从Java层开始,Opencv 2.4.13中imencode函数封装在了Highgui类中,而3.0.0以后,Highgui类被取缔,相关编解码操作放在了ImgCodecs这个类里面估计是为了Java和C++保持统一风格吧。这里直接调用即可
Highgui.imencode(String ext, Mat img, MatOfByte buf)Highgui.imencode(String ext, Mat img, MatOfByte buf, MatOfInt params)
以上可以通过MatOfInt可指定编码时相关参数
开始看到这个时,感觉很奇怪,为啥要用MatOfInt来指定参数呢???不用着急,我们接着往下看
以上两个函数分别对应的native方法如下
// C++: bool imencode(String ext, Mat img, vector_uchar& buf, vector_int params = std::vector<int>()) private static native boolean imencode_0(String ext, long img_nativeObj, long buf_mat_nativeObj, long params_mat_nativeObj); private static native boolean imencode_1(String ext, long img_nativeObj, long buf_mat_nativeObj);不难发现,Opencv还是遵循着以往的JNI风格,所有对象都只传递对象起始地址,并且对象可以通过该地址恢复对象所有信息,即存在以该地址为参数的构造函数
我们接着往下,看看JNI层的代码,这里以3.1.0为例,代码位于OPENCV_HOME/release/modules/java/imgcodecs.cpp中
JNIEXPORT jboolean JNICALL Java_org_opencv_imgcodecs_Imgcodecs_imencode_10 (JNIEnv*, jclass, jstring, jlong, jlong, jlong);JNIEXPORT jboolean JNICALL Java_org_opencv_imgcodecs_Imgcodecs_imencode_10 (JNIEnv* env, jclass , jstring ext, jlong img_nativeObj, jlong buf_mat_nativeObj, jlong params_mat_nativeObj){ static const char method_name[] = "imgcodecs::imencode_10()"; try { LOGD("%s", method_name); std::vector<uchar> buf; Mat& buf_mat = *((Mat*)buf_mat_nativeObj); std::vector<int> params; Mat& params_mat = *((Mat*)params_mat_nativeObj); Mat_to_vector_int( params_mat, params ); const char* utf_ext = env->GetStringUTFChars(ext, 0); String n_ext( utf_ext ? utf_ext : "" ); env->ReleaseStringUTFChars(ext, utf_ext); Mat& img = *((Mat*)img_nativeObj); bool _retval_ = cv::imencode( n_ext, img, buf, params ); vector_uchar_to_Mat( buf, buf_mat ); return _retval_; } catch(const std::exception &e) { throwJavaException(env, &e, method_name); } catch (...) { throwJavaException(env, 0, method_name); } return 0;}JNIEXPORT jboolean JNICALL Java_org_opencv_imgcodecs_Imgcodecs_imencode_11 (JNIEnv*, jclass, jstring, jlong, jlong);JNIEXPORT jboolean JNICALL Java_org_opencv_imgcodecs_Imgcodecs_imencode_11 (JNIEnv* env, jclass , jstring ext, jlong img_nativeObj, jlong buf_mat_nativeObj){ static const char method_name[] = "imgcodecs::imencode_11()"; try { LOGD("%s", method_name); std::vector<uchar> buf; Mat& buf_mat = *((Mat*)buf_mat_nativeObj); const char* utf_ext = env->GetStringUTFChars(ext, 0); String n_ext( utf_ext ? utf_ext : "" ); env->ReleaseStringUTFChars(ext, utf_ext); Mat& img = *((Mat*)img_nativeObj); <span style="color:#FF0000;">bool _retval_ = cv::imencode( n_ext, img, buf );</span> vector_uchar_to_Mat( buf, buf_mat ); return _retval_; } catch(const std::exception &e) { throwJavaException(env, &e, method_name); } catch (...) { throwJavaException(env, 0, method_name); } return 0;}
以带参编译的方法为例,也就是imencode10(),首先从地址分别构造出原Java中的MatOfByte和ParamsMat两个Mat的数据结构,分别用于存储encode完的数据和相应传入的编码参数,接下来param_mat转换成了一个int的vector,我们跟进看下发现
void Mat_to_vector_int(Mat& mat, std::vector<int>& v_int){ v_int.clear(); CHECK_MAT(mat.type()==CV_32SC1 && mat.cols==1); v_int = (std::vector<int>) mat;}
转换过程相当简单。。。其实这个原来Java中MatOfInt的类,存储的其实就是一系列的32为signed int的值而已。。。这些data是可以直接进行类型转换的
在Java层我们仅需要这样构建params参数即可
MatOfInt params = new MatOfInt(Imgcodecs.CV_IMWRITE_JPEG_QUALITY, 90);对应的构造方法原型为public MatOfInt(int...a)
我们接着回到正题,拿到编译参数以后,真正的编码调用的是cv::imencode,这个函数的实现是在imgcodes/src下面的loadsave.cpp中
bool imencode( const String& ext, InputArray _image, std::vector<uchar>& buf, const std::vector<int>& params ){ Mat image = _image.getMat(); int channels = image.channels(); CV_Assert( channels == 1 || channels == 3 || channels == 4 ); ImageEncoder encoder = findEncoder( ext ); if( !encoder ) CV_Error( CV_StsError, "could not find encoder for the specified extension" ); if( !encoder->isFormatSupported(image.depth()) ) { CV_Assert( encoder->isFormatSupported(CV_8U) ); Mat temp; image.convertTo(temp, CV_8U); image = temp; } bool code; if( encoder->setDestination(buf) ) { code = encoder->write(image, params); encoder->throwOnEror(); CV_Assert( code ); } else { String filename = tempfile(); code = encoder->setDestination(filename); CV_Assert( code ); code = encoder->write(image, params); encoder->throwOnEror(); CV_Assert( code ); FILE* f = fopen( filename.c_str(), "rb" ); CV_Assert(f != 0); fseek( f, 0, SEEK_END ); long pos = ftell(f); buf.resize((size_t)pos); fseek( f, 0, SEEK_SET ); buf.resize(fread( &buf[0], 1, buf.size(), f )); fclose(f); remove(filename.c_str()); } return code;}
整个imencode的流程如下,先检查图像的通道数,然后检查文件类型判断是否支持,每个像素点的单通道是否是8bit,接下来setDescription的逻辑也非常简单
bool BaseImageEncoder::setDestination( std::vector<uchar>& buf ){ if( !m_buf_supported ) return false; m_buf = &buf; m_buf->clear(); m_filename = String(); return true;}
如上,就是清空一下缓存区,接下来就进入了对应encoder的write函数,对应JpegEncoder的源码如下
bool JpegEncoder::write( const Mat& img, const std::vector<int>& params ){ m_last_error.clear(); struct fileWrapper { FILE* f; fileWrapper() : f(0) {} ~fileWrapper() { if(f) fclose(f); } }; volatile bool result = false; fileWrapper fw; int width = img.cols, height = img.rows; std::vector<uchar> out_buf(1 << 12); AutoBuffer<uchar> _buffer; uchar* buffer; struct jpeg_compress_struct cinfo; JpegErrorMgr jerr; JpegDestination dest; jpeg_create_compress(&cinfo); cinfo.err = jpeg_std_error(&jerr.pub); jerr.pub.error_exit = error_exit; if( !m_buf ) { fw.f = fopen( m_filename.c_str(), "wb" ); if( !fw.f ) goto _exit_; jpeg_stdio_dest( &cinfo, fw.f ); } else { dest.dst = m_buf; dest.buf = &out_buf; jpeg_buffer_dest( &cinfo, &dest ); dest.pub.next_output_byte = &out_buf[0]; dest.pub.free_in_buffer = out_buf.size(); } if( setjmp( jerr.setjmp_buffer ) == 0 ) { cinfo.image_width = width; cinfo.image_height = height; int _channels = img.channels(); int channels = _channels > 1 ? 3 : 1; cinfo.input_components = channels; cinfo.in_color_space = channels > 1 ? JCS_RGB : JCS_GRAYSCALE; int quality = 95; int progressive = 0; int optimize = 0; int rst_interval = 0; int luma_quality = -1; int chroma_quality = -1; for( size_t i = 0; i < params.size(); i += 2 ) { if( params[i] == CV_IMWRITE_JPEG_QUALITY ) { quality = params[i+1]; quality = MIN(MAX(quality, 0), 100); } if( params[i] == CV_IMWRITE_JPEG_PROGRESSIVE ) { progressive = params[i+1]; } if( params[i] == CV_IMWRITE_JPEG_OPTIMIZE ) { optimize = params[i+1]; } if( params[i] == CV_IMWRITE_JPEG_LUMA_QUALITY ) { if (params[i+1] >= 0) { luma_quality = MIN(MAX(params[i+1], 0), 100); quality = luma_quality; if (chroma_quality < 0) { chroma_quality = luma_quality; } } } if( params[i] == CV_IMWRITE_JPEG_CHROMA_QUALITY ) { if (params[i+1] >= 0) { chroma_quality = MIN(MAX(params[i+1], 0), 100); } } if( params[i] == CV_IMWRITE_JPEG_RST_INTERVAL ) { rst_interval = params[i+1]; rst_interval = MIN(MAX(rst_interval, 0), 65535L); } } jpeg_set_defaults( &cinfo ); cinfo.restart_interval = rst_interval; jpeg_set_quality( &cinfo, quality, TRUE /* limit to baseline-JPEG values */ ); if( progressive ) jpeg_simple_progression( &cinfo ); if( optimize ) cinfo.optimize_coding = TRUE;#if JPEG_LIB_VERSION >= 70 if (luma_quality >= 0 && chroma_quality >= 0) { cinfo.q_scale_factor[0] = jpeg_quality_scaling(luma_quality); cinfo.q_scale_factor[1] = jpeg_quality_scaling(chroma_quality); if ( luma_quality != chroma_quality ) { /* disable subsampling - ref. Libjpeg.txt */ cinfo.comp_info[0].v_samp_factor = 1; cinfo.comp_info[0].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[1].h_samp_factor = 1; } jpeg_default_qtables( &cinfo, TRUE ); }#endif // #if JPEG_LIB_VERSION >= 70 jpeg_start_compress( &cinfo, TRUE ); if( channels > 1 ) _buffer.allocate(width*channels); buffer = _buffer; for( int y = 0; y < height; y++ ) { uchar *data = img.data + img.step*y, *ptr = data; if( _channels == 3 ) { icvCvt_BGR2RGB_8u_C3R( data, 0, buffer, 0, cvSize(width,1) ); ptr = buffer; } else if( _channels == 4 ) { icvCvt_BGRA2BGR_8u_C4C3R( data, 0, buffer, 0, cvSize(width,1), 2 ); ptr = buffer; } jpeg_write_scanlines( &cinfo, &ptr, 1 ); } jpeg_finish_compress( &cinfo ); result = true; }_exit_: if(!result) { char jmsg_buf[JMSG_LENGTH_MAX]; jerr.pub.format_message((j_common_ptr)&cinfo, jmsg_buf); m_last_error = jmsg_buf; } jpeg_destroy_compress( &cinfo ); return result;}
这里jpeg编码的实现使用了libjpeg,我们一步一步的分析
首先是一些变量的声明和初始化,接着通过判断m_buf是否为NULL,m_buf就是等待写入编码后数据的缓冲区起始地址,如果为NULL,则通过文件读入,这与之前的代码保持一致,对于jpegEncoder而言,m_buf非NULL。接下来用到了setjmp(见我上一篇博客),这里第一次返回总是0。接下来是一些参数的初始化以及指定参数的检测。我们直接跳过这些步骤,进入关键的部分
有几个关键函数
1,jpeg_create_compress(&cinfo);
2,jpeg_start_compress( &cinfo, TRUE );
3,icvCvt_BGR2RGB_8u_C3R( data, 0, buffer, 0, cvSize(width,1) ); 或 icvCvt_BGRA2BGR_8u_C4C3R( data, 0, buffer, 0, cvSize(width,1), 2 );
4,jpeg_write_scanlines( &cinfo, &ptr, 1 );
5,jpeg_finish_compress( &cinfo );
这里默认大家了解Jpeg编码的原理了,如果需要补充,可以看下以下博文,除了编码原理外还有一些可能必要的知识
Jpeg压缩原理
YUV编码格式
Gamma校正及其实现
接下来先介绍一下libjpeg中一些重要的数据结构,这里只给出较为详细的介绍,如果有问题可以对照libjpeg源码按照流程自行阅读
1,cinfo是一个jpeg_compress_struct,其包含的类型为
struct jpeg_compress_struct { jpeg_common_fields;/* Fields shared with jpeg_decompress_struct */ /* Destination for compressed data */ struct jpeg_destination_mgr * dest; /* Description of source image --- these fields must be filled in by * outer application before starting compression. in_color_space must * be correct before you can even call jpeg_set_defaults(). */ JDIMENSION image_width;/* input image width */ JDIMENSION image_height;/* input image height */ int input_components;/* # of color components in input image */ J_COLOR_SPACE in_color_space;/* colorspace of input image */ double input_gamma;/* image gamma of input image */ /* Compression parameters --- these fields must be set before calling * jpeg_start_compress(). We recommend calling jpeg_set_defaults() to * initialize everything to reasonable defaults, then changing anything * the application specifically wants to change. That way you won't get * burnt when new parameters are added. Also note that there are several * helper routines to simplify changing parameters. */ unsigned int scale_num, scale_denom; /* fraction by which to scale image */ JDIMENSION jpeg_width;/* scaled JPEG image width */ JDIMENSION jpeg_height;/* scaled JPEG image height */ /* Dimensions of actual JPEG image that will be written to file, * derived from input dimensions by scaling factors above. * These fields are computed by jpeg_start_compress(). * You can also use jpeg_calc_jpeg_dimensions() to determine these values * in advance of calling jpeg_start_compress(). */ int data_precision;/* bits of precision in image data */ int num_components;/* # of color components in JPEG image */ J_COLOR_SPACE jpeg_color_space; /* colorspace of JPEG image */ jpeg_component_info * comp_info; /* comp_info[i] describes component that appears i'th in SOF */ JQUANT_TBL * quant_tbl_ptrs[NUM_QUANT_TBLS]; int q_scale_factor[NUM_QUANT_TBLS]; /* ptrs to coefficient quantization tables, or NULL if not defined, * and corresponding scale factors (percentage, initialized 100). */ JHUFF_TBL * dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; JHUFF_TBL * ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; /* ptrs to Huffman coding tables, or NULL if not defined */ UINT8 arith_dc_L[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */ UINT8 arith_dc_U[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */ UINT8 arith_ac_K[NUM_ARITH_TBLS]; /* Kx values for AC arith-coding tables */ int num_scans;/* # of entries in scan_info array */ const jpeg_scan_info * scan_info; /* script for multi-scan file, or NULL */ /* The default value of scan_info is NULL, which causes a single-scan * sequential JPEG file to be emitted. To create a multi-scan file, * set num_scans and scan_info to point to an array of scan definitions. */ boolean raw_data_in;/* TRUE=caller supplies downsampled data */ boolean arith_code;/* TRUE=arithmetic coding, FALSE=Huffman */ boolean optimize_coding;/* TRUE=optimize entropy encoding parms */ boolean CCIR601_sampling;/* TRUE=first samples are cosited */ boolean do_fancy_downsampling; /* TRUE=apply fancy downsampling */ int smoothing_factor;/* 1..100, or 0 for no input smoothing */ J_DCT_METHOD dct_method;/* DCT algorithm selector */ /* The restart interval can be specified in absolute MCUs by setting * restart_interval, or in MCU rows by setting restart_in_rows * (in which case the correct restart_interval will be figured * for each scan). */ unsigned int restart_interval; /* MCUs per restart, or 0 for no restart */ int restart_in_rows;/* if > 0, MCU rows per restart interval */ /* Parameters controlling emission of special markers. */ boolean write_JFIF_header;/* should a JFIF marker be written? */ UINT8 JFIF_major_version;/* What to write for the JFIF version number */ UINT8 JFIF_minor_version; /* These three values are not used by the JPEG code, merely copied */ /* into the JFIF APP0 marker. density_unit can be 0 for unknown, */ /* 1 for dots/inch, or 2 for dots/cm. Note that the pixel aspect */ /* ratio is defined by X_density/Y_density even when density_unit=0. */ UINT8 density_unit;/* JFIF code for pixel size units */ UINT16 X_density;/* Horizontal pixel density */ UINT16 Y_density;/* Vertical pixel density */ boolean write_Adobe_marker;/* should an Adobe marker be written? */ J_COLOR_TRANSFORM color_transform; /* Color transform identifier, writes LSE marker if nonzero */ /* State variable: index of next scanline to be written to * jpeg_write_scanlines(). Application may use this to control its * processing loop, e.g., "while (next_scanline < image_height)". */ JDIMENSION next_scanline;/* 0 .. image_height-1 */ /* Remaining fields are known throughout compressor, but generally * should not be touched by a surrounding application. */ /* * These fields are computed during compression startup */ boolean progressive_mode;/* TRUE if scan script uses progressive mode */ int max_h_samp_factor;/* largest h_samp_factor */ int max_v_samp_factor;/* largest v_samp_factor */ int min_DCT_h_scaled_size;/* smallest DCT_h_scaled_size of any component */ int min_DCT_v_scaled_size;/* smallest DCT_v_scaled_size of any component */ JDIMENSION total_iMCU_rows;/* # of iMCU rows to be input to coef ctlr */ /* The coefficient controller receives data in units of MCU rows as defined * for fully interleaved scans (whether the JPEG file is interleaved or not). * There are v_samp_factor * DCTSIZE sample rows of each component in an * "iMCU" (interleaved MCU) row. */ /* * These fields are valid during any one scan. * They describe the components and MCUs actually appearing in the scan. */ int comps_in_scan;/* # of JPEG components in this scan */ jpeg_component_info * cur_comp_info[MAX_COMPS_IN_SCAN]; /* *cur_comp_info[i] describes component that appears i'th in SOS */ JDIMENSION MCUs_per_row;/* # of MCUs across the image */ JDIMENSION MCU_rows_in_scan;/* # of MCU rows in the image */ int blocks_in_MCU;/* # of DCT blocks per MCU */ int MCU_membership[C_MAX_BLOCKS_IN_MCU]; /* MCU_membership[i] is index in cur_comp_info of component owning */ /* i'th block in an MCU */ int Ss, Se, Ah, Al;/* progressive JPEG parameters for scan */ int block_size;/* the basic DCT block size: 1..16 */ const int * natural_order;/* natural-order position array */ int lim_Se;/* min( Se, DCTSIZE2-1 ) */ /* * Links to compression subobjects (methods and private variables of modules) */ struct jpeg_comp_master * master; struct jpeg_c_main_controller * main; struct jpeg_c_prep_controller * prep; struct jpeg_c_coef_controller * coef; struct jpeg_marker_writer * marker; struct jpeg_color_converter * cconvert; struct jpeg_downsampler * downsample; struct jpeg_forward_dct * fdct; struct jpeg_entropy_encoder * entropy; jpeg_scan_info * script_space; /* workspace for jpeg_simple_progression */ int script_space_size;};
2,jpeg_common_struct
typedef struct jpeg_common_struct * j_common_ptr;/* Routines that are to be used by both halves of the library are declared * to receive a pointer to this structure. There are no actual instances of * jpeg_common_struct, only of jpeg_compress_struct and jpeg_decompress_struct. */struct jpeg_common_struct { jpeg_common_fields; /* Fields common to both master struct types */ /* Additional fields follow in an actual jpeg_compress_struct or * jpeg_decompress_struct. All three structs must agree on these * initial fields! (This would be a lot cleaner in C++.) */};#define jpeg_common_fields \ struct jpeg_error_mgr * err; /* Error handler module */\ struct jpeg_memory_mgr * mem; /* Memory manager module */\ struct jpeg_progress_mgr * progress; /* Progress monitor, or NULL if none */\ void * client_data; /* Available for use by application */\ boolean is_decompressor; /* So common code can tell which is which */\ int global_state /* For checking call sequence validity */从注释中可以看出,这个jpeg_common_struct是编码器和解码器两个部分所共享的,其中仅包含了jpeg_common_fileds,也就是一些全局性质的变量信息
接下来是函数类型方面的宏定义,以便大家接下来看代码不至于太迷茫
/* a function called through method pointers: */#define METHODDEF(type)static type/* a function used only in its module: */#define LOCAL(type)static type/* a function referenced thru EXTERNs: */#define GLOBAL(type)type/* a reference to a GLOBAL function: */#define EXTERN(type)extern type
初看libjpeg代码,觉得真的是满眼的宏定义,这也越发显示了grep命令的强大之处,linux下面读源码神器啊
我们直接进入jpeg_create_compress中看下逻辑
<pre name="code" class="cpp">#define jpeg_create_compress(cinfo) \ jpeg_CreateCompress((cinfo), JPEG_LIB_VERSION, \ (size_t) sizeof(struct jpeg_compress_struct))#define jpeg_CreateCompress jCreaCompressEXTERN(void) jpeg_CreateCompress JPP((j_compress_ptr cinfo, int version, size_t structsize));<pre name="code" class="cpp">/* * Initialization of a JPEG compression object. * The error manager must already be set up (in case memory manager fails). */GLOBAL(void)jpeg_CreateCompress (j_compress_ptr cinfo, int version, size_t structsize){ int i; /* Guard against version mismatches between library and caller. */ cinfo->mem = NULL;/* so jpeg_destroy knows mem mgr not called */ if (version != JPEG_LIB_VERSION) ERREXIT2(cinfo, JERR_BAD_LIB_VERSION, JPEG_LIB_VERSION, version); if (structsize != SIZEOF(struct jpeg_compress_struct)) ERREXIT2(cinfo, JERR_BAD_STRUCT_SIZE, (int) SIZEOF(struct jpeg_compress_struct), (int) structsize); /* For debugging purposes, we zero the whole master structure. * But the application has already set the err pointer, and may have set * client_data, so we have to save and restore those fields. * Note: if application hasn't set client_data, tools like Purify may * complain here. */ { struct jpeg_error_mgr * err = cinfo->err; void * client_data = cinfo->client_data; /* ignore Purify complaint here */ MEMZERO(cinfo, SIZEOF(struct jpeg_compress_struct)); cinfo->err = err; cinfo->client_data = client_data; } cinfo->is_decompressor = FALSE; /* Initialize a memory manager instance for this object */ jinit_memory_mgr((j_common_ptr) cinfo); /* Zero out pointers to permanent structures. */ cinfo->progress = NULL; cinfo->dest = NULL; cinfo->comp_info = NULL; for (i = 0; i < NUM_QUANT_TBLS; i++) { cinfo->quant_tbl_ptrs[i] = NULL; cinfo->q_scale_factor[i] = 100; } for (i = 0; i < NUM_HUFF_TBLS; i++) { cinfo->dc_huff_tbl_ptrs[i] = NULL; cinfo->ac_huff_tbl_ptrs[i] = NULL; } /* Must do it here for emit_dqt in case jpeg_write_tables is used */ cinfo->block_size = DCTSIZE; cinfo->natural_order = jpeg_natural_order; cinfo->lim_Se = DCTSIZE2-1; cinfo->script_space = NULL; cinfo->input_gamma = 1.0;/* in case application forgets */ /* OK, I'm ready */ cinfo->global_state = CSTATE_START;}
jpeg_create_compress先检查调用方和使用库中jpeg的版本,如果版本一致则调用jinit_memory_mgr进行内存的初始化,再接着是各种参数的初始化,大家可以对照jpeg_compress_struct中各个参数的注释理解下这个逻辑
GLOBAL(void)jpeg_start_compress (j_compress_ptr cinfo, boolean write_all_tables){ if (cinfo->global_state != CSTATE_START) ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); if (write_all_tables) jpeg_suppress_tables(cinfo, FALSE);/* mark all tables to be written */ /* (Re)initialize error mgr and destination modules */ (*cinfo->err->reset_error_mgr) ((j_common_ptr) cinfo); (*cinfo->dest->init_destination) (cinfo); /* Perform master selection of active modules */ jinit_compress_master(cinfo); /* Set up for the first pass */ (*cinfo->master->prepare_for_pass) (cinfo); /* Ready for application to drive first pass through jpeg_write_scanlines * or jpeg_write_raw_data. */ cinfo->next_scanline = 0; cinfo->global_state = (cinfo->raw_data_in ? CSTATE_RAW_OK : CSTATE_SCANNING);}
jpeg_start_compress中的逻辑也相当简单,初始化error manager与编码结果存储模块,初始化编码master,ready for jpeg_write_scanlines blabla,值得一提的是jinit_compress_master
GLOBAL(void)jinit_compress_master (j_compress_ptr cinfo){ /* Initialize master control (includes parameter checking/processing) */ jinit_c_master_control(cinfo, FALSE /* full compression */); /* Preprocessing */ if (! cinfo->raw_data_in) { jinit_color_converter(cinfo); jinit_downsampler(cinfo); jinit_c_prep_controller(cinfo, FALSE /* never need full buffer here */); } /* Forward DCT */ jinit_forward_dct(cinfo); /* Entropy encoding: either Huffman or arithmetic coding. */ if (cinfo->arith_code) jinit_arith_encoder(cinfo); else { jinit_huff_encoder(cinfo); } /* Need a full-image coefficient buffer in any multi-pass mode. */ jinit_c_coef_controller(cinfo, (boolean) (cinfo->num_scans > 1 || cinfo->optimize_coding)); jinit_c_main_controller(cinfo, FALSE /* never need full buffer here */); jinit_marker_writer(cinfo); /* We can now tell the memory manager to allocate virtual arrays. */ (*cinfo->mem->realize_virt_arrays) ((j_common_ptr) cinfo); /* Write the datastream header (SOI) immediately. * Frame and scan headers are postponed till later. * This lets application insert special markers after the SOI. */ (*cinfo->marker->write_file_header) (cinfo);}
这里包括jpeg整个编码流程中重要的数据结构的初始化,注释非常清楚,这里不详细介绍
/* * Write some scanlines of data to the JPEG compressor. * * The return value will be the number of lines actually written. * This should be less than the supplied num_lines only in case that * the data destination module has requested suspension of the compressor, * or if more than image_height scanlines are passed in. * * Note: we warn about excess calls to jpeg_write_scanlines() since * this likely signals an application programmer error. However, * excess scanlines passed in the last valid call are *silently* ignored, * so that the application need not adjust num_lines for end-of-image * when using a multiple-scanline buffer. */GLOBAL(JDIMENSION)jpeg_write_scanlines (j_compress_ptr cinfo, JSAMPARRAY scanlines, JDIMENSION num_lines){ JDIMENSION row_ctr, rows_left; if (cinfo->global_state != CSTATE_SCANNING) ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); if (cinfo->next_scanline >= cinfo->image_height) WARNMS(cinfo, JWRN_TOO_MUCH_DATA); /* Call progress monitor hook if present */ if (cinfo->progress != NULL) { cinfo->progress->pass_counter = (long) cinfo->next_scanline; cinfo->progress->pass_limit = (long) cinfo->image_height; (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); } /* Give master control module another chance if this is first call to * jpeg_write_scanlines. This lets output of the frame/scan headers be * delayed so that application can write COM, etc, markers between * jpeg_start_compress and jpeg_write_scanlines. */ if (cinfo->master->call_pass_startup) (*cinfo->master->pass_startup) (cinfo); /* Ignore any extra scanlines at bottom of image. */ rows_left = cinfo->image_height - cinfo->next_scanline; if (num_lines > rows_left) num_lines = rows_left; row_ctr = 0; (*cinfo->main->process_data) (cinfo, scanlines, &row_ctr, num_lines); cinfo->next_scanline += row_ctr; return row_ctr;}
z在jpeg_write_scanlines中,首先仍然是预处理和初始化的逻辑,主要需要注意的是process_data中的实现,在jpeg_start_compress --> jinit_compress_master --> jinit_c_main_controller 中 cinfo->main->start_pass = start_pass_main,而start_pass_main中则将cinfo->main->pub.process_data = process_data_simple_main;
即调用process_data相当于调用了process_data_simple_main
/* * Process some data. * This routine handles the simple pass-through mode, * where we have only a strip buffer. */METHODDEF(void)process_data_simple_main (j_compress_ptr cinfo, JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, JDIMENSION in_rows_avail){ my_main_ptr mainp = (my_main_ptr) cinfo->main; while (mainp->cur_iMCU_row < cinfo->total_iMCU_rows) { /* Read input data if we haven't filled the main buffer yet */ if (mainp->rowgroup_ctr < (JDIMENSION) cinfo->min_DCT_v_scaled_size) (*cinfo->prep->pre_process_data) (cinfo, input_buf, in_row_ctr, in_rows_avail, mainp->buffer, &mainp->rowgroup_ctr, (JDIMENSION) cinfo->min_DCT_v_scaled_size); /* If we don't have a full iMCU row buffered, return to application for * more data. Note that preprocessor will always pad to fill the iMCU row * at the bottom of the image. */ if (mainp->rowgroup_ctr != (JDIMENSION) cinfo->min_DCT_v_scaled_size) return; /* Send the completed row to the compressor */ if (! (*cinfo->coef->compress_data) (cinfo, mainp->buffer)) { /* If compressor did not consume the whole row, then we must need to * suspend processing and return to the application. In this situation * we pretend we didn't yet consume the last input row; otherwise, if * it happened to be the last row of the image, the application would * think we were done. */ if (! mainp->suspended) { (*in_row_ctr)--; mainp->suspended = TRUE; } return; } /* We did finish the row. Undo our little suspension hack if a previous * call suspended; then mark the main buffer empty. */ if (mainp->suspended) { (*in_row_ctr)++; mainp->suspended = FALSE; } mainp->rowgroup_ctr = 0; mainp->cur_iMCU_row++; }}
process_data中首先则是一个数据的预处理逻辑pre_process_data,在jpeg_start_compress --> jinit_compress_master -->jinit_c_prep_controller中,prep->pub.pre_process_data = pre_process_data;
/* * Process some data in the simple no-context case. * * Preprocessor output data is counted in "row groups". A row group * is defined to be v_samp_factor sample rows of each component. * Downsampling will produce this much data from each max_v_samp_factor * input rows. */METHODDEF(void)pre_process_data (j_compress_ptr cinfo, JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, JDIMENSION in_rows_avail, JSAMPIMAGE output_buf, JDIMENSION *out_row_group_ctr, JDIMENSION out_row_groups_avail){ my_prep_ptr prep = (my_prep_ptr) cinfo->prep; int numrows, ci; JDIMENSION inrows; jpeg_component_info * compptr; while (*in_row_ctr < in_rows_avail && *out_row_group_ctr < out_row_groups_avail) { /* Do color conversion to fill the conversion buffer. */ inrows = in_rows_avail - *in_row_ctr; numrows = cinfo->max_v_samp_factor - prep->next_buf_row; numrows = (int) MIN((JDIMENSION) numrows, inrows); (*cinfo->cconvert->color_convert) (cinfo, input_buf + *in_row_ctr, prep->color_buf, (JDIMENSION) prep->next_buf_row, numrows); *in_row_ctr += numrows; prep->next_buf_row += numrows; prep->rows_to_go -= numrows; /* If at bottom of image, pad to fill the conversion buffer. */ if (prep->rows_to_go == 0 && prep->next_buf_row < cinfo->max_v_samp_factor) { for (ci = 0; ci < cinfo->num_components; ci++) { expand_bottom_edge(prep->color_buf[ci], cinfo->image_width, prep->next_buf_row, cinfo->max_v_samp_factor); } prep->next_buf_row = cinfo->max_v_samp_factor; } /* If we've filled the conversion buffer, empty it. */ if (prep->next_buf_row == cinfo->max_v_samp_factor) { (*cinfo->downsample->downsample) (cinfo, prep->color_buf, (JDIMENSION) 0, output_buf, *out_row_group_ctr); prep->next_buf_row = 0; (*out_row_group_ctr)++; } /* If at bottom of image, pad the output to a full iMCU height. * Note we assume the caller is providing a one-iMCU-height output buffer! */ if (prep->rows_to_go == 0 && *out_row_group_ctr < out_row_groups_avail) { for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; ci++, compptr++) { numrows = (compptr->v_samp_factor * compptr->DCT_v_scaled_size) / cinfo->min_DCT_v_scaled_size; expand_bottom_edge(output_buf[ci], compptr->width_in_blocks * compptr->DCT_h_scaled_size, (int) (*out_row_group_ctr * numrows), (int) (out_row_groups_avail * numrows)); } *out_row_group_ctr = out_row_groups_avail; break;/* can exit outer loop without test */ } }}
pre_process_data中主要涉及的就是color_convert和expand_bottom_edge两个逻辑,在jpeg_start_compress --> jinit_compress_master -->jinit_color_converter中,通过判断输入图像的color_space去选择相关color转换的逻辑,此时color_convert则对应了相关处理的逻辑;而expand_bottom_edge则通过扩展input的bottom来将output的height
扩展至input的height
接着是compress_data函数,jpeg_start_compress --> jinit_compress_master -->jinit_c_coef_controller中,cinfo->coef->pub.start_pass = start_pass_coef; 接着在start_pass_coef 中 coef->pub.compress_data = compress_data;可以得到compress_data的逻辑
/* * Process some data in the single-pass case. * We process the equivalent of one fully interleaved MCU row ("iMCU" row) * per call, ie, v_samp_factor block rows for each component in the image. * Returns TRUE if the iMCU row is completed, FALSE if suspended. * * NB: input_buf contains a plane for each component in image, * which we index according to the component's SOF position. */METHODDEF(boolean)compress_data (j_compress_ptr cinfo, JSAMPIMAGE input_buf){ my_coef_ptr coef = (my_coef_ptr) cinfo->coef; JDIMENSION MCU_col_num;/* index of current MCU within row */ JDIMENSION last_MCU_col = cinfo->MCUs_per_row - 1; JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; int blkn, bi, ci, yindex, yoffset, blockcnt; JDIMENSION ypos, xpos; jpeg_component_info *compptr; forward_DCT_ptr forward_DCT; /* Loop to write as much as one whole iMCU row */ for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; yoffset++) { for (MCU_col_num = coef->mcu_ctr; MCU_col_num <= last_MCU_col; MCU_col_num++) { /* Determine where data comes from in input_buf and do the DCT thing. * Each call on forward_DCT processes a horizontal row of DCT blocks * as wide as an MCU; we rely on having allocated the MCU_buffer[] blocks * sequentially. Dummy blocks at the right or bottom edge are filled in * specially. The data in them does not matter for image reconstruction, * so we fill them with values that will encode to the smallest amount of * data, viz: all zeroes in the AC entries, DC entries equal to previous * block's DC value. (Thanks to Thomas Kinsman for this idea.) */ blkn = 0; for (ci = 0; ci < cinfo->comps_in_scan; ci++) { compptr = cinfo->cur_comp_info[ci]; forward_DCT = cinfo->fdct->forward_DCT[compptr->component_index]; blockcnt = (MCU_col_num < last_MCU_col) ? compptr->MCU_width : compptr->last_col_width; xpos = MCU_col_num * compptr->MCU_sample_width; ypos = yoffset * compptr->DCT_v_scaled_size; /* ypos == (yoffset+yindex) * DCTSIZE */ for (yindex = 0; yindex < compptr->MCU_height; yindex++) { if (coef->iMCU_row_num < last_iMCU_row || yoffset+yindex < compptr->last_row_height) { (*forward_DCT) (cinfo, compptr, input_buf[compptr->component_index], coef->MCU_buffer[blkn], ypos, xpos, (JDIMENSION) blockcnt); if (blockcnt < compptr->MCU_width) { /* Create some dummy blocks at the right edge of the image. */ FMEMZERO((void FAR *) coef->MCU_buffer[blkn + blockcnt], (compptr->MCU_width - blockcnt) * SIZEOF(JBLOCK)); for (bi = blockcnt; bi < compptr->MCU_width; bi++) { coef->MCU_buffer[blkn+bi][0][0] = coef->MCU_buffer[blkn+bi-1][0][0]; } } } else { /* Create a row of dummy blocks at the bottom of the image. */ FMEMZERO((void FAR *) coef->MCU_buffer[blkn], compptr->MCU_width * SIZEOF(JBLOCK)); for (bi = 0; bi < compptr->MCU_width; bi++) { coef->MCU_buffer[blkn+bi][0][0] = coef->MCU_buffer[blkn-1][0][0]; } } blkn += compptr->MCU_width; ypos += compptr->DCT_v_scaled_size; } } /* Try to write the MCU. In event of a suspension failure, we will * re-DCT the MCU on restart (a bit inefficient, could be fixed...) */ if (! (*cinfo->entropy->encode_mcu) (cinfo, coef->MCU_buffer)) { /* Suspension forced; update state counters and exit */ coef->MCU_vert_offset = yoffset; coef->mcu_ctr = MCU_col_num; return FALSE; } } /* Completed an MCU row, but perhaps not an iMCU row */ coef->mcu_ctr = 0; } /* Completed the iMCU row, advance counters for next one */ coef->iMCU_row_num++; start_iMCU_row(cinfo); return TRUE;}
这里就完成了forward_DCT的步骤,另外比较重要的就是encode_mcu了,与之前的形式一样,encode_mcu在start_pass_huff中确定了相应的实现方式,也就完成了Huffman编码的过程。另外,encode_mcu中,huffman编码后的结果也被记入cinfo->dest的数据结构中,也就对应到了之前OpenCV调用libJpeg时的一个名为JpegDestication的数据结构中,从而完成了整个的编码流程
- Opencv中jpeg编码完整流程分析
- Jpeg编码完整流程解析
- JPEG 编码源代码分析
- JPEG编码基本流程概述
- 【完整的数据分析流程】
- 【完整的数据分析流程】
- 完整的数据分析流程
- x264编码流程分析
- JPEG编码
- JPEG编码
- 完整的申请邓白氏编码的流程
- 完整的申请邓白氏编码的流程
- oracle一个事务的完整流程分析
- Oracle 事务处理的完整流程分析
- ORACLE 事务的完整流程的分析
- oracle一个事务的完整流程分析
- ORALCE一个事物的完整分析流程
- oracle一个事务的完整流程分析
- hdu 5873 思维题
- Appium输入中文的问题
- 利用协方差矩阵特征向量创建坐标系
- 明日大哲的第一篇博客
- Java基础-面向对象思想
- Opencv中jpeg编码完整流程分析
- Linear Least Squares 的多项式表达和矩阵表达 与 Python 实现
- Search Insert Position_Leetcode_35
- hdu5873 Football Games(模拟)
- linux服务器rz命令上传文件
- (五)ArcGIS Server之发布网络分析服务
- 对操作系统的一般理解
- HTML5 基础(003_Indexed Database_2)
- effective stl 第17条: 使用“swap 技巧”除去多余的容量