3. JPEG编码基础

任务说明

本案例目标:使用Android的YuvImage类,将NV21格式的YUV数据编码为JPEG字节流。你将实现一个工具方法,输入NV21字节数组及图像宽高,输出JPEG压缩后的字节数组,并验证编码结果可被正确解码为Bitmap。

核心实现

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.graphics.YuvImage;

import java.io.ByteArrayOutputStream;

public class JpegEncoder {

    /**
     * 将NV21数据编码为JPEG字节数组
     *
     * @param nv21Data   NV21格式的YUV数据
     * @param width      图像宽度
     * @param height     图像高度
     * @param quality    JPEG压缩质量(0-100)
     * @return JPEG编码后的字节数组;若输入无效返回null
     */
    public static byte[] encodeNv21ToJpeg(byte[] nv21Data, int width, int height, int quality) {
        if (nv21Data == null || width <= 0 || height <= 0) {
            return null;
        }

        // NV21数据长度必须等于 1.5 * width * height
        int expectedLength = width * height * 3 / 2;
        if (nv21Data.length != expectedLength) {
            return null;
        }

        // 创建YuvImage对象,NV21对应ImageFormat.NV21
        YuvImage yuvImage = new YuvImage(nv21Data, ImageFormat.NV21, width, height, null);

        // 将YUV数据压缩为JPEG
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        boolean success = yuvImage.compressToJpeg(new Rect(0, 0, width, height), quality, outStream);

        if (!success) {
            return null;
        }

        return outStream.toByteArray();
    }

    /**
     * 验证JPEG数据能否被正确解码为Bitmap
     *
     * @param jpegData JPEG字节数组
     * @return 解码成功返回Bitmap,失败返回null
     */
    public static Bitmap decodeJpegToBitmap(byte[] jpegData) {
        if (jpegData == null || jpegData.length == 0) {
            return null;
        }
        return BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
    }
}

使用示例

// 模拟生成一张纯色NV21图像(仅用于演示编码流程)
int width = 320;
int height = 240;
byte[] nv21 = generateSolidNv21(width, height, (byte) 128, (byte) 128);

// 编码为JPEG,质量设为85
byte[] jpegBytes = JpegEncoder.encodeNv21ToJpeg(nv21, width, height, 85);

if (jpegBytes != null) {
    // 验证:解码JPEG并检查尺寸
    Bitmap bitmap = JpegEncoder.decodeJpegToBitmap(jpegBytes);
    if (bitmap != null) {
        // 预期输出:bitmap.getWidth() == 320, bitmap.getHeight() == 240
        // 预期输出:jpegBytes.length > 0(具体大小取决于图像内容与质量参数)
    }
}

// 生成纯色NV21数据的辅助方法(仅测试用)
private static byte[] generateSolidNv21(int width, int height, byte yValue, byte uvValue) {
    int ySize = width * height;
    int uvSize = ySize / 2;
    byte[] nv21 = new byte[ySize + uvSize];

    // Y平面填充
    for (int i = 0; i < ySize; i++) {
        nv21[i] = yValue;
    }
    // UV平面填充(NV21为VU交替存储)
    for (int i = 0; i < uvSize; i += 2) {
        nv21[ySize + i] = uvValue;     // V
        nv21[ySize + i + 1] = uvValue; // U
    }
    return nv21;
}

落地与验证

运行环境:Android API 12+(YuvImage自API 1起可用,此处无特殊限制)。

验证步骤:

  1. 将JpegEncoder类复制到你的Android工程中。
  2. 在任意Activity或单元测试中调用encodeNv21ToJpeg(),传入有效的NV21数据。
  3. 调用decodeJpegToBitmap()解码返回的JPEG字节。
  4. 判定标准:decodeJpegToBitmap()返回的Bitmap非空,且其宽高与输入宽高一致(即320×240)。若输入数据无效(长度不匹配或宽高为0),方法返回null,此时应检查输入数据来源。

预期输出说明:以上代码中generateSolidNv21()生成的是纯色图像,编码后JPEG字节数取决于图像内容与质量参数,无法预先给出精确数值;但解码得到的Bitmap宽高必然等于输入宽高,这是由JPEG编码格式保证的确定性结果。