后台服务中的 Camera2 API 运行
Camera2 API running in background service
我已经使用 Camera2
API 实现后台录像机 运行 作为服务并从前置摄像头进行录制。为此,我创建了一个新的 SurfaceView
,将其大小设置为 1x1 并将其移至左上角。我的代码如下所示。我正在使用 Android 5.1.
使用相机 API 它工作得很好但不幸的是帧率只有 20 fps(当我增加曝光补偿时它下降得更多),尽管使用 Open Camera
应用程序我也有 30 fps,增加了曝光补偿。这就是为什么我想尝试 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; }
}
您不需要为 Camera2 或 Camera1 设置 SurfaceView(对于后者,只需创建一个 SurfaceTexture 以从中获取 Surface,而不要调用 updateTexImage;对于前者,只需不要根本不需要预览 Surface)。
就是说,您正在尝试在 Camera2 和 MediaRecorder 中使用相同的 Surface;那行不通。 MediaRecorder 根本不需要绘制到 Surface 即可进行记录,您可以将该部分关闭。它只允许独立于相机使用 MediaRecorder API,它在幕后管理相机。
我怀疑对 MediaRecorder.prepare() 的调用抛出了一个你忽略的错误,关于预览 Surface 已经在使用中,这就是为什么 start 稍后不起作用的原因。
是的,正如 CommonsWare 所说,您可能需要 Android P.
中的前台服务
我已经使用 Camera2
API 实现后台录像机 运行 作为服务并从前置摄像头进行录制。为此,我创建了一个新的 SurfaceView
,将其大小设置为 1x1 并将其移至左上角。我的代码如下所示。我正在使用 Android 5.1.
使用相机 API 它工作得很好但不幸的是帧率只有 20 fps(当我增加曝光补偿时它下降得更多),尽管使用 Open Camera
应用程序我也有 30 fps,增加了曝光补偿。这就是为什么我想尝试 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; }
}
您不需要为 Camera2 或 Camera1 设置 SurfaceView(对于后者,只需创建一个 SurfaceTexture 以从中获取 Surface,而不要调用 updateTexImage;对于前者,只需不要根本不需要预览 Surface)。
就是说,您正在尝试在 Camera2 和 MediaRecorder 中使用相同的 Surface;那行不通。 MediaRecorder 根本不需要绘制到 Surface 即可进行记录,您可以将该部分关闭。它只允许独立于相机使用 MediaRecorder API,它在幕后管理相机。
我怀疑对 MediaRecorder.prepare() 的调用抛出了一个你忽略的错误,关于预览 Surface 已经在使用中,这就是为什么 start 稍后不起作用的原因。
是的,正如 CommonsWare 所说,您可能需要 Android P.
中的前台服务