在onPreviewFrame()中重新添加回调缓冲区时相机预览速度变慢

时间:2013-09-19 22:29:45

标签: android android-camera surfaceview

我正在尝试从相机预览中捕捉帧,这样我就可以在用户看到相机预览时对背景中的帧进行一些图像处理。

为此,我最初在addCallbackBuffer()的{​​{1}}方法surfaceChanged()中使用SurfaceView添加了60个缓冲区,然后在每个onPreviewFrame()调用中添加,我正在重新添加使用过的缓冲区。

问题是在onPreviewFrame()中重新添​​加缓冲区会减慢预览速度。

我也在计算每秒拨打onPreviewFrame()的次数。在第一秒中,我收到了超过70次onPreviewFrame()的呼叫,在第二秒及之后减少到少于25次。

这是代码

public class MySurfaceView extends SurfaceView implements
            SurfaceHolder.Callback, Camera.PreviewCallback {

private static final int BUFFER_COUNT = 60;

private SurfaceHolder mHolder;
private Camera mCamera;
private boolean isPreviewRunning;

private final FPSCounter fpscounter = new FPSCounter();

private int frameWidth, frameHeight;

private byte[] prevFrameByteArr, currFrameByteArr;

public MySurfaceView(Context context, AttributeSet attrs) {
    super(context, attrs);
    mHolder = getHolder();
    mHolder.addCallback(this);
}

public byte[] getPrevFrameByteArray() {
    return prevFrameByteArr;
}

public byte[] getCurrFrameByteArray() {
    return currFrameByteArr;
}

public int getFrameRate() {
    return fpscounter.getLastFrameCount();
}

public int getFrameWidth() {
    return frameWidth;
}

public int getFrameHeight() {
    return frameHeight;
}

@Override
public void onPreviewFrame(byte[] data, Camera camera) {
    synchronized (this) {
        prevFrameByteArr = currFrameByteArr;
        currFrameByteArr = data;
    }
    mCamera.addCallbackBuffer(data);
    fpscounter.logFrame();
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
        int height) {
    synchronized (this) {
        if (isPreviewRunning)
             mCamera.stopPreview();

        Camera.Parameters parameters = mCamera.getParameters();
        parameters.setRecordingHint(true);
        parameters.setPreviewFormat(ImageFormat.NV21);

        /* To get better frame rate, get the least resolution that matches the current aspect ratio */
        List<Size> sizes = parameters.getSupportedPreviewSizes();
        Size currPreviewSize = parameters.getPreviewSize();
        float ar = (float) (Math.floor(((float) currPreviewSize.width / currPreviewSize.height) * 10) / 10);
        for (Size s : sizes) {
            int w = s.width, h = s.height;
            float resAr = (float) (Math.floor(((float) w / h) * 10) / 10);
            if (ar == resAr) {
                this.frameWidth = w;
                this.frameHeight = h;
                parameters.setPreviewSize(w, h);
                currPreviewSize = s;
                for (int i = 0; i < BUFFER_COUNT; i++) {
                    byte[] buffer = new byte[w * h *
                        ImageFormat.getBitsPerPixel(ImageFormat.NV21) / 8];
                    mCamera.addCallbackBuffer(buffer);
                }
                break;
            }
        }

        mCamera.setParameters(parameters);

        try {
            mCamera.setPreviewDisplay(holder);
            mCamera.setPreviewCallbackWithBuffer(this);
            mCamera.startPreview();
            isPreviewRunning = true;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

@Override
public void surfaceCreated(SurfaceHolder holder) {
    synchronized (this) {
        setWillNotDraw(true);
        mCamera = Camera.open();
    }
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    synchronized (this) {
        try {
            if (mCamera != null) {
                mCamera.setPreviewCallback(null);
                mCamera.stopPreview();
                mCamera.release();
                isPreviewRunning = false;
            }
        } catch (Exception e) {
            Log.e("cam error", e.getMessage());
        }
    }
}
}

FPSCounter

private long startTime; 
private int frames, lastFrameCount;

public void logFrame() {
    frames++;
    if (System.nanoTime() - startTime >= 1000000000) {
        lastFrameCount = frames;
        frames = 0;
        startTime = System.nanoTime();
    }
}

public int getLastFrameCount() {
    return lastFrameCount;
}

有人知道如何解决这个问题吗?

1 个答案:

答案 0 :(得分:1)

我还没有看到Android设备可靠地提供超过30 FPS。但是一个可能导致减速的警告是onPreviewFrame()到达主(UI)线程时,因此与触摸,布局甚至渲染等UI事件竞争时间。请参阅如何轻松地将预览回调卸载到辅助线程:https://stackoverflow.com/a/19154438/192373

无论如何,预分配60个缓冲区都有异味。如果捕获预览帧超过一秒钟,则必须实时处理和回收帧。因此,3个缓冲区应该足够了:一个是由程序处理的,一个是空闲的,可以随时被摄像机锁定,一个被相机锁定并接收当前帧。