ארכיטקטורה לצילום וידאו של CameraX

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

דיאגרמה מושגית של מערכת ללכידת וידאו ואודיו
איור 1. דיאגרמה מושגית של מערכת ללכידת וידאו ואודיו.

ב-CameraX, הפתרון לצילום וידאו הוא תרחיש השימוש VideoCapture:

דיאגרמה קונספטואלית שמראה איך Camera X מטפל בתרחיש השימוש של צילום וידאו
איור 2. דיאגרמה קונספטואלית שמראה איך CameraX מטפל VideoCapture בתרחיש השימוש.

כפי שמוצג באיור 2, צילום וידאו באמצעות CameraX כולל כמה רכיבים ארכיטקטוניים ברמה גבוהה:

  • SurfaceProvider למקור הווידאו.
  • AudioSource למקור האודיו.
  • שני מקודדים לקידוד ולדחיסה של וידאו ואודיו.
  • ממקסס מדיה כדי למקסס את שני הזרמים.
  • כלי לשמירת קבצים לכתיבת התוצאה.

ממשק VideoCapture API מפשט את מנוע הלכידה המורכב ומספק לאפליקציות ממשק API פשוט וישיר יותר.

סקירה כללית על VideoCapture API

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

ממשק ה-API של VideoCapture מורכב מהאובייקטים הבאים שמתקשרים עם אפליקציות:

  • VideoCapture היא מחלקת תרחישי השימוש ברמה העליונה. ‫VideoCapture נקשר ל-LifecycleOwner עם CameraSelector ול-CameraX UseCases אחרים. מידע נוסף על המושגים ועל השימושים האלה זמין במאמר ארכיטקטורת CameraX.
  • Recorder הוא הטמעה של VideoOutput שמקושרת באופן הדוק ל-VideoCapture. אפליקציית Recorder משמשת לצילום הווידאו והאודיו. אפליקציה יוצרת הקלטות מתוך Recorder.
  • PendingRecording מגדיר הקלטה, ומספק אפשרויות כמו הפעלת אודיו והגדרת event listener. כדי ליצור PendingRecording, צריך להשתמש בRecorder. ההקלטה של PendingRecording לא כוללת שום דבר.
  • Recording מבצע את ההקלטה בפועל. כדי ליצור Recording, צריך להשתמש בPendingRecording.

איור 3 מציג את הקשרים בין האובייקטים האלה:

דיאגרמה שמציגה את האינטראקציות שמתרחשות בתרחיש שימוש של צילום וידאו
איור 3. דיאגרמה שמציגה את האינטראקציות שמתרחשות בתרחיש שימוש של VideoCapture.

מקרא:

  1. יצירת Recorder עם QualitySelector.
  2. מגדירים את Recorder באמצעות אחת מהאפשרויות של OutputOptions.
  3. אם צריך, מפעילים את האודיו באמצעות withAudioEnabled().
  4. מתקשרים אל start() עם מאזין VideoRecordEvent כדי להתחיל את ההקלטה.
  5. כדי לשלוט בהקלטה, משתמשים בלחצנים pause()/resume()/stop() ב-Recording.
  6. מגיבים ל-VideoRecordEvents בתוך ה-event listener.

רשימת ה-API המפורטת מופיעה בקובץ current.txt בתוך קוד המקור.

שימוש ב-VideoCapture API

כדי לשלב את תרחיש השימוש של CameraX VideoCapture באפליקציה, מבצעים את הפעולות הבאות:

  1. כריכה VideoCapture.
  2. הכנה והגדרה של ההקלטה.
  3. מתחילים את ההקלטה של זמן הריצה ושולטים בה.

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

כריכת VideoCapture

כדי לקשר את תרחיש השימוש VideoCapture:

  1. יוצרים אובייקט Recorder.
  2. יצירת אובייקט VideoCapture.
  3. כבילה ל-Lifecycle.

ממשק CameraX VideoCapture API פועל לפי דפוס העיצוב של Builder. אפליקציות משתמשות ב-Recorder.Builder כדי ליצור Recorder. אפשר גם להגדיר את רזולוציית הסרטון של Recorder באמצעות אובייקט QualitySelector.

‫CameraX Recorder תומך בQualities מוגדרים מראש של רזולוציות וידאו:

  • Quality.UHD לגודל סרטון 4K Ultra HD‏ (2160p)
  • Quality.FHD לגודל סרטון Full HD ‏ (1080p)
  • Quality.HD לגודל של סרטון HD ‏ (720p)
  • Quality.SD לגודל וידאו SD ‏ (480p)

שימו לב: CameraX יכולה גם לבחור רזולוציות אחרות כשהאפליקציה מאשרת זאת.

הגודל המדויק של כל סרטון תלוי ביכולות של המצלמה והמקודד. מידע נוסף זמין במאמרי העזרה בנושא CamcorderProfile.

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

  • כדאי לספק כמה רזולוציות מועדפות באמצעות fromOrderedList(), ולכלול אסטרטגיה חלופית לשימוש במקרה שאף אחת מהרזולוציות המועדפות לא נתמכת.

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

    val qualitySelector = QualitySelector.fromOrderedList(
             listOf(Quality.UHD, Quality.FHD, Quality.HD, Quality.SD),
             FallbackStrategy.lowerQualityOrHigherThan(Quality.SD))
    
  • קודם שולחים שאילתה לגבי היכולות של המצלמה, ואז בוחרים מבין הרזולוציות הנתמכות באמצעות QualitySelector::from():

    val cameraInfo = cameraProvider.availableCameraInfos.filter {
        Camera2CameraInfo
        .from(it)
        .getCameraCharacteristic(CameraCharacteristics.LENS\_FACING) == CameraMetadata.LENS_FACING_BACK
    }
    
    val supportedQualities = QualitySelector.getSupportedQualities(cameraInfo[0])
    val filteredQualities = arrayListOf (Quality.UHD, Quality.FHD, Quality.HD, Quality.SD)
                           .filter { supportedQualities.contains(it) }
    
    // Use a simple ListView with the id of simple_quality_list_view
    viewBinding.simpleQualityListView.apply {
        adapter = ArrayAdapter(context,
                               android.R.layout.simple_list_item_1,
                               filteredQualities.map { it.qualityToString() })
    
        // Set up the user interaction to manually show or hide the system UI.
        setOnItemClickListener { _, _, position, _ ->
            // Inside View.OnClickListener,
            // convert Quality.* constant to QualitySelector
            val qualitySelector = QualitySelector.from(filteredQualities[position])
    
            // Create a new Recorder/VideoCapture for the new quality
            // and bind to lifecycle
            val recorder = Recorder.Builder()
                .setQualitySelector(qualitySelector).build()
    
             // ...
        }
    }
    
    // A helper function to translate Quality to a string
    fun Quality.qualityToString() : String {
        return when (this) {
            Quality.UHD -> "UHD"
            Quality.FHD -> "FHD"
            Quality.HD -> "HD"
            Quality.SD -> "SD"
            else -> throw IllegalArgumentException()
        }
    }
    
    

    שימו לב שהיכולת שמוחזרת מ- QualitySelector.getSupportedQualities() מובטחת לפעול לתרחיש השימוש VideoCapture או לשילוב של תרחישי השימוש VideoCapture ו-Preview. כשמשלבים עם תרחיש השימוש ImageCapture או ImageAnalysis, יכול להיות ש-CameraX עדיין לא יצליח לבצע את השילוב אם השילוב הנדרש לא נתמך במצלמה המבוקשת.

אחרי שיש לכם QualitySelector, האפליקציה יכולה ליצור אובייקט VideoCapture ולבצע את הקישור. הערה: הקישור הזה זהה לקישור בתרחישי שימוש אחרים:

val recorder = Recorder.Builder()
    .setExecutor(cameraExecutor).setQualitySelector(qualitySelector)
    .build()
val videoCapture = VideoCapture.withOutput(recorder)

try {
    // Bind use cases to camera
    cameraProvider.bindToLifecycle(
            this, CameraSelector.DEFAULT_BACK_CAMERA, preview, videoCapture)
} catch(exc: Exception) {
    Log.e(TAG, "Use case binding failed", exc)
}

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

Recorder בוחרת את הפורמט המתאים ביותר למערכת. קודק הווידאו הנפוץ ביותר הוא H.264 AVC) עם פורמט קונטיינר MPEG-4.

הגדרה ויצירה של הקלטה

מתוך Recorder, האפליקציה יכולה ליצור אובייקטים של הקלטה כדי לבצע את הלכידה של הווידאו והאודיו. אפליקציות יוצרות הקלטות על ידי ביצוע הפעולות הבאות:

  1. מגדירים את OutputOptions באמצעות prepareRecording().
  2. (אופציונלי) מפעילים את הקלטת האודיו.
  3. משתמשים ב-start() כדי לרשום מאזין VideoRecordEvent ולהתחיל בצילום הסרטון.

הפונקציהRecorder מחזירה אובייקט Recording כשמפעילים את הפונקציה start(). האפליקציה יכולה להשתמש באובייקט Recording הזה כדי לסיים את הצילום או כדי לבצע פעולות אחרות, כמו השהיה או הפעלה מחדש.

Recorder תומך באובייקט Recording אחד בכל פעם. אחרי שמתקשרים אל Recording.stop() או אל Recording.close() באובייקט Recording הקודם, אפשר להתחיל הקלטה חדשה.

בואו נבחן את השלבים האלה בפירוט. קודם כול, האפליקציה מגדירה את OutputOptions עבור מכשיר הקלטה עם Recorder.prepareRecording(). Recorder תומך בסוגים הבאים של OutputOptions:

  • FileDescriptorOutputOptions כדי לצלם ולשמור ב-FileDescriptor.
  • FileOutputOptions כדי לצלם ולשמור ב-File.
  • MediaStoreOutputOptions כדי לצלם ולשמור ב-MediaStore.

בכל סוגי OutputOptions אפשר להגדיר גודל קובץ מקסימלי באמצעות setFileSizeLimit(). אפשרויות אחרות ספציפיות לסוג הפלט, כמו ParcelFileDescriptor עבור FileDescriptorOutputOptions.

הפונקציה prepareRecording() מחזירה אובייקט PendingRecording, שהוא אובייקט ביניים שמשמש ליצירת האובייקט התואם Recording. ‫PendingRecording הוא מחלקה זמנית שלא אמורה להיות גלויה ברוב המקרים, והאפליקציה מאחסנת אותה במטמון לעיתים רחוקות.

אפשר להגדיר באפליקציות הגדרות נוספות להקלטה, כמו:

  • הפעלה של אודיו עם withAudioEnabled().
  • רושמים listener כדי לקבל אירועים של הקלטת וידאו באמצעות start(Executor, Consumer<VideoRecordEvent>).
  • אפשר להקליט הקלטה ברציפות בזמן ש-VideoCapture שאליו היא מצורפת מוגדר מחדש למצלמה אחרת, עם PendingRecording.asPersistentRecording().

כדי להתחיל לצלם, מתקשרים אל PendingRecording.start(). ‫CameraX הופך את PendingRecording ל-Recording, מוסיף את בקשת ההקלטה לתור ומחזיר לאפליקציה את אובייקט Recording שנוצר. אחרי שההקלטה מתחילה במצלמה המתאימה, CameraX שולח אירוע VideoRecordEvent.EVENT_TYPE_START.

בדוגמה הבאה אפשר לראות איך להקליט וידאו ואודיו לקובץ MediaStore:

// Create MediaStoreOutputOptions for our recorder
val name = "CameraX-recording-" +
        SimpleDateFormat(FILENAME_FORMAT, Locale.US)
                .format(System.currentTimeMillis()) + ".mp4"
val contentValues = ContentValues().apply {
   put(MediaStore.Video.Media.DISPLAY_NAME, name)
}
val mediaStoreOutput = MediaStoreOutputOptions.Builder(this.contentResolver,
                              MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
                              .setContentValues(contentValues)
                              .build()

// 2. Configure Recorder and Start recording to the mediaStoreOutput.
val recording = videoCapture.output
                .prepareRecording(context, mediaStoreOutput)
                .withAudioEnabled()
                .start(ContextCompat.getMainExecutor(this), captureListener)

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

יש שלוש אפשרויות ל-MirrorMode: ‏ MIRROR_MODE_OFF,‏ MIRROR_MODE_ON ו-MIRROR_MODE_ON_FRONT_ONLY. כדי ליישר את התצוגה המקדימה של המצלמה, Google ממליצה להשתמש ב-MIRROR_MODE_ON_FRONT_ONLY, כלומר שיקוף לא מופעל במצלמה האחורית, אבל כן מופעל במצלמה הקדמית. מידע נוסף על MirrorMode זמין במאמר MirrorMode constants.

בקטע הקוד הבא אפשר לראות איך מפעילים את VideoCapture.Builder.setMirrorMode() באמצעות MIRROR_MODE_ON_FRONT_ONLY. מידע נוסף זמין במאמר setMirrorMode().

Kotlin

val recorder = Recorder.Builder().build()

val videoCapture = VideoCapture.Builder(recorder)
    .setMirrorMode(MIRROR_MODE_ON_FRONT_ONLY)
    .build()

useCases.add(videoCapture);

Java

Recorder.Builder builder = new Recorder.Builder();
if (mVideoQuality != QUALITY_AUTO) {
    builder.setQualitySelector(
        QualitySelector.from(mVideoQuality));
}
  VideoCapture<Recorder> videoCapture = new VideoCapture.Builder<>(builder.build())
      .setMirrorMode(MIRROR_MODE_ON_FRONT_ONLY)
      .build();
    useCases.add(videoCapture);

שליטה בהקלטה פעילה

אפשר להשהות, להפעיל מחדש ולעצור Recording מתמשך באמצעות השיטות הבאות:

  • pause כדי להשהות את ההקלטה הפעילה הנוכחית.
  • resume() כדי להמשיך הקלטה פעילה שהושהתה.
  • stop() כדי לסיים את ההקלטה ולנקות את כל אובייקטי ההקלטה המשויכים.
  • mute() כדי להשתיק או לבטל את ההשתקה של ההקלטה הנוכחית.

שימו לב שאפשר להתקשר אל stop() כדי לסיים את Recording, בלי קשר למצב ההקלטה – מושהה או פעילה.

אם רשמתם EventListener ב-PendingRecording.start(), ‏ Recording מתקשר באמצעות VideoRecordEvent.

  • VideoRecordEvent.EVENT_TYPE_STATUS משמש להקלטת נתונים סטטיסטיים כמו גודל הקובץ הנוכחי ומשך הזמן של ההקלטה.
  • VideoRecordEvent.EVENT_TYPE_FINALIZE משמש לתוצאת ההקלטה וכולל מידע כמו ה-URI של הקובץ הסופי ושגיאות קשורות.

אחרי שהאפליקציה מקבלת EVENT_TYPE_FINALIZE שמציין שההקלטה הסתיימה בהצלחה, אפשר לגשת לסרטון שצולם מהמיקום שצוין ב-OutputOptions.

מקורות מידע נוספים

מידע נוסף על CameraX זמין במקורות המידע הבאים: