Я работаю с форматом изображения 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
Проблема заключается в следующем:
- Когда я выделяю прямой буфер в Kotlin, его фактический размер на 7 байтов больше, чем запрашиваемый размер (I выяснилось, что это из-за прямого выравнивания буфера JVM)
- Я заполняю этот прямой буфер в NDK, заполняю входное распределение этим буфером, вызываю метод forEach в ScriptIntrinsicYuvToRGB, и он всегда создает «поврежденное» выходное изображение. Самые левые столбцы слева на самом деле являются правыми столбцами
- Если я заполню 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)
...
Я также попытался выделить меньший прямой буфер, но не смог сгенерировать правильное выходное изображение. Есть ли способ запросить правильный размер прямого буфера или избежать обходного кода копирования?
Заранее спасибо