توجه: این صفحه به پکیج Camera2 اشاره دارد. توصیه میکنیم از CameraX استفاده کنید، مگر اینکه برنامه شما به ویژگیهای خاص و سطح پایین Camera2 نیاز داشته باشد. هر دو CameraX و Camera2 از اندروید 5.0 (سطح API 21) و بالاتر پشتیبانی می کنند.
یک برنامه دوربین می تواند از بیش از یک جریان فریم به طور همزمان استفاده کند. در برخی موارد، جریان های مختلف حتی به وضوح فریم یا فرمت پیکسل متفاوتی نیاز دارند. برخی از موارد استفاده معمولی عبارتند از:
- ضبط ویدئو : یک جریان برای پیش نمایش، دیگری در حال کدگذاری و ذخیره در یک فایل.
- اسکن بارکد : یک جریان برای پیش نمایش، دیگری برای تشخیص بارکد.
- عکاسی محاسباتی : یک جریان برای پیش نمایش، دیگری برای تشخیص چهره/صحنه.
هنگام پردازش فریم ها، هزینه عملکرد غیر پیش پا افتاده ای وجود دارد و هنگام انجام پردازش جریان موازی یا خط لوله، هزینه آن چند برابر می شود.
منابعی مانند 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
به بهترین اندازه مطابقت با وضوح صفحه نمایش دستگاه یا 1080p (1920x1080)، هر کدام کوچکتر اشاره دارد. نسبت تصویر ممکن است دقیقاً با نسبت ابعاد صفحه نمایش مطابقت نداشته باشد، بنابراین ممکن است لازم باشد برای نمایش در حالت تمام صفحه، حروف-باکس یا برش را روی جریان اعمال کنید. برای به دست آوردن اندازه پیش نمایش مناسب، اندازه های خروجی موجود را با اندازه نمایش مقایسه کنید و در عین حال در نظر بگیرید که نمایشگر ممکن است چرخش داشته باشد.
کد زیر یک کلاس کمکی به نام 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
بررسی کنید.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
است.
هدف 1 | هدف 2 | هدف 3 | نمونه مورد استفاده | |||
---|---|---|---|---|---|---|
تایپ کنید | حداکثر اندازه | تایپ کنید | حداکثر اندازه | تایپ کنید | حداکثر اندازه | |
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 | پیش نمایش به علاوه پردازش درون برنامه ای. | ||
PRIV | PREVIEW | YUV | PREVIEW | JPEG | MAXIMUM | همچنان ضبط به علاوه پردازش درون برنامه ای. |
LEGACY
پایین ترین سطح سخت افزاری ممکن است. این جدول نشان میدهد که هر دستگاهی که از Camera2 (سطح API 21 و بالاتر) پشتیبانی میکند، میتواند تا سه جریان همزمان را با استفاده از پیکربندی مناسب و در صورتی که عملکرد محدودکننده سربار زیادی مانند حافظه، 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
از ماژول مشترک نمونههای دوربین در 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
استفاده کنید، یا حتی برای دستگاههایی با سطح سختافزار FULL
آن را به MAXIMUM
اندازه افزایش دهید.