ImageAnalysis给的是YUV420_888的数据,但是我需要的是NV21的数据

2023-04-15by@richyeohx-ax4MO&@ChatGPT.Model: gpt-3.5
richyeohx-ax4MO

ImageAnalysis给的是YUV420_888的数据,但是我需要的是NV21的数据

Avatar
Avatar

您可以使用以下代码将YUV420_888数据转换为NV21数据:

public static byte[] convertYUV420888ToNV21(Image image) {
    byte[] nv21;
    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();

    nv21 = new byte[ySize + uSize + vSize];

    yBuffer.get(nv21, 0, ySize);
    vBuffer.get(nv21, ySize, vSize);
    uBuffer.get(nv21, ySize + vSize, uSize);

    return nv21;
}

然后,您可以使用以下代码获取NV21数据:

byte[] nv21Data = convertYUV420888ToNV21(image);
richyeohx-ax4MO

考虑一下行和列的步长问题。

Avatar
Avatar

当从YUV420_888转换为NV21时,需要考虑行和列的步长问题。步长是指在内存中每个像素之间的间隔。这是因为在某些设备上,内存中的像素是不连续的,而是间隔着存储的。

以下是考虑步长问题的代码示例:

public static byte[] convertYUV420888ToNV21(Image image) {
    byte[] nv21;
    ByteBuffer yBuffer = image.getPlanes()[0].getBuffer();
    ByteBuffer uBuffer = image.getPlanes()[1].getBuffer();
    ByteBuffer vBuffer = image.getPlanes()[2].getBuffer();
    int yRowStride = image.getPlanes()[0].getRowStride();
    int uRowStride = image.getPlanes()[1].getRowStride();
    int vRowStride = image.getPlanes()[2].getRowStride();
    int yPixelStride = image.getPlanes()[0].getPixelStride();
    int vPixelStride = image.getPlanes()[2].getPixelStride();

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

    nv21 = new byte[ySize + uSize + vSize];

    int nv21Index = 0;
    int yIndex = 0;
    int uIndex = 0;
    int vIndex = 0;

    for (int i = 0; i < image.getHeight(); i++) {
        for (int j = 0; j < image.getWidth(); j++) {
            nv21[nv21Index++] = yBuffer.get(yIndex++);
            if (i % 2 == 0 && j % 2 == 0) {
                nv21[nv21Index++] = vBuffer.get(vIndex);
                nv21[nv21Index++] = uBuffer.get(uIndex);
                vIndex += vPixelStride;
                uIndex += uPixelStride;
            }
        }
        yIndex += yRowStride - image.getWidth() * yPixelStride;
        if (i % 2 == 0) {
            vIndex += vRowStride - image.getWidth() * vPixelStride;
            uIndex += uRowStride - image.getWidth() * uPixelStride;
        }
    }

    return nv21;
}

在此示例中,我们使用getYRowStride(),getURowStride()和getVRowStride()方法获取行步长,使用getYPixelStride()和getVPixelStride()方法获取列步长。我们使用这些变量来计算正确的索引,并将数据复制到NV21数组中。

richyeohx-ax4MO

还可以优化一下循环什么的么。

Avatar
Avatar

是的,您可以对循环进行优化,以提高转换速度。以下是优化后的代码示例:

public static byte[] convertYUV420888ToNV21(Image image) {
    byte[] nv21;
    ByteBuffer yBuffer = image.getPlanes()[0].getBuffer();
    ByteBuffer uBuffer = image.getPlanes()[1].getBuffer();
    ByteBuffer vBuffer = image.getPlanes()[2].getBuffer();
    int yRowStride = image.getPlanes()[0].getRowStride();
    int uRowStride = image.getPlanes()[1].getRowStride();
    int vRowStride = image.getPlanes()[2].getRowStride();
    int yPixelStride = image.getPlanes()[0].getPixelStride();
    int vPixelStride = image.getPlanes()[2].getPixelStride();

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

    nv21 = new byte[ySize + uSize + vSize];

    int nv21Index = 0;
    int yIndex = 0;
    int uIndex = 0;
    int vIndex = 0;

    for (int i = 0; i < image.getHeight(); i++) {
        int yOffset = i * yRowStride;
        int uvOffset = (i >> 1) * vRowStride;
        for (int j = 0; j < image.getWidth(); j++) {
            int uvIndex = uvOffset + ((j >> 1) << 1) * vPixelStride;
            nv21[nv21Index++] = yBuffer.get(yIndex + yOffset + j * yPixelStride);
            nv21[nv21Index++] = vBuffer.get(uvIndex + vIndex);
            nv21[nv21Index++] = uBuffer.get(uvIndex + uIndex);
        }
    }

    return nv21;
}

在此示例中,我们使用yOffset和uvOffset变量来计算正确的索引。我们还使用位移运算符来计算uvIndex,而不是使用乘法和除法运算符。这可以提高循环速度并减少计算时间。

richyeohx-ax4MO

还可以优化一下么。

Avatar
Avatar

是的,您可以进一步优化代码并提高转换速度。以下是更快的代码示例:

public static byte[] convertYUV420888ToNV21(Image image) {
    byte[] nv21;
    ByteBuffer yBuffer = image.getPlanes()[0].getBuffer();
    ByteBuffer uBuffer = image.getPlanes()[1].getBuffer();
    ByteBuffer vBuffer = image.getPlanes()[2].getBuffer();
    int yRowStride = image.getPlanes()[0].getRowStride();
    int uRowStride = image.getPlanes()[1].getRowStride();
    int vRowStride = image.getPlanes()[2].getRowStride();
    int yPixelStride = image.getPlanes()[0].getPixelStride();
    int vPixelStride = image.getPlanes()[2].getPixelStride();

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

    nv21 = new byte[ySize + uSize + vSize];

    int nv21Index = 0;
    int yIndex = 0;
    int uIndex = 0;
    int vIndex = 0;

    int w = image.getWidth();
    int h = image.getHeight();
    int halfH = h / 2;
    int halfW = w / 2;
    int uvRowStride = vRowStride;
    int uvPixelStride = vPixelStride;

    if (uRowStride != vRowStride || uPixelStride != vPixelStride) {
        // if the resolution is odd, the last pixel column of the U/V plane is dropped
        uvRowStride = (w % 2 == 0 ? uRowStride : uRowStride - 1);
        uvPixelStride = (w % 2 == 0 ? uPixelStride : vPixelStride * 2);
    }

    byte[] yRow = new byte[yRowStride];
    byte[] uvRow = new byte[uvRowStride];
    for (int i = 0; i < h; i++) {
        yBuffer.position(yIndex + i * yRowStride);
        yBuffer.get(yRow, 0, Math.min(w, yRowStride));
        int uvRowIndex = uvRowStride * (i >> 1);
        uBuffer.position(uIndex + uvRowIndex);
        vBuffer.position(vIndex + uvRowIndex);
        if ((i & 1) == 0) {
            uBuffer.get(uvRow, 0, uvRowStride);
            vBuffer.get(uvRow, 1, uvRowStride);
        }
        for (int j = 0; j < w; j++) {
            int uvIndex = uvRowIndex + ((j >> 1) << 1) * uvPixelStride;
            nv21[nv21Index++] = yRow[j * yPixelStride];
            nv21[nv21Index++] = uvRow[uvIndex + vPixelStride];
            nv21[nv21Index++] = uvRow[uvIndex + uPixelStride];
        }
    }

    return nv21;
}

在此示例中,我们使用临时数组来存储每行的数据,然后使用get()方法一次性获取所有数据。我们还使用位移运算符来计算uvRowIndex和uvIndex,而不是使用乘法和除法运算符。这可以提高循环速度并减少计算时间。此外,我们还考虑了奇数分辨率的情况,以确保正确的转换结果。

richyeohx-ax4MO

这样子的话内存是不是就占用多了。

Avatar
Avatar

是的,使用临时数组会占用更多的内存。如果内存使用是一个问题,您可以使用以下代码示例,它不使用临时数组,并且直接从缓冲区复制数据到目标数组中:

public static byte[] convertYUV420888ToNV21(Image image) {
    byte[] nv21;
    ByteBuffer yBuffer = image.getPlanes()[0].getBuffer();
    ByteBuffer uBuffer = image.getPlanes()[1].getBuffer();
    ByteBuffer vBuffer = image.getPlanes()[2].getBuffer();
    int yRowStride = image.getPlanes()[0].getRowStride();
    int uRowStride = image.getPlanes()[1].getRowStride();
    int vRowStride = image.getPlanes()[2].getRowStride();
    int yPixelStride = image.getPlanes()[0].getPixelStride();
    int vPixelStride = image.getPlanes()[2].getPixelStride();

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

    nv21 = new byte[ySize + uSize + vSize];

    int nv21Index = 0;
    int yIndex = 0;
    int uIndex = 0;
    int vIndex = 0;

    int w = image.getWidth();
    int h = image.getHeight();
    int halfH = h / 2;
    int halfW = w / 2;
    int uvRowStride = vRowStride;
    int uvPixelStride = vPixelStride;

    if (uRowStride != vRowStride || uPixelStride != vPixelStride) {
        // if the resolution is odd, the last pixel column of the U/V plane is dropped
        uvRowStride = (w % 2 == 0 ? uRowStride : uRowStride - 1);
        uvPixelStride = (w % 2 == 0 ? uPixelStride : vPixelStride * 2);
    }

    for (int i = 0; i < h; i++) {
        int yOffset = i * yRowStride;
        if (i % 2 == 0) {
            int uvOffset = (i >> 1) * uvRowStride;
            for (int j = 0; j < w; j += 2) {
                int uvIndex = uvOffset + j * uvPixelStride;
                nv21[nv21Index++] = yBuffer.get(yIndex + yOffset + j * yPixelStride);
                nv21[nv21Index++] = vBuffer.get(vIndex + uvIndex + vPixelStride);
                nv21[nv21Index++] = uBuffer.get(uIndex + uvIndex + uPixelStride);
                nv21[nv21Index++] = yBuffer.get(yIndex + yOffset + (j + 1) * yPixelStride);
            }
        } else {
            for (int j = 0; j < w; j++) {
                nv21[nv21Index++] = yBuffer.get(yIndex + yOffset + j * yPixelStride);
            }
        }
    }

    return nv21;
}

在此示例中,我们直接从缓冲区复制数据到目标数组中。对于偶数行,我们交错地复制V和U分量,而对于奇数行,我们只复制Y分量。这种方法不需要使用临时数组,因此内存使用更少。

richyeohx-ax4MO

可是每次返回的新的ByteArray也挺占用内存的啊。

Avatar
Avatar

是的,每次返回新的字节数组确实会占用更多的内存。如果内存使用是一个问题,您可以将返回类型更改为ByteBuffer,并使用一个重用的ByteBuffer来存储转换后的数据。

以下是使用重用的ByteBuffer进行转换的示例代码:

public static ByteBuffer convertYUV420888ToNV21(Image image, ByteBuffer nv21Buffer) {
    ByteBuffer yBuffer = image.getPlanes()[0].getBuffer();
    ByteBuffer uBuffer = image.getPlanes()[1].getBuffer();
    ByteBuffer vBuffer = image.getPlanes()[2].getBuffer();
    int yRowStride = image.getPlanes()[0].getRowStride();
    int uRowStride = image.getPlanes()[1].getRowStride();
    int vRowStride = image.getPlanes()[2].getRowStride();
    int yPixelStride = image.getPlanes()[0].getPixelStride();
    int vPixelStride = image.getPlanes()[2].getPixelStride();

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

    int w = image.getWidth();
    int h = image.getHeight();
    int halfH = h / 2;
    int halfW = w / 2;
    int uvRowStride = vRowStride;
    int uvPixelStride = vPixelStride;

    if (uRowStride != vRowStride || uPixelStride != vPixelStride) {
        // if the resolution is odd, the last pixel column of the U/V plane is dropped
        uvRowStride = (w % 2 == 0 ? uRowStride : uRowStride - 1);
        uvPixelStride = (w % 2 == 0 ? uPixelStride : vPixelStride * 2);
    }

    nv21Buffer.rewind();

    for (int i = 0; i < h; i++) {
        int yOffset = i * yRowStride;
        if (i % 2 == 0) {
            int uvOffset = (i >> 1) * uvRowStride;
            for (int j = 0; j < w; j += 2) {
                int uvIndex = uvOffset + j * uvPixelStride;
                nv21Buffer.put(yBuffer.get(yOffset + j * yPixelStride));
                nv21Buffer.put(vBuffer.get(uvIndex + vPixelStride));
                nv21Buffer.put(uBuffer.get(uvIndex + uPixelStride));
                nv21Buffer.put(yBuffer.get(yOffset + (j + 1) * yPixelStride));
            }
        } else {
            for (int j = 0; j < w; j++) {
                nv21Buffer.put(yBuffer.get(yOffset + j * yPixelStride));
            }
        }
    }

    nv21Buffer.rewind();
    return nv21Buffer;
}

在此示例中,我们使用传递给方法的ByteBuffer来存储转换后的数据。我们使用rewind()方法将ByteBuffer的位置设置为0,以便从头开始写入数据。然后,我们使用put()方法将数据写入ByteBuffer。最后,我们将位置设置为0,并返回ByteBuffer。

在此方法中,您需要传递一个ByteBuffer作为参数,而不是返回一个字节数组。因此,您需要在调用此方法之前创建一个ByteBuffer,并在每次调用此方法时重用它。

richyeohx-ax4MO

不行,我不喜欢,在每次调用的时候都要重置他。

Avatar