หากแอปใช้ 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 ข้อ ที่มีประโยชน์ในการทำความเข้าใจ ข้ามไปที่โค้ดด้านล่าง:
- ดูการเชื่อมโยง จะสร้างคลาสการเชื่อมโยงสำหรับ
ไฟล์เค้าโครง XML ของคุณ ซึ่งช่วยให้คุณสามารถ
อ้างอิงมุมมองของคุณในกิจกรรม
เช่นเดียวกับที่ทำในข้อมูลโค้ดต่างๆ ด้านล่าง มีบางส่วน
ความแตกต่างระหว่างการเชื่อมโยงข้อมูลพร็อพเพอร์ตี้กับ
findViewById()
(วิธีก่อนหน้านี้ในการอ้างอิงจำนวนการดู) แต่ในโค้ดด้านล่าง คุณควรจะสามารถ แทนที่บรรทัดการเชื่อมโยงมุมมองด้วยการเรียกfindViewById()
ที่คล้ายกัน - Coroutines แบบไม่พร้อมกันเป็นการออกแบบที่เกิดขึ้นพร้อมกัน
รูปแบบที่เพิ่มเข้ามาใน Kotlin 1.3 ที่สามารถใช้จัดการกับเมธอด CameraX ที่
ส่งคืน
ListenableFuture
ทำสิ่งต่างๆ ได้ง่ายขึ้นด้วย 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
การเลือกกล้อง
ในแอปพลิเคชันกล้องถ่ายรูป สิ่งแรกๆ ที่คุณอาจต้องการนำเสนอคือ วิธีเลือกกล้องต่างๆ
กล้อง 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
มีดังนี้
- ตั้งค่าตัวตรวจจับท่าทางสัมผัสเพื่อจัดการเหตุการณ์การแตะ
- ในกิจกรรมการแตะ ให้สร้าง
MeteringPoint
โดยใช้MeteringPointFactory.createPoint()
- สร้าง
FocusMeteringAction
ด้วยMeteringPoint
- พร้อมออบเจ็กต์
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 }
บีบเพื่อซูม
การซูมเข้าและออกจากการแสดงตัวอย่างเป็น การควบคุมโดยตรงอีกอย่างหนึ่งของ แสดงตัวอย่างจากกล้อง จำนวนกล้องบนอุปกรณ์ ที่เพิ่มมากขึ้น ผู้ใช้ ระบบจะเลือกเลนส์ที่มีความยาวโฟกัสดีที่สุดโดยอัตโนมัติเป็น จากการซูม
กล้อง 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
มีดังนี้
- ตั้งค่าตัวตรวจจับท่าทางสัมผัสการปรับขนาดเพื่อจัดการเหตุการณ์การบีบนิ้ว
- รับ
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 }
การถ่ายรูป
ส่วนนี้จะแสดงวิธีเรียกใช้การจับภาพ ไม่ว่าคุณจะต้องทำใน การกดปุ่มชัตเตอร์ หลังจากผ่านตัวจับเวลาไป หรือเมื่อเกิดเหตุการณ์อื่นๆ การเลือก
กล้อง 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
และวิธีการต้อง
ตามลำดับที่กำหนด คุณต้องทำตามคำสั่งซื้อนี้สำหรับ
เพื่อให้แอปพลิเคชันทำงานได้อย่างถูกต้อง
- เปิดกล้องถ่ายรูป
- เตรียมความพร้อมและเริ่มแสดงตัวอย่าง (หากแอปแสดงวิดีโอที่กําลังบันทึก ซึ่งมักจะเป็นเช่นนั้น)
- ปลดล็อกกล้องเพื่อให้
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
แยกกัน
ตราบใดที่สามารถใช้รายการ 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: 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 กลุ่ม