如果查看CameraX库的拍照功能ImageCapture类,可以发现两个常量:
private static final byte JPEG_QUALITY_MAXIMIZE_QUALITY_MODE = 100;
private static final byte JPEG_QUALITY_MINIMIZE_LATENCY_MODE = 95;
这两个常量用于CameraX的拍照时,设置保存JPEG图片的质量:
其中,最大质量模式的值为100,减少延迟模式的值为95。最终用在compressToJpeg的quality参数中:
YuvImage yuvImage = new YuvImage(yuvBytes, ImageFormat.NV21, imageProxy.getWidth(),
imageProxy.getHeight(), null);
...
...
yuvImage.compressToJpeg(imageRect, mQuality, os);
而CameraX默认使用减少延迟模式进行拍照:
@CaptureMode
private static final int DEFAULT_CAPTURE_MODE = CAPTURE_MODE_MINIMIZE_LATENCY;
既然YUV数据压缩为JPEG数据时,选择牺牲一点质量,换取更少的延迟,说明当质量为95时,相对于最大质量的100,消耗的时间有显著的减少。
实测代码如下:
public void testYuvImageCompressSpeed() {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.big_size_photo);
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int[] pixels = new int[width * height];
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
byte[] yuv = rgb2YCbCr420(pixels, width, height);
//
for (int i = 1; i <= 10; i++) {
String[] qualities = new String[21];
String[] costTimes = new String[21];
for (int quality = 100; quality >= 80; quality--) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
long startTime = System.currentTimeMillis();
YuvImage image = new YuvImage(yuv, ImageFormat.NV21, width, height, null); //将NV21 data保存成YuvImage
//图像压缩
image.compressToJpeg(
new Rect(0, 0, image.getWidth(), image.getHeight()),
quality, stream); // 将NV21格式图片,以质量 quality 压缩成Jpeg,并得到JPEG数据流
qualities[100 - quality] = String.valueOf(quality);
costTimes[100 - quality] = String.valueOf(System.currentTimeMillis() - startTime);
}
Log.d(TAG, "qualities:" + TextUtils.join(",", qualities));
Log.d(TAG, "costTimes:" + TextUtils.join(",", costTimes));
}
}
public byte[] rgb2YCbCr420(int[] pixels, int width, int height) {
int len = width * height;
//yuv格式数组大小,y亮度占len长度,u,v各占len/4长度。
byte[] yuv = new byte[len * 3 / 2];
int y, u, v;
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
//屏蔽ARGB的透明度值
int rgb = pixels[i * width + j] & 0x00FFFFFF;
//像素的颜色顺序为bgr,移位运算。
int r = rgb & 0xFF;
int g = (rgb >> 8) & 0xFF;
int b = (rgb >> 16) & 0xFF;
//套用公式
y = ((66 * r + 129 * g + 25 * b + 128) >> 8) + 16;
u = ((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128;
v = ((112 * r - 94 * g - 18 * b + 128) >> 8) + 128;
//调整
y = y < 16 ? 16 : (Math.min(y, 255));
u = u < 0 ? 0 : (Math.min(u, 255));
v = v < 0 ? 0 : (Math.min(v, 255));
//赋值
yuv[i * width + j] = (byte) y;
yuv[len + (i >> 1) * width + (j & ~1) + 0] = (byte) u;
yuv[len + +(i >> 1) * width + (j & ~1) + 1] = (byte) v;
}
}
return yuv;
}
使用手机拍摄了一个分辨率3456 x 4608,大小为8.3 MB的图片,就是代码中的R.drawable.big_size_photo文件。
解码转换为Bitmap文件,然后读取像素数据,转换为YUV格式,最后使用YuvImage的compressToJpeg函数压缩为Jpeg(压缩质量为100~80)。
得到测试数据,转换为图表如下:
其中,横轴为压缩质量(100~80),纵轴为时间(单位毫秒)。
可以看出,选项“减少延迟”的质量选取 95 这个值是有一定的道理:保证图片的质量同时,消耗的时间显著减少。
既然YUV压缩为JPEG时,消耗时间有这种关系,那么进一步拓展,Bitmap压缩为JPEG时,消耗时间是不是也这样的,下面进行测试:
public void testBitmapCompressSpeed() {
for (int i = 1; i <= 10; i++) {
String[] qualities = new String[21];
String[] costTimes = new String[21];
for (int quality = 100; quality >= 80; quality--) {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.big_size_photo);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
long startTime = System.currentTimeMillis();
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream);
bitmap.recycle();
qualities[100 - quality] = String.valueOf(quality);
costTimes[100 - quality] = String.valueOf(System.currentTimeMillis() - startTime);
}
Log.d(TAG, "qualities:" + TextUtils.join(",", qualities));
Log.d(TAG, "costTimes:" + TextUtils.join(",", costTimes));
}
}
同样使用R.drawable.big_size_photo这个图片。
解码转换为Bitmap文件,使用Bitmap的compress函数压缩为Jpeg(压缩质量为100~80)。
得到测试数据,转换为图表如下:
其中,横轴为压缩质量(100~80),纵轴为时间(单位毫秒)。
可以看出,Bitmap压缩为JPEG时,质量和耗时依然有类似的关系,如果只是想要减少延迟,图片的质量不想有太大的下降,依然可以选择 95 这个值。
PS:压缩高分辨率的图片时,这种关系比较明显,如果压缩分辨率比较低的普通图片,质量和耗时就没有这种明显的关系了。