.setPreviewFpsRange():在相机中每秒更新帧时出现问题.setParameters()

时间:2015-10-26 02:56:52

标签: android-camera surfaceview frame-rate mjpeg

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)

检查引用第5行和第6行的LogCat,它们对应于:

(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>

屏幕截图:显示字段,按钮和曲面配置

分辨率:上述大部分不稳定行为可能与松下Toughpad JT-B1等主要测试设备有关。正在运行

.getSupportedPreviewFpsRange(); 
此设备上的

返回8,000-30,000 fps的范围。但是,此范围内的许多值会导致崩溃,并且此范围之外的某些值似乎可以正常工作。测试三星S4 Active不会导致这些不一致,返回范围内的所有值(4,000 - 30,000)都能正常工作,并且没有超出此范围的测试值显示任何预期的功能。

1 个答案:

答案 0 :(得分:2)

Camera 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平板电脑上。