最近做一个安卓的视频帧处理的项目,需要在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)