2015-10-26 6 views
1

ОБНОВЛЕНО 28 октября 2015 года, чтобы отразить текущий прогресс. У меня есть приложение, которое позволяет пользователю установить параметры камеры для записи Motion JPEG, создать файл MJPEG, а затем пользователь может изменить эти настройки и создать другой файл с обновленными настройками. У меня возникает проблема обновления настроек кадров в секунду, когда начальное значение составляет не более 30 FPS. Когда начальное значение составляет 30 FPS, я могу обновиться до другого уровня FPS и успешно записать видео на этом уровне. Однако я не могу обновить с уровня, который не равен 30FPS другому уровню FPM. Я получаю сбой с LogCat, показывая проблему на.setPreviewFpsRange(): Проблемы с обновлением кадров в секунду в камере .setParameters()

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); 
    } 
} 

Layout 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) работали нормально, а не проверенные значения вне этого диапазона, демонстрирующие любую функциональность, как ожидалось.

+0

Вставка: 'camera.stopPreview(); p = camera.getParameters(); p.setPreviewSize (camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight); p.setPreviewFpsRange (intFramesPerSecond, intFramesPerSecond); camera.setParameters (p); camera.startPreview(); ' в распределительном регистре R.id.ChangeButton делает код вызывает правильное изменение частоты кадров, однако, я все еще получаю сбои при ' camera.setParameters (P); ' для последующих обновлений, почему? – portsample

+0

Суть проблемы - это «p.setPreviewFpsRange (intFramesPerSecond, intFramesPerSecond)», поскольку комментирование этого события останавливает сбои и позволяет повторять обновления. – portsample

+1

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

ответ

2

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

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

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

Кроме этого, вероятно, вы можете использовать обходное решение для поддержки поддерживаемых параметров. Для достижения 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 кадров в секунду.


Наконец, есть еще одна подсказка: многие устройства по-прежнему поддерживать устаревшие setPreviewFrameRate(), и даже объявить поддерживаемые значения FPS, которые могут быть интересны для вас:

[1, 2, 3, 4, 5, 8, 10, 15, 20, 30] 

на моем не имя Snapdragon -801.

+0

Это было полезно. Спасибо за комментарии и перспективы. – portsample