4. JPEG质量参数调整

任务说明

本案例在Android Camera2相机预览回调中获取YUV_420_888图像,使用不同质量参数(80/90/100)执行JPEG编码,对比输出文件大小与图像清晰度差异。通过实际编码结果验证质量参数对压缩率与画质的影响规律。

核心实现

// JpegQualityEncoder.java
public class JpegQualityEncoder {
    private static final String TAG = "JpegQualityEncoder";
    private ImageReader mImageReader;
    private File mOutputDir;

    public JpegQualityEncoder(Context context) {
        mOutputDir = new File(context.getExternalFilesDir(null), "jpeg_quality_test");
        if (!mOutputDir.exists()) mOutputDir.mkdirs();
    }

    // 创建不同质量参数的ImageReader(质量通过编码阶段控制)
    public ImageReader createImageReader(int width, int height) {
        mImageReader = ImageReader.newInstance(width, height, ImageFormat.YUV_420_888, 2);
        mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
            @Override
            public void onImageAvailable(ImageReader reader) {
                Image image = reader.acquireLatestImage();
                if (image != null) {
                    // 分别用三种质量编码同一帧
                    encodeWithQualities(image, new int[]{80, 90, 100});
                    image.close();
                }
            }
        }, null);
        return mImageReader;
    }

    private void encodeWithQualities(Image image, int[] qualities) {
        ByteBuffer yBuffer = image.getPlanes()[0].getBuffer();
        ByteBuffer uBuffer = image.getPlanes()[1].getBuffer();
        ByteBuffer vBuffer = image.getPlanes()[2].getBuffer();

        int ySize = yBuffer.remaining();
        int uSize = uBuffer.remaining();
        int vSize = vBuffer.remaining();

        byte[] nv21 = new byte[ySize + uSize + vSize];
        yBuffer.get(nv21, 0, ySize);
        vBuffer.get(nv21, ySize, vSize);
        uBuffer.get(nv21, ySize + vSize, uSize);

        for (int quality : qualities) {
            encodeJpeg(nv21, image.getWidth(), image.getHeight(), quality);
        }
    }

    private void encodeJpeg(byte[] nv21, int width, int height, int quality) {
        YuvImage yuvImage = new YuvImage(nv21, ImageFormat.NV21, width, height, null);
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        yuvImage.compressToJpeg(new Rect(0, 0, width, height), quality, outStream);

        File outputFile = new File(mOutputDir, "quality_" + quality + "_" + System.currentTimeMillis() + ".jpg");
        try (FileOutputStream fos = new FileOutputStream(outputFile)) {
            fos.write(outStream.toByteArray());
            Log.d(TAG, "Quality=" + quality + ", Size=" + outStream.size() + " bytes, File=" + outputFile.getName());
        } catch (IOException e) {
            Log.e(TAG, "Save failed: " + e.getMessage());
        }
    }
}

集成到Camera2预览

// 在Camera2配置中替换原有ImageReader
JpegQualityEncoder encoder = new JpegQualityEncoder(this);
mImageReader = encoder.createImageReader(640, 480);

// 创建CaptureSession时使用该ImageReader的Surface
List<Surface> surfaces = new ArrayList<>();
Surface imageSurface = mImageReader.getSurface();
surfaces.add(imageSurface);
// 如有预览需求可同时添加mPreviewSurface

验证方法

  1. 运行应用并授权相机权限,应用启动后自动捕获一帧图像并分别以质量80/90/100编码保存
  2. 打开Logcat过滤“JpegQualityEncoder”,观察三条日志记录,确认三个文件均已生成
  3. 使用文件管理器进入Android/data/<包名>/files/jpeg_quality_test/目录,查看三个JPEG文件
  4. 对比文件大小:预期质量100文件最大,质量80文件最小(具体数值取决于图像内容,但大小顺序固定)
  5. 将三个文件传输到电脑或支持图片缩放的设备,放大同一细节区域对比清晰度:质量100边缘最锐利,质量80可能出现块状模糊

判定标准:三个质量参数均成功生成JPEG文件;文件大小随质量值递减;视觉上质量100图像细节保留最完整,质量80图像存在可感知的压缩伪影。