توجه: این صفحه به بسته 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 basic 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 basic 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 افزایش دهید.