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

הערה: הדף הזה מתייחס לחבילה 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() ההחזרות תלויות בהטמעת ה-OEM. אם יש רשת מרובת מצלמות כדי לשמור על תאימות לאחור של האפליקציה, השיטה הזו תחשוף רק לכל מצלמה לוגית ולכל קבוצה של מצלמות פיזיות. כדי לראות את כל המצלמות, צריך להשתמש ב- Camera2 API.

למידע נוסף על כיוון התצוגה של המצלמה: Camera.CameraInfo.orientation

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

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

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