ในบทเรียนนี้ เราจะพูดถึงวิธีควบคุมฮาร์ดแวร์กล้องโดยตรงโดยใช้ API ของเฟรมเวิร์ก
หมายเหตุ: หน้านี้อ้างอิงถึงคลาส Camera ซึ่งเลิกใช้งานแล้ว เราขอแนะนำให้ใช้ CameraX หรือ Camera2 สำหรับกรณีการใช้งานที่เฉพาะเจาะจง ทั้ง CameraX และ Camera2 รองรับ Android 5.0 (API ระดับ 21) ขึ้นไป
ดูวิธีย้ายข้อมูลจาก Camera1 ไปยัง CameraX ได้ที่หัวข้อย้ายข้อมูล Camera1 ไปยัง CameraX
หรือจะใช้ทักษะ Agentic สำหรับการย้ายข้อมูลจาก Camera1 ไปยัง CameraX ก็ได้
การควบคุมกล้องของอุปกรณ์โดยตรงต้องใช้โค้ดมากกว่าการขอรูปภาพหรือวิดีโอจากแอปพลิเคชันกล้องที่มีอยู่มาก อย่างไรก็ตาม หากคุณต้องการสร้างแอปพลิเคชันกล้องเฉพาะทางหรือแอปพลิเคชันที่ผสานรวมกับ UI ของแอปอย่างสมบูรณ์ บทเรียนนี้จะแสดงวิธีดำเนินการให้คุณทราบ
โปรดดูแหล่งข้อมูลที่เกี่ยวข้องต่อไปนี้
เปิดวัตถุกล้อง
การรับอินสแตนซ์ของออบเจ็กต์ Camera เป็นขั้นตอนแรกในกระบวนการควบคุมกล้องโดยตรง เช่นเดียวกับที่แอปพลิเคชันกล้องของ Android ทำ วิธีที่แนะนำในการเข้าถึงกล้องคือการเปิด Camera ในชุดข้อความแยกต่างหากที่เปิดจาก onCreate() แนวทางนี้เป็นแนวคิดที่ดีเนื่องจากอาจใช้เวลาสักครู่และอาจทำให้เธรด UI ทำงานช้าลง ในการติดตั้งใช้งานขั้นพื้นฐาน การเปิดกล้องสามารถเลื่อนไปที่เมธอด onResume() เพื่ออำนวยความสะดวกในการนำโค้ดกลับมาใช้ใหม่และทำให้ขั้นตอนการควบคุมเป็นไปอย่างตรงไปตรงมา
การเรียก Camera.open() จะแสดงข้อยกเว้นหากแอปพลิเคชันอื่นใช้กล้องอยู่แล้ว ดังนั้นเราจึงรวมไว้ในบล็อก try
Kotlin
private fun safeCameraOpen(id: Int): Boolean { return try { releaseCameraAndPreview() mCamera = Camera.open(id) true } catch (e: Exception) { Log.e(getString(R.string.app_name), "failed to open Camera") e.printStackTrace() false } } private fun releaseCameraAndPreview() { preview?.setCamera(null) mCamera?.also { camera -> camera.release() mCamera = null } }
Java
private boolean safeCameraOpen(int id) { boolean qOpened = false; try { releaseCameraAndPreview(); camera = Camera.open(id); qOpened = (camera != null); } catch (Exception e) { Log.e(getString(R.string.app_name), "failed to open Camera"); e.printStackTrace(); } return qOpened; } private void releaseCameraAndPreview() { preview.setCamera(null); if (camera != null) { camera.release(); camera = null; } }
ตั้งแต่ระดับ API 9 เป็นต้นมา เฟรมเวิร์กกล้องรองรับกล้องหลายตัว หากคุณใช้ API เดิมและเรียกใช้ open() โดยไม่มีอาร์กิวเมนต์ คุณจะได้รับกล้องหลังตัวแรก
สร้างตัวอย่างจากกล้อง
โดยปกติแล้ว การถ่ายภาพกำหนดให้ผู้ใช้เห็นตัวอย่างของวัตถุก่อนที่จะคลิกชัตเตอร์ ซึ่งทำได้โดยใช้ SurfaceView เพื่อวาดตัวอย่างสิ่งที่เซ็นเซอร์กล้องจับภาพได้
คลาสตัวอย่าง
หากต้องการเริ่มต้นแสดงตัวอย่าง คุณต้องมีคลาสตัวอย่าง ตัวอย่างต้องมีการติดตั้งใช้งานอินเทอร์เฟซ android.view.SurfaceHolder.Callback ซึ่งใช้เพื่อส่งข้อมูลรูปภาพจากฮาร์ดแวร์กล้องไปยังแอปพลิเคชัน
Kotlin
class Preview( context: Context, val surfaceView: SurfaceView = SurfaceView(context) ) : ViewGroup(context), SurfaceHolder.Callback { var mHolder: SurfaceHolder = surfaceView.holder.apply { addCallback(this@Preview) setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS) } ... }
Java
class Preview extends ViewGroup implements SurfaceHolder.Callback { SurfaceView surfaceView; SurfaceHolder holder; Preview(Context context) { super(context); surfaceView = new SurfaceView(context); addView(surfaceView); // Install a SurfaceHolder.Callback so we get notified when the // underlying surface is created and destroyed. holder = surfaceView.getHolder(); holder.addCallback(this); holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } ... }
ต้องส่งคลาสตัวอย่างไปยังออบเจ็กต์ Camera ก่อนจึงจะเริ่มแสดงตัวอย่างรูปภาพสดได้ ดังที่แสดงในส่วนถัดไป
ตั้งค่าและเริ่มการแสดงตัวอย่าง
ต้องสร้างอินสแตนซ์กล้องและตัวอย่างที่เกี่ยวข้องตามลำดับที่เฉพาะเจาะจง โดยต้องสร้างออบเจ็กต์กล้องก่อน ในข้อมูลโค้ดต่อไปนี้ กระบวนการเริ่มต้นกล้องจะถูกรวมไว้เพื่อให้เมธอด setCamera() เรียกใช้ Camera.startPreview() เมื่อใดก็ตามที่ผู้ใช้ดำเนินการบางอย่างเพื่อเปลี่ยนกล้อง นอกจากนี้ คุณต้องรีสตาร์ทตัวอย่างในเมธอด Callback surfaceChanged() ของคลาสตัวอย่างด้วย
Kotlin
fun setCamera(camera: Camera?) { if (mCamera == camera) { return } stopPreviewAndFreeCamera() mCamera = camera mCamera?.apply { mSupportedPreviewSizes = parameters.supportedPreviewSizes requestLayout() try { setPreviewDisplay(holder) } catch (e: IOException) { e.printStackTrace() } // Important: Call startPreview() to start updating the preview // surface. Preview must be started before you can take a picture. startPreview() } }
Java
public void setCamera(Camera camera) { if (mCamera == camera) { return; } stopPreviewAndFreeCamera(); mCamera = camera; if (mCamera != null) { List<Size> localSizes = mCamera.getParameters().getSupportedPreviewSizes(); supportedPreviewSizes = localSizes; requestLayout(); try { mCamera.setPreviewDisplay(holder); } catch (IOException e) { e.printStackTrace(); } // Important: Call startPreview() to start updating the preview // surface. Preview must be started before you can take a picture. mCamera.startPreview(); } }
แก้ไขการตั้งค่ากล้อง
การตั้งค่ากล้องจะเปลี่ยนวิธีที่กล้องถ่ายภาพ ตั้งแต่ระดับการซูมไปจนถึงการชดเชยการรับแสง ตัวอย่างนี้จะเปลี่ยนเฉพาะขนาดตัวอย่างเท่านั้น โปรดดูซอร์สโค้ดของแอปพลิเคชันกล้องเพื่อดูตัวอย่างอื่นๆ เพิ่มเติม
Kotlin
override fun surfaceChanged(holder: SurfaceHolder, format: Int, w: Int, h: Int) { mCamera?.apply { // Now that the size is known, set up the camera parameters and begin // the preview. parameters?.also { params -> params.setPreviewSize(previewSize.width, previewSize.height) requestLayout() parameters = params } // Important: Call startPreview() to start updating the preview surface. // Preview must be started before you can take a picture. startPreview() } }
Java
@Override public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { // Now that the size is known, set up the camera parameters and begin // the preview. Camera.Parameters parameters = mCamera.getParameters(); parameters.setPreviewSize(previewSize.width, previewSize.height); requestLayout(); mCamera.setParameters(parameters); // Important: Call startPreview() to start updating the preview surface. // Preview must be started before you can take a picture. mCamera.startPreview(); }
ตั้งค่าการวางแนวตัวอย่าง
แอปพลิเคชันกล้องส่วนใหญ่จะล็อกการแสดงผลเป็นโหมดแนวนอนเนื่องจากเป็นแนวการวางตามธรรมชาติของเซ็นเซอร์กล้อง การตั้งค่านี้ไม่ได้ป้องกันไม่ให้คุณถ่ายภาพในโหมดแนวตั้ง เนื่องจากระบบจะบันทึกการวางแนวของอุปกรณ์ไว้ในส่วนหัว EXIF เมธอด setCameraDisplayOrientation() ช่วยให้คุณเปลี่ยนวิธีแสดงตัวอย่างได้โดยไม่ส่งผลต่อวิธีบันทึกรูปภาพ อย่างไรก็ตาม ใน Android ที่มีระดับ API ต่ำกว่า 14 คุณต้องหยุดการแสดงตัวอย่างก่อนเปลี่ยนการวางแนว แล้วจึงเริ่มใหม่
ถ่ายภาพ
ใช้เมธอด Camera.takePicture() เพื่อถ่ายภาพเมื่อตัวอย่างเริ่มทำงาน คุณสามารถสร้างออบเจ็กต์ Camera.PictureCallback และ Camera.ShutterCallback แล้วส่งไปยัง Camera.takePicture() ได้
หากต้องการจับภาพอย่างต่อเนื่อง คุณสามารถสร้าง Camera.PreviewCallback ที่ใช้ onPreviewFrame() ได้ หากต้องการตัวเลือกที่อยู่กึ่งกลางระหว่าง 2 ตัวเลือกนี้ คุณสามารถจับภาพเฉพาะเฟรมตัวอย่างที่เลือก หรือตั้งค่าการดำเนินการที่ล่าช้าเพื่อเรียกใช้ takePicture()
รีสตาร์ทตัวอย่าง
หลังจากถ่ายภาพแล้ว คุณต้องรีสตาร์ทตัวอย่างก่อนที่ผู้ใช้จะถ่ายภาพอื่นได้ ในตัวอย่างนี้ การรีสตาร์ทจะทำได้โดยการกดปุ่มชัตเตอร์ค้างไว้
Kotlin
fun onClick(v: View) { previewState = if (previewState == K_STATE_FROZEN) { camera?.startPreview() K_STATE_PREVIEW } else { camera?.takePicture(null, rawCallback, null) K_STATE_BUSY } shutterBtnConfig() }
Java
@Override public void onClick(View v) { switch(previewState) { case K_STATE_FROZEN: camera.startPreview(); previewState = K_STATE_PREVIEW; break; default: camera.takePicture( null, rawCallback, null); previewState = K_STATE_BUSY; } // switch shutterBtnConfig(); }
หยุดการแสดงตัวอย่างและปล่อยกล้อง
เมื่อแอปพลิเคชันใช้กล้องเสร็จแล้ว ก็ถึงเวลาล้างข้อมูล โดยเฉพาะอย่างยิ่ง คุณต้องปล่อยออบเจ็กต์ Camera มิฉะนั้นอาจเสี่ยงต่อการทำให้แอปพลิเคชันอื่นๆ ขัดข้อง รวมถึงอินสแตนซ์ใหม่ของแอปพลิเคชันของคุณเอง
คุณควรหยุดแสดงตัวอย่างและปล่อยกล้องเมื่อใด การที่พื้นผิวตัวอย่างถูกทำลายเป็นสัญญาณที่ดีว่าถึงเวลาหยุดการแสดงตัวอย่างและปล่อยกล้องแล้ว ดังที่แสดงในเมธอดเหล่านี้จากคลาส Preview
Kotlin
override fun surfaceDestroyed(holder: SurfaceHolder) { // Surface will be destroyed when we return, so stop the preview. // Call stopPreview() to stop updating the preview surface. mCamera?.stopPreview() } /** * When this function returns, mCamera will be null. */ private fun stopPreviewAndFreeCamera() { mCamera?.apply { // Call stopPreview() to stop updating the preview surface. stopPreview() // Important: Call release() to release the camera for use by other // applications. Applications should release the camera immediately // during onPause() and re-open() it during onResume()). release() mCamera = null } }
Java
@Override public void surfaceDestroyed(SurfaceHolder holder) { // Surface will be destroyed when we return, so stop the preview. if (mCamera != null) { // Call stopPreview() to stop updating the preview surface. mCamera.stopPreview(); } } /** * When this function returns, mCamera will be null. */ private void stopPreviewAndFreeCamera() { if (mCamera != null) { // Call stopPreview() to stop updating the preview surface. mCamera.stopPreview(); // Important: Call release() to release the camera for use by other // applications. Applications should release the camera immediately // during onPause() and re-open() it during onResume()). mCamera.release(); mCamera = null; } }
ก่อนหน้านี้ในบทเรียน ขั้นตอนนี้เป็นส่วนหนึ่งของเมธอด setCamera() ด้วย ดังนั้นการเริ่มต้นกล้องจึงเริ่มด้วยการหยุดการแสดงตัวอย่างเสมอ