Camera2 API работает в фоновом режиме
Я использовал Camera2
API для реализации фонового видеорегистратора, работающего как сервис и записывающего с передней камеры. Для этого я создал новый SurfaceView
, установите его размер 1x1 и переместите его в верхний левый угол. Мой код показан ниже. Я использую Android 5.1.
С Camera API он работал очень хорошо, но, к сожалению, частота кадров составляет всего 20 кадров в секунду (а когда я увеличиваю компенсацию экспозиции, она падает еще больше), хотя с Open Camera
Приложение у меня 30 кадров в секунду также с повышенной компенсацией экспозиции. Вот почему я хочу попробовать Camera2
API (в надежде получить более высокий fps). К сожалению, я получаю следующую ошибку:
MediaRecoder: setOutputFile called in an invalid state(2)
MediaRecorder: start called in an invalid state: 2
Вот мой код:
public class RecorderServiceCamera2 extends Service implements SurfaceHolder.Callback {
private WindowManager windowManager;
private SurfaceView surfaceView;
private CameraDevice mCamera;
private MediaRecorder mediaRecorder = null;
private CaptureRequest mCaptureRequest;
private CameraCaptureSession mSession;
@Override
public void onCreate() {
// Create new SurfaceView, set its size to 1x1, move it to the top left corner and set this service as a callback
windowManager = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);
surfaceView = new SurfaceView(this);
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
1, 1,
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
PixelFormat.TRANSLUCENT
);
layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
windowManager.addView(surfaceView, layoutParams);
surfaceView.getHolder().addCallback(this);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
notificationIntent, 0);
Notification notification = new NotificationCompat.Builder(this)
//.setSmallIcon(R.mipmap.app_icon)
.setContentTitle("Background Video Recorder")
.setContentText("")
.setContentIntent(pendingIntent).build();
startForeground(MainActivity.NOTIFICATION_ID_RECORDER_SERVICE, notification);
return Service.START_NOT_STICKY;
}
@Override
public void surfaceCreated(final SurfaceHolder surfaceHolder) {
mediaRecorder = new MediaRecorder();
try {
CameraManager manager = (CameraManager) getSystemService(CAMERA_SERVICE);
String[] cameras = manager.getCameraIdList();
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameras[1]);
StreamConfigurationMap configs = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
Size[] sizes = configs.getOutputSizes(MediaCodec.class);
final Size sizeHigh = sizes[0];
manager.openCamera(cameras[1], new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
mCamera = camera;
mediaRecorder.setPreviewDisplay(surfaceHolder.getSurface());
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
mediaRecorder.setMaxFileSize(0);
mediaRecorder.setOrientationHint(0);
mediaRecorder.setOutputFile("test.mp4");
try { mediaRecorder.prepare(); } catch (Exception ignored) {}
List<Surface> list = new ArrayList<>();
list.add(surfaceHolder.getSurface());
try {
CaptureRequest.Builder captureRequest = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
captureRequest.addTarget(surfaceHolder.getSurface());
mCaptureRequest = captureRequest.build();
} catch (CameraAccessException e) {
e.printStackTrace();
}
try {
mCamera.createCaptureSession(list, new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
mSession = session;
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
mSession = session;
}
}, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
mediaRecorder.start();
try {
mSession.setRepeatingRequest(mCaptureRequest,
new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) {
super.onCaptureStarted(session, request, timestamp, frameNumber);
}
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
super.onCaptureCompleted(session, request, result);
}
@Override
public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) {
}
}, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onDisconnected(@NonNull CameraDevice camera) {
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
}
}, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
// Stop recording and remove SurfaceView
@Override
public void onDestroy() {
mediaRecorder.stop();
mediaRecorder.reset();
mediaRecorder.release();
mCamera.close();
mediaRecorder= null;
windowManager.removeView(surfaceView);
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {}
@Override
public IBinder onBind(Intent intent) { return null; }
}
1 ответ
Вам не нужно устанавливать SurfaceView с Camera2 или даже с Camera1 (с последним просто создайте SurfaceTexture, чтобы получить Surface, и никогда не вызывайте updateTexImage; с первым просто не включайте предварительный просмотр Surface в все, это не нужно).
Тем не менее, вы пытаетесь использовать одну и ту же поверхность как в Camera2, так и в MediaRecorder; это не работает MediaRecorder вообще не нужно рисовать на Surface для записи, вы можете просто оставить эту часть отключенной. Это только для того, чтобы MediaRecorder мог использоваться независимо от API камеры, где он управляет камерой за кулисами.
Я подозреваю, что вызов MediaRecorder.prepare() выдает ошибку, которую вы игнорируете, из-за того, что предварительный просмотр Surface уже используется, поэтому запуск позже не работает.
И да, как говорит CommonsWare, вам, вероятно, понадобится служба переднего плана в Android P.