אתם מגדירים כל תרחיש לדוגמה של CameraX כדי לשלוט בהיבטים שונים של הפעולות בתרחיש לדוגמה.
לדוגמה, בתרחיש לדוגמה של צילום תמונות, אפשר להגדיר יחס גובה-רוחב ומצב פלאש. הקוד הבא מציג דוגמה אחת:
Kotlin
val imageCapture = ImageCapture.Builder() .setFlashMode(...) .setTargetAspectRatio(...) .build()
Java
ImageCapture imageCapture = new ImageCapture.Builder() .setFlashMode(...) .setTargetAspectRatio(...) .build();
בנוסף לאפשרויות ההגדרה, בחלק מתרחישי השימוש נחשפים ממשקי API שמאפשרים לשנות את ההגדרות באופן דינמי אחרי שתרחיש השימוש נוצר. למידע על הגדרות ספציפיות לתרחישי שימוש שונים, ראו הטמעת תצוגה מקדימה, ניתוח תמונות וצילום תמונות.
CameraXConfig
כדי לפשט את התהליך, ל-CameraX יש הגדרות ברירת מחדל כמו מבצעים פנימיים ו-handlers שמתאימים לרוב תרחישי השימוש. עם זאת, אם לאפליקציה יש דרישות מיוחדות או שאתם מעדיפים להתאים אישית את ההגדרות האלה, CameraXConfig הוא הממשק למטרה הזו.
בעזרת CameraXConfig, אפליקציה יכולה:
- אופטימיזציה של זמן האחזור בהפעלה באמצעות
setAvailableCameraLimiter(). - מספקים את ה-executor של האפליקציה ל-CameraX באמצעות
setCameraExecutor(). - מחליפים את ה-handler של ברירת המחדל של המתזמן ב-
setSchedulerHandler(). - משנים את רמת הרישום ביומן באמצעות
setMinimumLoggingLevel().
מודל השימוש
כך משתמשים ב-CameraXConfig:
- יוצרים אובייקט
CameraXConfigעם ההגדרות האישיות שלכם. - מטמיעים את הממשק
CameraXConfig.Providerב-Applicationומחזירים את האובייקטCameraXConfigב-getCameraXConfig(). - מוסיפים את המחלקה
ApplicationלקובץAndroidManifest.xml, כמו שמתואר כאן.
לדוגמה, דוגמת הקוד הבאה מגבילה את הרישום ביומן של CameraX להודעות שגיאה בלבד:
Kotlin
class CameraApplication : Application(), CameraXConfig.Provider { override fun getCameraXConfig(): CameraXConfig { return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig()) .setMinimumLoggingLevel(Log.ERROR).build() } }
אם האפליקציה צריכה לדעת מהי ההגדרה של CameraX אחרי שהיא נקבעה, צריך לשמור עותק מקומי של האובייקט CameraXConfig.
הגבלת המצלמה
בפעם הראשונה שמפעילים את 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 מנהל את ה-threads האלה ברקע באופן פנימי, כך שההתנהגות הזו נראית שקופה. עם זאת, יש אפליקציות שדורשות שליטה קפדנית בשרשורים. CameraXConfig מאפשר לאפליקציה להגדיר את השרשורים ברקע שמשמשים את CameraXConfig.Builder.setCameraExecutor() ואת CameraXConfig.Builder.setSchedulerHandler().
Camera Executor
ה-executor של המצלמה משמש לכל הקריאות הפנימיות ל-API של פלטפורמת המצלמה, וגם לקריאות חוזרות (callbacks) מממשקי ה-API האלה. CameraX מקצה ומנהלת Executor פנימי כדי לבצע את המשימות האלה.
עם זאת, אם האפליקציה שלכם דורשת שליטה מחמירה יותר בשרשורים, השתמשו ב-CameraXConfig.Builder.setCameraExecutor().
Scheduler Handler
ה-scheduler handler משמש לתזמון משימות פנימיות במרווחי זמן קבועים, כמו ניסיון חוזר לפתוח את המצלמה כשהיא לא זמינה. ה-handler הזה לא מבצע משימות, אלא רק שולח אותן ל-executor של המצלמה. לפעמים משתמשים בו גם בפלטפורמות API מדור קודם שדורשות Handler להפעלות חוזרות (callbacks). במקרים כאלה, הקריאות החוזרות עדיין נשלחות רק ישירות ל-executor של המצלמה. CameraX מקצה ומנהל HandlerThread פנימי כדי לבצע את המשימות האלה, אבל אפשר לשנות אותו באמצעות CameraXConfig.Builder.setSchedulerHandler().
רישום ביומן
רישום ביומן של CameraX מאפשר לאפליקציות לסנן הודעות logcat, כי כדאי להימנע מהודעות מפורטות בקוד הייצור. CameraX תומך בארבע רמות רישום ביומן, מהמפורטת ביותר ועד החמורה ביותר:
-
Log.DEBUG(ברירת מחדל) Log.INFOLog.WARNLog.ERROR
במסמכי התיעוד של Android Log מפורטים תיאורים של רמות היומן האלה. משתמשים ב-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.
מלבן חיתוך
כברירת מחדל, מלבן החיתוך הוא מלבן המאגר המלא. אפשר להתאים אישית את המדיניות הזו באמצעות כללי המדיניות 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.DEFAULT_FRONT_CAMERA. - מבקשים את המצלמה האחורית שמוגדרת כברירת מחדל באמצעות
CameraSelector.DEFAULT_BACK_CAMERA. - מסננים את רשימת המכשירים הזמינים לפי
CameraCharacteristicsבאמצעותCameraSelector.Builder.addCameraFilter().
בדוגמת הקוד הבאה אפשר לראות איך יוצרים 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, ואיך לקבל את שני האובייקטים של המצלמה מהאובייקט 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() כדי לאחזר את זה.
|
ציון רזולוציה
אפשר להגדיר רזולוציות ספציפיות כשיוצרים תרחישים לדוגמה באמצעות ה-method 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(). אם לא, קריאה ל-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 משתמש במספר המקסימלי של נקודות MeteringPoint שנתמכות, לפי הסדר שבו הנקודות נוספו. המערכת מתעלמת מכל נקודות המדידה שנוספו אחרי המספר המקסימלי. לדוגמה, אם FocusMeteringAction מסופק עם 3 נקודות מדידה בפלטפורמה שתומכת רק ב-2, המערכת תשתמש רק ב-2 נקודות המדידה הראשונות. ה-MeteringPoint האחרון לא נלקח בחשבון על ידי CameraX.
פיצוי חשיפה
פיצוי חשיפה שימושי כשאפליקציות צריכות לכוונן את ערכי החשיפה (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
דוגמת קוד
קהילת המפתחים
קבוצת הדיון של Android CameraX