Điều khiển camera

Trong bài học này, chúng ta sẽ thảo luận cách trực tiếp điều khiển phần cứng máy ảnh bằng các API khung.

Lưu ý: Trang này đề cập đến lớp Camera (Máy ảnh) (không dùng nữa). Bạn nên dùng CameraX hoặc Camera2 (trong một số trường hợp sử dụng cụ thể). Cả CameraX và Camera2 đều hỗ trợ Android 5.0 (API cấp 21) trở lên.

Việc điều khiển trực tiếp máy ảnh của thiết bị cần nhiều mã hơn so với việc yêu cầu hình ảnh hoặc video từ các ứng dụng máy ảnh hiện có. Tuy nhiên, nếu bạn muốn tạo một ứng dụng máy ảnh chuyên dụng hoặc tích hợp đầy đủ trong giao diện người dùng của ứng dụng, bài học này sẽ hướng dẫn bạn cách thực hiện.

Hãy tham khảo các tài nguyên liên quan sau:

Mở đối tượng Camera (Máy ảnh)

Việc lấy thực thể của đối tượng Camera là bước đầu tiên trong quy trình điều khiển trực tiếp máy ảnh. Như ứng dụng Máy ảnh của Android, để truy cập vào máy ảnh, bạn nên mở Camera trên một luồng riêng được chạy từ onCreate(). Bạn nên sử dụng phương pháp này vì có thể mất một chút thời gian và có thể làm chậm luồng giao diện người dùng. Trong cách triển khai cơ bản hơn, bạn có thể trì hoãn việc mở máy ảnh đối với phương thức onResume() để hỗ trợ việc sử dụng lại mã và giữ cho quy trình điều khiển đơn giản.

Việc gọi Camera.open() sẽ gửi một trường hợp ngoại lệ nếu máy ảnh đang được ứng dụng khác dùng, vì vậy, chúng ta sẽ gói máy ảnh đó trong một khối 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;
    }
}

Kể từ API cấp 9, khung máy ảnh hỗ trợ nhiều máy ảnh. Nếu sử dụng API cũ và gọi open() mà không có đối số, bạn sẽ có máy ảnh mặt sau đầu tiên.

Tạo bản xem trước trên máy ảnh

Việc chụp ảnh thường yêu cầu người dùng xem đối tượng trước khi nhấp vào nút chụp. Để thực hiện việc này, bạn có thể dùng SurfaceView để vẽ các bản xem trước về đối tượng mà cảm biến của máy ảnh đang thu lại.

Lớp Preview (Xem trước)

Để bắt đầu hiển thị bản xem trước, bạn cần có lớp xem trước. Bản xem trước yêu cầu triển khai giao diện android.view.SurfaceHolder.Callback. Giao diện này được dùng để truyền dữ liệu hình ảnh từ phần cứng máy ảnh sang ứng dụng.

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

Bạn phải chuyển lớp xem trước vào đối tượng Camera thì mới có thể bắt đầu quá trình xem trước hình ảnh trực tiếp, như minh hoạ trong phần tiếp theo.

Đặt và bắt đầu bản xem trước

Bạn phải tạo một thực thể máy ảnh và bản xem trước có liên quan của máy ảnh theo thứ tự cụ thể, trước tiên là đối tượng máy ảnh. Trong đoạn mã dưới đây, quy trình khởi động máy ảnh được đóng gói để Camera.startPreview() được gọi bằng phương thức setCamera() bất cứ khi nào người dùng thay đổi máy ảnh. Bạn cũng phải bắt đầu lại bản xem trước trong phương pháp gọi lại surfaceChanged() của lớp xem trước.

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

Sửa đổi các chế độ cài đặt của Máy ảnh

Các chế độ cài đặt của Máy ảnh thay đổi cách máy ảnh chụp ảnh, từ mức thu phóng đến bù phơi sáng. Ví dụ này chỉ thay đổi kích thước bản xem trước; hãy xem mã nguồn của ứng dụng Máy ảnh để biết thêm thông tin.

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

Đặt hướng bản xem trước

Hầu hết các ứng dụng máy ảnh khoá màn hình ở chế độ ngang vì đó là hướng tự nhiên của cảm biến máy ảnh. Chế độ cài đặt này không ngăn bạn chụp ảnh ở chế độ chân dung, vì hướng của thiết bị được ghi lại trong tiêu đề EXIF. Phương thức setCameraDisplayOrientation() cho phép bạn thay đổi cách hiển thị bản xem trước mà không ảnh hưởng đến cách ghi lại hình ảnh. Tuy nhiên, trong Android trước phiên bản 4.0 (API cấp 14), bạn phải dừng bản xem trước thì mới có thể thay đổi hướng rồi khởi động lại.

Chụp ảnh

Sử dụng phương thức Camera.takePicture() để chụp ảnh sau khi bản xem trước bắt đầu. Bạn có thể tạo các đối tượng Camera.PictureCallbackCamera.ShutterCallback rồi truyền các đối tượng đó vào Camera.takePicture().

Nếu muốn liên tục lấy hình ảnh, bạn có thể tạo Camera.PreviewCallback triển khai onPreviewFrame(). Đối với đối tượng ở giữa, bạn chỉ có thể chụp các khung xem trước đã chọn hoặc thiết lập hành động bị trì hoãn để gọi takePicture().

Bắt đầu lại bản xem trước

Sau khi ảnh được chụp, bạn phải bắt đầu lại bản xem trước thì người dùng mới có thể chụp ảnh khác. Trong ví dụ này, việc bắt đầu lại được thực hiện bằng cách nạp chồng nút chụp.

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

Dừng bản xem trước và nhả đối tượng Camera (Máy ảnh)

Sau khi ứng dụng của bạn dùng xong máy ảnh, đã đến lúc dọn dẹp. Cụ thể, bạn phải nhả đối tượng Camera, nếu không, sẽ có nguy cơ gây ra sự cố cho các ứng dụng khác, bao gồm cả các phiên bản ứng dụng mới của chính bạn.

Khi nào bạn nên dừng bản xem trước và nhả đối tượng máy ảnh? Nếu đã huỷ bỏ giao diện xem trước, thì giờ là lúc bạn nên dừng bản xem trước và nhả đối tượng máy ảnh (như minh hoạ trong các phương thức này từ lớp 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;
    }
}

Trước đó, trong bài học này, quy trình này cũng là một phần của phương thức setCamera(), vì vậy, việc khởi động một máy ảnh sẽ luôn bắt đầu bằng cách dừng bản xem trước.