Camera2 录制视频,按下录制按钮后屏幕冻结(仅在使用前置摄像头和某些设备上时)
Camera2 recording video, Screen freeze after pressing the record button (only when using front camera and on certain devices)
我已经在网上冲浪了,但没有找到任何解决方案,我正在使用 Camera2 api 从我的前置摄像头录制视频,我已经在多个设备上进行了测试并且工作正常,但是当我在我的 Samsung Galaxy 3 上试过,按下录制按钮后有时会录制,有时相机预览会冻结,您可以在下面找到我实现的代码
- 通过延迟加载创建预览和录制请求
private val previewRequest: CaptureRequest? by lazy {
mCaptureSession.device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW).apply {
addTarget(viewFinder.holder.surface)
}.build()
}
private val recordRequest: CaptureRequest by lazy {
mCaptureSession.device.createCaptureRequest(CameraDevice.TEMPLATE_RECORD).apply {
addTarget(viewFinder.holder.surface)
addTarget(mMediaRecorder.surface)
}.build()
}
- 我正在使用 AutoFitSurfaceView,onSurfaceCreated 我正在执行以下操作:
when (cameraDirection) { // I am getting this variable to see what camera I should open
CameraDirection.BACK -> { //getCameraPosition gets the cameraId for the given //LENS_FACING direction
mCameraId = getCameraPosition(CameraCharacteristics.LENS_FACING_BACK)
}
CameraDirection.FRONT -> {
mCameraId = getCameraPosition(CameraCharacteristics.LENS_FACING_FRONT)
}
else -> {
mCameraId = getCameraPosition(CameraCharacteristics.LENS_FACING_BACK)
}
}
characteristics = cameraManager.getCameraCharacteristics(mCameraId!!)
// Selects appropriate preview size and configures view finder
mPreviewSize = getPreviewOutputSize(
viewFinder.display, characteristics, SurfaceHolder::class.java
)
// Selects appropriate video size
mVideoSize = getPreviewOutputSize(
viewFinder.display, characteristics, MediaRecorder::class.java
)
viewFinder.setAspectRatio(mPreviewSize.width, mPreviewSize.height)
// To ensure that size is set, initialize camera in the view's thread
viewFinder.post {
initializeCamera()
}
- initializeCamera() 函数如下所示
private fun initializeCamera() = lifecycleScope.launch(Dispatchers.Main) {
//viewFinder is the AutoFitSurfaceView
camera = openCamera(cameraManager, mCameraId!!, cameraHandler)
setupMediaRecorder()
val targets = listOf(viewFinder.holder.surface)
camera.createCaptureSession(targets, object : CameraCaptureSession.StateCallback() {
override fun onConfigured(session: CameraCaptureSession) {
mCaptureSession = session
session.setRepeatingRequest(previewRequest!!, null, cameraHandler)
}
override fun onConfigureFailed(session: CameraCaptureSession) {
}
}, cameraHandler)
}
- openCamera() 如下所示
private suspend fun openCamera(
manager: CameraManager,
cameraId: String,
handler: Handler? = null
): CameraDevice = suspendCancellableCoroutine { cont ->
manager.openCamera(cameraId, object : CameraDevice.StateCallback() {
override fun onOpened(device: CameraDevice) = cont.resume(device)
override fun onDisconnected(device: CameraDevice) {
finish()
}
override fun onError(device: CameraDevice, error: Int) {
val msg = when (error) {
ERROR_CAMERA_DEVICE -> "Fatal (device)"
ERROR_CAMERA_DISABLED -> "Device policy"
ERROR_CAMERA_IN_USE -> "Camera in use"
ERROR_CAMERA_SERVICE -> "Fatal (service)"
ERROR_MAX_CAMERAS_IN_USE -> "Maximum cameras in use"
else -> "Unknown"
}
val exc = RuntimeException("Camera $cameraId error: ($error) $msg")
if (cont.isActive) cont.resumeWithException(exc)
}
}, handler)
}
- setupMediaRecorder() 看起来像这样
private fun setupMediaRecorder() {
mMediaRecorder = MediaRecorder()
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE)
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
mMediaRecorder.setOutputFile(outputFile.absolutePath)
Log.i("CAMERA_INFO", mCameraId!!)
val profile = CamcorderProfile.get(mCameraId!!.toInt(), CamcorderProfile.QUALITY_LOW)
Log.i("CAMERA_INFO", "Frame Rate: " + profile.videoFrameRate)
mMediaRecorder.setVideoEncodingBitRate(profile.videoBitRate)
mMediaRecorder.setVideoFrameRate(profile.videoFrameRate)
mMediaRecorder.setVideoSize(mPreviewSize.width, mPreviewSize.height)
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264)
when (mCameraDirection) {
CameraDirection.BACK -> {
mMediaRecorder.setOrientationHint(90)
}
CameraDirection.FRONT -> {
mMediaRecorder.setOrientationHint(270)
}
else -> {
}
}
mMediaRecorder.prepare()
}
说明:
首先调用 initializeCamera() 并在此函数中设置相机和预览会话并准备 MediaRecorder 在用户按下录制按钮时开始录制
用户按下录制按钮后,我将执行以下操作:
- 关闭预览会话
- 创建记录会话
- session配置成功后,我正在设置懒加载初始化的recordRequest
下面是代码:
button_record_video.setOnClickListener {
mCaptureSession.close() //Closing the previewSession
try {
camera.createCaptureSession( //Creating the record session passing the viewFinder surface //and the MediaRecorder session
listOf(
viewFinder.holder.surface,
mMediaRecorder.surface
), object : CameraCaptureSession.StateCallback() {
override fun onConfigured(session: CameraCaptureSession) {
mCaptureSession = session
session.setRepeatingRequest(recordRequest, null, cameraHandler)
mMediaRecorder.start()
}
override fun onConfigureFailed(p0: CameraCaptureSession) {
}
}, cameraHandler
)
} catch (e: Exception) {
}
}
PS:此代码在从后置摄像头捕获时有效,对于前置摄像头,它在某些设备上正常工作而在其他设备上偶尔会失败(设备测试此代码偶尔会失败 Samsung Galaxy S3)。
如果需要更多信息,我很乐意提供
提前致谢
我没有在您的代码中看到相机在不同于 Main/GUI 的线程上的处理。事实上,我从来没有看到一个很好的教程来做到这一点 - 所以 - 这就是我所做的:
声明handlerThread
private HandlerThread mBackgroundHandlerThread;
private Handler mBackgroundHandler;
启动和停止后台线程
private void startBackgroundThread() {
mBackgroundThread = new HandlerThread("CameraBackground");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
private void stopBackgroundThread() {
if(mBackgroundHandlerThread == null)
return;
try {
mBackgroundHandlerThread.quitSafely();
mBackgroundHandlerThread.join();
mBackgroundHandlerThread = null;
mBackgroundHandler = null;
} catch (InterruptedException e) {
Log.e(TAG, e.toString());
}
catch (Exception e) {
Log.e(TAG, e.toString());
}
}
神奇的是当你从后台线程的回调中更新 gui 时
mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback()
{
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession)
{
mPreviewSession = cameraCaptureSession;
State = Constants.RecordingState.RECORDING;
// Start recording
if (mMediaRecorder != null)
{
mMediaRecorder.start();
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
// Updating the GUI
mButtonVideo.setEnabled(true);
txtMainMessage.setText("Recording");
}
});
}
@Override public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
Activity activity = getActivity();
if (null != activity) {
Toast.makeText(activity, "Failed", Toast.LENGTH_SHORT).show();
}
}
}, mBackgroundHandler);
}
由于您正在立即准备媒体记录器,因此您可以只进行一个捕获会话,在预览和录制之间共享。然后在你想要开始录制的时候,将录制目标Surface添加到捕获请求中即可。
这避免了创建新捕获会话时出现的故障,并且可能与您遇到问题的设备更加兼容。此外,您可能希望查看来自 MediaCodec 的持久记录表面,以避免必须为第二个记录创建新会话(如果这是您想要支持的)。
我已经在网上冲浪了,但没有找到任何解决方案,我正在使用 Camera2 api 从我的前置摄像头录制视频,我已经在多个设备上进行了测试并且工作正常,但是当我在我的 Samsung Galaxy 3 上试过,按下录制按钮后有时会录制,有时相机预览会冻结,您可以在下面找到我实现的代码
- 通过延迟加载创建预览和录制请求
private val previewRequest: CaptureRequest? by lazy {
mCaptureSession.device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW).apply {
addTarget(viewFinder.holder.surface)
}.build()
}
private val recordRequest: CaptureRequest by lazy {
mCaptureSession.device.createCaptureRequest(CameraDevice.TEMPLATE_RECORD).apply {
addTarget(viewFinder.holder.surface)
addTarget(mMediaRecorder.surface)
}.build()
}
- 我正在使用 AutoFitSurfaceView,onSurfaceCreated 我正在执行以下操作:
when (cameraDirection) { // I am getting this variable to see what camera I should open
CameraDirection.BACK -> { //getCameraPosition gets the cameraId for the given //LENS_FACING direction
mCameraId = getCameraPosition(CameraCharacteristics.LENS_FACING_BACK)
}
CameraDirection.FRONT -> {
mCameraId = getCameraPosition(CameraCharacteristics.LENS_FACING_FRONT)
}
else -> {
mCameraId = getCameraPosition(CameraCharacteristics.LENS_FACING_BACK)
}
}
characteristics = cameraManager.getCameraCharacteristics(mCameraId!!)
// Selects appropriate preview size and configures view finder
mPreviewSize = getPreviewOutputSize(
viewFinder.display, characteristics, SurfaceHolder::class.java
)
// Selects appropriate video size
mVideoSize = getPreviewOutputSize(
viewFinder.display, characteristics, MediaRecorder::class.java
)
viewFinder.setAspectRatio(mPreviewSize.width, mPreviewSize.height)
// To ensure that size is set, initialize camera in the view's thread
viewFinder.post {
initializeCamera()
}
- initializeCamera() 函数如下所示
private fun initializeCamera() = lifecycleScope.launch(Dispatchers.Main) {
//viewFinder is the AutoFitSurfaceView
camera = openCamera(cameraManager, mCameraId!!, cameraHandler)
setupMediaRecorder()
val targets = listOf(viewFinder.holder.surface)
camera.createCaptureSession(targets, object : CameraCaptureSession.StateCallback() {
override fun onConfigured(session: CameraCaptureSession) {
mCaptureSession = session
session.setRepeatingRequest(previewRequest!!, null, cameraHandler)
}
override fun onConfigureFailed(session: CameraCaptureSession) {
}
}, cameraHandler)
}
- openCamera() 如下所示
private suspend fun openCamera(
manager: CameraManager,
cameraId: String,
handler: Handler? = null
): CameraDevice = suspendCancellableCoroutine { cont ->
manager.openCamera(cameraId, object : CameraDevice.StateCallback() {
override fun onOpened(device: CameraDevice) = cont.resume(device)
override fun onDisconnected(device: CameraDevice) {
finish()
}
override fun onError(device: CameraDevice, error: Int) {
val msg = when (error) {
ERROR_CAMERA_DEVICE -> "Fatal (device)"
ERROR_CAMERA_DISABLED -> "Device policy"
ERROR_CAMERA_IN_USE -> "Camera in use"
ERROR_CAMERA_SERVICE -> "Fatal (service)"
ERROR_MAX_CAMERAS_IN_USE -> "Maximum cameras in use"
else -> "Unknown"
}
val exc = RuntimeException("Camera $cameraId error: ($error) $msg")
if (cont.isActive) cont.resumeWithException(exc)
}
}, handler)
}
- setupMediaRecorder() 看起来像这样
private fun setupMediaRecorder() {
mMediaRecorder = MediaRecorder()
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE)
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
mMediaRecorder.setOutputFile(outputFile.absolutePath)
Log.i("CAMERA_INFO", mCameraId!!)
val profile = CamcorderProfile.get(mCameraId!!.toInt(), CamcorderProfile.QUALITY_LOW)
Log.i("CAMERA_INFO", "Frame Rate: " + profile.videoFrameRate)
mMediaRecorder.setVideoEncodingBitRate(profile.videoBitRate)
mMediaRecorder.setVideoFrameRate(profile.videoFrameRate)
mMediaRecorder.setVideoSize(mPreviewSize.width, mPreviewSize.height)
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264)
when (mCameraDirection) {
CameraDirection.BACK -> {
mMediaRecorder.setOrientationHint(90)
}
CameraDirection.FRONT -> {
mMediaRecorder.setOrientationHint(270)
}
else -> {
}
}
mMediaRecorder.prepare()
}
说明: 首先调用 initializeCamera() 并在此函数中设置相机和预览会话并准备 MediaRecorder 在用户按下录制按钮时开始录制
用户按下录制按钮后,我将执行以下操作:
- 关闭预览会话
- 创建记录会话
- session配置成功后,我正在设置懒加载初始化的recordRequest
下面是代码:
button_record_video.setOnClickListener {
mCaptureSession.close() //Closing the previewSession
try {
camera.createCaptureSession( //Creating the record session passing the viewFinder surface //and the MediaRecorder session
listOf(
viewFinder.holder.surface,
mMediaRecorder.surface
), object : CameraCaptureSession.StateCallback() {
override fun onConfigured(session: CameraCaptureSession) {
mCaptureSession = session
session.setRepeatingRequest(recordRequest, null, cameraHandler)
mMediaRecorder.start()
}
override fun onConfigureFailed(p0: CameraCaptureSession) {
}
}, cameraHandler
)
} catch (e: Exception) {
}
}
PS:此代码在从后置摄像头捕获时有效,对于前置摄像头,它在某些设备上正常工作而在其他设备上偶尔会失败(设备测试此代码偶尔会失败 Samsung Galaxy S3)。
如果需要更多信息,我很乐意提供 提前致谢
我没有在您的代码中看到相机在不同于 Main/GUI 的线程上的处理。事实上,我从来没有看到一个很好的教程来做到这一点 - 所以 - 这就是我所做的:
声明handlerThread
private HandlerThread mBackgroundHandlerThread;
private Handler mBackgroundHandler;
启动和停止后台线程
private void startBackgroundThread() {
mBackgroundThread = new HandlerThread("CameraBackground");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
private void stopBackgroundThread() {
if(mBackgroundHandlerThread == null)
return;
try {
mBackgroundHandlerThread.quitSafely();
mBackgroundHandlerThread.join();
mBackgroundHandlerThread = null;
mBackgroundHandler = null;
} catch (InterruptedException e) {
Log.e(TAG, e.toString());
}
catch (Exception e) {
Log.e(TAG, e.toString());
}
}
神奇的是当你从后台线程的回调中更新 gui 时
mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback()
{
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession)
{
mPreviewSession = cameraCaptureSession;
State = Constants.RecordingState.RECORDING;
// Start recording
if (mMediaRecorder != null)
{
mMediaRecorder.start();
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
// Updating the GUI
mButtonVideo.setEnabled(true);
txtMainMessage.setText("Recording");
}
});
}
@Override public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
Activity activity = getActivity();
if (null != activity) {
Toast.makeText(activity, "Failed", Toast.LENGTH_SHORT).show();
}
}
}, mBackgroundHandler);
}
由于您正在立即准备媒体记录器,因此您可以只进行一个捕获会话,在预览和录制之间共享。然后在你想要开始录制的时候,将录制目标Surface添加到捕获请求中即可。
这避免了创建新捕获会话时出现的故障,并且可能与您遇到问题的设备更加兼容。此外,您可能希望查看来自 MediaCodec 的持久记录表面,以避免必须为第二个记录创建新会话(如果这是您想要支持的)。