بنية التقاط الفيديوهات باستخدام CameraX

يسجّل نظام الالتقاط بشكل عام بث الفيديو والصوت، ويضغطها، ثم يدمج البثين، ثم يكتب البث الناتج على القرص.

مخطط توضيحي لنظام تسجيل الفيديو والصوت
الشكل 1. مخطط توضيحي لنظام تسجيل الفيديو والصوت

في CameraX، يتمثّل حل التقاط الفيديو في VideoCapture حالة الاستخدام التالية:

مخطط تصوري يوضّح كيفية تعامل CameraX مع حالة استخدام التقاط الفيديو
الشكل 2. مخطّط تصوري يوضّح كيفية تعامل CameraX مع VideoCapture حالة الاستخدام.

كما هو موضّح في الشكل 2، يتضمّن التقاط الفيديو باستخدام CameraX بعض المكوّنات المعمارية ذات المستوى العالي:

  • SurfaceProvider لمصدر الفيديو
  • AudioSource لمصدر الصوت
  • برنامجا ترميز لترميز الفيديو/الصوت وضغطهما
  • أداة دمج وسائط لدمج تدفقَي البيانات.
  • أداة لحفظ الملفات من أجل كتابة النتيجة

تجرِّد واجهة برمجة التطبيقات VideoCapture محرك الالتقاط المعقّد وتوفّر للتطبيقات واجهة برمجة تطبيقات أبسط وأكثر وضوحًا.

نظرة عامة على VideoCapture API

VideoCapture هي حالة استخدام في CameraX تعمل بشكل جيد بمفردها أو عند دمجها مع حالات استخدام أخرى. تعتمد المجموعات المتوافقة المحدّدة على إمكانات أجهزة الكاميرا، ولكن Preview وVideoCapture هما مجموعة صالحة من حالات الاستخدام على جميع الأجهزة.

تتألف واجهة برمجة التطبيقات VideoCapture من العناصر التالية التي تتواصل مع التطبيقات:

  • VideoCapture هو فئة حالة الاستخدام ذات المستوى الأعلى. يرتبط VideoCapture بـ LifecycleOwner مع CameraSelector وحالات استخدام CameraX الأخرى. لمزيد من المعلومات حول هذه المفاهيم والاستخدامات، راجِع بنية CameraX.
  • Recorder هو تنفيذ لواجهة VideoOutput مرتبط بشكل وثيق بـ VideoCapture. يتم استخدام Recorder لتسجيل الفيديو والصوت. ينشئ تطبيق التسجيلات من Recorder.
  • يضبط PendingRecording تسجيلاً، ويوفّر خيارات مثل تفعيل الصوت وضبط أداة معالجة الحدث. يجب استخدام Recorder لإنشاء PendingRecording. لا يسجّل PendingRecording أي بيانات.
  • Recording هو الذي يجري التسجيل الفعلي. يجب استخدام PendingRecording لإنشاء Recording.

يوضّح الشكل 3 العلاقات بين هذه العناصر:

مخطّط يوضّح التفاعلات التي تحدث في حالة استخدام التقاط فيديو
الشكل 3. مخطّط يوضّح التفاعلات التي تحدث في حالة استخدام VideoCapture

وسيلة الإيضاح:

  1. إنشاء Recorder باستخدام QualitySelector
  2. اضبط Recorder باستخدام أحد OutputOptions.
  3. فعِّل الصوت باستخدام withAudioEnabled() إذا لزم الأمر.
  4. اتّصِل start() بمستمع VideoRecordEvent لبدء التسجيل.
  5. استخدِم pause()/resume()/stop() على Recording للتحكّم في التسجيل.
  6. الرد على VideoRecordEvents داخل أداة معالجة الحدث

تتوفّر قائمة مفصّلة بواجهات برمجة التطبيقات في ملف current.txt داخل رمز المصدر.

استخدام VideoCapture API

لدمج حالة استخدام VideoCapture CameraX في تطبيقك، اتّبِع الخطوات التالية:

  1. ربط VideoCapture
  2. إعداد التسجيل وضبطه
  3. ابدأ تسجيل وقت التشغيل وتحكَّم فيه.

توضّح الأقسام التالية الإجراءات التي يمكنك اتّخاذها في كل خطوة للحصول على جلسة تسجيل شاملة.

Bind VideoCapture

لربط حالة الاستخدام VideoCapture، اتّبِع الخطوات التالية:

  1. أنشئ عنصر Recorder.
  2. أنشئ عنصر VideoCapture.
  3. الربط بـ Lifecycle

تتّبع واجهة برمجة التطبيقات VideoCapture في CameraX نمط التصميم الخاص بأداة الإنشاء. تستخدم التطبيقات Recorder.Builder لإنشاء Recorder. يمكنك أيضًا ضبط درجة دقة الفيديو Recorder من خلال عنصر QualitySelector.

تتوافق مكتبة CameraX Recorder مع Qualities المحدّدة مسبقًا التالية لدرجات دقة الفيديو:

  • Quality.UHD لحجم فيديو بدقة فائقة للغاية (2160p) بتنسيق 4K
  • Quality.FHD لحجم الفيديو بدقة فائقة (1080p)
  • Quality.HD لحجم الفيديو بدقة عالية (720p)
  • Quality.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().
  • تسجيل أداة معالجة لتلقّي أحداث تسجيل الفيديو باستخدام 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، يُرجى الاطّلاع على المراجع الإضافية التالية: