कॉन्फ़िगरेशन विकल्प

CameraX के हर इस्तेमाल के उदाहरण को कॉन्फ़िगर किया जाता है, ताकि इस्तेमाल के उदाहरण के अलग-अलग पहलुओं को कंट्रोल किया जा सके.

उदाहरण के लिए, इमेज कैप्चर करने के इस्तेमाल के उदाहरण के साथ, टारगेट आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) और फ़्लैश मोड सेट किया जा सकता है. यहां दिए गए कोड में इसका एक उदाहरण दिया गया है:

val imageCapture = ImageCapture.Builder()
    .setFlashMode(...)
    .setTargetAspectRatio(...)
    .build()

कॉन्फ़िगरेशन के विकल्पों के अलावा, इस्तेमाल के कुछ उदाहरणों में एपीआई को एक्सपोज़ किया जाता है, ताकि इस्तेमाल का उदाहरण बनाने के बाद सेटिंग में डाइनैमिक तौर पर बदलाव किया जा सके. अलग-अलग इस्तेमाल के उदाहरणों के लिए, कॉन्फ़िगरेशन के बारे में जानकारी पाने के लिए, झलक दिखाएं, इमेज का विश्लेषण करें, और इमेज कैप्चर करें देखें.

CameraXConfig

इस्तेमाल में आसानी के लिए, CameraX में डिफ़ॉल्ट कॉन्फ़िगरेशन होते हैं. जैसे, इंटरनल एक्ज़ीक्यूटर और हैंडलर, जो ज़्यादातर इस्तेमाल के मामलों के लिए सही होते हैं. हालांकि, अगर आपके ऐप्लिकेशन की कुछ खास ज़रूरतें हैं या आपको उन कॉन्फ़िगरेशन को पसंद के मुताबिक बनाना है, तो इसके लिए CameraXConfig इंटरफ़ेस का इस्तेमाल करें.

CameraXConfig की मदद से, कोई ऐप्लिकेशन ये काम कर सकता है:

  • setAvailableCameraLimiter() की मदद से, स्टार्टअप लेटेंसी को ऑप्टिमाइज़ करें.
  • setCameraExecutor() का इस्तेमाल करके, CameraX को ऐप्लिकेशन का एक्ज़ीक्यूटर उपलब्ध कराएं.
  • डिफ़ॉल्ट शेड्यूलर हैंडलर को setSchedulerHandler() से बदलें.
  • लॉगिंग लेवल को setMinimumLoggingLevel() से बदलें.

इस्तेमाल का मॉडल

यहां CameraXConfig को इस्तेमाल करने का तरीका बताया गया है:

  1. अपनी पसंद के मुताबिक कॉन्फ़िगरेशन के साथ CameraXConfig ऑब्जेक्ट बनाएं.
  2. अपने Application में CameraXConfig.Provider इंटरफ़ेस लागू करें और getCameraXConfig() में अपना CameraXConfig ऑब्जेक्ट वापस करें.
  3. यहां बताई गई जानकारी के मुताबिक, अपनी Application क्लास को AndroidManifest.xml फ़ाइल में जोड़ें.

उदाहरण के लिए, यहां दिया गया कोड सैंपल, CameraX लॉगिंग को सिर्फ़ गड़बड़ी के मैसेज तक सीमित करता है:

class CameraApplication : Application(), CameraXConfig.Provider {
    override fun getCameraXConfig(): CameraXConfig {
        return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
            .setMinimumLoggingLevel(Log.ERROR).build()
    }
}

अगर आपके ऐप्लिकेशन को CameraX कॉन्फ़िगरेशन सेट करने के बाद, उसके बारे में जानना है, तो CameraXConfig ऑब्जेक्ट की एक लोकल कॉपी सेव करके रखें.

कैमरा लिमिटर

ProcessCameraProvider.getInstance() को पहली बार शुरू करने के दौरान, CameraX डिवाइस पर उपलब्ध कैमरों की विशेषताओं की गिनती करता है और उनके बारे में क्वेरी करता है. CameraX को हार्डवेयर कॉम्पोनेंट के साथ कम्यूनिकेट करना होता है. इसलिए, हर कैमरे के लिए इस प्रोसेस में काफ़ी समय लग सकता है. खास तौर पर, कम क्षमता वाले डिवाइसों पर. अगर आपका ऐप्लिकेशन डिवाइस पर सिर्फ़ कुछ खास कैमरों का इस्तेमाल करता है, जैसे कि डिफ़ॉल्ट फ़्रंट कैमरा, तो CameraX को अन्य कैमरों को अनदेखा करने के लिए सेट किया जा सकता है. इससे, आपके ऐप्लिकेशन में इस्तेमाल होने वाले कैमरों को चालू होने में लगने वाला समय कम हो सकता है.

अगर CameraXConfig.Builder.setAvailableCamerasLimiter() को पास किया गया CameraSelector किसी कैमरे को फ़िल्टर करता है, तो CameraX ऐसा व्यवहार करता है जैसे वह कैमरा मौजूद ही न हो. उदाहरण के लिए, नीचे दिया गया कोड, ऐप्लिकेशन को सिर्फ़ डिवाइस के डिफ़ॉल्ट बैक कैमरे का इस्तेमाल करने की अनुमति देता है:

class MainApplication : Application(), CameraXConfig.Provider {
    override fun getCameraXConfig(): CameraXConfig {
        return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
            .setAvailableCamerasLimiter(CameraSelector.DEFAULT_BACK_CAMERA)
            .build()
    }
}

थ्रेड

CameraX को जिन प्लैटफ़ॉर्म एपीआई पर बनाया गया है उनमें से कई एपीआई को हार्डवेयर के साथ इंटरप्रोसेस कम्यूनिकेशन (आईपीसी) को ब्लॉक करना पड़ता है. इस वजह से, कभी-कभी जवाब देने में सैकड़ों मिलीसेकंड लग सकते हैं. इस वजह से, CameraX इन एपीआई को सिर्फ़ बैकग्राउंड थ्रेड से कॉल करता है, ताकि मुख्य थ्रेड ब्लॉक न हो और यूज़र इंटरफ़ेस (यूआई) आसानी से काम करता रहे. CameraX, इन बैकग्राउंड थ्रेड को इंटरनल तौर पर मैनेज करता है, ताकि यह व्यवहार पारदर्शी दिखे. हालांकि, कुछ ऐप्लिकेशन में थ्रेड को कंट्रोल करने के लिए, सख्त नियमों की ज़रूरत होती है. CameraXConfig, किसी ऐप्लिकेशन को बैकग्राउंड थ्रेड सेट करने की अनुमति देता है. इन थ्रेड का इस्तेमाल, CameraXConfig.Builder.setCameraExecutor() और CameraXConfig.Builder.setSchedulerHandler() के ज़रिए किया जाता है.

कैमरा एक्ज़ीक्यूटर

कैमरा एक्ज़ीक्यूटर का इस्तेमाल, सभी इंटरनल Camera Platform एपीआई कॉल के साथ-साथ इन एपीआई से मिलने वाले कॉलबैक के लिए भी किया जाता है. CameraX, इन टास्क को पूरा करने के लिए, एक इंटरनल Executor को असाइन और मैनेज करता है. हालांकि, अगर आपके ऐप्लिकेशन को थ्रेड पर ज़्यादा कंट्रोल की ज़रूरत है, तो CameraXConfig.Builder.setCameraExecutor() का इस्तेमाल करें.

शेड्यूलर हैंडलर

शेड्यूलर हैंडलर का इस्तेमाल, तय समयावधि पर इंटरनल टास्क शेड्यूल करने के लिए किया जाता है. जैसे, कैमरा उपलब्ध न होने पर उसे खोलने की फिर से कोशिश करना. यह हैंडलर, जॉब को एक्ज़ीक्यूट नहीं करता. यह उन्हें सिर्फ़ कैमरा एक्ज़ीक्यूटर को भेजता है. कभी-कभी इसका इस्तेमाल, लेगसी एपीआई प्लैटफ़ॉर्म पर भी किया जाता है. इन प्लैटफ़ॉर्म पर कॉलबैक के लिए, Handler की ज़रूरत होती है. इन मामलों में, कॉलबैक अब भी सीधे कैमरा एक्ज़ीक्यूटर को ही भेजे जाते हैं. CameraX, इन टास्क को पूरा करने के लिए, एक इंटरनल HandlerThread को असाइन और मैनेज करता है. हालांकि, CameraXConfig.Builder.setSchedulerHandler() का इस्तेमाल करके इसे बदला जा सकता है.

लॉग इन हो रहा है

CameraX लॉगिंग की मदद से, ऐप्लिकेशन logcat मैसेज को फ़िल्टर कर सकते हैं. इससे आपके प्रोडक्शन कोड में ज़्यादा शब्दों वाले मैसेज से बचा जा सकता है. CameraX में लॉगिंग के चार लेवल होते हैं. इनमें सबसे ज़्यादा जानकारी वाले लेवल से लेकर सबसे गंभीर लेवल तक शामिल हैं:

  • Log.DEBUG (डिफ़ॉल्ट)
  • Log.INFO
  • Log.WARN
  • Log.ERROR

लॉग के इन लेवल के बारे में ज़्यादा जानकारी पाने के लिए, Android लॉग से जुड़ा दस्तावेज़ देखें. अपने ऐप्लिकेशन के लिए सही लॉगिंग लेवल सेट करने के लिए, CameraXConfig.Builder.setMinimumLoggingLevel(int) का इस्तेमाल करें.

अपने-आप चुनने की सुविधा

CameraX, उस डिवाइस के हिसाब से अपने-आप सुविधाएं उपलब्ध कराता है जिस पर आपका ऐप्लिकेशन चल रहा है. उदाहरण के लिए, अगर आपने कोई रिज़ॉल्यूशन तय नहीं किया है या आपने जो रिज़ॉल्यूशन तय किया है वह काम नहीं करता है, तो CameraX अपने-आप सबसे अच्छा रिज़ॉल्यूशन तय करता है. यह सब लाइब्रेरी मैनेज करती है. इससे आपको डिवाइस के हिसाब से कोड लिखने की ज़रूरत नहीं पड़ती.

CameraX का मकसद, कैमरा सेशन को शुरू करना है. इसका मतलब है कि CameraX, डिवाइस की क्षमता के आधार पर रिज़ॉल्यूशन और आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) के साथ समझौता करता है. ऐसा इन वजहों से हो सकता है:

  • डिवाइस पर, अनुरोध किया गया रिज़ॉल्यूशन काम नहीं करता.
  • डिवाइस में, साथ काम करने से जुड़ी समस्याएं हैं. जैसे, लेगसी डिवाइसों को सही तरीके से काम करने के लिए, कुछ खास रिज़ॉल्यूशन की ज़रूरत होती है.
  • कुछ डिवाइसों पर, कुछ फ़ॉर्मैट सिर्फ़ कुछ आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) में ही उपलब्ध होते हैं.
  • डिवाइस में JPEG या वीडियो एन्कोडिंग के लिए, "सबसे नज़दीकी mod16" को प्राथमिकता दी जाती है. ज़्यादा जानकारी के लिए, SCALER_STREAM_CONFIGURATION_MAP देखें.

हालांकि, CameraX सेशन बनाता है और उसे मैनेज करता है, लेकिन अपने कोड में इस्तेमाल के उदाहरण के आउटपुट पर, इमेज के साइज़ की जांच हमेशा करें और उसके हिसाब से अडजस्ट करें.

घुमाव

इस्तेमाल के उदाहरण को बनाते समय, कैमरे का रोटेशन डिफ़ॉल्ट रूप से, डिसप्ले के डिफ़ॉल्ट रोटेशन से मैच करने के लिए सेट होता है. इस डिफ़ॉल्ट मामले में, CameraX आउटपुट जनरेट करता है, ताकि ऐप्लिकेशन झलक में आपकी उम्मीद के मुताबिक आउटपुट दिखा सके. मल्टी-डिसप्ले डिवाइसों के साथ काम करने के लिए, रोटेशन को कस्टम वैल्यू में बदला जा सकता है. इसके लिए, इस्तेमाल के उदाहरण वाले ऑब्जेक्ट को कॉन्फ़िगर करते समय या उन्हें बनाने के बाद डाइनैमिक तौर पर, मौजूदा डिसप्ले ओरिएंटेशन को पास करें.

आपका ऐप्लिकेशन, कॉन्फ़िगरेशन सेटिंग का इस्तेमाल करके टारगेट रोटेशन सेट कर सकता है. इसके बाद, यह इस्तेमाल के उदाहरण वाले एपीआई (जैसे, ImageAnalysis.setTargetRotation()) के तरीकों का इस्तेमाल करके, रोटेशन की सेटिंग को अपडेट कर सकता है. भले ही, लाइफ़साइकल चालू हो. इसका इस्तेमाल तब किया जा सकता है, जब ऐप्लिकेशन को पोर्ट्रेट मोड में लॉक किया गया हो. इससे रोटेशन पर कोई रीकॉन्फ़िगरेशन नहीं होता. हालांकि, फ़ोटो या विश्लेषण के इस्तेमाल के उदाहरण के लिए, डिवाइस के मौजूदा रोटेशन की जानकारी होना ज़रूरी है. उदाहरण के लिए, रोटेशन अवेयरनेस की ज़रूरत तब पड़ सकती है, जब चेहरे की पहचान के लिए चेहरों को सही तरीके से ओरिएंट करना हो या फ़ोटो को लैंडस्केप या पोर्ट्रेट मोड में सेट करना हो.

कैप्चर की गई इमेज का डेटा, रोटेशन की जानकारी के बिना सेव किया जा सकता है. Exif डेटा में रोटेशन की जानकारी होती है, ताकि गैलरी ऐप्लिकेशन, इमेज को सेव करने के बाद उसे सही ओरिएंटेशन में दिखा सकें.

यहां दिए गए कोड सैंपल से पता चलता है कि ओरिएंटेशन इवेंट पर रोटेशन कैसे सेट किया जाता है:

override fun onCreate() {
    val imageCapture = ImageCapture.Builder().build()

    val orientationEventListener = object : OrientationEventListener(this as Context) {
        override fun onOrientationChanged(orientation: Int) {
            // Monitors orientation values to determine the target rotation value.
            val rotation: Int = when (orientation) {
                in 45..134 -> Surface.ROTATION_270
                in 135..224 -> Surface.ROTATION_180
                in 225..314 -> Surface.ROTATION_90
                else -> Surface.ROTATION_0
            }

            imageCapture.targetRotation = rotation
        }
    }
    orientationEventListener.enable()
}

सेट किए गए रोटेशन के आधार पर, इस्तेमाल का हर उदाहरण या तो इमेज डेटा को सीधे तौर पर घुमाता है या फिर बिना घुमाए गए इमेज डेटा के उपभोक्ताओं को रोटेशन मेटाडेटा उपलब्ध कराता है.

  • झलक: मेटाडेटा आउटपुट दिया जाता है, ताकि Preview.getTargetRotation() का इस्तेमाल करके टारगेट रिज़ॉल्यूशन के रोटेशन का पता लगाया जा सके. झलक वाले डेटा को सही ओरिएंटेशन के साथ दिखाने के लिए, SurfaceRequest.TransformationInfo से मिले मेटाडेटा आउटपुट का इस्तेमाल करके, ट्रांसफ़ॉर्म बनाए जा सकते हैं.
  • ImageAnalysis: मेटाडेटा आउटपुट दिया जाता है, ताकि डिसप्ले कोऑर्डिनेट के हिसाब से इमेज बफ़र कोऑर्डिनेट की जानकारी मिल सके.
  • ImageCapture: इमेज के Exif मेटाडेटा, बफ़र या बफ़र और मेटाडेटा, दोनों में बदलाव किया जाता है, ताकि रोटेशन की सेटिंग को नोट किया जा सके. बदली गई वैल्यू, एचएएल के लागू होने पर निर्भर करती है.

क्रॉप रेक्ट

डिफ़ॉल्ट रूप से, क्रॉप रेक्ट, पूरा बफ़र रेक्ट होता है. इसे ViewPort और UseCaseGroup की मदद से, अपनी पसंद के मुताबिक बनाया जा सकता है. इस्तेमाल के उदाहरणों को ग्रुप करके और व्यूपोर्ट सेट करके, CameraX यह पक्का करता है कि ग्रुप में मौजूद इस्तेमाल के सभी उदाहरणों के क्रॉप रेक्ट, कैमरा सेंसर में एक ही हिस्से पर पॉइंट करें.

यहां दिए गए कोड स्निपेट से पता चलता है कि इन दोनों क्लास का इस्तेमाल कैसे किया जाता है:

val viewPort = ViewPort.Builder(Rational(width, height), display.rotation).build()
val useCaseGroup = UseCaseGroup.Builder()
    .addUseCase(preview)
    .addUseCase(imageAnalysis)
    .addUseCase(imageCapture)
    .setViewPort(viewPort)
    .build()
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, useCaseGroup)

ViewPort से, असली उपयोगकर्ताओं को दिखने वाले बफ़र रेक्टैंगल के बारे में पता चलता है. इसके बाद, CameraX, व्यूपोर्ट की प्रॉपर्टी और अटैच किए गए इस्तेमाल के उदाहरणों के आधार पर, सबसे बड़े क्रॉप रेक्ट का हिसाब लगाता है. आम तौर पर, WYSIWYG इफ़ेक्ट पाने के लिए, झलक के इस्तेमाल के उदाहरण के आधार पर व्यूपोर्ट को कॉन्फ़िगर किया जा सकता है. व्यूपोर्ट पाने का आसान तरीका है कि PreviewView का इस्तेमाल किया जाए.

यहां दिए गए कोड स्निपेट से, ViewPort ऑब्जेक्ट को पाने का तरीका पता चलता है:

val viewport = findViewById<PreviewView>(R.id.preview_view).viewPort

ऊपर दिए गए उदाहरण में, ऐप्लिकेशन को ImageAnalysis और ImageCapture से जो मिलता है वह असली उपयोगकर्ता को PreviewView में दिखने वाले कॉन्टेंट से मैच करता है. ऐसा तब होता है, जब PreviewView का स्केल टाइप, डिफ़ॉल्ट FILL_CENTER पर सेट हो. आउटपुट बफ़र में, इमेज को काटने के लिए रेक्टैंगल और रोटेशन लागू करने के बाद, सभी इस्तेमाल के उदाहरणों से मिलने वाली इमेज एक जैसी होती है. हालांकि, हो सकता है कि उनके रिज़ॉल्यूशन अलग-अलग हों. बदलाव की जानकारी लागू करने के तरीके के बारे में ज़्यादा जानने के लिए, आउटपुट में बदलाव करना लेख पढ़ें.

कैमरा चुनना

CameraX, आपके ऐप्लिकेशन की ज़रूरतों और इस्तेमाल के उदाहरणों के हिसाब से, सबसे अच्छा कैमरा डिवाइस अपने-आप चुनता है. अगर आपको चुने गए डिवाइस के बजाय किसी दूसरे डिवाइस का इस्तेमाल करना है, तो आपके पास ये विकल्प हैं:

यहां दिए गए कोड सैंपल में, डिवाइस चुनने की प्रोसेस पर असर डालने के लिए, CameraSelector बनाने का तरीका बताया गया है:

fun selectExternalOrBestCamera(provider: ProcessCameraProvider): CameraSelector? {
    val cam2Infos = provider.availableCameraInfos.map {
        Camera2CameraInfo.from(it)
    }.sortedByDescending {
        // HARDWARE_LEVEL is Int type, with the order of:
        // LEGACY < LIMITED < FULL < LEVEL_3 < EXTERNAL.
        it.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
    }

    return when {
        cam2Infos.isNotEmpty() -> {
            CameraSelector.Builder()
                .addCameraFilter {
                    it.filter { camInfo ->
                        // cam2Infos[0] is either EXTERNAL or best built-in camera.
                        val thisCamId = Camera2CameraInfo.from(camInfo).cameraId
                        thisCamId == cam2Infos[0].cameraId
                    }
                }.build()
        }
        else -> null
    }
}

// create a CameraSelector for the USB camera (or highest level internal camera).
val selector = selectExternalOrBestCamera(processCameraProvider) ?: return
processCameraProvider.bindToLifecycle(this, selector, preview, analysis)

एक साथ कई कैमरे चुनना

CameraX 1.3 से, एक साथ कई कैमरे भी चुने जा सकते हैं. उदाहरण के लिए, फ़्रंट और रियर कैमरे को एक साथ बाइंड करके, दोनों से एक साथ फ़ोटो ली जा सकती हैं या वीडियो रिकॉर्ड किए जा सकते हैं.

कॉन्करेंट कैमरा सुविधा का इस्तेमाल करने पर, डिवाइस एक ही समय पर अलग-अलग दिशाओं में लेंस वाले दो कैमरे या एक ही समय पर दो बैक कैमरे इस्तेमाल कर सकता है. यहां दिए गए कोड ब्लॉक से पता चलता है कि bindToLifecycle को कॉल करते समय दो कैमरे कैसे सेट किए जाते हैं. साथ ही, यह भी पता चलता है कि वापस किए गए ConcurrentCamera ऑब्जेक्ट से दोनों कैमरा ऑब्जेक्ट कैसे पाए जाते हैं.

// Build ConcurrentCameraConfig.
val primary = ConcurrentCamera.SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
)

val secondary = ConcurrentCamera.SingleCameraConfig(
    secondaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
)

val concurrentCamera = cameraProvider.bindToLifecycle(
    listOf(primary, secondary)
)

val primaryCamera = concurrentCamera.cameras[0]
val secondaryCamera = concurrentCamera.cameras[1]

कैमरे का रिज़ॉल्यूशन

आपके पास यह चुनने का विकल्प होता है कि CameraX, इमेज का रिज़ॉल्यूशन सेट करे. इसके लिए, वह डिवाइस की क्षमताओं, डिवाइस के साथ काम करने वाले हार्डवेयर लेवल, इस्तेमाल के उदाहरण, और दिए गए आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) के कॉम्बिनेशन का इस्तेमाल करता है. इसके अलावा, आपके पास उन मामलों में टारगेट रिज़ॉल्यूशन या आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) सेट करने का विकल्प होता है जिनमें यह कॉन्फ़िगरेशन काम करता है.

अपने-आप रिज़ॉल्यूशन सेट होने की सुविधा

CameraX, cameraProcessProvider.bindToLifecycle() में बताए गए इस्तेमाल के उदाहरणों के आधार पर, सबसे सही रिज़ॉल्यूशन सेटिंग अपने-आप तय कर सकता है. जब भी संभव हो, एक ही सेशन में एक साथ इस्तेमाल होने वाले सभी उदाहरणों के बारे में बताएं. इसके लिए, एक ही bindToLifecycle() कॉल का इस्तेमाल करें. CameraX, इस्तेमाल के उदाहरणों के सेट के आधार पर रिज़ॉल्यूशन तय करता है. इसके लिए, डिवाइस के साथ काम करने वाले हार्डवेयर लेवल को ध्यान में रखा जाता है. साथ ही, डिवाइस के हिसाब से होने वाले अंतर को भी ध्यान में रखा जाता है. इसमें, डिवाइस उपलब्ध स्ट्रीम कॉन्फ़िगरेशन से ज़्यादा या कम होता है. इसका मकसद, ऐप्लिकेशन को अलग-अलग डिवाइसों पर चलाने की सुविधा देना है. साथ ही, डिवाइस के हिसाब से कोड पाथ को कम करना है.

इमेज कैप्चर करने और इमेज का विश्लेषण करने के लिए, डिफ़ॉल्ट आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) 4:3 होता है.

इस्तेमाल के उदाहरणों में, आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) को कॉन्फ़िगर किया जा सकता है. इससे ऐप्लिकेशन, यूज़र इंटरफ़ेस (यूआई) डिज़ाइन के आधार पर अपनी पसंद का आसपेक्ट रेशियो तय कर सकता है. CameraX आउटपुट को इस तरह से जनरेट किया जाता है कि वह डिवाइस के हिसाब से, अनुरोध किए गए आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) से ज़्यादा से ज़्यादा मैच करे. अगर एग्ज़ैक्ट मैच वाला कोई रिज़ॉल्यूशन उपलब्ध नहीं है, तो वह रिज़ॉल्यूशन चुना जाता है जो ज़्यादातर शर्तों को पूरा करता है. इसलिए, ऐप्लिकेशन यह तय करता है कि ऐप्लिकेशन में कैमरा कैसा दिखेगा. साथ ही, CameraX अलग-अलग डिवाइसों पर कैमरे के रिज़ॉल्यूशन की सबसे सही सेटिंग तय करता है, ताकि ऐप्लिकेशन में कैमरा सही तरीके से दिखे.

उदाहरण के लिए, कोई ऐप्लिकेशन इनमें से कोई भी काम कर सकता है:

  • किसी इस्तेमाल के उदाहरण के लिए, 4:3 या 16:9 का टारगेट रिज़ॉल्यूशन तय करना
  • कस्टम रिज़ॉल्यूशन तय करें, ताकि CameraX सबसे सटीक मैच ढूंढ सके
  • ImageCapture के लिए, क्रॉप करने का आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) तय करें

CameraX, इंटरनल Camera2 के रिज़ॉल्यूशन अपने-आप चुनता है. यहां दी गई टेबल में, रिज़ॉल्यूशन के बारे में बताया गया है:

इस्तेमाल का उदाहरण इंटरनल सरफ़ेस रिज़ॉल्यूशन आउटपुट डेटा रिज़ॉल्यूशन
झलक देखें आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात): वह रिज़ॉल्यूशन जो टारगेट को सेटिंग के हिसाब से सबसे सही तरीके से फ़िट करता है. अंदरूनी सतह का रिज़ॉल्यूशन. मेटाडेटा इसलिए दिया जाता है, ताकि टारगेट आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) के लिए, व्यू को काटा जा सके, स्केल किया जा सके, और घुमाया जा सके.
डिफ़ॉल्ट रिज़ॉल्यूशन: झलक का सबसे ज़्यादा रिज़ॉल्यूशन या डिवाइस के हिसाब से सबसे ज़्यादा रिज़ॉल्यूशन, जो झलक के आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) से मैच करता हो.
ज़्यादा से ज़्यादा रिज़ॉल्यूशन: झलक का साइज़, जो डिवाइस के स्क्रीन रिज़ॉल्यूशन या 1080p (1920x1080) के हिसाब से सबसे सही साइज़ होता है. इनमें से जो भी छोटा हो उसे चुनें.
इमेज ऐनलिसिस आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात): ऐसा रिज़ॉल्यूशन जो टारगेट को सेटिंग के हिसाब से सबसे सही तरीके से फ़िट करता है. अंदरूनी प्लैटफ़ॉर्म पर समस्या का समाधान.
डिफ़ॉल्ट रिज़ॉल्यूशन: टारगेट रिज़ॉल्यूशन की डिफ़ॉल्ट सेटिंग 640x480 है. टारगेट रिज़ॉल्यूशन और उससे जुड़े आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात), दोनों में बदलाव करने पर, सबसे सही रिज़ॉल्यूशन मिलता है.
ज़्यादा से ज़्यादा रिज़ॉल्यूशन: कैमरे का ज़्यादा से ज़्यादा आउटपुट रिज़ॉल्यूशन, YUV_420_888 फ़ॉर्मैट में होता है. इसे StreamConfigurationMap.getOutputSizes() से वापस पाया जाता है. टारगेट रिज़ॉल्यूशन, डिफ़ॉल्ट रूप से 640x480 पर सेट होता है. इसलिए, अगर आपको 640x480 से ज़्यादा रिज़ॉल्यूशन चाहिए, तो आपको setTargetResolution() और setTargetAspectRatio() का इस्तेमाल करना होगा, ताकि आपको काम करने वाले रिज़ॉल्यूशन में से सबसे नज़दीकी रिज़ॉल्यूशन मिल सके.
इमेज कैप्चर करने की सुविधा आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात): ऐसा आसपेक्ट रेशियो जो सेटिंग के हिसाब से सबसे सही हो. अंदरूनी सतह का रिज़ॉल्यूशन.
डिफ़ॉल्ट रिज़ॉल्यूशन: उपलब्ध सबसे ज़्यादा रिज़ॉल्यूशन या डिवाइस के लिए सबसे सही रिज़ॉल्यूशन, जो ImageCapture के आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) से मेल खाता हो.
ज़्यादा से ज़्यादा रिज़ॉल्यूशन: JPEG फ़ॉर्मैट में, कैमरे का ज़्यादा से ज़्यादा आउटपुट रिज़ॉल्यूशन. इसे वापस पाने के लिए, StreamConfigurationMap.getOutputSizes() का इस्तेमाल करें.

रिज़ॉल्यूशन की जानकारी देना

setTargetResolution(Size resolution) तरीके का इस्तेमाल करके, इस्तेमाल के उदाहरण बनाते समय, खास रिज़ॉल्यूशन सेट किए जा सकते हैं. जैसा कि इस कोड सैंपल में दिखाया गया है:

val imageAnalysis = ImageAnalysis.Builder()
    .setTargetResolution(Size(1280, 720))
    .build()

एक ही इस्तेमाल के उदाहरण के लिए, टारगेट आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) और टारगेट रिज़ॉल्यूशन, दोनों सेट नहीं किए जा सकते. ऐसा करने पर, कॉन्फ़िगरेशन ऑब्जेक्ट बनाते समय IllegalArgumentException दिखता है.

सपोर्ट किए जाने वाले साइज़ को टारगेट रोटेशन के हिसाब से घुमाने के बाद, रिज़ॉल्यूशन Size को कोऑर्डिनेट फ़्रेम में दिखाएं. उदाहरण के लिए, पोर्ट्रेट इमेज का अनुरोध करने वाला कोई डिवाइस, 480x640 की जानकारी दे सकता है. यह डिवाइस, पोर्ट्रेट ओरिएंटेशन में नैचुरल टारगेट रोटेशन के साथ काम करता है. वहीं, 90 डिग्री घुमाया गया और लैंडस्केप ओरिएंटेशन को टारगेट करने वाला डिवाइस, 640x480 की जानकारी दे सकता है.

टारगेट रिज़ॉल्यूशन, इमेज के रिज़ॉल्यूशन के लिए कम से कम सीमा तय करने की कोशिश करता है. इमेज का असल रिज़ॉल्यूशन, साइज़ में उपलब्ध सबसे नज़दीकी रिज़ॉल्यूशन होता है. यह रिज़ॉल्यूशन, टारगेट रिज़ॉल्यूशन से छोटा नहीं होता. इसे कैमरे के सेटअप से तय किया जाता है.

हालांकि, अगर टारगेट रिज़ॉल्यूशन के बराबर या उससे ज़्यादा रिज़ॉल्यूशन वाला कोई वीडियो मौजूद नहीं है, तो टारगेट रिज़ॉल्यूशन से कम रिज़ॉल्यूशन वाला सबसे नज़दीकी वीडियो चुना जाता है. दिए गए Size के आसपेक्ट रेशियो वाले रिज़ॉल्यूशन को, अलग-अलग आसपेक्ट रेशियो वाले रिज़ॉल्यूशन की तुलना में ज़्यादा प्राथमिकता दी जाती है.

CameraX, अनुरोधों के आधार पर सबसे सही रिज़ॉल्यूशन लागू करता है. अगर आपकी मुख्य ज़रूरत आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) को पूरा करना है, तो सिर्फ़ setTargetAspectRatio तय करें. इसके बाद, CameraX डिवाइस के हिसाब से सही रिज़ॉल्यूशन तय करता है. अगर ऐप्लिकेशन को इमेज प्रोसेसिंग को ज़्यादा बेहतर बनाने के लिए, रिज़ॉल्यूशन तय करने की ज़रूरत है, तो setTargetResolution(Size resolution) का इस्तेमाल करें. जैसे, डिवाइस की प्रोसेसिंग क्षमता के आधार पर छोटी या मीडियम साइज़ की इमेज.

अगर आपके ऐप्लिकेशन को सटीक रिज़ॉल्यूशन की ज़रूरत है, तो createCaptureSession() में दी गई टेबल देखें. इससे आपको यह पता चलेगा कि हर हार्डवेयर लेवल पर, ज़्यादा से ज़्यादा कितने रिज़ॉल्यूशन काम करते हैं. मौजूदा डिवाइस पर काम करने वाले रिज़ॉल्यूशन देखने के लिए, StreamConfigurationMap.getOutputSizes(int) देखें.

अगर आपका ऐप्लिकेशन Android 10 या इसके बाद के वर्शन पर चल रहा है, तो किसी खास SessionConfiguration की पुष्टि करने के लिए, isSessionConfigurationSupported() का इस्तेमाल किया जा सकता है.

कैमरे के आउटपुट को कंट्रोल करना

CameraX, हर इस्तेमाल के उदाहरण के लिए, ज़रूरत के हिसाब से कैमरा आउटपुट को कॉन्फ़िगर करने की सुविधा देता है. इसके अलावा, यह बाउंड किए गए सभी इस्तेमाल के उदाहरणों के लिए, कैमरे से जुड़े सामान्य कामों को पूरा करने के लिए, ये इंटरफ़ेस भी लागू करता है:

  • CameraControl की मदद से, कैमरे की सामान्य सुविधाओं को कॉन्फ़िगर किया जा सकता है.
  • CameraInfo की मदद से, कैमरे की इन सामान्य सुविधाओं की स्थिति के बारे में क्वेरी की जा सकती है.

CameraControl के साथ, कैमरे की ये सुविधाएं इस्तेमाल की जा सकती हैं:

CameraControl और CameraInfo के इंस्टेंस पाना

ProcessCameraProvider.bindToLifecycle() से मिले Camera ऑब्जेक्ट का इस्तेमाल करके, CameraControl और CameraInfo के इंस्टेंस वापस पाएं. यहां दिए गए कोड में एक उदाहरण दिखाया गया है:

val camera = processCameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)

// For performing operations that affect all outputs.
val cameraControl = camera.cameraControl
// For querying information and states.
val cameraInfo = camera.cameraInfo

उदाहरण के लिए, bindToLifecycle() को कॉल करने के बाद, ज़ूम और अन्य CameraControl कार्रवाइयां सबमिट की जा सकती हैं. कैमरा इंस्टेंस को बाइंड करने के लिए इस्तेमाल की गई गतिविधि को रोकने या खत्म करने के बाद, CameraControl कार्रवाइयां नहीं कर सकता. साथ ही, यह ListenableFuture दिखाता है.

ज़ूम करें

CameraControl, ज़ूम करने का लेवल बदलने के लिए दो तरीके उपलब्ध कराता है:

  • setZoomRatio() ज़ूम रेशियो के हिसाब से ज़ूम सेट करता है.

    यह अनुपात, CameraInfo.getZoomState().getValue().getMinZoomRatio() और CameraInfo.getZoomState().getValue().getMaxZoomRatio() के बीच होना चाहिए. ऐसा न होने पर, फ़ंक्शन ListenableFuture दिखाता है.

  • setLinearZoom() 0 से 1.0 के बीच की लीनियर ज़ूम वैल्यू के साथ, मौजूदा ज़ूम को सेट करता है.

    लीनियर ज़ूम का फ़ायदा यह है कि ज़ूम में बदलाव होने पर, फ़ील्ड ऑफ़ व्यू (एफ़ओवी) स्केल होता है. इसलिए, यह Slider व्यू के साथ इस्तेमाल करने के लिए सबसे सही है.

CameraInfo.getZoomState() यह मौजूदा ज़ूम स्टेट का LiveData दिखाता है. कैमरा शुरू होने पर या setZoomRatio() या setLinearZoom() का इस्तेमाल करके ज़ूम करने का लेवल सेट करने पर, वैल्यू बदल जाती है. इनमें से किसी भी तरीके को कॉल करने पर, ZoomState.getZoomRatio() और ZoomState.getLinearZoom() की वैल्यू सेट हो जाती हैं. अगर आपको स्लाइडर के बगल में ज़ूम रेशियो का टेक्स्ट दिखाना है, तो यह सुविधा मददगार साबित हो सकती है. दोनों को अपडेट करने के लिए, बस ZoomState LiveData को देखें. इसके लिए, आपको कन्वर्ज़न करने की ज़रूरत नहीं है.

दोनों एपीआई से मिलने वाला ListenableFuture, ऐप्लिकेशन को यह विकल्प देता है कि ज़ूम की तय वैल्यू के साथ बार-बार किया जाने वाला अनुरोध पूरा होने पर, उन्हें सूचना दी जाए. इसके अलावा, अगर पिछली कार्रवाई के दौरान ज़ूम की नई वैल्यू सेट की जाती है, तो पिछली ज़ूम कार्रवाई का ListenableFuture तुरंत फ़ेल हो जाता है.

टॉर्च

CameraControl.enableTorch(boolean) टॉर्च (इसे फ़्लैशलाइट भी कहा जाता है) को चालू या बंद करता है.

CameraInfo.getTorchState() का इस्तेमाल, टॉर्च की मौजूदा स्थिति के बारे में क्वेरी करने के लिए किया जा सकता है. CameraInfo.hasFlashUnit() से मिली वैल्यू को देखकर, यह पता लगाया जा सकता है कि टॉर्च उपलब्ध है या नहीं. अगर ऐसा नहीं है, तो CameraControl.enableTorch(boolean) को कॉल करने पर, वापस किया गया ListenableFuture तुरंत पूरा हो जाता है. साथ ही, टॉर्च की स्थिति को TorchState.OFF पर सेट कर देता है.

टॉर्च चालू होने पर, फ़ोटो और वीडियो कैप्चर करते समय यह चालू रहती है. भले ही, फ़्लैश मोड की सेटिंग कुछ भी हो. ImageCapture.setFlashMode() के साथ सेट किया गया फ़्लैश मोड, सिर्फ़ तब काम करता है, जब टॉर्च बंद हो.

फ़ोकस और मीटरिंग

CameraControl.startFocusAndMetering(), दिए गए FocusMeteringAction के आधार पर मीटरिंग रीजन सेट करके, ऑटोफ़ोकस (एएफ़), ऑटो एक्सपोज़र (एई), और ऑटो व्हाइट बैलेंस (एडब्ल्यूबी) मीटरिंग को ट्रिगर करता है. इस तरीके का इस्तेमाल करके, कैमरा ऐप्लिकेशन में टैप-टू-फ़ोकस सुविधा को लागू किया जा सकता है.

MeteringPoint

शुरू करने के लिए, MeteringPointFactory.createPoint(float x, float y, float size) का इस्तेमाल करके MeteringPoint बनाएं. MeteringPoint, कैमरे Surface पर एक पॉइंट को दिखाता है. इसे सामान्य फ़ॉर्मैट में सेव किया जाता है, ताकि इसे सेंसर कोऑर्डिनेट में बदला जा सके. इससे एएफ़/एई/एडब्ल्यूबी क्षेत्रों की जानकारी दी जा सकती है.

MeteringPoint का साइज़ 0 से 1 के बीच होता है. इसका डिफ़ॉल्ट साइज़ 0.15f होता है. size के लिए MeteringPointFactory.createPoint(float x, float y, float size) को कॉल करने पर, CameraX (x, y) पर रेक्टैंगल रीजन बनाता है.

यहां दिए गए कोड से, MeteringPoint बनाने का तरीका पता चलता है:

// Use PreviewView.getMeteringPointFactory if PreviewView is used for preview.
previewView.setOnTouchListener { view, motionEvent ->
    if (motionEvent.action == MotionEvent.ACTION_UP) {
        val meteringPoint = previewView.meteringPointFactory
            .createPoint(motionEvent.x, motionEvent.y)
        // Build and start a FocusMeteringAction with meteringPoint.
        view.performClick()
    }
    true
}

// Use DisplayOrientedMeteringPointFactory if SurfaceView / TextureView is used
// for preview. Note that if the preview is scaled or cropped in the View,
// it's the application's responsibility to transform the coordinates properly
// so that the width and height of this factory represents the full Preview FOV.
// And the (x,y) passed to create MeteringPoint might need to be adjusted with
// the offsets.
val displayFactory = DisplayOrientedMeteringPointFactory(
    surfaceView.display,
    camera.cameraInfo,
    surfaceView.width.toFloat(),
    surfaceView.height.toFloat()
)

// Use SurfaceOrientedMeteringPointFactory if the point is specified in
// ImageAnalysis ImageProxy.
val surfaceFactory = SurfaceOrientedMeteringPointFactory(
    imageWidth.toFloat(),
    imageHeight.toFloat(),
    imageAnalysis
)

startFocusAndMetering और FocusMeteringAction

startFocusAndMetering() को शुरू करने के लिए, ऐप्लिकेशन को FocusMeteringAction बनाना होगा. इसमें एक या उससे ज़्यादा MeteringPoint ऑब्जेक्ट होने चाहिए. साथ ही, FLAG_AF, FLAG_AE, और FLAG_AWB से मीटरिंग मोड के वैकल्पिक कॉम्बिनेशन होने चाहिए. यहां दिए गए कोड से, इसके इस्तेमाल का तरीका पता चलता है:

// meteringPointFactory is any MeteringPointFactory from the previous example.
val meteringPoint1 = meteringPointFactory.createPoint(x1, y1)
val meteringPoint2 = meteringPointFactory.createPoint(x2, y2)
val action = FocusMeteringAction.Builder(meteringPoint1) // default AF|AE|AWB.
    // Optionally add meteringPoint2 for AF/AE.
    .addPoint(meteringPoint2, FLAG_AF or FLAG_AE)
    // The action is canceled in 3 seconds (if not set, default is 5s).
    .setAutoCancelDuration(3, TimeUnit.SECONDS)
    .build()

val result = cameraControl.startFocusAndMetering(action)
// Adds listener to the ListenableFuture if you need to know the focusMetering
// result.
result.addListener({
    // result.get().isFocusSuccessful returns if the auto focus is successful
    // or not.
}, ContextCompat.getMainExecutor(this))

पिछले कोड में दिखाए गए तरीके के मुताबिक, startFocusAndMetering() एक FocusMeteringAction लेता है. इसमें एएफ़/एई/एडब्ल्यूबी मीटरिंग क्षेत्रों के लिए एक MeteringPoint और सिर्फ़ एएफ़ और एई के लिए दूसरा MeteringPoint शामिल होता है.

हर डिवाइस में एएफ़/एई/एडब्ल्यूबी और कई इलाकों के लिए सहायता उपलब्ध नहीं होती. इसलिए, CameraX FocusMeteringAction को बेहतर तरीके से लागू करता है. CameraX, MeteringPoint ऑब्जेक्ट की ज़्यादा से ज़्यादा संख्या का इस्तेमाल करता है. यह संख्या, पॉइंट जोड़े जाने के क्रम में होती है. ज़्यादा से ज़्यादा संख्या के बाद जोड़े गए सभी पॉइंट को अनदेखा कर दिया जाता है. उदाहरण के लिए, अगर किसी FocusMeteringAction को तीन MeteringPoint ऑब्जेक्ट के साथ किसी ऐसे प्लैटफ़ॉर्म पर उपलब्ध कराया जाता है जो सिर्फ़ दो ऑब्जेक्ट के साथ काम करता है, तो सिर्फ़ पहले दो पॉइंट इस्तेमाल किए जाते हैं. CameraX, आखिरी MeteringPoint को अनदेखा कर देता है.

एक्सपोज़र कंपंसेशन

एक्सपोज़र कंपंसेशन तब काम आता है, जब ऐप्लिकेशन को ऑटो एक्सपोज़र (एई) आउटपुट के नतीजे से ज़्यादा, एक्सपोज़र वैल्यू (ईवी) को बेहतर बनाना होता है. मौजूदा इमेज की स्थितियों के लिए ज़रूरी एक्सपोज़र तय करने के लिए, एक्सपोज़र कंपंसेशन की वैल्यू को इस तरह से जोड़ा जाता है:

Exposure = ExposureCompensationIndex * ExposureCompensationStep

CameraX, इंडेक्स वैल्यू के तौर पर एक्सपोज़र कंपंसेशन सेट करने के लिए, CameraControl.setExposureCompensationIndex() फ़ंक्शन उपलब्ध कराता है.

पॉज़िटिव इंडेक्स वैल्यू से इमेज ज़्यादा चमकदार बनती है, जबकि नेगेटिव वैल्यू से इमेज की चमक कम हो जाती है. ऐप्लिकेशन, ExposureState.getExposureCompensationRange() की मदद से, काम करने वाली रेंज के बारे में क्वेरी कर सकते हैं. अगर वैल्यू का इस्तेमाल किया जा सकता है, तो कैप्चर करने के अनुरोध में वैल्यू के चालू होने पर, दिखाया गया ListenableFuture पूरा हो जाता है. अगर तय किया गया इंडेक्स, इस्तेमाल की जा सकने वाली रेंज से बाहर है, तो setExposureCompensationIndex() की वजह से, दिखाया गया ListenableFuture तुरंत पूरा हो जाता है. हालांकि, इसका नतीजा फ़ेल होता है.

CameraX, सिर्फ़ सबसे हाल ही में किए गए setExposureCompensationIndex() अनुरोध को सेव रखता है. अगर CameraX के पिछले अनुरोध को लागू करने से पहले, फ़ंक्शन को फिर से कॉल किया जाता है, तो CameraX पिछले अनुरोध को रद्द कर देता है.

नीचे दिया गया स्निपेट, एक्सपोज़र कंपंसेशन इंडेक्स सेट करता है और एक्सपोज़र में बदलाव का अनुरोध पूरा होने पर, कॉलबैक को रजिस्टर करता है:

camera.cameraControl.setExposureCompensationIndex(exposureCompensationIndex)
    .addListener({
        // Get the current exposure compensation index, it might be
        // different from the asked value in case this request was
        // canceled by a newer setting request.
        val currentExposureIndex = camera.cameraInfo.exposureState.exposureCompensationIndex
        // Handle the updated exposure compensation index.
    }, mainExecutor)

  • CameraInfo.getExposureState() मौजूदा ExposureState को वापस लाता है. इसमें ये शामिल हैं:

    • एक्सपोज़र कंपंसेशन कंट्रोल की सुविधा.
    • एक्सपोज़र कंपंसेशन का मौजूदा इंडेक्स.
    • एक्सपोज़र कंपंसेशन इंडेक्स की रेंज.
    • एक्सपोज़र कंपंसेशन वैल्यू का हिसाब लगाने के लिए, एक्सपोज़र कंपंसेशन स्टेप का इस्तेमाल किया जाता है.

उदाहरण के लिए, यहां दिया गया कोड, मौजूदा ExposureState वैल्यू के साथ एक्सपोज़र SeekBar की सेटिंग को शुरू करता है. SeekBar प्रोग्रेस वैल्यू 0 से शुरू होती है. इसलिए, कोड इसे एक्सपोज़र कंपंसेशन रेंज की निचली सीमा से ऑफ़सेट करता है:

val exposureState = camera.cameraInfo.exposureState
val range = exposureState.exposureCompensationRange
binding.seekBar.apply {
    isEnabled = exposureState.isExposureCompensationSupported
    max = range.upper - range.lower
    progress = exposureState.exposureCompensationIndex - range.lower
    setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
        override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
            if (fromUser) {
                camera.cameraControl.setExposureCompensationIndex(progress + range.lower)
            }
        }
        override fun onStartTrackingTouch(seekBar: SeekBar) {}
        override fun onStopTrackingTouch(seekBar: SeekBar) {}
    })
}

अन्य संसाधन

CameraX के बारे में ज़्यादा जानने के लिए, यहां दिए गए अन्य संसाधन देखें.

कोडलैब (कोड बनाना सीखना)

  • CameraX का इस्तेमाल शुरू करना
  • कोड सैंपल

  • CameraX के सैंपल ऐप्लिकेशन
  • डेवलपर कम्यूनिटी

    Android CameraX Discussion Group