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

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

رسم تخطيطي مفاهيمي لنظام التقاط الصوت والفيديو
الشكل 1. رسم بياني مفاهيمي لنظام التقاط الصوت والفيديو

في CameraX، يكون الحل لالتقاط صورة الفيديو هو حالة الاستخدام VideoCapture:

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

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

  • SurfaceProvider لمصدر الفيديو.
  • AudioSource لمصدر الصوت.
  • برنامجا ترميز لتشفير وضغط الفيديو/الصوت.
  • برنامج دمج وسائط لمزج مجموعتَي البث.
  • موفر ملف لكتابة النتيجة.

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

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

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

تتكوّن واجهة برمجة التطبيقات VideoCapture من الكائنات التالية التي تتصل بالتطبيقات:

  • VideoCapture هي فئة حالة الاستخدام ذات المستوى الأعلى. يربط VideoCapture بـ LifecycleOwner مع CameraSelector و CameraX UseCases الأخرى. لمزيد من المعلومات حول هذه المفاهيم والاستخدامات، يُرجى الاطّلاع على بنية CameraX.
  • Recorder هي عملية تنفيذ لمخرجات الفيديو ترتبط ارتباطًا وثيقًا بـ 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

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

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

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

ربط التقاط الفيديو

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

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

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

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

  • Quality.UHD لحجم الفيديو بدقة فائقة بتنسيق 4K (2160p)
  • Quality.FHD لحجم الفيديو بدقة فائقة (1080p)
  • Quality.HD لحجم الفيديو بدقة عالية (720p)
  • Quality.SD لحجم الفيديو بدقة عادية (480p)

وتجدر الإشارة إلى أنّ تطبيق CameraX يمكنه أيضًا اختيار درجات دقة أخرى عندما يسمح التطبيق بذلك.

يعتمد الحجم الدقيق للفيديو لكلّ اختيار على إمكانات الكاميرا وبرنامج الترميز. لمزيد من المعلومات، يمكنك الاطّلاع على مستندات CamcorderProfile.

يمكن للتطبيقات ضبط درجة الدقة من خلال إنشاء QualitySelector. يمكنك إنشاء QualitySelector باستخدام إحدى الطرق التالية:

  • قدِّم بعض درجات الدقة المفضّلة باستخدام fromOrderedList()، وأدرِج استراتيجية احتياطية لاستخدامها في حال عدم توافق أي من درجات الدقة المفضّلة.

    يمكن أن تحدّد CameraX أفضل مطابقة احتياطية استنادًا إلى إمكانات الكاميرا التي تم اختيارها. يُرجى الرجوع إلى FallbackStrategy specification الخاصة بـ QualitySelector للاطّلاع على مزيد من التفاصيل. على سبيل المثال، يطلب الرمز التالي الحصول على درجة دقة أعلى متوافقة للتسجيل، وإذا كان لا يمكن إتاحة أي من درجات دقة الطلب، يمكنك تفويض تطبيق CameraX لاختيار درجة الدقة الأقرب من حيث الدقة العادية:

    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>).
  • السماح للتسجيل بالتسجيل بشكل مستمر أثناء ارتداد كاميرا الفيديو المرفقة بها إلى كاميرا أخرى باستخدام 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 باستخدام MIROR_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، يمكنك الاطّلاع على الموارد الإضافية التالية: