1

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

为此,我最初使用addCallbackBuffer()insurfaceChanged()方法添加 60 个缓冲区SurfaceView,然后在每次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;
}

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

4

1 回答 1

1

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

无论如何,预先分配 60 个缓冲区是错误的。如果您捕获的预览帧超过一秒,则必须实时处理和回收这些帧。所以,3个缓冲区就足够了:一个由你的程序处理,一个是空闲的,可以随时被相机锁定,一个被相机锁定并接收当前帧。

于 2013-11-12T08:25:22.973 回答