توجه: این صفحه به بسته Camera2 اشاره دارد. توصیه میکنیم از CameraX استفاده کنید، مگراینکه برنامهتان به ویژگیهای خاص و سطح پایین Camera2 نیاز داشته باشد. CameraX و Camera2 هر دو از Android 5.0 (سطح میانای برنامهسازی کاربردی ۲۱) و بالاتر پشتیبانی میکنند.
برنامه دوربین میتواند بهطور همزمان از بیشاز یک جریان فریم استفاده کند. در برخی موارد، جاریسازیهای مختلف حتی به وضوح قاب یا قالب پیکسل متفاوتی نیاز دارند. برخیاز موارد استفاده معمول عبارتاند از:
- ضبط ویدیو: یک جاریسازی برای پیشنمایش، و جاریسازی دیگری که کدگذاری و در فایل ذخیره میشود.
- اسکن بارکد: یک جاریسازی برای پیشنمایش، دیگری برای تشخیص بارکد.
- عکاسی رایانشی: یک جاریسازی برای پیشنمایش، دیگری برای تشخیص چهره/صحنه.
هنگام پردازش فریمها، هزینه عملکرد غیرجزئی وجود دارد و این هزینه هنگام انجام پردازش موازی جاریسازی یا خط لوله چندبرابر میشود.
منابعی مانند CPU، GPU، و DSP ممکن است بتوانند از قابلیتهای پردازش مجدد چارچوب بهره ببرند، اما منابعی مانند حافظه بهصورت خطی رشد خواهند کرد.
چندین هدف در هر درخواست
میتوان چند جاریسازی دوربین را در یک
CameraCaptureRequest ترکیب کرد.
تکهکد زیر نحوه راهاندازی جلسه دوربین را با یک جاریسازی برای پیشنمایش دوربین و جاریسازی دیگری برای پردازش تصویر نشان میدهد:
کاتلین
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)
جاوا
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);
اگر سطوح هدف را بهدرستی پیکربندی کنید، این کد فقط
جاریسازیهایی را تولید میکند که حداقل FPS تعیینشده توسط
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،
از کد زیر استفاده کنید:
کاتلین
val characteristics: CameraCharacteristics = ... val supportedFormats = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).outputFormats
جاوا
CameraCharacteristics characteristics = …; int[] supportedFormats = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputFormats();
اندازه برونداد
همه اندازههای خروجی دردسترس توسط
StreamConfigurationMap.getOutputSizes() فهرست شدهاند،
اما فقط دو مورد به سازگاری مربوط میشوند: PREVIEW و MAXIMUM. اندازهها
بهعنوان کرانهای بالا عمل میکنند. اگر چیزی با اندازه PREVIEW کار کند، هر چیزی با
اندازه کوچکتر از PREVIEW نیز کار خواهد کرد. این موضوع برای MAXIMUM نیز صادق است. سند مربوط به
CameraDevice
این اندازهها را توضیح میدهد.
اندازههای خروجی دردسترس به قالب انتخابی بستگی دارد. با داشتن
CameraCharacteristics
و یک قالب، میتوانید اندازههای خروجی دردسترس را بهاینصورت پُرسمان کنید:
کاتلین
val characteristics: CameraCharacteristics = ... val outputFormat: Int = ... // such as ImageFormat.JPEG val sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(outputFormat)
جاوا
CameraCharacteristics characteristics = …; int outputFormat = …; // such as ImageFormat.JPEG Size[] sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(outputFormat);
در موارد استفاده از پیشنمایش و ضبط دوربین، از کلاس هدف برای تعیین اندازههای پشتیبانیشده استفاده کنید. قالب توسط خود چارچوب دوربین مدیریت خواهد شد:
کاتلین
val characteristics: CameraCharacteristics = ... val targetClass: Class <T> = ... // such as SurfaceView::class.java val sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(targetClass)
جاوا
CameraCharacteristics characteristics = …; int outputFormat = …; // such as ImageFormat.JPEG Size[] sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(outputFormat);
برای دریافت اندازه MAXIMUM، اندازههای خروجی را براساس مساحت مرتب کنید و بزرگترین را برگردانید:
کاتلین
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 } }
جاوا
@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 به بهترین اندازه مطابق با وضوح صفحهنمایش دستگاه یا
۱۰۸۰ پیکسل (۱۹۲۰×۱۰۸۰)، هرکدام که کوچکتر باشد، اشاره دارد. ممکن است نسبت ابعادی دقیقاً با نسبت ابعادی صفحهنمایش مطابقت نداشته باشد، بنابراین ممکن است لازم باشد برای نمایش آن در حالت تمامصفحه، از جعبهبندی یا برش در جاریسازی استفاده کنید. برای دریافت اندازه پیشنمایش مناسب، اندازههای خروجی دردسترس را با اندازه نمایش مقایسه کنید و درعینحال توجه داشته باشید که ممکن است نمایشگر چرخانده شود.
کد زیر کلاس کمکی SmartSize را تعریف میکند که مقایسه اندازه را کمی آسانتر میکند:
کاتلین
/** 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 }
جاوا
/** 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
میتوانید سطح سختافزار را با یک عبارت بازیابی کنید:
کاتلین
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)
جاوا
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 است.
| هدف ۱ | هدف ۲ | هدف ۳ | مورد(های) استفاده نمونه | |||
|---|---|---|---|---|---|---|
| نوع | حداکثر اندازه | نوع | حداکثر اندازه | نوع | حداکثر اندازه | |
PRIV |
MAXIMUM |
پیشنمایش ساده، پردازش ویدیو GPU، یا ضبط ویدیو بدون پیشنمایش. | ||||
JPEG |
MAXIMUM |
ضبط تصویر ثابت بدون نمایاب. | ||||
YUV |
MAXIMUM |
پردازش ویدیو/تصویر درونبرنامه. | ||||
PRIV |
PREVIEW |
JPEG |
MAXIMUM |
تصویربرداری ثابت استاندارد. | ||
YUV |
PREVIEW |
JPEG |
MAXIMUM |
پردازش درونبرنامه بهعلاوه ثبت عکس ثابت. | ||
PRIV |
PREVIEW |
PRIV |
PREVIEW |
ضبط استاندارد. | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
پیشنمایش بهعلاوه پردازش درونبرنامه. | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
JPEG |
MAXIMUM |
ضبط عکس بهعلاوه پردازش درونبرنامه. |
LEGACY پایینترین سطح سختافزار ممکن است. این جدول نشان میدهد که هر دستگاهی که از Camera2 (سطح میانای برنامهسازی کاربردی ۲۱ و بالاتر) پشتیبانی میکند میتواند بااستفاده از پیکربندی مناسب و درصورت عدم وجود سربار بیشازحد که عملکرد را محدود کند (مانند محدودیتهای حافظه، CPU، یا حرارتی)، تا سه جریان همزمان را خروجی دهد.
برنامه شما همچنین باید میانگیرهای خروجی هدفیابی را پیکربندی کند. برای مثال، برای هدفیابی دستگاهی با سطح سختافزار LEGACY، میتوانید دو سطح خروجی هدف راهاندازی کنید، یکی بااستفاده از ImageFormat.PRIVATE و دیگری بااستفاده از ImageFormat.YUV_420_888. این ترکیب هنگام استفاده از اندازه PREVIEW پشتیبانی میشود. بااستفاده از تابع تعریفشده در ابتدای این موضوع، دریافت
اندازههای پیشنمایش موردنیاز برای شناسه دوربین به کد زیر نیاز دارد:
کاتلین
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)
جاوا
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 بااستفاده از فراخوانیهای برگشتی ارائهشده آماده شود:
کاتلین
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 } ... })
جاوا
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 } ... });
میتوانید با فراخوانی
SurfaceHolder.setFixedSize()
SurfaceView را مجبور کنید با اندازه خروجی دوربین مطابقت داشته باشد
یا میتوانید رویکردی مشابه
AutoFitSurfaceView از
ماژول Common
نمونههای دوربین در GitHub اتخاذ کنید که اندازه مطلق را تنظیم میکند و
هم نسبت ابعادی و هم فضای دردسترس را درنظر میگیرد، درحالیکه بهطور خودکار
هنگام راهاندازی تغییرات فعالیت تنظیم میشود.
راهاندازی سطح دیگر از
ImageReader با قالب مطلوب
آسانتر است، زیرا هیچ فراخوانیای وجود ندارد که منتظرش بمانید:
کاتلین
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)
جاوا
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، فریمها را پساز استفاده دور بیندازید:
کاتلین
imageReader.setOnImageAvailableListener({ val frame = it.acquireNextImage() // Do something with "frame" here it.close() }, null)
جاوا
imageReader.setOnImageAvailableListener(listener -> { Image frame = listener.acquireNextImage(); // Do something with "frame" here listener.close(); }, null);
سطح سختافزار LEGACY دستگاههای دارای پایینترین مخرج مشترک را هدف قرار میدهد. میتوانید
انشعاب شرطی اضافه کنید و از اندازه RECORD برای یکی از سطوح هدف خروجی
در دستگاههای دارای سطح سختافزار LIMITED استفاده کنید، یا حتی آن را به
اندازه MAXIMUM برای دستگاههای دارای سطح سختافزار FULL افزایش دهید.