Android конвертирует ImageReader Image в массив байтов YCbCr_420_SP (NV21), используя сценарий рендеринга?

В настоящее время я работаю с Javacv, который использует public void onPreviewFrame(byte[] data, Camera camera) .

Поскольку камера устарела, я просматриваю камеры2 и MediaProjection . Обе библиотеки используют класс ImageReader .

В настоящее время я создаю экземпляр ImageReader со следующим кодом:

ImageReader.newInstance(DISPLAY_WIDTH, DISPLAY_HEIGHT, PixelFormat.RGBA_8888, 2);

И прикрепите OnImageAvailableListener следующим образом:

  private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { mBackgroundHandler.post(new processImage(reader.acquireNextImage())); } 

Я попытался использовать формат RGBA_8888 с Javacv в соответствии с этим потоком: https://github.com/bytedeco/javacv/issues/298, но это не работает для меня.

Поэтому вместо этого я думал об использовании сценария Render для преобразования этих изображений в NV21(YUV_420_SP) (который является выходным сигналом по умолчанию для камеры в функции onPreviewFrame ), поскольку это работало для меня с помощью библиотеки camera .

Я также читал сообщения, такие как этот и этот веб-сайт, чтобы сделать преобразование, но это не сработало для меня, и я боюсь, что они будут слишком медленными. Кроме того, мои знания о C строго ограничены. В принципе, похоже, что мне нужна обратная операция https://developer.android.com/reference/android/renderscript/ScriptIntrinsicYuvToRGB.html

Итак, как вы можете перейти от Image к массиву байтов, который будет соответствовать выходному onPreviewFrame функции NV21(YUV_420_SP) т.е. NV21(YUV_420_SP) ? Предпочтительно использовать Renderscript, поскольку он быстрее.


Изменить 1:

Я пробовал использовать ImageFormat.YUV_420_888 но безрезультатно. Я продолжал получать ошибки, такие как The producer output buffer format 0x1 doesn't match the ImageReader's configured buffer format . Я переключился на PixelFormat.RGBA_8888 и обнаружил, что в PixelFormat.RGBA_8888 есть только одна плоскость. Байт-буфер этой плоскости имеет размер ширины * высота * 4 (один байт для R, G, B, A соответственно). Поэтому я попытался преобразовать это в формат NV21 .

Я изменил код из этого ответа, чтобы создать следующую функцию:

 void RGBtoNV21(byte[] yuv420sp, byte[] argb, int width, int height) { final int frameSize = width * height; int yIndex = 0; int uvIndex = frameSize; int A, R, G, B, Y, U, V; int index = 0; int rgbIndex = 0; for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { R = argb[rgbIndex++]; G = argb[rgbIndex++]; B = argb[rgbIndex++]; A = argb[rgbIndex++]; // RGB to YUV conversion according to // https://en.wikipedia.org/wiki/YUV#Y.E2.80.B2UV444_to_RGB888_conversion Y = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16; U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128; V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128; // NV21 has a plane of Y and interleaved planes of VU each sampled by a factor // of 2 meaning for every 4 Y pixels there are 1 V and 1 U. // Note the sampling is every other pixel AND every other scanline. yuv420sp[yIndex++] = (byte) ((Y < 0) ? 0 : ((Y > 255) ? 255 : Y)); if (i % 2 == 0 && index % 2 == 0) { yuv420sp[uvIndex++] = (byte) ((V < 0) ? 0 : ((V > 255) ? 255 : V)); yuv420sp[uvIndex++] = (byte) ((U < 0) ? 0 : ((U > 255) ? 255 : U)); } index++; } } } 

И вызывать его, используя:

 int mWidth = mImage.getWidth(); int mHeight = mImage.getHeight(); byte[] rgbaBytes = new byte[mWidth * mHeight * 4]; mImage.getPlanes()[0].getBuffer().get(rgbaBytes); mImage.close(); byte[] yuv = new byte[mWidth * mHeight * 3 / 2]; RGBtoNV21(yuv, rgbaBytes, mWidth, mHeight); 

Здесь mImage – объект Image созданный моим ImageReader .

Тем не менее это дает результат, подобный этому изображению:

Искаженное изображение

Который явно искажен. Кажется, что мое преобразование выключено, но я не могу понять, что именно.

Solutions Collecting From Web of "Android конвертирует ImageReader Image в массив байтов YCbCr_420_SP (NV21), используя сценарий рендеринга?"

 @TargetApi(19) public static byte[] yuvImageToByteArray(Image image) { assert(image.getFormat() == ImageFormat.YUV_420_888); int width = image.getWidth(); int height = image.getHeight(); Image.Plane[] planes = image.getPlanes(); byte[] result = new byte[width * height * 3 / 2]; int stride = planes[0].getRowStride(); if (stride == width) { planes[0].getBuffer().get(result, 0, width); } else { for (int row = 0; row < height; row++) { planes[0].getBuffer().position(row*stride); planes[0].getBuffer().get(result, row*width, width); } } stride = planes[1].getRowStride(); assert (stride == planes[2].getRowStride()); byte[] rowBytesCb = new byte[stride]; byte[] rowBytesCr = new byte[stride]; for (int row = 0; row < height/2; row++) { int rowOffset = width*height + width/2 * row; planes[1].getBuffer().position(row*stride); planes[1].getBuffer().get(rowBytesCb, 0, width/2); planes[2].getBuffer().position(row*stride); planes[2].getBuffer().get(rowBytesCr, 0, width/2); for (int col = 0; col < width/2; col++) { result[rowOffset + col*2] = rowBytesCr[col]; result[rowOffset + col*2 + 1] = rowBytesCb[col]; } } return result; }