Camera1 को CameraX पर माइग्रेट करें

अगर आपका ऐप्लिकेशन ओरिजनल Camera क्लास ("Camera1") का इस्तेमाल करता है, जिसे Android 5.0 (एपीआई लेवल 21) के बाद से बंद कर दिया गया है, तो हमारा सुझाव है कि आप इसे Android के नए कैमरा एपीआई पर अपडेट करें. Android पर, CameraX (स्टैंडर्ड और बेहतर Jetpack कैमरा एपीआई) और Camera2 (लो-लेवल फ़्रेमवर्क एपीआई) उपलब्ध है. ज़्यादातर मामलों में, हमारा सुझाव है कि आप अपने ऐप्लिकेशन को CameraX पर माइग्रेट करें. इसकी वजह यह है:

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

इस गाइड में, आपको कैमरा ऐप्लिकेशन के लिए सामान्य स्थितियां मिलेंगी. हर स्थिति में, तुलना करने के लिए Camera1 और CameraX, दोनों का इस्तेमाल किया गया है.

माइग्रेशन के मामले में, कभी-कभी आपको मौजूदा कोडबेस के साथ इंटिग्रेट करने के लिए ज़्यादा सुविधाओं की ज़रूरत होती है. इस गाइड में मौजूद सभी CameraX कोड को CameraController के तौर पर लागू किया गया है. अगर आपको CameraX का इस्तेमाल करने का सबसे आसान तरीका चाहिए, तो यह तरीका अपनाएं. साथ ही, इसे CameraProvider के तौर पर भी लागू किया गया है. अगर आपको ज़्यादा सुविधाएं चाहिए, तो यह तरीका अपनाएं. आपके लिए कौनसा विकल्प सही है, यह तय करने में आपकी मदद करने के लिए, यहां हर विकल्प के फ़ायदे बताए गए हैं:

CameraController

CameraProvider

इसके लिए, सेटअप कोड की ज़रूरत कम होती है ज़्यादा कंट्रोल की सुविधा मिलती है
CameraX को सेटअप की ज़्यादा प्रोसेस को मैनेज करने की अनुमति देने का मतलब है कि टैप करके फ़ोकस करने और पिंच करके ज़ूम करने जैसी सुविधाएं अपने-आप काम करती हैं ऐप्लिकेशन डेवलपर सेटअप मैनेज करता है. इसलिए, कॉन्फ़िगरेशन को पसंद के मुताबिक बनाने के ज़्यादा अवसर होते हैं. जैसे, आउटपुट इमेज को घुमाने की सुविधा चालू करना या ImageAnalysis में आउटपुट इमेज का फ़ॉर्मैट सेट करना
कैमरे की झलक के लिए PreviewView की ज़रूरत होने पर, CameraX को एंड-टू-एंड इंटिग्रेशन आसानी से ऑफ़र करने में मदद मिलती है. जैसे, हमारे ML Kit इंटिग्रेशन में, एमएल मॉडल के नतीजे के निर्देशांक (जैसे, चेहरे के बॉउंडिंग बॉक्स) को सीधे झलक के निर्देशांक पर मैप किया जा सकता है कैमरे की झलक दिखाने के लिए कस्टम `Surface` का इस्तेमाल करने से, आपको ज़्यादा सुविधाएं मिलती हैं. जैसे, अपने मौजूदा `Surface` कोड का इस्तेमाल करना, जो आपके ऐप्लिकेशन के दूसरे हिस्सों के लिए इनपुट हो सकता है

अगर माइग्रेट करने में कोई समस्या आती है, तो CameraX के डिस्कस ग्रुप पर हमसे संपर्क करें.

माइग्रेट करने से पहले

CameraX और Camera1 के इस्तेमाल की तुलना करना

कोड अलग दिख सकता है, लेकिन Camera1 और CameraX में मौजूद कॉन्सेप्ट काफ़ी मिलते-जुलते हैं. CameraX, कैमरे की सामान्य सुविधाओं को इस्तेमाल के उदाहरणों में बदल देता है. इस वजह से, Camera1 में डेवलपर को जो काम करने होते थे उन्हें CameraX अपने-आप मैनेज कर लेता है. CameraX में चार तरह के UseCase होते हैं. इनका इस्तेमाल, कैमरे से जुड़े अलग-अलग कामों के लिए किया जा सकता है: Preview, ImageCapture, VideoCapture, और ImageAnalysis.

CameraX, डेवलपर के लिए कम-लेवल की जानकारी को मैनेज करता है. इसका एक उदाहरण, ViewPort है, जो ऐक्टिव UseCase के बीच शेयर किया जाता है. इससे यह पक्का होता है कि सभी UseCase को एक जैसे पिक्सल दिखें. Camera1 में, आपको इन जानकारी को खुद मैनेज करना होगा. साथ ही, डिवाइसों के कैमरे सेंसर और स्क्रीन के आसपेक्ट रेशियो में अंतर होने की वजह से, यह पक्का करना मुश्किल हो सकता है कि झलक, कैप्चर की गई फ़ोटो और वीडियो से मेल खाती हो.

एक और उदाहरण के तौर पर, CameraX आपके पास किए गए Lifecycle इंस्टेंस पर, Lifecycle कॉलबैक को अपने-आप मैनेज करता है. इसका मतलब है कि CameraX, Android गतिविधि के पूरे लाइफ़साइकल के दौरान, आपके ऐप्लिकेशन के कैमरे से कनेक्शन को मैनेज करता है. इसमें ये मामले भी शामिल हैं: आपका ऐप्लिकेशन बैकग्राउंड में जाने पर कैमरे को बंद करना; स्क्रीन पर कैमरे की झलक दिखाने की ज़रूरत न होने पर उसे हटाना; और आने वाले वीडियो कॉल जैसी किसी दूसरी गतिविधि के फ़ोरग्राउंड में आने पर, कैमरे की झलक को रोकना.

आखिर में, CameraX आपके लिए रोटेशन और स्केलिंग को मैनेज करता है. इसके लिए, आपको कोई अतिरिक्त कोड डालने की ज़रूरत नहीं होती. अगर Activity के ओरिएंटेशन को अनलॉक किया गया है, तो डिवाइस को हर बार घुमाने पर UseCase का सेटअप किया जाता है. ऐसा इसलिए होता है, क्योंकि ओरिएंटेशन में बदलाव होने पर सिस्टम, Activity को मिटा देता है और फिर से बना देता है. इस वजह से, UseCases हर बार डिफ़ॉल्ट रूप से डिसप्ले के ओरिएंटेशन से मैच करने के लिए, टारगेट रोटेशन सेट करता है. CameraX में रोटेशन के बारे में ज़्यादा पढ़ें.

ज़्यादा जानकारी देने से पहले, यहां CameraX के UseCase के बारे में खास जानकारी दी गई है. साथ ही, यह भी बताया गया है कि Camera1 ऐप्लिकेशन इससे कैसे जुड़ा होगा. (CameraX के कॉन्सेप्ट, नीले रंग में और Camera1 के कॉन्सेप्ट, हरे रंग में हैं.)

CameraX

CameraController / CameraProvider कॉन्फ़िगरेशन
झलक देखें ImageCapture VideoCapture ImageAnalysis
झलक दिखाने वाली सतह को मैनेज करना और उसे कैमरे पर सेट करना PictureCallback सेट करें और Camera पर takePicture() को कॉल करें कैमरा और MediaRecorder कॉन्फ़िगरेशन को किसी खास क्रम में मैनेज करना प्रीव्यू के ऊपर बनाया गया कस्टम विश्लेषण कोड
डिवाइस के हिसाब से कोड
डिवाइस रोटेशन और स्केलिंग मैनेजमेंट
कैमरा सेशन मैनेजमेंट (कैमरा चुनना, लाइफ़साइकल मैनेजमेंट)

Camera1

CameraX में काम करने की सुविधा और परफ़ॉर्मेंस

CameraX, Android 5.0 (एपीआई लेवल 21) और इसके बाद के वर्शन वाले डिवाइसों पर काम करता है. यह मौजूदा 98% से ज़्यादा Android डिवाइसों के बारे में बताता है. CameraX को डिवाइसों के बीच के अंतर को अपने-आप मैनेज करने के लिए बनाया गया है. इससे, आपके ऐप्लिकेशन में डिवाइस के हिसाब से कोड की ज़रूरत कम हो जाती है. इसके अलावा, हम अपने CameraX टेस्ट लैब में, Android 5.0 के बाद के सभी वर्शन पर 150 से ज़्यादा फ़िज़िकल डिवाइसों की जांच करते हैं. फ़िलहाल, टेस्ट लैब में मौजूद डिवाइसों की पूरी सूची देखी जा सकती है.

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

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

Android डेवलपमेंट के कॉन्सेप्ट

इस गाइड में यह माना गया है कि आपको Android डेवलपमेंट के बारे में सामान्य जानकारी है. यहां कुछ कॉन्सेप्ट दिए गए हैं. इनकी मदद से, नीचे दिए गए कोड को समझने में मदद मिलती है:

  • व्यू बाइंडिंग, आपकी एक्सएमएल लेआउट फ़ाइलों के लिए एक बाइंडिंग क्लास जनरेट करती है. इससे, ऐक्टिविटी में अपने व्यू को आसानी से रेफ़रंस किया जा सकता है. ऐसा, यहां दिए गए कई कोड स्निपेट में किया गया है. व्यू बाइंडिंग और findViewById() (व्यू का रेफ़रंस देने का पिछला तरीका) के बीच कुछ अंतर हैं. हालांकि, नीचे दिए गए कोड में, व्यू बाइंडिंग की लाइनों को मिलते-जुलते findViewById() कॉल से बदला जा सकता है.
  • एसिंक्रोनस कोरूटीन, Kotlin 1.3 में जोड़ा गया एक ऐसा डिज़ाइन पैटर्न है जो एक साथ कई काम करने की सुविधा देता है. इसका इस्तेमाल, CameraX के उन तरीकों को मैनेज करने के लिए किया जा सकता है जो ListenableFuture दिखाते हैं. Jetpack के 1.1.0 वर्शन में, Concurrent लाइब्रेरी की मदद से, यह काम आसानी से किया जा सकता है. अपने ऐप्लिकेशन में एसिंक्रोनस कोरूटीन जोड़ने के लिए:
    1. अपनी Gradle फ़ाइल में implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0") जोड़ें.
    2. ListenableFuture दिखाने वाला कोई भी CameraX कोड, launch ब्लॉक या रोकने वाले फ़ंक्शन में डालें.
    3. ListenableFuture दिखाने वाले फ़ंक्शन कॉल में, await() कॉल जोड़ें.
    4. कोरूटीन के काम करने के तरीके के बारे में ज़्यादा जानने के लिए, कोरूटीन शुरू करना गाइड देखें.

सामान्य स्थितियों को माइग्रेट करना

इस सेक्शन में, सामान्य स्थितियों को Camera1 से CameraX पर माइग्रेट करने का तरीका बताया गया है. हर स्थिति में, Camera1, CameraX CameraProvider , और CameraX CameraController को लागू करने के तरीके के बारे में बताया गया है.

कैमरा चुनना

अपने कैमरा ऐप्लिकेशन में, सबसे पहले अलग-अलग कैमरे चुनने का तरीका उपलब्ध कराएं.

Camera1

Camera1 में, पीछे वाला पहला कैमरा खोलने के लिए, Camera.open() को बिना किसी पैरामीटर के कॉल किया जा सकता है. इसके अलावा, जिस कैमरे को खोलना है उसके लिए कोई पूर्णांक आईडी भी पास किया जा सकता है. यहां एक उदाहरण दिया गया है कि यह कैसा दिख सकता है:

// Camera1: select a camera from id.

// Note: opening the camera is a non-trivial task, and it shouldn't be
// called from the main thread, unlike CameraX calls, which can be
// on the main thread since CameraX kicks off background threads
// internally as needed.

private fun safeCameraOpen(id: Int): Boolean {
    return try {
        releaseCameraAndPreview()
        camera = Camera.open(id)
        true
    } catch (e: Exception) {
        Log.e(TAG, "failed to open camera", e)
        false
    }
}

private fun releaseCameraAndPreview() {
    preview?.setCamera(null)
    camera?.release()
    camera = null
}

CameraX: CameraController

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

CameraController के साथ डिफ़ॉल्ट बैक कैमरे का इस्तेमाल करने के लिए, यहां CameraX कोड दिया गया है:

// CameraX: select a camera with CameraController

var cameraController = LifecycleCameraController(baseContext)
val selector = CameraSelector.Builder()
    .requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
cameraController.cameraSelector = selector

CameraX: CameraProvider

यहां CameraProvider के साथ डिफ़ॉल्ट फ़्रंट कैमरा चुनने का उदाहरण दिया गया है (CameraController या CameraProvider के साथ, फ़्रंट या बैक कैमरे में से किसी एक का इस्तेमाल किया जा सकता है):

// CameraX: select a camera with CameraProvider.

// Use await() within a suspend function to get CameraProvider instance.
// For more details on await(), see the "Android development concepts"
// section above.
private suspend fun startCamera() {
    val cameraProvider = ProcessCameraProvider.getInstance(this).await()

    // Set up UseCases (more on UseCases in later scenarios)
    var useCases:Array = ...

    // Set the cameraSelector to use the default front-facing (selfie)
    // camera.
    val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA

    try {
        // Unbind UseCases before rebinding.
        cameraProvider.unbindAll()

        // Bind UseCases to camera. This function returns a camera
        // object which can be used to perform operations like zoom,
        // flash, and focus.
        var camera = cameraProvider.bindToLifecycle(
            this, cameraSelector, useCases)

    } catch(exc: Exception) {
        Log.e(TAG, "UseCase binding failed", exc)
    }
})

...

// Call startCamera in the setup flow of your app, such as in onViewCreated.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    ...

    lifecycleScope.launch {
        startCamera()
    }
}

अगर आपको यह कंट्रोल करना है कि कौनसा कैमरा चुना जाए, तो CameraX में भी ऐसा किया जा सकता है. इसके लिए, getAvailableCameraInfos() को कॉल करके CameraProvider का इस्तेमाल करें. इससे आपको isFocusMeteringSupported() जैसी कुछ कैमरा प्रॉपर्टी की जांच करने के लिए, CameraInfo ऑब्जेक्ट मिलता है. इसके बाद, इसे CameraSelector में बदला जा सकता है, ताकि ऊपर दिए गए उदाहरणों में बताए गए तरीके के मुताबिक, इसका इस्तेमाल किया जा सके.CameraInfo.getCameraSelector()

Camera2CameraInfo क्लास का इस्तेमाल करके, हर कैमरे के बारे में ज़्यादा जानकारी पाई जा सकती है. अपने कैमरे के जिस डेटा को ऐक्सेस करना है उसके लिए, getCameraCharacteristic() कोड का इस्तेमाल करके कॉल करें. उन सभी कुंजियों की सूची के लिए CameraCharacteristics क्लास देखें जिनके लिए क्वेरी की जा सकती है.

यहां कस्टम checkFocalLength() फ़ंक्शन का इस्तेमाल करने का उदाहरण दिया गया है. इसे खुद से तय किया जा सकता है:

// CameraX: get a cameraSelector for first camera that matches the criteria
// defined in checkFocalLength().

val cameraInfo = cameraProvider.getAvailableCameraInfos()
    .first { cameraInfo ->
        val focalLengths = Camera2CameraInfo.from(cameraInfo)
            .getCameraCharacteristic(
                CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS
            )
        return checkFocalLength(focalLengths)
    }
val cameraSelector = cameraInfo.getCameraSelector()

झलक दिखाना

ज़्यादातर कैमरा ऐप्लिकेशन को किसी समय पर कैमरे का फ़ीड, स्क्रीन पर दिखाना होता है. Camera1 के साथ, आपको लाइफ़साइकल कॉलबैक को सही तरीके से मैनेज करना होगा. साथ ही, आपको झलक के लिए रोटेशन और स्केलिंग तय करनी होगी.

इसके अलावा, Camera1 में आपको यह तय करना होगा कि झलक दिखाने के लिए, TextureView या SurfaceView में से किसका इस्तेमाल करना है. दोनों विकल्पों के साथ कुछ समस्याएं आती हैं. दोनों ही मामलों में, Camera1 के लिए ज़रूरी है कि आप रोटेशन और स्केलिंग को सही तरीके से मैनेज करें. वहीं दूसरी ओर, CameraX के PreviewView में TextureView और SurfaceView, दोनों के लिए पहले से लागू सुविधाएं हैं. CameraX यह तय करता है कि कौनसा तरीका सबसे सही है. यह कई बातों पर निर्भर करता है, जैसे कि डिवाइस का टाइप और आपका ऐप्लिकेशन किस Android वर्शन पर चल रहा है. अगर दोनों में से कोई भी तरीका काम करता है, तो PreviewView.ImplementationMode का इस्तेमाल करके अपनी पसंद बताएं. COMPATIBLE विकल्प, झलक के लिए TextureView का इस्तेमाल करता है. साथ ही, PERFORMANCE वैल्यू, SurfaceView का इस्तेमाल करती है (जब मुमकिन हो).

Camera1

झलक दिखाने के लिए, आपको अपनी Preview क्लास लिखनी होगी. साथ ही, android.view.SurfaceHolder.Callback इंटरफ़ेस को लागू करना होगा. इसका इस्तेमाल, कैमरा हार्डवेयर से ऐप्लिकेशन में इमेज का डेटा भेजने के लिए किया जाता है. इसके बाद, लाइव इमेज की झलक शुरू करने से पहले, Preview क्लास को Camera ऑब्जेक्ट को पास करना होगा.

// Camera1: set up a camera preview.

class Preview(
        context: Context,
        private val camera: Camera
) : SurfaceView(context), SurfaceHolder.Callback {

    private val holder: SurfaceHolder = holder.apply {
        addCallback(this@Preview)
        setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)
    }

    override fun surfaceCreated(holder: SurfaceHolder) {
        // The Surface has been created, now tell the camera
        // where to draw the preview.
        camera.apply {
            try {
                setPreviewDisplay(holder)
                startPreview()
            } catch (e: IOException) {
                Log.d(TAG, "error setting camera preview", e)
            }
        }
    }

    override fun surfaceDestroyed(holder: SurfaceHolder) {
        // Take care of releasing the Camera preview in your activity.
    }

    override fun surfaceChanged(holder: SurfaceHolder, format: Int,
                                w: Int, h: Int) {
        // If your preview can change or rotate, take care of those
        // events here. Make sure to stop the preview before resizing
        // or reformatting it.
        if (holder.surface == null) {
            return  // The preview surface does not exist.
        }

        // Stop preview before making changes.
        try {
            camera.stopPreview()
        } catch (e: Exception) {
            // Tried to stop a non-existent preview; nothing to do.
        }

        // Set preview size and make any resize, rotate or
        // reformatting changes here.

        // Start preview with new settings.
        camera.apply {
            try {
                setPreviewDisplay(holder)
                startPreview()
            } catch (e: Exception) {
                Log.d(TAG, "error starting camera preview", e)
            }
        }
    }
}

class CameraActivity : AppCompatActivity() {
    private lateinit var viewBinding: ActivityMainBinding
    private var camera: Camera? = null
    private var preview: Preview? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(viewBinding.root)

        // Create an instance of Camera.
        camera = getCameraInstance()

        preview = camera?.let {
            // Create the Preview view.
            Preview(this, it)
        }

        // Set the Preview view as the content of the activity.
        val cameraPreview: FrameLayout = viewBinding.cameraPreview
        cameraPreview.addView(preview)
    }
}

CameraX: CameraController

CameraX में, डेवलपर को बहुत कम चीज़ें मैनेज करनी होती हैं. अगर आपने CameraController का इस्तेमाल किया है, तो आपको PreviewView का भी इस्तेमाल करना होगा. इसका मतलब है कि Preview UseCase का इस्तेमाल किया गया है. इससे सेटअप करने में काफ़ी कम मेहनत लगती है:

// CameraX: set up a camera preview with a CameraController.

class MainActivity : AppCompatActivity() {
    private lateinit var viewBinding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(viewBinding.root)

        // Create the CameraController and set it on the previewView.
        var cameraController = LifecycleCameraController(baseContext)
        cameraController.bindToLifecycle(this)
        val previewView: PreviewView = viewBinding.cameraPreview
        previewView.controller = cameraController
    }
}

CameraX: CameraProvider

CameraX के CameraProvider का इस्तेमाल करने पर, आपको PreviewView का इस्तेमाल करने की ज़रूरत नहीं होती. हालांकि, इससे भी Camera1 की तुलना में झलक देखने की सुविधा को सेट अप करना काफ़ी आसान हो जाता है. उदाहरण के तौर पर, इस उदाहरण में PreviewView का इस्तेमाल किया गया है. हालांकि, अगर आपकी ज़रूरतें ज़्यादा जटिल हैं, तो setSurfaceProvider() में पास करने के लिए कस्टम SurfaceProvider लिखा जा सकता है.

यहां Preview UseCase का मतलब CameraController की तरह नहीं है, इसलिए आपको इसे सेट अप करना होगा:

// CameraX: set up a camera preview with a CameraProvider.

// Use await() within a suspend function to get CameraProvider instance.
// For more details on await(), see the "Android development concepts"
// section above.
private suspend fun startCamera() {
    val cameraProvider = ProcessCameraProvider.getInstance(this).await()

    // Create Preview UseCase.
    val preview = Preview.Builder()
        .build()
        .also {
            it.setSurfaceProvider(
                viewBinding.viewFinder.surfaceProvider
            )
        }

    // Select default back camera.
    val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

    try {
        // Unbind UseCases before rebinding.
        cameraProvider.unbindAll()

        // Bind UseCases to camera. This function returns a camera
        // object which can be used to perform operations like zoom,
        // flash, and focus.
        var camera = cameraProvider.bindToLifecycle(
            this, cameraSelector, useCases)

    } catch(exc: Exception) {
        Log.e(TAG, "UseCase binding failed", exc)
    }
})

...

// Call startCamera() in the setup flow of your app, such as in onViewCreated.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    ...

    lifecycleScope.launch {
        startCamera()
    }
}

फ़ोकस करने के लिए टैप करें

जब कैमरे की झलक स्क्रीन पर दिख रही हो, तो उपयोगकर्ता झलक पर टैप करके फ़ोकस पॉइंट सेट कर सकता है.

Camera1

Camera1 में टैप-टू-फ़ोकस की सुविधा लागू करने के लिए, आपको सबसे सही फ़ोकस Area का हिसाब लगाना होगा. इससे यह पता चलता है कि Camera को कहां फ़ोकस करना चाहिए. इस Area को setFocusAreas() में भेजा जाता है. साथ ही, आपको Camera पर काम करने वाला फ़ोकस मोड सेट करना होगा. फ़ोकस एरिया का असर सिर्फ़ तब पड़ता है, जब फ़ोकस मोड FOCUS_MODE_AUTO, FOCUS_MODE_MACRO, FOCUS_MODE_CONTINUOUS_VIDEO या FOCUS_MODE_CONTINUOUS_PICTURE हो.

हर Area, तय किए गए वज़न वाला एक रेक्टैंगल होता है. अहमियत की वैल्यू 1 से 1,000 के बीच होती है. अगर एक से ज़्यादा फ़ोकस Areas सेट किए जाते हैं, तो इसका इस्तेमाल फ़ोकस Areas को प्राथमिकता देने के लिए किया जाता है. इस उदाहरण में सिर्फ़ एक Area का इस्तेमाल किया गया है. इसलिए, वेट की वैल्यू का कोई फ़र्क़ नहीं पड़ता. रेक्टैंगल के निर्देशांक -1,000 से 1,000 तक के होते हैं. सबसे ऊपर बाईं ओर मौजूद पॉइंट (-1000, -1000) है. सबसे नीचे दाईं ओर मौजूद बिंदु (1,000, 1,000) है. दिशा, सेंसर के ओरिएंटेशन के हिसाब से तय होती है. इसका मतलब है कि सेंसर किस दिशा में है. Camera.setDisplayOrientation() के घूमने या मिरर होने से, दिशा पर कोई असर नहीं पड़ता. इसलिए, आपको टच इवेंट के निर्देशांकों को सेंसर के निर्देशांकों में बदलना होगा.

// Camera1: implement tap-to-focus.

class TapToFocusHandler : Camera.AutoFocusCallback {
    private fun handleFocus(event: MotionEvent) {
        val camera = camera ?: return
        val parameters = try {
            camera.getParameters()
        } catch (e: RuntimeException) {
            return
        }

        // Cancel previous auto-focus function, if one was in progress.
        camera.cancelAutoFocus()

        // Create focus Area.
        val rect = calculateFocusAreaCoordinates(event.x, event.y)
        val weight = 1  // This value's not important since there's only 1 Area.
        val focusArea = Camera.Area(rect, weight)

        // Set the focus parameters.
        parameters.setFocusMode(Parameters.FOCUS_MODE_AUTO)
        parameters.setFocusAreas(listOf(focusArea))

        // Set the parameters back on the camera and initiate auto-focus.
        camera.setParameters(parameters)
        camera.autoFocus(this)
    }

    private fun calculateFocusAreaCoordinates(x: Int, y: Int) {
        // Define the size of the Area to be returned. This value
        // should be optimized for your app.
        val focusAreaSize = 100

        // You must define functions to rotate and scale the x and y values to
        // be values between 0 and 1, where (0, 0) is the upper left-hand side
        // of the preview, and (1, 1) is the lower right-hand side.
        val normalizedX = (rotateAndScaleX(x) - 0.5) * 2000
        val normalizedY = (rotateAndScaleY(y) - 0.5) * 2000

        // Calculate the values for left, top, right, and bottom of the Rect to
        // be returned. If the Rect would extend beyond the allowed values of
        // (-1000, -1000, 1000, 1000), then crop the values to fit inside of
        // that boundary.
        val left = max(normalizedX - (focusAreaSize / 2), -1000)
        val top = max(normalizedY - (focusAreaSize / 2), -1000)
        val right = min(left + focusAreaSize, 1000)
        val bottom = min(top + focusAreaSize, 1000)

        return Rect(left, top, left + focusAreaSize, top + focusAreaSize)
    }

    override fun onAutoFocus(focused: Boolean, camera: Camera) {
        if (!focused) {
            Log.d(TAG, "tap-to-focus failed")
        }
    }
}

CameraX: CameraController

CameraController, टैप-टू-फ़ोकस की सुविधा को अपने-आप मैनेज करने के लिए, PreviewView के टच इवेंट को सुनता है. setTapToFocusEnabled() की मदद से, टैप-टू-फ़ोकस की सुविधा को चालू और बंद किया जा सकता है. साथ ही, इसके लिए इस्तेमाल होने वाले गेट्टर isTapToFocusEnabled() की मदद से, वैल्यू देखी जा सकती है.

CameraController पर फ़ोकस की स्थिति में होने वाले बदलावों को ट्रैक करने के लिए, getTapToFocusState() तरीका, LiveData ऑब्जेक्ट दिखाता है.

// CameraX: track the state of tap-to-focus over the Lifecycle of a PreviewView,
// with handlers you can define for focused, not focused, and failed states.

val tapToFocusStateObserver = Observer { state ->
    when (state) {
        CameraController.TAP_TO_FOCUS_NOT_STARTED ->
            Log.d(TAG, "tap-to-focus init")
        CameraController.TAP_TO_FOCUS_STARTED ->
            Log.d(TAG, "tap-to-focus started")
        CameraController.TAP_TO_FOCUS_FOCUSED ->
            Log.d(TAG, "tap-to-focus finished (focus successful)")
        CameraController.TAP_TO_FOCUS_NOT_FOCUSED ->
            Log.d(TAG, "tap-to-focus finished (focused unsuccessful)")
        CameraController.TAP_TO_FOCUS_FAILED ->
            Log.d(TAG, "tap-to-focus failed")
    }
}

cameraController.getTapToFocusState().observe(this, tapToFocusStateObserver)

CameraX: CameraProvider

CameraProvider का इस्तेमाल करते समय, टैप करके फ़ोकस करने की सुविधा को चालू करने के लिए कुछ सेटअप करना ज़रूरी है. इस उदाहरण में यह माना गया है कि PreviewView का इस्तेमाल किया जा रहा है. अगर ऐसा नहीं है, तो आपको अपने कस्टम Surface पर लागू करने के लिए, लॉजिक में बदलाव करना होगा.

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

  1. टैप इवेंट को मैनेज करने के लिए, जेस्चर डिटेक्टर सेट अप करें.
  2. टैप इवेंट के साथ, MeteringPointFactory.createPoint() का इस्तेमाल करके MeteringPoint बनाएं.
  3. MeteringPoint की मदद से, FocusMeteringAction बनाएं.
  4. Camera में मौजूद CameraControl ऑब्जेक्ट (bindToLifecycle() से मिला) के साथ, startFocusAndMetering() को कॉल करें. इसके लिए, FocusMeteringAction को पास करें.
  5. (ज़रूरी नहीं) FocusMeteringResult का जवाब दें.
  6. PreviewView.setOnTouchListener() में टच इवेंट के जवाब में, अपने जेस्चर डिटेक्टर को सेट करें.
// CameraX: implement tap-to-focus with CameraProvider.

// Define a gesture detector to respond to tap events and call
// startFocusAndMetering on CameraControl. If you want to use a
// coroutine with await() to check the result of focusing, see the
// "Android development concepts" section above.
val gestureDetector = GestureDetectorCompat(context,
    object : SimpleOnGestureListener() {
        override fun onSingleTapUp(e: MotionEvent): Boolean {
            val previewView = previewView ?: return
            val camera = camera ?: return
            val meteringPointFactory = previewView.meteringPointFactory
            val focusPoint = meteringPointFactory.createPoint(e.x, e.y)
            val meteringAction = FocusMeteringAction
                .Builder(meteringPoint).build()
            lifecycleScope.launch {
                val focusResult = camera.cameraControl
                    .startFocusAndMetering(meteringAction).await()
                if (!result.isFocusSuccessful()) {
                    Log.d(TAG, "tap-to-focus failed")
                }
            }
        }
    }
)

...

// Set the gestureDetector in a touch listener on the PreviewView.
previewView.setOnTouchListener { _, event ->
    // See pinch-to-zooom scenario for scaleGestureDetector definition.
    var didConsume = scaleGestureDetector.onTouchEvent(event)
    if (!scaleGestureDetector.isInProgress) {
        didConsume = gestureDetector.onTouchEvent(event)
    }
    didConsume
}

पिंच करके ज़ूम करना

झलक को ज़ूम इन और ज़ूम आउट करना, कैमरे की झलक में सीधे तौर पर बदलाव करने का एक और सामान्य तरीका है. डिवाइसों पर कैमरों की संख्या बढ़ने के साथ, उपयोगकर्ताओं को यह उम्मीद भी होती है कि ज़ूम करने पर, सबसे अच्छे फ़ोकल लेंथ वाला लेंस अपने-आप चुन लिया जाए.

Camera1

Camera1 का इस्तेमाल करके ज़ूम करने के दो तरीके हैं. Camera.startSmoothZoom() का तरीका, ज़ूम के मौजूदा लेवल से उस लेवल पर ऐनिमेशन करता है जिसे आपने डाला है. Camera.Parameters.setZoom() तरीका, सीधे उस ज़ूम लेवल पर ले जाता है जिसे आपने डाला है. इनमें से किसी एक का इस्तेमाल करने से पहले, isSmoothZoomSupported() या isZoomSupported() को दबाकर पक्का करें कि आपके कैमरे पर ज़ूम करने के लिए, ज़रूरी तरीके उपलब्ध हों.

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

// Camera1: implement pinch-to-zoom.

// Define a scale gesture detector to respond to pinch events and call
// setZoom on Camera.Parameters.
val scaleGestureDetector = ScaleGestureDetector(context,
    object : ScaleGestureDetector.OnScaleGestureListener {
        override fun onScale(detector: ScaleGestureDetector): Boolean {
            val camera = camera ?: return false
            val parameters = try {
                camera.parameters
            } catch (e: RuntimeException) {
                return false
            }

            // In case there is any focus happening, stop it.
            camera.cancelAutoFocus()

            // Set the zoom level on the Camera.Parameters, and set
            // the Parameters back onto the Camera.
            val currentZoom = parameters.zoom
            parameters.setZoom(detector.scaleFactor * currentZoom)
        camera.setParameters(parameters)
            return true
        }
    }
)

// Define a View.OnTouchListener to attach to your preview view.
class ZoomTouchListener : View.OnTouchListener {
    override fun onTouch(v: View, event: MotionEvent): Boolean =
        scaleGestureDetector.onTouchEvent(event)
}

// Set a ZoomTouchListener to handle touch events on your preview view
// if zoom is supported by the current camera.
if (camera.getParameters().isZoomSupported()) {
    view.setOnTouchListener(ZoomTouchListener())
}

CameraX: CameraController

टैप-टू-फ़ोकस की तरह ही, CameraController भी पिंच-टू-ज़ूम को अपने-आप मैनेज करने के लिए, PreviewView के टच इवेंट को सुनता है. setPinchToZoomEnabled() की मदद से, पिंच-टू-ज़ूम की सुविधा को चालू और बंद किया जा सकता है. साथ ही, इसके लिए इस्तेमाल होने वाले getter isPinchToZoomEnabled() की मदद से वैल्यू देखी जा सकती है.

CameraController पर ZoomState में होने वाले बदलावों को ट्रैक करने के लिए, getZoomState() तरीका एक LiveData ऑब्जेक्ट दिखाता है.

// CameraX: track the state of pinch-to-zoom over the Lifecycle of
// a PreviewView, logging the linear zoom ratio.

val pinchToZoomStateObserver = Observer { state ->
    val zoomRatio = state.getZoomRatio()
    Log.d(TAG, "ptz-zoom-ratio $zoomRatio")
}

cameraController.getZoomState().observe(this, pinchToZoomStateObserver)

CameraX: CameraProvider

CameraProvider के साथ पिंच-टू-ज़ूम की सुविधा काम करे, इसके लिए कुछ सेटअप करना ज़रूरी है. अगर PreviewView का इस्तेमाल नहीं किया जा रहा है, तो आपको अपने कस्टम Surface पर लागू करने के लिए, लॉजिक में बदलाव करना होगा.

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

  1. पिंच इवेंट को मैनेज करने के लिए, स्केल जेस्चर डिटेक्टर सेट अप करें.
  2. Camera.CameraInfo ऑब्जेक्ट से ZoomState पाएं, जहां bindToLifecycle() को कॉल करने पर Camera का इंस्टेंस दिखता है.
  3. अगर ZoomState की वैल्यू zoomRatio है, तो उसे मौजूदा ज़ूम रेशियो के तौर पर सेव करें. अगर ZoomState पर कोई zoomRatio नहीं है, तो कैमरे के डिफ़ॉल्ट ज़ूम रेट (1.0) का इस्तेमाल करें.
  4. नया ज़ूम रेशियो तय करने के लिए, मौजूदा ज़ूम रेशियो को scaleFactor के साथ गुणा करें और उसे CameraControl.setZoomRatio() में डालें.
  5. PreviewView.setOnTouchListener() में टच इवेंट के जवाब में, अपने जेस्चर डिटेक्टर को सेट करें.
// CameraX: implement pinch-to-zoom with CameraProvider.

// Define a scale gesture detector to respond to pinch events and call
// setZoomRatio on CameraControl.
val scaleGestureDetector = ScaleGestureDetector(context,
    object : SimpleOnGestureListener() {
        override fun onScale(detector: ScaleGestureDetector): Boolean {
            val camera = camera ?: return
            val zoomState = camera.cameraInfo.zoomState
            val currentZoomRatio: Float = zoomState.value?.zoomRatio ?: 1f
            camera.cameraControl.setZoomRatio(
                detector.scaleFactor * currentZoomRatio
            )
        }
    }
)

...

// Set the scaleGestureDetector in a touch listener on the PreviewView.
previewView.setOnTouchListener { _, event ->
    var didConsume = scaleGestureDetector.onTouchEvent(event)
    if (!scaleGestureDetector.isInProgress) {
        // See pinch-to-zooom scenario for gestureDetector definition.
        didConsume = gestureDetector.onTouchEvent(event)
    }
    didConsume
}

फ़ोटो खींचना

इस सेक्शन में, फ़ोटो कैप्चर करने का तरीका बताया गया है. चाहे आपको शटर बटन दबाकर, टाइमर खत्म होने के बाद या अपनी पसंद के किसी अन्य इवेंट पर फ़ोटो कैप्चर करनी हो.

Camera1

Camera1 में, सबसे पहले Camera.PictureCallback तय किया जाता है, ताकि फ़ोटो का डेटा मैनेज किया जा सके. यहां JPEG इमेज डेटा को मैनेज करने के लिए, PictureCallback का एक आसान उदाहरण दिया गया है:

// Camera1: define a Camera.PictureCallback to handle JPEG data.

private val picture = Camera.PictureCallback { data, _ ->
    val pictureFile: File = getOutputMediaFile(MEDIA_TYPE_IMAGE) ?: run {
        Log.d(TAG,
              "error creating media file, check storage permissions")
        return@PictureCallback
    }

    try {
        val fos = FileOutputStream(pictureFile)
        fos.write(data)
        fos.close()
    } catch (e: FileNotFoundException) {
        Log.d(TAG, "file not found", e)
    } catch (e: IOException) {
        Log.d(TAG, "error accessing file", e)
    }
}

इसके बाद, जब भी आपको फ़ोटो लेनी हो, तो अपने Camera इंस्टेंस पर takePicture() का तरीका इस्तेमाल करें. इस takePicture() तरीके में, अलग-अलग डेटा टाइप के लिए तीन अलग-अलग पैरामीटर होते हैं. पहला पैरामीटर, ShutterCallback के लिए है (जिसे इस उदाहरण में नहीं बताया गया है). दूसरा पैरामीटर, PictureCallback के लिए है, ताकि वह रॉ (बिना कंप्रेस किए गए) कैमरा डेटा को मैनेज कर सके. तीसरे पैरामीटर का इस्तेमाल इस उदाहरण में किया गया है, क्योंकि यह JPEG इमेज डेटा को मैनेज करने के लिए PictureCallback है.

// Camera1: call takePicture on Camera instance, passing our PictureCallback.

camera?.takePicture(null, null, picture)

CameraX: CameraController

CameraX का CameraController, इमेज कैप्चर करने के लिए Camera1 की सुविधाओं को आसान बनाता है. इसके लिए, वह अपना takePicture() तरीका लागू करता है. यहां, MediaStore एंट्री को कॉन्फ़िगर करने के लिए फ़ंक्शन तय करें और वहां सेव करने के लिए फ़ोटो लें.

// CameraX: define a function that uses CameraController to take a photo.

private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"

private fun takePhoto() {
   // Create time stamped name and MediaStore entry.
   val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
              .format(System.currentTimeMillis())
   val contentValues = ContentValues().apply {
       put(MediaStore.MediaColumns.DISPLAY_NAME, name)
       put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
       if(Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
           put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image")
       }
   }

   // Create output options object which contains file + metadata.
   val outputOptions = ImageCapture.OutputFileOptions
       .Builder(context.getContentResolver(),
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
       .build()

   // Set up image capture listener, which is triggered after photo has
   // been taken.
   cameraController.takePicture(
       outputOptions,
       ContextCompat.getMainExecutor(this),
       object : ImageCapture.OnImageSavedCallback {
           override fun onError(e: ImageCaptureException) {
               Log.e(TAG, "photo capture failed", e)
           }

           override fun onImageSaved(
               output: ImageCapture.OutputFileResults
           ) {
               val msg = "Photo capture succeeded: ${output.savedUri}"
               Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
               Log.d(TAG, msg)
           }
       }
   )
}

CameraX: CameraProvider

CameraProvider का इस्तेमाल करके फ़ोटो लेने का तरीका, CameraController का इस्तेमाल करके फ़ोटो लेने के तरीके से काफ़ी हद तक मिलता-जुलता है. हालांकि, takePicture() को कॉल करने के लिए, पहले आपको कोई ऑब्जेक्ट बनाना होगा और ImageCapture UseCase को बांधना होगा:

// CameraX: create and bind an ImageCapture UseCase.

// Make a reference to the ImageCapture UseCase at a scope that can be accessed
// throughout the camera logic in your app.
private var imageCapture: ImageCapture? = null

...

// Create an ImageCapture instance (can be added with other
// UseCase definitions).
imageCapture = ImageCapture.Builder().build()

...

// Bind UseCases to camera (adding imageCapture along with preview here, but
// preview is not required to use imageCapture). This function returns a camera
// object which can be used to perform operations like zoom, flash, and focus.
var camera = cameraProvider.bindToLifecycle(
    this, cameraSelector, preview, imageCapture)

इसके बाद, जब भी आपको कोई फ़ोटो कैप्चर करनी हो, तो ImageCapture.takePicture() को कॉल करें. takePhoto() फ़ंक्शन के पूरे उदाहरण के लिए, इस सेक्शन में CameraController कोड देखें.

// CameraX: define a function that uses CameraController to take a photo.

private fun takePhoto() {
    // Get a stable reference of the modifiable ImageCapture UseCase.
    val imageCapture = imageCapture ?: return

    ...

    // Call takePicture on imageCapture instance.
    imageCapture.takePicture(
        ...
    )
}

वीडियो रिकॉर्ड करना

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

आपको पता चलेगा कि CameraX, आपके लिए कई जटिल चीज़ों को हैंडल करता है.

Camera1

Camera1 का इस्तेमाल करके वीडियो कैप्चर करने के लिए, Camera और MediaRecorder को ध्यान से मैनेज करना ज़रूरी है. साथ ही, तरीकों को किसी खास क्रम में कॉल करना ज़रूरी है. आपका ऐप्लिकेशन सही तरीके से काम करे, इसके लिए आपको इस क्रम का पालन करना होगा:

  1. कैमरा खोलें.
  2. झलक तैयार करें और उसे शुरू करें. ऐसा तब करें, जब आपका ऐप्लिकेशन रिकॉर्ड किया जा रहा वीडियो दिखाता हो. आम तौर पर, ऐसा होता है.
  3. MediaRecorder को कैमरे का इस्तेमाल करने की अनुमति देने के लिए, Camera.unlock() बोलकर कैमरे को अनलॉक करें.
  4. MediaRecorder पर इन तरीकों को कॉल करके रिकॉर्डिंग कॉन्फ़िगर करें:
    1. अपने Camera इंस्टेंस को setCamera(camera) से कनेक्ट करें.
    2. setAudioSource(MediaRecorder.AudioSource.CAMCORDER) पर कॉल करें.
    3. setVideoSource(MediaRecorder.VideoSource.CAMERA) पर कॉल करें.
    4. क्वालिटी सेट करने के लिए, setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_1080P)) को कॉल करें. क्वालिटी के सभी विकल्पों के लिए, CamcorderProfile देखें.
    5. setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString()) पर कॉल करें.
    6. अगर आपके ऐप्लिकेशन में वीडियो की झलक मौजूद है, तो setPreviewDisplay(preview?.holder?.surface) को कॉल करें.
    7. setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) पर कॉल करें.
    8. setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT) पर कॉल करें.
    9. setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT) पर कॉल करें.
    10. अपने MediaRecorder के कॉन्फ़िगरेशन को पूरा करने के लिए, prepare() पर कॉल करें.
  5. रिकॉर्डिंग शुरू करने के लिए, MediaRecorder.start() को कॉल करें.
  6. रिकॉर्डिंग रोकने के लिए, इन तरीकों का इस्तेमाल करें. फिर से, इस क्रम में काम करें:
    1. MediaRecorder.stop() पर कॉल करें.
    2. इसके अलावा, MediaRecorder.reset() को कॉल करके, मौजूदा MediaRecorder कॉन्फ़िगरेशन को हटाया जा सकता है.
    3. MediaRecorder.release() पर कॉल करें.
    4. कैमरे को लॉक करें, ताकि आने वाले समय में MediaRecorder सेशन में Camera.lock() को कॉल करके, कैमरे का इस्तेमाल किया जा सके.
  7. झलक देखने की सुविधा बंद करने के लिए, Camera.stopPreview() को कॉल करें.
  8. आखिर में, Camera को रिलीज़ करने के लिए, Camera.release() को कॉल करें, ताकि अन्य प्रोसेस इसका इस्तेमाल कर सकें.

इन सभी चरणों को एक साथ यहां बताया गया है:

// Camera1: set up a MediaRecorder and a function to start and stop video
// recording.

// Make a reference to the MediaRecorder at a scope that can be accessed
// throughout the camera logic in your app.
private var mediaRecorder: MediaRecorder? = null
private var isRecording = false

...

private fun prepareMediaRecorder(): Boolean {
    mediaRecorder = MediaRecorder()

    // Unlock and set camera to MediaRecorder.
    camera?.unlock()

    mediaRecorder?.run {
        setCamera(camera)

        // Set the audio and video sources.
        setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
        setVideoSource(MediaRecorder.VideoSource.CAMERA)

        // Set a CamcorderProfile (requires API Level 8 or higher).
        setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH))

        // Set the output file.
        setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString())

        // Set the preview output.
        setPreviewDisplay(preview?.holder?.surface)

        setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
        setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT)
        setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT)

        // Prepare configured MediaRecorder.
        return try {
            prepare()
            true
        } catch (e: IllegalStateException) {
            Log.d(TAG, "preparing MediaRecorder failed", e)
            releaseMediaRecorder()
            false
        } catch (e: IOException) {
            Log.d(TAG, "setting MediaRecorder file failed", e)
            releaseMediaRecorder()
            false
        }
    }
    return false
}

private fun releaseMediaRecorder() {
    mediaRecorder?.reset()
    mediaRecorder?.release()
    mediaRecorder = null
    camera?.lock()
}

private fun startStopVideo() {
    if (isRecording) {
        // Stop recording and release camera.
        mediaRecorder?.stop()
        releaseMediaRecorder()
        camera?.lock()
        isRecording = false

        // This is a good place to inform user that video recording has stopped.
    } else {
        // Initialize video camera.
        if (prepareVideoRecorder()) {
            // Camera is available and unlocked, MediaRecorder is prepared, now
            // you can start recording.
            mediaRecorder?.start()
            isRecording = true

            // This is a good place to inform the user that recording has
            // started.
        } else {
            // Prepare didn't work, release the camera.
            releaseMediaRecorder()

            // Inform user here.
        }
    }
}

CameraX: CameraController

CameraX के CameraController की मदद से, ImageCapture, VideoCapture, और ImageAnalysis UseCase को अलग-अलग टॉगल किया जा सकता है. ऐसा तब तक किया जा सकता है, जब तक UseCases की सूची का एक साथ इस्तेमाल किया जा सकता है. ImageCapture और ImageAnalysis UseCase डिफ़ॉल्ट रूप से चालू होते हैं. इसलिए, आपको फ़ोटो लेने के लिए setEnabledUseCases() को कॉल करने की ज़रूरत नहीं पड़ती.

वीडियो रिकॉर्डिंग के लिए CameraController का इस्तेमाल करने के लिए, आपको सबसे पहले setEnabledUseCases() का इस्तेमाल करके, VideoCapture UseCase को अनुमति देनी होगी.

// CameraX: Enable VideoCapture UseCase on CameraController.

cameraController.setEnabledUseCases(VIDEO_CAPTURE);

वीडियो रिकॉर्ड करने के लिए, CameraController.startRecording() फ़ंक्शन का इस्तेमाल किया जा सकता है. इस फ़ंक्शन की मदद से, रिकॉर्ड किए गए वीडियो को File में सेव किया जा सकता है. इसका उदाहरण यहां दिया गया है. इसके अलावा, आपको एक Executor और एक ऐसी क्लास पास करनी होगी जो सफलता और गड़बड़ी के कॉलबैक को मैनेज करने के लिए, OnVideoSavedCallback को लागू करती है. रिकॉर्डिंग खत्म होने पर, कॉल करें CameraController.stopRecording().

ध्यान दें: अगर CameraX 1.3.0-alpha02 या इसके बाद के वर्शन का इस्तेमाल किया जा रहा है, तो एक और AudioConfig पैरामीटर होता है. इसकी मदद से, वीडियो में ऑडियो रिकॉर्डिंग की सुविधा चालू या बंद की जा सकती है. ऑडियो रिकॉर्डिंग की सुविधा चालू करने के लिए, आपको यह पक्का करना होगा कि आपके पास माइक्रोफ़ोन ऐक्सेस करने की अनुमतियां हों. इसके अलावा, 1.3.0-alpha02 में stopRecording() तरीका हटा दिया गया है और startRecording() एक Recording ऑब्जेक्ट दिखाता है. इसका इस्तेमाल, वीडियो रिकॉर्डिंग को रोकने, फिर से शुरू करने, और बंद करने के लिए किया जा सकता है.

// CameraX: implement video capture with CameraController.

private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"

// Define a VideoSaveCallback class for handling success and error states.
class VideoSaveCallback : OnVideoSavedCallback {
    override fun onVideoSaved(outputFileResults: OutputFileResults) {
        val msg = "Video capture succeeded: ${outputFileResults.savedUri}"
        Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
        Log.d(TAG, msg)
    }

    override fun onError(videoCaptureError: Int, message: String,
                         cause: Throwable?) {
        Log.d(TAG, "error saving video: $message", cause)
    }
}

private fun startStopVideo() {
    if (cameraController.isRecording()) {
        // Stop the current recording session.
        cameraController.stopRecording()
        return
    }

    // Define the File options for saving the video.
    val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
        .format(System.currentTimeMillis())

    val outputFileOptions = OutputFileOptions
        .Builder(File(this.filesDir, name))
        .build()

    // Call startRecording on the CameraController.
    cameraController.startRecording(
        outputFileOptions,
        ContextCompat.getMainExecutor(this),
        VideoSaveCallback()
    )
}

CameraX: CameraProvider

अगर CameraProvider का इस्तेमाल किया जा रहा है, तो आपको VideoCapture UseCase बनाना होगा और Recorder ऑब्जेक्ट को पास करना होगा. Recorder.Builder पर, वीडियो की क्वालिटी सेट की जा सकती है. इसके अलावा, FallbackStrategy भी सेट किया जा सकता है. यह विकल्प तब काम आता है, जब कोई डिवाइस आपकी पसंद की क्वालिटी की ज़रूरी शर्तों को पूरा नहीं कर पाता. इसके बाद, अपने दूसरे UseCase के साथ VideoCapture इंस्टेंस को CameraProvider से बांधें.

// CameraX: create and bind a VideoCapture UseCase with CameraProvider.

// Make a reference to the VideoCapture UseCase and Recording at a
// scope that can be accessed throughout the camera logic in your app.
private lateinit var videoCapture: VideoCapture
private var recording: Recording? = null

...

// Create a Recorder instance to set on a VideoCapture instance (can be
// added with other UseCase definitions).
val recorder = Recorder.Builder()
    .setQualitySelector(QualitySelector.from(Quality.FHD))
    .build()
videoCapture = VideoCapture.withOutput(recorder)

...

// Bind UseCases to camera (adding videoCapture along with preview here, but
// preview is not required to use videoCapture). This function returns a camera
// object which can be used to perform operations like zoom, flash, and focus.
var camera = cameraProvider.bindToLifecycle(
    this, cameraSelector, preview, videoCapture)

इस समय, Recorder को videoCapture.output प्रॉपर्टी पर ऐक्सेस किया जा सकता है. Recorder, File, ParcelFileDescriptor या MediaStore में सेव की गई वीडियो रिकॉर्डिंग शुरू कर सकता है. इस उदाहरण में MediaStore का इस्तेमाल किया गया है.

Recorder को तैयार करने के लिए, कई तरीके हैं. MediaStore आउटपुट के विकल्प सेट करने के लिए, prepareRecording() को कॉल करें. अगर आपके ऐप्लिकेशन के पास डिवाइस के माइक्रोफ़ोन का इस्तेमाल करने की अनुमति है, तो withAudioEnabled() को भी कॉल करें. इसके बाद, रिकॉर्डिंग शुरू करने के लिए start() को कॉल करें. साथ ही, वीडियो रिकॉर्ड करने के इवेंट को मैनेज करने के लिए, कोई कॉन्टेक्स्ट और Consumer<VideoRecordEvent> इवेंट लिसनर पास करें. अगर रिकॉर्डिंग शुरू हो जाती है, तो रिकॉर्डिंग को रोकने, फिर से शुरू करने या बंद करने के लिए, रिकॉर्डिंग के आइडेंटिफ़ायर Recording का इस्तेमाल किया जा सकता है.

// CameraX: implement video capture with CameraProvider.

private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"

private fun startStopVideo() {
   val videoCapture = this.videoCapture ?: return

   if (recording != null) {
       // Stop the current recording session.
       recording.stop()
       recording = null
       return
   }

   // Create and start a new recording session.
   val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
       .format(System.currentTimeMillis())
   val contentValues = ContentValues().apply {
       put(MediaStore.MediaColumns.DISPLAY_NAME, name)
       put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4")
       if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
           put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/CameraX-Video")
       }
   }

   val mediaStoreOutputOptions = MediaStoreOutputOptions
       .Builder(contentResolver, MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
       .setContentValues(contentValues)
       .build()

   recording = videoCapture.output
       .prepareRecording(this, mediaStoreOutputOptions)
       .withAudioEnabled()
       .start(ContextCompat.getMainExecutor(this)) { recordEvent ->
           when(recordEvent) {
               is VideoRecordEvent.Start -> {
                   viewBinding.videoCaptureButton.apply {
                       text = getString(R.string.stop_capture)
                       isEnabled = true
                   }
               }
               is VideoRecordEvent.Finalize -> {
                   if (!recordEvent.hasError()) {
                       val msg = "Video capture succeeded: " +
                           "${recordEvent.outputResults.outputUri}"
                       Toast.makeText(
                           baseContext, msg, Toast.LENGTH_SHORT
                       ).show()
                       Log.d(TAG, msg)
                   } else {
                       recording?.close()
                       recording = null
                       Log.e(TAG, "video capture ends with error",
                             recordEvent.error)
                   }
                   viewBinding.videoCaptureButton.apply {
                       text = getString(R.string.start_capture)
                       isEnabled = true
                   }
               }
           }
       }
}

अन्य संसाधन

हमारे Camera Samples GitHub Repository में, CameraX के कई ऐप्लिकेशन मौजूद हैं. इन सैंपल से पता चलता है कि इस गाइड में बताई गई स्थितियां, पूरी तरह से काम करने वाले Android ऐप्लिकेशन में कैसे फ़िट होती हैं.

अगर आपको CameraX पर माइग्रेट करने में और मदद चाहिए या Android Camera API के सुइट के बारे में आपका कोई सवाल है, तो कृपया CameraX के डिस्कशन ग्रुप पर हमसे संपर्क करें.