אפשרויות הגדרה

מגדירים כל תרחיש לדוגמה של CameraX כדי לשלוט בהיבטים שונים של הפעולות בתרחיש לדוגמה.

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

Kotlin

val imageCapture = ImageCapture.Builder()
    .setFlashMode(...)
    .setTargetAspectRatio(...)
    .build()

Java

ImageCapture imageCapture =
    new ImageCapture.Builder()
        .setFlashMode(...)
        .setTargetAspectRatio(...)
        .build();

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

CameraXConfig

כדי לפשט את התהליך, ל-CameraX יש הגדרות ברירת מחדל כמו מנהלי ביצוע פנימיים ומנהלי handler שמתאימים לרוב תרחישי השימוש. עם זאת, אם לאפליקציה שלכם יש דרישות מיוחדות או שאתם מעדיפים להתאים אישית את ההגדרות האלה, CameraXConfig הוא הממשק המתאים לכך.

באמצעות CameraXConfig, אפליקציה יכולה:

מודל השימוש

בשלבים הבאים מוסבר איך להשתמש ב-CameraXConfig:

  1. יוצרים אובייקט CameraXConfig עם ההגדרות המותאמות אישית.
  2. מטמיעים את הממשק CameraXConfig.Provider ב-Application ומחזירים את האובייקט CameraXConfig ב-getCameraXConfig().
  3. מוסיפים את הכיתה Application לקובץ AndroidManifest.xml, כמו שמתואר כאן.

לדוגמה, קטע הקוד הבא מגביל את הרישום ביומן של CameraX להודעות שגיאה בלבד:

Kotlin

class CameraApplication : Application(), CameraXConfig.Provider {
   override fun getCameraXConfig(): CameraXConfig {
       return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
           .setMinimumLoggingLevel(Log.ERROR).build()
   }
}

שומרים עותק מקומי של אובייקט CameraXConfig אם האפליקציה צריכה לדעת את ההגדרה של CameraX אחרי שמגדירים אותה.

מגביל המצלמה

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

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

Kotlin

class MainApplication : Application(), CameraXConfig.Provider {
   override fun getCameraXConfig(): CameraXConfig {
       return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
              .setAvailableCamerasLimiter(CameraSelector.DEFAULT_BACK_CAMERA)
              .build()
   }
}

שרשורים

הרבה ממשקי API של פלטפורמות שעליהם מבוסס CameraX דורשים חסימה של תקשורת בין תהליכים (IPC) עם חומרה, ולפעמים לוקח מאות אלפיות השנייה עד שהם מגיבים. לכן, CameraX קורא לממשקי ה-API האלה רק מ-threads ברקע, כדי שה-thread הראשי לא ייחסם וממשק המשתמש יישאר חלק. ‫CameraX מנהלת באופן פנימי את השרשורים האלה ברקע, כך שההתנהגות הזו נראית שקופה. עם זאת, יש אפליקציות שנדרש בהן בקרה קפדנית על השרשורים. ‫CameraXConfig מאפשר לאפליקציה להגדיר את השרשורים ברקע שנעשה בהם שימוש באמצעות CameraXConfig.Builder.setCameraExecutor() ו-CameraXConfig.Builder.setSchedulerHandler().

Camera Executor

ה-executor של המצלמה משמש לכל הקריאות הפנימיות ל-API של פלטפורמת המצלמה, וגם לקריאות חוזרות מממשקי ה-API האלה. ‫CameraX מקצה ומנהל Executor פנימי כדי לבצע את המשימות האלה. עם זאת, אם האפליקציה שלכם דורשת שליטה מחמירה יותר בשרשורים, אתם צריכים להשתמש ב-CameraXConfig.Builder.setCameraExecutor().

Scheduler Handler

ה-handler של מתזמן המשימות משמש לתזמון משימות פנימיות במרווחי זמן קבועים, כמו ניסיון חוזר לפתוח את המצלמה כשהיא לא זמינה. ה-handler הזה לא מריץ עבודות, והוא רק שולח אותן למבצע המצלמה. הוא משמש גם לפלטפורמות API מדור קודם שדורשות Handler לקריאות חוזרות. במקרים כאלה, הקריאות החוזרות עדיין נשלחות ישירות רק למבצע של המצלמה. ‫CameraX מקצה ומנהל HandlerThread פנימי כדי לבצע את המשימות האלה, אבל אפשר לבטל את ההקצאה באמצעות CameraXConfig.Builder.setSchedulerHandler().

רישום

רישום ביומן של CameraX מאפשר לאפליקציות לסנן הודעות ב-logcat, כי מומלץ להימנע מהודעות מפורטות בקוד הייצור. ‫CameraX תומך בארבע רמות של רישום ביומן, מהמפורט ביותר ועד החמור ביותר:

  • Log.DEBUG (ברירת מחדל)
  • Log.INFO
  • Log.WARN
  • Log.ERROR

במסמכי התיעוד של יומן Android מופיעים תיאורים מפורטים של רמות היומן האלה. כדי להגדיר את רמת הרישום המתאימה לאפליקציה, צריך להשתמש ב-CameraXConfig.Builder.setMinimumLoggingLevel(int).

בחירה אוטומטית

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

המטרה של CameraX היא לאתחל סשן מצלמה בהצלחה. המשמעות היא ש-CameraX מתפשרת על הרזולוציה ויחסי הגובה-רוחב בהתאם ליכולות המכשיר. הפריצה יכולה לקרות בגלל:

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

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

סיבוב

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

האפליקציה יכולה להגדיר את סיבוב היעד באמצעות הגדרות התצורה. לאחר מכן, אפשר לעדכן את הגדרות הרוטציה באמצעות ה-methods מממשקי ה-API של תרחישי השימוש (למשל ImageAnalysis.setTargetRotation()), גם בזמן שמחזור החיים נמצא במצב פעיל. אפשר להשתמש בזה כשהאפליקציה נעולה למצב אנכי – ולכן לא מתבצעת הגדרה מחדש בסיבוב – אבל התמונה או ניתוח מקרה השימוש צריכים להיות מודעים לסיבוב הנוכחי של המכשיר. לדוגמה, יכול להיות שיהיה צורך בהתאמה לסיבוב כדי שהפנים יהיו בכיוון הנכון לזיהוי פנים, או כדי שהתמונות יהיו בכיוון לרוחב או לאורך.

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

כדי להציג נתוני תצוגה מקדימה בכיוון הנכון, אפשר להשתמש בפלט המטא-נתונים מ-Preview.PreviewOutput() כדי ליצור טרנספורמציות.

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

Kotlin

override fun onCreate() {
    val imageCapture = ImageCapture.Builder().build()

    val orientationEventListener = object : OrientationEventListener(this as Context) {
        override fun onOrientationChanged(orientation : Int) {
            // Monitors orientation values to determine the target rotation value
            val rotation : Int = when (orientation) {
                in 45..134 -> Surface.ROTATION_270
                in 135..224 -> Surface.ROTATION_180
                in 225..314 -> Surface.ROTATION_90
                else -> Surface.ROTATION_0
            }

            imageCapture.targetRotation = rotation
        }
    }
    orientationEventListener.enable()
}

Java

@Override
public void onCreate() {
    ImageCapture imageCapture = new ImageCapture.Builder().build();

    OrientationEventListener orientationEventListener = new OrientationEventListener((Context)this) {
       @Override
       public void onOrientationChanged(int orientation) {
           int rotation;

           // Monitors orientation values to determine the target rotation value
           if (orientation >= 45 && orientation < 135) {
               rotation = Surface.ROTATION_270;
           } else if (orientation >= 135 && orientation < 225) {
               rotation = Surface.ROTATION_180;
           } else if (orientation >= 225 && orientation < 315) {
               rotation = Surface.ROTATION_90;
           } else {
               rotation = Surface.ROTATION_0;
           }

           imageCapture.setTargetRotation(rotation);
       }
    };

    orientationEventListener.enable();
}

בהתאם לסיבוב שמוגדר, כל תרחיש שימוש מסובב את נתוני התמונה ישירות או מספק מטא-נתונים של סיבוב לצרכנים של נתוני התמונה שלא סובבו.

  • תצוגה מקדימה: המערכת מספקת פלט של מטא-נתונים כדי לדעת מהי רזולוציית היעד של הסיבוב באמצעות Preview.getTargetRotation().
  • ImageAnalysis: פלט המטא-נתונים מסופק כדי שקואורדינטות מאגר התמונות יהיו ידועות ביחס לקואורדינטות התצוגה.
  • ImageCapture: המטא-נתונים של Exif של התמונה, המאגר או גם המאגר וגם המטא-נתונים משתנים כדי לציין את הגדרת הסיבוב. הערך שמשתנה תלוי בהטמעה של HAL.

חיתוך מלבני

כברירת מחדל, מלבן החיתוך הוא מלבן המאגר המלא. אפשר להתאים אותו אישית באמצעות ViewPort ו-UseCaseGroup. על ידי קיבוץ של תרחישי שימוש והגדרת אזור התצוגה, CameraX מבטיח שכל המלבנים לחיתוך של כל תרחישי השימוש בקבוצה יצביעו על אותו אזור בחיישן המצלמה.

קטע הקוד הבא מראה איך להשתמש בשתי המחלקות האלה:

Kotlin

val viewPort =  ViewPort.Builder(Rational(width, height), display.rotation).build()
val useCaseGroup = UseCaseGroup.Builder()
    .addUseCase(preview)
    .addUseCase(imageAnalysis)
    .addUseCase(imageCapture)
    .setViewPort(viewPort)
    .build()
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, useCaseGroup)

Java

ViewPort viewPort = new ViewPort.Builder(
         new Rational(width, height),
         getDisplay().getRotation()).build();
UseCaseGroup useCaseGroup = new UseCaseGroup.Builder()
    .addUseCase(preview)
    .addUseCase(imageAnalysis)
    .addUseCase(imageCapture)
    .setViewPort(viewPort)
    .build();
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, useCaseGroup);

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

בקטע הקוד הבא מוצג אופן ההשגה של האובייקט ViewPort:

Kotlin

val viewport = findViewById<PreviewView>(R.id.preview_view).viewPort

Java

ViewPort viewPort = ((PreviewView)findViewById(R.id.preview_view)).getViewPort();

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

בחירת מצלמה

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

בדוגמת הקוד הבאה אפשר לראות איך ליצור CameraSelector כדי להשפיע על בחירת המכשיר:

Kotlin

fun selectExternalOrBestCamera(provider: ProcessCameraProvider):CameraSelector? {
   val cam2Infos = provider.availableCameraInfos.map {
       Camera2CameraInfo.from(it)
   }.sortedByDescending {
       // HARDWARE_LEVEL is Int type, with the order of:
       // LEGACY < LIMITED < FULL < LEVEL_3 < EXTERNAL
       it.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
   }

   return when {
       cam2Infos.isNotEmpty() -> {
           CameraSelector.Builder()
               .addCameraFilter {
                   it.filter { camInfo ->
                       // cam2Infos[0] is either EXTERNAL or best built-in camera
                       val thisCamId = Camera2CameraInfo.from(camInfo).cameraId
                       thisCamId == cam2Infos[0].cameraId
                   }
               }.build()
       }
       else -> null
    }
}

// create a CameraSelector for the USB camera (or highest level internal camera)
val selector = selectExternalOrBestCamera(processCameraProvider)
processCameraProvider.bindToLifecycle(this, selector, preview, analysis)

בחירה של כמה מצלמות בו-זמנית

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

כשמשתמשים בתכונה 'מצלמות בו-זמניות', המכשיר יכול להפעיל שתי מצלמות עם עדשות שפונות לכיוונים שונים בו-זמנית, או להפעיל שתי מצלמות אחוריות בו-זמנית. בלוק הקוד הבא מראה איך להגדיר שתי מצלמות כשמבצעים קריאה ל-bindToLifecycle, ואיך לקבל את שני אובייקטי המצלמה מהאובייקט ConcurrentCamera שמוחזר.

Kotlin

// Build ConcurrentCameraConfig
val primary = ConcurrentCamera.SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
)

val secondary = ConcurrentCamera.SingleCameraConfig(
    secondaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
)

val concurrentCamera = cameraProvider.bindToLifecycle(
    listOf(primary, secondary)
)

val primaryCamera = concurrentCamera.cameras[0]
val secondaryCamera = concurrentCamera.cameras[1]

Java

// Build ConcurrentCameraConfig
SingleCameraConfig primary = new SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
);

SingleCameraConfig secondary = new SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
);

ConcurrentCamera concurrentCamera =  
    mCameraProvider.bindToLifecycle(Arrays.asList(primary, secondary));

Camera primaryCamera = concurrentCamera.getCameras().get(0);
Camera secondaryCamera = concurrentCamera.getCameras().get(1);

רזולוציית המצלמה

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

רזולוציה אוטומטית

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

יחס הגובה-רוחב שמוגדר כברירת מחדל לתרחישי שימוש של צילום תמונות וניתוח תמונות הוא 4:3.

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

לדוגמה, אפליקציה יכולה לבצע כל אחת מהפעולות הבאות:

  • הגדרת רזולוציית יעד של 4:3 או 16:9 לתרחיש שימוש
  • מציינים רזולוציה מותאמת אישית, ו-CameraX מנסה למצוא את הרזולוציה הכי קרובה
  • הגדרת יחס גובה-רוחב לחיתוך של ImageCapture

‫CameraX בוחר באופן אוטומטי את רזולוציות המשטח הפנימיות של Camera2. בטבלה הבאה מוצגות הרזולוציות:

תרחיש לדוגמה רזולוציה של משטחים פנימיים רזולוציית נתוני הפלט
תצוגה מקדימה יחס גובה-רוחב: הרזולוציה שהכי מתאימה ליעד בהגדרה. רזולוציית מסך פנימית. המטא-נתונים מסופקים כדי לאפשר חיתוך, שינוי גודל וסיבוב של התצוגה בהתאם ליחס הגובה-רוחב של היעד.
רזולוציית ברירת מחדל: הרזולוציה הגבוהה ביותר בתצוגה המקדימה, או הרזולוציה המועדפת הגבוהה ביותר במכשיר שתואמת ליחס הגובה-רוחב של התצוגה המקדימה.
רזולוציה מקסימלית: גודל התצוגה המקדימה, שמתייחס לגודל הטוב ביותר שמתאים לרזולוציית המסך של המכשיר, או ל-1080p‏ (‎1920x1080), הקטן מביניהם.
ניתוח תמונות יחס גובה-רוחב: הרזולוציה שהכי מתאימה ליעד בהגדרה. רזולוציית מסך פנימית.
רזולוציית ברירת מחדל: הגדרת ברירת המחדל של רזולוציית היעד היא 640x480. שינוי הרזולוציה של היעד ויחס הגובה-רוחב התואם יוביל לרזולוציה הנתמכת הטובה ביותר.
רזולוציה מקסימלית: רזולוציית הפלט המקסימלית של מכשיר המצלמה בפורמט YUV_420_888, שמאוחזרת מ-StreamConfigurationMap.getOutputSizes(). רזולוציית היעד מוגדרת כ-640x480 כברירת מחדל, לכן אם רוצים רזולוציה גדולה יותר מ-640x480, צריך להשתמש בתגים setTargetResolution() ו- setTargetAspectRatio() כדי לקבל את הרזולוציה הכי קרובה מבין הרזולוציות הנתמכות.
צילום תמונה יחס גובה-רוחב: יחס הגובה-רוחב שהכי מתאים להגדרה. רזולוציית מסך פנימית.
רזולוציית ברירת מחדל: הרזולוציה הגבוהה ביותר שזמינה, או הרזולוציה הגבוהה ביותר שהמכשיר מעדיף שתואמת ליחס הגובה-רוחב של ImageCapture.
רזולוציה מקסימלית: רזולוציית הפלט המקסימלית של מכשיר המצלמה בפורמט JPEG. אפשר לאחזר את הנתונים באמצעות StreamConfigurationMap.getOutputSizes().

ציון רזולוציה

אפשר להגדיר רזולוציות ספציפיות כשיוצרים תרחישים לדוגמה באמצעות השיטה setTargetResolution(Size resolution), כמו בדוגמת הקוד הבאה:

Kotlin

val imageAnalysis = ImageAnalysis.Builder()
    .setTargetResolution(Size(1280, 720))
    .build()

Java

ImageAnalysis imageAnalysis =
  new ImageAnalysis.Builder()
    .setTargetResolution(new Size(1280, 720))
    .build();

אי אפשר להגדיר גם יחס רוחב-גובה של היעד וגם רזולוציית יעד באותו תרחיש שימוש. אם עושים זאת, מתקבלת שגיאה IllegalArgumentException כשיוצרים את אובייקט ההגדרה.

מבטאים את הרזולוציה Size במערכת הצירים אחרי סיבוב המידות הנתמכות בסיבוב היעד. לדוגמה, מכשיר עם אוריינטציה טבעית לאורך בסיבוב טבעי של הטירגוט שמבקש תמונה לאורך יכול לציין ‎480x640, ואותו מכשיר, שמסובב ב-90 מעלות ומטרגט אוריינטציה לרוחב, יכול לציין ‎640x480.

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

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

‫CameraX בוחר את הרזולוציה המתאימה ביותר על סמך הבקשות. אם הצורך העיקרי הוא להתאים את יחס הגובה-רוחב, מציינים רק setTargetAspectRatio, ו-CameraX קובע רזולוציה ספציפית שמתאימה למכשיר. אם הצורך העיקרי של האפליקציה הוא לציין רזולוציה כדי לייעל את עיבוד התמונות (למשל, תמונה קטנה או בינונית על סמך יכולת העיבוד של המכשיר), צריך להשתמש בהרשאה setTargetResolution(Size resolution).

אם האפליקציה שלכם דורשת רזולוציה מדויקת, תוכלו לעיין בטבלה שבמאמר createCaptureSession() כדי לראות אילו רזולוציות מקסימליות נתמכות בכל רמת חומרה. כדי לבדוק אילו רזולוציות ספציפיות נתמכות במכשיר הנוכחי, אפשר לעיין במאמר StreamConfigurationMap.getOutputSizes(int).

אם האפליקציה פועלת ב-Android מגרסה 10 ואילך, אפשר להשתמש ב-isSessionConfigurationSupported() כדי לאמת SessionConfiguration ספציפי.

שליטה בפלט המצלמה

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

  • CameraControl מאפשר לכם להגדיר תכונות נפוצות של המצלמה.
  • CameraInfo מאפשר לכם לשלוח שאילתות לגבי המצבים של תכונות המצלמה הנפוצות האלה.

אלה תכונות המצלמה הנתמכות ב-CameraControl:

  • שינוי מרחק התצוגה
  • פנס
  • מיקוד ומדידה (הקשה למיקוד)
  • פיצוי חשיפה

קבלת מופעים של CameraControl ו-CameraInfo

מאחזרים מופעים של CameraControl ושל CameraInfo באמצעות האובייקט Camera שמוחזר על ידי ProcessCameraProvider.bindToLifecycle(). בדוגמה הבאה מוצג קוד:

Kotlin

val camera = processCameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)

// For performing operations that affect all outputs.
val cameraControl = camera.cameraControl
// For querying information and states.
val cameraInfo = camera.cameraInfo

Java

Camera camera = processCameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)

// For performing operations that affect all outputs.
CameraControl cameraControl = camera.getCameraControl()
// For querying information and states.
CameraInfo cameraInfo = camera.getCameraInfo()

לדוגמה, אפשר לשלוח פעולות של זום ושל CameraControl אחרי שקוראים ל-bindToLifecycle(). אחרי שמפסיקים או משמידים את הפעילות שמשמשת לקישור מופע המצלמה, CameraControl לא יכול יותר לבצע פעולות ומחזיר ListenableFuture שנכשלה.

שינוי מרחק התצוגה

ב-CameraControl יש שתי שיטות לשינוי רמת הזום:

  • setZoomRatio() מגדיר את הזום לפי יחס הזום.

    היחס צריך להיות בטווח שבין CameraInfo.getZoomState().getValue().getMinZoomRatio() ל-CameraInfo.getZoomState().getValue().getMaxZoomRatio(). אחרת, הפונקציה מחזירה ListenableFuture עם הערך 'נכשל'.

  • setLinearZoom() מגדיר את הזום הנוכחי עם ערך זום לינארי בטווח שבין 0 ל-1.0.

    היתרון של שינוי מרחק מתצוגה לינארי הוא ששדה הראייה (FOV) משתנה בהתאם לשינויים במרחק מתצוגה. לכן היא אידיאלית לשימוש בתצוגה Slider.

CameraInfo.getZoomState() מחזירה LiveData של מצב הזום הנוכחי. הערך משתנה כשהמצלמה מאותחלת או אם מגדירים את רמת הזום באמצעות setZoomRatio() או setLinearZoom(). הפעלת כל אחת מהשיטות מגדירה את הערכים שמאחורי ZoomState.getZoomRatio() ו-ZoomState.getLinearZoom(). האפשרות הזו שימושית אם רוצים להציג טקסט של יחס הזום לצד פס הזזה. פשוט מסתכלים על ZoomState LiveData כדי לעדכן את שניהם בלי להמיר.

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

פנס

CameraControl.enableTorch(boolean) מפעיל או משבית את הפנס.

אפשר להשתמש ב-CameraInfo.getTorchState() כדי לשאול מה המצב הנוכחי של הפנס. אפשר לבדוק את הערך שמוחזר על ידי CameraInfo.hasFlashUnit() כדי לקבוע אם יש פנס. אם לא, קריאה ל-calling CameraControl.enableTorch(boolean) גורמת להשלמה מיידית של ListenableFuture עם תוצאה של כשל, ומגדירה את מצב הפנס ל-TorchState.OFF.

כשהפנס מופעל, הוא נשאר דולק בזמן צילום תמונות וסרטונים, בלי קשר להגדרה של flashMode. ‫flashMode ב-ImageCapture פועל רק כשהפנס מושבת.

מיקוד ומדידה

CameraControl.startFocusAndMetering() מפעיל פוקוס אוטומטי ומדידת חשיפה על ידי הגדרת אזורי מדידה של AF/AE/AWB על סמך FocusMeteringAction שצוין. היא משמשת לעיתים קרובות להטמעה של התכונה 'הקשה למיקוד' בהרבה אפליקציות מצלמה.

MeteringPoint

כדי להתחיל, יוצרים MeteringPoint באמצעות MeteringPointFactory.createPoint(float x, float y, float size). MeteringPoint מייצג נקודה אחת במצלמה Surface. הוא מאוחסן בצורה מנורמלת כדי שאפשר יהיה להמיר אותו בקלות לקואורדינטות של חיישן לצורך הגדרת אזורי AF/AE/AWB.

הגודל של MeteringPoint נע בין 0 ל-1, והגודל שמוגדר כברירת מחדל הוא 0.15f. כשקוראים ל-MeteringPointFactory.createPoint(float x, float y, float size), ‏ CameraX יוצר אזור מלבני שממורכז ב-(x, y) עבור size שסופק.

בדוגמה הבאה מוצג קוד ליצירת MeteringPoint:

Kotlin

// Use PreviewView.getMeteringPointFactory if PreviewView is used for preview.
previewView.setOnTouchListener((view, motionEvent) ->  {
val meteringPoint = previewView.meteringPointFactory
    .createPoint(motionEvent.x, motionEvent.y)

}

// Use DisplayOrientedMeteringPointFactory if SurfaceView / TextureView is used for
// preview. Please note that if the preview is scaled or cropped in the View,
// it’s the application's responsibility to transform the coordinates properly
// so that the width and height of this factory represents the full Preview FOV.
// And the (x,y) passed to create MeteringPoint might need to be adjusted with
// the offsets.
val meteringPointFactory = DisplayOrientedMeteringPointFactory(
     surfaceView.display,
     camera.cameraInfo,
     surfaceView.width,
     surfaceView.height
)

// Use SurfaceOrientedMeteringPointFactory if the point is specified in
// ImageAnalysis ImageProxy.
val meteringPointFactory = SurfaceOrientedMeteringPointFactory(
     imageWidth,
     imageHeight,
     imageAnalysis)

startFocusAndMetering ו-FocusMeteringAction

כדי להפעיל את startFocusAndMetering(), האפליקציות צריכות ליצור FocusMeteringAction, שמורכב מאחד או יותר מ-MeteringPoints עם שילובים אופציונליים של מצבי מדידה מ-FLAG_AF,‏ FLAG_AE ו-FLAG_AWB. הקוד הבא מדגים את השימוש הזה:

Kotlin

val meteringPoint1 = meteringPointFactory.createPoint(x1, x1)
val meteringPoint2 = meteringPointFactory.createPoint(x2, y2)
val action = FocusMeteringAction.Builder(meteringPoint1) // default AF|AE|AWB
      // Optionally add meteringPoint2 for AF/AE.
      .addPoint(meteringPoint2, FLAG_AF | FLAG_AE)
      // The action is canceled in 3 seconds (if not set, default is 5s).
      .setAutoCancelDuration(3, TimeUnit.SECONDS)
      .build()

val result = cameraControl.startFocusAndMetering(action)
// Adds listener to the ListenableFuture if you need to know the focusMetering result.
result.addListener({
   // result.get().isFocusSuccessful returns if the auto focus is successful or not.
}, ContextCompat.getMainExecutor(this)

כפי שמוצג בקוד שלמעלה, הפונקציה startFocusAndMetering() מקבלת FocusMeteringAction שכולל MeteringPoint אחד לאזורי מדידה של AF/AE/AWB ועוד MeteringPoint ל-AF ול-AE בלבד.

באופן פנימי, CameraX ממירה את זה ל-Camera2 MeteringRectangles ומגדירה את הפרמטרים התואמים של CONTROL_AF_REGIONS / CONTROL_AE_REGIONS / CONTROL_AWB_REGIONS לבקשת הצילום.

מכיוון שלא כל מכשיר תומך ב-AF/AE/AWB ובאזורים מרובים, CameraX מבצע את FocusMeteringAction על בסיס הפתרון האפשרי הטוב ביותר. ‫CameraX משתמש במספר המקסימלי של MeteringPoints שנתמכים, בסדר שבו הנקודות נוספו. המערכת מתעלמת מכל הערכים של MeteringPoints שנוספו אחרי שהגעתם למספר המקסימלי. לדוגמה, אם מספקים FocusMeteringAction עם 3 MeteringPoints בפלטפורמה שתומכת רק ב-2, רק 2 ה-MeteringPoints הראשונים ישמשו. המצלמה מתעלמת מהערך הסופי של MeteringPoint.

פיצוי חשיפה

פיצוי החשיפה שימושי כשצריך לכוונן את ערכי החשיפה (EV) באפליקציות מעבר לתוצאת הפלט של החשיפה האוטומטית (AE). ערכי פיצוי החשיפה משולבים באופן הבא כדי לקבוע את החשיפה הנדרשת לתנאי התמונה הנוכחיים:

Exposure = ExposureCompensationIndex * ExposureCompensationStep

‫CameraX מספקת את הפונקציה Camera.CameraControl.setExposureCompensationIndex() להגדרת פיצוי החשיפה כערך אינדקס.

ערכים חיוביים של האינדקס מבהירים את התמונה, וערכים שליליים מכהים אותה. אפליקציות יכולות לשלוח שאילתה לגבי הטווח הנתמך באמצעות CameraInfo.ExposureState.exposureCompensationRange(), כפי שמתואר בקטע הבא. אם הערך נתמך, הפעולה ListenableFuture שמוחזרת מסתיימת כשהערך מופעל בהצלחה בבקשת הלכידה. אם האינדקס שצוין נמצא מחוץ לטווח הנתמך, הפעולה setExposureCompensationIndex() גורמת לסיום מיידי של הפעולה ListenableFuture שמוחזרת עם תוצאה של כשל.

‫CameraX שומרת רק את בקשת setExposureCompensationIndex() האחרונה שלא טופלה, וקריאה לפונקציה כמה פעמים לפני שהבקשה הקודמת מבוצעת גורמת לביטול שלה.

בקטע הקוד הבא מוגדר אינדקס של פיצוי חשיפה ונרשמת פונקציית קריאה חוזרת (callback) לביצוע כשבקשת שינוי החשיפה הושלמה:

Kotlin

camera.cameraControl.setExposureCompensationIndex(exposureCompensationIndex)
   .addListener({
      // Get the current exposure compensation index, it might be
      // different from the asked value in case this request was
      // canceled by a newer setting request.
      val currentExposureIndex = camera.cameraInfo.exposureState.exposureCompensationIndex
      
   }, mainExecutor)
  • Camera.CameraInfo.getExposureState() מאחזר את ExposureState הנוכחי, כולל:

    • התמיכה באמצעי הבקרה של פיצוי החשיפה.
    • מדד פיצוי החשיפה הנוכחי.
    • טווח האינדקס של פיצוי החשיפה.
    • שלב פיצוי החשיפה שמשמש לחישוב ערך פיצוי החשיפה.

לדוגמה, הקוד הבא מאתחל את ההגדרות של חשיפה SeekBar עם הערכים הנוכחיים של ExposureState:

Kotlin

val exposureState = camera.cameraInfo.exposureState
binding.seekBar.apply {
   isEnabled = exposureState.isExposureCompensationSupported
   max = exposureState.exposureCompensationRange.upper
   min = exposureState.exposureCompensationRange.lower
   progress = exposureState.exposureCompensationIndex
}

מקורות מידע נוספים

כדי לקבל מידע נוסף על CameraX, אפשר לעיין במקורות המידע הנוספים הבאים.

Codelab

  • תחילת העבודה עם CameraX
  • קוד לדוגמה

  • אפליקציות לדוגמה של CameraX
  • קהילת המפתחים

    קבוצת דיון בנושא Android CameraX