ย้ายข้อมูล Camera1 ไปยัง CameraX

หากแอปใช้ Camera เวอร์ชันเดิม class ("camera1") ซึ่งเลิกใช้งานแล้วตั้งแต่ Android 5.0 (API ระดับ 21) ขอแนะนำให้อัปเดตเป็น Android Camera API ที่ทันสมัย ข้อเสนอสำหรับ Android cameraX (กล้อง Jetpack ที่มีมาตรฐานและทนทาน API) และ camera2 (API เฟรมเวิร์กระดับต่ำ) สำหรับ ในกรณีส่วนใหญ่ เราขอแนะนำให้ย้ายข้อมูลแอปไปยัง CameraX เหตุผลก็คือ:

  • ใช้งานง่าย: CameraX จะจัดการรายละเอียดในระดับต่ำเพื่อให้คุณ ต้องการน้อยลงในการสร้างประสบการณ์การใช้งานกล้องตั้งแต่ต้นและอื่นๆ สร้างความแตกต่างให้แอปของคุณ
  • กล้องถ่ายรูป X จัดการการกระจาย Fragment ให้คุณ: CameraX ช่วยลดการใช้งานระยะยาว มีค่าใช้จ่ายในการบำรุงรักษาและโค้ดเฉพาะอุปกรณ์ ซึ่งทำให้ได้รับประสบการณ์การใช้งานที่มีคุณภาพสูงขึ้น ให้แก่ผู้ใช้ ดูข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้ได้ที่ อุปกรณ์เข้ากันได้กับ CameraX มากขึ้น บล็อกโพสต์
  • ความสามารถขั้นสูง: CameraX ได้รับการออกแบบมาอย่างพิถีพิถันเพื่อสร้าง ฟังก์ชันที่ง่ายต่อการผสานรวมลงในแอปของคุณ ตัวอย่างเช่น คุณสามารถ ใช้โบเก้, รีทัชใบหน้า, HDR (ช่วงไดนามิกสูง) และความสว่างแสงน้อย โหมดจับภาพกลางคืนสำหรับรูปภาพของคุณด้วย ส่วนขยาย CameraX
  • ความสามารถในการอัปเดต: Android เปิดตัวความสามารถและการแก้ไขข้อบกพร่องใหม่ๆ ใน CameraX ตลอดทั้งปี เมื่อย้ายข้อมูลไปยัง CameraX แอปของคุณจะได้ใช้ Android เวอร์ชันล่าสุด เทคโนโลยีกล้อง กับ CameraX แต่ละรุ่น ไม่ใช่แค่ Android เวอร์ชันประจำปีที่เปิดตัวใหม่

ในคู่มือนี้ คุณจะได้พบสถานการณ์ที่พบบ่อยสำหรับแอปพลิเคชันกล้อง ชิ้น สถานการณ์รวมถึงการใช้ Camera1 และการใช้งาน CameraX สำหรับ เปรียบเทียบ

เมื่อพูดถึงการย้ายข้อมูล บางครั้งคุณต้องการความยืดหยุ่นเป็นพิเศษในการผสานรวม ด้วยฐานของโค้ดที่มีอยู่ โค้ด CameraX ทั้งหมดในคู่มือนี้มี CameraController เหมาะอย่างยิ่ง หากคุณต้องการวิธีที่ง่ายที่สุดในการใช้ CameraX รวมถึง CameraProvider เหมาะอย่างยิ่งหากคุณต้องการความยืดหยุ่นมากขึ้น เพื่อช่วยให้คุณตัดสินใจเลือก ตัวเลือกที่เหมาะกับคุณ ต่อไปนี้คือประโยชน์ของแต่ละวิธี

คอนโทรลเลอร์กล้อง

ผู้ให้บริการกล้อง

ต้องใช้รหัสการตั้งค่าเพียงเล็กน้อย ควบคุมได้มากขึ้น
การอนุญาตให้ CameraX จัดการขั้นตอนการตั้งค่าได้มากขึ้นหมายความว่า ฟังก์ชันอย่างเช่น "แตะเพื่อโฟกัส" หรือการบีบนิ้วเพื่อซูม จะทำงานโดยอัตโนมัติ เนื่องจากนักพัฒนาแอปเป็นผู้จัดการการตั้งค่า จึงมีโอกาสมากขึ้น เพื่อปรับแต่งการกำหนดค่า เช่น เปิดใช้การหมุนภาพเอาต์พุต หรือตั้งค่ารูปแบบรูปภาพเอาต์พุตใน ImageAnalysis
การกำหนดให้ใช้ PreviewView สำหรับการแสดงตัวอย่างจากกล้องอนุญาตให้ CameraX จะมอบการผสานรวมที่ราบรื่นจากต้นทางถึงปลายทาง เช่นเดียวกับใน ML Kit ของเรา การผสานรวมซึ่งสามารถแมปพิกัดผลลัพธ์ของโมเดล ML (เช่น ใบหน้า กรอบล้อมรอบ) ไปยังพิกัดตัวอย่างโดยตรง ความสามารถในการใช้ "พื้นผิว" ที่กำหนดเองสำหรับการแสดงตัวอย่างจากกล้องช่วยให้ มีความยืดหยุ่นมากขึ้น เช่น การใช้โค้ด "Surface" ที่มีอยู่ อาจเป็นอินพุตไปยังส่วนอื่นๆ ของแอป

หากพบปัญหาในการย้ายข้อมูล โปรดติดต่อเราที่ กลุ่มสนทนา CameraX

ก่อนย้ายข้อมูล

เปรียบเทียบการใช้งาน CameraX กับ Camera1

แม้ว่าโค้ดอาจดูแตกต่างออกไป แต่แนวคิดเบื้องหลังใน Camera1 และ CameraX มีความคล้ายคลึงกันมาก กล้องถ่ายรูป X รวมฟังก์ชันการทำงานทั่วไปของกล้องไว้ในกรณีการใช้งาน ทำให้หลายๆ งานที่เราทิ้งไว้ให้นักพัฒนาซอฟต์แวร์ใน Camera1 CameraX จะได้รับการจัดการโดยอัตโนมัติ มี 4 อย่าง UseCase ใน CameraX ที่คุณสามารถ ใช้กับงานต่างๆ ของกล้อง เช่น Preview, ImageCapture, VideoCapture และ ImageAnalysis

ตัวอย่างหนึ่งของ CameraX ในการจัดการรายละเอียดระดับต่ำสำหรับนักพัฒนาซอฟต์แวร์คือ ViewPort ที่ใช้ร่วมกันระหว่าง UseCase ที่ใช้งานอยู่ วิธีนี้ช่วยให้ UseCase ทั้งหมดเห็นพิกเซลเดียวกันพอดี ใน Camera1 คุณจะต้องจัดการรายละเอียดเหล่านี้ด้วยตนเอง และเนื่องจากมีการเปลี่ยนแปลง ในอัตราส่วนของอุปกรณ์ต่างๆ เซ็นเซอร์และหน้าจอของกล้อง อาจเป็นเรื่องยากที่จะ ตรวจสอบว่าตัวอย่างตรงกับรูปภาพและวิดีโอที่ถ่าย

อีกตัวอย่างหนึ่งคือ CameraX จะจัดการ Callback ของ Lifecycle โดยอัตโนมัติใน Lifecycle อินสแตนซ์ที่คุณสอบผ่าน หมายความว่า CameraX จะจัดการแอปของคุณ กับกล้องตลอดเวลา วงจรกิจกรรม Android รวมถึงกรณีต่อไปนี้ เช่น การปิดกล้องเมื่อแอปของคุณเข้าไปอยู่ใน พื้นหลัง; นำการแสดงตัวอย่างจากกล้องออกเมื่อไม่ต้องใช้หน้าจอแล้ว เพื่อแสดง และการหยุดการแสดงตัวอย่างของกล้องชั่วคราว เมื่อมีกิจกรรมอื่นเกิดขึ้น ลำดับความสำคัญในเบื้องหน้า เช่น วิดีโอคอลเข้า

สุดท้าย CameraX จัดการการหมุนและการปรับขนาดโดยไม่ต้องมีโค้ดเพิ่มเติม ในส่วนของคุณ ในกรณีของ Activity ที่มีการวางแนวที่ปลดล็อกแล้ว พารามิเตอร์ การตั้งค่า UseCase จะทำทุกครั้งที่หมุนอุปกรณ์ เนื่องจากระบบทำลาย และสร้าง Activity ขึ้นใหม่เมื่อเปลี่ยนการวางแนว ซึ่งส่งผลให้เกิด UseCases กำลังตั้งค่าการหมุนเป้าหมายให้ตรงกับการวางแนวของจอแสดงผลตาม เริ่มต้นทุกครั้ง อ่านเพิ่มเติมเกี่ยวกับการหมุนใน CameraX

ก่อนลงลึกในรายละเอียด มาดูภาพรวมกัน UseCase และความเกี่ยวข้องกับแอป Camera1 (แนวคิด CameraX อยู่ใน blue และกล้องถ่ายรูป 1 แนวคิดต่างๆ จะอยู่ใน green)

กล้องถ่ายรูป X

การกำหนดค่า CameraController / CameraProvider
แสดงตัวอย่าง จับภาพ การจับภาพวิดีโอ การวิเคราะห์รูปภาพ
จัดการ Surface เวอร์ชันตัวอย่างและตั้งค่าในกล้อง ตั้งค่า PictureCallback และ calltakePicture() ในกล้องถ่ายรูป จัดการการกำหนดค่ากล้องและ MediaRecorder ตามลำดับที่ระบุ โค้ดการวิเคราะห์ที่กำหนดเองซึ่งสร้างขึ้นที่ด้านบนของ Surface ตัวอย่าง
รหัสเฉพาะอุปกรณ์
การจัดการการหมุนและการปรับขนาดอุปกรณ์
การจัดการเซสชันของกล้อง (การเลือกกล้อง การจัดการอายุการใช้งาน)

กล้อง 1

ความเข้ากันได้และประสิทธิภาพใน CameraX

CameraX รองรับอุปกรณ์ที่ใช้ Android 5.0 (API ระดับ 21) ขึ้นไป ช่วงเวลานี้ คิดเป็นกว่า 98% ของอุปกรณ์ Android ที่มีอยู่แล้ว CameraX สร้างมาเพื่อจัดการ ความแตกต่างระหว่างอุปกรณ์โดยอัตโนมัติ ซึ่งช่วยลดความจำเป็นในการใช้ข้อมูลเฉพาะอุปกรณ์ โค้ดในแอปของคุณ นอกจากนี้ เรายังทดสอบอุปกรณ์จริงกว่า 150 เครื่องใน Android ทุกรุ่น ตั้งแต่ 5.0 ใน cameraX Test Lab ของเรา คุณ สามารถตรวจสอบรายการทั้งหมด อุปกรณ์ที่อยู่ใน Test Lab ในปัจจุบัน

CameraX ใช้Executorเพื่อ ให้กองซ้อนกล้อง คุณสามารถ ตั้งค่าผู้ดำเนินการของคุณเองใน CameraX หากแอปมีข้อกำหนดการแยกชุดข้อความที่เฉพาะเจาะจง หากไม่ได้ตั้งค่าไว้ CameraX จะสร้าง และใช้ Executor ภายในเริ่มต้นที่ได้รับการเพิ่มประสิทธิภาพ API ของแพลตฟอร์มจำนวนมากเปิดอยู่ ซึ่ง CameraX สร้างขึ้นต้องมีการบล็อกการสื่อสารระหว่างโปรเซส (IPC) ด้วย ฮาร์ดแวร์ที่อาจใช้เวลาหลายร้อยมิลลิวินาทีในการตอบสนอง สำหรับกรณีนี้ CameraX จะเรียก API เหล่านี้จากเทรดเบื้องหลังเท่านั้น ซึ่งช่วยให้แน่ใจว่า เทรดหลักจะไม่ถูกบล็อกและ UI ยังคงใช้งานได้อย่างต่อเนื่อง อ่านเพิ่มเติมเกี่ยวกับชุดข้อความ

หากตลาดเป้าหมายของแอปมีทั้งอุปกรณ์ระดับโลว์เอนด์ CameraX ให้บริการ วิธีลดเวลาในการตั้งค่า ตัวจำกัดกล้อง ตั้งแต่ การเชื่อมต่อกับส่วนประกอบของฮาร์ดแวร์อาจมี โดยเฉพาะในอุปกรณ์ระดับโลว์เอนด์ คุณจะระบุชุดกล้องที่แอปของคุณ ความต้องการ CameraX จะเชื่อมต่อกับกล้องเหล่านี้ในระหว่างการตั้งค่าเท่านั้น ตัวอย่างเช่น หาก แอปพลิเคชันใช้เฉพาะกล้องหลังที่สามารถกำหนดค่านี้ได้ ใช้ DEFAULT_BACK_CAMERA และ CameraX หลีกเลี่ยงการเริ่มต้นด้านหน้า เพื่อลดเวลาในการตอบสนอง

แนวคิดในการพัฒนา Android

คู่มือนี้จะถือว่ามีความคุ้นเคยโดยทั่วไปกับการพัฒนาแอป Android นอกเหนือจาก ข้อมูลเบื้องต้นเกี่ยวกับแนวคิด 2-3 ข้อ ที่มีประโยชน์ในการทำความเข้าใจ ข้ามไปที่โค้ดด้านล่าง:

ย้ายข้อมูลสถานการณ์ที่พบบ่อย

ส่วนนี้จะอธิบายวิธีย้ายข้อมูลสถานการณ์ทั่วไปจาก Camera1 ไปยัง CameraX แต่ละสถานการณ์จะครอบคลุมการใช้งาน Camera1, CameraX CameraProvider และการใช้งาน CameraX CameraController

การเลือกกล้อง

ในแอปพลิเคชันกล้องถ่ายรูป สิ่งแรกๆ ที่คุณอาจต้องการนำเสนอคือ วิธีเลือกกล้องต่างๆ

กล้อง 1

ใน Camera1 คุณสามารถเรียกใช้ Camera.open() ที่ไม่มีพารามิเตอร์ เพื่อเปิดกล้องหลังตัวแรก หรือคุณสามารถส่งรหัสตัวเลขจำนวนเต็มสำหรับ กล้องที่ต้องการเปิด ตัวอย่างลักษณะที่อาจมีลักษณะดังนี้

// 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

คลาส CameraSelector จะจัดการการเลือกกล้องใน CameraX กล้องถ่ายรูป X ทำให้การใช้งานกล้องเริ่มต้นเป็นเรื่องง่าย คุณสามารถระบุได้ว่า คุณต้องการกล้องหน้าเริ่มต้นหรือกล้องหลังเริ่มต้น นอกจากนี้ ออบเจ็กต์ CameraControl ของ CameraX ช่วยให้คุณ ตั้งค่าระดับการซูมสำหรับแอป ดังนั้นหาก แอปของคุณกำลังทำงานในอุปกรณ์ที่รองรับ กล้องตรรกะ แล้วกล้องจะเปลี่ยนไป กับเลนส์ที่เหมาะสม

นี่คือรหัส CameraX สำหรับใช้กล้องหลังเริ่มต้นที่มี 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

ต่อไปนี้คือตัวอย่างการเลือกกล้องหน้าเริ่มต้นที่มี CameraProvider (กล้องหน้าหรือกล้องหลังสามารถใช้กับ CameraController หรือ 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()
    }
}

หากต้องการควบคุมว่าจะเลือกกล้องใด ก็ทําได้เช่นกันใน CameraX หากคุณใช้ CameraProvider โดยการโทร getAvailableCameraInfos(), ซึ่งมอบออบเจ็กต์ CameraInfo ให้คุณตรวจสอบคุณสมบัติบางอย่างของกล้อง เช่น isFocusMeteringSupported() จากนั้นคุณอาจแปลงเป็น CameraSelector เพื่อใช้เหมือนกับข้างต้นได้ ตัวอย่างที่ใช้เมธอด CameraInfo.getCameraSelector()

คุณสามารถดูรายละเอียดเพิ่มเติมเกี่ยวกับกล้องแต่ละตัวได้โดยใช้ Camera2CameraInfo โทร getCameraCharacteristic() พร้อมด้วยคีย์สำหรับข้อมูลกล้องที่คุณต้องการ ตรวจสอบ CameraCharacteristics สำหรับรายการคีย์ทั้งหมดที่คุณสามารถค้นหาได้

นี่คือตัวอย่างที่ใช้ฟังก์ชัน checkFocalLength() ที่กำหนดเอง ซึ่งคุณสามารถ กำหนดตัวคุณเอง:

// 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()

แสดงตัวอย่าง

แอปพลิเคชันกล้องส่วนใหญ่ต้องแสดงฟีดกล้องบนหน้าจอ คะแนน เมื่อใช้ Camera1 คุณจะต้องจัดการ Callback ของวงจรอย่างถูกต้อง และ คุณยังต้องกำหนดการหมุนและการปรับขนาดสำหรับการแสดงตัวอย่างของคุณด้วย

นอกจากนี้ ใน Camera1 คุณจะต้องตัดสินใจว่าจะใช้ TextureView หรือ SurfaceView เป็นแพลตฟอร์มแสดงตัวอย่าง ทั้ง 2 ตัวเลือกมาพร้อมกับข้อเสีย และไม่ว่าในกรณีใด Camera1 กำหนดให้คุณต้องดำเนินการต่อไปนี้ จัดการการหมุนและการปรับขนาดอย่างถูกต้อง PreviewView ของ CameraX ในอุปกรณ์อื่น ได้ติดตั้งใช้งานทั้ง TextureView และ SurfaceView CameraX จะเลือกการใช้งานที่ดีที่สุดโดยขึ้นอยู่กับปัจจัยต่างๆ เช่น ประเภทอุปกรณ์และเวอร์ชัน Android ที่แอปของคุณใช้อยู่ หากมี ที่เข้ากันได้ คุณสามารถประกาศค่ากำหนดของคุณด้วย PreviewView.ImplementationMode ตัวเลือก COMPATIBLE จะใช้ TextureView สำหรับการแสดงตัวอย่าง และ ค่า PERFORMANCE ใช้ SurfaceView (เมื่อเป็นไปได้)

กล้อง 1

หากต้องการแสดงตัวอย่าง คุณต้องเขียนชั้นเรียน Preview ของคุณเองด้วย การนำ android.view.SurfaceHolder.Callback อินเทอร์เฟซ ซึ่งใช้สำหรับส่งข้อมูลภาพจากฮาร์ดแวร์กล้องไปยัง แอปพลิเคชัน จากนั้นก่อนเริ่มแสดงตัวอย่างรูปภาพแบบสด Preview ต้องส่งคลาสไปยังออบเจ็กต์ 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

ใน CameraX คุณซึ่งเป็นนักพัฒนาซอฟต์แวร์จะต้องจัดการสิ่งต่างๆ น้อยลงมาก หากคุณใช้ CameraController คุณต้องใช้ PreviewView ด้วย ซึ่งหมายความว่า Previewเป็น UseCase ซึ่งก็คือ ทำให้การตั้งค่าทำงานน้อยลงมาก

// 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

เมื่อใช้ CameraProvider ของ CameraX คุณไม่จำเป็นต้องใช้ PreviewView แต่ ยังทำให้การตั้งค่าการแสดงตัวอย่าง ผ่าน Camera1 ง่ายขึ้นมาก สำหรับการสาธิต ตัวอย่างนี้ใช้ PreviewView แต่คุณสามารถเขียน SurfaceProvider เพื่อผ่านไปยัง setSurfaceProvider() หากคุณมีทางที่ซับซ้อนขึ้น ความต้องการ

ในที่นี้ Preview UseCase ไม่ได้บอกเป็นนัยว่าเป็นเช่นนั้น CameraController คุณจึงต้องตั้งค่าดังนี้

// 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()
    }
}

แตะเพื่อโฟกัส

เมื่อการแสดงตัวอย่างจากกล้องปรากฏบนหน้าจอ การควบคุมทั่วไปคือการกำหนดโฟกัส จุดเมื่อผู้ใช้แตะตัวอย่าง

กล้อง 1

หากต้องการใช้การแตะเพื่อโฟกัสใน Camera1 คุณต้องคำนวณโฟกัสที่เหมาะสมที่สุด Area เพื่อระบุว่า Camera ควรพยายามโฟกัสที่จุดใด Areaเครื่องนี้ ส่งไปยัง setFocusAreas() นอกจากนี้ คุณต้องตั้งโหมดโฟกัสที่เข้ากันได้ใน Camera พื้นที่โฟกัสจะมีผลเมื่อโหมดโฟกัสปัจจุบันคือ FOCUS_MODE_AUTO, FOCUS_MODE_MACRO, FOCUS_MODE_CONTINUOUS_VIDEO หรือ FOCUS_MODE_CONTINUOUS_PICTURE

Area แต่ละรายการคือสี่เหลี่ยมผืนผ้าที่มีน้ำหนักที่ระบุ น้ำหนักคือค่าระหว่าง 1 และ 1000 และใช้เพื่อจัดลําดับความสําคัญของโฟกัส Areas หากมีการตั้งค่าไว้หลายรายการ ช่วงเวลานี้ ตัวอย่างนั้นใช้ Area เพียง 1 รายการ ดังนั้นค่าน้ำหนักจึงไม่มีความสำคัญ พิกัดของ จะอยู่ในช่วงของสี่เหลี่ยมผืนผ้าตั้งแต่ -1000 ถึง 1000 จุดซ้ายบนคือ (-1,000, -1000) จุดขวาล่างคือ (1,000, 1000) ทิศทางจะสัมพัทธ์กับเซ็นเซอร์ นั่นคือ สิ่งที่เซ็นเซอร์เห็น ทิศทางจะไม่ได้รับผลกระทบจาก การหมุนหรือการมิเรอร์ของ Camera.setDisplayOrientation() คุณจึงต้อง แปลงพิกัดเหตุการณ์การแตะเป็นพิกัดเซ็นเซอร์

// 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 จะฟังกิจกรรมการแตะของ PreviewView เพื่อจัดการ แตะเพื่อโฟกัสโดยอัตโนมัติ คุณเปิดและปิดใช้ฟีเจอร์แตะเพื่อโฟกัสได้ด้วย setTapToFocusEnabled() แล้วตรวจสอบค่าด้วย Getter ที่เกี่ยวข้อง isTapToFocusEnabled()

getTapToFocusState() แสดงผลออบเจ็กต์ LiveData สำหรับการติดตามการเปลี่ยนแปลงสถานะการโฟกัสใน 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

ต้องมีการตั้งค่าบางอย่างเมื่อใช้ CameraProvider จึงจะใช้ฟีเจอร์แตะเพื่อโฟกัสได้ ใช้งานได้ ตัวอย่างนี้จะสมมติว่าคุณกำลังใช้ PreviewView หากไม่เป็นเช่นนั้น คุณต้อง ปรับตรรกะให้มีผลกับ Surface ที่กําหนดเอง

ขั้นตอนเมื่อใช้ PreviewView มีดังนี้

  1. ตั้งค่าตัวตรวจจับท่าทางสัมผัสเพื่อจัดการเหตุการณ์การแตะ
  2. ในกิจกรรมการแตะ ให้สร้าง MeteringPoint โดยใช้ MeteringPointFactory.createPoint()
  3. สร้าง FocusMeteringAction ด้วย MeteringPoint
  4. พร้อมออบเจ็กต์ CameraControl บน Camera (ส่งคืนจาก bindToLifecycle()) เรียกใช้ startFocusAndMetering() ผ่านใน FocusMeteringAction
  5. (ไม่บังคับ) ตอบกลับ FocusMeteringResult
  6. ตั้งค่าตัวตรวจจับท่าทางสัมผัสเพื่อตอบสนองต่อกิจกรรมการสัมผัสใน 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
}

บีบเพื่อซูม

การซูมเข้าและออกจากการแสดงตัวอย่างเป็น การควบคุมโดยตรงอีกอย่างหนึ่งของ แสดงตัวอย่างจากกล้อง จำนวนกล้องบนอุปกรณ์ ที่เพิ่มมากขึ้น ผู้ใช้ ระบบจะเลือกเลนส์ที่มีความยาวโฟกัสดีที่สุดโดยอัตโนมัติเป็น จากการซูม

กล้อง 1

การซูมโดยใช้ Camera1 มี 2 วิธี เมธอด Camera.startSmoothZoom() จะเคลื่อนไหวจากระดับการซูมปัจจุบันถึงระดับการซูมที่คุณเลื่อนเข้า เมธอด Camera.Parameters.setZoom() จะข้ามไปยังระดับการซูมที่คุณผ่านโดยตรง นิ้ว ก่อนใช้บริการอย่างใดอย่างหนึ่ง โปรดโทรติดต่อ isSmoothZoomSupported() หรือ isZoomSupported() ตามลำดับ เพื่อให้มั่นใจได้ว่าจะใช้วิธีการย่อ/ขยายที่เกี่ยวข้องตามที่คุณต้องการ ที่มีอยู่ในกล้องถ่ายรูป

ในการใช้การบีบเพื่อซูม ตัวอย่างนี้ใช้ setZoom() เนื่องจากการแตะ Listener บนพื้นผิวตัวอย่างทำให้เหตุการณ์เริ่มทำงานอย่างต่อเนื่องเป็นการบีบ ด้วยท่าทางสัมผัส ระบบจึงอัปเดตระดับการซูมทันทีทุกครั้ง คลาส ZoomTouchListener กำหนดไว้ด้านล่าง และควรตั้งค่าเป็น Callback กับ Listener ระบบสัมผัสของแพลตฟอร์มตัวอย่าง

// 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

CameraController จะฟังการแตะของ PreviewView คล้ายกับการแตะเพื่อโฟกัส เหตุการณ์ที่จะใช้การบีบเพื่อซูมโดยอัตโนมัติ คุณจะเปิดและปิดใช้ได้ บีบเพื่อซูมด้วย setPinchToZoomEnabled() แล้วตรวจสอบค่าด้วย Getter ที่เกี่ยวข้อง isPinchToZoomEnabled()

getZoomState() จะแสดงออบเจ็กต์ LiveData สำหรับการติดตามการเปลี่ยนแปลงไปยัง ZoomState ใน 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

หากต้องการใช้การบีบเพื่อซูมกับ CameraProvider ต้องมีการตั้งค่าบางอย่าง ถ้า คุณไม่ได้ใช้ PreviewView คุณต้องปรับตรรกะให้มีผลกับ Surface ที่กำหนดเอง

ขั้นตอนเมื่อใช้ PreviewView มีดังนี้

  1. ตั้งค่าตัวตรวจจับท่าทางสัมผัสการปรับขนาดเพื่อจัดการเหตุการณ์การบีบนิ้ว
  2. รับ ZoomState จากออบเจ็กต์ Camera.CameraInfo โดยที่ Camera แสดงอินสแตนซ์เมื่อคุณเรียกใช้ bindToLifecycle()
  3. หาก ZoomState มีค่า zoomRatio ให้บันทึกเป็นการซูมปัจจุบัน หากไม่มีzoomRatioใน ZoomState ให้ใช้ค่าเริ่มต้นของกล้อง อัตราการซูม (1.0)
  4. คูณผลคูณของอัตราส่วนการซูมปัจจุบันด้วย scaleFactor เพื่อ กำหนดอัตราส่วนการซูมใหม่ แล้วส่งผ่านไปยัง CameraControl.setZoomRatio()
  5. ตั้งค่าตัวตรวจจับท่าทางสัมผัสเพื่อตอบสนองต่อกิจกรรมการสัมผัสใน 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
}

การถ่ายรูป

ส่วนนี้จะแสดงวิธีเรียกใช้การจับภาพ ไม่ว่าคุณจะต้องทำใน การกดปุ่มชัตเตอร์ หลังจากผ่านตัวจับเวลาไป หรือเมื่อเกิดเหตุการณ์อื่นๆ การเลือก

กล้อง 1

ใน Camera1 ก่อนอื่นคุณต้องกำหนด Camera.PictureCallback เพื่อจัดการข้อมูลภาพเมื่อมีการขอ ต่อไปนี้เป็นตัวอย่างง่ายๆ ของ PictureCallback สำหรับจัดการข้อมูลรูปภาพ 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)
    }
}

จากนั้นเมื่อต้องการถ่ายภาพ ให้เรียกใช้เมธอด takePicture() ในอินสแตนซ์ Camera เมธอด takePicture() นี้มี 3 วิธีที่แตกต่างกัน พารามิเตอร์สำหรับข้อมูลประเภทต่างๆ พารามิเตอร์แรกมีไว้สำหรับ ShutterCallback (ซึ่งไม่ได้กำหนดไว้ในตัวอย่างนี้) พารามิเตอร์ที่สองคือ สำหรับ PictureCallback ในการจัดการข้อมูลกล้องที่เป็นข้อมูลดิบ (ไม่บีบอัด) ที่ 3 พารามิเตอร์คือพารามิเตอร์ที่ตัวอย่างนี้ใช้ เนื่องจากเป็นพารามิเตอร์ PictureCallback ในการจัดการ ข้อมูลรูปภาพ JPEG

// Camera1: call takePicture on Camera instance, passing our PictureCallback.

camera?.takePicture(null, null, picture)

CameraX: CameraController

CameraController ของ CameraX ยังคงรักษาความเรียบง่ายของ Camera1 สำหรับรูปภาพ โดยใช้เมธอด takePicture() ของตนเอง ในที่นี้ ให้นิยาม เพื่อกำหนดค่ารายการ MediaStore และถ่ายภาพเพื่อบันทึกไว้ในนั้น

// 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

การถ่ายภาพด้วย CameraProvider จะทำงานในลักษณะเดียวกับ CameraController แต่คุณต้องสร้างและเชื่อมโยง ImageCapture ก่อน UseCase เพื่อให้มีออบเจ็กต์สำหรับเรียก takePicture():

// 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)

จากนั้น เมื่อต้องการถ่ายภาพ คุณสามารถโทรหา ImageCapture.takePicture() ดูรหัส CameraController ในส่วนนี้ สำหรับตัวอย่างที่สมบูรณ์ของฟังก์ชัน 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(
        ...
    )
}

การบันทึกวิดีโอ

การบันทึกวิดีโอมีความซับซ้อนกว่าสถานการณ์ที่เห็นอย่างมาก จนถึงตอนนี้ กระบวนการแต่ละส่วนจะต้องได้รับการตั้งค่าอย่างถูกต้อง โดยทั่วไปใน คำสั่งซื้อนั้นๆ นอกจากนี้ คุณอาจต้องตรวจสอบว่าวิดีโอและเสียงอยู่ในรูปแบบ ซิงค์หรือจัดการกับความไม่สอดคล้องกันของอุปกรณ์เพิ่มเติม

คุณจะเห็นได้ว่า CameraX จัดการกับความซับซ้อนนี้มากมายให้คุณอีกครั้ง

กล้อง 1

การจับภาพวิดีโอโดยใช้ Camera1 ต้องมีการจัดการ Camera และ MediaRecorder และวิธีการต้อง ตามลำดับที่กำหนด คุณต้องทำตามคำสั่งซื้อนี้สำหรับ เพื่อให้แอปพลิเคชันทำงานได้อย่างถูกต้อง

  1. เปิดกล้องถ่ายรูป
  2. เตรียมความพร้อมและเริ่มแสดงตัวอย่าง (หากแอปแสดงวิดีโอที่กําลังบันทึก ซึ่งมักจะเป็นเช่นนั้น)
  3. ปลดล็อกกล้องเพื่อให้ MediaRecorder ใช้ได้โดยโทรหา Camera.unlock()
  4. กำหนดค่าการบันทึกโดยเรียกใช้เมธอดเหล่านี้ใน MediaRecorder:
    1. เชื่อมต่ออินสแตนซ์ Camera กับ setCamera(camera)
    2. โทร setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
    3. โทร setVideoSource(MediaRecorder.VideoSource.CAMERA)
    4. โทรติดต่อ setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_1080P)) เพื่อตั้งค่าคุณภาพ โปรดดู CamcorderProfile สำหรับ ตัวเลือกคุณภาพ
    5. โทร setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString())
    6. หากแอปมีตัวอย่างวิดีโอ ให้โทร setPreviewDisplay(preview?.holder?.surface)
    7. โทร setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
    8. โทร setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT)
    9. โทร setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT)
    10. โทรหา prepare() เพื่อกำหนดค่า MediaRecorder ให้เสร็จ
  5. โทร MediaRecorder.start() เพื่อเริ่มบันทึก
  6. หากต้องการหยุดบันทึก ให้เรียกใช้วิธีการเหล่านี้ โปรดทำตามขั้นตอนต่อไปนี้อีกครั้ง
    1. โทร MediaRecorder.stop()
    2. คุณสามารถเลือกนำการกำหนดค่า MediaRecorder ปัจจุบันออกโดยการเรียกใช้ MediaRecorder.reset()
    3. โทร MediaRecorder.release()
    4. ล็อกกล้องเพื่อให้เซสชันของ MediaRecorder ในอนาคตใช้ได้ใน กำลังโทรหา Camera.lock()
  7. หากต้องการหยุดแสดงตัวอย่าง โปรดโทร Camera.stopPreview()
  8. สุดท้าย หากต้องการปล่อย Camera เพื่อให้กระบวนการอื่นๆ ใช้งานได้ ให้โทรหา Camera.release()

ขั้นตอนทั้งหมดรวมกันมีดังนี้

// 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

เมื่อใช้ CameraController ของ CameraX คุณสามารถสลับImageCapture VideoCapture และ ImageAnalysis UseCase แยกกัน ตราบใดที่สามารถใช้รายการ UseCases พร้อมกันได้ ImageCapture และ UseCase ของ ImageAnalysis จะเปิดใช้โดยค่าเริ่มต้น คือเหตุผลที่คุณไม่ต้องโทรหา setEnabledUseCases() เพื่อถ่ายรูป

หากต้องการใช้ CameraController สำหรับการบันทึกวิดีโอ คุณต้องใช้ setEnabledUseCases() เพื่ออนุญาต UseCase ในการVideoCapture

// CameraX: Enable VideoCapture UseCase on CameraController.

cameraController.setEnabledUseCases(VIDEO_CAPTURE);

เมื่อเริ่มบันทึกวิดีโอ คุณสามารถเรียกใช้ CameraController.startRecording() ฟังก์ชันนี้สามารถบันทึกวิดีโอที่บันทึกไปยัง File ดังที่คุณเห็น ในตัวอย่างด้านล่าง นอกจากนี้คุณต้องผ่านExecutorและชั้นเรียนด้วย ซึ่งใช้ OnVideoSavedCallback ในการจัดการการเรียกกลับที่สำเร็จและข้อผิดพลาด เมื่อการบันทึกควรสิ้นสุด ให้โทร CameraController.stopRecording()

หมายเหตุ: หากคุณใช้ CameraX 1.3.0-alpha02 หรือใหม่กว่า จะมีฟังก์ชันเพิ่มเติม พารามิเตอร์ AudioConfig ที่ให้คุณเปิดหรือปิดการบันทึกเสียงในวิดีโอของคุณ วิธีเปิดใช้งาน เสียงที่บันทึกไว้ คุณต้องตรวจสอบว่ามีสิทธิ์เข้าถึงไมโครโฟน นอกจากนี้ จะมีการนำเมธอด stopRecording() ออกจาก 1.3.0-alpha02 และ startRecording() แสดงผลออบเจ็กต์ Recording ที่ใช้หยุดชั่วคราวได้ เล่นต่อและหยุดการบันทึกวิดีโอ

// 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

หากใช้ CameraProvider คุณต้องสร้าง VideoCapture UseCase และส่งผ่านในออบเจ็กต์ Recorder ใน Recorder.Builder คุณสามารถ ตั้งค่าคุณภาพวิดีโอ และ FallbackStrategy ซึ่ง จัดการกรณีที่อุปกรณ์ไม่สามารถปฏิบัติตามข้อกำหนดด้านคุณภาพที่คุณต้องการ จากนั้น เชื่อมโยงอินสแตนซ์ VideoCapture กับ CameraProvider กับอินสแตนซ์อื่น UseCase วินาที

// 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: VideoCapture
private 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)

ณ จุดนี้ Recorder สามารถเข้าถึงได้บน videoCapture.output Recorder จะเริ่มการบันทึกวิดีโอที่บันทึกไว้ใน File ได้ ParcelFileDescriptor หรือ MediaStore ตัวอย่างนี้ใช้ MediaStore

ในRecorder คุณสามารถโทรเพื่อเตรียมพร้อมได้หลายวิธี โทร prepareRecording() เพื่อตั้งค่าตัวเลือกเอาต์พุต MediaStore หากแอปของคุณมี สิทธิ์ในการใช้ไมโครโฟนของอุปกรณ์ โทรหา withAudioEnabled() ด้วย จากนั้นเรียก start() เพื่อเริ่มการบันทึก ส่งผ่านในบริบท และ Consumer<VideoRecordEvent> Listener เหตุการณ์ที่จะจัดการเหตุการณ์บันทึกวิดีโอ ถ้า สำเร็จ Recording ที่แสดงผลแล้วจะใช้เพื่อหยุดชั่วคราว เล่นต่อ หรือหยุด บันทึก

// 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
                   }
               }
           }
       }
}

แหล่งข้อมูลเพิ่มเติม

เรามีแอป CameraX ที่สมบูรณ์หลายรายการใน ที่เก็บตัวอย่างใน GitHub ของกล้อง ตัวอย่างเหล่านี้จะแสดงให้คุณเห็นว่าสถานการณ์ในคู่มือนี้สอดคล้องกับ แอปสำหรับ Android

หากต้องการความช่วยเหลือเพิ่มเติมเกี่ยวกับการย้ายข้อมูลไปยัง CameraX หรือมีคำถาม เกี่ยวกับชุด Android Camera API โปรดติดต่อเราที่ การสนทนาเกี่ยวกับ CameraX กลุ่ม