Tuỳ chọn cấu hình

Bạn định cấu hình từng trường hợp sử dụng CameraX để kiểm soát các chương trình thành phần khác nhau của thao tác trong trường hợp sử dụng.

Ví dụ: Với trường hợp sử dụng tính năng chụp ảnh, bạn có thể đặt tỷ lệ khung hình mục tiêu và chế độ flash. Mã sau đây cho thấy một ví dụ:

Kotlin

val imageCapture = ImageCapture.Builder()
    .setFlashMode(...)
    .setTargetAspectRatio(...)
    .build()

Java

ImageCapture imageCapture =
    new ImageCapture.Builder()
        .setFlashMode(...)
        .setTargetAspectRatio(...)
        .build();

Ngoài các lựa chọn về cấu hình, một số trường hợp sử dụng còn cho phép các API linh động thay đổi chế độ cài đặt sau khi trường hợp sử dụng đã được tạo. Để biết thông tin về cấu hình cụ thể cho từng trường hợp sử dụng, hãy xem mục Triển khai bản xem trước, Phân tích hình ảnhChụp ảnh.

CameraXConfig

Để dễ sử dụng, CameraX có cấu hình mặc định như các bộ thực thi (executor) và bộ xử lý (handler) nội bộ phù hợp với hầu hết các tình huống sử dụng. Tuy nhiên, nếu ứng dụng của bạn có yêu cầu đặc biệt hoặc muốn tuỳ chỉnh các cấu hình đó, thì CameraXConfig là giao diện phục vụ mục đích đó.

Với CameraXConfig, ứng dụng sẽ có khả năng:

Mô hình sử dụng

Quy trình sau đây mô tả cách sử dụng CameraXConfig:

  1. Tạo đối tượng CameraXConfig có các cấu hình tuỳ chỉnh của bạn.
  2. Triển khai giao diện CameraXConfig.Provider trong Application và trả về đối tượng CameraXConfig trong getCameraXConfig().
  3. Thêm lớp Application vào tệp AndroidManifest.xml, như được mô tả tại đây.

Ví dụ: Mã mẫu sau đây giới hạn phạm vi ghi nhật ký của CameraX chỉ ở thông báo lỗi:

Kotlin

class CameraApplication : Application(), CameraXConfig.Provider {
   override fun getCameraXConfig(): CameraXConfig {
       return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
           .setMinimumLoggingLevel(Log.ERROR).build()
   }
}

Giữ một bản sao cục bộ của đối tượng CameraXConfig nếu ứng dụng của bạn cần biết cấu hình CameraX sau khi đặt.

Trình giới hạn camera

Trong lần gọi ProcessCameraProvider.getInstance() đầu tiên, CameraX liệt kê và truy vấn các đặc điểm của camera có trên thiết bị. Vì CameraX cần giao tiếp với các thành phần phần cứng, nên quá trình này có thể làm mỗi camera mất một lượng thời gian không nhỏ, đặc biệt là trên các thiết bị cấp thấp. Nếu ứng dụng của bạn chỉ dùng các camera cụ thể trên thiết bị, chẳng hạn như camera trước mặc định, thì bạn có thể thiết lập cho CameraX bỏ qua các camera khác, nhờ đó giúp giảm độ trễ khởi động cho các camera mà ứng dụng của bạn dùng.

Nếu CameraSelector truyền đến CameraXConfig.Builder.setAvailableCamerasLimiter() lọc ra một camera, thì CameraX sẽ hoạt động như thể không có camera đó. Ví dụ: Mã sau đây giới hạn ứng dụng chỉ dùng camera sau mặc định của thiết bị:

Kotlin

class MainApplication : Application(), CameraXConfig.Provider {
   override fun getCameraXConfig(): CameraXConfig {
       return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
              .setAvailableCamerasLimiter(CameraSelector.DEFAULT_BACK_CAMERA)
              .build()
   }
}

Luồng

Nhiều API nền tảng là cơ sở xây dựng CameraX yêu cầu chặn giao tiếp liên quy trình (IPC) bằng phần cứng. Quá trình này đôi khi có thể mất hàng trăm mili giây để phản hồi. Do đó, CameraX chỉ gọi những API này từ các luồng trong nền để luồng chính không bị chặn và giao diện người dùng vẫn linh hoạt. CameraX quản lý nội bộ các luồng trong nền này để hoạt động này xuất hiện minh bạch. Tuy nhiên, một số ứng dụng yêu cầu kiểm soát nghiêm ngặt các luồng. CameraXConfig cho phép ứng dụng đặt các luồng trong nền được dùng thông qua CameraXConfig.Builder.setCameraExecutor()CameraXConfig.Builder.setSchedulerHandler().

Bộ thực thi camera

Bộ thực thi camera được dùng cho mọi lệnh gọi API nền tảng Camera nội bộ, cũng như cho các lệnh gọi lại từ những API này. CameraX phân bổ và quản lý một Executor nội bộ để thực hiện những nhiệm vụ này. Tuy nhiên, nếu ứng dụng của bạn yêu cầu kiểm soát luồng nghiêm ngặt hơn, hãy dùng CameraXConfig.Builder.setCameraExecutor().

Bộ xử lý trình lập lịch biểu

Bộ xử lý trình lập lịch biểu dùng để lên lịch các nhiệm vụ nội bộ trong những khoảng thời gian cố định, chẳng hạn như thử mở lại camera khi camera không dùng được. Bộ xử lý này không thực thi các tác vụ mà chỉ gửi chúng đến bộ thực thi camera. Đôi khi, bộ xử lý này cũng được dùng trên các nền tảng API cũ yêu cầu phải có Handler để gọi lại. Trong những trường hợp này, lệnh gọi lại vẫn chỉ được gửi trực tiếp đến bộ thực thi camera. CameraX phân bổ và quản lý một HandlerThread nội bộ để thực hiện những nhiệm vụ này, nhưng bạn có thể ghi đè bằng CameraXConfig.Builder.setSchedulerHandler().

Ghi nhật ký

Tính năng ghi nhật ký CameraX cho phép các ứng dụng lọc thông báo logcat vì điều này có thể giúp bạn tránh được các thông báo chi tiết trong mã phát hành chính thức. CameraX hỗ trợ 4 cấp độ ghi nhật ký, từ chi tiết nhất đến nghiêm trọng nhất:

  • Log.DEBUG (mặc định)
  • Log.INFO
  • Log.WARN
  • Log.ERROR

Tham khảo tài liệu về Nhật ký Android để biết thông tin mô tả chi tiết về các cấp độ nhật ký này. Dùng CameraXConfig.Builder.setMinimumLoggingLevel(int) để đặt cấp độ ghi nhật ký thích hợp cho ứng dụng của bạn.

Tự động chọn

CameraX tự động cung cấp chức năng dành riêng cho thiết bị mà ứng dụng của bạn đang chạy. Ví dụ: CameraX sẽ tự động xác định độ phân giải tốt nhất để sử dụng nếu bạn không chỉ định độ phân giải hoặc nếu độ phân giải mà bạn chỉ định không được hỗ trợ. Tất cả những việc này đều do thư viện xử lý, giúp bạn không cần ghi mã dành riêng cho thiết bị.

Mục tiêu của CameraX là khởi chạy thành công một phiên camera. Nói cách khác, CameraX sẽ điều chỉnh độ phân giải và tỷ lệ khung hình dựa trên tính năng của thiết bị. Quá trình điều chỉnh có thể diễn ra vì:

  • Thiết bị không hỗ trợ độ phân giải mà bạn yêu cầu.
  • Thiết bị gặp các vấn đề về khả năng tương thích, chẳng hạn như các thiết bị cũ yêu cầu độ phân giải nhất định thì mới hoạt động chính xác.
  • Một số thiết bị chỉ có một số định dạng theo tỷ lệ khung hình nhất định.
  • Thiết bị có lựa chọn ưu tiên "nearest mod16" (mod16 gần nhất) để mã hoá tệp JPEG hoặc video. Để biết thêm thông tin, hãy xem SCALER_STREAM_CONFIGURATION_MAP.

Mặc dù CameraX tạo và quản lý phiên nhưng hãy luôn kiểm tra kích thước hình ảnh được trả về ở dữ liệu đầu ra của trường hợp sử dụng trong mã và điều chỉnh sao cho phù hợp.

Chế độ xoay

Theo mặc định, chế độ xoay của camera được đặt để khớp với chế độ xoay của màn hình mặc định trong khi tạo trường hợp sử dụng. Trong trường hợp mặc định này, CameraX sẽ tạo ra các kết quả đầu ra để cho phép ứng dụng khớp với những gì bạn muốn thấy trong bản xem trước. Bạn có thể thay đổi chế độ xoay thành một giá trị tuỳ chỉnh để hỗ trợ các thiết bị nhiều màn hình bằng cách truyền hướng màn hình hiện tại khi định cấu hình các đối tượng trường hợp sử dụng hoặc truyền linh động sau khi tạo các đối tượng đó.

Ứng dụng của bạn có thể đặt chế độ xoay mục tiêu thông qua các chế độ cài đặt cấu hình. Khi đó, ứng dụng có thể cập nhật chế độ cài đặt xoay bằng cách sử dụng các phương thức trong API trường hợp sử dụng (chẳng hạn như ImageAnalysis.setTargetRotation()), ngay cả khi vòng đời ở trạng thái đang chạy. Bạn có thể sử dụng tính năng này khi ứng dụng bị khoá ở chế độ chân dung, do đó không có hoạt động định cấu hình lại nào diễn ra khi xoay, nhưng trường hợp sử dụng ảnh hoặc phân tích cần biết về chế độ xoay hiện tại của thiết bị. Ví dụ: Ứng dụng có thể cần nhận biết chế độ xoay để các khuôn mặt được đặt đúng hướng giúp phát hiện khuôn mặt hoặc ảnh được đặt ở chế độ ngang hoặc chân dung.

Dữ liệu cho hình ảnh đã chụp có thể được lưu trữ mà không có thông tin về chế độ xoay. Dữ liệu Exif chứa thông tin về chế độ xoay để các ứng dụng thư viện có thể hiển thị hình ảnh theo đúng hướng sau khi lưu.

Để hiển thị dữ liệu xem trước theo đúng hướng, bạn có thể sử dụng đầu ra siêu dữ liệu từ Preview.PreviewOutput() để tạo ra sự biến đổi.

Mã mẫu sau đây cho biết cách cài đặt chế độ xoay trên một sự kiện (event) hướng:

Kotlin

override fun onCreate() {
    val imageCapture = ImageCapture.Builder().build()

    val orientationEventListener = object : OrientationEventListener(this as Context) {
        override fun onOrientationChanged(orientation : Int) {
            // Monitors orientation values to determine the target rotation value
            val rotation : Int = when (orientation) {
                in 45..134 -> Surface.ROTATION_270
                in 135..224 -> Surface.ROTATION_180
                in 225..314 -> Surface.ROTATION_90
                else -> Surface.ROTATION_0
            }

            imageCapture.targetRotation = rotation
        }
    }
    orientationEventListener.enable()
}

Java

@Override
public void onCreate() {
    ImageCapture imageCapture = new ImageCapture.Builder().build();

    OrientationEventListener orientationEventListener = new OrientationEventListener((Context)this) {
       @Override
       public void onOrientationChanged(int orientation) {
           int rotation;

           // Monitors orientation values to determine the target rotation value
           if (orientation >= 45 && orientation < 135) {
               rotation = Surface.ROTATION_270;
           } else if (orientation >= 135 && orientation < 225) {
               rotation = Surface.ROTATION_180;
           } else if (orientation >= 225 && orientation < 315) {
               rotation = Surface.ROTATION_90;
           } else {
               rotation = Surface.ROTATION_0;
           }

           imageCapture.setTargetRotation(rotation);
       }
    };

    orientationEventListener.enable();
}

Dựa trên chế độ xoay đã đặt, mỗi trường hợp sử dụng sẽ trực tiếp xoay dữ liệu hình ảnh hoặc cung cấp siêu dữ liệu về chế độ xoay cho người dùng dữ liệu hình ảnh không xoay.

  • Xem trước: Đầu ra siêu dữ liệu được cung cấp để xác định chế độ xoay độ phân giải mục tiêu bằng cách sử dụng Preview.getTargetRotation().
  • ImageAnalysis: Đầu ra siêu dữ liệu được cung cấp để xác định toạ độ vùng đệm hình ảnh so với toạ độ màn hình hiển thị.
  • ImageCapture: Siêu dữ liệu, vùng đệm Exif hình ảnh hoặc cả vùng đệm và siêu dữ liệu sẽ được thay đổi để ghi chú lại cách cài đặt chế độ xoay. Giá trị thay đổi tuỳ thuộc vào việc triển khai HAL (Lớp trừu tượng phần cứng).

Kích cỡ khuôn hình chữ nhật cắt ảnh

Theo mặc định, kích cỡ khuôn hình chữ nhật cắt ảnh là kích cỡ khuôn hình chữ nhật vùng đệm đầy đủ. Bạn có thể tuỳ chỉnh kích cỡ khuôn hình chữ nhật cắt ảnh bằng ViewPortUseCaseGroup. Bằng cách nhóm các trường hợp sử dụng và cài đặt khung nhìn, CameraX đảm bảo rằng kích cỡ khuôn hình chữ nhật cắt ảnh của tất cả các trường hợp sử dụng trong nhóm đó trỏ đến cùng một khu vực trong cảm biến của camera.

Đoạn mã sau đây cho thấy cách sử dụng hai lớp này:

Kotlin

val viewPort =  ViewPort.Builder(Rational(width, height), display.rotation).build()
val useCaseGroup = UseCaseGroup.Builder()
    .addUseCase(preview)
    .addUseCase(imageAnalysis)
    .addUseCase(imageCapture)
    .setViewPort(viewPort)
    .build()
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, useCaseGroup)

Java

ViewPort viewPort = new ViewPort.Builder(
         new Rational(width, height),
         getDisplay().getRotation()).build();
UseCaseGroup useCaseGroup = new UseCaseGroup.Builder()
    .addUseCase(preview)
    .addUseCase(imageAnalysis)
    .addUseCase(imageCapture)
    .setViewPort(viewPort)
    .build();
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, useCaseGroup);

ViewPort xác định kích cỡ khuôn hình chữ nhật vùng đệm mà người dùng cuối nhìn thấy. Sau đó, CameraX tính toán kích cỡ khuôn hình chữ nhật cắt ảnh lớn nhất có thể dựa trên các thuộc tính của khung nhìn và các trường hợp sử dụng đính kèm. Thông thường, để đạt được hiệu ứng WYSIWYG, bạn có thể định cấu hình khung nhìn dựa trên trường hợp sử dụng xem trước. Một cách đơn giản để tải khung nhìn là sử dụng PreviewView.

Các đoạn mã sau đây cho thấy cách tải đối tượng ViewPort:

Kotlin

val viewport = findViewById<PreviewView>(R.id.preview_view).viewPort

Java

ViewPort viewPort = ((PreviewView)findViewById(R.id.preview_view)).getViewPort();

Trong ví dụ trước, kết quả ứng dụng nhận được từ ImageAnalysisImageCapture khớp với kết quả mà người dùng cuối nhìn thấy trong PreviewView, giả sử loại tỷ lệ của PreviewView được đặt thành giá trị mặc định, FILL_CENTER. Sau khi áp dụng kích cỡ khuôn hình chữ nhật cắt ảnh và chế độ xoay cho vùng đệm đầu ra, hình ảnh trong tất cả các trường hợp sử dụng sẽ giống nhau, mặc dù có thể có độ phân giải khác nhau. Để biết thêm thông tin về cách áp dụng thông tin biến đổi, hãy xem đầu ra biến đổi.

Lựa chọn camera

CameraX tự động lựa chọn thiết bị camera phù hợp nhất với các trường hợp sử dụng và yêu cầu của ứng dụng. Nếu muốn sử dụng một thiết bị khác với thiết bị được chọn cho mình, bạn có một số lựa chọn:

Mã mẫu sau đây minh hoạ cách tạo một CameraSelector để tác động đến việc lựa chọn thiết bị:

Kotlin

fun selectExternalOrBestCamera(provider: ProcessCameraProvider):CameraSelector? {
   val cam2Infos = provider.availableCameraInfos.map {
       Camera2CameraInfo.from(it)
   }.sortedByDescending {
       // HARDWARE_LEVEL is Int type, with the order of:
       // LEGACY < LIMITED < FULL < LEVEL_3 < EXTERNAL
       it.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
   }

   return when {
       cam2Infos.isNotEmpty() -> {
           CameraSelector.Builder()
               .addCameraFilter {
                   it.filter { camInfo ->
                       // cam2Infos[0] is either EXTERNAL or best built-in camera
                       val thisCamId = Camera2CameraInfo.from(camInfo).cameraId
                       thisCamId == cam2Infos[0].cameraId
                   }
               }.build()
       }
       else -> null
    }
}

// create a CameraSelector for the USB camera (or highest level internal camera)
val selector = selectExternalOrBestCamera(processCameraProvider)
processCameraProvider.bindToLifecycle(this, selector, preview, analysis)

Chọn đồng thời nhiều camera

Kể từ CameraX 1.3, bạn cũng có thể chọn đồng thời nhiều camera. Ví dụ: bạn có thể liên kết với camera trước và sau để chụp ảnh hoặc quay video đồng thời từ cả hai góc nhìn.

Khi dùng tính năng Camera đồng thời, thiết bị có thể vận hành cùng lúc 2 camera trước và sau hoặc 2 camera sau. Khối mã sau đây cho biết cách đặt 2 camera khi gọi bindToLifecycle và cách lấy cả hai đối tượng Camera từ đối tượng ConcurrentCamera được trả về.

Kotlin

// Build ConcurrentCameraConfig
val primary = ConcurrentCamera.SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
)

val secondary = ConcurrentCamera.SingleCameraConfig(
    secondaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
)

val concurrentCamera = cameraProvider.bindToLifecycle(
    listOf(primary, secondary)
)

val primaryCamera = concurrentCamera.cameras[0]
val secondaryCamera = concurrentCamera.cameras[1]

Java

// Build ConcurrentCameraConfig
SingleCameraConfig primary = new SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
);

SingleCameraConfig secondary = new SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
);

ConcurrentCamera concurrentCamera =  
    mCameraProvider.bindToLifecycle(Arrays.asList(primary, secondary));

Camera primaryCamera = concurrentCamera.getCameras().get(0);
Camera secondaryCamera = concurrentCamera.getCameras().get(1);

Độ phân giải của camera

Bạn có thể chọn cho phép CameraX đặt độ phân giải hình ảnh dựa trên tổ hợp tính năng của thiết bị, cấp độ phần cứng được hỗ trợ của thiết bị, trường hợp sử dụng và tỷ lệ khung hình được cung cấp. Ngoài ra, bạn có thể đặt độ phân giải mục tiêu cụ thể hoặc tỷ lệ khung hình cụ thể trong các trường hợp sử dụng hỗ trợ cấu hình đó.

Độ phân giải tự động

CameraX có thể tự động xác định các chế độ cài đặt độ phân giải tốt nhất dựa trên các trường hợp sử dụng được chỉ định trong cameraProcessProvider.bindToLifecycle(). Bất cứ khi nào có thể, hãy chỉ định tất cả các trường hợp sử dụng cần thiết để chạy đồng thời trong một phiên duy nhất ở một lệnh gọi bindToLifecycle() duy nhất. CameraX sẽ xác định độ phân giải dựa trên nhóm trường hợp sử dụng bị ràng buộc bằng cách xem xét cấp độ phần cứng được hỗ trợ của thiết bị và tính đến phương sai dành riêng cho thiết bị (trong đó thiết bị có thể vượt quá hoặc không đáp ứng cấu hình luồng có sẵn). Mục đích là để ứng dụng chạy trên nhiều loại thiết bị, đồng thời giảm thiểu các đường dẫn mã dành riêng cho thiết bị.

Tỷ lệ khung hình mặc định cho các trường hợp sử dụng tính năng chụp ảnh và phân tích hình ảnh là 4:3.

Các trường hợp sử dụng có tỷ lệ khung hình có thể định cấu hình để cho phép ứng dụng chỉ định tỷ lệ khung hình mong muốn dựa trên thiết kế giao diện người dùng. Đầu ra CameraX được tạo để khớp với tỷ lệ khung hình theo yêu cầu ở mức tối đa mà thiết bị hỗ trợ. Nếu không có độ phân giải khớp chính xác được hỗ trợ, thì độ phân giải đáp ứng nhiều điều kiện nhất sẽ được chọn. Do đó, ứng dụng sẽ chỉ ra cách camera xuất hiện trong ứng dụng và CameraX xác định chế độ cài đặt độ phân giải tốt nhất cho camera để đáp ứng điều đó trên các thiết bị khác nhau.

Ví dụ: Một ứng dụng có thể thực hiện bất kỳ thao tác nào sau đây:

  • Chỉ định độ phân giải mục tiêu là 4:3 hoặc 16:9 cho một trường hợp sử dụng
  • Chỉ định độ phân giải tuỳ chỉnh mà CameraX cố gắng tìm kết quả khớp nhất
  • Chỉ định tỷ lệ khung hình cắt ảnh cho ImageCapture

CameraX tự động chọn độ phân giải bề mặt Camera2 bên trong. Bảng sau đây trình bày các độ phân giải:

Trường hợp sử dụng Độ phân giải bề mặt bên trong Độ phân giải dữ liệu đầu ra
Xem trước Tỷ lệ khung hình: Độ phân giải giúp mục tiêu phù hợp nhất với chế độ cài đặt. Độ phân giải bề mặt bên trong. Siêu dữ liệu được cung cấp để cho phép Khung hiển thị cắt, điều chỉnh theo tỷ lệ và xoay để đạt được tỷ lệ khung hình mục tiêu.
Độ phân giải mặc định: Độ phân giải cao nhất của bản xem trước hoặc độ phân giải cao nhất mà thiết bị ưu tiên khớp với tỷ lệ khung hình của Bản xem trước.
Độ phân giải tối đa: Kích thước bản xem trước, tức là kích thước phù hợp nhất với độ phân giải màn hình của thiết bị hoặc với độ phân giải 1080p (1920x1080), tuỳ theo kích thước nào nhỏ hơn.
Phân tích hình ảnh Tỷ lệ khung hình: Độ phân giải giúp mục tiêu phù hợp nhất với chế độ cài đặt. Độ phân giải bề mặt bên trong.
Độ phân giải mặc định: Chế độ cài đặt độ phân giải mục tiêu mặc định là 640x480. Việc điều chỉnh cả độ phân giải mục tiêu và tỷ lệ khung hình tương ứng sẽ dẫn đến độ phân giải được hỗ trợ tốt nhất.
Độ phân giải tối đa: Độ phân giải đầu ra tối đa của thiết bị camera ở định dạng YUV_420_888 được lấy từ StreamConfigurationMap.getOutputSizes(). Theo mặc định, độ phân giải mục tiêu được đặt là 640x480, nên nếu muốn có độ phân giải lớn hơn 640x480, bạn phải sử dụng setTargetResolution()setTargetAspectRatio() để lấy giá trị gần nhất trong số các độ phân giải được hỗ trợ.
Chụp ảnh Tỷ lệ khung hình: Tỷ lệ khung hình phù hợp nhất với chế độ cài đặt. Độ phân giải bề mặt bên trong.
Độ phân giải mặc định: Độ phân giải cao nhất hiện có hoặc độ phân giải cao nhất mà thiết bị ưu tiên khớp với tỷ lệ khung hình của ImageCapture.
Độ phân giải tối đa: Độ phân giải đầu ra tối đa của thiết bị camera ở định dạng JPEG. Sử dụng StreamConfigurationMap.getOutputSizes() để truy xuất dữ liệu này.

Chỉ định độ phân giải

Bạn có thể đặt độ phân giải cụ thể khi xây dựng các trường hợp sử dụng bằng cách sử dụng phương thức setTargetResolution(Size resolution), như trong mã mẫu sau đây:

Kotlin

val imageAnalysis = ImageAnalysis.Builder()
    .setTargetResolution(Size(1280, 720))
    .build()

Java

ImageAnalysis imageAnalysis =
  new ImageAnalysis.Builder()
    .setTargetResolution(new Size(1280, 720))
    .build();

Bạn không thể đặt cả tỷ lệ khung hình mục tiêu và độ phân giải mục tiêu cho cùng một trường hợp sử dụng. Thao tác này sẽ gửi một IllegalArgumentException khi tạo đối tượng cấu hình.

Nêu rõ độ phân giải Size trong khung toạ độ sau khi xoay các kích thước được hỗ trợ bằng chế độ xoay mục tiêu. Ví dụ: Một thiết bị có hướng mặc định theo chiều dọc đang xoay mục tiêu mặc định cần hình ảnh dọc có thể chỉ định 480x640, thiết bị tương tự được xoay 90 độ và nhắm mục tiêu đến hướng ngang có thể chỉ định 640x480.

Độ phân giải mục tiêu cố gắng thiết lập một giới hạn tối thiểu cho độ phân giải hình ảnh. Độ phân giải hình ảnh thực tế là độ phân giải có sẵn gần nhất về kích thước không nhỏ hơn độ phân giải mục tiêu, như được xác định trong quá trình triển khai Máy ảnh.

Tuy nhiên, nếu không có độ phân giải nào bằng hoặc lớn hơn độ phân giải mục tiêu, thì độ phân giải có sẵn gần nhất nhỏ hơn độ phân giải mục tiêu sẽ được chọn. Các độ phân giải có cùng tỷ lệ khung hình Size được cung cấp sẽ được ưu tiên hơn so với các độ phân giải có nhiều tỷ lệ khung hình.

Dựa trên yêu cầu, CameraX áp dụng độ phân giải phù hợp nhất. Nếu nhu cầu chính là để đáp ứng tỷ lệ khung hình, thì chỉ xác định setTargetAspectRatio, còn CameraX sẽ xác định độ phân giải cụ thể phù hợp với thiết bị. Nếu nhu cầu chính của ứng dụng là chỉ định độ phân giải để xử lý hình ảnh hiệu quả hơn (ví dụ: hình ảnh cỡ nhỏ hoặc cỡ trung dựa trên tính năng xử lý của thiết bị), thì sử dụng setTargetResolution(Size resolution).

Nếu ứng dụng của bạn yêu cầu độ phân giải chính xác thì hãy xem bảng trong createCaptureSession() để xác định độ phân giải tối đa được hỗ trợ theo từng cấp độ phần cứng. Để kiểm tra các độ phân giải cụ thể mà thiết bị hiện tại hỗ trợ, xem StreamConfigurationMap.getOutputSizes(int).

Nếu ứng dụng của bạn đang chạy trên Android 10 trở lên, bạn có thể sử dụng isSessionConfigurationSupported() để xác minh một SessionConfiguration cụ thể.

Kiểm soát đầu ra của camera

Ngoài việc cho phép bạn định cấu hình đầu ra của camera theo nhu cầu cho từng trường hợp sử dụng, CameraX còn triển khai các giao diện sau đây để hỗ trợ các thao tác của camera phổ biến cho tất cả các trường hợp sử dụng ràng buộc:

  • CameraControl cho phép bạn định cấu hình các tính năng phổ biến của camera.
  • CameraInfo cho phép bạn truy vấn trạng thái của những tính năng phổ biến đó của camera.

Sau đây là các tính năng camera được hỗ trợ với CameraControl:

  • Zoom (thu phóng)
  • Đèn pin
  • Lấy nét và đo sáng (nhấn để lấy nét)
  • Bù phơi sáng

Nhận thực thể của CameraControl và CameraInfo

Truy xuất các thực thể của CameraControlCameraInfo bằng cách sử dụng đối tượng Camera do ProcessCameraProvider.bindToLifecycle() trả về. Mã sau đây cho thấy một ví dụ:

Kotlin

val camera = processCameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)

// For performing operations that affect all outputs.
val cameraControl = camera.cameraControl
// For querying information and states.
val cameraInfo = camera.cameraInfo

Java

Camera camera = processCameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)

// For performing operations that affect all outputs.
CameraControl cameraControl = camera.getCameraControl()
// For querying information and states.
CameraInfo cameraInfo = camera.getCameraInfo()

Ví dụ: Bạn có thể gửi thao tác thu phóng và các thao tác CameraControl khác sau khi gọi bindToLifecycle(). Sau khi bạn dừng hoặc huỷ bỏ hoạt động dùng để liên kết thực thể camera, CameraControl sẽ không thực thi được các thao tác nữa và trả về một ListenableFuture không thành công.

Zoom (thu phóng)

CameraControl cung cấp 2 phương thức thay đổi mức thu phóng:

  • setZoomRatio() đặt mức thu phóng theo tỷ lệ thu phóng.

    Tỷ lệ này phải nằm trong phạm vi từ CameraInfo.getZoomState().getValue().getMinZoomRatio() đến CameraInfo.getZoomState().getValue().getMaxZoomRatio(). Nếu không, hàm sẽ trả về ListenableFuture không thành công.

  • setLinearZoom() đặt mức thu phóng hiện tại với giá trị thu phóng tuyến tính trong khoảng từ 0 đến 1.

    Ưu điểm của tính năng thu phóng tuyến tính là giúp mở rộng phạm vi của trường nhìn (FOV) với sự thay đổi về mức thu phóng. Nhờ vậy mà đây là tính năng lý tưởng để sử dụng với chế độ xem Slider.

CameraInfo.getZoomState() trả về một LiveData của trạng thái thu phóng hiện tại. Giá trị thay đổi khi camera được khởi chạy hoặc nếu mức thu phóng được đặt bằng setZoomRatio() hoặc setLinearZoom(). Khi gọi một trong hai phương thức, bạn sẽ đặt giá trị hỗ trợ ZoomState.getZoomRatio()ZoomState.getLinearZoom(). Việc này rất hữu ích nếu bạn muốn hiển thị văn bản tỷ lệ thu phóng cùng với thanh trượt. Chỉ cần quan sát ZoomState LiveData để cập nhật cả hai mà không cần thực hiện chuyển đổi.

ListenableFuture mà cả hai API trả về đều cung cấp cho ứng dụng tuỳ chọn nhận thông báo khi hoàn tất yêu cầu lặp lại với giá trị thu phóng được chỉ định. Ngoài ra, nếu bạn đặt một giá trị thu phóng mới trong khi thao tác trước đó vẫn đang thực thi, ListenableFuture của thao tác thu phóng trước đó sẽ ngay lập tức bị lỗi.

Đèn pin

CameraControl.enableTorch(boolean) bật hoặc tắt đèn pin (còn gọi là đèn flash).

Bạn có thể dùng CameraInfo.getTorchState() để truy vấn trạng thái đèn pin hiện tại. Bạn có thể kiểm tra giá trị được CameraInfo.hasFlashUnit() trả về để xác định xem có đèn pin hay không. Nếu không, thao tác gọi CameraControl.enableTorch(boolean) khiến ListenableFuture được trả về ngay lập tức hoàn tất với kết quả không thành công và đặt trạng thái đèn pin thành TorchState.OFF.

Khi được bật, đèn pin sẽ vẫn duy trì trạng thái bật trong khi chụp ảnh và quay video bất kể chế độ cài đặt flashMode. flashMode trong ImageCapture chỉ hoạt động khi đèn pin tắt.

Lấy nét và đo sáng

CameraControl.startFocusAndMetering() kích hoạt chế độ tự động lấy nét và đo độ phơi sáng bằng cách đặt vùng đo sáng AF/AE/AWB dựa trên FocusMeteringAction đã cho. Tính năng này thường dùng để triển khai tính năng "tap to focus" (nhấn để lấy nét) trong nhiều ứng dụng camera.

MeteringPoint

Để bắt đầu, hãy tạo một MeteringPoint bằng MeteringPointFactory.createPoint(float x, float y, float size). MeteringPoint đại diện cho một điểm duy nhất trên camera Surface. Điểm này được lưu trữ ở dạng chuẩn hoá để có thể dễ dàng chuyển đổi sang toạ độ cảm biến nhằm chỉ định vùng AF/AE/AWB.

Kích thước của MeteringPoint dao động từ 0 đến 1, với kích thước mặc định là 0,15f. Khi gọi MeteringPointFactory.createPoint(float x, float y, float size), CameraX sẽ tạo một vùng hình chữ nhật có tâm tại (x, y) cho size được cung cấp.

Mã sau đây minh hoạ cách tạo MeteringPoint:

Kotlin

// Use PreviewView.getMeteringPointFactory if PreviewView is used for preview.
previewView.setOnTouchListener((view, motionEvent) ->  {
val meteringPoint = previewView.meteringPointFactory
    .createPoint(motionEvent.x, motionEvent.y)
…
}

// Use DisplayOrientedMeteringPointFactory if SurfaceView / TextureView is used for
// preview. Please note that if the preview is scaled or cropped in the View,
// it’s the application's responsibility to transform the coordinates properly
// so that the width and height of this factory represents the full Preview FOV.
// And the (x,y) passed to create MeteringPoint might need to be adjusted with
// the offsets.
val meteringPointFactory = DisplayOrientedMeteringPointFactory(
     surfaceView.display,
     camera.cameraInfo,
     surfaceView.width,
     surfaceView.height
)

// Use SurfaceOrientedMeteringPointFactory if the point is specified in
// ImageAnalysis ImageProxy.
val meteringPointFactory = SurfaceOrientedMeteringPointFactory(
     imageWidth,
     imageHeight,
     imageAnalysis)

startFocusAndMetering và FocusMeteringAction

Để gọi startFocusAndMetering(), các ứng dụng phải tạo một FocusMeteringAction, bao gồm một hoặc nhiều MeteringPoints có kết hợp chế độ đo sáng không bắt buộc từ FLAG_AF, FLAG_AE, FLAG_AWB. Mã sau đây minh hoạ cách sử dụng này:

Kotlin

val meteringPoint1 = meteringPointFactory.createPoint(x1, x1)
val meteringPoint2 = meteringPointFactory.createPoint(x2, y2)
val action = FocusMeteringAction.Builder(meteringPoint1) // default AF|AE|AWB
      // Optionally add meteringPoint2 for AF/AE.
      .addPoint(meteringPoint2, FLAG_AF | FLAG_AE)
      // The action is canceled in 3 seconds (if not set, default is 5s).
      .setAutoCancelDuration(3, TimeUnit.SECONDS)
      .build()

val result = cameraControl.startFocusAndMetering(action)
// Adds listener to the ListenableFuture if you need to know the focusMetering result.
result.addListener({
   // result.get().isFocusSuccessful returns if the auto focus is successful or not.
}, ContextCompat.getMainExecutor(this)

Như đã trình bày trong mã trước, startFocusAndMetering() sẽ lấy một FocusMeteringAction bao gồm một MeteringPoint cho vùng đo sáng AF/AE/AWB và một vùng khác MeteringPoint chỉ dành cho AF và AE.

Trong nội bộ, CameraX chuyển đổi mã thành Camera2 MeteringRectangles và đặt các tham số CONTROL_AF_REGIONS / CONTROL_AE_REGIONS / CONTROL_AWB_REGIONS tương ứng với yêu cầu chụp.

Vì không phải mọi thiết bị đều hỗ trợ AF/AE/AWB và nhiều vùng, nên CameraX cố gắng nhất có thể để thực thi FocusMeteringAction. CameraX sử dụng số lượng MeteringPoint tối đa được hỗ trợ, theo thứ tự thêm điểm. Tất cả MeteringPoint được thêm sau khi bỏ qua số lượng tối đa. Ví dụ: Nếu FocusMeteringAction được cung cấp cùng với 3 MeteringPoint trên nền tảng chỉ hỗ trợ 2, thì chỉ 2 MeteringPoint đầu tiên được sử dụng. MeteringPoint cuối cùng bị CameraX bỏ qua.

Bù phơi sáng

Tính năng bù phơi sáng rất hữu ích khi các ứng dụng cần tinh chỉnh các giá trị phơi sáng (EV) ngoài kết quả đầu ra phơi sáng tự động (AE). Các giá trị bù phơi sáng được kết hợp theo cách sau để xác định mức phơi sáng cần thiết cho các điều kiện hình ảnh hiện tại:

Exposure = ExposureCompensationIndex * ExposureCompensationStep

CameraX cung cấp chức năng Camera.CameraControl.setExposureCompensationIndex() để đặt giá trị bù phơi sáng dưới dạng trị số.

Trị số dương làm cho hình ảnh sáng hơn, trong khi trị số âm làm mờ hình ảnh. Các ứng dụng có thể truy vấn phạm vi được hỗ trợ bằng CameraInfo.ExposureState.exposureCompensationRange() như được mô tả trong phần tiếp theo. Nếu giá trị được hỗ trợ, ListenableFuture được trả về sẽ hoàn tất khi giá trị được bật thành công trong yêu cầu chụp; nếu trị số đã chỉ định nằm ngoài phạm vi được hỗ trợ, setExposureCompensationIndex() sẽ khiến ListenableFuture được trả về ngay lập tức hoàn tất với kết quả không thành công.

CameraX chỉ giữ lại yêu cầu setExposureCompensationIndex() chưa xử lý mới nhất và gọi hàm nhiều lần trước khi yêu cầu trước đó được thực thi dẫn đến việc huỷ bỏ.

Đoạn mã sau đây sẽ thiết lập một trị số bù phơi sáng và đăng ký một lệnh gọi lại khi đã thực thi yêu cầu thay đổi độ phơi sáng:

Kotlin

camera.cameraControl.setExposureCompensationIndex(exposureCompensationIndex)
   .addListener({
      // Get the current exposure compensation index, it might be
      // different from the asked value in case this request was
      // canceled by a newer setting request.
      val currentExposureIndex = camera.cameraInfo.exposureState.exposureCompensationIndex
      …
   }, mainExecutor)
  • Camera.CameraInfo.getExposureState() truy xuất ExposureState hiện tại, bao gồm:

    • Khả năng hỗ trợ của tính năng kiểm soát bù phơi sáng.
    • Trị số bù phơi sáng hiện tại.
    • Phạm vi trị số bù phơi sáng.
    • Bước bù phơi sáng được dùng trong phép tính giá trị bù phơi sáng.

Ví dụ: Mã sau đây khởi chạy chế độ cài đặt cho SeekBar phơi sáng với các giá trị ExposureState hiện tại:

Kotlin

val exposureState = camera.cameraInfo.exposureState
binding.seekBar.apply {
   isEnabled = exposureState.isExposureCompensationSupported
   max = exposureState.exposureCompensationRange.upper
   min = exposureState.exposureCompensationRange.lower
   progress = exposureState.exposureCompensationIndex
}

Tài nguyên khác

Để tìm hiểu thêm về CameraX, hãy tham khảo các tài liệu bổ sung sau đây.

Lớp học lập trình

  • Bắt đầu sử dụng CameraX
  • Đoạn mã mẫu

  • Ứng dụng mẫu CameraX
  • Cộng đồng các nhà phát triển

    Nhóm thảo luận của Android CameraX