แฟลชหน้าจอ

แฟลชหน้าจอหรือที่เรียกว่าแฟลชหน้าหรือแฟลชเซลฟีจะใช้ความสว่างของหน้าจอโทรศัพท์เพื่อส่องสว่างวัตถุเมื่อถ่ายภาพด้วยกล้องหน้าในสภาพแสงน้อย ซึ่งพร้อมใช้งานในแอปกล้องและแอปโซเชียลมีเดียแบบดั้งเดิมหลายแอป เนื่องจากคนส่วนใหญ่ถือโทรศัพท์ไว้ใกล้ๆ เพียงพอเมื่อจัดเฟรมภาพบุคคล วิธีการนี้มีประสิทธิภาพ

อย่างไรก็ตาม เป็นเรื่องยากสำหรับนักพัฒนาแอปที่จะใช้งานฟีเจอร์นี้อย่างถูกต้องและรักษาคุณภาพการบันทึกที่ดีให้สม่ำเสมอในทุกอุปกรณ์ คู่มือนี้จะแสดงวิธีใช้ฟีเจอร์นี้อย่างถูกต้องโดยใช้ Camera2 ซึ่งเป็น API เฟรมเวิร์กกล้องระดับล่างของ Android

เวิร์กโฟลว์ทั่วไป

ปัจจัยสำคัญ 2 ประการในการใช้ฟีเจอร์นี้อย่างถูกต้องคือ การใช้ลำดับการวัดแสงก่อนการจับภาพ (การวัดแสงอัตโนมัติก่อนการจับภาพ) และเวลาของการดำเนินการ เวิร์กโฟลว์ทั่วไปแสดงอยู่ในรูปที่ 1

โฟลว์ชาร์ตแสดงวิธีใช้ UI ของแฟลชหน้าจอภายใน Camera2
รูปที่ 1 เวิร์กโฟลว์ทั่วไปในการใช้หน้าจอ

ขั้นตอนต่อไปนี้จะใช้เมื่อต้องจับภาพด้วยฟีเจอร์แฟลชหน้าจอ

  1. ใช้การเปลี่ยนแปลง UI ที่จําเป็นสําหรับแฟลชหน้าจอ ซึ่งจะให้แสงสว่างเพียงพอสําหรับการถ่ายภาพโดยใช้หน้าจอของอุปกรณ์ สําหรับกรณีการใช้งานทั่วไป Google ขอแนะนําให้เปลี่ยนแปลง UI ดังต่อไปนี้ตามที่เราใช้ในการทดสอบ
    • หน้าจอแอปถูกวางซ้อนด้วยสีขาว
    • ความสว่างของหน้าจอสูงสุด
  2. ตั้งค่าโหมดการเปิดรับแสงอัตโนมัติ (AE) เป็น CONTROL_AE_MODE_ON_EXTERNAL_FLASH หากรองรับ
  3. เรียกใช้ลำดับการวัดแสงก่อนการจับภาพโดยใช้ CONTROL_AE_PRECAPTURE_TRIGGER
  4. รอให้การเปิดรับแสงอัตโนมัติ (AE) และไวท์บาลานซ์อัตโนมัติ (AWB) ทำงานร่วมกัน

  5. เมื่อรวมแล้ว ระบบจะใช้ขั้นตอนการจับภาพปกติของแอป

  6. ส่งคําขอบันทึกไปยังเฟรมเวิร์ก

  7. รอรับผลการจับภาพ

  8. รีเซ็ตโหมด AE หากตั้งค่า CONTROL_AE_MODE_ON_EXTERNAL_FLASH ไว้

  9. ล้างการเปลี่ยนแปลง UI สำหรับแฟลชหน้าจอ

โค้ดตัวอย่างของ Camera2

ปกหน้าจอแอปด้วยการวางซ้อนสีขาว

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

ในตัวอย่างโค้ดต่อไปนี้ มีการใช้สีขาว (#FFFFFF) เป็นตัวอย่างสำหรับมุมมอง แอปพลิเคชันสามารถเลือกสี หรือเสนอหลายสีให้กับผู้ใช้ได้ตามความต้องการ

<View
    android:id="@+id/white_color_overlay"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FFFFFF"
    android:visibility="invisible"
    android:elevation="8dp" />

เพิ่มความสว่างของหน้าจอ

การเปลี่ยนความสว่างของหน้าจอในแอป Android ทําได้หลายวิธี วิธีหนึ่งคือการเปลี่ยนพารามิเตอร์ screenBrightness ของ WindowManager ในข้อมูลอ้างอิงหน้าต่างกิจกรรม

Kotlin

private var previousBrightness: Float = -1.0f

private fun maximizeScreenBrightness() {
    activity?.window?.let { window ->
        window.attributes?.apply {
            previousBrightness = screenBrightness
            screenBrightness = 1f
            window.attributes = this
        }
    }
}

private fun restoreScreenBrightness() {
    activity?.window?.let { window ->
        window.attributes?.apply {
            screenBrightness = previousBrightness
            window.attributes = this
        }
    }
}

Java

private float mPreviousBrightness = -1.0f;

private void maximizeScreenBrightness() {
    if (getActivity() == null || getActivity().getWindow() == null) {
        return;
    }

    Window window = getActivity().getWindow();
    WindowManager.LayoutParams attributes = window.getAttributes();

    mPreviousBrightness = attributes.screenBrightness;
    attributes.screenBrightness = 1f;
    window.setAttributes(attributes);
}

private void restoreScreenBrightness() {
    if (getActivity() == null || getActivity().getWindow() == null) {
        return;
    }

    Window window = getActivity().getWindow();
    WindowManager.LayoutParams attributes = window.getAttributes();

    attributes.screenBrightness = mPreviousBrightness;
    window.setAttributes(attributes);
}

ตั้งค่าโหมด AE เป็น CONTROL_AE_MODE_ON_EXTERNAL_FLASH

CONTROL_AE_MODE_ON_EXTERNAL_FLASH พร้อมใช้งานใน API ระดับ 28 ขึ้นไป อย่างไรก็ตาม โหมด AE นี้ไม่พร้อมใช้งานในอุปกรณ์บางรุ่น ดังนั้นโปรดตรวจสอบว่าอุปกรณ์มีโหมด AE หรือไม่ แล้วตั้งค่าตามนั้น หากต้องการตรวจสอบห้องว่าง ให้ใช้ CameraCharacteristics#CONTROL_AE_AVAILABLE_MODES

Kotlin

private val characteristics: CameraCharacteristics by lazy {
    cameraManager.getCameraCharacteristics(cameraId)
}

@RequiresApi(Build.VERSION_CODES.P)
private fun isExternalFlashAeModeAvailable() =
    characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES)
        ?.contains(CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH) ?: false

Java

try {
    mCharacteristics = mCameraManager.getCameraCharacteristics(mCameraId);
} catch (CameraAccessException e) {
    e.printStackTrace();
}

@RequiresApi(Build.VERSION_CODES.P)
private boolean isExternalFlashAeModeAvailable() {
    int[] availableAeModes = mCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES);

    for (int aeMode : availableAeModes) {
        if (aeMode == CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH) {
            return true;
        }
    }
    return false;
}

หากแอปพลิเคชันมีชุดคำขอบันทึกซ้ำ (จำเป็นสำหรับการแสดงตัวอย่าง) โหมด AE จะต้องตั้งค่าเป็นคำขอซ้ำ ไม่เช่นนั้น โหมด AE เริ่มต้นหรือโหมด AE อื่นที่ผู้ใช้ตั้งค่าไว้อาจลบล้างโหมดดังกล่าวในการจับภาพที่ซ้ำกันครั้งถัดไป หากเกิดกรณีนี้ขึ้น กล้องอาจไม่มีเวลาเพียงพอที่จะดำเนินการทั้งหมดตามปกติสำหรับโหมด AE ของแฟลชภายนอก

ตรวจสอบผลการจับภาพในการเรียกกลับการจับภาพซ้ำและรอให้โหมด AE อัปเดตในผลการจับภาพ เพื่อให้แน่ใจว่ากล้องประมวลผลคําขออัปเดตโหมด AE จนเสร็จสมบูรณ์

แบ็กโบนัสการจับภาพที่รอการอัปเดตโหมด AE ได้

ข้อมูลโค้ดต่อไปนี้แสดงให้เห็นวิธีดำเนินการดังกล่าว

Kotlin

private val repeatingCaptureCallback = object : CameraCaptureSession.CaptureCallback() {
    private var targetAeMode: Int? = null
    private var aeModeUpdateDeferred: CompletableDeferred? = null

    suspend fun awaitAeModeUpdate(targetAeMode: Int) {
        this.targetAeMode = targetAeMode
        aeModeUpdateDeferred = CompletableDeferred()
        // Makes the current coroutine wait until aeModeUpdateDeferred is completed. It is
        // completed once targetAeMode is found in the following capture callbacks
        aeModeUpdateDeferred?.await()
    }

    private fun process(result: CaptureResult) {
        // Checks if AE mode is updated and completes any awaiting Deferred
        aeModeUpdateDeferred?.let {
            val aeMode = result[CaptureResult.CONTROL_AE_MODE]
            if (aeMode == targetAeMode) {
                it.complete(Unit)
            }
        }
    }

    override fun onCaptureCompleted(
        session: CameraCaptureSession,
        request: CaptureRequest,
        result: TotalCaptureResult
    ) {
        super.onCaptureCompleted(session, request, result)
        process(result)
    }
}

Java

static class AwaitingCaptureCallback extends CameraCaptureSession.CaptureCallback {
    private int mTargetAeMode;
    private CountDownLatch mAeModeUpdateLatch = null;

    public void awaitAeModeUpdate(int targetAeMode) {
        mTargetAeMode = targetAeMode;
        mAeModeUpdateLatch = new CountDownLatch(1);
        // Makes the current thread wait until mAeModeUpdateLatch is released, it will be
        // released once targetAeMode is found in the capture callbacks below
        try {
            mAeModeUpdateLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void process(CaptureResult result) {
        // Checks if AE mode is updated and decrements the count of any awaiting latch
        if (mAeModeUpdateLatch != null) {
            int aeMode = result.get(CaptureResult.CONTROL_AE_MODE);
            if (aeMode == mTargetAeMode) {
                mAeModeUpdateLatch.countDown();
            }
        }
    }

    @Override
    public void onCaptureCompleted(@NonNull CameraCaptureSession session,
            @NonNull CaptureRequest request,
            @NonNull TotalCaptureResult result) {
        super.onCaptureCompleted(session, request, result);
        process(result);
    }
}

private final AwaitingCaptureCallback mRepeatingCaptureCallback = new AwaitingCaptureCallback();

ตั้งค่าคําขอที่ทําซ้ำเพื่อเปิดหรือปิดใช้โหมด AE

เมื่อใช้การบันทึก Callback แล้ว ตัวอย่างโค้ดต่อไปนี้จะแสดงวิธีตั้งค่าคำขอซ้ำ

Kotlin

/** [HandlerThread] where all camera operations run */
private val cameraThread = HandlerThread("CameraThread").apply { start() }

/** [Handler] corresponding to [cameraThread] */
private val cameraHandler = Handler(cameraThread.looper)

private suspend fun enableExternalFlashAeMode() {
    if (Build.VERSION.SDK_INT >= 28 && isExternalFlashAeModeAvailable()) {
        session.setRepeatingRequest(
            camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW).apply {
                addTarget(previewSurface)
                set(
                    CaptureRequest.CONTROL_AE_MODE,
                    CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH
                )
            }.build(), repeatingCaptureCallback, cameraHandler
        )

        // Wait for the request to be processed by camera
        repeatingCaptureCallback.awaitAeModeUpdate(CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH)
    }
}

private fun disableExternalFlashAeMode() {
    if (Build.VERSION.SDK_INT >= 28 && isExternalFlashAeModeAvailable()) {
        session.setRepeatingRequest(
            camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW).apply {
                addTarget(previewSurface)
            }.build(), repeatingCaptureCallback, cameraHandler
        )
    }
}

Java

private void setupCameraThread() {
    // HandlerThread where all camera operations run
    HandlerThread cameraThread = new HandlerThread("CameraThread");
    cameraThread.start();

    // Handler corresponding to cameraThread
    mCameraHandler = new Handler(cameraThread.getLooper());
}

private void enableExternalFlashAeMode() {
    if (Build.VERSION.SDK_INT >= 28 && isExternalFlashAeModeAvailable()) {
        try {
            CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            requestBuilder.addTarget(mPreviewSurface);
            requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH);
            mSession.setRepeatingRequest(requestBuilder.build(), mRepeatingCaptureCallback, mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }

        // Wait for the request to be processed by camera
        mRepeatingCaptureCallback.awaitAeModeUpdate(CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH);
    }
}

private void disableExternalFlashAeMode() {
    if (Build.VERSION.SDK_INT >= 28 && isExternalFlashAeModeAvailable()) {
        try {
            CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            requestBuilder.addTarget(mPreviewSurface);
            mSession.setRepeatingRequest(requestBuilder.build(), mRepeatingCaptureCallback, mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
}

ทริกเกอร์ลําดับก่อนการจับภาพ

หากต้องการเรียกใช้ลําดับการวัดแสงก่อนการจับภาพ ให้ส่ง CaptureRequest โดยตั้งค่า CONTROL_AE_PRECAPTURE_TRIGGER_START เป็นคําขอ คุณต้องรอให้ระบบประมวลผลคําขอ แล้วรอให้ AE และ AWB ทำงานร่วมกัน

แม้ว่าการจับภาพก่อนการจับภาพจะทริกเกอร์ด้วยคำขอจับภาพรายการเดียว แต่การรอการรวม AE และ AWB มีความซับซ้อนมากกว่า คุณสามารถติดตามสถานะ AE และสถานะ AWB โดยใช้การเรียกกลับเมื่อจับภาพที่ตั้งค่าเป็นคําขอที่ทําซ้ำ

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

บันทึกการอัปเดตโค้ดการเรียกกลับเพื่อรอการบรรจบ

หากต้องการอัปเดตการเรียกกลับการจับภาพซ้ำ ให้ใช้ข้อมูลโค้ดต่อไปนี้

Kotlin

private val repeatingCaptureCallback = object : CameraCaptureSession.CaptureCallback() {
    private var targetAeMode: Int? = null
    private var aeModeUpdateDeferred: CompletableDeferred? = null

    private var convergenceDeferred: CompletableDeferred? = null

    suspend fun awaitAeModeUpdate(targetAeMode: Int) {
        this.targetAeMode = targetAeMode
        aeModeUpdateDeferred = CompletableDeferred()
        // Makes the current coroutine wait until aeModeUpdateDeferred is completed. It is
        // completed once targetAeMode is found in the following capture callbacks
        aeModeUpdateDeferred?.await()
    }

    suspend fun awaitAeAwbConvergence() {
        convergenceDeferred = CompletableDeferred()
        // Makes the current coroutine wait until convergenceDeferred is completed, it will be
        // completed once both AE & AWB are reported as converged in the capture callbacks below
        convergenceDeferred?.await()
    }

    private fun process(result: CaptureResult) {
        // Checks if AE mode is updated and completes any awaiting Deferred
        aeModeUpdateDeferred?.let {
            val aeMode = result[CaptureResult.CONTROL_AE_MODE]
            if (aeMode == targetAeMode) {
                it.complete(Unit)
            }
        }

        // Checks for convergence and completes any awaiting Deferred
        convergenceDeferred?.let {
            val aeState = result[CaptureResult.CONTROL_AE_STATE]
            val awbState = result[CaptureResult.CONTROL_AWB_STATE]

            val isAeReady = (
                    aeState == null // May be null in some devices (e.g. legacy camera HW level)
                            || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED
                            || aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED
                    )

            val isAwbReady = (
                    awbState == null // May be null in some devices (e.g. legacy camera HW level)
                            || awbState == CaptureResult.CONTROL_AWB_STATE_CONVERGED
                    )

            if (isAeReady && isAwbReady) {
                // if any non-null convergenceDeferred is set, complete it
                it.complete(Unit)
            }
        }
    }

    override fun onCaptureCompleted(
        session: CameraCaptureSession,
        request: CaptureRequest,
        result: TotalCaptureResult
    ) {
        super.onCaptureCompleted(session, request, result)
        process(result)
    }
}

Java

static class AwaitingCaptureCallback extends CameraCaptureSession.CaptureCallback {
    private int mTargetAeMode;
    private CountDownLatch mAeModeUpdateLatch = null;

    private CountDownLatch mConvergenceLatch = null;

    public void awaitAeModeUpdate(int targetAeMode) {
        mTargetAeMode = targetAeMode;
        mAeModeUpdateLatch = new CountDownLatch(1);
        // Makes the current thread wait until mAeModeUpdateLatch is released, it will be
        // released once targetAeMode is found in the capture callbacks below
        try {
            mAeModeUpdateLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void awaitAeAwbConvergence() {
        mConvergenceLatch = new CountDownLatch(1);
        // Makes the current coroutine wait until mConvergenceLatch is released, it will be
        // released once both AE & AWB are reported as converged in the capture callbacks below
        try {
            mConvergenceLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void process(CaptureResult result) {
        // Checks if AE mode is updated and decrements the count of any awaiting latch
        if (mAeModeUpdateLatch != null) {
            int aeMode = result.get(CaptureResult.CONTROL_AE_MODE);
            if (aeMode == mTargetAeMode) {
                mAeModeUpdateLatch.countDown();
            }
        }

        // Checks for convergence and decrements the count of any awaiting latch
        if (mConvergenceLatch != null) {
            Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
            Integer awbState = result.get(CaptureResult.CONTROL_AWB_STATE);

            boolean isAeReady = (
                    aeState == null // May be null in some devices (e.g. legacy camera HW level)
                            || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED
                            || aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED
            );

            boolean isAwbReady = (
                    awbState == null // May be null in some devices (e.g. legacy camera HW level)
                            || awbState == CaptureResult.CONTROL_AWB_STATE_CONVERGED
            );

            if (isAeReady && isAwbReady) {
                mConvergenceLatch.countDown();
                mConvergenceLatch = null;
            }
        }
    }

    @Override
    public void onCaptureCompleted(@NonNull CameraCaptureSession session,
            @NonNull CaptureRequest request,
            @NonNull TotalCaptureResult result) {
        super.onCaptureCompleted(session, request, result);
        process(result);
    }
}

ตั้งค่าการเรียกกลับเป็นคำขอที่ซ้ำกันระหว่างการตั้งค่ากล้อง

ตัวอย่างโค้ดต่อไปนี้ช่วยให้คุณตั้งค่าการเรียกกลับเป็นคําขอที่ซ้ำกันในระหว่างการเริ่มต้นได้

Kotlin

// Open the selected camera
camera = openCamera(cameraManager, cameraId, cameraHandler)

// Creates list of Surfaces where the camera will output frames
val targets = listOf(previewSurface, imageReaderSurface)

// Start a capture session using our open camera and list of Surfaces where frames will go
session = createCameraCaptureSession(camera, targets, cameraHandler)

val captureRequest = camera.createCaptureRequest(
        CameraDevice.TEMPLATE_PREVIEW).apply { addTarget(previewSurface) }

// This will keep sending the capture request as frequently as possible until the
// session is torn down or session.stopRepeating() is called
session.setRepeatingRequest(captureRequest.build(), repeatingCaptureCallback, cameraHandler)

Java

// Open the selected camera
mCamera = openCamera(mCameraManager, mCameraId, mCameraHandler);

// Creates list of Surfaces where the camera will output frames
List targets = new ArrayList<>(Arrays.asList(mPreviewSurface, mImageReaderSurface));

// Start a capture session using our open camera and list of Surfaces where frames will go
mSession = createCaptureSession(mCamera, targets, mCameraHandler);

try {
    CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
    requestBuilder.addTarget(mPreviewSurface);

    // This will keep sending the capture request as frequently as possible until the
    // session is torn down or session.stopRepeating() is called
    mSession.setRepeatingRequest(requestBuilder.build(), mRepeatingCaptureCallback, mCameraHandler);
} catch (CameraAccessException e) {
    e.printStackTrace();
}

การทริกเกอร์และรอลำดับก่อนการจับภาพ

เมื่อตั้งค่าการเรียกกลับแล้ว คุณจะใช้ตัวอย่างโค้ดต่อไปนี้เพื่อทริกเกอร์และรอลำดับก่อนการจับภาพได้

Kotlin

private suspend fun runPrecaptureSequence() {
    // Creates a new capture request with CONTROL_AE_PRECAPTURE_TRIGGER_START
    val captureRequest = session.device.createCaptureRequest(
        CameraDevice.TEMPLATE_PREVIEW
    ).apply {
        addTarget(previewSurface)
        set(
            CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
            CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START
        )
    }

    val precaptureDeferred = CompletableDeferred()
    session.capture(captureRequest.build(), object: CameraCaptureSession.CaptureCallback() {
        override fun onCaptureCompleted(
            session: CameraCaptureSession,
            request: CaptureRequest,
            result: TotalCaptureResult
        ) {
            // Waiting for this callback ensures the precapture request has been processed
            precaptureDeferred.complete(Unit)
        }
    }, cameraHandler)

    precaptureDeferred.await()

    // Precapture trigger request has been processed, we can wait for AE & AWB convergence now
    repeatingCaptureCallback.awaitAeAwbConvergence()
}

Java

private void runPrecaptureSequence() {
    // Creates a new capture request with CONTROL_AE_PRECAPTURE_TRIGGER_START
    try {
        CaptureRequest.Builder requestBuilder =
                mSession.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        requestBuilder.addTarget(mPreviewSurface);
        requestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
                CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);

        CountDownLatch precaptureLatch = new CountDownLatch(1);
        mSession.capture(requestBuilder.build(), new CameraCaptureSession.CaptureCallback() {
            @Override
            public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                                           @NonNull CaptureRequest request,
                                           @NonNull TotalCaptureResult result) {
                Log.d(TAG, "CONTROL_AE_PRECAPTURE_TRIGGER_START processed");
                // Waiting for this callback ensures the precapture request has been processed
                precaptureLatch.countDown();
            }
        }, mCameraHandler);

        precaptureLatch.await();

        // Precapture trigger request has been processed, we can wait for AE & AWB convergence now
        mRepeatingCaptureCallback.awaitAeAwbConvergence();
    } catch (CameraAccessException | InterruptedException e) {
        e.printStackTrace();
    }
}

ต่อกัน

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

Kotlin

// User clicks captureButton to take picture
captureButton.setOnClickListener { v ->
    // Apply the screen flash related UI changes
    whiteColorOverlayView.visibility = View.VISIBLE
    maximizeScreenBrightness()

    // Perform I/O heavy operations in a different scope
    lifecycleScope.launch(Dispatchers.IO) {
        // Enable external flash AE mode and wait for it to be processed
        enableExternalFlashAeMode()

        // Run precapture sequence and wait for it to complete
        runPrecaptureSequence()

        // Start taking picture and wait for it to complete
        takePhoto()

        disableExternalFlashAeMode()
        v.post {
            // Clear the screen flash related UI changes
            restoreScreenBrightness()
            whiteColorOverlayView.visibility = View.INVISIBLE
        }
    }
}

Java

// User clicks captureButton to take picture
mCaptureButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // Apply the screen flash related UI changes
        mWhiteColorOverlayView.setVisibility(View.VISIBLE);
        maximizeScreenBrightness();

        // Perform heavy operations in a different thread
        Executors.newSingleThreadExecutor().execute(() -> {
            // Enable external flash AE mode and wait for it to be processed
            enableExternalFlashAeMode();

            // Run precapture sequence and wait for it to complete
            runPrecaptureSequence();

            // Start taking picture and wait for it to complete
            takePhoto();

            disableExternalFlashAeMode();

            v.post(() -> {
                // Clear the screen flash related UI changes
                restoreScreenBrightness();
                mWhiteColorOverlayView.setVisibility(View.INVISIBLE);
            });
        });
    }
});

รูปภาพตัวอย่าง

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

เมื่อทำผิด

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

ตารางต่อไปนี้แสดงตัวอย่างของปัญหาดังกล่าว ซึ่งถ่ายในโครงสร้างพื้นฐานของห้องทดลอง CameraX โดยแหล่งกำเนิดแสงจะเป็นสีขาวนวล แหล่งแสงสีขาวนวลนี้จะช่วยให้คุณเห็นว่าสีฟ้าเป็นปัญหาจริง ไม่ใช่ผลข้างเคียงของแหล่งแสง

สภาพแวดล้อม แสงน้อย การเปิดรับแสงมากเกินไป สีจาง
สภาพแวดล้อมที่มืด (ไม่มีแหล่งกำเนิดแสงอื่นนอกจากโทรศัพท์) รูปภาพที่มืดเกือบทั้งหมด รูปภาพที่สว่างเกินไป รูปภาพที่มีโทนสีม่วง
แสงน้อย (แหล่งแสงเพิ่มเติมประมาณ 3 ลักซ์) รูปภาพค่อนข้างมืด รูปภาพที่สว่างเกินไป รูปภาพที่มีโทนสีน้ำเงิน

เมื่อทำถูกต้องแล้ว

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

สภาพแวดล้อม การเปิดรับแสงน้อย (แก้ไขแล้ว) การรับแสงมากเกินไป (คงที่) สีจาง (แก้ไขแล้ว)
สภาพแวดล้อมที่มืด (ไม่มีแหล่งกำเนิดแสงอื่นนอกจากโทรศัพท์) ล้างรูปภาพ ล้างรูปภาพ รูปภาพที่ชัดเจนโดยไม่มีการปรับสี
แสงน้อย (แหล่งกำเนิดแสงประมาณ 3 ลักซ์เพิ่มเติม) ล้างรูปภาพ ล้างรูปภาพ รูปภาพที่ชัดเจนโดยไม่มีการปรับสี

จากที่ได้สังเกต คุณภาพรูปภาพดีขึ้นอย่างมากเมื่อใช้การติดตั้งมาตรฐาน