ملاحظة: تشير هذه الصفحة إلى حزمة Camera2. ننصحك باستخدام الكاميراX ما لم يكن تطبيقك يتطلب ميزات محدَّدة منخفضة المستوى من تطبيق Camera2. يتوافق كل من CameraX و Camera2 مع نظام التشغيل Android 5.0 (المستوى 21 لواجهة برمجة التطبيقات) والإصدارات الأحدث.
يمكن أن يستخدم تطبيق الكاميرا أكثر من بث واحد من الإطارات في الوقت نفسه. ضِمن في بعض الحالات، تحتاج مجموعات البث المختلفة إلى درجة دقة مختلفة للإطار أو وحدة بكسل مختلفة. . تتضمن بعض حالات الاستخدام النموذجية ما يلي:
- تسجيل الفيديو: بث واحد للمعاينة، وآخر يتم ترميزه وحفظه في ملف.
- مسح الرموز الشريطية: بث واحد للمعاينة والآخر لرصد الرمز الشريطي
- التصوير الحاسوبي: بث واحد للمعاينة والآخر للوجه أو المشهد الرصد.
هناك تكلفة أداء غير بسيطة عند معالجة الإطارات، عند إجراء بث متوازٍ أو معالجة خطوط أنابيب.
وقد تتمكن موارد مثل وحدة المعالجة المركزية (CPU) ووحدة معالجة الرسومات ووحدة معالجة البيانات (DSP) من الاستفادة من إعادة معالجة إطار العمل الإمكانات، لكن موارد مثل الذاكرة ستنمو بشكل خطي.
استهدافات متعددة لكل طلب
يمكن دمج عدّة فيديوهات بث مباشر للكاميرا في بث واحد
CameraCaptureRequest
يوضح مقتطف الرمز التالي كيفية إعداد جلسة كاميرا باستخدام إحدى
بث لمعاينة الكاميرا وبث آخر لمعالجة الصور:
Kotlin
val session: CameraCaptureSession = ... // from CameraCaptureSession.StateCallback // You will use the preview capture template for the combined streams // because it is optimized for low latency; for high-quality images, use // TEMPLATE_STILL_CAPTURE, and for a steady frame rate use TEMPLATE_RECORD val requestTemplate = CameraDevice.TEMPLATE_PREVIEW val combinedRequest = session.device.createCaptureRequest(requestTemplate) // Link the Surface targets with the combined request combinedRequest.addTarget(previewSurface) combinedRequest.addTarget(imReaderSurface) // In this simple case, the SurfaceView gets updated automatically. ImageReader // has its own callback that you have to listen to in order to retrieve the // frames so there is no need to set up a callback for the capture request session.setRepeatingRequest(combinedRequest.build(), null, null)
Java
CameraCaptureSession session = …; // from CameraCaptureSession.StateCallback // You will use the preview capture template for the combined streams // because it is optimized for low latency; for high-quality images, use // TEMPLATE_STILL_CAPTURE, and for a steady frame rate use TEMPLATE_RECORD CaptureRequest.Builder combinedRequest = session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); // Link the Surface targets with the combined request combinedRequest.addTarget(previewSurface); combinedRequest.addTarget(imReaderSurface); // In this simple case, the SurfaceView gets updated automatically. ImageReader // has its own callback that you have to listen to in order to retrieve the // frames so there is no need to set up a callback for the capture request session.setRepeatingRequest(combinedRequest.build(), null, null);
في حال ضبط مساحات العرض المستهدَفة بشكل صحيح، سينتج هذا الرمز فقط
مجموعات البث التي تستوفي الحدّ الأدنى لعدد اللقطات في الثانية الذي يتم تحديده من خلال
StreamComfigurationMap.GetOutputMinFrameDuration(int, Size)
أو
StreamComfigurationMap.GetOutputStallDuration(int, Size)
يختلف الأداء الفعلي من جهاز إلى آخر، إلا أن Android يوفّر بعض
ضمانات لدعم مجموعات معينة بناءً على ثلاثة متغيرات:
نوع الناتج وحجم الناتج ومستوى الأجهزة.
قد يؤدي استخدام مجموعة غير متوافقة من المتغيّرات إلى تحقيق عدد منخفض من اللقطات في الثانية، إذا
ولكن لم يحدث ذلك، سيؤدي ذلك إلى تشغيل إحدى عمليات معاودة الاتصال غير الناجحة.
مستندات createCaptureSession
ويصف ما هو مضمون للعمل.
نوع الإخراج
يشير نوع الإخراج إلى التنسيق الذي يتم به تشفير الإطارات. تشير رسالة الأشكال البيانية
والقيم المحتملة هي PRIV وYUV وJPEG وRAW. الوثائق الخاصة
createCaptureSession
ووصفها.
عند اختيار نوع إخراج التطبيق، إذا كان الهدف هو تعظيم
والتوافق، ثم استخدم
ImageFormat.YUV_420_888
لتحليل الإطار
ImageFormat.JPEG
للصور الثابتة
الصور. بالنسبة لسيناريوهات المعاينة والتسجيل، من المحتمل أن تستخدم
SurfaceView
,
TextureView
،
MediaRecorder
،
MediaCodec
، أو
RenderScript.Allocation
. ضِمن
في هذه الحالات، فلا تحدد تنسيقًا للصورة. للتوافق، سيتم احتسابه كـ
ImageFormat.PRIVATE
,
بصرف النظر عن التنسيق الفعلي المستخدَم داخليًا للاستعلام عن التنسيقات المتوافقة
بواسطة جهاز نظرًا
CameraCharacteristics
,
استخدم التعليمة البرمجية التالية:
Kotlin
val characteristics: CameraCharacteristics = ... val supportedFormats = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).outputFormats
Java
CameraCharacteristics characteristics = …; int[] supportedFormats = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputFormats();
حجم الإخراج
يتم سرد جميع أحجام الإخراج المتاحة حسب
StreamConfigurationMap.getOutputSizes()
،
ولكن هناك اثنان فقط مرتبطان بالتوافق: PREVIEW
وMAXIMUM
. الأحجام
بمثابة حدود قصوى. إذا كان الحجم PREVIEW
مناسبًا، يجب اختيار أي عنصر
سيعمل أيضًا الحجم الأصغر من PREVIEW
. وينطبق ذلك أيضًا على MAXIMUM
. تشير رسالة الأشكال البيانية
وثائق
CameraDevice
يشرح هذه الأحجام.
تعتمد أحجام الإخراج المتاحة على اختيار التنسيق. بناءً على
CameraCharacteristics
وتنسيق، يمكنك الاستعلام عن أحجام الإخراج المتاحة مثل هذا:
Kotlin
val characteristics: CameraCharacteristics = ... val outputFormat: Int = ... // such as ImageFormat.JPEG val sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(outputFormat)
Java
CameraCharacteristics characteristics = …; int outputFormat = …; // such as ImageFormat.JPEG Size[] sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(outputFormat);
استخدِم الفئة المستهدفة لتحديد حالات الاستخدام أثناء معاينة الكاميرا وتسجيلها. الأحجام المعتمدة. سيتم التعامل مع التنسيق من خلال إطار عمل الكاميرا نفسه:
Kotlin
val characteristics: CameraCharacteristics = ... val targetClass: Class <T> = ... // such as SurfaceView::class.java val sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(targetClass)
Java
CameraCharacteristics characteristics = …; int outputFormat = …; // such as ImageFormat.JPEG Size[] sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(outputFormat);
للحصول على الحجم MAXIMUM
، يمكنك ترتيب أحجام النتائج حسب المنطقة وعرض أكبر حجم.
أولاً:
Kotlin
fun <T>getMaximumOutputSize( characteristics: CameraCharacteristics, targetClass: Class <T>, format: Int? = null): Size { val config = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) // If image format is provided, use it to determine supported sizes; or else use target class val allSizes = if (format == null) config.getOutputSizes(targetClass) else config.getOutputSizes(format) return allSizes.maxBy { it.height * it.width } }
Java
@RequiresApi(api = Build.VERSION_CODES.N) <T> Size getMaximumOutputSize(CameraCharacteristics characteristics, Class <T> targetClass, Integer format) { StreamConfigurationMap config = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); // If image format is provided, use it to determine supported sizes; else use target class Size[] allSizes; if (format == null) { allSizes = config.getOutputSizes(targetClass); } else { allSizes = config.getOutputSizes(format); } return Arrays.stream(allSizes).max(Comparator.comparing(s -> s.getHeight() * s.getWidth())).get(); }
تشير السمة PREVIEW
إلى أفضل حجم مطابق لدرجة دقة شاشة الجهاز أو
1080p (1920x1080)، أيهما أصغر. قد لا تتطابق نسبة العرض إلى الارتفاع
نسبة العرض إلى الارتفاع للشاشة بالضبط، لذلك قد تحتاج إلى تطبيق الأشرطة السوداء أو
الاقتصاص إلى ساحة المشاركات لعرضها في وضع ملء الشاشة. الخطوات التي يجب اتّباعها
حجم المعاينة، ومقارنة أحجام المخرجات المتاحة بحجم العرض أثناء
مع الأخذ في الاعتبار إمكانية تدوير الشاشة.
يحدّد الرمز التالي فئة مساعدة، SmartSize
، وستجعل الحجم
المقارنات أسهل قليلاً:
Kotlin
/** Helper class used to pre-compute shortest and longest sides of a [Size] */ class SmartSize(width: Int, height: Int) { var size = Size(width, height) var long = max(size.width, size.height) var short = min(size.width, size.height) override fun toString() = "SmartSize(${long}x${short})" } /** Standard High Definition size for pictures and video */ val SIZE_1080P: SmartSize = SmartSize(1920, 1080) /** Returns a [SmartSize] object for the given [Display] */ fun getDisplaySmartSize(display: Display): SmartSize { val outPoint = Point() display.getRealSize(outPoint) return SmartSize(outPoint.x, outPoint.y) } /** * Returns the largest available PREVIEW size. For more information, see: * https://d.android.com/reference/android/hardware/camera2/CameraDevice */ fun <T>getPreviewOutputSize( display: Display, characteristics: CameraCharacteristics, targetClass: Class <T>, format: Int? = null ): Size { // Find which is smaller: screen or 1080p val screenSize = getDisplaySmartSize(display) val hdScreen = screenSize.long >= SIZE_1080P.long || screenSize.short >= SIZE_1080P.short val maxSize = if (hdScreen) SIZE_1080P else screenSize // If image format is provided, use it to determine supported sizes; else use target class val config = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!! if (format == null) assert(StreamConfigurationMap.isOutputSupportedFor(targetClass)) else assert(config.isOutputSupportedFor(format)) val allSizes = if (format == null) config.getOutputSizes(targetClass) else config.getOutputSizes(format) // Get available sizes and sort them by area from largest to smallest val validSizes = allSizes .sortedWith(compareBy { it.height * it.width }) .map { SmartSize(it.width, it.height) }.reversed() // Then, get the largest output size that is smaller or equal than our max size return validSizes.first { it.long <= maxSize.long && it.short <= maxSize.short }.size }
Java
/** Helper class used to pre-compute shortest and longest sides of a [Size] */ class SmartSize { Size size; double longSize; double shortSize; public SmartSize(Integer width, Integer height) { size = new Size(width, height); longSize = max(size.getWidth(), size.getHeight()); shortSize = min(size.getWidth(), size.getHeight()); } @Override public String toString() { return String.format("SmartSize(%sx%s)", longSize, shortSize); } } /** Standard High Definition size for pictures and video */ SmartSize SIZE_1080P = new SmartSize(1920, 1080); /** Returns a [SmartSize] object for the given [Display] */ SmartSize getDisplaySmartSize(Display display) { Point outPoint = new Point(); display.getRealSize(outPoint); return new SmartSize(outPoint.x, outPoint.y); } /** * Returns the largest available PREVIEW size. For more information, see: * https://d.android.com/reference/android/hardware/camera2/CameraDevice */ @RequiresApi(api = Build.VERSION_CODES.N) <T> Size getPreviewOutputSize( Display display, CameraCharacteristics characteristics, Class <T> targetClass, Integer format ){ // Find which is smaller: screen or 1080p SmartSize screenSize = getDisplaySmartSize(display); boolean hdScreen = screenSize.longSize >= SIZE_1080P.longSize || screenSize.shortSize >= SIZE_1080P.shortSize; SmartSize maxSize; if (hdScreen) { maxSize = SIZE_1080P; } else { maxSize = screenSize; } // If image format is provided, use it to determine supported sizes; else use target class StreamConfigurationMap config = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (format == null) assert(StreamConfigurationMap.isOutputSupportedFor(targetClass)); else assert(config.isOutputSupportedFor(format)); Size[] allSizes; if (format == null) { allSizes = config.getOutputSizes(targetClass); } else { allSizes = config.getOutputSizes(format); } // Get available sizes and sort them by area from largest to smallest List <Size> sortedSizes = Arrays.asList(allSizes); List <SmartSize> validSizes = sortedSizes.stream() .sorted(Comparator.comparing(s -> s.getHeight() * s.getWidth())) .map(s -> new SmartSize(s.getWidth(), s.getHeight())) .sorted(Collections.reverseOrder()).collect(Collectors.toList()); // Then, get the largest output size that is smaller or equal than our max size return validSizes.stream() .filter(s -> s.longSize <= maxSize.longSize && s.shortSize <= maxSize.shortSize) .findFirst().get().size; }
التحقق من مستوى الأجهزة المتوافقة
لتحديد الإمكانات المتاحة في وقت التشغيل، يُرجى التحقق من الأجهزة المتوافقة.
باستخدام المستوى
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL
مع
CameraCharacteristics
يمكنك استرداد مستوى الجهاز باستخدام عبارة واحدة:
Kotlin
val characteristics: CameraCharacteristics = ... // Hardware level will be one of: // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3 val hardwareLevel = characteristics.get( CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
Java
CameraCharacteristics characteristics = ...; // Hardware level will be one of: // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3 Integer hardwareLevel = characteristics.get( CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
وضع كل القطع معًا
ومن خلال نوع الإخراج وحجم الإخراج ومستوى الأجهزة، يمكنك تحديد
مجموعات البث صالحة. المخطط التالي هو لمحة عن
الإعدادات المتاحة في CameraDevice
LEGACY
مستوى الأجهزة.
الهدف 1 | الهدف 2 | الهدف 3 | أمثلة على حالات الاستخدام | |||
---|---|---|---|---|---|---|
النوع | أقصى حجم | النوع | أقصى حجم | النوع | أقصى حجم | |
PRIV |
MAXIMUM |
معاينة بسيطة أو معالجة الفيديو باستخدام وحدة معالجة الرسومات أو تسجيل الفيديو بدون معاينة | ||||
JPEG |
MAXIMUM |
التقاط صورة ثابتة بدون عدسة الكاميرا | ||||
YUV |
MAXIMUM |
معالجة الفيديوهات أو الصور داخل التطبيق | ||||
PRIV |
PREVIEW |
JPEG |
MAXIMUM |
التصوير الثابت العادي. | ||
YUV |
PREVIEW |
JPEG |
MAXIMUM |
المعالجة داخل التطبيق بالإضافة إلى التقاط الصور الثابتة | ||
PRIV |
PREVIEW |
PRIV |
PREVIEW |
تسجيل عادي | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
المعاينة والمعالجة داخل التطبيق | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
المعاينة والمعالجة داخل التطبيق | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
JPEG |
MAXIMUM |
لا يزال التصوير مستمرًا وعملية المعالجة داخل التطبيق. |
LEGACY
هو أدنى مستوى ممكن للأجهزة. يوضح هذا الجدول أن كل
جهاز متوافق مع Camera2 (المستوى 21 من واجهة برمجة التطبيقات والمستويات الأعلى) يمكن أن يؤدي إلى إخراج ما يصل إلى ثلاثة أجهزة
أحداث بث متزامنة باستخدام الإعدادات المناسبة، وإذا لم يكن عدد فيديوهاتها كبيرًا
الحد من الأداء الذي يحد من الأداء، مثل الذاكرة أو وحدة المعالجة المركزية (CPU) أو القيود الحرارية.
يحتاج تطبيقك أيضًا إلى ضبط المخازن المؤقتة للمخرجات المستهدفة. على سبيل المثال، بهدف
استهداف جهاز بمستوى جهاز واحد (LEGACY
)، يمكنك إعداد إخراجين مستهدَفين
مساحة العرض، إحداهما تستخدم "ImageFormat.PRIVATE
" والأخرى باستخدام
ImageFormat.YUV_420_888
هذه تركيبة مسموح بها أثناء استخدام
حجم واحد (PREVIEW
). باستخدام الدالة المحددة مسبقًا في هذا الموضوع، يمكن أن يكون الحصول على
لأحجام المعاينة المطلوبة لمعرّف الكاميرا، يجب استخدام الرمز التالي:
Kotlin
val characteristics: CameraCharacteristics = ... val context = this as Context // assuming you are inside of an activity val surfaceViewSize = getPreviewOutputSize( context, characteristics, SurfaceView::class.java) val imageReaderSize = getPreviewOutputSize( context, characteristics, ImageReader::class.java, format = ImageFormat.YUV_420_888)
Java
CameraCharacteristics characteristics = ...; Context context = this; // assuming you are inside of an activity Size surfaceViewSize = getPreviewOutputSize( context, characteristics, SurfaceView.class); Size imageReaderSize = getPreviewOutputSize( context, characteristics, ImageReader.class, format = ImageFormat.YUV_420_888);
يتطلب الأمر الانتظار حتى يصبح SurfaceView
جاهزًا باستخدام طلبات معاودة الاتصال المتوفرة:
Kotlin
val surfaceView = findViewById <SurfaceView>(...) surfaceView.holder.addCallback(object : SurfaceHolder.Callback { override fun surfaceCreated(holder: SurfaceHolder) { // You do not need to specify image format, and it will be considered of type PRIV // Surface is now ready and you could use it as an output target for CameraSession } ... })
Java
SurfaceView surfaceView = findViewById <SurfaceView>(...); surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) { // You do not need to specify image format, and it will be considered of type PRIV // Surface is now ready and you could use it as an output target for CameraSession } ... });
يمكنك فرض مطابقة SurfaceView
لحجم إخراج الكاميرا من خلال إجراء مكالمة.
SurfaceHolder.setFixedSize()
أو يمكنك اتباع نهج مشابه
AutoFitSurfaceView
من الشائعة
الوحدة
عينات الكاميرا على GitHub، والذي يضبط حجمًا مطلقًا، مع
مع مراعاة كل من نسبة العرض إلى الارتفاع والمساحة المتاحة، بينما يتم
تعديل وقت بدء تغييرات النشاط.
يعد إعداد السطح الآخر من
ImageReader
بالتنسيق المطلوب هو
لأنّه لا تتوفّر أي طلبات معاودة الاتصال لانتظارها:
Kotlin
val frameBufferCount = 3 // just an example, depends on your usage of ImageReader val imageReader = ImageReader.newInstance( imageReaderSize.width, imageReaderSize.height, ImageFormat.YUV_420_888, frameBufferCount)
Java
int frameBufferCount = 3; // just an example, depends on your usage of ImageReader ImageReader imageReader = ImageReader.newInstance( imageReaderSize.width, imageReaderSize.height, ImageFormat.YUV_420_888, frameBufferCount);
عند استخدام مخزن مؤقت مستهدَف للحظر مثل ImageReader
، تجاهل الإطارات بعد ذلك
باستخدامها:
Kotlin
imageReader.setOnImageAvailableListener({ val frame = it.acquireNextImage() // Do something with "frame" here it.close() }, null)
Java
imageReader.setOnImageAvailableListener(listener -> { Image frame = listener.acquireNextImage(); // Do something with "frame" here listener.close(); }, null);
يستهدف مستوى الأجهزة LEGACY
الأجهزة ذات القاسم المشترك الأقل. يمكنك
إضافة تفريع شرطي واستخدام حجم RECORD
لأحد الناتج المستهدف
الأجهزة التي تتضمّن مستوى جهاز LIMITED
، أو يمكن زيادتها إلى
الحجم MAXIMUM
للأجهزة التي تتضمّن مستوى أجهزة FULL
.