ควบคุมกล้อง

ในบทเรียนนี้ เราจะพูดถึงวิธีควบคุมฮาร์ดแวร์กล้องโดยตรงโดยใช้ API ของเฟรมเวิร์ก

หมายเหตุ: หน้านี้อ้างอิงถึงคลาสกล้อง ซึ่งเลิกใช้งานแล้ว เราขอแนะนำให้ใช้ CameraX หรือ Camera2 สำหรับ Use Case ที่เฉพาะเจาะจง ทั้ง CameraX และ Camera2 รองรับ Android 5.0 (API ระดับ 21) ขึ้นไป

การควบคุมกล้องของอุปกรณ์โดยตรงต้องใช้โค้ดมากกว่าการขอรูปภาพหรือวิดีโอ จากแอปพลิเคชันกล้องที่มีอยู่มาก อย่างไรก็ตาม หากต้องการสร้างแอปพลิเคชันกล้องเฉพาะทาง หรือสิ่งที่ผสานรวมอย่างเต็มรูปแบบใน 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() เมื่อใดก็ตามที่ผู้ใช้ดำเนินการบางอย่างเพื่อเปลี่ยนกล้อง นอกจากนี้ คุณยังต้องรีสตาร์ทการแสดงตัวอย่างใน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() ได้ หากต้องการ ใช้ตัวเลือกที่อยู่ระหว่างกลาง คุณจะบันทึกเฉพาะเฟรมตัวอย่างที่เลือก หรือตั้งค่า การดำเนินการที่ล่าช้าเพื่อเรียกใช้ 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()ด้วย ดังนั้นการเริ่มต้นกล้องจึงต้องเริ่มด้วยการหยุด ตัวอย่างเสมอ