Opencv中jpeg編碼完整流程分析
本文分析了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方法如下
不難發現,Opencv還是遵循著以往的JNI風格,所有物件都只傳遞物件起始地址,並且物件可以通過該地址恢復物件所有資訊,即存在以該地址為引數的建構函式// 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);
我們接著往下,看看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編碼的原理了,如果需要補充,可以看下以下博文,除了編碼原理外還有一些可能必要的知識
接下來先介紹一下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的資料結構中,從而完成了整個的編碼流程