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

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

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

Kotlin

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

Java

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

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

CameraXConfig

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

Camera Executor

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

Scheduler Handler

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

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

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

סיבוב

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

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

בחירת מצלמה

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 באופן אוטומטי. בטבלה הבאה מפורטות הרזולוציות:

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

Torch

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

פיצוי חשיפה

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

Exposure = ExposureCompensationIndex * ExposureCompensationStep

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

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

מערכת 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