צילום וידאו HDR

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

ממשקי ה-API של Camera2 תומכים בצילום וידאו בטווח דינמי גבוה (HDR), כך שאתם יכולים לצפות בתצוגה מקדימה של תוכן וידאו ב-HDR ולצלם אותו באמצעות המצלמה. בהשוואה לטווח דינמי רגיל (SDR), ‏ HDR מציע טווח צבעים רחב יותר ומגדיל את הטווח הדינמי של רכיב הבהירות (מ-100 cd/m2 הנוכחיים לאלפי cd/m2). התוצאה היא איכות וידאו שדומה יותר למציאות, עם צבעים עשירים יותר, נקודות אור בהירות יותר וצללים כהים יותר.

כך נראה שקיעה שמצולמת ב-HDR עם פרטים עשירים יותר.

איור 1. השוואה בין איכות הווידאו ב-SDR (למעלה) לבין איכות הווידאו ב-HDR (למטה).

דרישות מוקדמות לגבי מכשירים

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

  • מטרגטת ל-Android 13 (רמת API ‏33).
  • יש לו חיישן מצלמה עם יכולת של 10 ביט ומעלה. מידע נוסף על תמיכה ב-HDR זמין במאמר בדיקת התמיכה ב-HDR.

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

ארכיטקטורה של צילום ב-HDR

התרשים הבא מציג את הרכיבים העיקריים בארכיטקטורה של צילום HDR.

דיאגרמת ארכיטקטורה של צילום HDR.
איור 2. דיאגרמת ארכיטקטורה של צילום HDR.

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

בדיקת התמיכה ב-HDR

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

אפשר להשתמש בשיטה CameraManager getCameraCharacteristics() כדי לקבל מופע של CameraCharacteristics שאפשר לשלוח לו שאילתה לגבי יכולות ה-HDR של המכשיר.

כדי לבדוק אם מכשיר תומך ב-HLG10, פועלים לפי השלבים הבאים. ‫HLG10 הוא תקן ה-HDR הבסיסי שיצרני מכשירים צריכים לתמוך בו במצלמות עם פלט של 10 ביט.

  1. קודם כל, בודקים אם המכשיר תומך בפרופילים של 10 ביט (עומק הביט של HLG10):

    Kotlin

    private fun isTenBitProfileSupported(cameraId: String): Boolean {
      val cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId)
      val availableCapabilities = cameraCharacteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)
      for (capability in availableCapabilities!!) {
          if (capability == CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT) {
              return true
          }
      }
      return false
    }
  2. לאחר מכן, בודקים אם המכשיר תומך ב-HLG10 (או בפרופיל נתמך אחר):

    Kotlin

    @RequiresApi(api = 33)
    private fun isHLGSupported(cameraId: String): Boolean {
    if (isTenBitProfileSupported(cameraId)) {
      Val cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId)
      val availableProfiles = cameraCharacteristics
      .get(CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES)!!
      .getSupportedProfiles()
    
      // Checks for the desired profile, in this case HLG10
      return availableProfiles.contains(DynamicRangeProfiles.HLG10)
    }
    return false;
    }

אם המכשיר תומך ב-HDR, isHLGSupported() תמיד מחזירה true. מידע נוסף מופיע בCameraCharacteristicsמאמרי העזרה.

הגדרת צילום ב-HDR

אחרי שמוודאים שהמכשיר תומך ב-HDR, מגדירים את האפליקציה כך שתצלם שידור וידאו בפורמט RAW HDR מהמצלמה. משתמשים ב-setDynamicRangeProfile() כדי לספק ל-OutputConfiguration פרופיל HDR שנתמך במכשיר, שמועבר לאחר מכן ל-CameraCaptureSession בזמן היצירה. כאן אפשר לעיין ברשימת פרופילי ה-HDR הנתמכים.

בדוגמת הקוד הבאה, setupSessionDynamicRangeProfile() בודק קודם אם במכשיר פועלת מערכת Android 13. לאחר מכן, הוא מגדיר את CameraCaptureSession עם פרופיל ה-HDR שנתמך במכשיר כOutputConfiguration:

Kotlin

  /**
  * Creates a [CameraCaptureSession] with a dynamic range profile.
  */
  private fun setupSessionWithDynamicRangeProfile(
      dynamicRange: Long,
      device: CameraDevice,
      targets: List,
      handler: Handler? = null,
      stateCallback: CameraCaptureSession.StateCallback
  ): Boolean {
      if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
          val outputConfigs = mutableListOf()
          for (target in targets) {
              val outputConfig = OutputConfiguration(target)
              //sets the dynamic range profile, for example DynamicRangeProfiles.HLG10
              outputConfig.setDynamicRangeProfile(dynamicRange)
              outputConfigs.add(outputConfig)
          }
          device.createCaptureSessionByOutputConfigurations(
              outputConfigs, stateCallback, handler
          )
          return true
      } else {
          device.createCaptureSession(targets, stateCallback, handler)
          return false
      }
  }
  

כשאפליקציית המצלמה מאתחלת את המצלמה, היא שולחת CaptureRequest חוזר כדי להציג תצוגה מקדימה של ההקלטה:

Kotlin

session.setRepeatingRequest(previewRequest, null, cameraHandler)

כדי להתחיל את הקלטת הסרטון:

Kotlin

// Start recording repeating requests, which stops the ongoing preview
//  repeating requests without having to explicitly call
//  `session.stopRepeating`
session.setRepeatingRequest(recordRequest,
        object : CameraCaptureSession.CaptureCallback() {
    override fun onCaptureCompleted(session: CameraCaptureSession,
            request: CaptureRequest, result: TotalCaptureResult) {
        if (currentlyRecording) {
            encoder.frameAvailable()
        }
    }
}, cameraHandler)

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

כדי לקודד את הסטרימינג מהמצלמה ב-HDR ולכתוב את הקובץ לדיסק, משתמשים ב-MediaCodec.

קודם כל, מקבלים את OutputSurface, שממופה למאגר שבו מאוחסנים נתוני וידאו גולמיים. בשביל MediaCodec, משתמשים ב-createInputSurface().

כדי לאתחל את MediaCodec, אפליקציה צריכה ליצור MediaFormat עם פרופיל קודק, מרחב צבעים, טווח צבעים ופונקציית העברה מוגדרים:

Kotlin

val mimeType = when {
    dynamicRange == DynamicRangeProfiles.STANDARD -> MediaFormat.MIMETYPE_VIDEO_AVC
    dynamicRange < DynamicRangeProfiles.PUBLIC_MAX ->
            MediaFormat.MIMETYPE_VIDEO_HEVC
    else -> throw IllegalArgumentException("Unknown dynamic range format")
}

val codecProfile = when {
    dynamicRange == DynamicRangeProfiles.HLG10 ->
            MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10
    dynamicRange == DynamicRangeProfiles.HDR10 ->
            MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10
    dynamicRange == DynamicRangeProfiles.HDR10_PLUS ->
            MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10Plus
    else -> -1
}
// Failing to correctly set color transfer causes quality issues
// for example, washout and color clipping
val transferFunction = when (codecProfile) {
    MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10 ->
            MediaFormat.COLOR_TRANSFER_HLG
    MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10 ->
            MediaFormat.COLOR_TRANSFER_ST2084
    MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10Plus ->
            MediaFormat.COLOR_TRANSFER_ST2084
    else -> MediaFormat.COLOR_TRANSFER_SDR_VIDEO
}

val format = MediaFormat.createVideoFormat(mimeType, width, height)

// Set some properties.  Failing to specify some of these can cause the MediaCodec
// configure() call to throw an exception.
format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
        MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface)
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate)
format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate)
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL)

if (codecProfile != -1) {
    format.setInteger(MediaFormat.KEY_PROFILE, codecProfile)
    format.setInteger(MediaFormat.KEY_COLOR_STANDARD,
            MediaFormat.COLOR_STANDARD_BT2020)
    format.setInteger(MediaFormat.KEY_COLOR_RANGE, MediaFormat.COLOR_RANGE_LIMITED)
    format.setInteger(MediaFormat.KEY_COLOR_TRANSFER, transferFunction)
    format.setFeatureEnabled(MediaCodecInfo.CodecCapabilities.FEATURE_HdrEditing,
            true)
}

mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)

פרטים נוספים על ההטמעה זמינים בEncoderWrapper.kt של אפליקציית הדוגמה Camera2Video.

פורמטים של HDR

החל מ-Android 13, מכשירי מצלמה עם יכולות פלט של 10 ביט חייבים לתמוך ב-HLG10 לצילום והפעלה של HDR. בנוסף, יצרני מכשירים יכולים להפעיל כל פורמט HDR לפי בחירתם באמצעות ארכיטקטורת צילום HDR.

בטבלה הבאה מפורטים פורמטי ה-HDR הזמינים והיכולות שלהם לצילום וידאו באיכות HDR.

פורמט פונקציית העברה (TF) מטא-נתונים קודק עומק ביט
HLG10 HLG לא HEVC ‫10 ביט
HDR10 PQ סטטי HEVC ‫10 ביט
HDR10+‎ PQ דינמי HEVC ‫10 ביט
‫Dolby Vision 8.4 HLG דינמי HEVC ‫10 ביט

משאבים

אפליקציה לדוגמה עם פונקציונליות של צילום וידאו באיכות HDR זמינה ב-GitHub בכתובת Camera2Video sample.