ควบคุมกล้อง

ในบทเรียนนี้ เราจะพูดถึงวิธีควบคุมฮาร์ดแวร์กล้องโดยตรง 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 ก่อนเผยแพร่ สามารถเริ่มต้นตัวอย่างรูปภาพได้ ดังที่แสดงในส่วนถัดไป

ตั้งค่าและเริ่มการแสดงตัวอย่าง

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