Nếu ứng dụng của bạn dùng lớp Camera
gốc ("Camera1"), lớp này không được dùng nữa kể từ Android 5.0 (API cấp 21), thì bạn nên cập nhật lên API camera Android hiện đại. Android cung cấp CameraX (API camera Jetpack chuẩn và mạnh mẽ) và Camera2 (API khung cấp thấp). Trong phần lớn trường hợp, bạn nên di chuyển ứng dụng của mình sang CameraX. Dưới đây là lý do:
- Dễ sử dụng: CameraX xử lý các chi tiết cấp thấp để bạn không phải quá tập trung vào việc xây dựng trải nghiệm máy ảnh từ đầu, trong khi tập trung nhiều hơn nữa vào việc phân biệt ứng dụng của mình.
- CameraX xử lý tình trạng phân mảnh cho bạn: CameraX giảm chi phí bảo trì dài hạn và mã dành riêng cho thiết bị, mang lại trải nghiệm chất lượng cao hơn cho người dùng. Để biết thêm về điều này, hãy xem bài đăng Better Device Compatibility with CameraX (Cải thiện khả năng tương thích của thiết bị với CameraX) trên blog.
- Tính năng nâng cao: CameraX được thiết kế cẩn thận khiến chức năng nâng cao trở nên đơn giản để tích hợp vào ứng dụng của bạn. Ví dụ: bạn có thể dễ dàng áp dụng hiệu ứng Bokeh, Làm đẹp khuôn mặt, HDR (Dải động cao) và chế độ chụp ban đêm với khả năng làm sáng khi ở điều kiện ánh sáng yếu cho ảnh của bạn với tiện ích của CameraX.
- Khả năng cập nhật: Android phát hành các tính năng mới và bản sửa lỗi cho CameraX suốt cả năm. Bằng cách chuyển sang CameraX, ứng dụng của bạn sẽ nhận được công nghệ mới nhất cho máy ảnh Android với mỗi bản phát hành của CameraX, chứ không chỉ trên các bản phát hành phiên bản Android hằng năm.
Trong hướng dẫn này, bạn sẽ thấy các trường hợp phổ biến cho ứng dụng máy ảnh. Mỗi tình huống sẽ bao gồm cách triển khai Camera1 và CameraX để so sánh.
Trong quá trình di chuyển, đôi khi bạn cần thêm sự linh hoạt để tích hợp với cơ sở mã hiện có. Tất cả mã CameraX trong hướng dẫn này đều có cách triển khai CameraController
phù hợp nếu bạn muốn có cách sử dụng CameraX đơn giản nhất cũng như cách triển khai CameraProvider
phù hợp nếu bạn cần sự linh hoạt hơn. Để giúp bạn quyết định xem cách triển khai nào phù hợp với mình, sau đây là các lợi ích của từng cách:
CameraController |
CameraProvider |
Yêu cầu ít mã thiết lập | Cho phép có nhiều quyền kiểm soát hơn |
Việc cho phép CameraX xử lý nhiều quy trình thiết lập hơn có nghĩa là các chức năng như nhấn để lấy nét và chụm để thu phóng sẽ tự động hoạt động |
Vì nhà phát triển ứng dụng sẽ xử lý việc thiết lập nên sẽ có nhiều cơ hội hơn để tuỳ chỉnh cấu hình, chẳng hạn như bật tính năng xoay hình ảnh đầu ra hoặc đặt định dạng hình ảnh đầu ra trong ImageAnalysis
|
Việc yêu cầu PreviewView cho bản xem trước của máy ảnh cho phép CameraX tích hợp hai đầu liền mạch, như trong quá trình tích hợp Bộ công cụ học máy của chúng tôi. Quá trình này có thể liên kết toạ độ kết quả mô hình học máy (như hộp giới hạn khuôn mặt) trực tiếp vào toạ độ bản xem trước
|
Khả năng sử dụng `Surface` tuỳ chỉnh cho bản xem trước của máy ảnh giúp cải thiện tính linh hoạt, chẳng hạn như sử dụng mã `Surface` hiện có, đây có thể là dữ liệu đầu vào cho các phần khác của ứng dụng |
Nếu bạn gặp khó khăn khi di chuyển, hãy liên hệ với chúng tôi qua Nhóm thảo luận về CameraX.
Trước khi bạn di chuyển
So sánh mức sử dụng CameraX với Camera1
Mặc dù mã có thể khác nhau, nhưng các khái niệm cơ bản trong Camera1 và CameraX rất giống nhau. CameraX tóm tắt chức năng phổ biến của máy ảnh thành các trường hợp sử dụng, do đó, nhiều tác vụ mà nhà phát triển để lại trong Camera1 sẽ do CameraX tự động xử lý. Có 4 UseCase
trong CameraX mà bạn có thể dùng cho nhiều nhiệm vụ của máy ảnh: Preview
, ImageCapture
, VideoCapture
và ImageAnalysis
.
Một ví dụ về việc CameraX xử lý thông tin chi tiết cấp thấp cho nhà phát triển là ViewPort
được dùng chung giữa các UseCase
đang hoạt động. Điều này đảm bảo rằng tất cả các UseCase
đều thấy chính xác cùng một điểm ảnh.
Trong Camera1, bạn phải tự quản lý các thông tin chi tiết này và cung cấp sự thay đổi về tỷ lệ khung hình trên các màn hình và cảm biến của máy ảnh trên thiết bị. Tuy nhiên, việc đảm bảo bản xem trước khớp với ảnh và video đã chụp có thể rất khó khăn.
Một ví dụ khác là CameraX tự động xử lý các lệnh gọi lại Lifecycle
trên thực thể Lifecycle
mà bạn truyền. Điều này có nghĩa là CameraX xử lý kết nối của ứng dụng với máy ảnh trong toàn bộ vòng đời hoạt động trên Android, bao gồm các trường hợp sau: đóng máy ảnh khi ứng dụng chuyển đến chế độ nền; xoá bản xem trước của máy ảnh khi màn hình không còn yêu cầu hiển thị bản xem trước; và tạm dừng bản xem trước của máy ảnh khi một hoạt động khác ưu tiên nền trước, chẳng hạn như cuộc gọi video đến.
Cuối cùng, CameraX xử lý việc xoay và chia tỷ lệ mà bạn không cần thêm mã nào. Trong trường hợp Activity
có hướng được mở khoá, việc thiết lập UseCase
sẽ được thực hiện mỗi khi xoay thiết bị, vì hệ thống sẽ huỷ và tái tạo Activity
khi hướng thay đổi. Kết quả là lần nào UseCases
cũng được đặt thành chế độ xoay mục tiêu cho phù hợp với hướng của màn hình theo mặc định.
Đọc thêm về chế độ xoay trong CameraX.
Trước khi đi vào chi tiết, dưới đây là thông tin tổng quan về UseCase
của CameraX và mối liên hệ của một ứng dụng Camera1. (Các khái niệm của CameraX đều có màu xanh dương và khái niệm của Camera1 có màu xanh lục.)
CameraX |
|||
Cấu hình CameraController/CameraProvider | |||
↓ | ↓ | ↓ | ↓ |
Xem trước | ImageCapture | VideoCapture | ImageAnalysis |
⁞ | ⁞ | ⁞ | ⁞ |
Quản lý nền tảng xem trước và thiết lập trên máy ảnh | Đặt PictureCallback và gọi takePicture() trên máy ảnh | Quản lý cấu hình của máy ảnh và MediaRecorder theo thứ tự cụ thể | Mã phân tích tuỳ chỉnh được tạo dựa trên nền tảng xem trước |
↑ | ↑ | ↑ | ↑ |
Mã dành riêng cho thiết bị | |||
↑ | |||
Quản lý việc điều chỉnh theo lỷ lệ và xoay thiết bị | |||
↑ | |||
Quản lý phiên máy ảnh (Lựa chọn máy ảnh, Quản lý vòng đời) | |||
Camera1 |
Khả năng tương thích và hiệu suất khi dùng CameraX
CameraX hỗ trợ các thiết bị chạy Android 5.0 (API cấp 21) trở lên. Số thiết bị này tương đương với hơn 98% số thiết bị Android hiện có. CameraX được xây dựng để tự động xử lý điểm khác biệt giữa các thiết bị, giúp giảm nhu cầu sử dụng mã dành riêng cho thiết bị trong ứng dụng của bạn. Hơn nữa, chúng tôi cũng kiểm thử hơn 150 thiết bị thực trên tất cả các phiên bản Android kể từ phiên bản 5.0 trong Phòng thử nghiệm CameraX. Bạn có thể xem lại danh sách đầy đủ các thiết bị hiện có trong Phòng thử nghiệm.
CameraX sử dụng Executor
để điều khiển ngăn xếp máy ảnh. Bạn có thể đặt trình thực thi của mình trên CameraX nếu ứng dụng của bạn có các yêu cầu cụ thể về việc phân luồng. Nếu không, CameraX sẽ tạo và sử dụng Executor
nội bộ mặc định được tối ưu hoá. Nhiều API nền tảng là cơ sở CameraX được xây dựng yêu cầu chặn giao tiếp liên quy trình (IPC) với phần cứng đôi khi có thể mất hàng trăm mili giây để phản hồi. Vì lý do này, CameraX chỉ gọi những API này từ các luồng trong nền, giúp đảm bảo luồng chính không bị chặn và giao diện người dùng vẫn hiển thị linh hoạt.
Đọc thêm về luồng.
Nếu thị trường mục tiêu cho ứng dụng của bạn có thiết bị cấp thấp, thì CameraX sẽ cung cấp một cách để giảm thời gian thiết lập bằng trình giới hạn máy ảnh. Vì quá trình kết nối với các thành phần phần cứng có thể làm tiêu hao nhiều thời gian, đặc biệt là trên các thiết bị cấp thấp, nên bạn có thể chỉ định nhóm máy ảnh mà ứng dụng của bạn cần. CameraX chỉ kết nối với các máy ảnh này trong quá trình thiết lập. Ví dụ: Nếu chỉ sử dụng máy ảnh mặt sau, thì ứng dụng có thể thiết lập cấu hình này bằng DEFAULT_BACK_CAMERA
, sau đó CameraX sẽ tránh khởi chạy máy ảnh mặt trước để giảm độ trễ.
Khái niệm phát triển Android
Hướng dẫn này giả định rằng bạn đã làm quen với quá trình phát triển Android. Ngoài những khái niệm cơ bản, dưới đây là một số khái niệm hữu ích cần hiểu trước khi chuyển đến mã bên dưới:
- Tính năng Liên kết khung hiển thị tạo ra một lớp liên kết cho các tệp bố cục XML của bạn, cho phép bạn dễ dàng tham chiếu các khung hiển thị của mình trong các Hoạt động, như được thực hiện trong một số các đoạn mã dưới đây. Có một số điểm khác biệt giữa liên kết khung hiển thị và
findViewById()
(cách tham chiếu đến khung hiển thị trước đó), nhưng trong mã bên dưới, bạn có thể thay thế các dòng liên kết khung hiển thị với lệnh gọifindViewById()
tương tự. - Coroutine không đồng bộ là một mẫu thiết kế cho cơ chế xử lý đồng thời được thêm vào Kotlin 1.3, có thể dùng để xử lý các phương thức CameraX trả về
ListenableFuture
. Việc này sẽ dễ dàng hơn khi bạn sử dụng thư viện Jetpack đồng thời kể từ phiên bản 1.1.0. Cách thêm coroutine không đồng bộ vào ứng dụng:- Thêm
implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0")
vào tệp Gradle của bạn. - Đặt bất kỳ mã CameraX nào trả về
ListenableFuture
trong một khốilaunch
hoặc hàm tạm ngưng. - Thêm lệnh gọi
await()
vào lệnh gọi hàm trả vềListenableFuture
. - Để hiểu rõ hơn về cách hoạt động của coroutine, hãy xem hướng dẫn Bắt đầu coroutine.
- Thêm
Di chuyển các trường hợp phổ biến
Phần này giải thích cách di chuyển các trường hợp phổ biến từ Camera1 sang CameraX.
Mỗi tình huống bao gồm cách triển khai Camera1, cách triển khai CameraProvider
CameraX và cách triển khai CameraController
CameraX.
Chọn camera
Trong ứng dụng máy ảnh, một trong những điều đầu tiên bạn nên cung cấp đó là cách chọn nhiều loại máy ảnh.
Camera1
Trong Camera1, bạn có thể gọi Camera.open()
mà không cần có tham số để mở máy ảnh mặt sau đầu tiên hoặc bạn có thể truyền mã nhận dạng bằng số nguyên cho máy ảnh mà bạn muốn mở. Sau đây là một ví dụ về cách thực hiện:
// Camera1: select a camera from id. // Note: opening the camera is a non-trivial task, and it shouldn't be // called from the main thread, unlike CameraX calls, which can be // on the main thread since CameraX kicks off background threads // internally as needed. private fun safeCameraOpen(id: Int): Boolean { return try { releaseCameraAndPreview() camera = Camera.open(id) true } catch (e: Exception) { Log.e(TAG, "failed to open camera", e) false } } private fun releaseCameraAndPreview() { preview?.setCamera(null) camera?.release() camera = null }
CameraX: CameraController
Trong CameraX, việc lựa chọn máy ảnh do lớp CameraSelector
xử lý. CameraX giúp bạn dễ dàng sử dụng máy ảnh mặc định. Bạn có thể chỉ định xem bạn muốn dùng camera trước mặc định hay camera sau mặc định. Hơn nữa, đối tượng CameraControl
của CameraX cho phép bạn dễ dàng đặt mức thu phóng cho ứng dụng, vì vậy, nếu ứng dụng đang chạy trên một thiết bị hỗ trợ máy ảnh logic, thì máy ảnh sẽ chuyển sang ống kính thích hợp.
Dưới đây là mã của CameraX để sử dụng camera sau mặc định bằng CameraController
:
// CameraX: select a camera with CameraController var cameraController = LifecycleCameraController(baseContext) val selector = CameraSelector.Builder() .requireLensFacing(CameraSelector.LENS_FACING_BACK).build() cameraController.cameraSelector = selector
CameraX: CameraProvider
Dưới đây là ví dụ về cách chọn camera trước mặc định bằng CameraProvider
(bạn có thể sử dụng camera trước hoặc sau với CameraController
hoặc CameraProvider
):
// CameraX: select a camera with CameraProvider. // Use await() within a suspend function to get CameraProvider instance. // For more details on await(), see the "Android development concepts" // section above. private suspend fun startCamera() { val cameraProvider = ProcessCameraProvider.getInstance(this).await() // Set up UseCases (more on UseCases in later scenarios) var useCases:Array= ... // Set the cameraSelector to use the default front-facing (selfie) // camera. val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA try { // Unbind UseCases before rebinding. cameraProvider.unbindAll() // Bind UseCases to camera. This function returns a camera // object which can be used to perform operations like zoom, // flash, and focus. var camera = cameraProvider.bindToLifecycle( this, cameraSelector, useCases) } catch(exc: Exception) { Log.e(TAG, "UseCase binding failed", exc) } }) ... // Call startCamera in the setup flow of your app, such as in onViewCreated. override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) ... lifecycleScope.launch { startCamera() } }
Nếu muốn kiểm soát xem máy ảnh nào được chọn, bạn cũng có thể thực hiện việc này trong CameraX nếu sử dụng CameraProvider
bằng cách gọi getAvailableCameraInfos()
. Việc này sẽ cung cấp đối tượng CameraInfo
cho bạn để kiểm tra một số thuộc tính của máy ảnh như isFocusMeteringSupported()
.
Sau đó, bạn có thể chuyển đổi thành CameraSelector
để sử dụng như trong các ví dụ ở trên bằng phương thức CameraInfo.getCameraSelector()
.
Bạn có thể xem thêm thông tin chi tiết về từng máy ảnh bằng cách dùng lớp Camera2CameraInfo
. Gọi getCameraCharacteristic()
bằng khoá cho dữ liệu máy ảnh mà bạn muốn. Hãy xem lớp CameraCharacteristics
để biết danh sách tất cả các khoá mà bạn có thể truy vấn.
Sau đây là ví dụ về cách sử dụng hàm checkFocalLength()
tuỳ chỉnh mà bạn có thể tự xác định:
// CameraX: get a cameraSelector for first camera that matches the criteria // defined in checkFocalLength(). val cameraInfo = cameraProvider.getAvailableCameraInfos() .first { cameraInfo -> val focalLengths = Camera2CameraInfo.from(cameraInfo) .getCameraCharacteristic( CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS ) return checkFocalLength(focalLengths) } val cameraSelector = cameraInfo.getCameraSelector()
Hiển thị bản xem trước
Phần lớn các ứng dụng máy ảnh cần hiển thị nguồn cấp dữ liệu máy ảnh trên màn hình tại điểm nào đó. Với Camera1, bạn cần quản lý chính xác các phương thức gọi lại trong vòng đời cũng như cần xác định việc xoay và điều chỉnh theo tỷ lệ cho bản xem trước của mình.
Ngoài ra, trong Camera1, bạn cần quyết định sử dụng TextureView
hay SurfaceView
làm nền tảng xem trước.
Cả hai tuỳ chọn đều đi kèm với sự đánh đổi và dù trong trường hợp nào, Camera1 sẽ yêu cầu bạn xử lý việc xoay và chia tỷ lệ một cách chính xác. Mặt khác, PreviewView
của CameraX có các biện pháp triển khai cơ bản cho cả TextureView
và SurfaceView
.
CameraX sẽ quyết định cách triển khai phù hợp nhất tuỳ thuộc vào các yếu tố như loại thiết bị và phiên bản Android mà ứng dụng đang chạy. Nếu cách triển khai nào cũng đều tương thích, thì bạn có thể khai báo lựa chọn ưu tiên bằng PreviewView.ImplementationMode
.
Tuỳ chọn COMPATIBLE
dùng TextureView
cho bản xem trước và giá trị PERFORMANCE
sẽ dùng SurfaceView
(khi có thể).
Camera1
Để hiển thị bản xem trước, bạn cần viết lớp Preview
của riêng mình bằng cách triển khai giao diện android.view.SurfaceHolder.Callback
được dùng để truyền dữ liệu hình ảnh từ phần cứng máy ảnh đến ứng dụng. Sau đó, trước khi có thể bắt đầu xem trước hình ảnh trực tiếp, lớp Preview
phải được truyền vào đối tượng Camera
.
// Camera1: set up a camera preview. class Preview( context: Context, private val camera: Camera ) : SurfaceView(context), SurfaceHolder.Callback { private val holder: SurfaceHolder = holder.apply { addCallback(this@Preview) setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS) } override fun surfaceCreated(holder: SurfaceHolder) { // The Surface has been created, now tell the camera // where to draw the preview. camera.apply { try { setPreviewDisplay(holder) startPreview() } catch (e: IOException) { Log.d(TAG, "error setting camera preview", e) } } } override fun surfaceDestroyed(holder: SurfaceHolder) { // Take care of releasing the Camera preview in your activity. } override fun surfaceChanged(holder: SurfaceHolder, format: Int, w: Int, h: Int) { // If your preview can change or rotate, take care of those // events here. Make sure to stop the preview before resizing // or reformatting it. if (holder.surface == null) { return // The preview surface does not exist. } // Stop preview before making changes. try { camera.stopPreview() } catch (e: Exception) { // Tried to stop a non-existent preview; nothing to do. } // Set preview size and make any resize, rotate or // reformatting changes here. // Start preview with new settings. camera.apply { try { setPreviewDisplay(holder) startPreview() } catch (e: Exception) { Log.d(TAG, "error starting camera preview", e) } } } } class CameraActivity : AppCompatActivity() { private lateinit var viewBinding: ActivityMainBinding private var camera: Camera? = null private var preview: Preview? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewBinding = ActivityMainBinding.inflate(layoutInflater) setContentView(viewBinding.root) // Create an instance of Camera. camera = getCameraInstance() preview = camera?.let { // Create the Preview view. Preview(this, it) } // Set the Preview view as the content of the activity. val cameraPreview: FrameLayout = viewBinding.cameraPreview cameraPreview.addView(preview) } }
CameraX: CameraController
Trong CameraX, cả bạn và nhà phát triển đều không cần phải quản lý quá nhiều thứ. Nếu sử dụng CameraController
, bạn cũng phải sử dụng PreviewView
. Điều này có nghĩa là Preview
UseCase
được ngụ ý, giúp quá trình thiết lập trở nên ít hoạt động hơn:
// CameraX: set up a camera preview with a CameraController. class MainActivity : AppCompatActivity() { private lateinit var viewBinding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewBinding = ActivityMainBinding.inflate(layoutInflater) setContentView(viewBinding.root) // Create the CameraController and set it on the previewView. var cameraController = LifecycleCameraController(baseContext) cameraController.bindToLifecycle(this) val previewView: PreviewView = viewBinding.cameraPreview previewView.controller = cameraController } }
CameraX: CameraProvider
Với CameraProvider
của CameraX, bạn không nhất thiết phải sử dụng PreviewView
, nhưng vẫn giúp đơn giản hoá đáng kể việc thiết lập bản xem trước so với Camera1. Đối với mục đích minh hoạ, ví dụ này sử dụng PreviewView
, nhưng bạn có thể viết SurfaceProvider
tuỳ chỉnh để truyền vào setSurfaceProvider()
nếu có nhu cầu phức tạp hơn.
Ở đây, Preview
UseCase
không được ngụ ý như với CameraController
, vì vậy, bạn cần phải thiết lập:
// CameraX: set up a camera preview with a CameraProvider. // Use await() within a suspend function to get CameraProvider instance. // For more details on await(), see the "Android development concepts" // section above. private suspend fun startCamera() { val cameraProvider = ProcessCameraProvider.getInstance(this).await() // Create Preview UseCase. val preview = Preview.Builder() .build() .also { it.setSurfaceProvider( viewBinding.viewFinder.surfaceProvider ) } // Select default back camera. val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA try { // Unbind UseCases before rebinding. cameraProvider.unbindAll() // Bind UseCases to camera. This function returns a camera // object which can be used to perform operations like zoom, // flash, and focus. var camera = cameraProvider.bindToLifecycle( this, cameraSelector, useCases) } catch(exc: Exception) { Log.e(TAG, "UseCase binding failed", exc) } }) ... // Call startCamera() in the setup flow of your app, such as in onViewCreated. override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) ... lifecycleScope.launch { startCamera() } }
Nhấn để lấy nét
Khi bản xem trước của máy ảnh xuất hiện trên màn hình, một tuỳ chọn điều khiển phổ biến là đặt điểm lấy nét khi người dùng nhấn vào bản xem trước.
Camera1
Để triển khai tính năng nhấn để lấy nét trong Camera1, bạn phải tính toán tiêu điểm tối ưu Area
để cho biết Camera
sẽ cố gắng lấy nét ở đâu. Area
này được truyền vào setFocusAreas()
. Ngoài ra, bạn phải đặt chế độ lấy nét tương thích trên Camera
. Vùng lấy nét chỉ có hiệu lực nếu chế độ lấy nét hiện tại là FOCUS_MODE_AUTO
, FOCUS_MODE_MACRO
, FOCUS_MODE_CONTINUOUS_VIDEO
hoặc FOCUS_MODE_CONTINUOUS_PICTURE
.
Mỗi Area
là một hình chữ nhật có trọng số được chỉ định. Trọng số là một giá trị nằm trong khoảng từ 1 đến 1000 và dùng để ưu tiên điểm lấy nét Areas
nếu bạn đặt nhiều tiêu điểm. Ví dụ này chỉ sử dụng một Area
, vì vậy, giá trị trọng số là không đáng kể. Toạ độ của hình chữ nhật nằm trong khoảng từ -1000 đến 1000. Điểm trên bên trái là (-1000, -1000).
Điểm dưới bên phải là (1000, 1000). Hướng là tương đối với hướng của cảm biến, tức là những gì cảm biến nhìn thấy. Hướng không bị ảnh hưởng bởi chế độ xoay hoặc phản chiếu của Camera.setDisplayOrientation()
, vì vậy, bạn cần chuyển đổi toạ độ sự kiện chạm sang toạ độ cảm biến.
// Camera1: implement tap-to-focus. class TapToFocusHandler : Camera.AutoFocusCallback { private fun handleFocus(event: MotionEvent) { val camera = camera ?: return val parameters = try { camera.getParameters() } catch (e: RuntimeException) { return } // Cancel previous auto-focus function, if one was in progress. camera.cancelAutoFocus() // Create focus Area. val rect = calculateFocusAreaCoordinates(event.x, event.y) val weight = 1 // This value's not important since there's only 1 Area. val focusArea = Camera.Area(rect, weight) // Set the focus parameters. parameters.setFocusMode(Parameters.FOCUS_MODE_AUTO) parameters.setFocusAreas(listOf(focusArea)) // Set the parameters back on the camera and initiate auto-focus. camera.setParameters(parameters) camera.autoFocus(this) } private fun calculateFocusAreaCoordinates(x: Int, y: Int) { // Define the size of the Area to be returned. This value // should be optimized for your app. val focusAreaSize = 100 // You must define functions to rotate and scale the x and y values to // be values between 0 and 1, where (0, 0) is the upper left-hand side // of the preview, and (1, 1) is the lower right-hand side. val normalizedX = (rotateAndScaleX(x) - 0.5) * 2000 val normalizedY = (rotateAndScaleY(y) - 0.5) * 2000 // Calculate the values for left, top, right, and bottom of the Rect to // be returned. If the Rect would extend beyond the allowed values of // (-1000, -1000, 1000, 1000), then crop the values to fit inside of // that boundary. val left = max(normalizedX - (focusAreaSize / 2), -1000) val top = max(normalizedY - (focusAreaSize / 2), -1000) val right = min(left + focusAreaSize, 1000) val bottom = min(top + focusAreaSize, 1000) return Rect(left, top, left + focusAreaSize, top + focusAreaSize) } override fun onAutoFocus(focused: Boolean, camera: Camera) { if (!focused) { Log.d(TAG, "tap-to-focus failed") } } }
CameraX: CameraController
CameraController
theo dõi các sự kiện chạm của PreviewView
để tự động xử lý thao tác nhấn để lấy nét. Bạn có thể bật và tắt chế độ nhấn để lấy nét bằng setTapToFocusEnabled()
và kiểm tra giá trị bằng phương thức getter tương ứng isTapToFocusEnabled()
.
Phương thức getTapToFocusState()
sẽ trả về một đối tượng LiveData
để theo dõi các thay đổi đối với trạng thái lấy nét trên CameraController
.
// CameraX: track the state of tap-to-focus over the Lifecycle of a PreviewView, // with handlers you can define for focused, not focused, and failed states. val tapToFocusStateObserver = Observer{ state -> when (state) { CameraController.TAP_TO_FOCUS_NOT_STARTED -> Log.d(TAG, "tap-to-focus init") CameraController.TAP_TO_FOCUS_STARTED -> Log.d(TAG, "tap-to-focus started") CameraController.TAP_TO_FOCUS_FOCUSED -> Log.d(TAG, "tap-to-focus finished (focus successful)") CameraController.TAP_TO_FOCUS_NOT_FOCUSED -> Log.d(TAG, "tap-to-focus finished (focused unsuccessful)") CameraController.TAP_TO_FOCUS_FAILED -> Log.d(TAG, "tap-to-focus failed") } } cameraController.getTapToFocusState().observe(this, tapToFocusStateObserver)
CameraX: CameraProvider
Khi sử dụng CameraProvider
, bạn cần thực hiện bước thiết lập nào đó để chế độ nhấn để lấy nét hoạt động. Ví dụ này giả định bạn đang sử dụng PreviewView
. Nếu không, bạn cần điều chỉnh logic để áp dụng cho Surface
tuỳ chỉnh.
Dưới đây là các bước khi sử dụng PreviewView
:
- Thiết lập trình phát hiện cử chỉ để xử lý các sự kiện nhấn.
- Với sự kiện nhấn, hãy tạo
MeteringPoint
bằngMeteringPointFactory.createPoint()
. - Với
MeteringPoint
, tạoFocusMeteringAction
. - Với đối tượng
CameraControl
trênCamera
(được trả về từbindToLifecycle()
), hãy gọistartFocusAndMetering()
truyền trongFocusMeteringAction
. - (Không bắt buộc) Phản hồi
FocusMeteringResult
. - Đặt trình phát hiện cử chỉ ở chế độ phản hồi với các sự kiện chạm trong
PreviewView.setOnTouchListener()
.
// CameraX: implement tap-to-focus with CameraProvider. // Define a gesture detector to respond to tap events and call // startFocusAndMetering on CameraControl. If you want to use a // coroutine with await() to check the result of focusing, see the // "Android development concepts" section above. val gestureDetector = GestureDetectorCompat(context, object : SimpleOnGestureListener() { override fun onSingleTapUp(e: MotionEvent): Boolean { val previewView = previewView ?: return val camera = camera ?: return val meteringPointFactory = previewView.meteringPointFactory val focusPoint = meteringPointFactory.createPoint(e.x, e.y) val meteringAction = FocusMeteringAction .Builder(meteringPoint).build() lifecycleScope.launch { val focusResult = camera.cameraControl .startFocusAndMetering(meteringAction).await() if (!result.isFocusSuccessful()) { Log.d(TAG, "tap-to-focus failed") } } } } ) ... // Set the gestureDetector in a touch listener on the PreviewView. previewView.setOnTouchListener { _, event -> // See pinch-to-zooom scenario for scaleGestureDetector definition. var didConsume = scaleGestureDetector.onTouchEvent(event) if (!scaleGestureDetector.isInProgress) { didConsume = gestureDetector.onTouchEvent(event) } didConsume }
Chụm để thu phóng
Phóng to và thu nhỏ bản xem trước là một thao tác trực tiếp phổ biến khác đối với bản xem trước của máy ảnh. Khi số lượng máy ảnh trên các thiết bị tăng lên, người dùng cũng kỳ vọng rằng ống kính có tiêu cự tốt nhất sẽ tự động được chọn do việc thu phóng.
Camera1
Có 2 cách để thu phóng bằng Camera1. Phương thức Camera.startSmoothZoom()
tạo hiệu ứng ảnh động từ mức thu phóng hiện tại đến mức thu phóng mà bạn chuyển vào. Phương thức Camera.Parameters.setZoom()
sẽ chuyển thẳng đến mức thu phóng mà bạn chuyển vào. Trước khi sử dụng một trong hai phương thức này, hãy gọi lần lượt isSmoothZoomSupported()
hoặc isZoomSupported()
để đảm bảo các phương thức thu phóng liên quan mà bạn cần có trong Máy ảnh.
Để triển khai tính năng chụm để thu phóng, ví dụ này sử dụng setZoom()
vì trình nghe thao tác chạm trên nền tảng xem trước liên tục kích hoạt các sự kiện khi cử chỉ chụm xảy ra, vì vậy, tính năng này sẽ cập nhật mức thu phóng ngay lập tức. Lớp ZoomTouchListener
được định nghĩa ở bên dưới và lớp này nên được đặt làm lệnh gọi lại cho trình nghe thao tác chạm của giao diện xem trước.
// Camera1: implement pinch-to-zoom. // Define a scale gesture detector to respond to pinch events and call // setZoom on Camera.Parameters. val scaleGestureDetector = ScaleGestureDetector(context, object : ScaleGestureDetector.OnScaleGestureListener { override fun onScale(detector: ScaleGestureDetector): Boolean { val camera = camera ?: return false val parameters = try { camera.parameters } catch (e: RuntimeException) { return false } // In case there is any focus happening, stop it. camera.cancelAutoFocus() // Set the zoom level on the Camera.Parameters, and set // the Parameters back onto the Camera. val currentZoom = parameters.zoom parameters.setZoom(detector.scaleFactor * currentZoom) camera.setParameters(parameters) return true } } ) // Define a View.OnTouchListener to attach to your preview view. class ZoomTouchListener : View.OnTouchListener { override fun onTouch(v: View, event: MotionEvent): Boolean = scaleGestureDetector.onTouchEvent(event) } // Set a ZoomTouchListener to handle touch events on your preview view // if zoom is supported by the current camera. if (camera.getParameters().isZoomSupported()) { view.setOnTouchListener(ZoomTouchListener()) }
CameraX: CameraController
Tương tự như thao tác nhấn để lấy nét, CameraController
theo dõi các sự kiện chạm của PreviewView để tự động xử lý quá trình chụm để thu phóng. Bạn có thể bật và tắt tính năng chụm để thu phóng bằng setPinchToZoomEnabled()
và kiểm tra giá trị bằng phương thức getter isPinchToZoomEnabled()
tương ứng.
Phương thức getZoomState()
sẽ trả về đối tượng LiveData
để theo dõi các thay đổi đối với ZoomState
trên CameraController
.
// CameraX: track the state of pinch-to-zoom over the Lifecycle of // a PreviewView, logging the linear zoom ratio. val pinchToZoomStateObserver = Observer{ state -> val zoomRatio = state.getZoomRatio() Log.d(TAG, "ptz-zoom-ratio $zoomRatio") } cameraController.getZoomState().observe(this, pinchToZoomStateObserver)
CameraX: CameraProvider
Để tính năng chụm để thu phóng hoạt động với CameraProvider
, bạn bắt buộc phải thực hiện một vài bước thiết lập. Nếu không sử dụng PreviewView
, bạn cần điều chỉnh logic để áp dụng cho Surface
tuỳ chỉnh.
Dưới đây là các bước khi sử dụng PreviewView
:
- Thiết lập trình phát hiện cử chỉ điều chỉnh theo tỷ lệ để xử lý các sự kiện chụm.
- Lấy
ZoomState
từ đối tượngCamera.CameraInfo
, trong đó thực thểCamera
sẽ được trả về khi bạn gọibindToLifecycle()
. - Nếu
ZoomState
có giá trịzoomRatio
, hãy lưu giá trị đó dưới dạng tỷ lệ thu phóng hiện tại. Nếu không cózoomRatio
trênZoomState
, hãy dùng tỷ lệ thu phóng mặc định của máy ảnh (1.0). - Lấy tích của tỷ lệ thu phóng hiện tại với
scaleFactor
để xác định tỷ lệ thu phóng mới và truyền tỷ lệ đó vàoCameraControl.setZoomRatio()
. - Đặt trình phát hiện cử chỉ ở chế độ phản hồi với các sự kiện chạm trong
PreviewView.setOnTouchListener()
.
// CameraX: implement pinch-to-zoom with CameraProvider. // Define a scale gesture detector to respond to pinch events and call // setZoomRatio on CameraControl. val scaleGestureDetector = ScaleGestureDetector(context, object : SimpleOnGestureListener() { override fun onScale(detector: ScaleGestureDetector): Boolean { val camera = camera ?: return val zoomState = camera.cameraInfo.zoomState val currentZoomRatio: Float = zoomState.value?.zoomRatio ?: 1f camera.cameraControl.setZoomRatio( detector.scaleFactor * currentZoomRatio ) } } ) ... // Set the scaleGestureDetector in a touch listener on the PreviewView. previewView.setOnTouchListener { _, event -> var didConsume = scaleGestureDetector.onTouchEvent(event) if (!scaleGestureDetector.isInProgress) { // See pinch-to-zooom scenario for gestureDetector definition. didConsume = gestureDetector.onTouchEvent(event) } didConsume }
Chụp ảnh
Phần này trình bày cách kích hoạt tính năng chụp ảnh, cho dù bạn cần thực hiện thao tác này khi nhấn nút chụp, sau khi hết thời gian hẹn giờ hoặc trên bất kỳ sự kiện nào khác mà bạn chọn.
Camera1
Trong Camera1, trước tiên bạn cần xác định Camera.PictureCallback
để quản lý dữ liệu hình ảnh khi được yêu cầu. Dưới đây là ví dụ đơn giản về PictureCallback
để xử lý dữ liệu hình ảnh JPEG:
// Camera1: define a Camera.PictureCallback to handle JPEG data. private val picture = Camera.PictureCallback { data, _ -> val pictureFile: File = getOutputMediaFile(MEDIA_TYPE_IMAGE) ?: run { Log.d(TAG, "error creating media file, check storage permissions") return@PictureCallback } try { val fos = FileOutputStream(pictureFile) fos.write(data) fos.close() } catch (e: FileNotFoundException) { Log.d(TAG, "file not found", e) } catch (e: IOException) { Log.d(TAG, "error accessing file", e) } }
Sau đó, bất cứ khi nào bạn muốn chụp ảnh, hãy gọi phương thức takePicture()
trên thực thể Camera
. Phương thức takePicture()
này có 3 tham số khác nhau cho các loại dữ liệu khác nhau. Tham số đầu tiên là dành cho ShutterCallback
(không được xác định trong ví dụ này). Tham số thứ hai là dành cho PictureCallback
để xử lý dữ liệu thô của máy ảnh (không nén). Tham số thứ ba là tham số mà ví dụ này sử dụng, vì đây là PictureCallback
để xử lý dữ liệu hình ảnh JPEG.
// Camera1: call takePicture on Camera instance, passing our PictureCallback. camera?.takePicture(null, null, picture)
CameraX: CameraController
CameraController
của CameraX duy trì sự đơn giản của Camera1 phục vụ việc chụp ảnh bằng cách triển khai phương thức takePicture()
của riêng CameraX này. Ở đây, hãy xác định một hàm để định cấu hình mục nhập MediaStore
và chụp ảnh để lưu vào đó.
// CameraX: define a function that uses CameraController to take a photo. private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" private fun takePhoto() { // Create time stamped name and MediaStore entry. val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US) .format(System.currentTimeMillis()) val contentValues = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, name) put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg") if(Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image") } } // Create output options object which contains file + metadata. val outputOptions = ImageCapture.OutputFileOptions .Builder(context.getContentResolver(), MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) .build() // Set up image capture listener, which is triggered after photo has // been taken. cameraController.takePicture( outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback { override fun onError(e: ImageCaptureException) { Log.e(TAG, "photo capture failed", e) } override fun onImageSaved( output: ImageCapture.OutputFileResults ) { val msg = "Photo capture succeeded: ${output.savedUri}" Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() Log.d(TAG, msg) } } ) }
CameraX: CameraProvider
Việc chụp ảnh bằng CameraProvider
hoạt động gần giống như cách thực hiện với CameraController
, nhưng trước tiên, bạn cần tạo và liên kết ImageCapture
UseCase
để có một đối tượng cần gọi takePicture()
vào:
// CameraX: create and bind an ImageCapture UseCase. // Make a reference to the ImageCapture UseCase at a scope that can be accessed // throughout the camera logic in your app. private var imageCapture: ImageCapture? = null ... // Create an ImageCapture instance (can be added with other // UseCase definitions). imageCapture = ImageCapture.Builder().build() ... // Bind UseCases to camera (adding imageCapture along with preview here, but // preview is not required to use imageCapture). This function returns a camera // object which can be used to perform operations like zoom, flash, and focus. var camera = cameraProvider.bindToLifecycle( this, cameraSelector, preview, imageCapture)
Sau đó, bất cứ khi nào muốn chụp ảnh, bạn có thể gọi ImageCapture.takePicture()
. Hãy xem mã CameraController
trong phần này để biết ví dụ đầy đủ về hàm takePhoto()
.
// CameraX: define a function that uses CameraController to take a photo. private fun takePhoto() { // Get a stable reference of the modifiable ImageCapture UseCase. val imageCapture = imageCapture ?: return ... // Call takePicture on imageCapture instance. imageCapture.takePicture( ... ) }
Quay video
Việc quay video phức tạp hơn nhiều so với các tình huống đã xem xét từ trước đến nay. Bạn phải thiết lập từng phần của quy trình đúng cách, thường là theo một thứ tự cụ thể. Ngoài ra, bạn có thể cần xác minh rằng video và âm thanh được đồng bộ hoá hoặc cần xử lý các vấn đề không nhất quán khác trên thiết bị.
Như bạn thấy, CameraX tiếp tục xử lý những phức tạp này cho bạn.
Camera1
Việc quay video bằng Camera1 yêu cầu bạn phải quản lý cẩn thận Camera
và MediaRecorder
. Ngoài ra, các phương thức phải được gọi theo thứ tự cụ thể. Bạn phải tuân theo thứ tự sau để ứng dụng hoạt động đúng cách:
- Mở máy ảnh.
- Chuẩn bị và bắt đầu xem trước (nếu ứng dụng của bạn hiển thị video đang được quay, thường là như vậy).
- Mở khoá máy ảnh để
MediaRecorder
sử dụng bằng cách gọiCamera.unlock()
. - Định cấu hình bản ghi bằng cách gọi các phương thức này trên
MediaRecorder
:- Kết nối thực thể
Camera
vớisetCamera(camera)
. - Gọi
setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
. - Gọi
setVideoSource(MediaRecorder.VideoSource.CAMERA)
. - Gọi
setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_1080P))
để đặt chất lượng. XemCamcorderProfile
để biết tất cả các tuỳ chọn chất lượng. - Gọi
setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString())
. - Nếu ứng dụng của bạn có bản xem trước của video, hãy gọi
setPreviewDisplay(preview?.holder?.surface)
. - Gọi
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
. - Gọi
setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT)
. - Gọi
setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT)
. - Gọi
prepare()
để hoàn tất cấu hình củaMediaRecorder
.
- Kết nối thực thể
- Để bắt đầu quay video, hãy gọi
MediaRecorder.start()
. - Để dừng ghi, hãy gọi các phương thức này. Một lần nữa, hãy làm theo thứ tự chính xác sau:
- Gọi
MediaRecorder.stop()
. - Nếu muốn, hãy xoá cấu hình
MediaRecorder
hiện tại bằng cách gọiMediaRecorder.reset()
. - Gọi
MediaRecorder.release()
. - Khoá máy ảnh để các phiên
MediaRecorder
trong tương lai có thể sử dụng máy ảnh bằng cách gọiCamera.lock()
.
- Gọi
- Để dừng bản xem trước, hãy gọi
Camera.stopPreview()
. - Cuối cùng, để phát hành
Camera
để các quy trình khác có thể sử dụng, hãy gọiCamera.release()
.
Dưới đây là tất cả các bước kết hợp đó:
// Camera1: set up a MediaRecorder and a function to start and stop video // recording. // Make a reference to the MediaRecorder at a scope that can be accessed // throughout the camera logic in your app. private var mediaRecorder: MediaRecorder? = null private var isRecording = false ... private fun prepareMediaRecorder(): Boolean { mediaRecorder = MediaRecorder() // Unlock and set camera to MediaRecorder. camera?.unlock() mediaRecorder?.run { setCamera(camera) // Set the audio and video sources. setAudioSource(MediaRecorder.AudioSource.CAMCORDER) setVideoSource(MediaRecorder.VideoSource.CAMERA) // Set a CamcorderProfile (requires API Level 8 or higher). setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH)) // Set the output file. setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString()) // Set the preview output. setPreviewDisplay(preview?.holder?.surface) setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT) setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT) // Prepare configured MediaRecorder. return try { prepare() true } catch (e: IllegalStateException) { Log.d(TAG, "preparing MediaRecorder failed", e) releaseMediaRecorder() false } catch (e: IOException) { Log.d(TAG, "setting MediaRecorder file failed", e) releaseMediaRecorder() false } } return false } private fun releaseMediaRecorder() { mediaRecorder?.reset() mediaRecorder?.release() mediaRecorder = null camera?.lock() } private fun startStopVideo() { if (isRecording) { // Stop recording and release camera. mediaRecorder?.stop() releaseMediaRecorder() camera?.lock() isRecording = false // This is a good place to inform user that video recording has stopped. } else { // Initialize video camera. if (prepareVideoRecorder()) { // Camera is available and unlocked, MediaRecorder is prepared, now // you can start recording. mediaRecorder?.start() isRecording = true // This is a good place to inform the user that recording has // started. } else { // Prepare didn't work, release the camera. releaseMediaRecorder() // Inform user here. } } }
CameraX: CameraController
Với CameraController
của CameraX, bạn có thể chuyển đổi ImageCapture
, VideoCapture
và ImageAnalysis
UseCase
một cách độc lập, miễn là danh sách trường hợp sử dụng có thể được dùng một cách đồng thời.
Theo mặc định, ImageCapture
và ImageAnalysis
UseCase
được bật. Đó là lý do bạn không cần gọi setEnabledUseCases()
để chụp ảnh.
Nếu muốn dùng CameraController
để quay video, trước tiên, bạn cần dùng setEnabledUseCases()
để cho phép VideoCapture
UseCase
.
// CameraX: Enable VideoCapture UseCase on CameraController. cameraController.setEnabledUseCases(VIDEO_CAPTURE);
Khi muốn bắt đầu quay video, bạn có thể gọi hàm CameraController.startRecording()
. Hàm này có thể lưu video đã quay vào File
, như bạn thấy trong ví dụ bên dưới. Ngoài ra, bạn cần truyền Executor
và một lớp giúp triển khai OnVideoSavedCallback
để xử lý các phương thức gọi lại thành công và có lỗi. Khi quá trình ghi kết thúc, hãy gọi CameraController.stopRecording()
.
Lưu ý: Nếu đang sử dụng CameraX 1.3.0-alpha02 trở lên, sẽ có thêm một tham số AudioConfig
cho phép bạn bật hoặc tắt tính năng ghi âm trên video của bạn. Để bật tính năng ghi âm, bạn phải đảm bảo rằng mình có quyền truy cập vào micrô.
Ngoài ra, phương thức stopRecording()
sẽ bị xoá trong phiên bản 1.3.0-alpha02 và startRecording()
sẽ trả về đối tượng Recording
có thể dùng để tạm dừng, tiếp tục và dừng quay video.
// CameraX: implement video capture with CameraController. private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" // Define a VideoSaveCallback class for handling success and error states. class VideoSaveCallback : OnVideoSavedCallback { override fun onVideoSaved(outputFileResults: OutputFileResults) { val msg = "Video capture succeeded: ${outputFileResults.savedUri}" Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() Log.d(TAG, msg) } override fun onError(videoCaptureError: Int, message: String, cause: Throwable?) { Log.d(TAG, "error saving video: $message", cause) } } private fun startStopVideo() { if (cameraController.isRecording()) { // Stop the current recording session. cameraController.stopRecording() return } // Define the File options for saving the video. val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US) .format(System.currentTimeMillis()) val outputFileOptions = OutputFileOptions .Builder(File(this.filesDir, name)) .build() // Call startRecording on the CameraController. cameraController.startRecording( outputFileOptions, ContextCompat.getMainExecutor(this), VideoSaveCallback() ) }
CameraX: CameraProvider
Nếu đang sử dụng CameraProvider
, bạn cần tạo VideoCapture
UseCase
và truyền vào đối tượng Recorder
. Trên Recorder.Builder
, bạn có thể đặt chất lượng video và FallbackStrategy
một cách tuỳ ý nhằm xử lý các trường hợp mà thiết bị không thể đáp ứng các thông số kỹ thuật mong muốn về chất lượng. Sau đó, hãy liên kết thực thể VideoCapture
với CameraProvider
bằng UseCase
khác.
// CameraX: create and bind a VideoCapture UseCase with CameraProvider. // Make a reference to the VideoCapture UseCase and Recording at a // scope that can be accessed throughout the camera logic in your app. private lateinit var videoCapture: VideoCaptureprivate var recording: Recording? = null ... // Create a Recorder instance to set on a VideoCapture instance (can be // added with other UseCase definitions). val recorder = Recorder.Builder() .setQualitySelector(QualitySelector.from(Quality.FHD)) .build() videoCapture = VideoCapture.withOutput(recorder) ... // Bind UseCases to camera (adding videoCapture along with preview here, but // preview is not required to use videoCapture). This function returns a camera // object which can be used to perform operations like zoom, flash, and focus. var camera = cameraProvider.bindToLifecycle( this, cameraSelector, preview, videoCapture)
Tại thời điểm này, bạn có thể truy cập vào Recorder
trên thuộc tính videoCapture.output
. Recorder
có thể bắt đầu quay video được lưu vào File
, ParcelFileDescriptor
hoặc MediaStore
. Ví dụ này sử dụng MediaStore
.
Trên Recorder
, bạn có thể gọi một số phương thức để chuẩn bị. Hãy gọi prepareRecording()
để đặt các tuỳ chọn đầu ra MediaStore
. Nếu ứng dụng có quyền sử dụng micrô của thiết bị, hãy gọi withAudioEnabled()
.
Sau đó, hãy gọi start()
để bắt đầu quay video, truyền trong một ngữ cảnh và trình nghe sự kiện Consumer<VideoRecordEvent>
để xử lý các sự kiện quay video. Nếu thành công, bạn có thể sử dụng Recording
được trả về để tạm dừng, tiếp tục hoặc dừng quá trình quay video.
// CameraX: implement video capture with CameraProvider. private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" private fun startStopVideo() { val videoCapture = this.videoCapture ?: return if (recording != null) { // Stop the current recording session. recording.stop() recording = null return } // Create and start a new recording session. val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US) .format(System.currentTimeMillis()) val contentValues = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, name) put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4") if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/CameraX-Video") } } val mediaStoreOutputOptions = MediaStoreOutputOptions .Builder(contentResolver, MediaStore.Video.Media.EXTERNAL_CONTENT_URI) .setContentValues(contentValues) .build() recording = videoCapture.output .prepareRecording(this, mediaStoreOutputOptions) .withAudioEnabled() .start(ContextCompat.getMainExecutor(this)) { recordEvent -> when(recordEvent) { is VideoRecordEvent.Start -> { viewBinding.videoCaptureButton.apply { text = getString(R.string.stop_capture) isEnabled = true } } is VideoRecordEvent.Finalize -> { if (!recordEvent.hasError()) { val msg = "Video capture succeeded: " + "${recordEvent.outputResults.outputUri}" Toast.makeText( baseContext, msg, Toast.LENGTH_SHORT ).show() Log.d(TAG, msg) } else { recording?.close() recording = null Log.e(TAG, "video capture ends with error", recordEvent.error) } viewBinding.videoCaptureButton.apply { text = getString(R.string.start_capture) isEnabled = true } } } } }
Tài nguyên khác
Chúng tôi có một số ứng dụng CameraX hoàn chỉnh trong Kho lưu trữ GitHub mẫu cho máy ảnh. Các mẫu này cho bạn biết các tình huống trong hướng dẫn này phù hợp với ứng dụng Android hoàn chỉnh.
Nếu bạn muốn được hỗ trợ thêm khi di chuyển sang CameraX hoặc có thắc mắc về bộ API dành cho máy ảnh Android, vui lòng liên hệ với chúng tôi qua Nhóm thảo luận về CameraX.