Cấu trúc quay video của CameraX

Một hệ thống quay video thường ghi lại các luồng video và âm thanh, nén các luồng đó, kết hợp 2 luồng rồi ghi luồng kết quả vào ổ đĩa.

sơ đồ khái niệm cho một hệ thống quay video và ghi âm thanh
Hình 1. Sơ đồ khái niệm cho một hệ thống quay video và ghi âm thanh.

Trong CameraX, giải pháp quay video là trường hợp sử dụng VideoCapture:

sơ đồ khái niệm cho thấy cách CameraX xử lý trường hợp sử dụng quay video
Hình 2. Sơ đồ khái niệm cho thấy cách CameraX xử lý trường hợp sử dụng VideoCapture.

Như minh hoạ trong hình 2, chức năng quay video của CameraX có bao gồm một số thành phần cấu trúc cấp cao:

  • SurfaceProvider cho nguồn video.
  • AudioSource cho nguồn âm thanh.
  • 2 bộ mã hoá để mã hoá và nén video/âm thanh.
  • Một trình kết hợp nội dung đa phương tiện để kết hợp 2 luồng.
  • Một trình lưu tệp để ghi kết quả.

API VideoCapture trừu tượng hoá công cụ quay video phức tạp và mang lại cho các ứng dụng một API đơn giản và dễ hiểu hơn nhiều.

Tổng quan về API VideoCapture

VideoCapture là một trường hợp sử dụng của CameraX, hoạt động độc lập hoặc được kết hợp với các trường hợp sử dụng khác. Những cách kết hợp cụ thể được hỗ trợ phụ thuộc vào chức năng phần cứng của máy ảnh. Tuy nhiên, PreviewVideoCapture là cách kết hợp trường hợp sử dụng hợp lệ trên mọi thiết bị.

API VideoCapture bao gồm những đối tượng sau để kết nối với các ứng dụng:

  • VideoCapture là lớp trường hợp sử dụng cấp cao nhất. VideoCapture liên kết với LifecycleOwner bằng CameraSelector và các UseCase khác của CameraX. Để biết thêm thông tin về những khái niệm và cách sử dụng này, hãy xem bài viết Cấu trúc CameraX.
  • Recorder là một cách triển khai VideoOutput được kết hợp chặt chẽ với VideoCapture. Recorder được dùng để thực hiện quá trình quay video và ghi âm thanh. Một ứng dụng tạo bản ghi từ Recorder.
  • PendingRecording sẽ định cấu hình bản ghi, cung cấp các tuỳ chọn như bật âm thanh và đặt trình nghe sự kiện. Bạn phải dùng Recorder để tạo PendingRecording. PendingRecording không ghi lại nội dung nào.
  • Recording thực hiện quá trình ghi thực tế. Bạn phải dùng PendingRecording để tạo Recording.

Hình 3 cho thấy mối quan hệ giữa các đối tượng này:

sơ đồ cho thấy các hoạt động tương tác diễn ra trong trường hợp sử dụng quay video
Hình 3. Sơ đồ cho thấy các hoạt động tương tác diễn ra trong trường hợp sử dụng VideoCapture.

Chú giải:

  1. Tạo Recorder bằng QualitySelector.
  2. Định cấu hình Recorder bằng một trong các OutputOptions.
  3. Bật âm thanh bằng withAudioEnabled() nếu cần.
  4. Gọi start() bằng trình nghe VideoRecordEvent để bắt đầu quay video.
  5. Sử dụng pause()/resume()/stop() trên Recording để điều khiển quá trình quay video.
  6. Phản hồi VideoRecordEvents bên trong trình nghe sự kiện.

Danh sách API chi tiết nằm ở current.txt bên trong mã nguồn.

Sử dụng API VideoCapture

Để tích hợp trường hợp sử dụng VideoCapture của CameraX vào ứng dụng của bạn, hãy làm như sau:

  1. Liên kết VideoCapture.
  2. Chuẩn bị và định cấu hình quá trình quay video.
  3. Bắt đầu và điều khiển quá trình quay video trong thời gian chạy.

Các phần sau đây trình bày những việc bạn có thể làm ở mỗi bước để thực hiện phiên quay video hai đầu.

Liên kết VideoCapture

Để liên kết trường hợp sử dụng VideoCapure, hãy làm như sau:

  1. Tạo đối tượng Recorder.
  2. Tạo đối tượng VideoCapture.
  3. Liên kết với Lifecycle.

API VideoCapture của CameraX tuân theo mẫu thiết kế của trình tạo. Các ứng dụng dùng Recorder.Builder để tạo Recorder. Bạn cũng có thể định cấu hình độ phân giải video cho Recorder thông qua đối tượng QualitySelector.

Recorder của CameraX hỗ trợ các Qualities định sẵn sau đây cho độ phân giải video:

  • Quality.UHD cho kích thước video độ nét siêu cao 4K (2160p)
  • Quality.FHD cho kích thước video HD đầy đủ (1080p)
  • Quality.HD cho kích thước video HD (720p)
  • Quality.SD cho kích thước video SD (480p)

Lưu ý rằng CameraX cũng có thể chọn những độ phân giải khác khi được ứng dụng cho phép.

Kích thước video chính xác của từng lựa chọn phụ thuộc vào chức năng của máy ảnh và bộ mã hoá. Để biết thêm thông tin, hãy xem tài liệu về CamcorderProfile.

Các ứng dụng có thể định cấu hình độ phân giải bằng cách tạo một QualitySelector. Bạn có thể tạo QualitySelector bằng một trong các phương thức sau đây:

  • Hãy cung cấp một vài độ phân giải ưu tiên bằng cách sử dụng fromOrderedList(), đồng thời thêm chiến lược dự phòng để dùng trong trường hợp không có độ phân giải ưu tiên nào được hỗ trợ.

    CameraX có thể quyết định độ phân giải dự phòng phù hợp nhất dựa trên chức năng của máy ảnh đã chọn, hãy tham khảo FallbackStrategy specification của QualitySelector để biết thêm thông tin chi tiết. Ví dụ: mã sau đây yêu cầu độ phân giải cao nhất được hỗ trợ để quay video. Nếu không có độ phân giải yêu cầu nào được hỗ trợ, hãy cho phép CameraX chọn độ phân giải gần nhất với độ phân giải Quality.SD:

    val qualitySelector = QualitySelector.fromOrderedList(
             listOf(Quality.UHD, Quality.FHD, Quality.HD, Quality.SD),
             FallbackStrategy.lowerQualityOrHigherThan(Quality.SD))
    
  • Trước tiên, hãy truy vấn chức năng của máy ảnh rồi chọn trong số những độ phân giải được hỗ trợ bằng cách sử dụng QualitySelector::from():

    val cameraInfo = cameraProvider.availableCameraInfos.filter {
        Camera2CameraInfo
        .from(it)
        .getCameraCharacteristic(CameraCharacteristics.LENS\_FACING) == CameraMetadata.LENS_FACING_BACK
    }
    
    val supportedQualities = QualitySelector.getSupportedQualities(cameraInfo[0])
    val filteredQualities = arrayListOf (Quality.UHD, Quality.FHD, Quality.HD, Quality.SD)
                           .filter { supportedQualities.contains(it) }
    
    // Use a simple ListView with the id of simple_quality_list_view
    viewBinding.simpleQualityListView.apply {
        adapter = ArrayAdapter(context,
                               android.R.layout.simple_list_item_1,
                               filteredQualities.map { it.qualityToString() })
    
        // Set up the user interaction to manually show or hide the system UI.
        setOnItemClickListener { _, _, position, _ ->
            // Inside View.OnClickListener,
            // convert Quality.* constant to QualitySelector
            val qualitySelector = QualitySelector.from(filteredQualities[position])
    
            // Create a new Recorder/VideoCapture for the new quality
            // and bind to lifecycle
            val recorder = Recorder.Builder()
                .setQualitySelector(qualitySelector).build()
    
             // ...
        }
    }
    
    // A helper function to translate Quality to a string
    fun Quality.qualityToString() : String {
        return when (this) {
            Quality.UHD -> "UHD"
            Quality.FHD -> "FHD"
            Quality.HD -> "HD"
            Quality.SD -> "SD"
            else -> throw IllegalArgumentException()
        }
    }
    
    

    Lưu ý rằng chức năng trả về từ QualitySelector.getSupportedQualities() được đảm bảo sẽ hoạt động cho trường hợp sử dụng VideoCapture hoặc sự kết hợp giữa trường hợp sử dụng VideoCapturePreview. Khi liên kết với nhau bằng trường hợp sử dụng ImageCapture hoặc ImageAnalysis, CameraX có thể vẫn không liên kết được khi cách kết hợp yêu cầu không được hỗ trợ trên máy ảnh yêu cầu.

Sau khi bạn có QualitySelector, ứng dụng có thể tạo một đối tượng VideoCapture và thực hiện quá trình liên kết đó. Xin lưu ý rằng việc liên kết này giống với các trường hợp sử dụng khác:

val recorder = Recorder.Builder()
    .setExecutor(cameraExecutor).setQualitySelector(qualitySelector)
    .build()
val videoCapture = VideoCapture.withOutput(recorder)

try {
    // Bind use cases to camera
    cameraProvider.bindToLifecycle(
            this, CameraSelector.DEFAULT_BACK_CAMERA, preview, videoCapture)
} catch(exc: Exception) {
    Log.e(TAG, "Use case binding failed", exc)
}

Lưu ý rằng bindToLifecycle() sẽ trả về một đối tượng Camera. Hãy xem hướng dẫn này để biết thêm thông tin về cách kiểm soát đầu ra của máy ảnh, chẳng hạn như thu phóng và độ phơi sáng.

Recorder sẽ chọn định dạng phù hợp nhất với hệ thống. Bộ mã hóa và giải mã video phổ biến nhất là H.264 AVC) có định dạng vùng chứa MPEG-4.

Định cấu hình và tạo bản ghi

Từ Recorder, ứng dụng có thể tạo các đối tượng quay video để thực hiện quá trình quay video và ghi âm thanh. Các ứng dụng tạo bản ghi bằng cách làm như sau:

  1. Định cấu hình OutputOptions bằng prepareRecording().
  2. (Không bắt buộc) Bật tính năng ghi âm.
  3. Sử dụng start() để đăng ký trình nghe VideoRecordEvent và bắt đầu quay video.

Recorder sẽ trả về đối tượng Recording khi bạn gọi hàm start(). Ứng dụng của bạn có thể dùng đối tượng Recording này để hoàn tất quá trình quay video hoặc thực hiện các thao tác khác, chẳng hạn như tạm dừng hoặc tiếp tục.

Mỗi lần, Recorder hỗ trợ một đối tượng Recording. Bạn có thể bắt đầu quá trình quay video mới sau khi gọi Recording.stop() hoặc Recording.close() trên đối tượng Recording trước đó.

Hãy cùng tìm hiểu chi tiết các bước này. Trước tiên, ứng dụng sẽ định cấu hình OutputOptions cho Trình ghi bằng Recorder.prepareRecording(). Recorder hỗ trợ các loại OutputOptions sau:

  • FileDescriptorOutputOptions để ghi vào FileDescriptor.
  • FileOutputOptions để ghi vào File.
  • MediaStoreOutputOptions để ghi vào MediaStore.

Mọi loại OutputOptions đều cho phép bạn đặt kích thước tệp tối đa bằng setFileSizeLimit(). Các tuỳ chọn khác dành riêng cho từng loại đầu ra, chẳng hạn như ParcelFileDescriptor cho FileDescriptorOutputOptions.

prepareRecording() sẽ trả về một đối tượng PendingRecording. Đây là đối tượng trung gian dùng để tạo đối tượng Recording tương ứng. PendingRecording là một lớp tạm thời. Lớp này sẽ không hiển thị trong hầu hết các trường hợp và hiếm khi được ứng dụng lưu vào bộ nhớ đệm.

Các ứng dụng có thể định cấu hình thêm bản ghi, chẳng hạn như:

  • Bật âm thanh bằng withAudioEnabled().
  • Đăng ký một trình nghe để nhận các sự kiện quay video bằng start(Executor, Consumer<VideoRecordEvent>).
  • Dùng PendingRecording.asPersistentRecording() để cho phép một bản ghi quay liên tục trong khi VideoCapture đính kèm bản ghi đó được chuyển lại một camera khác.

Để bắt đầu quay video, hãy gọi PendingRecording.start(). CameraX sẽ chuyển PendingRecording thành Recording, đưa yêu cầu quay video vào hàng đợi và trả về đối tượng Recording mới tạo cho ứng dụng. Sau khi quá trình quay video bắt đầu trên thiết bị Máy ảnh tương ứng, CameraX sẽ gửi một sự kiện VideoRecordEvent.EVENT_TYPE_START.

Ví dụ sau đây cho biết cách quay video và ghi âm thanh vào tệp MediaStore:

// Create MediaStoreOutputOptions for our recorder
val name = "CameraX-recording-" +
        SimpleDateFormat(FILENAME_FORMAT, Locale.US)
                .format(System.currentTimeMillis()) + ".mp4"
val contentValues = ContentValues().apply {
   put(MediaStore.Video.Media.DISPLAY_NAME, name)
}
val mediaStoreOutput = MediaStoreOutputOptions.Builder(this.contentResolver,
                              MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
                              .setContentValues(contentValues)
                              .build()

// 2. Configure Recorder and Start recording to the mediaStoreOutput.
val recording = videoCapture.output
                .prepareRecording(context, mediaStoreOutput)
                .withAudioEnabled()
                .start(ContextCompat.getMainExecutor(this), captureListener)

Mặc dù bản xem trước của camera được phản chiếu trên camera trước theo mặc định nhưng video quay bằng VideoCapture lại không như vậy. Với CameraX 1.3, giờ đây, bạn có thể phản chiếu các bản ghi video để bản xem trước của camera trước và video đã quay trùng khớp nhau.

Có 3 lựa chọn MirrorMode: MIRROR_MODE_OFF, MIRROR_MODE_ON và MIRROR_MODE_ON_FRONT_ONLY. Để phù hợp với bản xem trước của camera, Google đề xuất sử dụng MIROR_MODE_ON_FRONT_ONLY, tức là tính năng phản chiếu không được bật cho camera sau nhưng lại được bật cho camera trước. Để biết thêm thông tin về MirrorMode, hãy xem MirrorMode constants.

Đoạn mã này cho biết cách gọi VideoCapture.Builder.setMirrorMode() bằng MIRROR_MODE_ON_FRONT_ONLY. Để biết thêm thông tin, hãy xem setMirrorMode().

Kotlin


val recorder = Recorder.Builder().build()

val videoCapture = VideoCapture.Builder(recorder)
    .setMirrorMode(MIRROR_MODE_ON_FRONT_ONLY)
    .build()

useCases.add(videoCapture);

Java


Recorder.Builder builder = new Recorder.Builder();
if (mVideoQuality != QUALITY_AUTO) {
    builder.setQualitySelector(
        QualitySelector.from(mVideoQuality));
}
  VideoCapture<Recorder> videoCapture = new VideoCapture.Builder<>(builder.build())
      .setMirrorMode(MIRROR_MODE_ON_FRONT_ONLY)
      .build();
    useCases.add(videoCapture);

Điều khiển quá trình quay video đang hoạt động

Bạn có thể tạm dừng, tiếp tục và dừng Recording đang diễn ra thông qua các phương thức sau:

  • pause để tạm dừng quá trình quay video đang hoạt động.
  • resume() để tiếp tục quá trình quay video đã tạm dừng.
  • stop() để hoàn tất quá trình quay video và xoá mọi đối tượng quay video liên quan.
  • mute() để tắt hoặc bật tiếng bản ghi video hiện tại.

Xin lưu ý rằng bạn có thể gọi stop() để chấm dứt Recording, bất kể quá trình quay video ở trạng thái đã tạm dừng hay đang hoạt động.

Nếu bạn đã đăng ký EventListener bằng PendingRecording.start(), thì Recording sẽ giao tiếp thông qua VideoRecordEvent.

  • VideoRecordEvent.EVENT_TYPE_STATUS được dùng cho số liệu thống kê quay video, chẳng hạn như kích thước tệp hiện tại và khoảng thời gian được quay.
  • VideoRecordEvent.EVENT_TYPE_FINALIZE được dùng cho kết quả quay video và bao gồm thông tin như URI của tệp cuối cùng kèm theo mọi lỗi liên quan.

Sau khi ứng dụng của bạn nhận được EVENT_TYPE_FINALIZE cho biết phiên quay video thành công, bạn có thể truy cập vào video đã quay từ vị trí được chỉ định trong OutputOptions.

Tài nguyên khác

Để tìm hiểu thêm về CameraX, hãy xem các tài nguyên bổ sung sau đây: