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

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

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

KotlinJava
val imageCapture = ImageCapture.Builder()
    .setFlashMode(...)
    .setTargetAspectRatio(...)
    .build()
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 האלה רק מ: שרשורי רקע, כדי שה-thread הראשי לא יהיה חסום וממשק המשתמש נשאר נוזל. CameraX מנהלת באופן פנימי את שרשורי הרקע האלה כדי נראה שקוף. עם זאת, באפליקציות מסוימות נדרש בקרה קפדנית על השרשור. CameraXConfig מאפשר לאפליקציה להגדיר את שרשראות הרקע שבהן נעשה שימוש דרך CameraXConfig.Builder.setCameraExecutor() ו-CameraXConfig.Builder.setSchedulerHandler().

Camera Executor

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

מטפל במתזמן

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

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

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

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

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

KotlinJava
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()
}
@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 מבטיח אזורי חיתוך התרחישים לדוגמה בקבוצה מצביעים על אותו אזור בחיישן המצלמה.

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

KotlinJava
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)
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:

KotlinJava
val viewport = findViewById<PreviewView>(R.id.preview_view).viewPort
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 המוחזר.

KotlinJava
// 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]
// 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), כפי שמתואר בדוגמת הקוד הבאה:

KotlinJava
val imageAnalysis = ImageAnalysis.Builder()
    .setTargetResolution(Size(1280, 720))
    .build()
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(). הקוד הבא מציג דוגמה:

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

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

לפיד

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, נעשה שימוש רק בשתי נקודות המדידה הראשונות. MeteringPoint הסופי הוא CameraX התעלמה.

פיצוי חשיפה

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

Exposure = ExposureCompensationIndex * ExposureCompensationStep

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

ערכים חיוביים של המדד מבהירים את התמונה, ואילו ערכים שליליים מעמעמים אותה. האפליקציות יכולות לשלוח שאילתות על הטווח הנתמך, CameraInfo.ExposureState.exposureCompensationRange() בקטע הבא. אם הערך נתמך, הערך המוחזר של ListenableFuture יושלם כשהערך יופעל בהצלחה בבקשת הצילום. אם המדד שצוין מחוץ לטווח הנתמך, הערך המוחזר של 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