Camera2 - Android
Ở bài viết này mình xin giới thiệu về cách sử dụng Camera2 trong android SDK 21. Với các lập trình viên android việc sử dụng Camera có rất nhiều trong ứng dụng: Camera Capture Images, Barcode - QR Code Reader, AR, Video Record,.... Nhiều ứng dụng chỉ ở tầng ứng dụng sử dụng thông qua Intent như ...
Ở bài viết này mình xin giới thiệu về cách sử dụng Camera2 trong android SDK 21. Với các lập trình viên android việc sử dụng Camera có rất nhiều trong ứng dụng: Camera Capture Images, Barcode - QR Code Reader, AR, Video Record,....
Nhiều ứng dụng chỉ ở tầng ứng dụng sử dụng thông qua Intent như vậy hệ thống tự động được gọi ở mức tối ưu nhất, nhưng cũng không ít ứng dụng cần can thiệp vào tầng native để xử lý
Với Camera developer.android.com đã deprecate nó đã không còn được sử dụng cơ bản trong các ứng dụng nữa vì rất nhiều nguyên nhân trong đó phải kể tới: tốn tài nguyên, thời gian capture khá chậm và đặc biệt phục vụ nhu cầu ngày càng cao của người dùng như 'Chụp ảnh liên tục, chụp nhiều ảnh và tự động lấy nét' thì Camera không đáp ứng được
Với Camera2 đã đáp ứng được những thiếu xót trên ngoài ra việc customize cho ảnh là rất dễ dàng mang lại chất lượng cao ngoài ra việc sử dụng cũng không có nhiều thay đổi so với Camera
Nếu các bạn muốn đọc chi tiết hoặc muốn biết thêm tài liệu về Camera2 các bạn có thể tham khảo tại android developer và giải thích chi tiết về kiến trúc cũng như luồng xử lý tại Camera2 lifecycle
Dưới đây là ví dụ về cách sự dụng Camera2 trong android SDK 21
-
Lớp CameraManager sử dụng để quản lý tất cả các thiết bị Camera trong android
-
Mỗi thiết bị Camera của các hãng sản xuất khác nhau có các thông số và tuỳ chỉnh khác nhau, vì vậy trước khi sử dụng hoặc làm việc với Camera cần quan tâm các risk có thể xảy ra với các trường hợp cụ thể, ngoài ra cần nắm vững các cấu trúc cơ bản của các hãng sản xuất
-
Để có được ảnh từ thiết bị Camera đầu tiên khởi tạo sự kiện Camera Capture
-
Khi customize với Camera hoặc Camera2 ta cần 1 holder để preview hoặc capture trên bề mặt của nó, như vậy trong trường hợp này ta có thể sử dụng SurfaceView hoặc TextureView (Surface, SurfaceTexture) MediaCodec, MediaRecorder, Allocation, hoặc ImageReader
-
Ứng dụng cần khởi tạo CaptureRequest và khai báo các thuộc tính params cho việc tạo ra ảnh được như ý: ảnh vuông, ảnh tỉ lệ axb
-
Khi mọi thông số đã được điều chỉnh, giờ chỉ cần lắng nghe lớp capture để trả về giá trị ảnh và các thông số của bức ảnh
Capture của example
Dưới đây là chi tiết code cũng như cấu hình của Project
MANIFEST.XML
Android Permission
cấu hình Manifest.xml cho phép quyền sử dụng Camera
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.androidcameraapi2"> <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="25" /> <!-- Yêu cầu quyền truy cập đến Camera trong ứng dụng --> <uses-permission android:name="android.permission.CAMERA" /> <!-- Yêu cầu quyền Storage để lưu ảnh vào máy hoặc thẻ nhớ --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- Gọi đến chức năng của device --> <uses-feature android:name="android.hardware.camera2.full" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".AndroidCameraApi"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
Trong ứng dụng này cần request 2 permission đó là CAMERA và STORAGE phục vụ việc chụp ảnh và lưu ảnh vào bộ nhớ
STRINGS.XML
Khai báo strings.xml cần thiết
<resources> <string name="app_name">Android Camera API 2</string> <!-- Dùng cho button chụp ảnh --> <string name="take_picture">Take picture</string> </resources>
COLOR.XML
Khai báo 1 số giái trị về color cho việc sử dụng Toolbar
<?xml version="1.0" encoding="utf-8"?> <resources> <!-- Màu cho actionbar --> <color name="colorPrimary">#3F51B5</color> <!-- Màu cho status bar --> <color name="colorPrimaryDark">#303F9F</color> <!-- Màu sử dụng cho text default của button, cursor, ... --> <color name="colorAccent">#FF4081</color> </resources>
LAYOUT
Customize layout cho việc preview và chức năng chụp ảnh, như có nói bên trên ta cần một lớp để preview Camera2 và thêm 1 button để chụp ảnh
activity_android_camera_api.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_awidth="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.inducesmile.androidcameraapi2.AndroidCameraApi"> <!-- TextureView cho việc preview Camera2 --> <TextureView android:id="@+id/texture" android:layout_awidth="match_parent" android:layout_height="wrap_content" android:layout_above="@+id/btn_takepicture" android:layout_alignParentTop="true"/> <!-- Button thực hiện việc chụp ảnh --> <Button android:id="@+id/btn_takepicture" android:layout_awidth="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:layout_marginBottom="16dp" android:layout_marginTop="16dp" android:text="@string/take_picture" /> </RelativeLayout>
ACTIVITY.JAVA
Trong class thực hiển các nhiệm vụ constructor, open camera, preview and capture
pckage com.example.androidcameraapi2; import android.Manifest; import android.content.Context; import android.content.pm.PackageManager; import android.graphics.ImageFormat; import android.graphics.SurfaceTexture; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.TotalCaptureResult; import android.hardware.camera2.params.StreamConfigurationMap; import android.media.Image; import android.media.ImageReader; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.util.Size; import android.util.SparseIntArray; import android.view.Surface; import android.view.TextureView; import android.view.View; import android.widget.Button; import android.widget.Toast; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class AndroidCameraApi extends AppCompatActivity { private static final String TAG = "AndroidCameraApi"; // Button cho capture ảnh private Button takePictureButton; // preview camera private TextureView textureView; // kiểm tra trạng thái ORIENTATION của ảnh đầu ra private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); static { ORIENTATIONS.append(Surface.ROTATION_0, 90); ORIENTATIONS.append(Surface.ROTATION_90, 0); ORIENTATIONS.append(Surface.ROTATION_180, 270); ORIENTATIONS.append(Surface.ROTATION_270, 180); } private String cameraId; protected CameraDevice cameraDevice; protected CameraCaptureSession cameraCaptureSessions; protected CaptureRequest captureRequest; protected CaptureRequest.Builder captureRequestBuilder; private Size imageDimension; private ImageReader imageReader; // LƯU RA FILE private File file; private static final int REQUEST_CAMERA_PERMISSION = 200; private boolean mFlashSupported; private Handler mBackgroundHandler; private HandlerThread mBackgroundThread; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_android_camera_api); textureView = (TextureView) findViewById(R.id.texture); assert textureView != null; textureView.setSurfaceTextureListener(textureListener); takePictureButton = (Button) findViewById(R.id.btn_takepicture); assert takePictureButton != null; takePictureButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { takePicture(); } }); } TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int awidth, int height) { // Open camera khi ready openCamera(); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int awidth, int height) { // Transform you image captured size according to the surface awidth and height, và thay đổi kích thước ảnh } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { return false; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surface) { } }; private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() { @Override public void onOpened(CameraDevice camera) { // Camera opened Log.e(TAG, "onOpened"); cameraDevice = camera; createCameraPreview(); } @Override public void onDisconnected(CameraDevice camera) { cameraDevice.close(); } @Override public void onError(CameraDevice camera, int error) { cameraDevice.close(); cameraDevice = null; } }; // Thực hiển việc capture ảnh thông qua CAMERACAPTURESESSION final CameraCaptureSession.CaptureCallback captureCallbackListener = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) { super.onCaptureCompleted(session, request, result); Toast.makeText(AndroidCameraApi.this, "Saved:" + file, Toast.LENGTH_SHORT).show(); createCameraPreview(); } }; protected void startBackgroundThread() { mBackgroundThread = new HandlerThread("Camera Background"); mBackgroundThread.start(); mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); } protected void stopBackgroundThread() { mBackgroundThread.quitSafely(); try { mBackgroundThread.join(); mBackgroundThread = null; mBackgroundHandler = null; } catch (InterruptedException e) { e.printStackTrace(); } } protected void takePicture() { if(null == cameraDevice) { Log.e(TAG, "cameraDevice is null"); return; } CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); try { CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraDevice.getId()); Size[] jpegSizes = null; if (characteristics != null) { jpegSizes = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputSizes(ImageFormat.JPEG); } // CAPTURE IMAGE với tuỳ chỉnh kích thước int awidth = 640; int height = 480; if (jpegSizes != null && 0 < jpegSizes.length) { awidth = jpegSizes[0].getWidth(); height = jpegSizes[0].getHeight(); } ImageReader reader = ImageReader.newInstance(awidth, height, ImageFormat.JPEG, 1); List<Surface> outputSurfaces = new ArrayList<Surface>(2); outputSurfaces.add(reader.getSurface()); outputSurfaces.add(new Surface(textureView.getSurfaceTexture())); final CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); captureBuilder.addTarget(reader.getSurface()); captureBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); // kiểm tra orientation tuỳ thuộc vào mỗi device khác nhau như có nói bên trên int rotation = getWindowManager().getDefaultDisplay().getRotation(); captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation)); final File file = new File(Environment.getExternalStorageDirectory()+"/pic.jpg"); ImageReader.OnImageAvailableListener readerListener = new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { Image image = null; try { image = reader.acquireLatestImage(); ByteBuffer buffer = image.getPlanes()[0].getBuffer(); byte[] bytes = new byte[buffer.capacity()]; buffer.get(bytes); save(bytes); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (image != null) { image.close(); } } } // Lưu ảnh private void save(byte[] bytes) throws IOException { OutputStream output = null; try { output = new FileOutputStream(file); output.write(bytes); } finally { if (null != output) { output.close(); } } } }; reader.setOnImageAvailableListener(readerListener, mBackgroundHandler); final CameraCaptureSession.CaptureCallback captureListener = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) { super.onCaptureCompleted(session, request, result); Toast.makeText(AndroidCameraApi.this, "Saved:" + file, Toast.LENGTH_SHORT).show(); createCameraPreview(); } }; cameraDevice.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() { @Override public void onConfigured(CameraCaptureSession session) {