Android YUV_420_888 для RGBA с ScriptIntrinsicYuvToRGB NV21 и буферами nio в JNI - PullRequest
1 голос
/ 16 февраля 2020

Я работаю с форматом изображения YUV_420_888 (с камеры) и хочу преобразовать его в RGBA на Android. Я знаю, что есть много доступных решений, но я хотел бы использовать встроенный ScriptIntrinsicYuvToRGB и сделать буферную копию в JNI / NDK.

Я знаю, что в этом формате доступны плоскости Y, U, V, и это гарантируется, что плоскости U и V будут иметь одинаковые значения pixelstride / rowstride и длину, а на некоторых устройствах U и V могут перекрываться.

Для simplicity давайте рассмотрим случай, когда pixelstride Y = 1, rowstride = ширина изображения и pixelstride UV = 2 и rowstride = ширина изображения. Пусть размер изображения будет 1920x1080

Проблема заключается в следующем:

  1. Когда я выделяю прямой буфер в Kotlin, его фактический размер на 7 байтов больше, чем запрашиваемый размер (I выяснилось, что это из-за прямого выравнивания буфера JVM)
  2. Я заполняю этот прямой буфер в NDK, заполняю входное распределение этим буфером, вызываю метод forEach в ScriptIntrinsicYuvToRGB, и он всегда создает «поврежденное» выходное изображение. Самые левые столбцы слева на самом деле являются правыми столбцами
  3. Если я заполню ByteArray в Kotlin и заполню входное распределение из него, то выходное изображение будет правильным.
// JNI code
void nFillBuffer(JNIEnv *env, jobject thiz,
                jobject dest,
                jint width, jint height,
                jobject y, jobject u, jobject v,
                jint ySize, jint uSize, jint vSize,
                jint yRowStride,
                jint uvRowStride, jint uvPixelStride)
{
    jbyte* destPtr = (jbyte*)env->GetDirectBufferAddress(dest);
    jbyte* yBuffer = (jbyte*)env->GetDirectBufferAddress(y);
    jbyte* uBuffer = (jbyte*)env->GetDirectBufferAddress(u);
    jbyte* vBuffer = (jbyte*)env->GetDirectBufferAddress(v);

    int32_t pos = 0;

    // copy Y
    if (width == yRowStride)
    {
        pos = (width * height);
        memcpy(destPtr, yBuffer, pos);
    }
    else
    {
        for (int32_t i = 0; i < height; ++i) {
            memcpy(destPtr + pos, yBuffer + (i * yRowStride), width);
            pos += width;
        }
    }

    // is UV overlapped?
    if (uvRowStride == width && uvPixelStride == 2 && uBuffer == vBuffer + 1) {

        memcpy(destPtr + pos, vBuffer, vSize);
        return;
    }
    else
    {
        for (int i=0; i < height / 2; ++i)
            for (int j=0; j < width / 2; ++j)
            {
                destPtr[pos++] = vBuffer[i*uvRowStride + j*uvPixelStride];
                destPtr[pos++] = uBuffer[i*uvRowStride + j*uvPixelStride];
            }
    }
}

В этом фрагменте кода он вызывается из Kotlin

...
...
// Renderscript init
rs = RenderScript.create(context)
yuvToRGB =  ScriptIntrinsicYuvToRGB.create(rs, Element.RGBA_8888(rs));
val yuvType: Type.Builder =
    Type.Builder(rs, Element.U8(rs))
        .setX(width)
        .setY(height)
        .setYuvFormat(ImageFormat.NV21)

inputAllocation = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT)
yuvToRGB.setInput(inputAllocation);

nioBuffer = ByteBuffer.allocateDirect(inputAllocation.bytesSize  /*width*height + ((width * height) / 2)*/)

val rgbType: Type.Builder =
    Type.Builder(rs, Element.RGBA_8888(rs))
        .setX(width)
        .setY(height)

outputAllocation = Allocation.createTyped(rs, rgbType.create(), Allocation.USAGE_SCRIPT)
...
...
// Process input image 
val y = image.planes[0].buffer!!
val u = image.planes[1].buffer!!
val v = image.planes[2].buffer!!

val ySize = y.remaining()
val uSize = u.remaining()
val vSize = v.remaining()

val yRowStride = image.planes[0].rowStride;
val uvPixelStride = image.planes[1].pixelStride
val uvRowStride = image.planes[1].rowStride

assert(1 == image.planes[0].pixelStride)             // must be 1
assert(uvRowStride == image.planes[2].rowStride)     // must be equal
assert(uvPixelStride == image.planes[2].pixelStride) // must be equal
assert(uvPixelStride == 1 || uvPixelStride == 2)     // must be 1 or 2

// Native
nFillBuffer(
        nioBuffer,
        image.width, image.height,
        y, u, v,
        ySize, uSize, vSize,
        yRowStride,
        uvRowStride, uvPixelStride)

inputAllocation.copyFrom(nioBuffer.array())

// Workaround: copy the right size buffer (image is NOT corrupted)
// val nioCopy = ByteArray(ySize + vSize)
// nioBuffer[nioCopy, 0, ySize + vSize]
// inputAllocation.copyFrom(nioCopy)
// nioBuffer.rewind();

yuvToRGB.forEach(outputAllocation);
outputAllocation.copyTo(outputBitmap)
...

Я также попытался выделить меньший прямой буфер, но не смог сгенерировать правильное выходное изображение. Есть ли способ запросить правильный размер прямого буфера или избежать обходного кода копирования?

Заранее спасибо

...