หากแอปของคุณใช้คลาส 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
หากต้องการควบคุมกล้องที่จะเลือก คุณก็ทำได้ใน 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
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
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: 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