Отображение изображения YUV в Android
В моем приложении нам нужно отобразить видеокадр, полученный с сервера, на наше приложение для Android,
Сервер отправляет видеоданные со скоростью 50 кадров в секунду, закодировав их в WebM, т.е. используя libvpx для кодирования и декодирования изображений,
Теперь после декодирования из libvpx он получает YUV-данные, которые мы можем отобразить поверх макета изображения,
текущая реализация примерно такая,
В коде JNI / Native C++ мы конвертируем данные YUV в данные RGB В платформе Android, вызывая
public Bitmap createImgae(byte[] bits, int width, int height, int scan) {
Bitmap bitmap=null;
System.out.println("video: creating bitmap");
//try{
bitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(bits));
//}catch(OutOfMemoryError ex){
//}
System.out.println("video: bitmap created");
return bitmap;
}
Чтобы создать растровое изображение,
чтобы отобразить изображение поверх imageView, используя следующий код,
img = createImgae(imgRaw, imgInfo[0], imgInfo[1], 1);
if(img!=null && !img.isRecycled()){
iv.setImageBitmap(img);
//img.recycle();
img=null;
System.out.println("video: image displayed");
}
Мой запрос, в целом, эта функция занимает около 40 мс, есть ли способ ее оптимизировать,
1 - Есть ли способ отобразить данные YUV в imageView?
2 - Есть ли другой способ создать изображение (растровое изображение) из данных RGB,
3 - Я считаю, что я всегда создаю изображение, но я полагаю, что я должен создать растровое изображение только один раз и делать / предоставлять новый буфер всегда, как и когда мы получили.
Пожалуйста, поделитесь своими взглядами.
5 ответов
Следующий код решит вашу проблему, и это может занять меньше времени на данные формата Yuv, потому что класс YuvImage поставляется с Android-SDK.
Вы можете попробовать это,
ByteArrayOutputStream out = new ByteArrayOutputStream();
YuvImage yuvImage = new YuvImage(data, ImageFormat.NV21, width, height, null);
yuvImage.compressToJpeg(new Rect(0, 0, width, height), 50, out);
byte[] imageBytes = out.toByteArray();
Bitmap image = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
iv.setImageBitmap(image);
или же
void yourFunction(byte[] data, int mWidth, int mHeight)
{
int[] mIntArray = new int[mWidth*mHeight];
// Decode Yuv data to integer array
decodeYUV420SP(mIntArray, data, mWidth, mHeight);
//Initialize the bitmap, with the replaced color
Bitmap bmp = Bitmap.createBitmap(mIntArray, mWidth, mHeight, Bitmap.Config.ARGB_8888);
// Draw the bitmap with the replaced color
iv.setImageBitmap(bmp);
}
static public void decodeYUV420SP(int[] rgba, byte[] yuv420sp, int width,
int height) {
final int frameSize = width * height;
for (int j = 0, yp = 0; j < height; j++) {
int uvp = frameSize + (j >> 1) * width, u = 0, v = 0;
for (int i = 0; i < width; i++, yp++) {
int y = (0xff & ((int) yuv420sp[yp])) - 16;
if (y < 0)
y = 0;
if ((i & 1) == 0) {
v = (0xff & yuv420sp[uvp++]) - 128;
u = (0xff & yuv420sp[uvp++]) - 128;
}
int y1192 = 1192 * y;
int r = (y1192 + 1634 * v);
int g = (y1192 - 833 * v - 400 * u);
int b = (y1192 + 2066 * u);
if (r < 0)
r = 0;
else if (r > 262143)
r = 262143;
if (g < 0)
g = 0;
else if (g > 262143)
g = 262143;
if (b < 0)
b = 0;
else if (b > 262143)
b = 262143;
// rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) &
// 0xff00) | ((b >> 10) & 0xff);
// rgba, divide 2^10 ( >> 10)
rgba[yp] = ((r << 14) & 0xff000000) | ((g << 6) & 0xff0000)
| ((b >> 2) | 0xff00);
}
}
}
Другой способ - использовать ScriptIntrinsicYuvToRGB, это более эффективно, чем кодирование (и декодирование) каждый раз, когда JPEG
fun yuvByteArrayToBitmap(bytes: ByteArray, width: Int, height: Int): Bitmap {
val rs = RenderScript.create(this)
val yuvToRgbIntrinsic = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs));
val yuvType = Type.Builder(rs, Element.U8(rs)).setX(bytes.size);
val input = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT);
val rgbaType = Type.Builder(rs, Element.RGBA_8888(rs)).setX(width).setY(height);
val output = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT);
input.copyFrom(bytes);
yuvToRgbIntrinsic.setInput(input);
yuvToRgbIntrinsic.forEach(output);
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
output.copyTo(bitmap)
input.destroy()
output.destroy()
yuvToRgbIntrinsic.destroy()
rs.destroy()
return bitmap
}
Создайте растровое изображение после получения ширины и высоты в onCreate.
editedBitmap = Bitmap.createBitmap(widthPreview, heightPreview,
android.graphics.Bitmap.Config.ARGB_8888);
И в onPreviewFrame.
int[] rgbData = decodeGreyscale(aNv21Byte,widthPreview,heightPreview);
editedBitmap.setPixels(rgbData, 0, widthPreview, 0, 0, widthPreview, heightPreview);
А также
private int[] decodeGreyscale(byte[] nv21, int width, int height) {
int pixelCount = width * height;
int[] out = new int[pixelCount];
for (int i = 0; i < pixelCount; ++i) {
int luminance = nv21[i] & 0xFF;
// out[i] = Color.argb(0xFF, luminance, luminance, luminance);
out[i] = 0xff000000 | luminance <<16 | luminance <<8 | luminance;//No need to create Color object for each.
}
return out;
}
И бонус.
if(cameraId==CameraInfo.CAMERA_FACING_FRONT)
{
matrix.setRotate(270F);
}
finalBitmap = Bitmap.createBitmap(editedBitmap, 0, 0, widthPreview, heightPreview, matrix, true);
Основываясь на ответе @wiomoc, вот версия Java:
Bitmap yuvByteArrayToBitmap(byte[] bytes, int width, int height)
{
RenderScript rs = RenderScript.create(this);
ScriptIntrinsicYuvToRGB yuvToRgbIntrinsic = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs));
Type.Builder yuvType = new Type.Builder(rs, Element.U8(rs)).setX(bytes.length);
Allocation input = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT);
Type.Builder rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs)).setX(width).setY(height);
Allocation output = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT);
input.copyFrom(bytes);
yuvToRgbIntrinsic.setInput(input);
yuvToRgbIntrinsic.forEach(output);
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
output.copyTo(bitmap);
input.destroy();
output.destroy();
yuvToRgbIntrinsic.destroy();
rs.destroy();
return bitmap;
}
Основываясь на принятом ответе, я смог найти довольно быстрый способ сделать преобразование YUV в RGB, используя метод преобразования встроенного RenderScript. Я нашел здесь прямой пример: Yuv2RgbRenderScript.
Это может быть так же просто, как скопировать метод convertYuvToRgbIntrinsic в класс RenderScriptHelper, чтобы заменить decodeYUV420SP, который Хитеш Патель дает в своем ответе. Также вам нужно будет инициализировать объект RenderScript (пример находится в классе MainActivity).
И не забудьте добавить в градиент проекта использование скрипта рендеринга (на странице Android вы можете найти способ сделать это).