在Android中显示YUV图像

29

我的应用程序需要在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数据创建Image(Bitmap图像)?

3--我相信我总是在创建图像,但我应该只创建位图一次,并始终进行/提供新的缓冲区,每当我们收到时。
请分享您的意见。


bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(bits)); 这段代码是将 YUV 转换为 RGB 吗?还是你是在本地进行转换? - weston
这里有一种方法可以实现。请查看https://dev59.com/tmox5IYBdhLWcg3weUCe - bob
5个回答

46

以下代码可以解决您的问题,因为Android-SDK提供了YuvImage类,所以在处理Yuv格式数据时可能需要更少的时间。

您可以尝试使用以下代码:

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);
    }
}
}

2
我尝试了你的decodeYUV420SP()方法,但是它创建的图像不好。它充满了黄色和绿色的波浪。我尝试了这个方法,它有效:https://dev59.com/XG435IYBdhLWcg3wrSPN#12702836 - Derzu
1
我不明白为什么rgba [yp] = ...要向左移动8位。注释掉的那行更正确。我得到了一张旋转的图像。 - over_optimistic
7
有没有一种方法可以使用无损压缩来完成这个任务(即不使用JPEG)? - Archimedes Trajano
请看我的问题:http://stackoverflow.com/questions/29645950/how-can-i-add-thermal-effect-to-yuv-image 和 http://stackoverflow.com/questions/29649137/how-to-modify-rgb-pixel-of-an-bitmap-to-look-different - Zar E Ahmer
2
YuvImage对于此目的来说相当无用,每帧转换为JPG再转回RGB对于视频播放来说太慢了,更不用说JPG是有损格式,所以视频看起来会比原始画面更糟糕。 - Pointer Null
不支持YUV420P。 - Johann

2
另一种方法是使用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
}

1
在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);

实际上,这回答了一个不同的问题(https://stackoverflow.com/questions/14936829/create-grayscale-bitmap-image-from-byte-in-android)。 - Alex Cohn

1

基于 @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;
}

0

基于被接受的答案,我找到了一种更快的方法使用RenderScript内部转换方式将YUV转为RGB。我在这里找到了一个直接的例子:Yuv2RgbRenderScript

它可以像在RenderScriptHelper类中复制convertYuvToRgbIntrinsic方法来替换Hitesh Patel在他的答案中提供的decodeYUV420SP方法一样简单。此外,您需要初始化一个RenderScript对象(示例代码位于MainActivity类中)。

别忘了在项目gradle中添加对渲染脚本的使用(在android页面中,您可以找到如何执行此操作的方法)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接