Computer >> Máy Tính >  >> Hệ thống >> Android

Android Camera2 - Cách sử dụng API Camera2 để chụp ảnh và quay video

Tất cả chúng ta đều sử dụng máy ảnh trên điện thoại của mình và chúng ta sử dụng nó một cách dễ dàng. Thậm chí có một số ứng dụng đã tích hợp camera như một tính năng.

Ở một đầu, có một cách tiêu chuẩn để tương tác với máy ảnh. Mặt khác, có một cách để tùy chỉnh tương tác của bạn với máy ảnh. Sự phân biệt này là một điều quan trọng cần thực hiện. Và đó là lúc Camera2 xuất hiện.

Camera2 là gì?

Mặc dù nó đã có sẵn từ API cấp 21, nhưng API Camera2 phải là một trong những phần phức tạp hơn mà các nhà phát triển kiến ​​trúc phải đối phó.

API này và người tiền nhiệm của nó đã được đưa ra để các nhà phát triển có thể khai thác sức mạnh của việc tương tác với camera bên trong ứng dụng của họ.

Tương tự như cách tương tác với micrô hoặc âm lượng của thiết bị, API Camera2 cung cấp cho bạn các công cụ để tương tác với camera của thiết bị.

Nói chung, nếu bạn muốn sử dụng API Camera2, nó có thể không chỉ là chụp ảnh hoặc quay video. Điều này là do API cho phép bạn kiểm soát sâu máy ảnh bằng cách hiển thị các lớp khác nhau sẽ cần được định cấu hình cho mỗi thiết bị cụ thể.

Ngay cả khi bạn đã xử lý máy ảnh trước đây, đó là một sự thay đổi mạnh mẽ so với API máy ảnh cũ, đến nỗi bạn cũng có thể quên tất cả những gì bạn biết.

Có rất nhiều tài nguyên cố gắng giới thiệu cách sử dụng API này một cách trực tiếp, nhưng một số tài nguyên trong số đó có thể đã lỗi thời và một số tài nguyên không thể hiện toàn bộ bức tranh.

Vì vậy, thay vì cố gắng tự mình điền vào những phần còn thiếu, bài viết này (hy vọng) sẽ là điểm dừng duy nhất để bạn tương tác với API Camera2.

Các trường hợp sử dụng của Camera2

Trước khi chúng ta đi sâu vào bất cứ điều gì, điều quan trọng là phải hiểu rằng nếu bạn chỉ muốn sử dụng máy ảnh để chụp ảnh hoặc quay video, bạn không cần phải bận tâm đến API Camera2.

Lý do chính để sử dụng API Camera2 là nếu ứng dụng của bạn yêu cầu một số tương tác tùy chỉnh với máy ảnh hoặc chức năng của nó.

Nếu bạn quan tâm đến việc thực hiện cái trước thay vì cái sau, tôi khuyên bạn nên truy cập tài liệu sau của Google:

  1. Chụp ảnh
  2. Quay video

Ở đó, bạn sẽ tìm thấy tất cả các bước cần thiết bạn cần thực hiện để chụp những bức ảnh và video tuyệt vời bằng máy ảnh của mình. Nhưng trong bài viết này, trọng tâm chính sẽ là cách sử dụng Camera2.

Bây giờ, có một số thứ chúng tôi cần thêm vào tệp kê khai của mình:

Quyền đối với máy ảnh:

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

Tính năng máy ảnh:

<uses-feature android:name="android.hardware.camera" />

Bạn sẽ phải kiểm tra xem quyền đối với máy ảnh đã được cấp hay chưa, nhưng vì chủ đề này đã được đề cập rộng rãi nên chúng tôi sẽ không giải quyết vấn đề đó trong bài viết này.

Cách thiết lập Thành phần API Camera2

API Camera2 giới thiệu một số giao diện và lớp mới. Hãy chia nhỏ từng thứ để chúng ta có thể hiểu rõ hơn về cách sử dụng chúng.

Android Camera2 - Cách sử dụng API Camera2 để chụp ảnh và quay video
Xem tất cả các thành phần đó

Trước hết, chúng ta sẽ bắt đầu với TextureView.

Thành phần Camera2 TextureView

TextureView là một thành phần giao diện người dùng mà bạn sử dụng để hiển thị một luồng nội dung (nghĩ đến video). Chúng ta cần sử dụng TextureView để hiển thị nguồn cấp dữ liệu từ máy ảnh, cho dù đó là bản xem trước hay trước khi chụp ảnh / quay video.

Hai thuộc tính quan trọng để sử dụng liên quan đến TextureView là:

  • Trường SurfaceTexture
  • Giao diện SurfaceTextureListener

Đầu tiên là nơi nội dung sẽ được hiển thị và thứ hai có bốn lệnh gọi lại:

  1. onSurfaceTextureAvailable
  2. onSurfaceTextureSizeChanged
  3. onSurfaceTextureUpdated
  4. onSurfaceTextureDestroyed
private val surfaceTextureListener = object : TextureView.SurfaceTextureListener {
        override fun onSurfaceTextureAvailable(texture: SurfaceTexture, width: Int, height: Int) {

        }
        override fun onSurfaceTextureSizeChanged(texture: SurfaceTexture, width: Int, height: Int) {
        
        }
        
        override fun onSurfaceTextureDestroyed(texture: SurfaceTexture) {
           
        }
        override fun onSurfaceTextureUpdated(texture: SurfaceTexture) {
          
        }
}

Lần gọi lại đầu tiên rất quan trọng khi sử dụng máy ảnh. Điều này là do chúng tôi muốn được thông báo khi SurfaceTexture khả dụng để chúng tôi có thể bắt đầu hiển thị nguồn cấp dữ liệu trên đó.

Hãy lưu ý rằng chỉ khi TextureView được gắn vào một cửa sổ thì nó mới khả dụng.

Tương tác với máy ảnh đã thay đổi so với API trước đó. Bây giờ, chúng ta có CameraManager. Đây là một dịch vụ hệ thống cho phép chúng ta tương tác với các đối tượng CameraDevice.

Các phương pháp bạn muốn chú ý là:

  • openCamera
  • getCameraCharacteristics
  • getCameraIdList

Sau khi chúng tôi biết rằng TextureView đã có và sẵn sàng, chúng tôi cần gọi openCamera để mở kết nối với máy ảnh. Phương thức này có ba đối số:

  1. CameraId - Chuỗi
  2. CameraDevice.StateCallback
  3. Người xử lý

Đối số CameraId cho biết máy ảnh nào chúng ta muốn kết nối. Trên điện thoại của bạn chủ yếu có hai camera, mặt trước và mặt sau. Mỗi cái đều có id duy nhất của riêng nó. Thông thường, nó là số không hoặc số một.

Làm cách nào để lấy id camera? Chúng tôi sử dụng phương thức getCamerasIdList của CameraManager. Nó sẽ trả về một mảng loại chuỗi của tất cả các id camera được xác định từ thiết bị.

val cameraManager: CameraManager = getSystemService(Context.CAMERA_SERVICE) as CameraManager
val cameraIds: Array<String> = cameraManager.cameraIdList
var cameraId: String = ""
for (id in cameraIds) {
    val cameraCharacteristics = cameraManager.getCameraCharacteristics(id)
    //If we want to choose the rear facing camera instead of the front facing one
    if (cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) 
      continue
    }
    
    val previewSize = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!.getOutputSizes(ImageFormat.JPEG).maxByOrNull { it.height * it.width }!!
    val imageReader = ImageReader.newInstance(previewSize.width, previewSize.height, ImageFormat.JPEG, 1)
    imageReader.setOnImageAvailableListener(onImageAvailableListener, backgroundHandler)
    cameraId = id
}

Các đối số tiếp theo là các lệnh gọi lại trạng thái camera sau khi chúng tôi cố gắng mở nó. Nếu bạn nghĩ về nó, chỉ có thể có một số kết quả cho hành động này:

  • Máy ảnh quản lý để mở thành công
  • Máy ảnh ngắt kết nối
  • Một số lỗi xảy ra

Và đó là những gì bạn sẽ tìm thấy bên trong CameraDevice.StateCallback:

 private val cameraStateCallback = object : CameraDevice.StateCallback() {
        override fun onOpened(camera: CameraDevice) {
           
        }

        override fun onDisconnected(cameraDevice: CameraDevice) {
           
        }

        override fun onError(cameraDevice: CameraDevice, error: Int) {
            val errorMsg = when(error) {
                ERROR_CAMERA_DEVICE -> "Fatal (device)"
                ERROR_CAMERA_DISABLED -> "Device policy"
                ERROR_CAMERA_IN_USE -> "Camera in use"
                ERROR_CAMERA_SERVICE -> "Fatal (service)"
                ERROR_MAX_CAMERAS_IN_USE -> "Maximum cameras in use"
                else -> "Unknown"
            }
            Log.e(TAG, "Error when trying to connect camera $errorMsg")
        }
    }

Đối số thứ ba đề cập đến nơi công việc này sẽ xảy ra. Vì chúng tôi không muốn chiếm luồng chính, nên tốt hơn là thực hiện công việc này trong nền.

Đó là lý do tại sao chúng ta cần phải chuyển một Người xử lý cho nó. Sẽ là khôn ngoan nếu thể hiện trình xử lý này được khởi tạo bằng một chuỗi do chúng tôi chọn để chúng tôi có thể ủy quyền công việc cho nó.

private lateinit var backgroundHandlerThread: HandlerThread
private lateinit var backgroundHandler: Handler

 private fun startBackgroundThread() {
    backgroundHandlerThread = HandlerThread("CameraVideoThread")
    backgroundHandlerThread.start()
    backgroundHandler = Handler(
        backgroundHandlerThread.looper)
}

private fun stopBackgroundThread() {
    backgroundHandlerThread.quitSafely()
    backgroundHandlerThread.join()
}

Với mọi thứ chúng tôi đã làm, giờ đây chúng tôi có thể gọi openCamera:

cameraManager.openCamera(cameraId, cameraStateCallback,backgroundHandler)

Sau đó, trong onOpened gọi lại, chúng tôi có thể bắt đầu xử lý logic về cách trình bày nguồn cấp dữ liệu máy ảnh cho người dùng thông qua TextureView.

Android Camera2 - Cách sử dụng API Camera2 để chụp ảnh và quay video
Ảnh của Markus Spiske trên Unsplash

Cách hiển thị bản xem trước của nguồn cấp dữ liệu

Chúng tôi có máy ảnh (cameraDevice) và TextureView của chúng tôi để hiển thị nguồn cấp dữ liệu. Nhưng chúng tôi cần kết nối chúng với nhau để có thể hiển thị bản xem trước của nguồn cấp dữ liệu.

Để làm điều đó, chúng tôi sẽ sử dụng thuộc tính SurfaceTexture của TextureView và chúng tôi sẽ xây dựng CaptureRequest.

val surfaceTexture : SurfaceTexture? = textureView.surfaceTexture // 1

val cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId) //2
val previewSize = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
  .getOutputSizes(ImageFormat.JPEG).maxByOrNull { it.height * it.width }!!

surfaceTexture?.setDefaultBufferSize(previewSize.width, previewSize.height) //3

val previewSurface: Surface = Surface(surfaceTexture)

captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) //4
captureRequestBuilder.addTarget(previewSurface) //5

cameraDevice.createCaptureSession(listOf(previewSurface, imageReader.surface), captureStateCallback, null) //6
Tạo bản xem trước

Trong đoạn mã trên, đầu tiên chúng ta lấy surfaceTexture từ TextureView của chúng ta. Sau đó, chúng tôi sử dụng đối tượng cameraCharacteristics để lấy danh sách tất cả các kích thước đầu ra. Để có được kích thước mong muốn, chúng tôi đặt nó cho surfaceTexture.

Tiếp theo, chúng tôi tạo một captureRequest để chuyển vào TEMPLATE_PREVIEW . Chúng tôi thêm bề mặt đầu vào của mình vào captureRequest.

Cuối cùng, chúng tôi bắt đầu một captureSession với các bề mặt đầu vào và đầu ra của chúng tôi, captureStateCallback và chuyển vào null cho trình xử lý

Vậy captureStateCallback này là gì? Nếu bạn nhớ sơ đồ từ đầu bài viết này, thì đó là một phần của CameraCaptureSession mà chúng ta đang bắt đầu. Đối tượng này theo dõi tiến trình của captureRequest với các lệnh gọi lại sau:

  • onConfigured
  • onConfigureFailed
private val captureStateCallback = object : CameraCaptureSession.StateCallback() {
        override fun onConfigureFailed(session: CameraCaptureSession) {
            
        }
        override fun onConfigured(session: CameraCaptureSession) {
         
        }
}

Khi cameraCaptureSession được cấu hình thành công, chúng tôi đặt yêu cầu lặp lại cho phiên để cho phép chúng tôi hiển thị bản xem trước liên tục.

Để làm điều đó, chúng tôi sử dụng đối tượng phiên mà chúng tôi nhận được trong lệnh gọi lại:

 session.setRepeatingRequest(captureRequestBuilder.build(), null, backgroundHandler)

Bạn sẽ nhận ra đối tượng captureRequestBuilder mà chúng ta đã tạo trước đó làm đối số đầu tiên cho phương thức này. Chúng tôi thực hiện phương thức xây dựng để tham số cuối cùng được truyền vào là CaptureRequest.

Đối số thứ hai là trình nghe CameraCaptureSession.captureCallback, nhưng vì chúng tôi không muốn làm bất cứ điều gì với các hình ảnh đã chụp (vì đây là bản xem trước) nên chúng tôi chuyển sang giá trị rỗng.

Đối số thứ ba là một trình xử lý và ở đây chúng tôi sử dụng trình xử lý backgroundHandler của riêng mình. Đây cũng là lý do tại sao chúng tôi đã chuyển bằng null trong phần trước, vì yêu cầu lặp lại sẽ chạy trên chuỗi nền.

Android Camera2 - Cách sử dụng API Camera2 để chụp ảnh và quay video
Ảnh của Dicky Jiang trên Unsplash

Cách Chụp ảnh

Có một bản xem trước trực tiếp của máy ảnh thật tuyệt vời, nhưng hầu hết người dùng có thể sẽ muốn làm điều gì đó với nó. Một số logic mà chúng tôi sẽ viết để chụp ảnh sẽ tương tự như những gì chúng tôi đã làm trong phần trước.

  1. Chúng tôi sẽ tạo một captureRequest
  2. Chúng tôi sẽ sử dụng ImageReader và trình nghe của nó để thu thập ảnh đã chụp
  3. Sử dụng cameraCaptureSession của chúng tôi, chúng tôi sẽ gọi phương thức chụp
val orientations : SparseIntArray = SparseIntArray(4).apply {
    append(Surface.ROTATION_0, 0)
    append(Surface.ROTATION_90, 90)
    append(Surface.ROTATION_180, 180)
    append(Surface.ROTATION_270, 270)
}

val captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)
captureRequestBuilder.addTarget(imageReader.surface)

val rotation = windowManager.defaultDisplay.rotation
captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, orientations.get(rotation))
cameraCaptureSession.capture(captureRequestBuilder.build(), captureCallback, null)
Lần này chúng tôi đang tạo một yêu cầu nắm bắt với TEMPLATE_STILL_CAPTURE

Nhưng ImageReader này là gì? Vâng, ImageReader cung cấp quyền truy cập vào dữ liệu hình ảnh được hiển thị trên một bề mặt. Trong trường hợp của chúng tôi, nó là bề mặt của TextureView.

Nếu bạn nhìn vào đoạn mã từ phần trước, bạn sẽ nhận thấy rằng chúng tôi đã xác định một ImageReader ở đó.

val cameraManager: CameraManager = getSystemService(Context.CAMERA_SERVICE) as CameraManager
val cameraIds: Array<String> = cameraManager.cameraIdList
var cameraId: String = ""
for (id in cameraIds) {
    val cameraCharacteristics = cameraManager.getCameraCharacteristics(id)
    //If we want to choose the rear facing camera instead of the front facing one
    if (cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) 
      continue
    }
    
    val previewSize = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!.getOutputSizes(ImageFormat.JPEG).maxByOrNull { it.height * it.width }!!
    val imageReader = ImageReader.newInstance(previewSize.width, previewSize.height, ImageFormat.JPEG, 1)
    imageReader.setOnImageAvailableListener(onImageAvailableListener, backgroundHandler)
    cameraId = id
}
Dòng thông báo 12 - 14

Như bạn có thể thấy ở trên, chúng tôi khởi tạo ImageReader bằng cách truyền chiều rộng và chiều cao, định dạng hình ảnh mà chúng tôi muốn có hình ảnh của mình và số lượng hình ảnh mà nó có thể chụp được.

Một thuộc tính mà lớp ImageReader có là một trình lắng nghe được gọi là onImageAvailableListener. Trình nghe này sẽ được kích hoạt sau khi một bức ảnh được chụp (vì chúng tôi đã chuyển vào bề mặt của nó làm nguồn đầu ra cho yêu cầu chụp của chúng tôi).

val onImageAvailableListener = object: ImageReader.OnImageAvailableListener{
        override fun onImageAvailable(reader: ImageReader) {
            val image: Image = reader.acquireLatestImage()
        }
    }

⚠️ Đảm bảo đóng ảnh sau khi xử lý, nếu không bạn sẽ không thể chụp ảnh khác.

Android Camera2 - Cách sử dụng API Camera2 để chụp ảnh và quay video
Ảnh của Jakob Owens trên Unsplash

Cách quay video

Để quay video, chúng ta cần tương tác với một đối tượng mới có tên là MediaRecorder. Đối tượng ghi media chịu trách nhiệm ghi lại âm thanh và video và chúng tôi sẽ sử dụng nó để làm việc đó.

Trước khi làm bất cứ điều gì, chúng ta cần thiết lập trình ghi phương tiện. Có nhiều cấu hình khác nhau cần xử lý và chúng phải theo đúng thứ tự nếu không sẽ có các ngoại lệ .

Dưới đây là ví dụ về một loạt các cấu hình sẽ cho phép chúng tôi quay video (không có âm thanh).

fun setupMediaRecorder(width: Int, height: Int) {
  val mediaRecorder: MediaRecorder = MediaRecorder()
  mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE)
  mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
  mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264)
  mediaRecorder.setVideoSize(videoSize.width, videoSize.height)
  mediaRecorder.setVideoFrameRate(30)
  mediaRecorder.setOutputFile(PATH_TO_FILE)
  mediaRecorder.setVideoEncodingBitRate(10_000_000)
  mediaRecorder.prepare()
}
Thứ tự của các bộ thiết lập rất quan trọng

Chú ý đến setOutputFile vì nó mong đợi một đường dẫn đến tệp sẽ lưu trữ video của chúng tôi. Khi kết thúc việc đặt tất cả các cấu hình này, chúng ta cần gọi chuẩn bị.

Lưu ý rằng mediaRecorder cũng có một phương thức bắt đầu và chúng ta phải gọi phương thức chuẩn bị trước khi gọi nó.

Sau khi thiết lập mediaRecoder, chúng tôi cần tạo một yêu cầu chụp và một phiên chụp.

fun startRecording() {
        val surfaceTexture : SurfaceTexture? = textureView.surfaceTexture
        surfaceTexture?.setDefaultBufferSize(previewSize.width, previewSize.height)
        val previewSurface: Surface = Surface(surfaceTexture)
        val recordingSurface = mediaRecorder.surface
        captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD)
        captureRequestBuilder.addTarget(previewSurface)
        captureRequestBuilder.addTarget(recordingSurface)

        cameraDevice.createCaptureSession(listOf(previewSurface, recordingSurface), captureStateVideoCallback, backgroundHandler)
    }

Tương tự như thiết lập bản xem trước hoặc chụp ảnh, chúng ta phải xác định bề mặt đầu vào và đầu ra của mình.

Ở đây chúng tôi đang tạo một đối tượng Surface từ surfaceTexture của TextureView và cũng lấy bề mặt từ trình ghi phương tiện. Chúng tôi sẽ vượt qua TEMPLATE_RECORD giá trị khi tạo một yêu cầu nắm bắt.

CaptureStateVideoCallback của chúng tôi thuộc cùng loại mà chúng tôi đã sử dụng cho ảnh tĩnh, nhưng bên trong lệnh gọi lại onConfigured, chúng tôi gọi là phương thức bắt đầu của trình ghi phương tiện.

val captureStateVideoCallback = object : CameraCaptureSession.StateCallback() {
      override fun onConfigureFailed(session: CameraCaptureSession) {
         
      }
      
      override fun onConfigured(session: CameraCaptureSession) {
          session.setRepeatingRequest(captureRequestBuilder.build(), null, backgroundHandler)
          mediaRecorder.start()
      }
  }
Ở đây chúng tôi cũng đang đặt một yêu cầu lặp lại vì chúng tôi muốn quay một video liên tục

Bây giờ chúng ta đang quay video, nhưng làm cách nào để dừng quay? Đối với điều đó, chúng tôi sẽ sử dụng các phương pháp dừng và đặt lại trên đối tượng mediaRecorder:

mediaRecorder.stop()
mediaRecorder.reset()

Kết luận

Đó là rất nhiều để xử lý. Vì vậy, nếu bạn đã đến đây, xin chúc mừng! Không có cách nào để giải quyết vấn đề này - chỉ bằng cách nhúng tay vào mã, bạn sẽ bắt đầu hiểu cách mọi thứ kết nối với nhau.

Chúng tôi khuyến khích bạn xem tất cả các mã có trong bài viết này bên dưới:

MediumArticles / Camrea2API at master · TomerPacific / MediumArticles Một kho chứa mã liên quan đến các bài báo Medium khác nhau mà tôi đã viết - MediumArticles / Camrea2API ở master · TomerPacific / MediumArticles Android Camera2 - Cách sử dụng API Camera2 để chụp ảnh và quay video TomerPacificGitHub Android Camera2 - Cách sử dụng API Camera2 để chụp ảnh và quay video

Hãy nhớ rằng đây chỉ là phần nổi của tảng băng chìm khi nói đến API Camera2. Bạn có thể làm nhiều việc khác như quay video chuyển động chậm, chuyển đổi giữa camera trước và sau, điều khiển tiêu điểm, v.v.