一次在多个YuvImage上使用compresstojpeg时内存不足 [英] Out of Memory when using compresstojpeg on multiple YuvImage one at a time

查看:380
本文介绍了一次在多个YuvImage上使用compresstojpeg时内存不足的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在构建一个缓冲 N 个相机帧的应用程序,当用户点击一个按钮时,它将使用所有已保存的应用效果的帧来保存照片.

I am building an app that buffers N camera frames and when the user taps a button it saves the photo using all the saved frames applying an effect.

我正在保存照片并在 AsyncTask 上处理相框.执行该命令时,我将从屏幕上删除所有内容,仅保留 TextView 来显示保存照片的进度.

I am saving the photo and processing the frames on an AsyncTask. When I execute it, I remove everything from the screen and leave only a TextView to display the progress of saving the photo.

当前 AsyncTask doInBackground看起来像这样:

protected Void doInBackground(Integer... params) 
{
    int w = mBuffer.get(0).getWidth();
    int h = mBuffer.get(0).getHeight();
    int lineHeight = h / mBuffer.size();
    int currentHeight = 0;

    Log.d("output", "saving photo "+w+", "+h);

    for (int i = 0; i < mBuffer.size(); i++)
    {
        YuvImage image = mBuffer.get(i);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        if (!image.compressToJpeg(new Rect(0, currentHeight, w, lineHeight), 50, out))
        {
            Log.d("output", "problem converting yuv to jpg");
            break;
        }

        currentHeight += lineHeight;

        Log.d("output", "currentHeight: "+currentHeight);

        publishProgress((int)((i / (float)mBuffer.size()) * 100));
        try
        {
            out.flush();
            out.close();
            out = null;
        }
        catch(Exception e)
        {

        }

        System.gc();

        try
        {
            Thread.sleep(200);
        }
        catch(Exception e)
        {

        }

        if (isCancelled())
        {
            break;
        }
    }
    return null;
}

对于该问题,我已经删除了许多不太重要的代码部分,而这只是引起 OutOfMemory 问题的部分.基本上,我在这里所做的就是从图像中提取一行并将其压缩为JPEG.但是它只能压缩第一张图像.转到第二个时,将引发 OutOfMemory 异常.

I've already removed a lot of not-so-important parts of code for this issue and this is only the part that is raising OutOfMemory issue. Basically what I'm doing there is getting a line from the image and compressing it to JPEG. But it only manages to compress the first image. When it goes to the second one, it raises an OutOfMemory exception.

System.GC()Thread.sleep()out.flush()out.close()均未成功解决问题.

The System.GC(), Thread.sleep(), out.flush(), out.close(), are all unsuccessful attemps to fix the problem.

mBuffer的当前大小为5,最初为32.通过DDMS Heap调试,其Heap Size为30MB,正在分配9MB.显然还有很大的增长空间.如果我删除compressToJpeg(),则 AsyncTask 完成就可以了.

The currently size of mBuffer is 5, initially it was 32. From the DDMS Heap debug, its Heap Size is 30MB and it is allocating 9MB. Apparently there is a lot of room to grow. If I remove the compressToJpeg() the AsyncTask completes just fine.

有人对此问题有解决方案吗?我应该尝试自己的YUV-> JPEG转换器吗?

Does anyone have a solution for this problem? Should I try my own YUV -> JPEG converter?

使用最新代码进行

protected Void doInBackground(Integer... params) 
{
    int lineHeight = mHeight / mBufferSize;
    int currentHeight = 0;

    Log.d("output", "saving photo: "+mWidth+", "+mHeight);
    File outputPath = new File(Environment.getExternalStorageDirectory().getPath() + "/camerafluid-output");
    outputPath.mkdirs();
    for (int i = 0; i < mBufferSize; i++)
    {
        File imageFile = new File(mCachePath, "image-"+0);
        File outputFile = new File(outputPath, "image-"+0);

        if (!imageFile.exists())
        {
            Log.d("output", "image "+i+" not found on cache directory");
            continue;
        }

        try
        {
            int size = (int)imageFile.length();
            byte[] imageBytes = new byte[size];
            BufferedInputStream buf = new BufferedInputStream(new FileInputStream(imageFile));
            buf.read(imageBytes, 0, size);
            buf.close();

            YuvImage image = new YuvImage(imageBytes, ImageFormat.NV21, mWidth, mHeight, null);
            imageBytes = null;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            if (!image.compressToJpeg(new Rect(0, currentHeight, mWidth, lineHeight), 50, out))
            {
                Log.d("output", "problem converting yuv to jpg");
                break;
            }

            FileOutputStream s = new FileOutputStream(outputFile);
            s.write(out.toByteArray());
            s.flush();
            s.close();
            s = null;

            currentHeight += lineHeight;
            Log.d("output", "currentHeight: "+currentHeight);

            publishProgress((int)((i / (float)mBufferSize) * 100));
            System.gc();
        }
        catch(Exception e)
        {

        }

        if (isCancelled())
        {
            break;
        }
    }
    return null;
}

我正确保存了图像,因为在缓存目录中可以看到已保存的文件,这是活动"部分,用于保存缓冲区并执行AsyncTask:

I am saving the images correctly as I can see the saved files on the cache directory, here is the Activity part which saves the buffer and executes the AsyncTask:

public void takePhoto()
{
    Log.d("output", "taking photo");

    //vfranchi - save the buffer on the disk to free memory
    File cachePath = new File(getExternalCacheDir().getPath() + "/buffer");
    if (!cachePath.exists())
    {
        Log.d("output", "cache path doesnt exist");
        cachePath.mkdirs();
    }

    int count = 0;
    int width = mPhotoBuffer.get(0).getWidth();
    int height = mPhotoBuffer.get(0).getHeight();
    int bufferSize = mPhotoBuffer.size();
    for(YuvImage i : mPhotoBuffer)
    {
        File f = new File(cachePath, "image-"+count);
        try
        {
            FileOutputStream s = new FileOutputStream(f);
            s.write(i.getYuvData());
            s.flush();
            s.close();
            Log.d("output", "saved image "+count);
            count++;
        }
        catch(Exception e)
        {
            Log.d("output", "unable to save image "+count+"\n"+e.getLocalizedMessage());
        }
    }

    mPhotoBuffer = null;
    System.gc();

    SavePhotoTask task = new SavePhotoTask(this, cachePath, width, height, bufferSize);
    task.execute(0);
}

Logcat在拍照方法中乞讨:

Logcat beggining at the taking photo method:

03-30 12:48:08.534: D/output(15359): taking photo
03-30 12:48:09.011: V/Camera-JNI(15359): setHasPreviewCallback: installed:0, manualBuffer:0
03-30 12:48:09.011: V/Camera-JNI(15359): get_native_camera: context=0x138520, camera=0x1eea78
03-30 12:48:09.011: V/Camera-JNI(15359): Clearing callback buffers, 0 remained
03-30 12:48:09.011: V/Camera-JNI(15359): stopPreview
03-30 12:48:09.011: V/Camera-JNI(15359): get_native_camera: context=0x138520, camera=0x1eea78
03-30 12:48:15.323: D/output(15359): saved image 0
03-30 12:48:16.628: D/output(15359): saved image 1
03-30 12:48:17.880: D/output(15359): saved image 2
03-30 12:48:19.073: D/output(15359): saved image 3
03-30 12:48:20.545: D/output(15359): saved image 4
03-30 12:48:28.089: D/output(15359): saving photo: 848, 480
03-30 12:48:33.276: D/skia(15359): onFlyCompress
03-30 12:48:35.020: D/output(15359): currentHeight: 96
03-30 12:48:44.144: D/output(15359): currentHeight: 192
03-30 12:48:52.581: D/skia(15359): onFlyCompress
03-30 12:48:52.878: I/dalvikvm-heap(15359): Grow heap (frag case) to 14.975MB for 2095120-byte allocation
03-30 12:48:53.112: I/dalvikvm-heap(15359): Grow heap (frag case) to 17.975MB for 4192272-byte allocation
03-30 12:48:53.566: I/dalvikvm-heap(15359): Grow heap (frag case) to 23.975MB for 8386576-byte allocation
03-30 12:48:54.855: I/dalvikvm-heap(15359): Grow heap (frag case) to 35.975MB for 16775184-byte allocation
03-30 12:48:56.620: I/dalvikvm-heap(15359): Forcing collection of SoftReferences for 33552400-byte allocation
03-30 12:48:56.644: E/dalvikvm-heap(15359): Out of memory on a 33552400-byte allocation.
03-30 12:48:56.644: I/dalvikvm(15359): "AsyncTask #1" prio=5 tid=12 RUNNABLE
03-30 12:48:56.644: I/dalvikvm(15359):   | group="main" sCount=0 dsCount=0 obj=0x40d71628 self=0x22eea8
03-30 12:48:56.644: I/dalvikvm(15359):   | sysTid=15404 nice=10 sched=0/0 cgrp=bg_non_interactive handle=2056240
03-30 12:48:56.651: I/dalvikvm(15359):   | schedstat=( 3387725830 799926751 876 ) utm=327 stm=10 core=0
03-30 12:48:56.651: I/dalvikvm(15359):   at java.io.ByteArrayOutputStream.expand(ByteArrayOutputStream.java:~91)
03-30 12:48:56.651: I/dalvikvm(15359):   at java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:201)
03-30 12:48:56.651: I/dalvikvm(15359):   at android.graphics.YuvImage.nativeCompressToJpeg(Native Method)
03-30 12:48:56.667: I/dalvikvm(15359):   at android.graphics.YuvImage.compressToJpeg(YuvImage.java:141)
03-30 12:48:56.667: I/dalvikvm(15359):   at com.vfranchi.camerafluid.SavePhotoTask.doInBackground(SavePhotoTask.java:78)
03-30 12:48:56.667: I/dalvikvm(15359):   at com.vfranchi.camerafluid.SavePhotoTask.doInBackground(SavePhotoTask.java:1)
03-30 12:48:56.667: I/dalvikvm(15359):   at android.os.AsyncTask$2.call(AsyncTask.java:287)
03-30 12:48:56.667: I/dalvikvm(15359):   at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
03-30 12:48:56.667: I/dalvikvm(15359):   at java.util.concurrent.FutureTask.run(FutureTask.java:137)
03-30 12:48:56.667: I/dalvikvm(15359):   at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:230)
03-30 12:48:56.667: I/dalvikvm(15359):   at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076)
03-30 12:48:56.667: I/dalvikvm(15359):   at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569)
03-30 12:48:56.667: I/dalvikvm(15359):   at java.lang.Thread.run(Thread.java:856)
03-30 12:48:56.683: W/System.err(15359): java.lang.OutOfMemoryError
03-30 12:48:56.683: W/System.err(15359):    at java.io.ByteArrayOutputStream.expand(ByteArrayOutputStream.java:91)
03-30 12:48:56.683: W/System.err(15359):    at java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:201)
03-30 12:48:56.691: W/System.err(15359):    at android.graphics.YuvImage.nativeCompressToJpeg(Native Method)
03-30 12:48:56.691: W/System.err(15359):    at android.graphics.YuvImage.compressToJpeg(YuvImage.java:141)
03-30 12:48:56.691: W/System.err(15359):    at com.vfranchi.camerafluid.SavePhotoTask.doInBackground(SavePhotoTask.java:78)
03-30 12:48:56.691: W/System.err(15359):    at com.vfranchi.camerafluid.SavePhotoTask.doInBackground(SavePhotoTask.java:1)
03-30 12:48:56.691: W/System.err(15359):    at android.os.AsyncTask$2.call(AsyncTask.java:287)
03-30 12:48:56.698: W/System.err(15359):    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
03-30 12:48:56.698: W/System.err(15359):    at java.util.concurrent.FutureTask.run(FutureTask.java:137)
03-30 12:48:56.698: W/System.err(15359):    at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:230)
03-30 12:48:56.698: W/System.err(15359):    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076)
03-30 12:48:56.698: W/System.err(15359):    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569)
03-30 12:48:56.706: W/System.err(15359):    at java.lang.Thread.run(Thread.java:856)
03-30 12:48:56.706: D/skia(15359): ------- write threw an exception
03-30 12:49:15.181: W/jdwp(15359): Debugger is telling the VM to exit with code=1
03-30 12:49:15.181: I/dalvikvm(15359): GC lifetime allocation: 5 bytes

YuvImage缓冲区是在onPreviewFrame上创建的,如下所示:

The YuvImage buffer is created on the onPreviewFrame which is shown below:

public void onPreviewFrame(byte[] data, Camera camera) 
{
    Camera.Parameters parameters = camera.getParameters();
    Camera.Size size = parameters.getPreviewSize();

    // Generate a YuvImage from the camera data
    int w = size.width;
    int h = size.height;
    YuvImage photoImage = new YuvImage(data, parameters.getPreviewFormat(), w, h, null);
    mPhotoBuffer.add(0, photoImage);

    if (mPhotoBuffer.size() > mBufferSize)
    {
        mPhotoBuffer.remove(mPhotoBuffer.size() - 1);
    }
}

推荐答案

我设法找到解决问题的方法,这是通过不使用YuvImage类.我想compressToJpeg()方法有问题,至少在我不知道的设备上是这样.

I managed to find a solution for my problem, and it was by not using YuvImage class. I guess something is wrong with the compressToJpeg() method, at least on my device I don't know.

这是我的 doInBackground 方法的最终代码:

Here is the final code for my doInBackground method:

protected Void doInBackground(Integer... params) 
{
    int lineHeight = mHeight / mBufferSize;
    int currentHeight = 0;

    Bitmap result = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
    Canvas c = new Canvas(result);

    Log.d("output", "saving photo: "+mWidth+", "+mHeight);
    File outputPath = new File(Environment.getExternalStorageDirectory().getPath() + "/camerafluid-output");
    outputPath.mkdirs();
    for (int i = 0; i < mBufferSize; i++)
    {
        File imageFile = new File(mCachePath, "image-"+i);
        File outputFile = new File(outputPath, "image-"+i+".jpg");

        if (!imageFile.exists())
        {
            Log.d("output", "image "+i+" not found on cache directory");
            continue;
        }

        try
        {
            int size = (int)imageFile.length();
            byte[] imageBytes = new byte[size];
            BufferedInputStream buf = new BufferedInputStream(new FileInputStream(imageFile));
            buf.read(imageBytes, 0, size);
            buf.close();

            int[] imagePixels = MainActivity.convertYUV420_NV21toRGB8888(imageBytes, mWidth, mHeight);
            imageBytes = null;
            Bitmap bitmapImage = Bitmap.createBitmap(imagePixels, mWidth, mHeight, Bitmap.Config.ARGB_8888);
            imagePixels = null;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            if (!bitmapImage.compress(CompressFormat.JPEG, 50, out))
            {
                Log.d("output", "problem converting yuv to jpg");
                break;
            }

            FileOutputStream s = new FileOutputStream(outputFile);
            s.write(out.toByteArray());
            s.flush();
            s.close();
            s = null;

            currentHeight += lineHeight;
            Log.d("output", "currentHeight: "+currentHeight);

            publishProgress((int)((i / (float)mBufferSize) * 100));
            System.gc();
        }
        catch(Exception e)
        {

        }

        if (isCancelled())
        {
            break;
        }

    }

    return null;
}

该方法是将Yuv数据转换为RGB并创建一个位图,以便我可以压缩为JPEG并保存到外部存储中.这仅用于测试目的,因此,没有Signal错误11或OutOfMemory异常.我已经尝试使用40张图像的缓冲区.

The method is converting the Yuv data to RGB and creating a bitmap so I can compress to JPEG and save to the external storage. This was only for testing purposes, and this way, there is no Signal error 11 or an OutOfMemory exception. I already tried with a buffer of 40 images.

在这里找到YUV-> RGB转换器

The YUV -> RGB converter was found here Displaying YUV Image in Android. And I'm using minhaz idea to cache the buffer on disk before processing the images to avoid high memory usage.

这篇关于一次在多个YuvImage上使用compresstojpeg时内存不足的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆