Управление камерой

В этом уроке мы обсудим, как управлять аппаратным обеспечением камеры напрямую с помощью API фреймворка.

Примечание. На этой странице рассматривается класс Camera, который больше не поддерживается. Рекомендуем использовать CameraX или, в некоторых случаях, Camera2. CameraX и Camera2 поддерживают Android 5.0 (уровень API 21) и более поздние версии.

Для прямого управления камерой устройства требуется гораздо больше кода, чем для запроса фотографий или видео из существующих приложений камеры. Однако если вы хотите создать специализированное приложение для камеры или полностью интегрировать его в пользовательский интерфейс своего приложения, в этом уроке показано, как это сделать.

Ознакомьтесь со следующей информацией по теме:

Как открыть объект камеры

Получение экземпляра объекта Camera – это первый шаг в процессе непосредственного управления камерой. Как и в случае с приложением "Камера" для Android, рекомендуется открывать Camera в отдельном потоке, запущенном из onCreate(). Это хороший подход, поскольку процесс может занять некоторое время и затормозить поток UI. В более простой реализации открытие камеры можно отложить до метода onResume(), чтобы упростить повторное использование кода и сделать поток управления более понятным.

Вызов Camera.open() вызывает исключение, если камера уже используется другим приложением, поэтому мы помещаем его в блок try.

Kotlin

private fun safeCameraOpen(id: Int): Boolean {
    return try {
        releaseCameraAndPreview()
        mCamera = Camera.open(id)
        true
    } catch (e: Exception) {
        Log.e(getString(R.string.app_name), "failed to open Camera")
        e.printStackTrace()
        false
    }
}

private fun releaseCameraAndPreview() {
    preview?.setCamera(null)
    mCamera?.also { camera ->
        camera.release()
        mCamera = null
    }
}

Java

private boolean safeCameraOpen(int id) {
    boolean qOpened = false;

    try {
        releaseCameraAndPreview();
        camera = Camera.open(id);
        qOpened = (camera != null);
    } catch (Exception e) {
        Log.e(getString(R.string.app_name), "failed to open Camera");
        e.printStackTrace();
    }

    return qOpened;
}

private void releaseCameraAndPreview() {
    preview.setCamera(null);
    if (camera != null) {
        camera.release();
        camera = null;
    }
}

Начиная с уровня API 9, фреймворк камеры поддерживает несколько камер. Если вы используете устаревший API и вызываете open() без аргумента, вы получите первую основную камеру.

Создание предварительного просмотра камеры

Чтобы сделать снимок, пользователям обычно требуется увидеть предварительный просмотр объекта, прежде чем нажать кнопку затвора. Для этого можно использовать SurfaceView, чтобы рисовать предварительные изображения того, что улавливает датчик камеры.

Класс Preview

Чтобы начать показ предварительной версии, вам нужен класс предварительной версии. Для предварительного просмотра требуется реализация интерфейса android.view.SurfaceHolder.Callback, который используется для передачи данных изображения с камеры в приложение.

Kotlin

class Preview(
        context: Context,
        val surfaceView: SurfaceView = SurfaceView(context)
) : ViewGroup(context), SurfaceHolder.Callback {

    var mHolder: SurfaceHolder = surfaceView.holder.apply {
        addCallback(this@Preview)
        setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)
    }
    ...
}

Java

class Preview extends ViewGroup implements SurfaceHolder.Callback {

    SurfaceView surfaceView;
    SurfaceHolder holder;

    Preview(Context context) {
        super(context);

        surfaceView = new SurfaceView(context);
        addView(surfaceView);

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        holder = surfaceView.getHolder();
        holder.addCallback(this);
        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }
...
}

Класс предварительного просмотра необходимо передать объекту Camera, прежде чем можно будет запустить предварительный просмотр изображения, как показано в следующем разделе.

Как настроить и запустить предварительную версию

Экземпляр камеры и связанный с ним предварительный просмотр должны быть созданы в определенном порядке, причем объект камеры должен быть первым. В приведенном ниже фрагменте кода процесс инициализации камеры инкапсулирован, поэтому метод Camera.startPreview() вызывается методом setCamera() каждый раз, когда пользователь выполняет действие, изменяющее камеру. Предварительный просмотр также необходимо перезапустить в методе обратного вызова класса предварительного просмотра surfaceChanged().

Kotlin

fun setCamera(camera: Camera?) {
    if (mCamera == camera) {
        return
    }

    stopPreviewAndFreeCamera()

    mCamera = camera

    mCamera?.apply {
        mSupportedPreviewSizes = parameters.supportedPreviewSizes
        requestLayout()

        try {
            setPreviewDisplay(holder)
        } catch (e: IOException) {
            e.printStackTrace()
        }

        // Important: Call startPreview() to start updating the preview
        // surface. Preview must be started before you can take a picture.
        startPreview()
    }
}

Java

public void setCamera(Camera camera) {
    if (mCamera == camera) { return; }

    stopPreviewAndFreeCamera();

    mCamera = camera;

    if (mCamera != null) {
        List<Size> localSizes = mCamera.getParameters().getSupportedPreviewSizes();
        supportedPreviewSizes = localSizes;
        requestLayout();

        try {
            mCamera.setPreviewDisplay(holder);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Important: Call startPreview() to start updating the preview
        // surface. Preview must be started before you can take a picture.
        mCamera.startPreview();
    }
}

Изменить настройки камеры

Настройки камеры позволяют менять параметры съемки, например уровень масштабирования и экспокоррекцию. В этом примере изменяется только размер предварительного просмотра. Дополнительные сведения см. в исходном коде приложения Camera.

Kotlin

override fun surfaceChanged(holder: SurfaceHolder, format: Int, w: Int, h: Int) {
    mCamera?.apply {
        // Now that the size is known, set up the camera parameters and begin
        // the preview.
        parameters?.also { params ->
            params.setPreviewSize(previewSize.width, previewSize.height)
            requestLayout()
            parameters = params
        }

        // Important: Call startPreview() to start updating the preview surface.
        // Preview must be started before you can take a picture.
        startPreview()
    }
}

Java

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
    // Now that the size is known, set up the camera parameters and begin
    // the preview.
    Camera.Parameters parameters = mCamera.getParameters();
    parameters.setPreviewSize(previewSize.width, previewSize.height);
    requestLayout();
    mCamera.setParameters(parameters);

    // Important: Call startPreview() to start updating the preview surface.
    // Preview must be started before you can take a picture.
    mCamera.startPreview();
}

Как задать ориентацию предварительного просмотра

Большинство приложений камеры блокируют экран в горизонтальном режиме, поскольку это естественная ориентация датчика камеры. Эта настройка не мешает делать снимки в портретном режиме, поскольку ориентация устройства записывается в заголовок EXIF. Метод setCameraDisplayOrientation() позволяет изменить способ отображения предварительного просмотра, не влияя на то, как записывается изображение. Однако в Android до уровня API 14 необходимо остановить предварительный просмотр перед изменением ориентации, а затем перезапустить его.

Сделать снимок

Чтобы сделать снимок после запуска предварительного просмотра, используйте метод Camera.takePicture(). Вы можете создать объекты Camera.PictureCallback и Camera.ShutterCallback и передать их в Camera.takePicture().

Если вы хотите непрерывно получать изображения, создайте Camera.PreviewCallback, который реализует onPreviewFrame(). Если вам нужен промежуточный вариант, вы можете захватывать только выбранные кадры предварительного просмотра или настроить отложенное действие для вызова takePicture().

Перезапустить предварительную версию

После того как снимок сделан, необходимо перезапустить предварительный просмотр, прежде чем пользователь сможет сделать ещё один снимок. В этом примере перезапуск выполняется путем перегрузки кнопки спуска затвора.

Kotlin

fun onClick(v: View) {
    previewState = if (previewState == K_STATE_FROZEN) {
        camera?.startPreview()
        K_STATE_PREVIEW
    } else {
        camera?.takePicture(null, rawCallback, null)
        K_STATE_BUSY
    }
    shutterBtnConfig()
}

Java

@Override
public void onClick(View v) {
    switch(previewState) {
    case K_STATE_FROZEN:
        camera.startPreview();
        previewState = K_STATE_PREVIEW;
        break;

    default:
        camera.takePicture( null, rawCallback, null);
        previewState = K_STATE_BUSY;
    } // switch
    shutterBtnConfig();
}

Как остановить предварительный просмотр и освободить камеру

После того как приложение завершит работу с камерой, необходимо выполнить очистку. В частности, необходимо освободить объект Camera, иначе другие приложения, в том числе новые экземпляры вашего приложения, могут работать со сбоями.

Когда следует остановить предварительный просмотр и освободить камеру? Уничтожение поверхности предварительного просмотра – это хороший намек на то, что пора остановить предварительный просмотр и освободить камеру, как показано в этих методах из класса Preview.

Kotlin

override fun surfaceDestroyed(holder: SurfaceHolder) {
    // Surface will be destroyed when we return, so stop the preview.
    // Call stopPreview() to stop updating the preview surface.
    mCamera?.stopPreview()
}

/**
 * When this function returns, mCamera will be null.
 */
private fun stopPreviewAndFreeCamera() {
    mCamera?.apply {
        // Call stopPreview() to stop updating the preview surface.
        stopPreview()

        // Important: Call release() to release the camera for use by other
        // applications. Applications should release the camera immediately
        // during onPause() and re-open() it during onResume()).
        release()

        mCamera = null
    }
}

Java

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    // Surface will be destroyed when we return, so stop the preview.
    if (mCamera != null) {
        // Call stopPreview() to stop updating the preview surface.
        mCamera.stopPreview();
    }
}

/**
 * When this function returns, mCamera will be null.
 */
private void stopPreviewAndFreeCamera() {

    if (mCamera != null) {
        // Call stopPreview() to stop updating the preview surface.
        mCamera.stopPreview();

        // Important: Call release() to release the camera for use by other
        // applications. Applications should release the camera immediately
        // during onPause() and re-open() it during onResume()).
        mCamera.release();

        mCamera = null;
    }
}

Ранее в этом уроке эта процедура также была частью метода setCamera(), поэтому инициализация камеры всегда начинается с остановки предварительного просмотра.