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ù)量
好了腻窒,寫到這里收筆昵宇。再逼逼我怕各位看客們煩??