如何在 Android 中以每秒至少 15 帧的速度从 Camera 对象获取原始预览数据?
我需要从 Camera 对象获取至少每秒 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
...
这意味着 onPreviewFrame(byte[] data, Camera camera) 每 110 毫秒调用一次,所以我不能得到超过每秒 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>
I need to obtain raw preview data from Camera object at least 15 frame per second, but I can only get a frame in 110 milliseconds which means I can get only 9 frames per second. I brief my code below.
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();
In the briefed code above, dataBufferSize and videoCaptureViewHolder is defined and calculated or assigned in other statements.
I run my code, I can see preview on the screen and I get the log below:
...
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
...
This means onPreviewFrame(byte[] data, Camera camera) is called every 110 milliseconds so I can get no more than 9 frames per second. And no matter what preview frame rate I set by issue setPreviewFrameRate() and what preview Fps range I set by issue setPreviewFpsRange(), the log is the same.
Would some one give me some help on this problem? I need to obtain raw preview data from Camera object at least 15 frames per second. Thank you in advance.
I put my entire code below.
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>
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
恐怕你不能。预览帧速率设置是相机应用程序的提示(在单独的进程中运行) - 并且可以自由地接受或默默地忽略它。它也与预览帧检索无关
当您请求预览帧时,您只需说外部应用程序您想要它。它的缓冲区在相机应用程序中分配,然后通过 mmaped 内存段传递到您的活动 - 这需要时间。
您可能会在某些设备上获得所需的性能,但不一定在您正在使用的设备上获得期望的性能。
如果您需要定义的帧速率,则必须捕获视频,然后解析/解压缩生成的二进制流。
I'm affraid, you can not. Preview framerate setting is hint for camera appplication ( which runs in separate process) - and it is free to accept or silently ignore it. It is also not related with preview frame retrieval
When you request preview frame, you just say external application that you would like to have it. Buffer for it is allocated in camera application and then passed to your activity via mmaped memory segment - this takes time.
You may get desired performance on some devices, but not necessarily on one you are playing with.
If you need defined frame rate, you will have to capture video and then parse / decompress resulting binary stream.
我对相机的使用经验非常繁琐并且依赖于硬件。因此,如果可以的话,请尝试在其他硬件上运行它。
也可能值得尝试更多的相机设置。
setRecordingHint(布尔提示)。
感谢您提供代码示例顺便说一句。
Me experience with the camera stuff has been fiddly and hardware dependent. So try running it on other hardware sometime if you can.
Also might be worth trying some more camera settings.
setRecordingHint (boolean hint).
Thanks for including a code sample btw.
这应该不是问题。我的 androangelo(市场上有)应用程序的帧率高达每秒 30 帧(至少我实施了速率制动来减慢速度)。
请仔细检查您的日志是否充满了垃圾收集器语句。如果添加的缓冲区太少,就会出现这种情况。这对我来说就是诀窍。至少我加了20个!相机的缓冲区。
然后每个帧的处理应该在单独的线程中进行。当图像在线程中进行处理时,回调应跳过当前帧。
This should not be a problem. My androangelo (it's in the market) app get's up to 30 frames per second (at least I implemented a rate-brake to slow it down).
Please check carefully, whether Your log is filled with garbage-collector statements. This is the case if too few buffers are added. This had be the trick for me. At least I came up to add 20! buffers to the camera.
Then the processing of each frame should take place in a separate thread. While an image is in the thread for processing, the callback should skip the current frame.
据我了解,Android 不允许用户设置固定的帧速率,也不保证您指定的 fps 值会受到相机硬件或固件设置的帧曝光时间的影响。您观察到的帧速率可能是光照条件的函数。例如,某些手机在白天可以为您提供 30 fps 的预览速率,但在弱光条件下拍摄时只能提供 7 fps。
In my understanding, Android does not allow user to a set a fixed framerate, nor guarantee the value of fps that you specify will be respected is due to the frame exposure time which is set by the camera hardware or firmware. The frame rate you observe may be a function of lighting conditions. For example, a certain phone may give you a 30fps preview rate in a day light but only 7 fps if you are filming in a low light condition.
似乎可以增加预览流动性的一件事(如果不一定是实际 FPS)是将 PreviewFormat 设置为 YV12(如果支持)。复制的字节数更少,16 字节对齐,并且可能以其他方式进行优化:
http://developer.android.com/reference/android/graphics/ImageFormat.html#YV12 描述了该格式。这加上一些备用缓冲区使我的“时间间隙”低至 80...这仍然不是“足够好”,但是...更好吗? (实际上,我有一个 69 岁的人……但实际上,他们的平均年龄在 90 岁左右)。不确定有多少日志记录会减慢速度?
将 PreviewSize 设置为 320x240(相对于 1280x720)可以将时间降低到 50-70 毫秒范围...所以也许这就是您需要做的?诚然,这些小数据的用处可能要小得多。
// 全部在 Nexus4 上测试
One thing that seems to increase the fluidity of the preview, if not the actual FPS necessarily, is setting the previewFormat to YV12 if supported. It's fewer bytes to copy, 16-byte aligned, and possibly optimized in other ways:
http://developer.android.com/reference/android/graphics/ImageFormat.html#YV12 describes the format. This plus a few spare buffers gives me "Time Gaps" of as low as 80...which is still not "good enough", but ... better? (actually I've got one at 69...but really, they're more around 90 on average). Not sure how much logging is slowing things down?
Setting the previewSize to 320x240 (versus 1280x720) gets things down to the 50-70msec range...so maybe that's what you need to do? Admittedly, that little data may be a lot less useful.
// all tested on Nexus4
我通常声明一个全局布尔lockCameraUse。回调函数通常如下所示。
I usually declare a global boolean lockCameraUse. The the callback function usually looks like this.