.setPreviewFpsRange():在相机 .setParameters() 中每秒更新帧时出现问题
.setPreviewFpsRange(): Having problems updating frames per second in camera .setParameters()
2015 年 10 月 28 日更新以反映当前进展。
我有一个应用程序,允许用户为 Motion JPEG 记录设置相机参数,创建 MJPEG 文件,然后用户可以修改这些设置并使用更新的设置创建另一个文件。当初始值不是 30 FPS 时,我在更新每秒帧数设置时遇到问题。当初始值为 30 FPS 时,我可以更新到不同的 FPS 级别并在该级别成功录制视频。但是,我无法从不等于 30FPS 的级别更新到另一个 FPM 级别。 LogCat 我崩溃了,显示
有问题
camera.setParameters(参数);
全部 LogCat 错误如下,
10-26 20:27:36.414: E/AndroidRuntime(2275): FATAL EXCEPTION: main
10-26 20:27:36.414: E/AndroidRuntime(2275): java.lang.RuntimeException: setParameters failed
10-26 20:27:36.414: E/AndroidRuntime(2275): at android.hardware.Camera.native_setParameters(Native Method)
10-26 20:27:36.414: E/AndroidRuntime(2275): at android.hardware.Camera.setParameters(Camera.java:1333)
10-26 20:27:36.414: E/AndroidRuntime(2275): at net.blepsias.riverwatch.RiverWatch.setCamera(RiverWatch.java:191)
10-26 20:27:36.414: E/AndroidRuntime(2275): at net.blepsias.riverwatch.RiverWatch.onClick(RiverWatch.java:167)
10-26 20:27:36.414: E/AndroidRuntime(2275): at android.view.View.performClick(View.java:3514)
10-26 20:27:36.414: E/AndroidRuntime(2275): at android.view.View$PerformClick.run(View.java:14111)
10-26 20:27:36.414: E/AndroidRuntime(2275): at android.os.Handler.handleCallback(Handler.java:605)
10-26 20:27:36.414: E/AndroidRuntime(2275): at android.os.Handler.dispatchMessage(Handler.java:92)
10-26 20:27:36.414: E/AndroidRuntime(2275): at android.os.Looper.loop(Looper.java:137)
10-26 20:27:36.414: E/AndroidRuntime(2275): at android.app.ActivityThread.main(ActivityThread.java:4429)
10-26 20:27:36.414: E/AndroidRuntime(2275): at java.lang.reflect.Method.invokeNative(Native Method)
10-26 20:27:36.414: E/AndroidRuntime(2275): at java.lang.reflect.Method.invoke(Method.java:511)
10-26 20:27:36.414: E/AndroidRuntime(2275): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
10-26 20:27:36.414: E/AndroidRuntime(2275): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
10-26 20:27:36.414: E/AndroidRuntime(2275): at dalvik.system.NativeStart.main(Native Method)
检查 LogCat 引用的第 5 行和第 6 行,这些对应于:
(191) camera.setParameters(parameters);
(167) setCamera(camera);
下面是应用程序。我还将包括布局 .xml 文件以供参考,以及用于接地的屏幕截图。
RiverWatch.java
public class RiverWatch extends Activity implements OnClickListener, SurfaceHolder.Callback, Camera.PreviewCallback {
public static final String LOGTAG = "VIDEOCAPTURE";
String szBoundaryStart = "\r\n\r\n--myboundary\r\nContent-Type: image/jpeg\r\nContent-Length: ";
String szBoundaryDeltaTime = "\r\nDelta-time: 110";
String szBoundaryEnd = "\r\n\r\n";
private SurfaceHolder holder;
private Camera camera;
private CamcorderProfile camcorderProfile;
Spinner spinnerCamcorderProfile;
public TextView tvFramesPerSecond, tvJpegQuality, tvSegmentDuration;
boolean bRecording = false;
boolean bPreviewRunning = false;
int intFramesPerSecond = 30000; //this is 30fps...mult by 1,000
int intJpegQuality=50; //must be above 20
int intSegmentDuration=10;
boolean ckbxRepeat=false;
byte[] previewCallbackBuffer;
File mjpegFile;
FileOutputStream fos;
BufferedOutputStream bos;
Button btnStartRecord, btnStopRecord, btnExit, btnChange;
Camera.Parameters parameters;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Date T = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
String szFileName = "videocapture-"+sdf.format(T)+"-";
try {
mjpegFile = File.createTempFile(szFileName, ".mjpeg", Environment.getExternalStorageDirectory());
} catch (Exception e) {
finish();
}
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
setContentView(R.layout.main);
tvFramesPerSecond = (TextView) this.findViewById(R.id.textboxframespersecondxml);
int iFPS = intFramesPerSecond/1000;
String szFPS = Integer.toString(iFPS);
tvFramesPerSecond.setClickable(true);
tvFramesPerSecond.setText(szFPS);
tvFramesPerSecond.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
getSupportedPreviewFpsRange();
}
});
tvJpegQuality = (TextView) this.findViewById(R.id.textboxJpegQualityxml);
String szJpegQuality = Integer.toString(intJpegQuality);
tvJpegQuality.setText(szJpegQuality);
tvSegmentDuration = (TextView) this.findViewById(R.id.textboxSegmentDurationxml);
String szSegmentDuration = Integer.toString(intSegmentDuration);
tvSegmentDuration.setText(szSegmentDuration);
btnStartRecord = (Button) this.findViewById(R.id.StartRecordButton);
btnStartRecord.setOnClickListener(this);
btnStopRecord = (Button) this.findViewById(R.id.StopRecordButton);
btnStopRecord.setOnClickListener(this);
btnExit = (Button) this.findViewById(R.id.ExitButton);
btnExit.setOnClickListener(this);
btnChange = (Button) this.findViewById(R.id.ChangeButton);
btnChange.setOnClickListener(this);
camcorderProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_TIME_LAPSE_480P);
SurfaceView cameraView = (SurfaceView) findViewById(R.id.CameraView);
holder = cameraView.getHolder();
holder.addCallback(this);
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
cameraView.setClickable(true);
cameraView.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.StartRecordButton:
try {
fos = new FileOutputStream(mjpegFile);
bos = new BufferedOutputStream(fos);
bRecording = true;
} catch (FileNotFoundException e) {
e.printStackTrace();
}
Toast.makeText(this, "Recording started.", Toast.LENGTH_SHORT).show();
break;
case R.id.StopRecordButton:
try {
bos.flush();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
Toast.makeText(this, "Recording stopped.", Toast.LENGTH_SHORT).show();
break;
case R.id.ChangeButton:
//Frames Per Second- expressed x1000 in the function
String szFPS=tvFramesPerSecond.getText().toString();
int iFPS = Integer.parseInt(szFPS);
intFramesPerSecond = iFPS *1000;
//Jpeg quality- cant be <20 or >100, checks this and populates field with entered or corrected value.
String szJpegQuality=tvJpegQuality.getText().toString();
int intJpegQualityTemp = Integer.parseInt(szJpegQuality);
if (intJpegQualityTemp < 21){//...can't be less than 21
intJpegQuality = 21;
}else if(intJpegQualityTemp > 100){//can't be greater than 100
intJpegQuality = 100;
}else{ //quality is between 21 and 100...
intJpegQuality = intJpegQualityTemp;
}
szJpegQuality = Integer.toString(intJpegQuality);
tvJpegQuality.setText(szJpegQuality);
//Segment duration
String szSegmentDuration=tvSegmentDuration.getText().toString();
intSegmentDuration = Integer.parseInt(szSegmentDuration);
releaseCamera();
setCamera(camera);
camera.startPreview();
Toast.makeText(this, "Change button pressed.", Toast.LENGTH_SHORT).show();
break;
case R.id.ExitButton:
System.exit(0);
break;
}
}
public void releaseCamera(){
camera.stopPreview();
//camera.release(); //...cause crash
//camera = null;
}
public void setCamera(Camera camera){
Camera.Parameters parameters=camera.getParameters();
parameters.setPreviewFpsRange(intFramesPerSecond, intFramesPerSecond);//note: This is fps x 1000 (!)
parameters.setPreviewSize(camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight);
Log.v(LOGTAG,"FPS: " + parameters.getSupportedPreviewFpsRange());
camera.setParameters(parameters);
}
public void getSupportedPreviewFpsRange(){
/****************************************************************
* getSupportedPreviewFpsRange()- Returns specified frame rate
* (.getSupportedPreviewFpsRange()) to log file and also displays
* as toast message.
****************************************************************/
Camera.Parameters camParameter = camera.getParameters();
List<int[]> frame = camParameter.getSupportedPreviewFpsRange();
Iterator<int[]> supportedPreviewFpsIterator = frame.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.d(LOGTAG, "FPS6: " + sb.toString());
Toast.makeText(this, "FPS = "+sb.toString(), Toast.LENGTH_SHORT).show();
}//*****************end getSupportedPreviewFpsRange()**********************
}
public void surfaceCreated(SurfaceHolder holder) {
camera = Camera.open();
}
@SuppressLint("NewApi")
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if (!bRecording) {
if (bPreviewRunning = true){
camera.stopPreview();
} try {
parameters = camera.getParameters();
parameters.setPreviewSize(camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight);
parameters.setPreviewFpsRange(intFramesPerSecond, intFramesPerSecond);//note: This is fps x 1000 (!)
//p.setPreviewFrameRate(intFramesPerSecond);
camera.setParameters(parameters);
camera.setPreviewDisplay(holder);
camera.setPreviewCallback(this);
camera.setDisplayOrientation(90);
camera.startPreview();
bPreviewRunning = true;
}
catch (IOException e) {
e.printStackTrace();
}
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
if (bRecording) {
bRecording = false;
try {
bos.flush();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
bPreviewRunning = false;
camera.release();
finish();
}
public void onPreviewFrame(byte[] b, Camera c) {
if (bRecording) {
// Assuming ImageFormat.NV21
if (parameters.getPreviewFormat() == ImageFormat.NV21) {
try {
YuvImage im = new YuvImage(b, ImageFormat.NV21, parameters.getPreviewSize().width, parameters.getPreviewSize().height, null);
Rect r = new Rect(0,0,parameters.getPreviewSize().width,parameters.getPreviewSize().height);
ByteArrayOutputStream jpegByteArrayOutputStream = new ByteArrayOutputStream();
im.compressToJpeg(r, intJpegQuality, jpegByteArrayOutputStream);//note: qual = 20 or less doesn't work.
byte[] jpegByteArray = jpegByteArrayOutputStream.toByteArray();
byte[] boundaryBytes = (szBoundaryStart + jpegByteArray.length + szBoundaryDeltaTime + szBoundaryEnd).getBytes();
bos.write(boundaryBytes);
bos.write(jpegByteArray);
bos.flush();
//bos.close();
} catch (IOException e) {
e.printStackTrace();
}
} else {
Log.v(LOGTAG,"NOT THE RIGHT FORMAT");
}
}
}
@Override
public void onConfigurationChanged(Configuration conf){
super.onConfigurationChanged(conf);
}
}
布局main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button
android:id="@+id/StartRecordButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start Recording" />
<Button
android:id="@+id/StopRecordButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Stop Recording" />
<Button
android:id="@+id/ChangeButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="50dip"
android:text="Reset settings" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right">
<TextView
style="@style/myStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Frames/second:" />
<EditText
android:id="@+id/textboxframespersecondxml"
android:editable="true"
style="@style/myStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="right"
android:text="0"
android:layout_marginRight="10dip"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right">
<TextView
style="@style/myStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="JPEG image quality:" />
<EditText
android:id="@+id/textboxJpegQualityxml"
android:editable="true"
style="@style/myStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="right"
android:text="0"
android:layout_marginRight="10dip"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
style="@style/myStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:text="Camcorder profile: " />
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right">
<TextView
style="@style/myStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Segment duration (file length):" />
<EditText
android:id="@+id/textboxSegmentDurationxml"
android:editable="true"
style="@style/myStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="right"
android:text="0"
android:layout_marginRight="10dip"/>
<TextView
style="@style/myStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=" minutes" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="right" >
<CheckBox
android:id="@+id/repeat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Repeat" />
<Button
android:id="@+id/ExitButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Exit Application" />
</LinearLayout>
<SurfaceView
android:id="@+id/CameraView"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>
屏幕截图: 显示字段、按钮和表面配置
解决方案: 看来上述大部分不稳定行为可能与主要测试设备 Panasonic Toughpad JT-B1 有关。 运行
.getSupportedPreviewFpsRange();
在此设备上 returns 范围为 8,000-30,000 fps。但是,此范围内的许多值会导致崩溃,而此范围外的某些值似乎工作正常。测试 Samsung S4 Active 导致 none 这些不一致,返回范围 (4,000 - 30,000) 中的所有值都工作正常,并且没有超出此范围的测试值显示任何预期的功能。
相机API 不允许将预览 FPS 范围设置为任意值。您应该查询相机参数以获取支持范围列表,并且不保证任何其他组合都有效。
原则上,Camera.setParameters()
使用不受支持的值是 undefined behavior。当您尝试相同的输入时,不同的设备会出现故障或工作方式不同。
当然,您应该停止预览以更改相机参数,然后再重新启动预览。
除此之外,您可能可以使用变通方法来保留受支持的参数。要达到 2 fps 并切换到 10 fps,您无需更改相机设置。您的逻辑可以按时间戳过滤掉 onPreviewFrame()
中的相关帧。
此外,您的代码在预览回调方面不够理想。首先,你应该在一个单独的处理线程上打开相机,然后预览回调将不会到达 UI 线程(Android 的新版本变得更加嫉妒应用程序劫持主线程CPU 或网络密集型任务)。
其次,考虑使用camera.setPreviewCallbackWithBuffer()来避免不必要的垃圾收集。这种技术的一个额外好处是,如果你只准备一个预览缓冲区,你只会在释放它时收到预览回调。所以,你可以简单地使用代码:
public void onPreviewFrame(byte[] data, Camera camera) {
long timestampBeforecompression = SystemClock.uptimeMillis();
compress(data);
long compressionMillis = SystemClock.uptimeMillis() - timestampBeforecompression;
SystemClock.sleep(1000000/intFramesPerSecond - compressionMillis);
camera.addCallbackBuffer(data);
}
也许,如果您还补偿当前的相机帧速率,您可以更精确,但这在谈到 2 或 3 FPS 时可能并不重要。
最后,还有一个提示:许多设备仍然支持已弃用的 setPreviewFrameRate(),甚至声明您可能感兴趣的支持的 FPS 值:
[1, 2, 3, 4, 5, 8, 10, 15, 20, 30]
在我的无名 Snapdragon-801 平板电脑上。
2015 年 10 月 28 日更新以反映当前进展。 我有一个应用程序,允许用户为 Motion JPEG 记录设置相机参数,创建 MJPEG 文件,然后用户可以修改这些设置并使用更新的设置创建另一个文件。当初始值不是 30 FPS 时,我在更新每秒帧数设置时遇到问题。当初始值为 30 FPS 时,我可以更新到不同的 FPS 级别并在该级别成功录制视频。但是,我无法从不等于 30FPS 的级别更新到另一个 FPM 级别。 LogCat 我崩溃了,显示
有问题camera.setParameters(参数);
全部 LogCat 错误如下,
10-26 20:27:36.414: E/AndroidRuntime(2275): FATAL EXCEPTION: main
10-26 20:27:36.414: E/AndroidRuntime(2275): java.lang.RuntimeException: setParameters failed
10-26 20:27:36.414: E/AndroidRuntime(2275): at android.hardware.Camera.native_setParameters(Native Method)
10-26 20:27:36.414: E/AndroidRuntime(2275): at android.hardware.Camera.setParameters(Camera.java:1333)
10-26 20:27:36.414: E/AndroidRuntime(2275): at net.blepsias.riverwatch.RiverWatch.setCamera(RiverWatch.java:191)
10-26 20:27:36.414: E/AndroidRuntime(2275): at net.blepsias.riverwatch.RiverWatch.onClick(RiverWatch.java:167)
10-26 20:27:36.414: E/AndroidRuntime(2275): at android.view.View.performClick(View.java:3514)
10-26 20:27:36.414: E/AndroidRuntime(2275): at android.view.View$PerformClick.run(View.java:14111)
10-26 20:27:36.414: E/AndroidRuntime(2275): at android.os.Handler.handleCallback(Handler.java:605)
10-26 20:27:36.414: E/AndroidRuntime(2275): at android.os.Handler.dispatchMessage(Handler.java:92)
10-26 20:27:36.414: E/AndroidRuntime(2275): at android.os.Looper.loop(Looper.java:137)
10-26 20:27:36.414: E/AndroidRuntime(2275): at android.app.ActivityThread.main(ActivityThread.java:4429)
10-26 20:27:36.414: E/AndroidRuntime(2275): at java.lang.reflect.Method.invokeNative(Native Method)
10-26 20:27:36.414: E/AndroidRuntime(2275): at java.lang.reflect.Method.invoke(Method.java:511)
10-26 20:27:36.414: E/AndroidRuntime(2275): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
10-26 20:27:36.414: E/AndroidRuntime(2275): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
10-26 20:27:36.414: E/AndroidRuntime(2275): at dalvik.system.NativeStart.main(Native Method)
检查 LogCat 引用的第 5 行和第 6 行,这些对应于:
(191) camera.setParameters(parameters);
(167) setCamera(camera);
下面是应用程序。我还将包括布局 .xml 文件以供参考,以及用于接地的屏幕截图。
RiverWatch.java
public class RiverWatch extends Activity implements OnClickListener, SurfaceHolder.Callback, Camera.PreviewCallback {
public static final String LOGTAG = "VIDEOCAPTURE";
String szBoundaryStart = "\r\n\r\n--myboundary\r\nContent-Type: image/jpeg\r\nContent-Length: ";
String szBoundaryDeltaTime = "\r\nDelta-time: 110";
String szBoundaryEnd = "\r\n\r\n";
private SurfaceHolder holder;
private Camera camera;
private CamcorderProfile camcorderProfile;
Spinner spinnerCamcorderProfile;
public TextView tvFramesPerSecond, tvJpegQuality, tvSegmentDuration;
boolean bRecording = false;
boolean bPreviewRunning = false;
int intFramesPerSecond = 30000; //this is 30fps...mult by 1,000
int intJpegQuality=50; //must be above 20
int intSegmentDuration=10;
boolean ckbxRepeat=false;
byte[] previewCallbackBuffer;
File mjpegFile;
FileOutputStream fos;
BufferedOutputStream bos;
Button btnStartRecord, btnStopRecord, btnExit, btnChange;
Camera.Parameters parameters;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Date T = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
String szFileName = "videocapture-"+sdf.format(T)+"-";
try {
mjpegFile = File.createTempFile(szFileName, ".mjpeg", Environment.getExternalStorageDirectory());
} catch (Exception e) {
finish();
}
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
setContentView(R.layout.main);
tvFramesPerSecond = (TextView) this.findViewById(R.id.textboxframespersecondxml);
int iFPS = intFramesPerSecond/1000;
String szFPS = Integer.toString(iFPS);
tvFramesPerSecond.setClickable(true);
tvFramesPerSecond.setText(szFPS);
tvFramesPerSecond.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
getSupportedPreviewFpsRange();
}
});
tvJpegQuality = (TextView) this.findViewById(R.id.textboxJpegQualityxml);
String szJpegQuality = Integer.toString(intJpegQuality);
tvJpegQuality.setText(szJpegQuality);
tvSegmentDuration = (TextView) this.findViewById(R.id.textboxSegmentDurationxml);
String szSegmentDuration = Integer.toString(intSegmentDuration);
tvSegmentDuration.setText(szSegmentDuration);
btnStartRecord = (Button) this.findViewById(R.id.StartRecordButton);
btnStartRecord.setOnClickListener(this);
btnStopRecord = (Button) this.findViewById(R.id.StopRecordButton);
btnStopRecord.setOnClickListener(this);
btnExit = (Button) this.findViewById(R.id.ExitButton);
btnExit.setOnClickListener(this);
btnChange = (Button) this.findViewById(R.id.ChangeButton);
btnChange.setOnClickListener(this);
camcorderProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_TIME_LAPSE_480P);
SurfaceView cameraView = (SurfaceView) findViewById(R.id.CameraView);
holder = cameraView.getHolder();
holder.addCallback(this);
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
cameraView.setClickable(true);
cameraView.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.StartRecordButton:
try {
fos = new FileOutputStream(mjpegFile);
bos = new BufferedOutputStream(fos);
bRecording = true;
} catch (FileNotFoundException e) {
e.printStackTrace();
}
Toast.makeText(this, "Recording started.", Toast.LENGTH_SHORT).show();
break;
case R.id.StopRecordButton:
try {
bos.flush();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
Toast.makeText(this, "Recording stopped.", Toast.LENGTH_SHORT).show();
break;
case R.id.ChangeButton:
//Frames Per Second- expressed x1000 in the function
String szFPS=tvFramesPerSecond.getText().toString();
int iFPS = Integer.parseInt(szFPS);
intFramesPerSecond = iFPS *1000;
//Jpeg quality- cant be <20 or >100, checks this and populates field with entered or corrected value.
String szJpegQuality=tvJpegQuality.getText().toString();
int intJpegQualityTemp = Integer.parseInt(szJpegQuality);
if (intJpegQualityTemp < 21){//...can't be less than 21
intJpegQuality = 21;
}else if(intJpegQualityTemp > 100){//can't be greater than 100
intJpegQuality = 100;
}else{ //quality is between 21 and 100...
intJpegQuality = intJpegQualityTemp;
}
szJpegQuality = Integer.toString(intJpegQuality);
tvJpegQuality.setText(szJpegQuality);
//Segment duration
String szSegmentDuration=tvSegmentDuration.getText().toString();
intSegmentDuration = Integer.parseInt(szSegmentDuration);
releaseCamera();
setCamera(camera);
camera.startPreview();
Toast.makeText(this, "Change button pressed.", Toast.LENGTH_SHORT).show();
break;
case R.id.ExitButton:
System.exit(0);
break;
}
}
public void releaseCamera(){
camera.stopPreview();
//camera.release(); //...cause crash
//camera = null;
}
public void setCamera(Camera camera){
Camera.Parameters parameters=camera.getParameters();
parameters.setPreviewFpsRange(intFramesPerSecond, intFramesPerSecond);//note: This is fps x 1000 (!)
parameters.setPreviewSize(camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight);
Log.v(LOGTAG,"FPS: " + parameters.getSupportedPreviewFpsRange());
camera.setParameters(parameters);
}
public void getSupportedPreviewFpsRange(){
/****************************************************************
* getSupportedPreviewFpsRange()- Returns specified frame rate
* (.getSupportedPreviewFpsRange()) to log file and also displays
* as toast message.
****************************************************************/
Camera.Parameters camParameter = camera.getParameters();
List<int[]> frame = camParameter.getSupportedPreviewFpsRange();
Iterator<int[]> supportedPreviewFpsIterator = frame.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.d(LOGTAG, "FPS6: " + sb.toString());
Toast.makeText(this, "FPS = "+sb.toString(), Toast.LENGTH_SHORT).show();
}//*****************end getSupportedPreviewFpsRange()**********************
}
public void surfaceCreated(SurfaceHolder holder) {
camera = Camera.open();
}
@SuppressLint("NewApi")
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if (!bRecording) {
if (bPreviewRunning = true){
camera.stopPreview();
} try {
parameters = camera.getParameters();
parameters.setPreviewSize(camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight);
parameters.setPreviewFpsRange(intFramesPerSecond, intFramesPerSecond);//note: This is fps x 1000 (!)
//p.setPreviewFrameRate(intFramesPerSecond);
camera.setParameters(parameters);
camera.setPreviewDisplay(holder);
camera.setPreviewCallback(this);
camera.setDisplayOrientation(90);
camera.startPreview();
bPreviewRunning = true;
}
catch (IOException e) {
e.printStackTrace();
}
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
if (bRecording) {
bRecording = false;
try {
bos.flush();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
bPreviewRunning = false;
camera.release();
finish();
}
public void onPreviewFrame(byte[] b, Camera c) {
if (bRecording) {
// Assuming ImageFormat.NV21
if (parameters.getPreviewFormat() == ImageFormat.NV21) {
try {
YuvImage im = new YuvImage(b, ImageFormat.NV21, parameters.getPreviewSize().width, parameters.getPreviewSize().height, null);
Rect r = new Rect(0,0,parameters.getPreviewSize().width,parameters.getPreviewSize().height);
ByteArrayOutputStream jpegByteArrayOutputStream = new ByteArrayOutputStream();
im.compressToJpeg(r, intJpegQuality, jpegByteArrayOutputStream);//note: qual = 20 or less doesn't work.
byte[] jpegByteArray = jpegByteArrayOutputStream.toByteArray();
byte[] boundaryBytes = (szBoundaryStart + jpegByteArray.length + szBoundaryDeltaTime + szBoundaryEnd).getBytes();
bos.write(boundaryBytes);
bos.write(jpegByteArray);
bos.flush();
//bos.close();
} catch (IOException e) {
e.printStackTrace();
}
} else {
Log.v(LOGTAG,"NOT THE RIGHT FORMAT");
}
}
}
@Override
public void onConfigurationChanged(Configuration conf){
super.onConfigurationChanged(conf);
}
}
布局main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button
android:id="@+id/StartRecordButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start Recording" />
<Button
android:id="@+id/StopRecordButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Stop Recording" />
<Button
android:id="@+id/ChangeButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="50dip"
android:text="Reset settings" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right">
<TextView
style="@style/myStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Frames/second:" />
<EditText
android:id="@+id/textboxframespersecondxml"
android:editable="true"
style="@style/myStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="right"
android:text="0"
android:layout_marginRight="10dip"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right">
<TextView
style="@style/myStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="JPEG image quality:" />
<EditText
android:id="@+id/textboxJpegQualityxml"
android:editable="true"
style="@style/myStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="right"
android:text="0"
android:layout_marginRight="10dip"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
style="@style/myStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:text="Camcorder profile: " />
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right">
<TextView
style="@style/myStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Segment duration (file length):" />
<EditText
android:id="@+id/textboxSegmentDurationxml"
android:editable="true"
style="@style/myStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="right"
android:text="0"
android:layout_marginRight="10dip"/>
<TextView
style="@style/myStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=" minutes" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="right" >
<CheckBox
android:id="@+id/repeat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Repeat" />
<Button
android:id="@+id/ExitButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Exit Application" />
</LinearLayout>
<SurfaceView
android:id="@+id/CameraView"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>
屏幕截图: 显示字段、按钮和表面配置
解决方案: 看来上述大部分不稳定行为可能与主要测试设备 Panasonic Toughpad JT-B1 有关。 运行
.getSupportedPreviewFpsRange();
在此设备上 returns 范围为 8,000-30,000 fps。但是,此范围内的许多值会导致崩溃,而此范围外的某些值似乎工作正常。测试 Samsung S4 Active 导致 none 这些不一致,返回范围 (4,000 - 30,000) 中的所有值都工作正常,并且没有超出此范围的测试值显示任何预期的功能。
相机API 不允许将预览 FPS 范围设置为任意值。您应该查询相机参数以获取支持范围列表,并且不保证任何其他组合都有效。
原则上,Camera.setParameters()
使用不受支持的值是 undefined behavior。当您尝试相同的输入时,不同的设备会出现故障或工作方式不同。
当然,您应该停止预览以更改相机参数,然后再重新启动预览。
除此之外,您可能可以使用变通方法来保留受支持的参数。要达到 2 fps 并切换到 10 fps,您无需更改相机设置。您的逻辑可以按时间戳过滤掉 onPreviewFrame()
中的相关帧。
此外,您的代码在预览回调方面不够理想。首先,你应该在一个单独的处理线程上打开相机,然后预览回调将不会到达 UI 线程(Android 的新版本变得更加嫉妒应用程序劫持主线程CPU 或网络密集型任务)。
其次,考虑使用camera.setPreviewCallbackWithBuffer()来避免不必要的垃圾收集。这种技术的一个额外好处是,如果你只准备一个预览缓冲区,你只会在释放它时收到预览回调。所以,你可以简单地使用代码:
public void onPreviewFrame(byte[] data, Camera camera) {
long timestampBeforecompression = SystemClock.uptimeMillis();
compress(data);
long compressionMillis = SystemClock.uptimeMillis() - timestampBeforecompression;
SystemClock.sleep(1000000/intFramesPerSecond - compressionMillis);
camera.addCallbackBuffer(data);
}
也许,如果您还补偿当前的相机帧速率,您可以更精确,但这在谈到 2 或 3 FPS 时可能并不重要。
最后,还有一个提示:许多设备仍然支持已弃用的 setPreviewFrameRate(),甚至声明您可能感兴趣的支持的 FPS 值:
[1, 2, 3, 4, 5, 8, 10, 15, 20, 30]
在我的无名 Snapdragon-801 平板电脑上。