Android: переключение камеры при нажатии кнопки

У меня есть метод под названием switchCamera, я пытаюсь переключить камеру спереди назад, нажав кнопку, одним плавным переходом. Мое приложение зависает, когда я вызываю этот метод - я знаю, что я не делаю что-то правильно. Может ли кто-нибудь помочь мне здесь?

Любая помощь очень ценится.

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

Ответ 1

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

Если вы хотите, чтобы изображение камеры отображалось в той же ориентации, что и дисплей, вы можете использовать следующий код.

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

Ответ 2

Сначала вам нужно уничтожить SurfacePreview предыдущей камеры, а затем создать новый объект камеры (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();
}`

Ответ 3

После долгого поиска, наконец, я могу успешно переключить камеру. Ответ mjosh - полезный ответ, но это не сработало для меня. Трюк, который я нашел, наконец, создает новый класс CameraPreview и добавляет его снова.

Вот мой класс 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<Camera.Size>?

    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<Camera.Area>()
            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()
        }
        [email protected] 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<Camera.Size>?, 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()
        }
    }
}

Мой метод openCamera который я открываю с ним:

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

Перед созданием CameraPreview вам необходимо вычислить поворот камеры и установить ее как 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
    }

И я получаю cameraId как cameraId ниже:

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
    }

И, наконец, моя кнопка SwtichCamera работает следующим образом:

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

Это рабочее решение для меня. Надеюсь, это поможет вам и другим.

Изменить: просмотр камеры может быть проблемой для устройств Samsung. Вот альтернативный метод получения наилучшего размера предварительного просмотра.

private fun getOptimalPreviewSize(sizes: List<Camera.Size>?, 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
    }

Ответ 4

Попробуйте использовать AsyncTask. Код будет работать в отдельном потоке, не нарушая поток пользовательского интерфейса. Существует несколько примеров использования AsyncTask на этом сайте.