Android: cambiar de cámara cuando se hace clic en el botón

Tengo un método llamado switchCamera, estoy tratando de cambiar la cámara de adelante hacia atrás con el clic de un botón, en una transición suave. Mi aplicación se congela cuando llamo a este método: sé que no estoy haciendo algo bien. ¿Puede alguien ayudarme aquí?

Cualquier ayuda es muy apreciada.

public void switchCamera(){ int camNum = 0; camNum = Camera.getNumberOfCameras(); int camBackId = Camera.CameraInfo.CAMERA_FACING_BACK; int camFrontId = Camera.CameraInfo.CAMERA_FACING_FRONT; Camera.CameraInfo currentCamInfo = new Camera.CameraInfo(); //if camera is running if (camera != null){ //and there is more than one camera if (camNum > 1){ //stop current camera camera.stopPreview(); camera.setPreviewCallback(null); //camera.takePicture(null, null, PictureCallback); camera.release(); camera = null; //stop surfaceHolder? if (currentCamInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT){ //switch camera to back camera camera=Camera.open(camBackId); } else{ //switch camera to front camera camera=Camera.open(camFrontId); } //switch camera back on //specify surface? try { camera.setPreviewDisplay(surfaceHolder); camera.setPreviewCallback((PreviewCallback) this); camera.startPreview(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } 

 Button otherCamera = (Button) findViewById(R.id.OtherCamera); otherCamera.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (inPreview) { camera.stopPreview(); } //NB: if you don't release the current camera before switching, you app will crash camera.release(); //swap the id of the camera to be used if(currentCameraId == Camera.CameraInfo.CAMERA_FACING_BACK){ currentCameraId = Camera.CameraInfo.CAMERA_FACING_FRONT; } else { currentCameraId = Camera.CameraInfo.CAMERA_FACING_BACK; } camera = Camera.open(currentCameraId); setCameraDisplayOrientation(CameraActivity.this, currentCameraId, camera); try { camera.setPreviewDisplay(previewHolder); } catch (IOException e) { e.printStackTrace(); } camera.startPreview(); } 

Si desea que la imagen de la cámara se muestre con la misma orientación que la pantalla, puede usar el siguiente código.

 public static void setCameraDisplayOrientation(Activity activity, int cameraId, android.hardware.Camera camera) { android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo(); android.hardware.Camera.getCameraInfo(cameraId, info); int rotation = activity.getWindowManager().getDefaultDisplay() .getRotation(); int degrees = 0; switch (rotation) { case Surface.ROTATION_0: degrees = 0; break; case Surface.ROTATION_90: degrees = 90; break; case Surface.ROTATION_180: degrees = 180; break; case Surface.ROTATION_270: degrees = 270; break; } int result; if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { result = (info.orientation + degrees) % 360; result = (360 - result) % 360; // compensate the mirror } else { // back-facing result = (info.orientation - degrees + 360) % 360; } camera.setDisplayOrientation(result); } 

Primero debe destruir SurfacePreview de la cámara anterior, luego debe crear un nuevo objeto de cámara (Back / Front)

 `//Code to destroy SurfacePreview mPreview.surfaceDestroyed(mPreview.getHolder()); mPreview.getHolder().removeCallback(mPreview); mPreview.destroyDrawingCache(); preview.removeView(mPreview); mCamera.stopPreview(); mCamera.stopPreview(); mCamera.setPreviewCallback(null); mCamera.release(); //Now create new camera object if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) { mCamera = Camera.open(camIdx); mPreview = new CameraPreview(CameraActivity.this, mCamera); preview.addView(mPreview); mCamera.setPreviewDisplay(mPreview.getHolder()); mCamera.startPreview(); }` 

Después de una larga búsqueda, finalmente puedo cambiar la cámara con éxito. La respuesta de mjosh es una respuesta útil pero no funcionó para mí. El truco que encontré finalmente es crear una nueva clase CameraPreview y agregarla de nuevo.

Aquí está mi clase CameraPreview .

 @SuppressLint("ViewConstructor") class CameraPreview(context: Context?, private var camera: Camera, private val displayRotation: Int) : SurfaceView(context), SurfaceHolder.Callback { companion object { private const val TAG = "TAG" private const val FOCUS_AREA_SIZE = 300 } val surfaceHolder: SurfaceHolder = holder private var previewSize: Camera.Size? = null private val supportedPreviewSizes: MutableList? init { surfaceHolder.addCallback(this) supportedPreviewSizes = camera.parameters.supportedPreviewSizes } private val surfaceViewTouchListener: View.OnTouchListener = OnTouchListener { v, event -> camera.cancelAutoFocus() val focusRect = calculateFocusArea(event.x, event.y) val parameters = camera.parameters if (parameters.focusMode == Camera.Parameters.FOCUS_MODE_AUTO) { parameters.focusMode = Camera.Parameters.FOCUS_MODE_AUTO } if (parameters.maxNumFocusAreas > 0) { val areaList = ArrayList() areaList.add(Camera.Area(focusRect, 1000)) parameters.focusAreas = areaList } try { camera.cancelAutoFocus() camera.parameters = parameters camera.startPreview() camera.autoFocus { _, cam -> if (cam.parameters.focusMode == Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE) { val parameters = cam.parameters; parameters.focusMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE if (parameters.maxNumFocusAreas > 0) { parameters.focusAreas = null } camera.parameters = parameters camera.startPreview() } } } catch (e: Exception) { e.printStackTrace() } return@OnTouchListener true } override fun surfaceCreated(holder: SurfaceHolder?) { setOnTouchListener(surfaceViewTouchListener) // The Surface has been created, now tell the camera where to draw the preview. try { camera.setPreviewDisplay(holder) camera.setDisplayOrientation(displayRotation) camera.startPreview() } catch (e: IOException) { Log.d(TAG, "Error setting camera preview: " + e.message) } } override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) { // If your preview can change or rotate, take care of those events here. // Make sure to stop the preview before resizing or reformatting it. if (holder?.surface == null) { // preview surface does not exist return } // stop preview before making changes try { camera.stopPreview() } catch (e: Exception) { // ignore: tried to stop a non-existent preview } // set preview size and make any resize, rotate or // reformatting changes here // start preview with new settings try { val parameters = camera.parameters val bestPictureSize = getBestPictureSize(width, height, parameters) bestPictureSize?.let { parameters.setPictureSize(it.width, it.height) } previewSize?.let { parameters.setPreviewSize(it.width, it.height) } camera.parameters = parameters camera.setPreviewDisplay(holder) camera.startPreview() } catch (e: Exception) { Log.d(TAG, "Error starting camera preview: " + e.message) } } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { val width = View.resolveSize(suggestedMinimumWidth, widthMeasureSpec) val height = View.resolveSize(suggestedMinimumHeight, heightMeasureSpec) setMeasuredDimension(width, height) if (supportedPreviewSizes != null) { previewSize = getOptimalPreviewSize(supportedPreviewSizes, width, height) } } private fun getOptimalPreviewSize(sizes: List?, w: Int, h: Int): Camera.Size? { val ASPECT_TOLERANCE = 0.1 val targetRatio = h.toDouble() / w if (sizes == null) return null var optimalSize: Camera.Size? = null var minDiff = java.lang.Double.MAX_VALUE for (size in sizes) { val ratio = size.width.toDouble() / size.height if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue if (Math.abs(size.height - h) < minDiff) { optimalSize = size minDiff = Math.abs(size.height - h).toDouble() } } if (optimalSize == null) { minDiff = java.lang.Double.MAX_VALUE for (size in sizes) { if (Math.abs(size.height - h) < minDiff) { optimalSize = size minDiff = Math.abs(size.height - h).toDouble() } } } return optimalSize } override fun surfaceDestroyed(holder: SurfaceHolder?) { // no-op } private fun getBestPictureSize(width: Int, height: Int, parameters: Camera.Parameters): Camera.Size? { var bestSize: Camera.Size? val sizeList = parameters.supportedPictureSizes bestSize = sizeList[0] for (i in 1 until sizeList.size) { if (sizeList[i].width * sizeList[i].height > bestSize!!.width * bestSize.height) { bestSize = sizeList[i] } } return bestSize } private fun calculateFocusArea(x: Float, y: Float): Rect { val left = clamp(java.lang.Float.valueOf(x / width * 2000 - 1000).toInt(), FOCUS_AREA_SIZE) val top = clamp(java.lang.Float.valueOf(y / height * 2000 - 1000).toInt(), FOCUS_AREA_SIZE) return Rect(left, top, left + FOCUS_AREA_SIZE, top + FOCUS_AREA_SIZE) } private fun clamp(touchCoordinateInCameraReper: Int, focusAreaSize: Int): Int { return if (Math.abs(touchCoordinateInCameraReper) + focusAreaSize / 2 > 1000) { if (touchCoordinateInCameraReper > 0) { 1000 - focusAreaSize / 2 } else { -1000 + focusAreaSize / 2 } } else { touchCoordinateInCameraReper - focusAreaSize / 2 } } fun turnFlashOnOrOff() { try { camera.stopPreview() } catch (e: Exception) { // ignore } val params = camera.parameters params?.let { if (params.flashMode == Camera.Parameters.FLASH_MODE_TORCH) { params.flashMode = Camera.Parameters.FLASH_MODE_OFF //flash.setImageResource(R.mipmap.baseline_flash_off_white_24dp) } else { params.flashMode = Camera.Parameters.FLASH_MODE_TORCH //flash.setImageResource(R.mipmap.baseline_flash_on_white_24dp) } camera.setPreviewDisplay(holder) try { camera.parameters = params } catch (e: Exception) { e.printStackTrace() } camera.startPreview() } } } 

Mi método openCamera que abro la cámara con él:

 private fun openCamera() { camera = CameraUtil.getCameraInstance(getCameraId()) rotation = getDisplayRotation() cameraPreview = CameraPreview(activity, camera!!, rotation) fl_camera.addView(cameraPreview) } 

Antes de crear CameraPreview , debe calcular la rotación de la cámara y configurarla como displayOrientation

 private fun getDisplayRotation(): Int { val info = Camera.CameraInfo() Camera.getCameraInfo(getCameraId(), info) val rotation = activity.windowManager.defaultDisplay.rotation var degrees = 0 when (rotation) { Surface.ROTATION_0 -> degrees = 0 Surface.ROTATION_90 -> degrees = 90 Surface.ROTATION_180 -> degrees = 180 Surface.ROTATION_270 -> degrees = 270 } var result: Int if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { result = (info.orientation + degrees) % 360 result = (360 - result) % 360 // compensate the mirror } else { // back-facing result = (info.orientation - degrees + 360) % 360; } return result } 

Y me cameraId como a continuación:

 private fun getCameraId(): Int { val numberOfCameras = Camera.getNumberOfCameras() var cameraInfo: Camera.CameraInfo for (i in 0 until numberOfCameras) { cameraInfo = Camera.CameraInfo() Camera.getCameraInfo(i, cameraInfo) if (cameraInfo.facing == currentCamera) { return i } } return 0 } 

Y finalmente mi botón SwtichCamera funciona así:

 switch_camera.setOnClickListener { try { camera?.stopPreview() } catch (e: Exception) { e.printStackTrace() } camera?.release() currentCamera = if (currentCamera === android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK) { Camera.CameraInfo.CAMERA_FACING_FRONT } else { Camera.CameraInfo.CAMERA_FACING_BACK } fl_camera.removeView(cameraPreview) openCamera() } 

Esta es una solución de trabajo para mí. Espero que esto te ayude a otros también.

Editar: la vista previa de la cámara puede ser un problema para los dispositivos Samsung. Aquí hay un método alternativo para obtener el mejor tamaño de vista previa.

 private fun getOptimalPreviewSize(sizes: List?, w: Int, h: Int): Camera.Size? { if (sizes == null) return null var optimalSize: Camera.Size? = null val ratio = h.toDouble() / w var minDiff = java.lang.Double.MAX_VALUE var newDiff: Double for (size in sizes) { newDiff = Math.abs(size.width.toDouble() / size.height - ratio) if (newDiff < minDiff) { optimalSize = size minDiff = newDiff } } return optimalSize }