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的数据结构中,从而完成了整个的编码流程



0 0