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

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

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

Kotlin

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

Java

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

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

CameraXConfig

כדי לשמור על פשטות, ל- CameraX יש הגדרות ברירת מחדל, כמו סקריפטים ומעבדים פנימיים, שמתאימות לרוב תרחישי השימוש. עם זאת, אם לאפליקציה שלכם יש דרישות מיוחדות או שאתם מעדיפים להתאים אישית את ההגדרות האלה, 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 האלה רק משרשורים ברקע, כדי שה-thread הראשי לא חסום וממשק המשתמש יישאר גמיש. CameraX מנהלת באופן פנימי את השרשור ברקע כדי שההתנהגות הזו תהיה שקופה. אבל, אפליקציות מסוימות דורשות שליטה מחמירה על תהליכונים. CameraXConfig מאפשר לאפליקציה להגדיר את שרשראות הרקע שבהן נעשה שימוש דרך CameraXConfig.Builder.setCameraExecutor() ו-CameraXConfig.Builder.setSchedulerHandler().

Camera Executor

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

מטפל במתזמן

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

  • המכשיר לא תומך ברזולוציה המבוקשת.
  • יש בעיות תאימות במכשיר, למשל מכשירים מדור קודם שדורשים רזולוציות מסוימות כדי לפעול כראוי.
  • במכשירים מסוימים, פורמטים מסוימים זמינים רק ביחסי גובה-רוחב מסוימים.
  • למכשיר יש העדפה ל-'mod16 קרוב' לקידוד JPEG או וידאו. מידע נוסף זמין במאמר 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.

Crop rect

כברירת מחדל, מלבן החיתוך הוא מלבן מלא. אפשר להתאים אישית את הקוד באמצעות 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)

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

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

כשמשתמשים בתכונה 'מצלמה בו-זמנית', המכשיר יכול להפעיל בו-זמנית שתי מצלמות עם עדשות הפונות לכיוונים שונים, או להפעיל בו-זמנית שתי מצלמות אחוריות. בקוד הבא מוסבר איך להגדיר שתי מצלמות בקריאה ל-bindToLifecycle, ואיך לקבל את שני אובייקטי Camera מהאובייקט 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 באופן אוטומטי. בטבלה הבאה מפורטות הרזולוציות:

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

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

אחזור של מופעים של 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() כדי לקבוע אם פנס זמין. אם לא, הקריאה ל-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 משתמש במספר המקסימלי של נקודות מדידה נתמכות, לפי הסדר שבו הנקודות נוספו. המערכת מתעלמת מכל נקודות המדידה שנוספו אחרי המספר המקסימלי. לדוגמה, אם FocusMeteringAction מסופק עם 3 נקודות מדידה בפלטפורמה שתומכת רק ב-2, נעשה שימוש רק בשתי נקודות המדידה הראשונות. מערכת CameraX מתעלמת מה-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