Android libJpeg使用

Android 5.0推出了新的Camera API

android.hardware.camera2

就的Camera API在新的版本已經(jīng)被廢棄掉了蜗巧。
關(guān)于Camera2如何使用球榆,相信網(wǎng)上也有一大堆文章俩檬,我們這里不做講解琳钉。

如果你按照
https://github.com/googlesamples/android-Camera2Basic
這里面的方式來拍照的話,你會(huì)發(fā)現(xiàn),無論如何也得不到舊版本Camera API拍出的高質(zhì)量照片了。

    Size largest = Collections.max(
                    Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
                    new CompareSizesByArea());
     mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),
                    ImageFormat.JPEG, /*maxImages*/2);

這里我用的測(cè)試機(jī)是三星的s6,這臺(tái)手機(jī)后置攝像頭拍攝16:9的照片纵势,最大分辨率為5312x2988,但是如果你想上面的代碼所示拍出來的照片最大分辨率為3264x2448。

那怎么辦呢钦铁,我去翻看了Android 6.0系統(tǒng)源碼里的Camera app的代碼软舌,發(fā)現(xiàn)Google的那幫人用的格式為YUV420_888
當(dāng)然還有另外一個(gè)格式:RAW_SENSOR,Google的前輩們把這種格式保存成了png
那我們想要的肯定是jpeg嘛(jpeg和png的區(qū)別自行g(shù)oogle)

    if (sCaptureImageFormat == ImageFormat.RAW_SENSOR) {
        if (!RAW_DIRECTORY.exists()) {
            if (!RAW_DIRECTORY.mkdirs()) {
                throw new RuntimeException("Could not create RAW directory.");
            }
        }
        File dngFile = new File(RAW_DIRECTORY, capture.session.getTitle() + ".dng");
        writeDngBytesAndClose(capture.image, capture.totalCaptureResult,
                mCharacteristics, dngFile);
    } else {
        // Since this is not an HDR+ session, we will just save the
        // result.
        byte[] imageBytes = acquireJpegBytesAndClose(capture.image);
        saveJpegPicture(imageBytes, capture.parameters, capture.session,
                capture.totalCaptureResult);
    }

然后就看到了這段代碼,一直向下跟蹤牛曹,發(fā)現(xiàn)了

    private static native int compressJpegFromYUV420pNative(
        int width, int height,
        Object yBuf, int yPStride, int yRStride,
        Object cbBuf, int cbPStride, int cbRStride,
        Object crBuf, int crPStride, int crRStride,
        Object outBuf, int outBufCapacity,
        int quality,
        int cropLeft, int cropTop, int cropRight, int cropBottom,
        int rot90);

看看對(duì)應(yīng)的c/c++代碼

    #include "jpegutil.h"
    ......
    extern "C" {
    #include "jpeglib.h"
    }

果然 用到了libjpeg

===== 以下內(nèi)容來自http://blog.csdn.net/talkxin/article/details/50696511 ======

為何Android圖片壓縮效率比IOS低質(zhì)量差
為什么Android的圖片壓縮質(zhì)量要比iPhone的壓縮質(zhì)量差很多佛点,這是因?yàn)锳ndroid底層犯的一個(gè)小錯(cuò)誤:libjpeg。并且這個(gè)錯(cuò)誤一直持續(xù)到了今天黎比。
libjpeg是廣泛使用的開源JPEG圖像庫(參考 http://en.wikipedia.org/wiki/Libjpeg )超营,安卓也依賴libjpeg來壓縮圖片。通過查看源碼阅虫,我們會(huì)發(fā)現(xiàn)安卓并不是直接封裝的libjpeg演闭,而是基于了另一個(gè)叫Skia的開源項(xiàng)目 (http://en.wikipedia.org/wiki/Skia_Graphics_Engine)來作為的圖像處理引擎。Skia是谷歌自己維 護(hù)著的一個(gè)大而全的引擎颓帝,各種圖像處理功能均在其中予以實(shí)現(xiàn)米碰,并且廣泛的應(yīng)用于谷歌自己和其它公司的產(chǎn)品中(如:Chrome、Firefox购城、 Android等)见间。Skia對(duì)libjpeg進(jìn)行了良好的封裝,基于這個(gè)引擎可以很方便為操作系統(tǒng)工猜、瀏覽器等開發(fā)圖像處理功能。 libjpeg在壓縮圖像時(shí)菱蔬,有一個(gè)參數(shù)叫optimize_coding篷帅,關(guān)于這個(gè)參數(shù),libjpeg.doc有如下解釋:

boolean optimize_coding
TRUE causes the compressor to compute optimal Huffman coding tables
for the image. This requires an extra pass over the data and
therefore costs a good deal of space and time. The default is
FALSE, which tells the compressor to use the supplied or default
Huffman tables. In most cases optimal tables save only a few percent
of file size compared to the default tables. Note that when this is
TRUE, you need not supply Huffman tables at all, and any you do
supply will be overwritten.

這段話大概的意思就是如果設(shè)置optimize_coding為TRUE拴泌,將會(huì)使得壓縮圖像過程中基于圖像數(shù)據(jù)計(jì)算哈弗曼表(關(guān)于圖片壓縮中的哈弗曼表魏身,請(qǐng)自行查閱相關(guān)資料),由于這個(gè)計(jì)算會(huì)顯著消耗空間和時(shí)間蚪腐,默認(rèn)值被設(shè)置為FALSE箭昵。
這段解釋乍看起來沒有任何問題,libjpeg的代碼也經(jīng)受了十多年的考驗(yàn)回季,健壯而高效家制。但很多人忽略了這一點(diǎn),那就是泡一,這段解釋是十多年前寫的颤殴,對(duì)于當(dāng) 時(shí)的計(jì)算設(shè)備來說,空間和時(shí)間的消耗可能是顯著的鼻忠,但到今天涵但,這似乎不應(yīng)再是問題,相反,我們應(yīng)該更多的考慮圖片的品質(zhì)(越來越好的顯示技術(shù))和圖片的大 邪痢(越來越依賴于云服務(wù))瞳脓。
谷歌的Skia項(xiàng)目工程師們最終沒有設(shè)置這個(gè)參數(shù),optimize_coding在Skia中默認(rèn)的等于了FALSE澈侠,這就意味著更差的圖片質(zhì)量和更大的圖片文件劫侧,而壓縮圖片過程中所耗費(fèi)的時(shí)間和空間其實(shí)反而是可以忽略不計(jì)的。那么埋涧,這個(gè)參數(shù)的影響究竟會(huì)有多大呢板辽?
經(jīng)我們實(shí)測(cè),使用相同的原始圖片棘催,分別設(shè)置optimize_coding=TRUE和FALSE進(jìn)行壓縮劲弦,想達(dá)到接近的圖片質(zhì)量(用Photoshop 放大到像素級(jí)逐塊對(duì)比),F(xiàn)ALSE時(shí)的圖片大小大約是TRUE時(shí)的5-10倍醇坝。換句話說邑跪,如果我們想在FALSE和TRUE時(shí)壓縮成相同大小的JPEG 圖片,F(xiàn)ALSE的品質(zhì)將大大遜色于TRUE的(雖然品質(zhì)很難量化呼猪,但我們不妨說成是差5-10倍)画畅。
我們又對(duì)Android和iOS進(jìn)行了對(duì)比(均使用標(biāo)準(zhǔn)的JPEG壓縮方法),兩個(gè)系統(tǒng)都沒有提供設(shè)置optimize_coding的接口(通過閱讀源 碼宋距,我們已經(jīng)知道Android是FALSE轴踱,iOS不詳),當(dāng)壓縮相同的原始圖片時(shí)谚赎,結(jié)果也是一樣淫僻,iOS完勝。想要品質(zhì)接近壶唤,文件大小就會(huì)差出 5-10倍雳灵,而如果要壓縮出相同大小的文件,Android的壓縮品質(zhì)簡(jiǎn)直就是慘不忍睹闸盔。
結(jié)果說明悯辙,蘋果很清楚optimize_coding參數(shù)和哈弗曼表的意義,這里需要特別指出迎吵,蘋果使用的哈弗曼表算法與libjpeg(及我們后來自行 采用的libjpeg-turbo)不同躲撰,像素級(jí)可以看出區(qū)別,蘋果似乎基于libjpeg又進(jìn)行了進(jìn)一步的優(yōu)化击费,壓縮出來的圖片細(xì)節(jié)上更柔和茴肥、更平滑。

在Android項(xiàng)目中如何使用libjpeg-trubo

首先你要安裝ndk荡灾,如果不知道ndk是什么瓤狐,建議你先從一些簡(jiǎn)單的Android知識(shí)開始補(bǔ)這文章可能不太適合你瞬铸;

第二你要安裝git(如果不會(huì)請(qǐng)google)

    git clone git://git.linaro.org/people/tomgall/libjpeg-turbo/libjpeg-turbo.git -b linaro-android

把最新的版本克隆下來

2、編譯

克隆下來的文件夾名為libjpeg-turbo础锐,所以我們?cè)谑褂肗DK編譯前需要將文件夾命名為JNI:

    mv libjpeg-turbo jni

使用NDK編譯時(shí)嗓节,這里需要注意的是APP_ABI這個(gè)參數(shù),若需要在不同的平臺(tái)下運(yùn)行皆警,則需要設(shè)置平臺(tái)參數(shù)拦宣,如例所示,將編譯出兩個(gè)cpu平臺(tái)的so庫信姓,不同的平臺(tái)用逗號(hào)分隔

    ndk-build APP_ABI=armeabi-v7a,armeabi

這時(shí)就可以看到在jni同目錄下會(huì)生成libs與objs兩個(gè)文件夾鸵隧,生成的.so類庫就在libs文件夾內(nèi)。

====以上內(nèi)容來自http://blog.csdn.net/talkxin/article/details/50696511 ========

你還需要把頭文件找齊意推,都在我們剛剛克隆下來的代碼目錄里

cderror.h
cdjpeg.h
config.h
jconfig.h
jerror.h
jinclude.h
jmorecfg.h
jpeglib.h
jversion.h

好了豆瘫,接下來是怎么編寫c代碼

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "jpeg/jpeglib.h"
#import <omp.h>

#ifdef ANDROID

#include <jni.h>
#include <android/log.h>

#define LOGE(format, ...)  __android_log_print(ANDROID_LOG_ERROR, "    (>_<)", format, ##__VA_ARGS__)
#define LOGI(format, ...)  __android_log_print(ANDROID_LOG_INFO,  "(^_^)", format, ##__VA_ARGS__)
#else
#define LOGE(format, ...)  printf("(>_<) " format "\n", ##__VA_ARGS__)
#define LOGI(format, ...)  printf("(^_^) " format "\n", ##__VA_ARGS__)
#endif

int write_JPEG_file(const char *filename, unsigned char *yData, unsigned char *uData,
                unsigned char *vData, int quality, int image_width, int image_height);

void Java_${這里替換成類的全路徑}_writeJpegFile(JNIEnv *env, jobject jobj,
                                                                     jstring fileName,
                                                                     jobject yBuffer,
                                                                     jint yLen,
                                                                     jobject cbBuffer,
                                                                     jint cbLen,
                                                                     jobject crBuffer,
                                                                     jint uvStride,
                                                                     jint quality,
                                                                     jint width, jint height) {
    char *filename[500] = {0};
    sprintf(filename, "%s", (*env)->GetStringUTFChars(env, fileName, NULL));
    jbyte *y = (*env)->GetDirectBufferAddress(env, yBuffer);
    jbyte *cb = (*env)->GetDirectBufferAddress(env, cbBuffer);
    jbyte *cr = (*env)->GetDirectBufferAddress(env, crBuffer);
    uint8_t *uData = malloc(cbLen);
    uint8_t *vData = malloc(cbLen);
    int j, k;

    int uLimit = 0;
    int vLimit = 0;
    if (uvStride == 2) { // yuv420 sp uv交錯(cuò)
        #pragma omp parallel for num_threads(4)
        for (j = 0; j < cbLen; j++) {
            if (j % 2 == 0) {
                uData[uLimit++] = cb[j];
            } else {
                vData[vLimit++] = cb[j];
            }
        }
        #pragma omp parallel for num_threads(4)
        for (k = 0; k < cbLen; k++) {
            if (k % 2 == 0) {
                uData[uLimit++] = cr[k];
            } else {
                vData[vLimit++] = cr[k];
            }
        }
        write_JPEG_file(filename, y, uData, vData, quality, width, height);
    } else {    // yuv420p
        write_JPEG_file(filename, y, cb, cr, quality, width, height);
    }

    free(uData);
    free(vData);
}

int write_JPEG_file(const char *filename, unsigned char *yData, unsigned char *uData,
                unsigned char *vData, int quality, int image_width, int image_height) {
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;

    FILE *outfile;
    JSAMPIMAGE buffer;
    unsigned char *pSrc, *pDst;
    int band, i, buf_width[3], buf_height[3];
    cinfo.err = jpeg_std_error(&jerr);
    jpeg_create_compress(&cinfo); 
    if ((outfile = fopen(filename, "wb")) == NULL) {
    return -1;
    }
    jpeg_stdio_dest(&cinfo, outfile);
    cinfo.image_width = image_width;  // image width and height, in pixels
    cinfo.image_height = image_height;
    cinfo.input_components = 3;    // # of color components per pixel
    cinfo.in_color_space = JCS_RGB;  //colorspace of input image

    jpeg_set_defaults(&cinfo);
    jpeg_set_quality(&cinfo, quality, TRUE);

    cinfo.raw_data_in = TRUE;
    cinfo.jpeg_color_space = JCS_YCbCr;
    cinfo.comp_info[0].h_samp_factor = 2;
    cinfo.comp_info[0].v_samp_factor = 2;

    jpeg_start_compress(&cinfo, TRUE);

    buffer = (JSAMPIMAGE) (*cinfo.mem->alloc_small)((j_common_ptr) &cinfo,
                                                JPOOL_IMAGE, 3 * sizeof(JSAMPARRAY));
    #pragma omp parallel for num_threads(4)
    for (band = 0; band < 3; band++) {
        buf_width[band] = cinfo.comp_info[band].width_in_blocks * DCTSIZE;
        buf_height[band] = cinfo.comp_info[band].v_samp_factor * DCTSIZE;
        buffer[band] = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo,
                                              JPOOL_IMAGE, buf_width[band],   buf_height[band]);
    }
    unsigned char *rawData[3];
    rawData[0] = yData;
    rawData[1] = uData;
    rawData[2] = vData;

    int src_width[3], src_height[3];
    #pragma omp parallel for num_threads(4)
    for (i = 0; i < 3; i++) {
        src_width[i] = (i == 0) ? image_width : image_width / 2;
        src_height[i] = (i == 0) ? image_height : image_height / 2;
    }
    int max_line = cinfo.max_v_samp_factor * DCTSIZE;
    int counter;
    #pragma omp parallel for num_threads(4)
    for (counter = 0; cinfo.next_scanline < cinfo.image_height; counter++) {
    //buffer image copy.
        #pragma omp parallel for num_threads(4)
        for (band = 0; band < 3; band++) {  //每個(gè)分量分別處理
            int mem_size = src_width[band];//buf_width[band];
            pDst = (unsigned char *) buffer[band][0];
            pSrc = (unsigned char *) rawData[band] + counter * buf_height[band] *
                                                 src_width[band];//buf_width[band];  //yuv.data[band]分別表示YUV起始地址
            #pragma omp parallel for num_threads(4)
            for (i = 0; i < buf_height[band]; i++) { //處理每行數(shù)據(jù)
                memcpy(pDst, pSrc, mem_size);
                pSrc += src_width[band];//buf_width[band];
                pDst += buf_width[band];
            }
        }
        jpeg_write_raw_data(&cinfo, buffer, max_line);
    }
    jpeg_finish_compress(&cinfo);
    fclose(outfile);
    jpeg_destroy_compress(&cinfo);
    return 0;
}

這里面用到了openMP對(duì)for循環(huán)進(jìn)行并線處理,感興趣的同學(xué)可以去google一下菊值,這里只簡(jiǎn)單介紹一下怎么用

首先要加入頭文件

    #include<omp.h>

然后你可能會(huì)驚恐的發(fā)現(xiàn)AndroidStudio找不到這個(gè)東西(用eclipse的...祝你好運(yùn)吧) 外驱,別急。打開module里的 build.gradle文件加上

      cmake {
             arguments "-DANDROID_TOOLCHAIN=gcc"
             ...
             cFlags="-fopenmp"
      }

然后在for循環(huán)前加上這句

     #pragma omp parallel for num_threads(4)

可以按需求指定并發(fā)數(shù)量

好了腻窒,寫到這里收筆昵宇。再逼逼我怕各位看客們煩??

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市儿子,隨后出現(xiàn)的幾起案子瓦哎,更是在濱河造成了極大的恐慌,老刑警劉巖柔逼,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杭煎,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡卒落,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門蜂桶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來儡毕,“玉大人,你說我怎么就攤上這事扑媚⊙澹” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵疆股,是天一觀的道長(zhǎng)费坊。 經(jīng)常有香客問我,道長(zhǎng)旬痹,這世上最難降的妖魔是什么附井? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任讨越,我火速辦了婚禮,結(jié)果婚禮上永毅,老公的妹妹穿的比我還像新娘把跨。我一直安慰自己,他們只是感情好沼死,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布着逐。 她就那樣靜靜地躺著,像睡著了一般意蛀。 火紅的嫁衣襯著肌膚如雪耸别。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天县钥,我揣著相機(jī)與錄音秀姐,去河邊找鬼。 笑死魁蒜,一個(gè)胖子當(dāng)著我的面吹牛囊扳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播兜看,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼锥咸,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了细移?” 一聲冷哼從身側(cè)響起搏予,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎弧轧,沒想到半個(gè)月后雪侥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡精绎,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年速缨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片代乃。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡旬牲,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出搁吓,到底是詐尸還是另有隱情原茅,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布堕仔,位于F島的核電站擂橘,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏摩骨。R本人自食惡果不足惜通贞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一朗若、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧滑频,春花似錦捡偏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至绘搞,卻和暖如春彤避,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背夯辖。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工琉预, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蒿褂。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓圆米,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親啄栓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子娄帖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容