עדשות מצלמה ויכולות

הערה: הדף הזה מתייחס לחבילה Camera2. אלא אם האפליקציה שלך דורשת תכונות ספציפיות ברמה נמוכה מ-Camera2, מומלץ להשתמש ב-CameraX. ‫CameraX ו-Camera2 תומכות ב-Android מגרסה 5.0 (רמת API‏ 21) ואילך.

במכשירי Android מודרניים רבים יש שתי מצלמות או יותר בחלק הקדמי, האחורי או בשני הצדדים של המכשיר. לכל עדשה יכולות ייחודיות, כמו צילום רצף, שליטה ידנית או מעקב תנועה. אפליקציה להפקדת צ'קים עשויה להשתמש רק במצלמה הראשונה שפונה לאחור, בעוד שאפליקציה לרשתות חברתיות עשויה להשתמש כברירת מחדל במצלמה שפונה קדימה, אבל לתת למשתמשים את האפשרות לעבור בין כל העדשות הזמינות. הוא יכול גם לזכור את הבחירות שלהם.

בדף הזה מוסבר איך לפרט את עדשות המצלמה והיכולות שלהן, כדי שתוכלו לקבל החלטות באפליקציה לגבי העדשה שבה כדאי להשתמש במצב נתון. בקטע הקוד הבא מאחזרים רשימה של כל המצלמות ומבצעים איטרציה על הרשימה:

Kotlin

try {
    val cameraIdList = cameraManager.cameraIdList // may be empty

    // iterate over available camera devices
    for (cameraId in cameraIdList) {
        val characteristics = cameraManager.getCameraCharacteristics(cameraId)
        val cameraLensFacing = characteristics.get(CameraCharacteristics.LENS_FACING)
        val cameraCapabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)

        // check if the selected camera device supports basic features
        // ensures backward compatibility with the original Camera API
        val isBackwardCompatible = cameraCapabilities?.contains(
            CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) ?: false
        ...
    }
} catch (e: CameraAccessException) {
    e.message?.let { Log.e(TAG, it) }
    ...
}

Java

try {
    String[] cameraIdList = cameraManager.getCameraIdList(); // may be empty

    // iterate over available camera devices
    for (String cameraId : cameraIdList) {
        CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
        int cameraLensFacing = characteristics.get(CameraCharacteristics.LENS_FACING);
        int[] cameraCapabilities =
            characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);

        // check if the selected camera device supports basic features
        // ensures backward compatibility with the original Camera API
        boolean isBackwardCompatible = false;
        for (int capability : cameraCapabilities) {
            if (capability == CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) {
                isBackwardCompatible = true;
                break;
            }
        }
        ...
    }
} catch (CameraAccessException e) {
    Log.e(TAG, e.getMessage());
    ...
}

המשתנה cameraLensFacing מתאר את הכיוון שאליו המצלמה פונה ביחס למסך המכשיר, ויכול לקבל אחד מהערכים הבאים:

מידע נוסף על ההגדרה של המצלמה הקדמית זמין במאמר CameraCharacteristics.LENS_FACING.

המשתנה cameraCapabilities מדוגמת הקוד הקודמת מכיל מידע על יכולות שונות, כולל האם המצלמה יכולה להפיק פריים סטנדרטי כפלט (בניגוד, למשל, לנתוני חיישן עומק בלבד). אפשר לבדוק אם CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE היא אחת מהיכולות של המצלמה שמופיעות ברשימה, והיא מאוחסנת כדגל ב-isBackwardCompatible.

בחירת הגדרות ברירת מחדל הגיוניות

בדרך כלל, באפליקציה רוצים לפתוח מצלמה ספציפית כברירת מחדל (אם היא זמינה). לדוגמה, אפליקציה לצילום סלפי כנראה תפתח את המצלמה הקדמית, ואילו אפליקציה של מציאות רבודה עשויה להתחיל עם המצלמה האחורית. הפונקציה הבאה מחזירה את המצלמה הראשונה שפונה לכיוון נתון:

Kotlin

fun getFirstCameraIdFacing(cameraManager: CameraManager,
                           facing: Int = CameraMetadata.LENS_FACING_BACK): String? {
    try {
        // Get list of all compatible cameras
        val cameraIds = cameraManager.cameraIdList.filter {
            val characteristics = cameraManager.getCameraCharacteristics(it)
            val capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)
            capabilities?.contains(
                    CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) ?: false
        }

        // Iterate over the list of cameras and return the first one matching desired
        // lens-facing configuration
        cameraIds.forEach {
            val characteristics = cameraManager.getCameraCharacteristics(it)
            if (characteristics.get(CameraCharacteristics.LENS_FACING) == facing) {
                return it
            }
        }

        // If no camera matched desired orientation, return the first one from the list
        return cameraIds.firstOrNull()
    } catch (e: CameraAccessException) {
        e.message?.let { Log.e(TAG, it) }
    }
}

Java

public String getFirstCameraIdFacing(CameraManager cameraManager, @Nullable Integer facing) {
    if (facing == null) facing = CameraMetadata.LENS_FACING_BACK;
    String cameraId = null;

    try {
        // Get a list of all compatible cameras
        String[] cameraIdList = cameraManager.getCameraIdList();

        // Iterate over the list of cameras and return the first one matching desired
        // lens-facing configuration and backward compatibility
        for (String id : cameraIdList) {
            CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id);
            int[] capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
            for (int capability : capabilities) {
                if (capability == CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE
                        && characteristics.get(CameraCharacteristics.LENS_FACING).equals(facing)) {
                    cameraId = id;
                    break;
                }
            }
        }

        // If no camera matches the desired orientation, return the first one from the list
        cameraId = cameraIdList[0];
    } catch (CameraAccessException e) {
        Log.e(TAG, "getFirstCameraIdFacing: " + e.getMessage());
    }

    return cameraId;
}

הפעלת האפשרות להחלפת מצלמות

בהרבה אפליקציות מצלמה יש למשתמשים אפשרות לעבור בין המצלמות:

איור 1. לחצן להחלפת מצלמה באפליקציית מצלמת Google

במכשירים רבים יש כמה מצלמות שפונות לאותו כיוון. לחלקם יש אפילו מצלמות USB חיצוניות. כדי לספק למשתמשים ממשק משתמש שמאפשר להם לעבור בין מצלמות שפונות לכיוונים שונים, בוחרים את המצלמה הזמינה הראשונה לכל הגדרה אפשרית של עדשה שפונה לכיוון מסוים.

למרות שאין לוגיקה אוניברסלית לבחירת המצלמה הבאה, הקוד הבא מתאים לרוב תרחישי השימוש:

Kotlin

fun filterCompatibleCameras(cameraIds: Array<String>,
                            cameraManager: CameraManager): List<String> {
    return cameraIds.filter {
        val characteristics = cameraManager.getCameraCharacteristics(it)
        characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)?.contains(
                CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) ?: false
    }
}

fun filterCameraIdsFacing(cameraIds: List<String>, cameraManager: CameraManager,
                          facing: Int): List<String> {
    return cameraIds.filter {
        val characteristics = cameraManager.getCameraCharacteristics(it)
        characteristics.get(CameraCharacteristics.LENS_FACING) == facing
    }
}

fun getNextCameraId(cameraManager: CameraManager, currCameraId: String? = null): String? {
    // Get all front, back and external cameras in 3 separate lists
    val cameraIds = filterCompatibleCameras(cameraManager.cameraIdList, cameraManager)
    val backCameras = filterCameraIdsFacing(
            cameraIds, cameraManager, CameraMetadata.LENS_FACING_BACK)
    val frontCameras = filterCameraIdsFacing(
            cameraIds, cameraManager, CameraMetadata.LENS_FACING_FRONT)
    val externalCameras = filterCameraIdsFacing(
            cameraIds, cameraManager, CameraMetadata.LENS_FACING_EXTERNAL)

    // The recommended order of iteration is: all external, first back, first front
    val allCameras = (externalCameras + listOf(
            backCameras.firstOrNull(), frontCameras.firstOrNull())).filterNotNull()

    // Get the index of the currently selected camera in the list
    val cameraIndex = allCameras.indexOf(currCameraId)

    // The selected camera may not be in the list, for example it could be an
    // external camera that has been removed by the user
    return if (cameraIndex == -1) {
        // Return the first camera from the list
        allCameras.getOrNull(0)
    } else {
        // Return the next camera from the list, wrap around if necessary
        allCameras.getOrNull((cameraIndex + 1) % allCameras.size)
    }
}

Java

public List<String> filterCompatibleCameras(CameraManager cameraManager, String[] cameraIds) {
    final List<String> compatibleCameras = new ArrayList<>();

    try {
        for (String id : cameraIds) {
            CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id);
            int[] capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
            for (int capability : capabilities) {
                if (capability == CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) {
                    compatibleCameras.add(id);
                }
            }
        }
    } catch (CameraAccessException e) {
        Log.e(TAG, "filterCompatibleCameras: " + e.getMessage());
    }

    return compatibleCameras;
}

public List<String> filterCameraIdsFacing(CameraManager cameraManager, List<String> cameraIds, int lensFacing) {
    final List<String> compatibleCameras = new ArrayList<>();

    try {
        for (String id : cameraIds) {
            CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id);
            if (characteristics.get(CameraCharacteristics.LENS_FACING) == lensFacing) {
                compatibleCameras.add(id);
            }
        }
    } catch (CameraAccessException e) {
        Log.e(TAG, "filterCameraIdsFacing: " + e.getMessage());
    }

    return compatibleCameras;
}

public String getNextCameraId(CameraManager cameraManager, @Nullable String currentCameraId) {
    String nextCameraId = null;

    try {
        // Get all front, back, and external cameras in 3 separate lists
        List<String> compatibleCameraIds = filterCompatibleCameras(cameraManager, cameraManager.getCameraIdList());
        List<String> backCameras = filterCameraIdsFacing(cameraManager, compatibleCameraIds, CameraMetadata.LENS_FACING_BACK);
        List<String> frontCameras = filterCameraIdsFacing(cameraManager, compatibleCameraIds, CameraMetadata.LENS_FACING_FRONT);
        List<String>externalCameras = filterCameraIdsFacing(cameraManager, compatibleCameraIds, CameraMetadata.LENS_FACING_EXTERNAL);

        // The recommended order of iteration is: all external, first back, first front
        List<String> allCameras = new ArrayList<>(externalCameras);
        if (!backCameras.isEmpty()) allCameras.add(backCameras.get(0));
        if (!frontCameras.isEmpty()) allCameras.add(frontCameras.get(0));

        // Get the index of the currently selected camera in the list
        int cameraIndex = allCameras.indexOf(currentCameraId);

        // The selected camera may not be in the list, for example it could be an
        // external camera that has been removed by the user
        if (cameraIndex == -1) {
            // Return the first camera from the list
            nextCameraId = !allCameras.isEmpty() ? allCameras.get(0) : null;
        else {
            if (!allCameras.isEmpty()) {
                // Return the next camera from the list, wrap around if necessary
                nextCameraId = allCameras.get((cameraIndex + 1) % allCameras.size());
            }
        }
    } catch (CameraAccessException e) {
        Log.e(TAG, "getNextCameraId: " + e.getMessage());
    }

    return nextCameraId;
}

הקוד הזה פועל במגוון רחב של מכשירים עם תצורות שונות. מידע נוסף על הטיפול במקרים חריגים זמין במאמר CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA.

יצירת אפליקציות תואמות

באפליקציות שעדיין משתמשות ב-Camera API שהוצא משימוש, המספר של המצלמות שמוחזרות על ידי Camera.getNumberOfCameras() תלוי בהטמעה של יצרן הציוד המקורי. אם יש במערכת מצלמה לוגית מרובת עדשות, כדי לשמור על תאימות לאחור של האפליקציה, השיטה הזו תחשוף רק מצלמה אחת לכל מצלמה לוגית וקבוצת מצלמות פיזיות בסיסית. אפשר להשתמש ב-Camera2 API כדי לראות את כל המצלמות.

מידע נוסף על כיווני המצלמה זמין במאמר בנושא Camera.CameraInfo.orientation.

באופן כללי, כדאי להשתמש ב-API של Camera.getCameraInfo() כדי לשלוח שאילתה לכל מצלמות orientation, ולהציג רק מצלמה אחת לכל כיוון זמין למשתמשים שעוברים בין מצלמות.

התאמה לכל סוגי המכשירים

אל תניחו שהאפליקציה שלכם תמיד פועלת במכשיר שניתן להחזיק ביד עם מצלמה אחת או שתיים. במקום זאת, בוחרים את המצלמות המתאימות ביותר לאפליקציה. אם לא צריך מצלמה ספציפית, בוחרים את המצלמה הראשונה שפונה לכיוון הרצוי. אם המצלמה שפונה לכיוון מסוים לא זמינה, כדאי לבדוק אם המשתמש יכול להשלים את התהליך באמצעות מצלמה אחרת. אל תגבילו את הזמינות של האפליקציה במכשירים מסוימים על סמך חומרת המצלמה. אם מחוברת מצלמה חיצונית, סביר להניח שהמשתמש מעדיף אותה כברירת מחדל.