Android使用RenderScript转换原始视频帧到Bitmap

最近做一个安卓的视频帧处理的项目,需要在onPreviewFrame回调中快速的把YUV格式的原始数据帧转换为Bitmap。

最开始在网上找了个使用YuvImage.compressToJpeg,然后BitmapFactory.decodeByteArray解码成Bitmap的函数,实测速度还不错。

实现函数如下:

    public static Bitmap Bytes2Bitmap(byte[] data, Camera.Size previewSize) {
        //处理data
        BitmapFactory.Options newOpts = new BitmapFactory.Options();
        newOpts.inJustDecodeBounds = true;
        YuvImage yuvimage = new YuvImage(
                data,
                ImageFormat.NV21,
                previewSize.width,
                previewSize.height,
                null);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        // 80--JPG图片的质量[0-100],100最高
        yuvimage.compressToJpeg(new Rect(0, 0, previewSize.width, previewSize.height), 80, baos);
        byte[] rawImage = baos.toByteArray();
        //将rawImage转换成bitmap
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        try {
            baos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        yuvimage = null;
        return BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length, options);
    }

 

机器是RK3288,2GB RAM。大约40~50 ms可以转换完成。就在感觉解决了问题时,发现程序大约每隔10小时,就会自动挂掉。

logcat翻了几页日志,发现几台设备全部都是因为程序内存占用过大导致被系统杀死。而程序正常占用内存不过40~50 MB而已。

日志中显示程序被杀死前,大约占用了1.8GB的内存,明显是哪里有内存泄漏。

好在Android Studio 3.0开始有了Android Profiler,用这个抓Memory的分配记录。

最终定位到,就是在使用将视频帧原始数据压缩为Jpeg然后解码为Bitmap时出现的内存泄漏:

byte[] rawImage = baos.toByteArray();

反复修改了几遍代码,并且返回后的Bitmap也有回收掉,但还是会出现内存泄漏。

只能找其它的解决方案,一开始找了个纯Java实现的解码YUV到RGB数据,然后根据RGB数据解码出Bitmap的函数。

可惜速度太慢,测试时,大约200~300 ms才能完成转换。

后来终于找到个使用RanderScript实现解码YUV数据到Bitmap的函数,测试发现速度大约20~30 ms,比用YuvImage.compressToJpeg还要快一些。

整理后,实现的代码如下:

  public static Bitmap YuvToBmp(Context context, byte[] data, int width, int height) {
    RenderScript rs;
    ScriptIntrinsicYuvToRGB yuvToRgbIntrinsic;
    byte[] outBytes = new byte[width * height * 4];

    rs = RenderScript.create(context);
    yuvToRgbIntrinsic = ScriptIntrinsicYuvToRGB.create(rs, Element.RGBA_8888(rs));

    Type.Builder yuvType = new Type.Builder(rs, Element.U8(rs))
            .setX(width).setY(height)
            .setYuvFormat(android.graphics.ImageFormat.NV21);

    Allocation in = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT);

    Type.Builder rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs)).setX(width).setY(height);

    Allocation out = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT);

    in.copyFrom(data);

    yuvToRgbIntrinsic.setInput(in);
    yuvToRgbIntrinsic.forEach(out);

    out.copyTo(outBytes);

    Bitmap bmpout = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    out.copyTo(bmpout);

    return bmpout;
  }

要编译通过上面的函数,需要在build.gradle文件的android->defaultConfig中加入2行:

renderscriptTargetApi 18
renderscriptSupportModeEnabled true

然后在相关Java文件引入:

import android.support.v8.renderscript.*;

即可。

 

更换为这种转换方式,程序跑了几天几夜都没有挂掉。

参考链接:

How to use ScriptIntrinsicYuvToRGB (converting byte[] yuv to byte[] rgba)

发表评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据