ควบคุมกล้อง

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

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

การควบคุมกล้องของอุปกรณ์โดยตรงต้องใช้โค้ดมากกว่าการขอรูปภาพหรือวิดีโอจากแอปพลิเคชันกล้องที่มีอยู่ อย่างไรก็ตาม หากต้องการสร้างแอปพลิเคชันกล้องเฉพาะทางหรือสิ่งที่ผสานรวมกับ UI ของแอปอย่างเต็มรูปแบบ บทเรียนนี้จะแสดงวิธีให้คุณทราบ

โปรดดูแหล่งข้อมูลที่เกี่ยวข้องต่อไปนี้

เปิดออบเจ็กต์กล้อง

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