如何在安卓系统中从相机对象获取至少每秒15帧的原始预览数据?

30
我需要从相机对象中获取原始预览数据,至少每秒15帧,但我只能在110毫秒内获取一帧,这意味着我只能获得每秒9帧。以下是我的代码简介。
Camera mCamera = Camera.open();
Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewFrameRate(30);
parameters.setPreviewFpsRange(15000,30000);
mCamera.setParameters(parameters);
mCamera.addCallbackBuffer(new byte[dataBufferSize]);
//dataBufferSize stands for the byte size for a picture frame
mCamera.addCallbackBuffer(new byte[dataBufferSize]);
mCamera.addCallbackBuffer(new byte[dataBufferSize]);
mCamera.setPreviewDisplay(videoCaptureViewHolder);
//videoCaptureViewHolder is a SurfaceHolder object
mCamera.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() {
  private long timestamp=0;
  public synchronized void onPreviewFrame(byte[] data, Camera camera) {
    Log.v("CameraTest","Time Gap = "+(System.currentTimeMillis()-timestamp));
    timestamp=System.currentTimeMillis();
    //do picture data process
    camera.addCallbackBuffer(data);
    return;
  }
}
mCamera.startPreview();

在上述简要代码中,dataBufferSize和videoCaptureViewHolder被定义并在其他语句中计算或赋值。
我运行我的代码,可以在屏幕上看到预览,并获得以下日志:
...
V/CameraTest( 5396): Time Gap = 105
V/CameraTest( 5396): Time Gap = 112
V/CameraTest( 5396): Time Gap = 113
V/CameraTest( 5396): Time Gap = 115
V/CameraTest( 5396): Time Gap = 116
V/CameraTest( 5396): Time Gap = 113
V/CameraTest( 5396): Time Gap = 115
...

这意味着每110毫秒就会调用 onPreviewFrame(byte[] data, Camera camera) 方法,因此我最多只能获得9帧每秒的预览。无论我通过 setPreviewFrameRate()setPreviewFpsRange() 设置什么样的预览帧率和预览FPS范围,日志都是相同的。

有人能帮我解决这个问题吗?我需要至少15帧每秒的Camera对象原始预览数据。谢谢。

我将我的整个代码放在下面。

CameraTest.java

package test.cameratest;

import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import android.app.Activity;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.hardware.Camera.ErrorCallback;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.Size;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.SurfaceHolder.Callback;

public class CameraTestActivity extends Activity {
    SurfaceView mVideoCaptureView;
    Camera mCamera;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        mVideoCaptureView = (SurfaceView) findViewById(R.id.video_capture_surface);
        SurfaceHolder videoCaptureViewHolder = mVideoCaptureView.getHolder();
        videoCaptureViewHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        videoCaptureViewHolder.addCallback(new Callback() {
            public void surfaceDestroyed(SurfaceHolder holder) {
            }

            public void surfaceCreated(SurfaceHolder holder) {
                startVideo();
            }

            public void surfaceChanged(SurfaceHolder holder, int format,
                    int width, int height) {
            }
        });
    }
    private void startVideo() {
        SurfaceHolder videoCaptureViewHolder = null;
        try {
            mCamera = Camera.open();
        } catch (RuntimeException e) {
            Log.e("CameraTest", "Camera Open filed");
            return;
        }
        mCamera.setErrorCallback(new ErrorCallback() {
            public void onError(int error, Camera camera) {
            }
        }); 
        Camera.Parameters parameters = mCamera.getParameters();
        parameters.setPreviewFrameRate(30);
        parameters.setPreviewFpsRange(15000,30000);
        List<int[]> supportedPreviewFps=parameters.getSupportedPreviewFpsRange();
        Iterator<int[]> supportedPreviewFpsIterator=supportedPreviewFps.iterator();
        while(supportedPreviewFpsIterator.hasNext()){
            int[] tmpRate=supportedPreviewFpsIterator.next();
            StringBuffer sb=new StringBuffer();
            sb.append("supportedPreviewRate: ");
            for(int i=tmpRate.length,j=0;j<i;j++){
                sb.append(tmpRate[j]+", ");
            }
            Log.v("CameraTest",sb.toString());
        }

        List<Size> supportedPreviewSizes=parameters.getSupportedPreviewSizes();
        Iterator<Size> supportedPreviewSizesIterator=supportedPreviewSizes.iterator();
        while(supportedPreviewSizesIterator.hasNext()){
            Size tmpSize=supportedPreviewSizesIterator.next();
            Log.v("CameraTest","supportedPreviewSize.width = "+tmpSize.width+"supportedPreviewSize.height = "+tmpSize.height);
        }

        mCamera.setParameters(parameters);
        if (null != mVideoCaptureView)
            videoCaptureViewHolder = mVideoCaptureView.getHolder();
        try {
            mCamera.setPreviewDisplay(videoCaptureViewHolder);
        } catch (Throwable t) {
        }
        Log.v("CameraTest","Camera PreviewFrameRate = "+mCamera.getParameters().getPreviewFrameRate());
        Size previewSize=mCamera.getParameters().getPreviewSize();
        int dataBufferSize=(int)(previewSize.height*previewSize.width*
                               (ImageFormat.getBitsPerPixel(mCamera.getParameters().getPreviewFormat())/8.0));
        mCamera.addCallbackBuffer(new byte[dataBufferSize]);
        mCamera.addCallbackBuffer(new byte[dataBufferSize]);
        mCamera.addCallbackBuffer(new byte[dataBufferSize]);
        mCamera.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() {
            private long timestamp=0;
            public synchronized void onPreviewFrame(byte[] data, Camera camera) {
                Log.v("CameraTest","Time Gap = "+(System.currentTimeMillis()-timestamp));
                timestamp=System.currentTimeMillis();
                try{
                    camera.addCallbackBuffer(data);
                }catch (Exception e) {
                    Log.e("CameraTest", "addCallbackBuffer error");
                    return;
                }
                return;
            }
        });
        try {
            mCamera.startPreview();
        } catch (Throwable e) {
            mCamera.release();
            mCamera = null;
            return;
        }
    }
    private void stopVideo() {
        if(null==mCamera)
            return;
        try {
            mCamera.stopPreview();
            mCamera.setPreviewDisplay(null);
            mCamera.setPreviewCallbackWithBuffer(null);
            mCamera.release();
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }
        mCamera = null;
    }
    public void finish(){
        stopVideo();
        super.finish();
    };
}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="test.cameratest"
      android:versionCode="1"
      android:versionName="1.0">
    <uses-sdk android:minSdkVersion="9" android:targetSdkVersion="10" android:maxSdkVersion="10"/>
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
    <uses-permission android:name="android.permission.READ_CONTACTS"/>
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
    <uses-permission android:name="android.permission.CALL_PHONE"/>
    <uses-permission android:name="android.permission.BOOT_COMPLETED"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_SETTINGS" />    
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".CameraTestActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

上面的源代码有一个问题:在Camera.Parameters.setPreviewFrameRate()Camera.Parameters.setPreviewFpsRange()中使用不支持的值比根本不调用这些方法更糟糕。因此,首先获取支持的FPS(或范围)列表,并选择最好的一个,而不是像上面的示例中那样硬编码为30。 - Alex Cohn
一个相关的新问题(和答案):https://dev59.com/73_aa4cB1Zd3GeqP67iG#23687111。剧透:使用一个新的[**Event**线程](http://developer.android.com/reference/android/os/HandlerThread.html)...两年半后,该解决方案仍然没有被很好地理解。 - Alex Cohn
6个回答

7

我使用相机的经验很难处理且依赖硬件。如果可以的话,尝试在其他硬件上运行它。

此外,尝试一些不同的相机设置可能会有所帮助。

顺便感谢您提供了代码示例。


7
很抱歉,您无法做到。预览帧率设置是相机应用程序的提示(在单独的进程中运行),它可以自由地接受或默默忽略它。这也与预览帧检索无关。
当您请求预览帧时,只是告诉外部应用程序您想要它。缓冲器在相机应用程序中分配,然后通过mmaped内存段传递给您的活动 - 这需要时间。
您可能会在某些设备上获得所需的性能,但不一定在您正在使用的设备上。
如果您需要定义的帧速率,则必须捕获视频,然后解析/解压缩生成的二进制流。

3
实际上,我获取了setPreviewFpsRange(int min, int max)<http:///developer.android.com/reference/android/hardware/Camera.Parameters.html#setPreviewFpsRange(int, int)>中的说明。它表示该API可以“设置最小和最大预览帧速率。这控制Camera.PreviewCallback中接收到的预览帧速率。”因此,我认为通过使用此API,我可以获得超过15帧每秒的帧速率。我的假设可行吗?顺便说一下,通过调用parameters.getSupportedPreviewFpsRange() API,我的设备支持的预览FPS范围为(1000,15000)和(5000,30000)。 - EricOops
好的,它还表示实际范围在这些值之间波动。(因此,如果您设置1000-15000,您将得到介于两者之间的东西,不是特别稳定的)我不会依靠它,因为控制相机行为需要很多黑暗巫术。 - Konstantin Pribluda
我也对这个API的效果有疑问。但是无论我设置setPreviewFpsRange(1000,1000)还是setPreviewFpsRange(20000,30000),我都得到相同的日志。所以我很困惑。这个API在我的设备上完全不起作用吗?还是这个API的工作方式与我所想的不同?或者我错过了某些因素或语句?因此,我尝试通过首先使用这个API来解决这个问题。如果我无法解决,我会尝试您的建议。非常感谢。 :) - EricOops
我只使用单个快照(OCR处理大约需要1秒)。据我所知,只能使用宣传范围 - 但我也经历过并非所有预览分辨率实际可用的情况,尽管它们被宣传(这就是我发现mmaped缓冲区用于传递帧的方式 - 得到了一个不错的segv)。我认为你将不得不坚持使用这个API - 没有其他选择。 - Konstantin Pribluda

4
这应该不是问题。我的androangelo应用程序(在市场上)每秒可达到30帧(至少我实现了速率制动来减慢它的速度)。
请仔细检查您的日志是否充满垃圾回收器语句。如果添加的缓冲区太少,则会出现这种情况。这对我来说就是诀窍。至少我增加了20个缓冲区给相机。
然后,每个帧的处理都应在单独的线程中进行。当图像在处理线程中时,回调应跳过当前帧。

1
实际上,我只是在onPreviewFrame中记录系统时间,以计算每个回调之间的时间差。它非常快速地完成。所以我认为不会跳帧。我尝试了你的建议。我添加了20、30甚至更多的缓冲区,但结果仍然相同。在日志中,有一些垃圾收集器语句,我认为这与此部分无关。 - EricOops

2

似乎能够提高预览流畅度的一个方法,尽管不一定会提高实际的FPS,是将previewFormat设置为YV12(如果支持)。这样可以减少复制的字节数,保证16字节对齐,并可能在其他方面进行了优化:

    // PREVIEW FORMATS
    List<Integer> supportedPreviewFormats = parameters.getSupportedPreviewFormats();
    Iterator<Integer> supportedPreviewFormatsIterator = supportedPreviewFormats.iterator();
    while(supportedPreviewFormatsIterator.hasNext()){
        Integer previewFormat =supportedPreviewFormatsIterator.next();
        // 16 ~ NV16 ~ YCbCr
        // 17 ~ NV21 ~ YCbCr ~ DEFAULT
        // 4  ~ RGB_565
        // 256~ JPEG
        // 20 ~ YUY2 ~ YcbCr ...
        // 842094169 ~ YV12 ~ 4:2:0 YCrCb comprised of WXH Y plane, W/2xH/2 Cr & Cb. see documentation
        Log.v("CameraTest","Supported preview format:"+previewFormat);
        if (previewFormat == ImageFormat.YV12) {
            parameters.setPreviewFormat(previewFormat);
            Log.v("CameraTest","SETTING FANCY YV12 FORMAT");                
        }
    }

http://developer.android.com/reference/android/graphics/ImageFormat.html#YV12 描述了格式。这个加上一些空余缓冲区可以得到“时间间隔”低至80...虽然还不够好,但是...更好了?(实际上我有一个在69的...但是真的,它们平均大约在90左右)。不确定日志记录会减慢多少速度?

将previewSize设置为320x240(而不是1280x720)可以将时间降至50-70毫秒范围内...所以也许这就是你需要做的?不可否认,那些数据可能会更不那么有用。

// 在Nexus4上进行了所有测试


3
就其实用价值而言,YV12 并不比 NV21 更高效,即使相机支持它,也没有方法将其转换为 Jpeg 或 Bitmap。 - Alex Cohn

2

据我所了解,Android不允许用户设置固定帧率,也不能保证您指定的fps值将被遵守,这是由于相机硬件或固件设置的帧曝光时间导致的。您观察到的帧速率可能是光照条件的函数。例如,在日光下,某些手机可能会给您提供30fps的预览速率,但在低光条件下拍摄时只有7fps。


0

我通常声明一个全局布尔变量lockCameraUse。回调函数通常看起来像这样。

  public void onPreviewFrame(byte[] data, Camera camera) {
    if (lockCameraUse) {
      camera.addCallbackBuffer(data);
      return;
    }
    lockCameraUse = true;
    // processinng data

    // done processing data
    camera.addCallbackBuffer(data);
    lockCameraUse = false;
    return;
  }

3
如果你需要一种阻止代码被执行的方法,请注意这段代码并没有实现完全可靠的锁定。 - DonSteep
这样做行不通,因为所有回调都在同一个线程上到达(除非您_为此准备了单独的looper_,否则它将是主looper,拥塞UI线程)。为了使此模式正常工作,您应该将处理卸载到单独的线程中。 - Alex Cohn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接