.setPreviewFpsRange(): возникают проблемы с обновлением кадров в секунду в камере.setParameters()

ОБНОВЛЕНО 28 октября 2015 года, чтобы отразить текущий прогресс. У меня есть приложение, которое позволяет пользователю устанавливать параметры камеры для записи Motion JPEG, создавать файл MJPEG, а затем пользователь может изменять эти настройки и создавать другой файл с обновленными настройками. У меня проблема с обновлением настроек кадров в секунду, когда начальное значение отличается от 30 FPS. Когда начальное значение составляет 30 кадров в секунду, я могу перейти на другой уровень 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(); 

на этом устройстве возвращается диапазон от 8 000 до 30 000 кадров в секунду. Однако многие значения в этом диапазоне приводят к сбоям, и некоторые значения за пределами этого диапазона работают нормально. Тестирование Samsung S4 Active не привело ни к одному из этих несоответствий: все значения в возвращаемом диапазоне (4000–30 000) работали нормально, и ни одно из протестированных значений за пределами этого диапазона не демонстрировало какую-либо функциональность, как ожидалось.

1 ответ

Решение

Camera API не позволяет устанавливать диапазон FPS предварительного просмотра на произвольные значения. Вы должны запросить параметры камеры для получения списка поддерживаемых диапазонов, и любая другая комбинация не гарантируется.

В принципе, используя неподдерживаемые значения для Camera.setParameters() является неопределенным поведением. Разные устройства будут выходить из строя или работать по-разному, если вы попробуете одни и те же входы.

Определенно, однако, вы должны остановить предварительный просмотр, чтобы изменить параметры камеры, и перезапустить предварительный просмотр после этого.

Кроме этого, вы можете использовать обходной путь для поддержки поддерживаемых параметров. Чтобы достичь 2 кадров в секунду и переключиться на 10 кадров в секунду, вам не нужно менять настройки камеры. Ваша логика может отфильтровать соответствующие кадры в вашем onPreviewFrame() по метке времени.


Кроме того, ваш код неоптимален, когда дело доходит до предварительных обратных вызовов. Прежде всего, вы должны открыть камеру в отдельном потоке обработчика, тогда обратные вызовы предварительного просмотра не будут поступать в потоке пользовательского интерфейса (более новые версии Android становятся еще более ревнивыми к приложениям, угоняющим основной поток для ЦП или задач, интенсивно использующих сеть).

Во-вторых, рассмотрите возможность использования 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.

Другие вопросы по тегам