إذا كان تطبيقك يستخدم فئة Camera
الأصلية ("Camera1")، التي تم إيقافها نهائيًا منذ Android 5.0 (المستوى 21 لواجهة برمجة التطبيقات)،
ننصح بشدة بالتحديث إلى واجهة برمجة تطبيقات حديثة لكاميرا Android. يوفّر نظام التشغيل Android واجهتَي برمجة التطبيقات
CameraX (واجهة برمجة تطبيقات Jetpack قوية ومتّسقة لمعالجة كاميرا) وCamera2 (واجهة برمجة تطبيقات لإطار العمل من المستوى الأدنى). في
الغالبية العظمى من الحالات، ننصحك بنقل بيانات تطبيقك إلى CameraX. وفي ما يلي السبب في ذلك:
- سهولة الاستخدام: تعالج CameraX التفاصيل المنخفضة المستوى، ما يتيح لك التركيز بشكل أقل على إنشاء تجربة كاميرا من الصفر والتركيز بشكل أكبر على تمييز تطبيقك.
- تعالج CameraX مشكلة التجزئة نيابةً عنك: تقلل CameraX من تكاليف الصيانة على المدى الطويل والرمز المخصّص للأجهزة، ما يقدّم تجارب فائقة الجودة للمستخدمين. لمزيد من المعلومات حول هذا الموضوع، يُرجى الاطّلاع على مشاركة المدوّنة بعنوان تحسين توافق الأجهزة باستخدام CameraX.
- الإمكانات المتقدّمة: تم تصميم CameraX بعناية لتسهيل دمج الوظائف المتقدمة في تطبيقك. على سبيل المثال، يمكنك بسهولة تطبيق تأثير "الخلفية الضبابية" و"تجميل الوجه" و"النطاق العالي الديناميكية" و"تحسين الإضاءة المنخفضة" و"وضع التصوير الليلي" على صورك باستخدام إضافات CameraX.
- قابلية التحديث: يطرح نظام التشغيل Android إمكانات جديدة وإصلاحات للأخطاء في CameraX على مدار العام. عند نقل البيانات إلى CameraX، سيحصل تطبيقك على أحدث تكنولوجيا كاميرا Android مع كل إصدار من CameraX، وليس فقط عند طرح الإصدارات السنوية من Android.
في هذا الدليل، ستجد سيناريوهات شائعة لتطبيقات الكاميرا. يتضمّن كل سيناريو عملية تنفيذ Camera1 وعملية تنفيذ CameraX بغرض مقارنة بينهما.
عندما يتعلق الأمر بنقل البيانات، تحتاج أحيانًا إلى مرونة إضافية للدمج
مع قاعدة بيانات حالية. يتضمّن كل رمز CameraX في هذا الدليل تنفيذًا باستخدام CameraController
، وهو مثالي إذا كنت تريد أبسط طريقة لاستخدام CameraX، بالإضافة إلى تنفيذ باستخدام CameraProvider
، وهو مثالي إذا كنت بحاجة إلى مزيد من المرونة. لمساعدتك في تحديد الخيار المناسب لك، إليك مزايا كلّ منهما:
CameraController |
CameraProvider |
تتطلّب القليل من رمز الإعداد | السماح بمزيد من التحكّم |
يتيح السماح لـ CameraX بمعالجة المزيد من عملية الإعداد استخدام وظائف مثل النقر للتركيز والتصغير/التكبير عن طريق تصغير إصبعَيك تلقائيًا. |
بما أنّ مطوّر التطبيقات هو المسؤول عن الإعداد، تتوفّر المزيد من الفرص
لتخصيص الإعدادات، مثل تفعيل دوران الصورة المعروضة
أو ضبط تنسيق الصورة المعروضة في ImageAnalysis .
|
إنّ طلب PreviewView لمعاينة الكاميرا يسمح لـ CameraX
بتوفير عملية دمج سلسة من البداية إلى النهاية، كما هو الحال في دمج ML Kit
الذي يمكنه ربط إحداثيات نتيجة نموذج تعلُّم الآلة (مثل مربّعات حدود
الوجوه) مباشرةً بإحداثيات المعاينة.
|
توفّر إمكانية استخدام عنصر "سطح" مخصّص لمعاينة الكاميرا قدرًا أكبر من المرونة، مثل استخدام رمز "سطح" الحالي الذي يمكن أن يكون إدخالًا لأجزاء أخرى من تطبيقك. |
إذا واجهت مشكلة في نقل البيانات، يُرجى التواصل معنا على مجموعة مناقشة CameraX.
قبل نقل البيانات
مقارنة استخدام CameraX بتطبيق Camera1
على الرغم من أنّ الرمز قد يبدو مختلفًا، إلا أنّ المفاهيم الأساسية في Camera1 و
CameraX متشابهة جدًا. تُنشئ CameraX وظائف تجريدية للكاميرا الشائعة في حالات الاستخدام، وبالتالي، تعالج CameraX تلقائيًا العديد من المهام التي كان المطوّر ينفّذها في Camera1. هناك أربعة
UseCase
في CameraX، يمكنك
استخدامها لتنفيذ مجموعة متنوعة من مهام الكاميرا: Preview
،
ImageCapture
،
VideoCapture
،
ImageAnalysis
.
من الأمثلة على تعامل CameraX مع التفاصيل المنخفضة المستوى للمطوّرين هو
ViewPort
التي تتم مشاركتها بين
UseCase
s النشطة. ويضمن ذلك أن ترى جميع UseCase
s وحدات البكسل نفسها تمامًا.
في Camera1، عليك إدارة هذه التفاصيل بنفسك، ونظرًا للاختلاف
في نِسب العرض إلى الارتفاع على مستوى كاميرات الأجهزة وشاشاتها، قد يكون من الصعب
ضمان تطابق المعاينة مع الصور والفيديوهات التي تم التقاطها.
في مثال آخر، تعالج CameraX طلبات Lifecycle
الاستدعاء تلقائيًا في مثيل
Lifecycle
الذي ترسله. وهذا يعني أنّ CameraX تتعامل مع
ربط تطبيقك بالكاميرا خلال دورة حياة نشاط Android بالكامل،
بما في ذلك الحالات التالية: إغلاق الكاميرا عندما ينتقل تطبيقك إلى
الخلفية، وإزالة معاينة الكاميرا عندما لا تتطلب الشاشة
عرضها، وإيقاف معاينة الكاميرا مؤقتًا عندما يأخذ نشاط آخر
الأولوية في المقدّمة، مثل مكالمة فيديو واردة.
أخيرًا، تتعامل CameraX مع التدوير والتكبير/التصغير بدون الحاجة إلى أي رمز إضافي
من جانبك. في حال استخدام Activity
مع اتجاه غير مقفل، يتم
إعداد UseCase
في كل مرة يتم فيها تدوير الجهاز، لأنّ النظام يزيل
Activity
ويعيد إنشائه عند تغيير الاتجاه. يؤدي ذلك إلى ضبط
UseCases
لدوران الشاشة المستهدَف ليطابق اتجاه الشاشة بشكل
تلقائي في كل مرة.
اطّلِع على مزيد من المعلومات عن عمليات التدوير في CameraX.
قبل الانتقال إلى التفاصيل، إليك نظرة عامة على واجهة برمجة التطبيقات CameraX
UseCase
وكيفية ارتباط تطبيق Camera1 بها. (مفاهيم CameraX باللون
الأزرق ومفاهيم Camera1
باللون
الأخضر)
CameraX |
|||
إعدادات CameraController / CameraProvider | |||
↓ | ↓ | ↓ | ↓ |
معاينة | ImageCapture | VideoCapture | ImageAnalysis |
⁞ | ⁞ | ⁞ | ⁞ |
إدارة سطح المعاينة وضبطه على الكاميرا | ضبط PictureCallback واستدعاء takePicture() في Camera | إدارة إعدادات Camera وMediaRecorder بترتيب محدّد | رمز تحليل مخصّص تم إنشاؤه على سطح المعاينة |
↑ | ↑ | ↑ | ↑ |
رمز خاص بالجهاز | |||
↑ | |||
إدارة تدوير الجهاز وتغيير حجمه | |||
↑ | |||
إدارة جلسة الكاميرا (اختيار الكاميرا وإدارة مراحل النشاط) | |||
Camera1 |
التوافق والأداء في 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
. وقد تم تسهيل ذلك من خلال مكتبة Concurrent في Jetpack اعتبارًا من الإصدار 1.1.0. لإضافة دالة معالجة مهام غير متزامنة إلى تطبيقك:- أضِف
implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0")
إلى ملف Gradle. - ضَع أي رمز CameraX يعرض القيمة
ListenableFuture
في ملف برمجي يحتوي علىlaunch
وحدة أو دالة تعليق. - أضِف دعوة
await()
إلى طلب الدالة الذي يعرضListenableFuture
. - لفهم آلية عمل الدوالّ المتعدّدة المهام بشكلٍ أعمق، اطّلِع على دليل بدء دالة متعدّدة المهام.
- أضِف
نقل السيناريوهات الشائعة
يوضّح هذا القسم كيفية نقل السيناريوهات الشائعة من Camera1 إلى CameraX.
يتناول كل سيناريو عملية تنفيذ Camera1 وتنفيذ CameraX CameraProvider
وتنفيذ CameraX CameraController
.
اختيار كاميرا
في تطبيق الكاميرا، من أوّل الميزات التي قد تريد توفيرها هو طريقة لاختيار كاميرات مختلفة.
Camera1
في 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
اختيار الكاميرا. تسهِّل مكتبة CameraX استخدام الكاميرا التلقائية في الحالات الشائعة. يمكنك تحديد ما إذا
كنت تريد استخدام الكاميرا الأمامية التلقائية أو الكاميرا الخلفية التلقائية. بالإضافة إلى ذلك، يتيح لك 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()
عرض معاينة
يجب أن تعرض معظم تطبيقات الكاميرا خلاصة الكاميرا على الشاشة في مرحلة ما. باستخدام Camera1، عليك إدارة عمليات الاستدعاء المتعلّقة بدورة الحياة بشكل صحيح، وعلىك أيضًا تحديد دوران المعاينة وحجمها.
بالإضافة إلى ذلك، في Camera1، عليك تحديد ما إذا كنت تريد استخدام ملف شخصي على شكل
TextureView
أو
SurfaceView
كسطح معاينة.
يتضمّن كلا الخيارَين بعض السلبيات، وفي كلتا الحالتَين، تتطلّب Camera1 منك
معالجة التدوير والتكبير/التصغير بشكل صحيح. من ناحية أخرى،
تتضمّن واجهة برمجة التطبيقات PreviewView
في CameraX عمليات تنفيذ أساسية لكل من TextureView
وSurfaceView
.
تحدِّد CameraX أفضل طريقة للتنفيذ استنادًا إلى عوامل مثل
نوع الجهاز وإصدار Android الذي يعمل عليه تطبيقك. إذا كان أي من هذين الأسلوبَين
متوافقًا، يمكنك تحديد إعداداتك المفضّلة باستخدام
PreviewView.ImplementationMode
.
يستخدم الخيار COMPATIBLE
الرمز TextureView
لمعاينة المحتوى، وتستخدم القيمة
PERFORMANCE
الرمز SurfaceView
(عند الإمكان).
Camera1
لعرض معاينة، عليك كتابة فئة 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
، ولكنه
يظلّ يبسّط إعداد المعاينة بشكل كبير مقارنةً بـ Camera1. لأغراض توضيحية، يستخدم هذا المثال PreviewView
، ولكن يمكنك كتابة SurfaceProvider
مخصّصة لإرسالها إلى setSurfaceProvider()
إذا كانت لديك احتياجات أكثر تعقيدًا.
في هذه الحالة، لا يتم تضمين Preview
UseCase
بشكل ضمني كما هو الحال مع 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() } }
النقر للتركيز
عندما تظهر معاينة الكاميرا على الشاشة، يكون من الشائع استخدام عنصر تحكّم لضبط نقطة تركيز عند نقر المستخدم على المعاينة.
Camera1
لتنفيذ ميزة "النقر للتركيز" في Camera1، يجب احتساب التركيز الأمثل
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
:
- إعداد أداة رصد الإيماءات لمعالجة أحداث النقر
- باستخدام حدث النقر، أنشئ
MeteringPoint
باستخدامMeteringPointFactory.createPoint()
. - باستخدام
MeteringPoint
، أنشئFocusMeteringAction
. - باستخدام عنصر
CameraControl
فيCamera
(المُعاد منbindToLifecycle()
)، يمكنك استدعاءstartFocusAndMetering()
مع تضمينFocusMeteringAction
. - (اختياري) الردّ على
FocusMeteringResult
- اضبط أداة رصد الإيماءات للردّ على أحداث اللمس في
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 }
التكبير أو التصغير بإصبعَين
يُعدّ تكبير المعاينة وتصغيرها عملية تلاعب مباشرة شائعة أخرى في معاينة الكاميرا. مع تزايد عدد الكاميرات على الأجهزة، يتوقع المستخدمون أيضًا أن يتم اختيار العدسة ذات أفضل طول بؤري تلقائيًا نتيجةً للتكبير.
Camera1
هناك طريقتان لتكبير الصورة باستخدام Camera1. تؤدي الطريقة 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
إلى أحداث اللمس
في PreviewView للتعامل مع ميزة "التصغير/التكبير بإصبعَين" تلقائيًا. يمكنك تفعيل ميزة
التصغير/التكبير باستخدام إصبعَين باستخدام
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
:
- إعداد أداة رصد إيماءات تغيير الحجم للتعامل مع أحداث التصغير/التكبير
- احصل على
ZoomState
من عنصرCamera.CameraInfo
، حيث يتم عرضCamera
عند استدعاءbindToLifecycle()
. - إذا كان
ZoomState
يحتوي على قيمةzoomRatio
، احفظها كنسبة التكبير/التصغير الحالية. إذا لم يكن هناكzoomRatio
علىZoomState
، استخدِم ميزة التكبير/التصغير التلقائية (1.0) في الكاميرا. - اضرب نسبة التكبير/التصغير الحالية في
scaleFactor
لتحديد نسبة التكبير/التصغير الجديدة، ثمّ أدخِل هذا المقياس فيCameraControl.setZoomRatio()
. - اضبط أداة رصد الإيماءات للردّ على أحداث اللمس في
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 }
التقاط صورة
يوضّح هذا القسم كيفية بدء التقاط الصور، سواءً كان ذلك عند ضغطك على زر الغالق أو بعد انقضاء وقت محدّد أو عند حدوث أي حدث آخر من اختيارك.
Camera1
في 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 على بساطة Camera1 لالتقاط
الصور من خلال تنفيذ طريقة 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 مرة أخرى الكثير من هذه المهام المعقدة نيابةً عنك.
Camera1
يتطلب تسجيل الفيديو باستخدام Camera1 إدارةً دقيقةً لـ Camera
و
MediaRecorder
، ويجب
استدعاء الطرق بترتيب معيّن. يجب اتّباع هذا الترتيب لنعُدّ تطبيقك للعمل بشكلٍ صحيح:
- افتح الكاميرا.
- حضِّر معاينة وابدأها (إذا كان تطبيقك يعرض الفيديو الذي يتم تسجيله، وهو ما يحدث عادةً).
- افتح قفل الكاميرا لاستخدامها من قِبل
MediaRecorder
من خلال الاتصال علىCamera.unlock()
. - يمكنك ضبط التسجيل من خلال استدعاء هذه الطرق على
MediaRecorder
:- ربط نسخة
Camera
بـsetCamera(camera)
- الاتصال بالرقم
setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
. - الاتصال بالرقم
setVideoSource(MediaRecorder.VideoSource.CAMERA)
. - يُرجى الاتصال بالرقم
setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_1080P))
لضبط الجودة. راجِعCamcorderProfile
للاطّلاع على كل خيارات الجودة. - الاتصال بالرقم
setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString())
. - إذا كان تطبيقك يتضمّن معاينة للفيديو، يُرجى الاتصال بالرقم
setPreviewDisplay(preview?.holder?.surface)
. - الاتصال بالرقم
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
. - الاتصال بالرقم
setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT)
. - الاتصال بالرقم
setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT)
. - يُرجى الاتصال برقم
prepare()
لإنهاء ضبطMediaRecorder
.
- ربط نسخة
- لبدء التسجيل، اتصل بالرقم
MediaRecorder.start()
. - لإيقاف التسجيل، يمكنك استدعاء هذه الطرق. يُرجى اتّباع الخطوات التالية بالترتيب نفسه:
- الاتصال بالرقم
MediaRecorder.stop()
. - يمكنك اختياريًا إزالة إعدادات
MediaRecorder
الحالية من خلال الاتصال بالرقمMediaRecorder.reset()
. - الاتصال بالرقم
MediaRecorder.release()
. - يمكنك قفل الكاميرا لكي تتمكّن جلسات
MediaRecorder
المستقبلية من استخدامها من خلال الاتصال بالرقمCamera.lock()
.
- الاتصال بالرقم
- لإيقاف المعاينة، اتصل على
Camera.stopPreview()
. - أخيرًا، لتحرير
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
بشكل مستقل،
ما دام بإمكانك استخدام قائمة حالات الاستخدام بشكل متزامن.
تكون ميزة ImageCapture
وImageAnalysis
UseCase
مفعّلة تلقائيًا، لذلك
لم تكن بحاجة إلى الاتصال بـ setEnabledUseCases()
لالتقاط صورة.
لاستخدام CameraController
لتسجيل الفيديو، عليك أولاً استخدام
setEnabledUseCases()
للسماحVideoCapture
UseCase
.
// CameraX: Enable VideoCapture UseCase on CameraController. cameraController.setEnabledUseCases(VIDEO_CAPTURE);
عندما تريد بدء تسجيل فيديو، يمكنك استدعاء الدالة
CameraController.startRecording()
. يمكن لهذه الوظيفة حفظ الفيديو المسجّل في File
، كما هو موضّح في المثال أدناه. بالإضافة إلى ذلك، عليك تمرير Executor
وclass
تنفِّذ
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: VideoCaptureprivate 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 Camera API، يُرجى التواصل معنا على مجموعة مناقشة CameraX.