توجه: این صفحه به بسته Camera2 اشاره دارد. توصیه میکنیم از CameraX استفاده کنید، مگراینکه برنامهتان به ویژگیهای خاص و سطح پایین Camera2 نیاز داشته باشد. CameraX و Camera2 هر دو از Android 5.0 (سطح میانای برنامهسازی کاربردی ۲۱) و بالاتر پشتیبانی میکنند.
چنددوربینی با Android 9 (سطح میانای برنامهسازی کاربردی ۲۸) معرفی شد. از زمان انتشار آن، دستگاههایی که از این API پشتیبانی میکنند به بازار آمدهاند. بسیاری از موارد استفاده از چند دوربین بهطور محکم با پیکربندی سختافزاری خاصی مرتبط هستند. بهعبارت دیگر، همه موارد استفاده با همه دستگاهها سازگار نیستند، که این امر ویژگیهای چنددوربینی را به گزینه خوبی برای ویژگی تحویل Play تبدیل میکند.
برخیاز موارد استفاده معمول عبارتاند از:
- زوم: جابهجایی بین دوربینها بسته به منطقه برش یا فاصله کانونی انتخابشده.
- عمق: استفاده از چند دوربین برای ساختن نقشه عمق.
- بوکه: استفاده از اطلاعات عمق استنباطشده برای شبیهسازی دامنه فوکوس باریک شبیه DSLR.
تفاوت بین دوربینهای منطقی و فیزیکی
برای درک میانای برنامهسازی کاربردی چنددوربینه باید تفاوت بین دوربینهای منطقی و فیزیکی را درک کنید. برای مثال، دستگاهی را با سه دوربین پشت درنظر بگیرید. در این مثال، هرکدام از سه دوربین پشت دوربین فیزیکی درنظر گرفته میشود. دوربین منطقی درواقع گروهی از دو یا چند دوربین فیزیکی است. خروجی دوربین منطقی میتواند جریانی باشد که از یکی از دوربینهای فیزیکی زیرین میآید، یا جریانی ادغامشده که بهطور همزمان از بیش از یک دوربین فیزیکی زیرین میآید. در هر دو حالت، جاریسازی توسط «لایه انتزاعی سختافزار» (HAL) دوربین مدیریت میشود.
بسیاری از سازندگان تلفن برنامههای دوربین طرف اول را توسعه میدهند که معمولاً بهصورت ازپیشنصبشده در دستگاههایشان ارائه میشود. برای استفاده از همه قابلیتهای سختافزار، ممکن است از میاناهای برنامهسازی کاربردی خصوصی یا پنهان استفاده کنند یا از پیادهسازی درایور رفتار ویژهای دریافت کنند که برنامههای دیگر به آن دسترسی ندارند. برخیاز دستگاهها مفهوم دوربینهای منطقی را با ارائه جاریسازی ادغامشدهای از فریمها از دوربینهای فیزیکی مختلف پیادهسازی میکنند، اما فقط برای برخیاز برنامههای دارای امتیاز. اغلب، فقط یکی از دوربینهای فیزیکی درمعرض چارچوب قرار میگیرد. وضعیت توسعهدهندگان طرف سوم قبلاز Android 9 در نمودار زیر نشان داده شده است:
از Android 9 بهبعد، میاناهای برنامهسازی کاربردی خصوصی دیگر در برنامههای Android مجاز نیستند. با گنجاندن پشتیبانی از چند دوربین در چارچوب، روالهای مطلوب Android اکیداً توصیه میکنند که سازندگان تلفن یک دوربین منطقی برای همه دوربینهای فیزیکی که در یک جهت قرار دارند ارائه دهند. توسعهدهندگان طرف سوم باید انتظار داشته باشند که موارد زیر را در دستگاههای دارای Android نسخه ۹ و بالاتر ببینند:
آنچه دوربین منطقی ارائه میدهد کاملاً به پیادهسازی OEM از HAL دوربین بستگی دارد. برای مثال، دستگاهی مثل Pixel 3 دوربین منطقی خود را بهگونهای پیادهسازی میکند که یکی از دوربینهای فیزیکی خود را براساس فاصله کانونی و منطقه برش درخواستشده انتخاب میکند.
میانای برنامهسازی کاربردی چنددوربینی
میانای برنامهسازی کاربردی جدید ثابتها، کلاسها، و روشهای جدید زیر را اضافه میکند:
CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERACameraCharacteristics.getPhysicalCameraIds()CameraCharacteristics.getAvailablePhysicalCameraRequestKeys()CameraDevice.createCaptureSession(SessionConfiguration config)CameraCharacteritics.LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE-
OutputConfigurationوSessionConfiguration
بهدلیل تغییرات در «سند تعریف سازگاری Android» (CDD)، میانای برنامهسازی کاربردی چنددوربینه نیز با انتظارات خاصی از توسعهدهندگان همراه است. دستگاههای دارای دوربین دوگانه قبلاز Android 9 وجود داشتند، اما باز کردن بیشاز یک دوربین بهطور همزمان شامل آزمون و خطا بود. در Android نسخه ۹ و بالاتر، چنددوربین مجموعهای از قوانین را برای مشخص کردن زمان امکان باز کردن جفت دوربین فیزیکی که بخشی از یک دوربین منطقی هستند ارائه میدهد.
در بیشتر موارد، دستگاههای دارای Android نسخه ۹ و بالاتر همه دوربینهای فیزیکی را (بهجز احتمالاً انواع حسگرهای کمتر رایج مانند مادون قرمز) بههمراه یک دوربین منطقی با استفاده آسانتر نشان میدهند. برای هر ترکیبی از جاریسازیها که تضمین میشود کار کنند، یک جاریسازی متعلق به دوربین منطقی میتواند با دو جاریسازی از دوربینهای فیزیکی زیرین جایگزین شود.
چندین جاریسازی همزمان
استفاده همزمان از چند جاریسازی دوربین
قوانین استفاده همزمان از چند جاریسازی در یک دوربین را پوشش میدهد. با یک
افزودنی قابلتوجه، همان قوانین برای چند دوربین اعمال میشود.
CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA
نحوه جایگزین کردن جریان منطقی YUV_420_888 یا خام را با دو جریان فیزیکی توضیح میدهد. یعنی هر جاریسازی از نوع YUV یا RAW را میتوان با دو جاریسازی از نوع و اندازه یکسان جایگزین کرد. میتوانید با جاریسازی دوربین با پیکربندی تضمینشده زیر برای دستگاههای تکدوربینه شروع کنید:
- جاریسازی ۱: نوع YUV، اندازه
MAXIMUMاز دوربین منطقیid = 0
سپس، دستگاهی با پشتیبانی از چند دوربین به شما امکان میدهد جلسهای ایجاد کنید که آن جاریسازی منطقی YUV را با دو جاریسازی فیزیکی جایگزین میکند:
- جاریسازی ۱: نوع YUV، اندازه
MAXIMUMاز دوربین فیزیکیid = 1 - جاریسازی ۲: نوع YUV، اندازه
MAXIMUMاز دوربین فیزیکیid = 2
میتوانید یک جاریسازی YUV یا RAW را با دو جاریسازی معادل جایگزین کنید، اگر و تنها اگر
آن دو دوربین بخشی از یک گروه دوربین منطقی باشند، که در زیر
CameraCharacteristics.getPhysicalCameraIds() فهرست شده است.
تضمینهای ارائهشده توسط چارچوب فقط حداقل الزامات لازم برای دریافت همزمان قابها از بیشاز یک دوربین فیزیکی است. جاریسازیهای اضافی در اکثر دستگاهها پشتیبانی میشود و گاهی حتی امکان باز کردن مستقل چندین دستگاه دوربین فیزیکی را فراهم میکند. ازآنجاییکه این تضمین قطعی ازسوی چارچوب نیست، انجام این کار مستلزم آزمایش و تنظیم برای هر دستگاه بااستفاده از آزمون و خطا است.
ایجاد جلسه با چند دوربین فیزیکی
هنگام استفاده از دوربینهای فیزیکی در دستگاهی که چند دوربین دارد، یک
CameraDevice (دوربین منطقی) را باز کنید و در یک جلسه با آن تعامل داشته باشید. ایجاد کردن جلسه تکی بااستفاده از میانای برنامهسازی کاربردی
CameraDevice.createCaptureSession(SessionConfiguration config)، که در
سطح ۲۸ میانای برنامهسازی کاربردی اضافه شده است. پیکربندی جلسه تعدادی پیکربندی برونداد دارد که هرکدام مجموعهای از هدفهای برونداد و، بهصورت اختیاری، شناسه دوربین فیزیکی انتخابشدهای دارند.
درخواستهای ضبط دارای یک هدف خروجی مرتبط با آنها هستند. چارچوب تعیین میکند که درخواستها براساس هدف خروجی پیوستشده به کدام دوربین فیزیکی (یا منطقی) ارسال شوند. اگر هدف خروجی با یکی از اهداف خروجی که بهعنوان پیکربندی خروجی همراه با شناسه دوربین فیزیکی ارسال شده است مطابقت داشته باشد، آن دوربین فیزیکی درخواست را دریافت و پردازش میکند.
استفاده از یک جفت دوربین فیزیکی
یکی دیگر از موارد اضافهشده به میاناهای برنامهسازی کاربردی دوربین برای چنددوربین، توانایی شناسایی دوربینهای منطقی و پیدا کردن دوربینهای فیزیکی پشت آنها است. میتوانید تابعی را تعریف کنید که به شناسایی جفتهای احتمالی دوربینهای فیزیکی کمک کند تا بتوانید از آنها برای جایگزینی یکی از جاریسازیهای دوربین منطقی استفاده کنید:
/** * Helper class used to encapsulate a logical camera and two underlying * physical cameras */ data class DualCamera(val logicalId: String, val physicalId1: String, val physicalId2: String) fun findDualCameras(manager: CameraManager, facing: Int? = null): List<DualCamera> { val dualCameras = mutableListOf<DualCamera>() // Iterate over all the available camera characteristics. manager.cameraIdList.map { Pair(manager.getCameraCharacteristics(it), it) }.filter { // Filter by cameras facing the requested direction. facing == null || it.first.get(CameraCharacteristics.LENS_FACING) == facing }.filter { // Filter by logical cameras. // CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA requires API >= 28. it.first.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)?.contains( CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA ) ?: false }.forEach { // All possible pairs from the list of physical cameras are valid results. // NOTE: There could be N physical cameras as part of a logical camera grouping. // getPhysicalCameraIds() requires API >= 28. val physicalCameras = it.first.physicalCameraIds.toTypedArray() for (idx1 in 0 until physicalCameras.size) { for (idx2 in (idx1 + 1) until physicalCameras.size) { dualCameras.add( DualCamera( it.second, physicalCameras[idx1], physicalCameras[idx2] ) ) } } } return dualCameras }
مدیریت وضعیت دوربینهای فیزیکی توسط دوربین منطقی کنترل میشود. برای باز کردن «دوربین دوگانه»، دوربین منطقی مربوط به دوربینهای فیزیکی را باز کنید:
fun openDualCamera( cameraManager: CameraManager, dualCamera: DualCamera, executor: Executor, callback: (CameraDevice) -> Unit ) { // openCamera() requires API >= 28. cameraManager.openCamera( dualCamera.logicalId, executor, object : CameraDevice.StateCallback() { override fun onOpened(device: CameraDevice) = callback(device) // Omitting for brevity... override fun onError(device: CameraDevice, error: Int) = onDisconnected(device) override fun onDisconnected(device: CameraDevice) = device.close() } ) }
علاوهبر انتخاب دوربین برای باز کردن، این فرایند مشابه باز کردن دوربین در نسخههای قبلی Android است. ایجاد یک جلسه ضبط بااستفاده از API جدید پیکربندی جلسه به چارچوب میگوید که اهداف خاصی را با شناسههای دوربین فیزیکی خاص مرتبط کند:
/** * Helper type definition that encapsulates 3 sets of output targets: * * 1. Logical camera * 2. First physical camera * 3. Second physical camera */ typealias DualCameraOutputs = Triple<MutableList<Surface>?, MutableList<Surface>?, MutableList<Surface>?> fun createDualCameraSession( cameraManager: CameraManager, dualCamera: DualCamera, targets: DualCameraOutputs, executor: Executor, callback: (CameraCaptureSession) -> Unit ) { // Create 3 sets of output configurations: one for the logical camera, and // one for each of the physical cameras. val outputConfigsLogical = targets.first?.map { OutputConfiguration(it) } val outputConfigsPhysical1 = targets.second?.map { OutputConfiguration(it).apply { setPhysicalCameraId(dualCamera.physicalId1) } } val outputConfigsPhysical2 = targets.third?.map { OutputConfiguration(it).apply { setPhysicalCameraId(dualCamera.physicalId2) } } // Put all the output configurations into a single flat array. val outputConfigsAll = arrayOf( outputConfigsLogical, outputConfigsPhysical1, outputConfigsPhysical2 ) .filterNotNull().flatMap { it } // Instantiate a session configuration that can be used to create a session. val sessionConfiguration = SessionConfiguration( SessionConfiguration.SESSION_REGULAR, outputConfigsAll, executor, object : CameraCaptureSession.StateCallback() { override fun onConfigured(session: CameraCaptureSession) = callback(session) // Omitting for brevity... override fun onConfigureFailed(session: CameraCaptureSession) = session.device.close() } ) // Open the logical camera using the previously defined function. openDualCamera(cameraManager, dualCamera, executor = executor) { // Finally create the session and return using the callback. it.createCaptureSession(sessionConfiguration) } }
برای کسب اطلاعات درباره اینکه کدام ترکیب از جاریسازیها پشتیبانی میشود، createCaptureSession را ببینید. ترکیب کردن جاریسازیها برای چند جاریسازی
در یک دوربین منطقی واحد است. سازگاری به استفاده از همان پیکربندی و جایگزین کردن یکی از آن جاریسازیها با دو جاریسازی از دو دوربین فیزیکی که بخشی از همان دوربین منطقی هستند گسترش مییابد.
وقتی جلسه دوربین آماده شد، درخواستهای ضبط انتخابشده را ارسال کنید. هر هدف درخواست ضبط، دادههایش را از دوربین فیزیکی مرتبط با آن (درصورت استفاده) دریافت میکند، یا به دوربین منطقی برمیگردد.
مورد استفاده نمونه Zoom
میتوان دوربینهای فیزیکی را در یک جاریسازی واحد ادغام کرد تا کاربران بتوانند بین دوربینهای فیزیکی مختلف جابهجا شوند و میدان دید متفاوتی را تجربه کنند و بهطور مؤثری «سطح بزرگنمایی» متفاوتی را ثبت کنند.
با انتخاب جفت دوربین فیزیکی شروع کنید تا کاربران بتوانند بین آنها جابهجا شوند. برای حداکثر تأثیر، میتوانید جفت دوربینی را انتخاب کنید که حداقل و حداکثر فاصله کانونی دردسترس را ارائه میدهند.
fun findShortLongCameraPair(manager: CameraManager, facing: Int? = null): DualCamera? { return findDualCameras(manager, facing).map { val characteristics1 = manager.getCameraCharacteristics(it.physicalId1) val characteristics2 = manager.getCameraCharacteristics(it.physicalId2) // Query the focal lengths advertised by each physical camera. val focalLengths1 = characteristics1.get( CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS ) ?: floatArrayOf(0F) val focalLengths2 = characteristics2.get( CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS ) ?: floatArrayOf(0F) // Compute the largest difference between min and max focal lengths between cameras. val focalLengthsDiff1 = (focalLengths2.maxOrNull() ?: 0f) - (focalLengths1.minOrNull() ?: 0f) val focalLengthsDiff2 = (focalLengths1.maxOrNull() ?: 0f) - (focalLengths2.minOrNull() ?: 0f) // Return the pair of camera IDs and the difference between min and max focal lengths. if (focalLengthsDiff1 < focalLengthsDiff2) { Pair(DualCamera(it.logicalId, it.physicalId1, it.physicalId2), focalLengthsDiff1) } else { Pair(DualCamera(it.logicalId, it.physicalId2, it.physicalId1), focalLengthsDiff2) } // Return only the pair with the largest difference, or null if no pairs are found. }.maxByOrNull { it.second }?.first }
معماری معقول برای این کار این است که دو
SurfaceViews داشته باشید—یکی برای هر جاریسازی. این SurfaceViews
براساس تعامل کاربر جابهجا میشوند تا در هر زمان فقط یکی از آنها قابلمشاهده باشد.
کد زیر نحوه باز کردن دوربین منطقی، پیکربندی خروجیهای دوربین، ایجاد جلسه دوربین، و شروع دو جاریسازی پیشنمایش را نشان میدهد:
val cameraManager: CameraManager = ... val cameraExecutor: Executor = ... // owned by your activity / fragment // Get the two output targets from the activity / fragment. val surface1 = ... // from SurfaceView val surface2 = ... // from SurfaceView val dualCamera = findShortLongCameraPair(cameraManager) ?: return val outputTargets = DualCameraOutputs( null, mutableListOf(surface1), mutableListOf(surface2) ) // Here you open the logical camera, configure the outputs and create a session. createDualCameraSession( cameraManager, dualCamera, targets = outputTargets, executor = cameraExecutor ) { session -> // Create a single request which has one target for each physical camera. // NOTE: Each target receive frames from only its associated physical camera. val requestTemplate = CameraDevice.TEMPLATE_PREVIEW val captureRequest = session.device.createCaptureRequest(requestTemplate).apply { arrayOf(surface1, surface2).forEach { addTarget(it) } }.build() // Set the sticky request for the session and you are done. session.setRepeatingRequest(captureRequest, null, null) }
تنها کاری که باید انجام دهید این است که میانای کاربری برای کاربر فراهم کنید تا بتواند بین دو سطح جابهجا شود، مثلاً دکمه یا دوضربه زدن روی SurfaceView. حتی میتوانید
نوعی تحلیل صحنه انجام دهید و بهطور خودکار
بین دو جاریسازی جابهجا شوید.
اعوجاج لنز
همه لنزها مقدار مشخصی اعوجاج ایجاد میکنند. در Android، میتوانید بااستفاده از
CameraCharacteristics.LENS_DISTORTION که جایگزین
CameraCharacteristics.LENS_RADIAL_DISTORTION منسوخشده
شده است، اعوجاج ایجادشده توسط لنزها را
پرسوجو کنید. برای
دوربینهای منطقی، اعوجاج حداقل است و برنامه شما میتواند از
قابها کموبیش همانطور که از دوربین میآیند استفاده کند. برای دوربینهای فیزیکی،
پیکربندیهای لنز بسیار متفاوتی وجود دارد، بهویژه در لنزهای زاویه باز.
برخیاز دستگاهها ممکن است تصحیح اعوجاج خودکار را بااستفاده از
CaptureRequest.DISTORTION_CORRECTION_MODE پیادهسازی کنند.
تصحیح اعوجاج بهطور پیشفرض در اکثر دستگاهها روشن است.
val cameraSession: CameraCaptureSession = ... // Use still capture template to build the capture request. val captureRequest = cameraSession.device.createCaptureRequest( CameraDevice.TEMPLATE_STILL_CAPTURE ) // Determine if this device supports distortion correction. val characteristics: CameraCharacteristics = ... val supportsDistortionCorrection = characteristics.get( CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES )?.contains( CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY ) ?: false if (supportsDistortionCorrection) { captureRequest.set( CaptureRequest.DISTORTION_CORRECTION_MODE, CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY ) } // Add output target, set other capture request parameters... // Dispatch the capture request. cameraSession.capture(captureRequest.build(), ...)
تنظیم درخواست ضبط در این حالت میتواند بر نرخ فریمی که دوربین میتواند تولید کند تأثیر بگذارد. میتوانید انتخاب کنید که تصحیح اعوجاج فقط برای عکسهای ثابت تنظیم شود.