פעילויות ובקשות של צילום מצלמה

הערה: הדף הזה מתייחס לחבילה camera2. מומלץ להשתמש ב-CameraX, אלא אם לאפליקציה שלך נדרשים תכונות ספציפיות ברמה נמוכה. גם CameraX וגם Camera2 תומכים ב-Android 5.0 (רמת API 21) ואילך.

במכשיר Android אחד יכולות להיות כמה מצלמות. כל מצלמה CameraDevice ו-CameraDevice יכול פלט של יותר משידור אחד בו-זמנית.

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

איור 1. איור מפיתוח של אפליקציית מצלמה אוניברסלית (Google I/O ‘18)

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

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

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

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

CameraCaptureSession מתאר את כל צינורות עיבוד הנתונים האפשריים שקשורים CameraDevice. כשיוצרים סשן, אי אפשר להוסיף או להסיר צינורות עיבוד נתונים. השדה CameraCaptureSession נשמר בתור של CaptureRequest, שהופך את התצורה הפעילה.

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

שימוש בתרחישים לדוגמה של 'עדכונים לכיתה' לביצועים טובים יותר

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

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

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

דוגמאות לתרחישים לדוגמה של עדכוני תוכן:

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

  • PREVIEW: מומלץ לצורך ניתוח תמונות בעינית או בתוך האפליקציה.

  • STILL_CAPTURE: עבר אופטימיזציה לצילום ברזולוציה גבוהה באיכות גבוהה, ולא צפויה לשמור על קצב פריימים כמו תצוגה מקדימה.

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

  • VIDEO_CALL: מומלץ לשימושים ממושכים במצלמה כאשר התרוקנות הסוללה חשש.

  • PREVIEW_VIDEO_STILL: מומלץ לאפליקציות של רשתות חברתיות או לשימוש בסטרימינג יחיד במקרים שונים. זהו שידור רב-תכליתי.

  • VENDOR_START: משמש לתרחישים לדוגמה שהוגדרו על ידי ה-OEM.

יצירה של סשן צילום מצלמה

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

בקטע הקוד הבא מוסבר איך להכין סשן צילום באמצעות מאגרי פלט, ששייך SurfaceView ועוד אחד ImageReader הוספת תרחיש לדוגמה של סטרימינג של PREVIEW אל previewSurface וגם STILL_CAPTURE שימוש בסטרימינג הכיסוי של imReaderSurface מאפשר לחומרת המכשיר לבצע אופטימיזציה של השידורים האלה אפילו עוד יותר.

Kotlin


// Retrieve the target surfaces, which might be coming from a number of places:
// 1. SurfaceView, if you want to display the image directly to the user
// 2. ImageReader, if you want to read each frame or perform frame-by-frame
// analysis
// 3. OpenGL Texture or TextureView, although discouraged for maintainability
      reasons
// 4. RenderScript.Allocation, if you want to do parallel processing
val surfaceView = findViewById<SurfaceView>(...)
val imageReader = ImageReader.newInstance(...)

// Remember to call this only *after* SurfaceHolder.Callback.surfaceCreated()
val previewSurface = surfaceView.holder.surface
val imReaderSurface = imageReader.surface
val targets = listOf(previewSurface, imReaderSurface)

// Create a capture session using the predefined targets; this also involves
// defining the session state callback to be notified of when the session is
// ready
// Setup Stream Use Case while setting up your Output Configuration.
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
fun configureSession(device: CameraDevice, targets: List<Surface>){
    val configs = mutableListOf<OutputConfiguration>()
    val streamUseCase = CameraMetadata
        .SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL

    targets.forEach {
        val config = OutputConfiguration(it)
        config.streamUseCase = streamUseCase.toLong()
        configs.add(config)
    }
    ...
    device.createCaptureSession(session)
}

Java


// Retrieve the target surfaces, which might be coming from a number of places:
// 1. SurfaceView, if you want to display the image directly to the user
// 2. ImageReader, if you want to read each frame or perform frame-by-frame
      analysis
// 3. RenderScript.Allocation, if you want to do parallel processing
// 4. OpenGL Texture or TextureView, although discouraged for maintainability
      reasons
Surface surfaceView = findViewById<SurfaceView>(...);
ImageReader imageReader = ImageReader.newInstance(...);

// Remember to call this only *after* SurfaceHolder.Callback.surfaceCreated()
Surface previewSurface = surfaceView.getHolder().getSurface();
Surface imageSurface = imageReader.getSurface();
List<Surface> targets = Arrays.asList(previewSurface, imageSurface);

// Create a capture session using the predefined targets; this also involves defining the
// session state callback to be notified of when the session is ready
private void configureSession(CameraDevice device, List<Surface> targets){
    ArrayList<OutputConfiguration> configs= new ArrayList()
    String streamUseCase=  CameraMetadata
        .SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL

    for(Surface s : targets){
        OutputConfiguration config = new OutputConfiguration(s)
        config.setStreamUseCase(String.toLong(streamUseCase))
        configs.add(config)
}

device.createCaptureSession(session)
}

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

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

תוכנת ה-framework עושה כמיטב יכולתה, אבל כמה Surface שילובי הגדרות אישיות עשויים לא לפעול, מה שעלול לגרום לבעיות כמו הסשן לא נוצרת, דבר שיוביל לשגיאת זמן ריצה בעת שליחת בקשה, או והירידה בביצועים שלהם. המסגרת מספקת אחריות שילוב של פרמטרים של מכשיר, פלטפורמה ובקשה. התיעוד עבור createCaptureSession() מספק מידע נוסף.

בקשות TakeRequests בודדות

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

בקשות איסוף משתמשות תבנית build ולתת למפתחים את ההזדמנות להגדיר אפשרויות, כולל חשיפה אוטומטית, מיקוד אוטומטי, וגם צמצם עדשה. לפני שמגדירים שדה, צריך לוודא שהאפשרות הספציפית זמינה המכשיר באמצעות התקשרות CameraCharacteristics.getAvailableCaptureRequestKeys() ושהערך הרצוי נתמך על ידי בדיקת המצלמה המתאימה מאפיין, כמו החשיפה האוטומטית הזמינה מצבים.

כדי ליצור בקשת תיעוד של SurfaceView באמצעות התבנית שמיועד לתצוגה מקדימה ללא שינויים, CameraDevice.TEMPLATE_PREVIEW:

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest = session.device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
captureRequest.addTarget(previewSurface)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest.Builder captureRequest =
    session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
captureRequest.addTarget(previewSurface);

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

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest = ...  // from CameraDevice.createCaptureRequest()

// The first null argument corresponds to the capture callback, which you
// provide if you want to retrieve frame metadata or keep track of failed capture
// requests that can indicate dropped frames; the second null argument
// corresponds to the Handler used by the asynchronous callback, which falls
// back to the current thread's looper if null
session.capture(captureRequest.build(), null, null)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest captureRequest = ...;  // from CameraDevice.createCaptureRequest()

// The first null argument corresponds to the capture callback, which you
// provide if you want to retrieve frame metadata or keep track of failed
// capture
// requests that can indicate dropped frames; the second null argument
// corresponds to the Handler used by the asynchronous callback, which falls
// back to the current thread's looper if null
session.capture(captureRequest.build(), null, null);

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

חזרה על בקשות צילום

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

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest = ...  // from CameraDevice.createCaptureRequest()

// This keeps sending the capture request as frequently as possible until
// the
// session is torn down or session.stopRepeating() is called
// session.setRepeatingRequest(captureRequest.build(), null, null)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest captureRequest = ...;  // from CameraDevice.createCaptureRequest()

// This keeps sending the capture request as frequently as possible until the
// session is torn down or session.stopRepeating() is called
// session.setRepeatingRequest(captureRequest.build(), null, null);

בקשת צילום חוזרת גורמת למכשיר המצלמה לצלם באופן רציף תמונות באמצעות ההגדרות שבמוצר CaptureRequest שסופק. ממשק API של Camera2 מאפשר למשתמשים לצלם וידאו מהמצלמה על ידי שליחת חוזר על CaptureRequests כפי שמוצג כאן דוגמה של Camera2 ב-GitHub. הוא יכול גם לעבד סרטון בהילוך איטי על ידי צילום סרטון במהירות גבוהה (בהילוך איטי) עם רצף חוזר של CaptureRequests כפי שמוצג באפליקציה לדוגמה של סרטונים בהילוך איטי של Camera2 ב-GitHub.

שילוב בקשה לצילום של מאפיינים

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

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

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback

// Create the repeating request and dispatch it
val repeatingRequest = session.device.createCaptureRequest(
CameraDevice.TEMPLATE_PREVIEW)
repeatingRequest.addTarget(previewSurface)
session.setRepeatingRequest(repeatingRequest.build(), null, null)

// Some time later...

// Create the single request and dispatch it
// NOTE: This can disrupt the ongoing repeating request momentarily
val singleRequest = session.device.createCaptureRequest(
CameraDevice.TEMPLATE_STILL_CAPTURE)
singleRequest.addTarget(imReaderSurface)
session.capture(singleRequest.build(), null, null)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback

// Create the repeating request and dispatch it
CaptureRequest.Builder repeatingRequest =
session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
repeatingRequest.addTarget(previewSurface);
session.setRepeatingRequest(repeatingRequest.build(), null, null);

// Some time later...

// Create the single request and dispatch it
// NOTE: This can disrupt the ongoing repeating request momentarily
CaptureRequest.Builder singleRequest =
session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
singleRequest.addTarget(imReaderSurface);
session.capture(singleRequest.build(), null, null);

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

איור 2. איור של תור בקשות לסשן המצלמה המתמשך

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

  • מוסיפים את יעדי הפלט מבקשה א כדי לבקש ב. כך, המסגרת של B מוכנה, היא מועתקת ליעדי הפלט של A. לדוגמה, השלב הזה חיוני כשמבצעים תמונות מצב של סרטונים כדי לשמור קצב פריימים יציב. בקוד הקודם, singleRequest.addTarget(previewSurface) לפני יצירת הבקשה.

  • משתמשים בשילוב של תבניות שמיועדות לתרחיש המסוים הזה, כמו אפס רעידות.