إذا كان تطبيقك يستخدم الفئة Camera
الأصلية ("الكاميرا1") التي تم إيقافها نهائيًا منذ
Android 5.0 (المستوى 21 من واجهة برمجة التطبيقات)،
ننصحك بشدة بالتحديث إلى واجهة برمجة تطبيقات حديثة لكاميرا Android. يوفّر Android
CameraX (واجهة برمجة تطبيقات كاميرا موحّدة وفعّالة Jetpack
) وcamera2 (واجهة برمجة تطبيقات إطار عمل منخفضة المستوى). بالنسبة إلى
الغالبية العظمى من الحالات، ننصحك بترحيل تطبيقك إلى CameraX. وفي ما يلي السبب في ذلك:
- سهولة الاستخدام: يعالج تطبيق CameraX التفاصيل المنخفضة المستوى، وبذلك يمكنك التركيز بشكل أقل على إنشاء تجربة كاميرا من البداية وحتى التركيز أكثر على إبراز تطبيقك.
- تعالج CameraX عملية التقسيم نيابةً عنك: تقلِّل CameraX من تكاليف الصيانة الطويلة المدى والرموز البرمجية الخاصة بالجهاز، ما يوفّر للمستخدمين تجارب ذات جودة أعلى. لمزيد من المعلومات حول هذا الموضوع، اطلع على مشاركة مدونة توافق أفضل مع الأجهزة مع CameraX.
- الإمكانات المتقدّمة: تم تصميم تطبيق CameraX بعناية لجعل وظائفه المتقدمة سهلة الدمج في تطبيقك. على سبيل المثال، يمكنك بسهولة تطبيق الوظائف "بوكيه" و"إصلاح الوجه" و"نطاق عالي الديناميكية (HDR)" (نطاق ديناميكي عالي) وضبط الإضاءة الساطعة المنخفضة على الصور باستخدام إضافات CameraX.
- قابلية التحديث: يطرح نظام Android إمكانات جديدة وإصلاحات للأخطاء في CameraX على مدار العام. عند الانتقال إلى CameraX، يحصل تطبيقك على أحدث تكنولوجيات كاميرا Android مع كل إصدار من CameraX، وليس فقط على إصدارات Android السنوية.
في هذا الدليل، ستعثر على سيناريوهات شائعة لتطبيقات الكاميرا. يشمل كل سيناريو عملية تنفيذ Camera1 وتطبيق CameraX للمقارنة.
عندما يتعلّق الأمر بنقل البيانات، قد تحتاج أحيانًا إلى مزيد من المرونة للدمج
مع قاعدة رموز برمجية حالية. تشتمل جميع رموز CameraX في هذا الدليل على عملية تنفيذ
CameraController
، وهي طريقة رائعة إذا كنت تريد أبسط طريقة لاستخدام CameraX، بالإضافة إلى
CameraProvider
عملية تنفيذ رائعة إذا كنت بحاجة إلى مزيد من المرونة. لمساعدتك في تحديد الشكل
المناسب لك، إليك فوائد كل منها:
وحدة التحكّم في الكاميرا |
CameraProvider |
يجب توفّر رمز إعداد صغير. | إتاحة المزيد من التحكّم |
عند السماح لتطبيق CameraX بالتعامل مع المزيد من أجزاء عملية الإعداد، تعمل وظائف مثل النقر للتركيز والتصغير أو التكبير بإصبعين تلقائيًا. |
وبما أنّ مطوّر التطبيقات يتولى عملية الإعداد، تتوفّر فرص أكبر لتخصيص الإعدادات، مثل تفعيل دوران الإخراج للصور أو ضبط تنسيق صورة الإخراج في ImageAnalysis .
|
إنّ الطلب من PreviewView لمعاينة الكاميرا يتيح
لتطبيق CameraX تقديم تكامل تام بين الأطراف، كما هو الحال في عملية دمج ML Kit التي يمكنها ربط إحداثيات نتائج نموذج تعلُّم الآلة (مثل مربّعات تحديد الوجوه) مباشرةً على إحداثيات المعاينة.
|
توفّر إمكانية استخدام "سطح" مخصص لمعاينة الكاميرا مزيدًا من المرونة، مثل استخدام رمز "Surface" الحالي الذي يمكن أن يكون إدخالاً لأجزاء أخرى من تطبيقك. |
إذا واجهتك مشكلة أثناء محاولة نقل البيانات، يمكنك التواصل معنا من خلال مجموعة مناقشة CameraX.
قبل نقل البيانات
مقارنة استخدام CameraX مع Camera1
رغم أن الرمز قد يبدو مختلفًا، إلا أن المفاهيم الأساسية في Camera1 وcameraX متشابهة جدًا. تدمج CameraX الوظائف الشائعة للكاميرا في حالات الاستخدام. ونتيجةً لذلك، يعالج CameraX العديد من المهام التي تركها للمطوّر في Camera1 تلقائيًا. تتوفر أربعة رموز
UseCase
في CameraX، ويمكنك استخدامها
لمجموعة متنوعة من مهام الكاميرا: Preview
وImageCapture
وVideoCapture
وImageAnalysis
.
إنّ أحد الأمثلة على معالجة CameraX للتفاصيل المنخفضة المستوى للمطوّرين هو
ViewPort
الذي تتم مشاركته بين
UseCase
النشطة. يضمن ذلك رؤية جميع UseCase
وحدات البكسل نفسها بالضبط.
في تطبيق "كاميرا1"، يجب إدارة هذه التفاصيل بنفسك، ونظرًا لتفاوت نِسب العرض إلى الارتفاع بين أجهزة استشعار الكاميرا والشاشات في الأجهزة، قد يكون من الصعب التأكّد من تطابق المعاينة مع الصور والفيديوهات التي تم التقاطها.
كمثال آخر، تعالج CameraX طلبات استدعاء Lifecycle
تلقائيًا في
مثيل Lifecycle
الذي تمرّره. وهذا يعني أن تطبيق CameraX يمكنه معالجة اتصال تطبيقك بالكاميرا خلال دورة حياة نشاط Android بأكملها،
بما في ذلك الحالات التالية: إغلاق الكاميرا عندما ينتقل تطبيقك إلى الخلفية، وإزالة معاينة الكاميرا عندما لا تكون الشاشة بحاجة إلى عرضها، وإيقاف معاينة الكاميرا مؤقتًا عندما يكون لنشاط آخر أولوية في المقدمة، مثل مكالمة فيديو واردة.
أخيرًا، تتعامل شركة CameraX مع التدوير والتحجيم بدون الحاجة إلى أي رمز إضافي من جانبك. وإذا كان جهاز Activity
باتجاه غير مقفل، يتم
ضبط إعدادات UseCase
في كل مرة يتم تدوير الجهاز، حيث يؤدي ذلك إلى تدمير Activity
وإعادة إنشائها عند تغيّر الاتجاه. يؤدي ذلك إلى ضبط UseCases
على إعداد الدوران المستهدف لمطابقة اتجاه العرض
تلقائيًا في كل مرة.
قراءة المزيد من المعلومات عن عمليات التدوير في CameraX
قبل التعمّق في التفاصيل، إليك نظرة عامة على كاميرا
UseCase
من خلال تطبيق CameraX وعلاقته بتطبيق Camera1. (تظهر مفاهيم CameraX باللون
الأزرق ومفاهيم
كاميرا1 باللون
الأخضر.)
كاميراX |
|||
إعدادات Camera Controller / CameraProvider | |||
↓ | ↓ | ↓ | ↓ |
معاينة | التقاط صورة | التقاط الفيديو | تحليل الصور |
⁞ | ⁞ | ⁞ | ⁞ |
إدارة معاينة Surface وضبطها على الكاميرا | ضبط ImageCallback واستدعاء TakeImage() على الكاميرا | إدارة إعداد الكاميرا وMediaRecorder بترتيب معيّن | رمز تحليل مخصّص تم إنشاؤه فوق مساحة المعاينة |
↑ | ↑ | ↑ | ↑ |
الرمز الخاص بالجهاز | |||
↑ | |||
إدارة تدوير الجهاز وتحجيمه | |||
↑ | |||
إدارة جلسة الكاميرا (اختيار الكاميرا وإدارة مراحل النشاط) | |||
الكاميرا1 |
التوافق والأداء في CameraX
يتوافق تطبيق CameraX مع الأجهزة التي تعمل بنظام Android 5.0 (المستوى 21 من واجهة برمجة التطبيقات) والإصدارات الأحدث. ويمثل ذلك أكثر من% 98 من أجهزة Android الحالية. تم تصميم CameraX للتعامل مع الاختلافات بين الأجهزة تلقائيًا، مما يقلل الحاجة إلى رموز برمجية خاصة بكل جهاز في تطبيقك. علاوةً على ذلك، نختبر أكثر من 150 جهازًا ماديًا على جميع إصدارات Android منذ الإصدار 5.0 في CameraX Test Lab. يمكنك مراجعة القائمة الكاملة للأجهزة الموجودة حاليًا في مركز الاختبار الافتراضي.
تستخدم CameraX Executor
لتشغيل حزمة الكاميرا. يمكنك
ضبط التطبيق الخاص بك على CameraX
إذا كان تطبيقك يتضمّن متطلبات محددة بشأن سلاسل المحادثات. في حال عدم ضبط هذه السياسة، تنشئ CameraX
وتستخدم دالة Executor
داخلية تلقائية ومحسَّنة. وتتطلب العديد من واجهات برمجة تطبيقات النظام الأساسي التي تم إنشاء CameraX عليها حظر الاتصال البيني للعمليات (IPC) بالأجهزة التي قد تستغرق أحيانًا مئات المللي ثانية للاستجابة. لهذا السبب، تستدعي CameraX واجهات برمجة التطبيقات هذه فقط من سلاسل التعليمات في الخلفية، ما يضمن عدم حظر سلسلة التعليمات الرئيسية وأن تظل واجهة المستخدم سلسة.
مزيد من المعلومات حول سلاسل المحادثات
إذا كان السوق المستهدف لتطبيقك يتضمّن أجهزة منخفضة المواصفات، توفّر CameraX طريقة لتقليل وقت الإعداد باستخدام
محدد الكاميرا. نظرًا لأن عملية الاتصال بمكونات الأجهزة قد تستغرق وقتًا قليلاً، خاصةً على الأجهزة ذات المواصفات المنخفضة، يمكنك تحديد مجموعة الكاميرات التي يحتاجها تطبيقك. لا يتم ربط CameraX بهذه الكاميرات إلا أثناء الإعداد. على سبيل المثال، إذا كان التطبيق يستخدم الكاميرات الأمامية فقط، يمكنه ضبط هذه الإعدادات باستخدام DEFAULT_BACK_CAMERA
ثم يتجنّب تطبيق CameraX ضبط إعدادات الكاميرات الأمامية
لتقليل زمن الانتقال.
مفاهيم تطوير Android
يفترض هذا الدليل إلمامك العام بتطوير Android. بالإضافة إلى الأساسيات، إليك مفهومان من المفيد فهمهما قبل الانتقال إلى التعليمات البرمجية أدناه:
- تنشئ ميزة View Slideing فئة ربط لملفات تنسيق XML، ما يتيح لك الاطّلاع بسهولة على طرق العرض في الأنشطة، كما هو الحال في العديد من مقتطفات الرموز أدناه. هناك بعض
الاختلافات بين ربط طريقة العرض و
findViewById()
(الطريقة السابقة للإشارة إلى طرق العرض المرجعية)، ولكن في الرمز أدناه، من المفترض أن تتمكن من استبدال أسطر ربط العرض باستدعاءfindViewById()
مشابه. - الكوروتين غير المتزامن هو نمط تصميم تزامن تمت إضافته في لغة البرمجة Kotlin 1.3 ويمكن استخدامه للتعامل مع طرق CameraX التي تعرض
ListenableFuture
. وقد أصبح ذلك أسهل من خلال استخدام مكتبة Jetpack اعتبارًا من الإصدار 1.1.0. لإضافة الكوروتين غير المتزامن إلى تطبيقك:- أضف
implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0")
إلى ملف Gradle. - ضَع أي رمز CameraX يعرض الرمز
ListenableFuture
في حظر أو وظيفة تعليق ضمنlaunch
. - أضف استدعاء
await()
إلى استدعاء الدالة التي تعرضListenableFuture
. - لفهم المزيد عن طريقة عمل الكوروتينات، راجِع دليل بدء استخدام الكوروتين.
- أضف
نقل السيناريوهات الشائعة
يوضّح هذا القسم كيفية نقل السيناريوهات الشائعة من Camera1 إلى CameraX.
ويغطي كل سيناريو عملية تنفيذ Camera1،
وتنفيذ CameraProvider
من CameraX،
وتنفيذ CameraX CameraController
.
اختيار كاميرا
في تطبيق الكاميرا الخاص بك، أحد أول الأشياء التي قد ترغب في تقديمها هو طريقة اختيار كاميرات مختلفة.
الكاميرا1
في تطبيق "كاميرا1"، يمكنك إمّا استدعاء
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: وحدة تحكّم الكاميرا
في 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
(إذا أمكن).
الكاميرا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: وحدة تحكّم الكاميرا
في 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()
إذا كانت لديك احتياجات أكثر تعقيدًا.
في هذه الحالة، لا يمكن استخدام 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
" إلى أحداث اللمس في "PreviewView
" لمعالجة
النقر للتركيز تلقائيًا. يمكنك تفعيل ميزة "النقر للتركيز" أو إيقافها باستخدام
setTapToFocusEnabled()
،
والتحقّق من القيمة باستخدام دالة getter المقابلة
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 }
التصغير أو التكبير بإصبعين
إنّ تكبير المعاينة وتصغيرها هو أحد التلاعبات المباشرة الشائعة الأخرى لمعاينة الكاميرا. مع تزايد عدد الكاميرات على الأجهزة، يتوقع المستخدمون أيضًا اختيار العدسة ذات البعد البؤري الأفضل تلقائيًا كنتيجة للتكبير/التصغير.
الكاميرا1
هناك طريقتان للتكبير أو التصغير باستخدام 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
إلى أحداث اللمس في PreviewView
للتعامل مع التصغير أو التكبير بإصبعين تلقائيًا. يمكنك تفعيل أو إيقاف
استخدام الإصبعين للتكبير أو التصغير باستخدام
setPinchToZoomEnabled()
،
والتحقّق من القيمة باستخدام دالة getter المناظرة
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 }
التقاط صورة
يعرض هذا القسم كيفية بدء التقاط الصور، سواء كنت تحتاج إلى القيام بذلك بضغطة زر الغالق، أو بعد انقضاء المؤقت، أو عند أي حدث آخر من اختيارك.
الكاميرا1
في تطبيق "كاميرا1"، يجب أولاً تحديد 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
في تطبيق 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 مرة أخرى مع الكثير من هذه التعقيدات.
الكاميرا1
يتطلب التقاط الفيديو باستخدام 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
في 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
وفئة يتم فيها تنفيذ علامة
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، يُرجى التواصل معنا من خلال مجموعة مناقشة CameraX.