نقل Camera1 إلى CameraX

إذا كان تطبيقك يستخدم الإصدار الأصلي Camera الفئة ("الكاميرا1")، التي تم إيقافها منذ Android 5.0 (المستوى 21 من واجهة برمجة التطبيقات)، ننصح بشدة بالتحديث إلى واجهة برمجة التطبيقات الحديثة لكاميرا Android. عروض Android cameraX (كاميرا Jetpack موحّدة وقوية واجهة برمجة التطبيقات) والكاميرا2 (واجهة برمجة تطبيقات لإطار العمل منخفض المستوى). بالنسبة إلى للغالبية العظمى من الحالات، ننصحك بنقل تطبيقك إلى CameraX. وفي ما يلي السبب في ذلك:

  • سهولة الاستخدام: يتعامل تطبيق CameraX مع التفاصيل المنخفضة المستوى، حتى يمكنك التركيز أقل على إنشاء تجربة كاميرا من البداية والمزيد على التي تميّز تطبيقك.
  • معالجة CameraX للتقسيم نيابةً عنك: يحدّ تطبيق CameraX من البيانات على المدى الطويل. تكاليف الصيانة ورمز خاص بالجهاز، ما يوفر تجارب ذات جودة أعلى للمستخدمين. لمزيد من المعلومات، يُرجى الاطّلاع على توافق أفضل مع الجهاز مع CameraX مشاركة المدونة.
  • الإمكانات المتقدّمة: تم تصميم تطبيق CameraX بعناية لتنفيذ مهام متقدّمة. وظيفة يسهل دمجها في تطبيقك. على سبيل المثال، يمكنك بسهولة تطبيق تأثير "ضبابي" و"تجميل الوجه" و"نطاق عالي الديناميكية" و"درجة سطوع منخفضة" استخدِم وضع "الالتقاط الليلي" لصورك باستخدام إضافات CameraX.
  • إمكانية التحديث: يُطرح Android إمكانات جديدة وإصلاحات للأخطاء في تطبيق CameraX. على مدار العام. من خلال الانتقال إلى استخدام CameraX، سيحصل تطبيقك على أحدث إصدار من نظام التشغيل Android. تكنولوجيا الكاميرا مع كل إصدار من إصدارات CameraX، وليس فقط في السنوية لإصدارات Android.

تعرّف في هذا الدليل على السيناريوهات الشائعة في تطبيقات الكاميرا. على كل تنفيذ كاميرا 1 وتنفيذ CameraX والمقارنة.

عندما يتعلق الأمر بالنقل، تحتاج أحيانًا إلى مرونة إضافية في الدمج باستخدام قاعدة رموز حالية تتضمن كل رموز CameraX في هذا الدليل CameraController طريقة رائعة إذا كنت تريد أبسط طريقة لاستخدام CameraX، وأيضًا CameraProvider والتنفيذ، وهذا أمر رائع إذا كنت بحاجة إلى مزيد من المرونة. لمساعدتك في تحديد الخيار المناسب لك، وإليك فوائد كل منهما:

وحدة تحكُّم في الكاميرا

موفّر الكاميرا

يتطلب القليل من رمز الإعداد إمكانية التحكّم بشكل أكبر
يعني السماح للكاميرا X بمعالجة المزيد من عملية الإعداد وتعمل وظائف مثل الضغط للتركيز والتكبير أو التصغير، تلقائيًا بما أنّ مطوّر التطبيقات هو المسؤول عن عملية الإعداد، هناك المزيد من الفرص لتخصيص الإعدادات، مثل تفعيل تدوير الصورة الناتجة أو ضبط تنسيق صورة الناتج في ImageAnalysis
من خلال طلب "PreviewView" لمعاينة الكاميرا، يمكنك CameraX لتوفير تكامل سلس بين الأطراف، كما هو الحال في حزمة تعلّم الآلة من التكامل الذي يمكنه تعيين إحداثيات نتائج نموذج تعلُّم الآلة (مثل المربعات المحيطية) مباشرةً على إحداثيات المعاينة تسمح إمكانية استخدام "سطح" مخصص لمعاينة الكاميرا المزيد من المرونة، مثل استخدام رمز "Surface" الحالي الذي يمكن أن يكون مدخلاً إلى أجزاء أخرى من التطبيق

إذا واجهتك مشكلة أثناء محاولة نقل البيانات، يُرجى التواصل معنا على مجموعة مناقشة CameraX.

قبل النقل

مقارنة استخدام الكاميراX مع الكاميرا 1

وعلى الرغم من أن الرمز قد يبدو مختلفًا، إلا أن المفاهيم الأساسية في تطبيق Camera1 أما تطبيق CameraX، فهو متشابه إلى حد كبير. الكاميراX تجريد وظائف الكاميرا الشائعة في حالات الاستخدام، ونتيجةً لذلك، يتم تنفيذ العديد من المهام التي تُركت للمطوّر في تطبيق "الكاميرا1" تلقائيًا باستخدام CameraX. هناك أربعة UseCase في CameraX، الذي يمكنك استخدامه لتنفيذ العديد من مهام الكاميرا: Preview ImageCapture, VideoCapture، ImageAnalysis.

أحد الأمثلة على التعامل مع التفاصيل المنخفضة المستوى لمطوّري البرامج في CameraX هو ViewPort تتم مشاركته بين UseCase نشط. يضمن ذلك رؤية جميع UseCase وحدات بكسل نفسها بالضبط. أمّا في Camera1، فعليك إدارة هذه التفاصيل بنفسك، مع الأخذ في الاعتبار التباين بنِسب العرض إلى الارتفاع على جميع الأجهزة وأجهزة الاستشعار في الكاميرا، فقد يكون من الصعب لضمان تطابق المعاينة مع الصور والفيديوهات التي تم التقاطها.

كمثال آخر، تعالج CameraX Lifecycle عمليات معاودة الاتصال تلقائيًا على Lifecycle مرة نجحت في تجاوزها. يعني هذا أنّ تطبيق CameraX يعالج الاتصال بالكاميرا خلال مراحل نشاط Android، بما في ذلك الحالات التالية: إغلاق الكاميرا عند انتقال التطبيق إلى الخلفية؛ إزالة معاينة الكاميرا عندما لا تتطلب الشاشة عرضه وإيقاف معاينة الكاميرا مؤقتًا عندما يتم إجراء نشاط آخر أولوية المقدّمة، مثل مكالمة فيديو واردة

وأخيرًا، يتعامل CameraX مع التدوير وتغيير الحجم بدون الحاجة إلى أي رمز إضافي. من جانبك. في حال توجيه Activity باتجاه غير مقفل، يعني ذلك أنّ يتم إعداد ميزة "UseCase" في كل مرة يتم فيها تدوير الجهاز، حيث يتعطّل النظام. وتعيد إنشاء Activity عند تغيير الاتجاه. وينتج عن ذلك يتم ضبط تدوير الجهاز "UseCases" ليطابق اتجاه الشاشة حسب افتراضيًا في كل مرة. مزيد من المعلومات حول عمليات التدوير في CameraX

قبل الانتقال إلى التفاصيل، إليك نظرة متعمّقة على ميزات الكاميرا UseCase وكيف يرتبط تطبيق Camera1. (تتوفّر مفاهيم CameraX في blue والكاميرا1 المفاهيم أخضر).

الكاميراX

إعداد CameraController / CameraProvider
معاينة التقاط الصور التقاط الفيديو تحليل الصور
إدارة سطح المعاينة وضبطه على الكاميرا ضبط PictureCallback واستدعاء TakePicture() على الكاميرا إدارة ضبط الكاميرا وMediaRecorder بترتيب معيّن رمز تحليل مخصّص مضمّن في أعلى سطح المعاينة
الرمز الخاص بالجهاز
إدارة تدوير الجهاز وقياسه
إدارة جلسة الكاميرا (اختيار الكاميرا وإدارة مراحل النشاط)

الكاميرا1

التوافق والأداء في CameraX

يتوافق تطبيق CameraX مع الأجهزة التي تعمل. Android 5.0 (المستوى 21 من واجهة برمجة التطبيقات) والإصدارات الأحدث. هذا النمط يمثل أكثر من 98% من أجهزة Android الحالية. تم تصميم كاميرا CameraX للتعامل مع الاختلافات بين الأجهزة تلقائيًا، مما يقلل الحاجة إلى ميزات الرمز البرمجي في تطبيقك. علاوةً على ذلك، نختبر ما يزيد عن 150 جهازًا ماديًا على جميع أجهزة Android بالإصدار 5.0 في مركز اختبار CameraX. إِنْتَ مراجعة القائمة الكاملة الأجهزة الموجودة حاليًا في مركز الاختبار الافتراضي.

يستخدم CameraX Executor من أجل قيادة حزمة الكاميرا. يمكنك إعداد منفِّذ العمل الخاص بك على CameraX إذا كان لتطبيقك متطلبات محدَّدة لسلاسل المحادثات وإذا لم يتم ضبط السياسة، سينشئ تطبيق CameraX. وتستخدم Executor تلقائيًا محسَّنًا داخليًا. العديد من واجهات برمجة التطبيقات للنظام الأساسي على التي تم إنشاؤها باستخدام CameraX والتي تتطلب حظر الاتصال البيني للعمليات (IPC) مع على أجهزة قد تستغرق أحيانًا مئات المللي ثانية للاستجابة. لهذا الغرض فإن CameraX لا يستدعي واجهات برمجة التطبيقات هذه إلا من سلاسل محادثات الخلفية، مما يضمن عدم حظر سلسلة التعليمات الرئيسية وأن واجهة المستخدم تظل سلسة. اطّلِع على مزيد من المعلومات عن سلاسل المحادثات.

وإذا كان السوق المستهدف لتطبيقك يشمل أجهزة منخفضة المواصفات، يوفر تطبيق CameraX طريقة لتقليل وقت الإعداد باستخدام محدّد الكاميرا. نظرًا لأن عملية الاتصال بمكونات الأجهزة يمكن أن تستغرق قدرًا غير تافه من الوقت، خاصة على الأجهزة المنخفضة المواصفات، يمكنك تحديد مجموعة الكاميرات التي يتضمنها تطبيقك احتياجاتهم. لا يتصل CameraX بهذه الكاميرات إلا أثناء الإعداد. على سبيل المثال، إذا يستخدم التطبيق الكاميرات الخلفية فقط، فيمكنه ضبط هذه الإعدادات مع DEFAULT_BACK_CAMERA ثم يتجنب CameraX إعداد الواجهة الأمامية الكاميرات لتقليل وقت الاستجابة.

مفاهيم تطوير Android

يفترض هذا الدليل إلمامًا عامًا بتطوير تطبيقات Android. ما وراء الأساسيات، فإليك بعض المفاهيم التي من المفيد فهمها قبل الانتقال إلى التعليمة البرمجية أدناه:

  • يؤدي ربط العرض إلى إنشاء فئة ربط ملفات تخطيط XML، مما يتيح لك بسهولة الإشارة إلى مشاهداتك في صفحة "الأنشطة" كما هي الحال في العديد من مقتطفات الرمز أدناه. تتوفر بعض الاختلافات بين ربط العرض وfindViewById() (الطريقة السابقة للرجوع إلى طرق العرض)، ولكن باستخدام الرمز أدناه، يجب أن تتمكن من استبدال خطوط ربط العرض باستدعاء findViewById() مشابه.
  • الكوروتينات غير المتزامنة عبارة عن تصميم متزامن التي تمت إضافتها في Kotlin 1.3 ويمكن استخدامها للتعامل مع طرق CameraX التي عرض ListenableFuture. أصبحت هذه الميزة أسهل باستخدام Jetpack مكتبة متزامنة اعتبارًا من الإصدار 1.1.0. لإضافة كوروتين غير متزامن إلى تطبيقك:
    1. إضافة الوضع "implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0")" إلى ملف Gradle الخاص بك.
    2. ضع أي رمز CameraX يعرض ListenableFuture في launch حظر أو دالة تعليق.
    3. إضافة await() إلى استدعاء الدالة الذي يُرجع ListenableFuture.
    4. للحصول على فهم أعمق لكيفية عمل الكوروتينات، راجع دليل بدء إعداد الكوروتين

نقل السيناريوهات الشائعة

يوضّح هذا القسم كيفية نقل السيناريوهات الشائعة من Camera1 إلى CameraX. يشمل كل سيناريو استخدام Camera1، وكاميرا CameraX CameraProvider. وتنفيذه واستخدام CameraX CameraController.

اختيار كاميرا

في تطبيق الكاميرا، من أول الأشياء التي قد ترغب في تقديمها لاختيار كاميرات مختلفة.

الكاميرا1

في تطبيق Camera1، يمكنك إما الاتصال Camera.open() بدون معلَمات لفتح أول كاميرا خلفية، أو يمكنك تمرير رقم تعريف صحيح الكاميرا التي تريد فتحها. فيما يلي مثال على كيف يمكن أن يبدو ذلك:

// Camera1: select a camera from id.

// Note: opening the camera is a non-trivial task, and it shouldn't be
// called from the main thread, unlike CameraX calls, which can be
// on the main thread since CameraX kicks off background threads
// internally as needed.

private fun safeCameraOpen(id: Int): Boolean {
    return try {
        releaseCameraAndPreview()
        camera = Camera.open(id)
        true
    } catch (e: Exception) {
        Log.e(TAG, "failed to open camera", e)
        false
    }
}

private fun releaseCameraAndPreview() {
    preview?.setCamera(null)
    camera?.release()
    camera = null
}

CameraX: CameraController

في CameraX، يتولّى الصف CameraSelector اختيار الكاميرا. الكاميراX يجعل الأمر الشائع لاستخدام الكاميرا الافتراضية أمرًا سهلاً. يمكنك تحديد ما إذا تريد استخدام الكاميرا الأمامية التلقائية أو الكاميرا الخلفية التلقائية. بالإضافة إلى ذلك، يتيح لك الكائن CameraControl في CameraX تنفيذ إجراءات ضبط مستوى التكبير/التصغير لتطبيقك، بحيث إذا تطبيقك يعمل على جهاز متوافق الكاميرات المنطقية، ثم يتم التبديل إلى العدسة المناسبة.

في ما يلي رمز CameraX لاستخدام الكاميرا الخلفية التلقائية مع CameraController:

// CameraX: select a camera with CameraController

var cameraController = LifecycleCameraController(baseContext)
val selector = CameraSelector.Builder()
    .requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
cameraController.cameraSelector = selector

CameraX: CameraProvider

في ما يلي مثال على اختيار الكاميرا الأمامية التلقائية باستخدام CameraProvider (يمكن استخدام الكاميرا الأمامية أو الخلفية إما مع CameraController أو CameraProvider):

// CameraX: select a camera with CameraProvider.

// Use await() within a suspend function to get CameraProvider instance.
// For more details on await(), see the "Android development concepts"
// section above.
private suspend fun startCamera() {
    val cameraProvider = ProcessCameraProvider.getInstance(this).await()

    // Set up UseCases (more on UseCases in later scenarios)
    var useCases:Array = ...

    // Set the cameraSelector to use the default front-facing (selfie)
    // camera.
    val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA

    try {
        // Unbind UseCases before rebinding.
        cameraProvider.unbindAll()

        // Bind UseCases to camera. This function returns a camera
        // object which can be used to perform operations like zoom,
        // flash, and focus.
        var camera = cameraProvider.bindToLifecycle(
            this, cameraSelector, useCases)

    } catch(exc: Exception) {
        Log.e(TAG, "UseCase binding failed", exc)
    }
})

...

// Call startCamera in the setup flow of your app, such as in onViewCreated.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    ...

    lifecycleScope.launch {
        startCamera()
    }
}

إذا كنت تريد التحكم في الكاميرا التي يتم اختيارها، يمكن أيضًا في هذه الحالة CameraX إذا كنت تستخدم CameraProvider من خلال الاتصال getAvailableCameraInfos(), والذي يمنحك كائن CameraInfo للتحقّق من خصائص كاميرا معيّنة مثل isFocusMeteringSupported() يمكنك بعد ذلك تحويله إلى CameraSelector لاستخدامه على النحو الوارد أعلاه. أمثلة على طريقة CameraInfo.getCameraSelector().

يمكنك الحصول على مزيد من التفاصيل حول كل كاميرا باستخدام Camera2CameraInfo الصف. اتصل getCameraCharacteristic() مع مفتاح لبيانات الكاميرا التي تريدها. اطّلِع على CameraCharacteristics الفئة لقائمة بجميع المفاتيح التي يمكنك الاستعلام عنها.

إليك مثال باستخدام دالة checkFocalLength() مخصصة يمكنك حدد نفسك:

// CameraX: get a cameraSelector for first camera that matches the criteria
// defined in checkFocalLength().

val cameraInfo = cameraProvider.getAvailableCameraInfos()
    .first { cameraInfo ->
        val focalLengths = Camera2CameraInfo.from(cameraInfo)
            .getCameraCharacteristic(
                CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS
            )
        return checkFocalLength(focalLengths)
    }
val cameraSelector = cameraInfo.getCameraSelector()

عرض معاينة

تحتاج معظم تطبيقات الكاميرا إلى عرض خلاصة الكاميرا على الشاشة في بعض نقطة واحدة. باستخدام الكاميرا1، عليك إدارة عمليات معاودة الاتصال بمراحل النشاط بشكل صحيح كما يجب عليك تحديد التدوير والتحجيم للمعاينة.

علاوة على ذلك، في تطبيق "الكاميرا1"، يجب أن تقرر ما إذا كنت تريد استخدام TextureView أو SurfaceView كمساحة عرض للمعاينة يأتي كلا الخيارين بمفاضلات، وفي كلتا الحالتين، تتطلب منك Camera1 ما يلي: التعامل مع التدوير والتحجيم بشكل صحيح. PreviewView الخاص بـ CameraX، على الجانب الآخر لها عمليات تنفيذ أساسية لكل من TextureView وSurfaceView. ويحدّد تطبيق CameraX التطبيق الأفضل بناءً على عوامل مثل ونوع الجهاز وإصدار Android الذي يعمل عليه تطبيقك. إذا كانت إحدى الأخرى، يمكنك إعلان تفضيلك باستخدام PreviewView.ImplementationMode يستخدم الخيار COMPATIBLE علامة TextureView للمعاينة. تستخدم القيمة PERFORMANCE السمة SurfaceView (إن أمكن ذلك).

الكاميرا1

لعرض معاينة، عليك كتابة صفك الخاص في Preview باستخدام تنفيذ android.view.SurfaceHolder.Callback تُستخدم لتمرير بيانات الصور من جهاز الكاميرا إلى التطبيق. وقبل أن تتمكّن من بدء معاينة الصورة المباشرة، انقر على "Preview" يجب تمرير الفئة إلى العنصر Camera.

// Camera1: set up a camera preview.

class Preview(
        context: Context,
        private val camera: Camera
) : SurfaceView(context), SurfaceHolder.Callback {

    private val holder: SurfaceHolder = holder.apply {
        addCallback(this@Preview)
        setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)
    }

    override fun surfaceCreated(holder: SurfaceHolder) {
        // The Surface has been created, now tell the camera
        // where to draw the preview.
        camera.apply {
            try {
                setPreviewDisplay(holder)
                startPreview()
            } catch (e: IOException) {
                Log.d(TAG, "error setting camera preview", e)
            }
        }
    }

    override fun surfaceDestroyed(holder: SurfaceHolder) {
        // Take care of releasing the Camera preview in your activity.
    }

    override fun surfaceChanged(holder: SurfaceHolder, format: Int,
                                w: Int, h: Int) {
        // If your preview can change or rotate, take care of those
        // events here. Make sure to stop the preview before resizing
        // or reformatting it.
        if (holder.surface == null) {
            return  // The preview surface does not exist.
        }

        // Stop preview before making changes.
        try {
            camera.stopPreview()
        } catch (e: Exception) {
            // Tried to stop a non-existent preview; nothing to do.
        }

        // Set preview size and make any resize, rotate or
        // reformatting changes here.

        // Start preview with new settings.
        camera.apply {
            try {
                setPreviewDisplay(holder)
                startPreview()
            } catch (e: Exception) {
                Log.d(TAG, "error starting camera preview", e)
            }
        }
    }
}

class CameraActivity : AppCompatActivity() {
    private lateinit var viewBinding: ActivityMainBinding
    private var camera: Camera? = null
    private var preview: Preview? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(viewBinding.root)

        // Create an instance of Camera.
        camera = getCameraInstance()

        preview = camera?.let {
            // Create the Preview view.
            Preview(this, it)
        }

        // Set the Preview view as the content of the activity.
        val cameraPreview: FrameLayout = viewBinding.cameraPreview
        cameraPreview.addView(preview)
    }
}

CameraX: CameraController

أمّا في CameraX، فليس هناك الكثير مما تحتاجه أنت بصفتك مطوّر البرامج لإدارته. إذا كنت تستخدم CameraController، وبالتالي عليك أيضًا استخدام PreviewView. وهذا يعني أن تم تضمين Preview UseCase، ما يجعل عملية الإعداد أقل بكثير:

// CameraX: set up a camera preview with a CameraController.

class MainActivity : AppCompatActivity() {
    private lateinit var viewBinding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(viewBinding.root)

        // Create the CameraController and set it on the previewView.
        var cameraController = LifecycleCameraController(baseContext)
        cameraController.bindToLifecycle(this)
        val previewView: PreviewView = viewBinding.cameraPreview
        previewView.controller = cameraController
    }
}

CameraX: CameraProvider

باستخدام CameraProvider في CameraX، لن تحتاج إلى استخدام PreviewView، بل يجب يعمل على تبسيط إعداد المعاينة بشكل كبير عبر الكاميرا1. للعرض التوضيحي يستخدم هذا المثال العلامة PreviewView، ولكن يمكنك كتابة يمكنك تمرير SurfaceProvider إلى setSurfaceProvider() إذا كانت لديك أسئلة أكثر تعقيدًا. احتياجاتهم.

هنا، لم يتم تضمين UseCase Preview كما هو الحال مع CameraController، لذا عليك إعداده:

// CameraX: set up a camera preview with a CameraProvider.

// Use await() within a suspend function to get CameraProvider instance.
// For more details on await(), see the "Android development concepts"
// section above.
private suspend fun startCamera() {
    val cameraProvider = ProcessCameraProvider.getInstance(this).await()

    // Create Preview UseCase.
    val preview = Preview.Builder()
        .build()
        .also {
            it.setSurfaceProvider(
                viewBinding.viewFinder.surfaceProvider
            )
        }

    // Select default back camera.
    val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

    try {
        // Unbind UseCases before rebinding.
        cameraProvider.unbindAll()

        // Bind UseCases to camera. This function returns a camera
        // object which can be used to perform operations like zoom,
        // flash, and focus.
        var camera = cameraProvider.bindToLifecycle(
            this, cameraSelector, useCases)

    } catch(exc: Exception) {
        Log.e(TAG, "UseCase binding failed", exc)
    }
})

...

// Call startCamera() in the setup flow of your app, such as in onViewCreated.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    ...

    lifecycleScope.launch {
        startCamera()
    }
}

النقر للتركيز

عند عرض معاينة الكاميرا على الشاشة، من الشائع ضبط البؤرة. عندما ينقر المستخدم على المعاينة.

الكاميرا1

لاستخدام ميزة النقر للتركيز في الكاميرا 1، يجب حساب التركيز الأمثل. Area للإشارة إلى المكان الذي يجب أن يحاول فيه Camera التركيز. هذا Area هو يتم إرسالها إلى setFocusAreas(). كما يجب عليك ضبط وضع تركيز متوافق على Camera لن يكون لمنطقة التركيز تأثير إلا إذا كان وضع التركيز الحالي هو FOCUS_MODE_AUTO أو FOCUS_MODE_MACRO أو FOCUS_MODE_CONTINUOUS_VIDEO أو FOCUS_MODE_CONTINUOUS_PICTURE

كل Area عبارة عن مستطيل بوزن محدد. الوزن قيمة بين 1 و1000، وتُستخدَم لمنح الأولوية للتركيز Areas في حال ضبط قيم متعددة. هذا النمط مثال واحد فقط Area، لذا فإن قيمة الوزن لا يهم. إحداثيات نطاق المستطيل من -1000 إلى 1000. والنقطة العلوية اليسرى هي (-1000، -1000). النقطة اليمنى السفلية هي (1000، 1000). يرتبط الاتجاه بأداة الاستشعار الاتجاه، أي ما يراه المستشعر. ولا يتأثر الاتجاه تدوير أو نسخ Camera.setDisplayOrientation()، ولذلك يجب تحويل إحداثيات حدث اللمس إلى إحداثيات أداة الاستشعار.

// Camera1: implement tap-to-focus.

class TapToFocusHandler : Camera.AutoFocusCallback {
    private fun handleFocus(event: MotionEvent) {
        val camera = camera ?: return
        val parameters = try {
            camera.getParameters()
        } catch (e: RuntimeException) {
            return
        }

        // Cancel previous auto-focus function, if one was in progress.
        camera.cancelAutoFocus()

        // Create focus Area.
        val rect = calculateFocusAreaCoordinates(event.x, event.y)
        val weight = 1  // This value's not important since there's only 1 Area.
        val focusArea = Camera.Area(rect, weight)

        // Set the focus parameters.
        parameters.setFocusMode(Parameters.FOCUS_MODE_AUTO)
        parameters.setFocusAreas(listOf(focusArea))

        // Set the parameters back on the camera and initiate auto-focus.
        camera.setParameters(parameters)
        camera.autoFocus(this)
    }

    private fun calculateFocusAreaCoordinates(x: Int, y: Int) {
        // Define the size of the Area to be returned. This value
        // should be optimized for your app.
        val focusAreaSize = 100

        // You must define functions to rotate and scale the x and y values to
        // be values between 0 and 1, where (0, 0) is the upper left-hand side
        // of the preview, and (1, 1) is the lower right-hand side.
        val normalizedX = (rotateAndScaleX(x) - 0.5) * 2000
        val normalizedY = (rotateAndScaleY(y) - 0.5) * 2000

        // Calculate the values for left, top, right, and bottom of the Rect to
        // be returned. If the Rect would extend beyond the allowed values of
        // (-1000, -1000, 1000, 1000), then crop the values to fit inside of
        // that boundary.
        val left = max(normalizedX - (focusAreaSize / 2), -1000)
        val top = max(normalizedY - (focusAreaSize / 2), -1000)
        val right = min(left + focusAreaSize, 1000)
        val bottom = min(top + focusAreaSize, 1000)

        return Rect(left, top, left + focusAreaSize, top + focusAreaSize)
    }

    override fun onAutoFocus(focused: Boolean, camera: Camera) {
        if (!focused) {
            Log.d(TAG, "tap-to-focus failed")
        }
    }
}

CameraX: CameraController

ينتظر "CameraController" سماع أحداث اللمس لـ "PreviewView" للتعامل معها. انقر للتركيز تلقائيًا. يمكنك تفعيل ميزة "الضغط للتركيز" أو إيقافها باستخدام setTapToFocusEnabled()، ونتحقق من القيمة باستخدام المعلمة المقابلة لها isTapToFocusEnabled()

تشير رسالة الأشكال البيانية getTapToFocusState() تُنتج الدالة LiveData لتتبّع التغييرات في حالة التركيز على CameraController.

// CameraX: track the state of tap-to-focus over the Lifecycle of a PreviewView,
// with handlers you can define for focused, not focused, and failed states.

val tapToFocusStateObserver = Observer { state ->
    when (state) {
        CameraController.TAP_TO_FOCUS_NOT_STARTED ->
            Log.d(TAG, "tap-to-focus init")
        CameraController.TAP_TO_FOCUS_STARTED ->
            Log.d(TAG, "tap-to-focus started")
        CameraController.TAP_TO_FOCUS_FOCUSED ->
            Log.d(TAG, "tap-to-focus finished (focus successful)")
        CameraController.TAP_TO_FOCUS_NOT_FOCUSED ->
            Log.d(TAG, "tap-to-focus finished (focused unsuccessful)")
        CameraController.TAP_TO_FOCUS_FAILED ->
            Log.d(TAG, "tap-to-focus failed")
    }
}

cameraController.getTapToFocusState().observe(this, tapToFocusStateObserver)

CameraX: CameraProvider

عند استخدام CameraProvider، يجب إجراء عملية إعداد للحصول على ميزة "النقر للتركيز". العمل. يفترض هذا المثال أنك تستخدم السمة PreviewView. إذا لم يكن الأمر كذلك، فأنت بحاجة إلى عليك تعديل المنطق لتطبيق Surface المخصّص.

في ما يلي الخطوات التي يجب اتّباعها عند استخدام "PreviewView":

  1. عليك إعداد أداة رصد الإيماءات للتعامل مع أحداث النقر.
  2. من خلال حدث النقر، يمكنك إنشاء MeteringPoint باستخدام MeteringPointFactory.createPoint()
  3. باستخدام MeteringPoint، يمكنك إنشاء FocusMeteringAction.
  4. مع العنصر CameraControl على جهاز Camera (مرتجع من bindToLifecycle())، الاتصال برقم startFocusAndMetering()، مرورًا في FocusMeteringAction
  5. (اختياري) يمكنك الردّ على FocusMeteringResult.
  6. يمكنك ضبط أداة رصد الإيماءات للاستجابة إلى أحداث اللمس التي PreviewView.setOnTouchListener()
// CameraX: implement tap-to-focus with CameraProvider.

// Define a gesture detector to respond to tap events and call
// startFocusAndMetering on CameraControl. If you want to use a
// coroutine with await() to check the result of focusing, see the
// "Android development concepts" section above.
val gestureDetector = GestureDetectorCompat(context,
    object : SimpleOnGestureListener() {
        override fun onSingleTapUp(e: MotionEvent): Boolean {
            val previewView = previewView ?: return
            val camera = camera ?: return
            val meteringPointFactory = previewView.meteringPointFactory
            val focusPoint = meteringPointFactory.createPoint(e.x, e.y)
            val meteringAction = FocusMeteringAction
                .Builder(meteringPoint).build()
            lifecycleScope.launch {
                val focusResult = camera.cameraControl
                    .startFocusAndMetering(meteringAction).await()
                if (!result.isFocusSuccessful()) {
                    Log.d(TAG, "tap-to-focus failed")
                }
            }
        }
    }
)

...

// Set the gestureDetector in a touch listener on the PreviewView.
previewView.setOnTouchListener { _, event ->
    // See pinch-to-zooom scenario for scaleGestureDetector definition.
    var didConsume = scaleGestureDetector.onTouchEvent(event)
    if (!scaleGestureDetector.isInProgress) {
        didConsume = gestureDetector.onTouchEvent(event)
    }
    didConsume
}

التكبير أو التصغير بإصبعَين

كما أن تكبير المعاينة وتصغيرها هو تلاعب مباشر شائع آخر معاينة الكاميرا. مع تزايد عدد الكاميرات على الأجهزة، يمكن للمستخدمين أيضًا نتوقع أن يتم تحديد العدسة ذات البعد البؤري الأفضل تلقائيًا على أنها نتيجة التكبير أو التصغير.

الكاميرا1

هناك طريقتان للتكبير/التصغير باستخدام الكاميرا1. الطريقة Camera.startSmoothZoom() متحركة من مستوى التكبير الحالي إلى مستوى التكبير/التصغير الذي تمرره. تشير رسالة الأشكال البيانية تنتقل طريقة Camera.Parameters.setZoom() مباشرةً إلى مستوى التكبير/التصغير الذي تجتازه بوصة قبل استخدام أي منهما، اتصِل بالرقم isSmoothZoomSupported() أو isZoomSupported() على التوالي لضمان اتّباع طرق التكبير/التصغير ذات الصلة التي تحتاج إليها المتوفرة على الكاميرا.

لتنفيذ التكبير أو التصغير، يستخدم هذا المثال setZoom() لأنّ اللمسة يشغّل المستمع على سطح المعاينة الأحداث باستمرار أثناء التصغير أو التكبير بإصبعين حدوث إيماءة، لذا يتم تحديث مستوى التكبير/التصغير فورًا في كل مرة. تشير رسالة الأشكال البيانية الفئة ZoomTouchListener محدّدة أدناه، ويجب ضبطها كاستدعاء اتصال. إلى أداة معالجة اللمس على مساحة المعاينة

// Camera1: implement pinch-to-zoom.

// Define a scale gesture detector to respond to pinch events and call
// setZoom on Camera.Parameters.
val scaleGestureDetector = ScaleGestureDetector(context,
    object : ScaleGestureDetector.OnScaleGestureListener {
        override fun onScale(detector: ScaleGestureDetector): Boolean {
            val camera = camera ?: return false
            val parameters = try {
                camera.parameters
            } catch (e: RuntimeException) {
                return false
            }

            // In case there is any focus happening, stop it.
            camera.cancelAutoFocus()

            // Set the zoom level on the Camera.Parameters, and set
            // the Parameters back onto the Camera.
            val currentZoom = parameters.zoom
            parameters.setZoom(detector.scaleFactor * currentZoom)
        camera.setParameters(parameters)
            return true
        }
    }
)

// Define a View.OnTouchListener to attach to your preview view.
class ZoomTouchListener : View.OnTouchListener {
    override fun onTouch(v: View, event: MotionEvent): Boolean =
        scaleGestureDetector.onTouchEvent(event)
}

// Set a ZoomTouchListener to handle touch events on your preview view
// if zoom is supported by the current camera.
if (camera.getParameters().isZoomSupported()) {
    view.setOnTouchListener(ZoomTouchListener())
}

CameraX: CameraController

تمامًا مثل النقر للتركيز، يستمع CameraController إلى لمس المعاينة الأحداث للتعامل مع التصغير أو التكبير تلقائيًا. يمكنك تفعيل الميزة وإيقافها التصغير أو التكبير بإصبعين باستخدام setPinchToZoomEnabled()، ونتحقق من القيمة باستخدام المعلمة المقابلة لها isPinchToZoomEnabled()

تشير رسالة الأشكال البيانية getZoomState() يتم إرجاع الكائن LiveData لتتبع التغييرات على ZoomState على CameraController

// CameraX: track the state of pinch-to-zoom over the Lifecycle of
// a PreviewView, logging the linear zoom ratio.

val pinchToZoomStateObserver = Observer { state ->
    val zoomRatio = state.getZoomRatio()
    Log.d(TAG, "ptz-zoom-ratio $zoomRatio")
}

cameraController.getZoomState().observe(this, pinchToZoomStateObserver)

CameraX: CameraProvider

لتفعيل ميزة التكبير/التصغير بإصبعَين مع CameraProvider، يجب ضبط بعض الإعدادات. في حال حذف لا تستخدم PreviewView، فينبغي عليك تعديل المنطق لتطبيقه على مخصص Surface.

في ما يلي الخطوات التي يجب اتّباعها عند استخدام "PreviewView":

  1. عليك إعداد أداة رصد إيماءات على شكل مقياس للتعامُل مع أحداث التصغير والتكبير.
  2. يمكنك الحصول على ZoomState من الكائن Camera.CameraInfo، حيث يتم استبعاد Camera. يتم إرجاع المثيل عند استدعاء bindToLifecycle()
  3. إذا كانت قيمة ZoomState تساوي zoomRatio، احفظها كعنصر التكبير الحالي. النسبة المئوية. في حال عدم توفّر zoomRatio على ZoomState، استخدِم الإعدادات التلقائية للكاميرا معدل التكبير/التصغير (1.0).
  4. استخدِم ناتج نسبة التكبير/التصغير الحالية باستخدام scaleFactor إلى لتحديد نسبة التكبير/التصغير الجديدة، وتمريره إلى CameraControl.setZoomRatio().
  5. يمكنك ضبط أداة رصد الإيماءات للاستجابة إلى أحداث اللمس التي PreviewView.setOnTouchListener()
// CameraX: implement pinch-to-zoom with CameraProvider.

// Define a scale gesture detector to respond to pinch events and call
// setZoomRatio on CameraControl.
val scaleGestureDetector = ScaleGestureDetector(context,
    object : SimpleOnGestureListener() {
        override fun onScale(detector: ScaleGestureDetector): Boolean {
            val camera = camera ?: return
            val zoomState = camera.cameraInfo.zoomState
            val currentZoomRatio: Float = zoomState.value?.zoomRatio ?: 1f
            camera.cameraControl.setZoomRatio(
                detector.scaleFactor * currentZoomRatio
            )
        }
    }
)

...

// Set the scaleGestureDetector in a touch listener on the PreviewView.
previewView.setOnTouchListener { _, event ->
    var didConsume = scaleGestureDetector.onTouchEvent(event)
    if (!scaleGestureDetector.isInProgress) {
        // See pinch-to-zooom scenario for gestureDetector definition.
        didConsume = gestureDetector.onTouchEvent(event)
    }
    didConsume
}

التقاط صورة

يوضح هذا القسم كيفية تشغيل التقاط الصور، وما إذا كنت تريد القيام بذلك على الضغط على زر الغالق أو بعد انتهاء الموقّت أو في أي حدث آخر الاختيار.

الكاميرا1

في Camera1، يمكنك أولاً تحديد Camera.PictureCallback لإدارة بيانات الصورة عند طلبها. فيما يلي مثال بسيط على PictureCallback للتعامل مع بيانات صور JPEG:

// Camera1: define a Camera.PictureCallback to handle JPEG data.

private val picture = Camera.PictureCallback { data, _ ->
    val pictureFile: File = getOutputMediaFile(MEDIA_TYPE_IMAGE) ?: run {
        Log.d(TAG,
              "error creating media file, check storage permissions")
        return@PictureCallback
    }

    try {
        val fos = FileOutputStream(pictureFile)
        fos.write(data)
        fos.close()
    } catch (e: FileNotFoundException) {
        Log.d(TAG, "file not found", e)
    } catch (e: IOException) {
        Log.d(TAG, "error accessing file", e)
    }
}

وعند التقاط صورة، عليك استدعاء طريقة takePicture() على الجهاز الافتراضي Camera. تعتمد طريقة takePicture() هذه على ثلاثة أنواع المعاملات لأنواع البيانات المختلفة. المعلمة الأولى هي ShutterCallback (لم يتم تعريفها في هذا المثال). المعلمة الثانية هي لـ PictureCallback لمعالجة بيانات الكاميرا الأولية (غير المضغوطة). الثالث هي التي يستخدمها هذا المثال، حيث إنّها PictureCallback يجب التعامل معها بيانات صورة JPEG

// Camera1: call takePicture on Camera instance, passing our PictureCallback.

camera?.takePicture(null, null, picture)

CameraX: CameraController

يحافظ CameraController الخاص بـ CameraX على بساطة الكاميرا1 للصور. تسجيلها من خلال تنفيذ طريقة takePicture() الخاصة بها. هنا، حدد لضبط إدخال MediaStore والتقاط صورة لحفظها هناك.

// CameraX: define a function that uses CameraController to take a photo.

private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"

private fun takePhoto() {
   // Create time stamped name and MediaStore entry.
   val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
              .format(System.currentTimeMillis())
   val contentValues = ContentValues().apply {
       put(MediaStore.MediaColumns.DISPLAY_NAME, name)
       put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
       if(Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
           put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image")
       }
   }

   // Create output options object which contains file + metadata.
   val outputOptions = ImageCapture.OutputFileOptions
       .Builder(context.getContentResolver(),
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
       .build()

   // Set up image capture listener, which is triggered after photo has
   // been taken.
   cameraController.takePicture(
       outputOptions,
       ContextCompat.getMainExecutor(this),
       object : ImageCapture.OnImageSavedCallback {
           override fun onError(e: ImageCaptureException) {
               Log.e(TAG, "photo capture failed", e)
           }

           override fun onImageSaved(
               output: ImageCapture.OutputFileResults
           ) {
               val msg = "Photo capture succeeded: ${output.savedUri}"
               Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
               Log.d(TAG, msg)
           }
       }
   )
}

CameraX: CameraProvider

تتم عملية التقاط صورة باستخدام CameraProvider بالطريقة نفسها المتّبعة مع التقاط الصور. CameraController، ولكن عليك أولاً إنشاء ImageCapture وربطه. UseCase للحصول على كائن لاستدعاء takePicture() عليه:

// CameraX: create and bind an ImageCapture UseCase.

// Make a reference to the ImageCapture UseCase at a scope that can be accessed
// throughout the camera logic in your app.
private var imageCapture: ImageCapture? = null

...

// Create an ImageCapture instance (can be added with other
// UseCase definitions).
imageCapture = ImageCapture.Builder().build()

...

// Bind UseCases to camera (adding imageCapture along with preview here, but
// preview is not required to use imageCapture). This function returns a camera
// object which can be used to perform operations like zoom, flash, and focus.
var camera = cameraProvider.bindToLifecycle(
    this, cameraSelector, preview, imageCapture)

وبعد ذلك، عندما تريد التقاط صورة، يمكنك الاتصال ImageCapture.takePicture() اطّلِع على الرمز CameraController في هذا القسم. للحصول على مثال كامل للدالة takePhoto().

// CameraX: define a function that uses CameraController to take a photo.

private fun takePhoto() {
    // Get a stable reference of the modifiable ImageCapture UseCase.
    val imageCapture = imageCapture ?: return

    ...

    // Call takePicture on imageCapture instance.
    imageCapture.takePicture(
        ...
    )
}

تسجيل مقطع فيديو

إنّ تسجيل فيديو هو أكثر تعقيدًا بكثير مما كانت عليه السيناريوهات حتى الآن. يجب إعداد كل جزء من العملية بشكل صحيح، عادةً في بترتيب معين. قد يلزمك أيضًا التحقق من تشغيل الفيديو والصوت المزامنة أو التعامل مع التناقضات الإضافية في الجهاز.

وكما ستلاحظ، سيعالج CameraX مرة أخرى الكثير من التعقيد بالنسبة لك.

الكاميرا1

يتطلّب التقاط الفيديو باستخدام الكاميرا 1 إدارة دقيقة لجهاز Camera MediaRecorder، كما يجب أن تكون الطرق بترتيب معين. يجب اتباع هذا الترتيب تطبيقك ليعمل بشكل صحيح:

  1. افتح الكاميرا.
  2. إعداد معاينة وبدء تشغيلها (إذا كان التطبيق يعرض الفيديو الذي يتم تسجيله، وهذا هو الحال عادةً).
  3. عليك فتح قفل الكاميرا لاستخدامها في تطبيق "MediaRecorder" من خلال الاتصال بالرقم Camera.unlock().
  4. يمكنك ضبط التسجيل من خلال استدعاء الطرق التالية على MediaRecorder:
    1. يمكنك ربط المثيل Camera بـ setCamera(camera).
    2. الاتصال بالرقم setAudioSource(MediaRecorder.AudioSource.CAMCORDER).
    3. الاتصال بالرقم setVideoSource(MediaRecorder.VideoSource.CAMERA).
    4. الاتصال بـ "setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_1080P))" لضبط الجودة. عرض CamcorderProfile لكل خيارات الجودة.
    5. الاتصال بالرقم setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString()).
    6. إذا كان تطبيقك يحتوي على معاينة للفيديو، يمكنك إجراء مكالمة setPreviewDisplay(preview?.holder?.surface)
    7. الاتصال بالرقم setOutputFormat(MediaRecorder.OutputFormat.MPEG_4).
    8. الاتصال بالرقم setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT).
    9. الاتصال بالرقم setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT).
    10. يمكنك الاتصال بالرقم prepare() لإكمال عملية ضبط جهاز MediaRecorder.
  5. لبدء التسجيل، اتصل برقم MediaRecorder.start().
  6. لإيقاف التسجيل، يمكنك طلب الطرق التالية. مرة أخرى، اتبع هذا الترتيب تحديدًا:
    1. الاتصال بالرقم MediaRecorder.stop().
    2. يمكنك، إن أردت، إزالة إعداد MediaRecorder الحالي عن طريق طلب MediaRecorder.reset()
    3. الاتصال بالرقم MediaRecorder.release().
    4. قفل الكاميرا بحيث يمكن لجلسات MediaRecorder المستقبلية استخدامها من خلال يَتِمُّ الْآنَ الِاتِّصَالْ بِـ Camera.lock().
  7. لإيقاف المعاينة، يُرجى الاتصال بالرقم Camera.stopPreview().
  8. وأخيرًا، لتحرير Camera حتى تتمكن العمليات الأخرى من استخدامها، اطلب Camera.release()

في ما يلي كل هذه الخطوات مجتمعةً:

// Camera1: set up a MediaRecorder and a function to start and stop video
// recording.

// Make a reference to the MediaRecorder at a scope that can be accessed
// throughout the camera logic in your app.
private var mediaRecorder: MediaRecorder? = null
private var isRecording = false

...

private fun prepareMediaRecorder(): Boolean {
    mediaRecorder = MediaRecorder()

    // Unlock and set camera to MediaRecorder.
    camera?.unlock()

    mediaRecorder?.run {
        setCamera(camera)

        // Set the audio and video sources.
        setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
        setVideoSource(MediaRecorder.VideoSource.CAMERA)

        // Set a CamcorderProfile (requires API Level 8 or higher).
        setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH))

        // Set the output file.
        setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString())

        // Set the preview output.
        setPreviewDisplay(preview?.holder?.surface)

        setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
        setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT)
        setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT)

        // Prepare configured MediaRecorder.
        return try {
            prepare()
            true
        } catch (e: IllegalStateException) {
            Log.d(TAG, "preparing MediaRecorder failed", e)
            releaseMediaRecorder()
            false
        } catch (e: IOException) {
            Log.d(TAG, "setting MediaRecorder file failed", e)
            releaseMediaRecorder()
            false
        }
    }
    return false
}

private fun releaseMediaRecorder() {
    mediaRecorder?.reset()
    mediaRecorder?.release()
    mediaRecorder = null
    camera?.lock()
}

private fun startStopVideo() {
    if (isRecording) {
        // Stop recording and release camera.
        mediaRecorder?.stop()
        releaseMediaRecorder()
        camera?.lock()
        isRecording = false

        // This is a good place to inform user that video recording has stopped.
    } else {
        // Initialize video camera.
        if (prepareVideoRecorder()) {
            // Camera is available and unlocked, MediaRecorder is prepared, now
            // you can start recording.
            mediaRecorder?.start()
            isRecording = true

            // This is a good place to inform the user that recording has
            // started.
        } else {
            // Prepare didn't work, release the camera.
            releaseMediaRecorder()

            // Inform user here.
        }
    }
}

CameraX: CameraController

باستخدام CameraController في CameraX، يمكنك إيقاف/تفعيل ImageCapture VideoCapture، وImageAnalysis UseCase كل على حدة، طالما يمكن استخدام قائمة UseCases بشكل متزامن. يتم تلقائيًا تفعيل UseCase وImageCapture وImageAnalysis، هو سبب عدم حاجتك إلى الاتصال بـ setEnabledUseCases() لالتقاط صورة.

لاستخدام CameraController لتسجيل الفيديو، يجب أولاً استخدام setEnabledUseCases() للسماح VideoCapture UseCase.

// CameraX: Enable VideoCapture UseCase on CameraController.

cameraController.setEnabledUseCases(VIDEO_CAPTURE);

عندما تريد بدء تسجيل الفيديو، يمكنك الاتصال CameraController.startRecording() الأخرى. يمكن لهذه الدالة حفظ الفيديو المسجَّل في File، كما ترى. في المثال أدناه. بالإضافة إلى ذلك، عليك اجتياز Executor وصف. تنفّذ OnVideoSavedCallback للتعامل مع استدعاءات النجاح والخطأ. عند انتهاء التسجيل، اتصل CameraController.stopRecording()

ملاحظة: إذا كنت تستخدم CameraX 1.3.0-alpha02 أو إصدارًا أحدث، هناك تطبيق إضافي مَعلمة AudioConfig تتيح لك تمكين أو تعطيل التسجيل الصوتي في الفيديو. للتفعيل تسجيل صوتي، فستحتاج إلى التأكد من امتلاكك أذونات استخدام الميكروفون. بالإضافة إلى ذلك، تتم إزالة الطريقة stopRecording() في 1.3.0-alpha02، تعرض الدالة startRecording() كائن Recording يمكن استخدامه للإيقاف المؤقت، واستئناف تسجيل الفيديو وإيقافه.

// CameraX: implement video capture with CameraController.

private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"

// Define a VideoSaveCallback class for handling success and error states.
class VideoSaveCallback : OnVideoSavedCallback {
    override fun onVideoSaved(outputFileResults: OutputFileResults) {
        val msg = "Video capture succeeded: ${outputFileResults.savedUri}"
        Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
        Log.d(TAG, msg)
    }

    override fun onError(videoCaptureError: Int, message: String,
                         cause: Throwable?) {
        Log.d(TAG, "error saving video: $message", cause)
    }
}

private fun startStopVideo() {
    if (cameraController.isRecording()) {
        // Stop the current recording session.
        cameraController.stopRecording()
        return
    }

    // Define the File options for saving the video.
    val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
        .format(System.currentTimeMillis())

    val outputFileOptions = OutputFileOptions
        .Builder(File(this.filesDir, name))
        .build()

    // Call startRecording on the CameraController.
    cameraController.startRecording(
        outputFileOptions,
        ContextCompat.getMainExecutor(this),
        VideoSaveCallback()
    )
}

CameraX: CameraProvider

إذا كنت تستخدم CameraProvider، عليك إنشاء VideoCapture. UseCase وتمريرها في كائن Recorder. في Recorder.Builder، يمكنك ضبط جودة الفيديو، واختياريًا FallbackStrategy، التي يعالج الحالات التي لا يستوفي فيها الجهاز مواصفات الجودة المطلوبة. بَعْدَ ذَلِكْ اربط المثيل VideoCapture بالسمة CameraProvider باستخدام UseCase

// CameraX: create and bind a VideoCapture UseCase with CameraProvider.

// Make a reference to the VideoCapture UseCase and Recording at a
// scope that can be accessed throughout the camera logic in your app.
private lateinit var videoCapture: VideoCapture
private var recording: Recording? = null

...

// Create a Recorder instance to set on a VideoCapture instance (can be
// added with other UseCase definitions).
val recorder = Recorder.Builder()
    .setQualitySelector(QualitySelector.from(Quality.FHD))
    .build()
videoCapture = VideoCapture.withOutput(recorder)

...

// Bind UseCases to camera (adding videoCapture along with preview here, but
// preview is not required to use videoCapture). This function returns a camera
// object which can be used to perform operations like zoom, flash, and focus.
var camera = cameraProvider.bindToLifecycle(
    this, cameraSelector, preview, videoCapture)

في هذه المرحلة، يمكن الوصول إلى Recorder على videoCapture.output. الموقع. يمكن لـ Recorder بدء تسجيلات الفيديو المحفوظة في File، ParcelFileDescriptor أو MediaStore. يستخدم هذا المثال السمة MediaStore.

في Recorder، هناك عدة طرق لطلب البيانات من أجل إعدادها. اتصل prepareRecording() لضبط خيارات الناتج MediaStore إذا كان تطبيقك يحتوي على للحصول على إذن لاستخدام ميكروفون الجهاز، اتصل بـ withAudioEnabled() أيضًا. بعد ذلك، يمكنك طلب start() لبدء التسجيل، ومراعٍ في سياقٍ. أداة معالجة الأحداث في Consumer<VideoRecordEvent> للتعامل مع أحداث تسجيل الفيديو. في حال حذف بنجاح، يمكن استخدام Recording المعروضة لإيقاف التسجيل.

// CameraX: implement video capture with CameraProvider.

private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"

private fun startStopVideo() {
   val videoCapture = this.videoCapture ?: return

   if (recording != null) {
       // Stop the current recording session.
       recording.stop()
       recording = null
       return
   }

   // Create and start a new recording session.
   val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
       .format(System.currentTimeMillis())
   val contentValues = ContentValues().apply {
       put(MediaStore.MediaColumns.DISPLAY_NAME, name)
       put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4")
       if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
           put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/CameraX-Video")
       }
   }

   val mediaStoreOutputOptions = MediaStoreOutputOptions
       .Builder(contentResolver, MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
       .setContentValues(contentValues)
       .build()

   recording = videoCapture.output
       .prepareRecording(this, mediaStoreOutputOptions)
       .withAudioEnabled()
       .start(ContextCompat.getMainExecutor(this)) { recordEvent ->
           when(recordEvent) {
               is VideoRecordEvent.Start -> {
                   viewBinding.videoCaptureButton.apply {
                       text = getString(R.string.stop_capture)
                       isEnabled = true
                   }
               }
               is VideoRecordEvent.Finalize -> {
                   if (!recordEvent.hasError()) {
                       val msg = "Video capture succeeded: " +
                           "${recordEvent.outputResults.outputUri}"
                       Toast.makeText(
                           baseContext, msg, Toast.LENGTH_SHORT
                       ).show()
                       Log.d(TAG, msg)
                   } else {
                       recording?.close()
                       recording = null
                       Log.e(TAG, "video capture ends with error",
                             recordEvent.error)
                   }
                   viewBinding.videoCaptureButton.apply {
                       text = getString(R.string.start_capture)
                       isEnabled = true
                   }
               }
           }
       }
}

مصادر إضافية

لدينا العديد من تطبيقات CameraX الكاملة في مستودع GitHub لعيّنات الكاميرا توضح هذه النماذج مدى توافق السيناريوهات في هذا الدليل مع تطبيق Android.

إذا أردت الحصول على دعم إضافي في نقل البيانات إلى CameraX أو كانت لديك أسئلة بخصوص مجموعة واجهات برمجة التطبيقات للكاميرا Android، يُرجى التواصل معنا على مناقشة CameraX المجموعة: