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

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

תרשים רעיוני למערכת להקלטת וידאו ואודיו
איור 1. תרשים רעיוני לווידאו ולאודיו במערכת הצילום.

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

תרשים רעיוני שמראה איך מצלמה x מטפלת
         תרחיש לדוגמה לשימוש בצילום וידאו
איור 2. תרשים רעיוני שמראה איך CameraX מטפל בתרחיש לדוגמה של VideoCapture.

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

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

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

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

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

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

  • VideoCapture הוא ברמה העליונה של תרחיש לדוגמה. VideoCapture מקושר אל LifecycleOwner עם CameraSelector ו- CameraX אחר תרחישים לדוגמה. למידע נוסף על המושגים והשימושים האלה, ראו ארכיטקטורת CameraX.
  • Recorder הוא של VideoOutput בשילוב עם VideoCapture. האפליקציה Recorder משמשת לצילום וידאו ואודיו. האפליקציה יוצרת הקלטות מ-Recorder.
  • PendingRecording מגדיר הקלטה, ומספק אפשרויות כמו הפעלת אודיו והגדרות האזנה לאירועים. כדי ליצור 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

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

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

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

קישור צילום וידאו

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

  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 לסרטון באיכות HD מלאה (1080p)
  • Quality.HD לגודל וידאו באיכות HD (720p)
  • Quality.SD לגודל וידאו באיכות SD (480p)

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

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

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

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

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

הפונקציה 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().
  • רישום מאזינים כדי לקבל אירועים של הקלטת וידאו עם 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_מצב_ON ו MIRROR_מצב_ON_FRONT_ONLY. כדי ליישר עם תצוגה מקדימה של המצלמה, Google ממליצה להשתמש ב-MIROR_מצב_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));
}
  VideoCaptureR<ecorder >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, ניתן לעיין במשאבים הנוספים הבאים: