หากแอปของคุณใช้คลาส Camera
เดิม ("Camera1") ซึ่งเลิกใช้งานแล้วตั้งแต่ Android 5.0 (API ระดับ 21) เราขอแนะนําอย่างยิ่งให้อัปเดตเป็น API กล้อง Android สมัยใหม่ Android มี CameraX (API กล้อง Jetpack มาตรฐานที่มีประสิทธิภาพ) และ Camera2 (API เฟรมเวิร์กระดับต่ำ) เราขอแนะนำให้ย้ายข้อมูลแอปของคุณไปยัง CameraX ในกรณีส่วนใหญ่ เหตุผลก็คือ:
- ใช้งานง่าย: CameraX จะจัดการรายละเอียดระดับล่างเพื่อให้คุณมุ่งเน้นที่การสร้างประสบการณ์การใช้งานกล้องตั้งแต่ต้นน้อยลง และมุ่งเน้นที่การทำให้แอปของคุณโดดเด่นมากขึ้น
- CameraX จัดการการแยกส่วนให้คุณ: CameraX จะช่วยลดค่าใช้จ่ายในการบำรุงรักษาในระยะยาวและโค้ดเฉพาะอุปกรณ์ ซึ่งจะช่วยให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่มีคุณภาพสูงขึ้น ดูข้อมูลเพิ่มเติมได้ในบล็อกโพสต์เรื่องความเข้ากันได้ของอุปกรณ์ที่ดีขึ้นด้วย CameraX
- ความสามารถขั้นสูง: CameraX ได้รับการออกแบบมาอย่างพิถีพิถันเพื่อให้คุณผสานรวมฟังก์ชันขั้นสูงเข้ากับแอปได้อย่างง่ายดาย เช่น คุณสามารถใช้โหมดโบเก้ การรีทัชใบหน้า HDR (High Dynamic Range) และโหมดถ่ายภาพกลางคืนที่เพิ่มความสว่างในที่แสงน้อยกับรูปภาพได้อย่างง่ายดายด้วยส่วนขยาย CameraX
- ความสามารถในการอัปเดต: Android จะเปิดตัวความสามารถใหม่ๆ และการแก้ไขข้อบกพร่องของ CameraX ตลอดทั้งปี เมื่อย้ายข้อมูลไปยัง CameraX แอปของคุณจะได้รับเทคโนโลยีกล้อง Android เวอร์ชันล่าสุดกับ CameraX แต่ละรุ่น ไม่ใช่แค่ใน Android เวอร์ชันประจำปี
ในคู่มือนี้ คุณจะเห็นสถานการณ์ทั่วไปสำหรับแอปพลิเคชันกล้อง แต่ละสถานการณ์ประกอบด้วยการใช้งาน Camera1 และการใช้งาน CameraX เพื่อเปรียบเทียบ
บางครั้งการย้ายข้อมูลอาจต้องใช้ความยืดหยุ่นเพิ่มเติมเพื่อผสานรวมกับโค้ดเบสที่มีอยู่ โค้ด CameraX ทั้งหมดในคู่มือนี้มีการใช้งานแบบ CameraController
ซึ่งเหมาะสําหรับผู้ที่ต้องการวิธีใช้ CameraX ที่ง่ายที่สุด และการใช้งานแบบ CameraProvider
ซึ่งเหมาะสําหรับผู้ที่ต้องการความยืดหยุ่นมากขึ้น ประโยชน์ของแต่ละตัวเลือกมีดังนี้เพื่อช่วยคุณตัดสินใจว่าตัวเลือกใดเหมาะกับคุณ
CameraController |
CameraProvider |
ต้องใช้โค้ดการตั้งค่าเพียงเล็กน้อย | ช่วยให้ควบคุมได้มากขึ้น |
การให้ CameraX จัดการขั้นตอนการตั้งค่าเพิ่มเติมจะทำให้ฟังก์ชันต่างๆ เช่น การแตะเพื่อโฟกัสและการบีบนิ้วเพื่อซูมทำงานโดยอัตโนมัติ |
เนื่องจากนักพัฒนาแอปเป็นผู้จัดการการตั้งค่า จึงมีโอกาสมากขึ้นในการปรับแต่งการกำหนดค่า เช่น การเปิดใช้การหมุนรูปภาพเอาต์พุตหรือการตั้งค่ารูปแบบรูปภาพเอาต์พุตใน ImageAnalysis
|
การกําหนดให้ใช้ PreviewView สําหรับการแสดงตัวอย่างกล้องจะช่วยให้ CameraX มอบการผสานรวมจากต้นทางถึงปลายทางได้อย่างราบรื่น เช่นเดียวกับการผสานรวม ML Kit ของเราซึ่งสามารถแมปพิกัดผลลัพธ์ของโมเดล ML (เช่น กรอบล้อมรอบใบหน้า) กับพิกัดของตัวอย่างโดยตรง
|
ความสามารถในการใช้ "Surface" ที่กําหนดเองสําหรับการแสดงตัวอย่างกล้องช่วยให้มีความยืดหยุ่นมากขึ้น เช่น การใช้โค้ด "Surface" ที่มีอยู่ซึ่งอาจเป็นอินพุตสําหรับส่วนอื่นๆ ของแอป |
หากพบปัญหาขณะย้ายข้อมูล โปรดติดต่อเราในกลุ่มสนทนา CameraX
ก่อนย้ายข้อมูล
เปรียบเทียบการใช้งาน CameraX กับ Camera1
แม้ว่าโค้ดอาจดูแตกต่างกัน แต่แนวคิดพื้นฐานใน Camera1 และ CameraX จะคล้ายกันมาก CameraX จะแยกฟังก์ชันการทำงานของกล้องทั่วไปออกเป็นกรณีการใช้งาน ด้วยเหตุนี้ CameraX จึงจัดการงานหลายอย่างที่นักพัฒนาแอปต้องดำเนินการใน Camera1 โดยอัตโนมัติ CameraX มี UseCase
4 รายการที่คุณสามารถใช้กับงานต่างๆ ของกล้องได้ ได้แก่ Preview
, ImageCapture
, VideoCapture
และ ImageAnalysis
ตัวอย่างหนึ่งของ CameraX ที่จัดการรายละเอียดระดับล่างสำหรับนักพัฒนาแอปคือ ViewPort
ที่แชร์ระหว่างUseCase
ที่ใช้งานอยู่ วิธีนี้ช่วยให้ UseCase
ทั้งหมดเห็นพิกเซลเดียวกันทุกประการ
ใน Camera1 คุณต้องจัดการรายละเอียดเหล่านี้ด้วยตนเอง และเนื่องจากเซ็นเซอร์กล้องและหน้าจอของอุปกรณ์แต่ละเครื่องมีสัดส่วนภาพที่ต่างกันไป คุณจึงอาจต้องตรวจสอบให้แน่ใจว่าตัวอย่างภาพตรงกับรูปภาพและวิดีโอที่ถ่าย
อีกตัวอย่างหนึ่งคือ CameraX จะจัดการLifecycle
การเรียกกลับโดยอัตโนมัติในLifecycle
อินสแตนซ์ที่คุณส่ง ซึ่งหมายความว่า CameraX จะจัดการการเชื่อมต่อของแอปกับกล้องตลอดวงจรกิจกรรม Android รวมถึงกรณีที่ปิดกล้องเมื่อแอปทำงานอยู่เบื้องหลัง นำการแสดงตัวอย่างกล้องออกเมื่อหน้าจอไม่จําเป็นต้องแสดงอีกต่อไป และหยุดการแสดงตัวอย่างกล้องชั่วคราวเมื่อกิจกรรมอื่นมีลําดับความสําคัญอยู่เบื้องหน้า เช่น วิดีโอคอลขาเข้า
สุดท้าย CameraX จะจัดการการหมุนและการปรับขนาดโดยที่คุณไม่ต้องเขียนโค้ดเพิ่มเติม ในกรณีที่ Activity
มีการวางแนวที่ปลดล็อกอยู่ ระบบจะตั้งค่า UseCase
ทุกครั้งที่อุปกรณ์หมุน เนื่องจากระบบจะทำลายและสร้าง Activity
ขึ้นมาใหม่เมื่อการวางแนวเปลี่ยนแปลง ซึ่งส่งผลให้ UseCases
ตั้งค่าการหมุนเป้าหมายให้ตรงกับการวางแนวของจอแสดงผลโดยค่าเริ่มต้นทุกครั้ง
อ่านเพิ่มเติมเกี่ยวกับการหมุนใน CameraX
ก่อนเจาะลึกรายละเอียด เราขออธิบายภาพรวมของ UseCase
ของ CameraX และวิธีที่แอป Camera1 จะเกี่ยวข้องกัน (แนวคิด CameraX เป็นสีน้ำเงิน และแนวคิด Camera1 เป็นสีเขียว)
CameraX |
|||
การกำหนดค่า CameraController / CameraProvider | |||
↓ | ↓ | ↓ | ↓ |
แสดงตัวอย่าง | ImageCapture | VideoCapture | ImageAnalysis |
⁞ | ⁞ | ⁞ | ⁞ |
จัดการพื้นผิวแสดงตัวอย่างและตั้งค่าในกล้อง | ตั้งค่า PictureCallback และเรียกใช้ takePicture() ในกล้อง | จัดการการกำหนดค่ากล้องและ MediaRecorder ตามลำดับที่เจาะจง | โค้ดการวิเคราะห์ที่กําหนดเองซึ่งสร้างขึ้นบนพื้นผิวเวอร์ชันตัวอย่าง |
↑ | ↑ | ↑ | ↑ |
รหัสเฉพาะอุปกรณ์ | |||
↑ | |||
การจัดการการหมุนและการปรับขนาดอุปกรณ์ | |||
↑ | |||
การจัดการเซสชันกล้อง (การเลือกกล้อง การจัดการวงจรการใช้งาน) | |||
Camera1 |
ความเข้ากันได้และประสิทธิภาพใน CameraX
CameraX รองรับอุปกรณ์ที่ใช้ Android 5.0 (API ระดับ 21) ขึ้นไป ซึ่งคิดเป็นมากกว่า 98% ของอุปกรณ์ Android ที่มีอยู่ CameraX สร้างขึ้นเพื่อจัดการความแตกต่างระหว่างอุปกรณ์โดยอัตโนมัติ ซึ่งช่วยลดความจำเป็นในการใช้โค้ดเฉพาะอุปกรณ์ในแอป นอกจากนี้ เรายังทดสอบอุปกรณ์จริงกว่า 150 เครื่องใน Android ทุกเวอร์ชันตั้งแต่ 5.0 ขึ้นไปใน CameraX Test Lab คุณสามารถตรวจสอบรายการอุปกรณ์ทั้งหมดที่อยู่ในห้องทดสอบได้
CameraX ใช้ Executor
เพื่อขับเคลื่อนกองซ้อนของกล้อง คุณสามารถตั้งค่าผู้ดำเนินการของคุณเองใน CameraX ได้หากแอปมีข้อกำหนดการแยกชุดข้อความที่เฉพาะเจาะจง หากไม่ได้ตั้งค่า CameraX จะสร้างและใช้ Executor
ภายในเริ่มต้นที่เพิ่มประสิทธิภาพ API ของแพลตฟอร์มหลายรายการที่ใช้สร้าง CameraX จำเป็นต้องบล็อกการสื่อสารระหว่างกระบวนการ (IPC) กับฮาร์ดแวร์ที่บางครั้งอาจใช้เวลาหลายร้อยมิลลิวินาทีในการตอบสนอง CameraX จึงเรียกใช้ API เหล่านี้จากเธรดเบื้องหลังเท่านั้น เพื่อให้มั่นใจว่าเธรดหลักจะไม่ถูกบล็อกและ UI จะยังคงทำงานได้อย่างราบรื่น
อ่านเพิ่มเติมเกี่ยวกับชุดข้อความ
หากตลาดเป้าหมายของแอปมีอุปกรณ์ระดับล่าง CameraX มีวิธีลดเวลาในการตั้งค่าด้วยตัวจำกัดกล้อง เนื่องจากกระบวนการเชื่อมต่อกับคอมโพเนนต์ฮาร์ดแวร์อาจใช้เวลานานพอสมควร โดยเฉพาะในอุปกรณ์ระดับล่าง คุณจึงระบุชุดกล้องที่แอปต้องใช้ได้ CameraX จะเชื่อมต่อกับกล้องเหล่านี้ระหว่างการตั้งค่าเท่านั้น เช่น หากแอปพลิเคชันใช้เฉพาะกล้องหลัง ก็จะตั้งค่านี้ได้ด้วย DEFAULT_BACK_CAMERA
จากนั้น CameraX จะหลีกเลี่ยงการเริ่มต้นกล้องหน้าเพื่อลดความล่าช้า
แนวคิดการพัฒนา Android
คู่มือนี้ถือว่าคุณคุ้นเคยกับการพัฒนาแอป Android โดยทั่วไปแล้ว นอกจากข้อมูลเบื้องต้นแล้ว ต่อไปนี้เป็นแนวคิด 2-3 ข้อที่ควรทำความเข้าใจก่อนดูโค้ดด้านล่าง
- การเชื่อมโยงมุมมองจะสร้างคลาสการเชื่อมโยงสำหรับไฟล์เลย์เอาต์ XML ซึ่งช่วยให้คุณอ้างอิงมุมมองในกิจกรรมได้อย่างง่ายดาย ดังที่ทำในตัวอย่างโค้ดหลายรายการด้านล่าง การเชื่อมโยงข้อมูลกับวิวและ
findViewById()
(วิธีอ้างอิงวิวก่อนหน้านี้) มีความแตกต่างกันอยู่บ้าง แต่ในโค้ดด้านล่าง คุณควรแทนที่บรรทัดการเชื่อมโยงข้อมูลกับวิวด้วยการเรียกใช้findViewById()
ที่คล้ายกัน - Coroutine แบบไม่พร้อมกันคือรูปแบบการออกแบบแบบพร้อมกันที่เพิ่มเข้ามาใน Kotlin 1.3 ซึ่งสามารถใช้เพื่อจัดการเมธอด CameraX ที่แสดงผล
ListenableFuture
ซึ่งทำได้ง่ายขึ้นด้วยไลบรารี Concurrent ของ Jetpack เวอร์ชัน 1.1.0 วิธีเพิ่มโคโริวทีนแบบอะซิงโครนัสลงในแอป- เพิ่ม
implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0")
ลงในไฟล์ Gradle - ใส่โค้ด CameraX ที่แสดงผล
ListenableFuture
ในบล็อกlaunch
หรือฟังก์ชันที่หยุดชั่วคราว - เพิ่มการเรียก
await()
ในการเรียกฟังก์ชันที่แสดงผลListenableFuture
- หากต้องการทําความเข้าใจวิธีทํางานของโคโรทีนให้ดียิ่งขึ้น โปรดดูคู่มือเริ่มโคโรทีน
- เพิ่ม
ย้ายข้อมูลสถานการณ์ที่พบบ่อย
ส่วนนี้จะอธิบายวิธีย้ายข้อมูลสถานการณ์ทั่วไปจาก Camera1 ไปยัง CameraX
แต่ละสถานการณ์ครอบคลุมการใช้งาน Camera1, การใช้งาน CameraX CameraProvider
และการใช้งาน CameraX CameraController
การเลือกกล้อง
ในแอปพลิเคชันกล้อง สิ่งแรกที่คุณอาจต้องการนำเสนอคือวิธีเลือกกล้องต่างๆ
Camera1
ใน 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
ใน CameraX การเลือกกล้องจะจัดการโดยคลาส CameraSelector
CameraX ช่วยให้คุณใช้งานกล้องเริ่มต้นได้ง่ายๆ คุณสามารถระบุได้ว่าต้องการใช้กล้องหน้าหรือกล้องหลังเริ่มต้น นอกจากนี้ ออบเจ็กต์ 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 คุณต้องจัดการการเรียกกลับของวงจรอย่างถูกต้อง และกำหนดการหมุนและการปรับขนาดสำหรับตัวอย่างด้วย
นอกจากนี้ ใน Camera1 คุณต้องตัดสินใจว่าจะใช้เป็น TextureView
หรือ SurfaceView
เป็นพื้นผิวแสดงตัวอย่าง
ตัวเลือกทั้ง 2 ตัวเลือกมีข้อดีข้อเสียต่างกันไป และไม่ว่าในกรณีใด Camera1 กำหนดให้คุณต้องจัดการการหมุนและการปรับขนาดอย่างถูกต้อง ในทางกลับกัน PreviewView
ของ CameraX มีการใช้งานพื้นฐานสำหรับทั้ง TextureView
และ SurfaceView
CameraX จะตัดสินใจว่าการใช้งานแบบใดดีที่สุดโดยพิจารณาจากปัจจัยต่างๆ เช่น ประเภทอุปกรณ์และเวอร์ชัน Android ที่แอปของคุณใช้อยู่ หากการติดตั้งใช้งานใดติดตั้งร่วมกันได้ คุณสามารถประกาศค่ากำหนดของคุณด้วย PreviewView.ImplementationMode
ตัวเลือก COMPATIBLE
ใช้ TextureView
สำหรับตัวอย่างเพลง และค่า PERFORMANCE
ใช้ SurfaceView
(หากเป็นไปได้)
Camera1
หากต้องการแสดงตัวอย่าง คุณต้องเขียนคลาส 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() } }
แตะเพื่อโฟกัส
เมื่อตัวอย่างกล้องแสดงบนหน้าจอ การควบคุมทั่วไปคือการกําหนดจุดโฟกัสเมื่อผู้ใช้แตะตัวอย่าง
Camera1
หากต้องการใช้การแตะเพื่อโฟกัสใน Camera1 คุณต้องคำนวณโฟกัสที่ดีที่สุด Area
เพื่อระบุตำแหน่งที่ Camera
ควรพยายามโฟกัส Area
นี้จะส่งผ่านไปยัง setFocusAreas()
นอกจากนี้ คุณต้องตั้งค่าโหมดโฟกัสที่เข้ากันได้ใน Camera
พื้นที่โฟกัสจะมีผลเฉพาะในกรณีที่โหมดโฟกัสปัจจุบันเป็น FOCUS_MODE_AUTO
, FOCUS_MODE_MACRO
, FOCUS_MODE_CONTINUOUS_VIDEO
หรือ FOCUS_MODE_CONTINUOUS_PICTURE
Area
แต่ละรายการคือสี่เหลี่ยมผืนผ้าที่มีน้ำหนักที่ระบุ น้ำหนักคือค่าระหว่าง 1 ถึง 1, 000 และใช้เพื่อจัดลําดับความสําคัญของโฟกัส Areas
หากตั้งค่าไว้หลายรายการ ตัวอย่างนี้ใช้ Area
เพียงรายการเดียว ดังนั้นค่าน้ำหนักจึงไม่สําคัญ พิกัดของสี่เหลี่ยมผืนผ้าอยู่ในช่วง -1000 ถึง 1000 จุดด้านซ้ายบนคือ (-1000, -1000)
จุดที่ด้านขวาล่างคือ (1000, 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()
และตรวจสอบค่าด้วยตัวรับค่าที่เกี่ยวข้อง 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
มีดังนี้
- ตั้งค่าตัวตรวจจับท่าทางสัมผัสเพื่อจัดการเหตุการณ์การแตะ
- สร้าง
MeteringPoint
โดยใช้MeteringPointFactory.createPoint()
กับเหตุการณ์การแตะ - ใช้
MeteringPoint
เพื่อสร้างFocusMeteringAction
- เมื่อออบเจ็กต์
CameraControl
ในCamera
(แสดงผลจากbindToLifecycle()
) ให้เรียกใช้startFocusAndMetering()
โดยส่งค่าFocusMeteringAction
- (ไม่บังคับ) ตอบกลับ
FocusMeteringResult
- ตั้งค่าตัวตรวจจับท่าทางสัมผัสให้ตอบสนองต่อเหตุการณ์การแตะใน
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 }
บีบและกางนิ้วเพื่อซูม
การซูมเข้าและออกของตัวอย่างเป็นการจัดการโดยตรงกับตัวอย่างจากกล้องอีกอย่างหนึ่งที่พบได้ทั่วไป เมื่ออุปกรณ์มีกล้องเพิ่มมากขึ้น ผู้ใช้ก็คาดหวังว่าระบบจะเลือกเลนส์ที่มีทางยาวโฟกัสที่ดีที่สุดโดยอัตโนมัติเมื่อซูม
Camera1
การซูมโดยใช้ Camera1 มี 2 วิธีดังนี้ เมธอด Camera.startSmoothZoom()
จะแสดงภาพเคลื่อนไหวจากระดับการซูมปัจจุบันไปยังระดับการซูมที่คุณส่งเข้ามา วิธี Camera.Parameters.setZoom()
จะข้ามไปยังระดับการซูมที่คุณส่งเข้ามาโดยตรง ก่อนใช้ฟีเจอร์ใดฟีเจอร์หนึ่ง ให้กด isSmoothZoomSupported()
หรือ isZoomSupported()
ตามลำดับเพื่อให้แน่ใจว่ากล้องมีวิธีการซูมที่เกี่ยวข้องซึ่งคุณต้องการ
หากต้องการใช้การบีบนิ้วเพื่อซูม ตัวอย่างนี้ใช้ setZoom()
เนื่องจากตัวรับฟังการสัมผัสบนแพลตฟอร์มแสดงตัวอย่างจะเรียกเหตุการณ์อย่างต่อเนื่องเมื่อเกิดท่าทางสัมผัสการบีบนิ้วขึ้น จึงอัปเดตระดับการซูมทันทีทุกครั้ง คลาส ZoomTouchListener
ได้รับการกําหนดไว้ด้านล่าง และควรตั้งค่าเป็นคอลแบ็กสําหรับโปรแกรมฟังการสัมผัสของพื้นผิวแสดงตัวอย่าง
// 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()
และตรวจสอบค่าด้วยตัวรับค่าที่เกี่ยวข้อง 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
มีดังนี้
- ตั้งค่าตัวตรวจจับท่าทางสัมผัสเพื่อปรับขนาดเพื่อจัดการเหตุการณ์การบีบนิ้ว
- รับ
ZoomState
จากออบเจ็กต์Camera.CameraInfo
ซึ่งระบบจะแสดงผลอินสแตนซ์Camera
เมื่อคุณเรียกใช้bindToLifecycle()
- หาก
ZoomState
มีค่าเป็นzoomRatio
ให้บันทึกค่านั้นเป็นอัตราส่วนการซูมปัจจุบัน หากไม่มีzoomRatio
ในZoomState
ให้ใช้อัตราการซูมเริ่มต้นของกล้อง (1.0) - นำอัตราส่วนการซูมปัจจุบันคูณกับ
scaleFactor
เพื่อหาอัตราส่วนการซูมใหม่ แล้วส่งค่านั้นไปยังCameraControl.setZoomRatio()
- ตั้งค่าตัวตรวจจับท่าทางสัมผัสให้ตอบสนองต่อเหตุการณ์การแตะใน
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 }
การถ่ายรูป
ส่วนนี้จะแสดงวิธีเรียกให้ถ่ายภาพ ไม่ว่าคุณจะต้องการเรียกให้ถ่ายภาพเมื่อกดปุ่มชัตเตอร์ หลังจากตัวจับเวลาหมดเวลา หรือเมื่อเกิดเหตุการณ์อื่นใดที่คุณเลือก
Camera1
ใน 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
(ซึ่งไม่ได้กำหนดไว้ในตัวอย่างนี้) พารามิเตอร์ที่ 2 นั้นใช้สำหรับ 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()
ดูตัวอย่างฟังก์ชัน takePhoto()
แบบเต็มได้ที่โค้ด CameraController
ในส่วนนี้
// 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 จัดการกับความซับซ้อนนี้ให้คุณได้มากมาย
Camera1
การจับภาพวิดีโอโดยใช้ Camera1 ต้องใช้การจัดการ Camera
และ MediaRecorder
อย่างระมัดระวัง และต้องเรียกใช้เมธอดตามลําดับที่เจาะจง คุณต้องทำตามลำดับต่อไปนี้เพื่อให้แอปพลิเคชันทำงานได้อย่างถูกต้อง
- เปิดกล้อง
- เตรียมและเริ่มแสดงตัวอย่าง (หากแอปแสดงวิดีโอที่กำลังบันทึก ซึ่งมักจะเป็นเช่นนั้น)
- ปลดล็อกกล้องเพื่อให้
MediaRecorder
ใช้ได้โดยโทรหาCamera.unlock()
- กำหนดค่าการบันทึกโดยเรียกใช้เมธอดเหล่านี้ใน
MediaRecorder
- เชื่อมต่ออินสแตนซ์
Camera
กับsetCamera(camera)
- โทร
setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
- โทร
setVideoSource(MediaRecorder.VideoSource.CAMERA)
- โทรหา
setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_1080P))
เพื่อตั้งค่าคุณภาพ ดูตัวเลือกคุณภาพทั้งหมดได้ที่CamcorderProfile
- โทร
setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString())
- หากแอปมีตัวอย่างวิดีโอ ให้เรียกใช้
setPreviewDisplay(preview?.holder?.surface)
- โทร
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
- โทร
setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT)
- โทร
setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT)
- โทรหา
prepare()
เพื่อกำหนดค่าMediaRecorder
ให้เสร็จสมบูรณ์
- เชื่อมต่ออินสแตนซ์
- หากต้องการเริ่มบันทึก ให้โทรหา
MediaRecorder.start()
- หากต้องการหยุดบันทึก ให้เรียกใช้เมธอดเหล่านี้ โปรดทำตามลำดับที่ระบุไว้ข้างต้น
- โทร
MediaRecorder.stop()
- (ไม่บังคับ) นำการกำหนดค่า
MediaRecorder
ในปัจจุบันออกโดยเรียกใช้MediaRecorder.reset()
- โทร
MediaRecorder.release()
- ล็อกกล้องเพื่อให้เซสชัน
MediaRecorder
ในอนาคตใช้กล้องได้โดยเรียกใช้Camera.lock()
- โทร
- หากต้องการหยุดการแสดงตัวอย่าง ให้โทรหา
Camera.stopPreview()
- สุดท้าย หากต้องการปล่อย
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
แยกกันได้ตราบใดที่รายการ UseCase สามารถใช้พร้อมกันได้
ImageCapture
และ ImageAnalysis
UseCase
จะเปิดใช้โดยค่าเริ่มต้น คุณจึงไม่จำเป็นต้องเรียกใช้ setEnabledUseCases()
เพื่อถ่ายภาพ
หากต้องการใช้ CameraController
ในการบันทึกวิดีโอ ก่อนอื่นคุณต้องใช้
setEnabledUseCases()
เพื่ออนุญาต VideoCapture
UseCase
// 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: 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)
เมื่อถึงจุดนี้ คุณจะเข้าถึง 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