অডিও গ্লাস ও ডিসপ্লে গ্লাসে হার্ডওয়্যার অ্যাক্সেস করতে প্রজেক্ট করা কন্টেক্সট ব্যবহার করা

প্রযোজ্য XR ডিভাইস
এই নির্দেশিকা আপনাকে এই ধরনের XR ডিভাইসের জন্য অভিজ্ঞতা তৈরি করতে সাহায্য করে।
অডিও ও
ডিসপ্লে গ্লাস

আপনি প্রয়োজনীয় অনুমতির জন্য অনুরোধ করার পরে তা মঞ্জুর করা হলে, আপনার অ্যাপ অডিও গ্লাস বা ডিসপ্লে গ্লাসের হার্ডওয়্যার অ্যাক্সেস করতে পারবে। চশমার হার্ডওয়্যার (ফোনের হার্ডওয়্যারের পরিবর্তে) অ্যাক্সেস করার মূল উপায় হল প্রোজেক্টেড কনটেক্সট ব্যবহার করা।

আপনার কোড কোথায় এক্সিকিউট হচ্ছে তার উপর নির্ভর করে, প্রোজেক্ট করা কনটেক্সট পাওয়ার দুটি প্রাথমিক উপায় আছে:

আপনার কোড কোনও প্রোজেক্টেড অ্যাক্টিভিটিতে রান করলে প্রোজেক্টেড কনটেক্সট পান

আপনার অ্যাপের কোড যদি প্রোজেক্ট করা অ্যাক্টিভিটি থেকে রান করে, তাহলে এর নিজস্ব অ্যাক্টিভিটি কনটেক্সট আগে থেকেই প্রোজেক্ট করা কনটেক্সট হিসেবে থাকে। এই পরিস্থিতিতে, সেই অ্যাক্টিভিটির মধ্যে করা কল আগে থেকেই চশমার হার্ডওয়্যার অ্যাক্সেস করতে পারে।

ফোন অ্যাপ কম্পোনেন্টে চলা কোডের জন্য একটি প্রোজেক্ট করা প্রসঙ্গ পান

আপনার প্রোজেক্ট করা অ্যাক্টিভিটির বাইরে আপনার অ্যাপের কোনও অংশ (যেমন ফোন অ্যাক্টিভিটি বা কোনও পরিষেবা) যদি চশমার হার্ডওয়্যার অ্যাক্সেস করতে চায়, তাহলে তাকে অবশ্যই স্পষ্টভাবে একটি প্রোজেক্ট করা কনটেক্সট পেতে হবে। এটি করতে, createProjectedDeviceContext পদ্ধতি ব্যবহার করুন:

@RequiresApi(Build.VERSION_CODES.BAKLAVA)
@OptIn(ExperimentalProjectedApi::class, ExperimentalCoroutinesApi::class)
private fun monitorProjectedConnectivity(activity: ComponentActivity) {
    activity.lifecycleScope.launch {
        // Before creating a projected context, check to see if the projected device is connected.
        // While this method returns true, the projected context remains valid.
        ProjectedContext.isProjectedDeviceConnected(activity, coroutineContext)
            .collectLatest { isConnected ->
                if (isConnected) {
                    // From a phone Activity or Service, get a context for the audio and display glasses.
                    // Re-initialize on reconnect: Obtain another context instance.
                    val projectedContext = try {
                        ProjectedContext.createProjectedDeviceContext(activity)
                    } catch (e: IllegalStateException) {
                        Log.e(TAG, "Failed to create projected context", e)
                        return@collectLatest
                    }

                    // Use the projectedContext to initialize system services (e.g., CameraManager).
                    Log.i(TAG, "Projected device connected. Initializing hardware...")
                } else {
                    // The projected context is destroyed when the device disconnects.
                    // Clean up on disconnect: Listen for 'false' and release resources.
                    Log.i(TAG, "Projected device disconnected. Cleaning up hardware resources...")
                }
            }
    }
}

বৈধতা চেক করুন

createProjectedDeviceContext কলটি ProjectedContext.isProjectedDeviceConnected-এর মধ্যে শেষ করুন। এই পদ্ধতি true ফিরিয়ে দিলেও, কানেক্ট করা ডিভাইসের ক্ষেত্রে প্রোজেক্ট করা প্রসঙ্গ বৈধ থাকে এবং আপনার ফোন অ্যাপ অ্যাক্টিভিটি বা পরিষেবা (যেমন CameraManager) AI চশমার হার্ডওয়্যার অ্যাক্সেস করতে পারে।

ডিসকানেক্ট করার সময় ক্লিন-আপ করুন

প্রজেক্ট করা কনটেক্সট কানেক্ট করা ডিভাইসের লাইফসাইকেলের সাথে যুক্ত থাকে, তাই ডিভাইস ডিসকানেক্ট হয়ে গেলে এটি নষ্ট হয়ে যায়। ডিভাইস ডিসকানেক্ট হয়ে গেলে, ProjectedContext.isProjectedDeviceConnected false রিটার্ন করে। আপনার অ্যাপকে এই পরিবর্তনের জন্য অপেক্ষা করতে হবে এবং যেকোনও সিস্টেম পরিষেবা (যেমন, CameraManager) বা রিসোর্স ক্লিন-আপ করতে হবে যা আপনার অ্যাপ ওই প্রোজেক্ট করা কন্টেক্সট ব্যবহার করে তৈরি করেছে।

আবার কানেক্ট করার সময় রি-ইনিশিয়ালাইজ করুন

চশমা আবার কানেক্ট হলে, আপনার অ্যাপ createProjectedDeviceContext ব্যবহার করে অন্য প্রোজেক্ট করা কনটেক্সট ইনস্ট্যান্স পেতে পারে এবং তারপর নতুন প্রোজেক্ট করা কনটেক্সট ব্যবহার করে যেকোনও সিস্টেম পরিষেবা বা রিসোর্স আবার শুরু করতে পারে।

স্মার্ট গ্লাসের মাইক্রোফোন দিয়ে অডিও রেকর্ড করুন

আপনি ব্লুটুথ হ্যান্ডস-ফ্রি প্রোফাইল (HFP) ব্যবহার করে চশমা থেকে অডিও রেকর্ড করতে পারবেন। এই মোডে, চশমা স্ট্যান্ডার্ড হেডসেট এবং উন্নত অডিও ডিস্ট্রিবিউশন প্রোফাইল (A2DP) প্রোফাইল ব্যবহার করে ফোনের সাথে কানেক্ট করে, যা সাধারণ ব্লুটুথ পেরিফেরালের মতো কাজ করে। আপনার অ্যাপ যদি আগে থেকেই স্ট্যান্ডার্ড ব্লুটুথ রেকর্ডিংয়ের জন্য ডিজাইন করা থাকে, তাহলে আপনি অতিরিক্ত ডেভেলপমেন্ট ছাড়াই চশমা থেকে অডিও রেকর্ড করতে এই পদ্ধতি ব্যবহার করতে পারেন।

ব্লুটুথ HFP ব্যবহার করে অডিও রেকর্ড করুন

ব্লুটুথ HFP ব্যবহার করে অডিও রেকর্ড করতে, প্রথমে প্রয়োজনীয় রানটাইম অনুমতির জন্য অনুরোধ করুন এবং তারপরে নিম্নলিখিত বিভাগে যেভাবে বর্ণনা করা হয়েছে সেইভাবে AudioManager API ব্যবহার করে অডিও রেকর্ড করুন।

অনুমতির জন্য অনুরোধ করুন

যেকোনও স্ট্যান্ডার্ড ব্লুটুথ অডিও ডিভাইসের মতো, RECORD_AUDIO, BLUETOOTH_CONNECT এবং অন্যান্য সম্পর্কিত অনুমতি ফোন দ্বারা নিয়ন্ত্রিত হয়, কানেক্ট করা ডিভাইস (যেমন অডিও গ্লাস বা ডিসপ্লে গ্লাস) দ্বারা নয়।

অনুমতির অনুরোধ করতে এইসব ধাপ অনুসরণ করুন:

  1. আপনার অ্যাপের ম্যানিফেস্ট ফাইলে নিম্নলিখিত অনুমতি ঘোষণা করুন:

  2. স্ট্যান্ডার্ড Android অনুমতি ফ্লো ব্যবহার করে রানটাইমে RECORD_AUDIO এবং BLUETOOTH_CONNECT, দুটি অনুমতির জন্যই অনুরোধ করুন।

অডিও রুট করতে AudioManager ব্যবহার করুন

ব্যবহারকারী আপনার অ্যাপকে প্রয়োজনীয় রানটাইম অনুমতি দেওয়ার পরে, AudioManager API ব্যবহার করে কমিউনিকেশন ডিভাইসকে TYPE_BLUETOOTH_SCO-এ সেট করুন যাতে ব্লুটুথ HFP-এর মাধ্যমে অডিও রুট করা যায়। এটি সিস্টেমকে ব্লুটুথ পেরিফেরাল থেকে অডিও ফিরিয়ে আনার নির্দেশ দেয়।

val audioManager = context.getSystemService(AudioManager::class.java) ?: return
val devices = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)
val hfpDevice = devices.find { it.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO }

hfpDevice?.let { device ->
    val audioRecord = AudioRecord.Builder()
        .setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION)
        .setAudioFormat(audioFormat)
        .setBufferSizeInBytes(bufferSize)
        .build()

    // Route recording to the Bluetooth device
    audioRecord.setPreferredDevice(device)
    audioManager.setCommunicationDevice(device)

    audioRecord.startRecording()

স্মার্ট গ্লাসের ক্যামেরা দিয়ে ছবি তুলুন

চশমার ক্যামেরা দিয়ে ছবি তুলতে, আপনার অ্যাপের জন্য সঠিক প্রসঙ্গ ব্যবহার করে চশমার ক্যামেরার সাথে CameraX-এর ImageCapture ব্যবহারের কেস সেট-আপ ও বাইন্ড করুন:

private fun startCameraOnGlasses(activity: ComponentActivity) {
    activity.lifecycleScope.launch {
        // Before creating a projected context, check to see if the projected device is connected.
        ProjectedContext.isProjectedDeviceConnected(activity, coroutineContext)
            .collectLatest { isConnected ->
                if (isConnected) {
                    // 1. Get the CameraProvider using the projected context.
                    // When using the projected context, DEFAULT_BACK_CAMERA maps to the audio and display glasses' camera.
                    val projectedContext = try {
                        ProjectedContext.createProjectedDeviceContext(activity)
                    } catch (e: IllegalStateException) {
                        Log.e(TAG, "Projected context could not be created", e)
                        return@collectLatest
                    }

                    val cameraProviderFuture = ProcessCameraProvider.getInstance(projectedContext)

                    cameraProviderFuture.addListener({
                        val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
                        val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

                        // 2. Check for the presence of a camera.
                        if (!cameraProvider.hasCamera(cameraSelector)) {
                            Log.w(TAG, "The selected camera is not available.")
                            return@addListener
                        }

                        // 3. Query supported streaming resolutions using Camera2 Interop.
                        val cameraInfo = cameraProvider.getCameraInfo(cameraSelector)
                        val camera2CameraInfo = Camera2CameraInfo.from(cameraInfo)
                        val cameraCharacteristics = camera2CameraInfo.getCameraCharacteristic(
                            CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP
                        )

                        // 4. Define the resolution strategy.
                        val targetResolution = Size(1920, 1080)
                        val resolutionStrategy = ResolutionStrategy(
                            targetResolution,
                            ResolutionStrategy.FALLBACK_RULE_CLOSEST_LOWER
                        )
                        val resolutionSelector = ResolutionSelector.Builder()
                            .setResolutionStrategy(resolutionStrategy)
                            .build()

                        // 5. If you have other continuous use cases bound, such as Preview or ImageAnalysis,
                        // you can use  Camera2 Interop's CaptureRequestOptions to set the FPS
                        val fpsRange = Range(30, 60)
                        val captureRequestOptions = CaptureRequestOptions.Builder()
                            .setCaptureRequestOption(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange)
                            .build()

                        // 6. Initialize the ImageCapture use case with options.
                        val imageCapture = ImageCapture.Builder()
                            // Optional: Configure resolution, format, etc.
                            .setResolutionSelector(resolutionSelector)
                            .build()

                        try {
                            // Unbind use cases before rebinding.
                            cameraProvider.unbindAll()

                            // Bind use cases to camera using the Activity as the LifecycleOwner.
                            cameraProvider.bindToLifecycle(
                                activity,
                                cameraSelector,
                                imageCapture
                            )
                        } catch (exc: Exception) {
                            Log.e(TAG, "Use case binding failed", exc)
                        }
                    }, ContextCompat.getMainExecutor(activity))
                }
            }
    }
}

কোড সম্পর্কে গুরুত্বপূর্ণ পয়েন্ট

  • প্রোজেক্ট করা ডিভাইসের প্রসঙ্গ ব্যবহার করে ProcessCameraProvider-এর ইনস্ট্যান্স পায়।
  • প্রজেক্ট করা প্রসঙ্গের স্কোপের মধ্যে, ক্যামেরা বেছে নেওয়ার সময়, স্মার্টগ্লাসের প্রাথমিক, বাইরের দিকে পয়েন্ট করা ক্যামেরা DEFAULT_BACK_CAMERA-এ ম্যাপ করে।
  • প্রি-বাইন্ডিং চেক cameraProvider.hasCamera(cameraSelector) ব্যবহার করে যাতে এগিয়ে যাওয়ার আগে যাচাই করা যায় যে বেছে নেওয়া ক্যামেরা ডিভাইসে উপলভ্য আছে কিনা।
  • Camera2CameraInfo-এর সাথে Camera2 Interop ব্যবহার করে অন্তর্নিহিত CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP রিড করে, যা কাজ করে এমন রেজোলিউশনের উন্নত চেকের জন্য উপযোগী হতে পারে।
  • ImageCapture-এর জন্য আউটপুট ইমেজ রেজোলিউশন সঠিকভাবে নিয়ন্ত্রণ করতে একটি কাস্টম ResolutionSelector তৈরি করা হয়েছে।
  • একটি ImageCapture ব্যবহারের কেস তৈরি করে যা একটি কাস্টম ResolutionSelector দিয়ে কনফিগার করা হয়।
  • অ্যাক্টিভিটির লাইফসাইকেলের সাথে ImageCapture ব্যবহারের কেস বাইন্ড করে। এটি অ্যাক্টিভিটির স্ট্যাটাসের উপর ভিত্তি করে ক্যামেরা খোলা এবং বন্ধ করা অটোমেটিক ম্যানেজ করে (যেমন, অ্যাক্টিভিটি পজ করা হলে ক্যামেরা বন্ধ করা)।
  • সিঙ্গেল-স্ট্রিম হার্ডওয়্যারের জন্য কনফিগার করা: স্মার্টগ্লাস ক্যামেরা পাইপলাইন একবারে একটি অ্যাক্টিভ স্ট্রিমে সীমাবদ্ধ। ImageCapture একা বাউন্ড করা থাকলে, CameraX ডিফল্ট হিসেবে ফোকাস ও মিটারিংয়ের জন্য ইন্টার্নাল রিপিটিং স্ট্রিম (MeteringRepeating) অ্যাটাচ করে, যার ফলে সিঙ্গেল-স্ট্রিম পাইপলাইন স্টল হয়ে যায়। প্রিভিউ স্ট্রিম ছাড়াই ImageCapture-কে নিজে থেকে বাইন্ড করতে, CameraXConfig.Builder-এ setRepeatingStreamForced(false) সেট করে জোর করে রিপিট করা স্ট্রিম বন্ধ করুন (যেমন, আপনার Application ক্লাসে CameraXConfig.Provider প্রয়োগ করে অথবা শুরু করার আগে ProcessCameraProvider কনফিগার করে)।

স্মার্ট গ্লাসের ক্যামেরা সেট-আপ করার পরে, আপনি CameraX-এর ImageCapture ক্লাস ব্যবহার করে ছবি ক্যাপচার করতে পারবেন। takePicture ব্যবহার করে ছবি ক্যাপচার করা সম্পর্কে জানতে CameraX-এর ডকুমেন্টেশন দেখুন।

স্মার্ট গ্লাসের ক্যামেরা দিয়ে ভিডিও ক্যাপচার করুন

চশমার ক্যামেরা দিয়ে ছবির পরিবর্তে ভিডিও ক্যাপচার করতে, ImageCapture কম্পোনেন্ট সংশ্লিষ্ট VideoCapture কম্পোনেন্ট দিয়ে পরিবর্তন করুন এবং ক্যাপচার এক্সিকিউশন লজিক পরিবর্তন করুন।

প্রধান পরিবর্তনগুলির মধ্যে রয়েছে একটি ভিন্ন ব্যবহারের কেস ব্যবহার করা, একটি ভিন্ন আউটপুট ফাইল তৈরি করা এবং উপযুক্ত ভিডিও রেকর্ডিং পদ্ধতি ব্যবহার করে ক্যাপচার শুরু করা। VideoCapture API এবং এটি কীভাবে ব্যবহার করতে হয় সেই সম্পর্কে আরও তথ্যের জন্য, CameraX-এর ভিডিও ক্যাপচার ডকুমেন্টেশন দেখুন।

আপনার অ্যাপের ব্যবহারের উপর নির্ভর করে নিম্নলিখিত সারণীতে সাজেস্ট করা রেজোলিউশন এবং ফ্রেম রেট দেখানো হয়েছে:

ব্যবহারের উদাহরণ রেজোলিউশন ফ্রেম রেট
ভিডিও কমিউনিকেশন ১২৮০ x ৭২০ ১৫ FPS
কম্পিউটার ভিশন ৬৪০ x ৪৮০ ১০ FPS
AI ভিডিও স্ট্রিমিং ৬৪০ x ৪৮০ ১ FPS

প্রোজেক্ট করা অ্যাক্টিভিটি থেকে ফোনের হার্ডওয়্যার অ্যাক্সেস করা

প্রোজেক্ট করা অ্যাক্টিভিটি হোস্ট ডিভাইসের (ফোন) প্রসঙ্গ পেতে createHostDeviceContext(context) ব্যবহার করে ফোনের হার্ডওয়্যারও (যেমন ক্যামেরা বা মাইক্রোফোন) অ্যাক্সেস করতে পারে:

@OptIn(ExperimentalProjectedApi::class)
private fun getPhoneContext(activity: ComponentActivity): Context? {
    return try {
        // From a projected Activity, get a context for the phone.
        ProjectedContext.createHostDeviceContext(activity)
    } catch (e: IllegalStateException) {
        Log.e(TAG, "Failed to create host device context", e)
        null
    }
}

হাইব্রিড অ্যাপে (যে অ্যাপে মোবাইল ও চশমা, দুটিরই অভিজ্ঞতা পাওয়া যায়) হোস্ট ডিভাইসের (ফোন) জন্য নির্দিষ্ট হার্ডওয়্যার বা রিসোর্স অ্যাক্সেস করার সময়, আপনার অ্যাপ যাতে সঠিক হার্ডওয়্যার অ্যাক্সেস করতে পারে তা নিশ্চিত করতে, আপনাকে অবশ্যই স্পষ্টভাবে সঠিক প্রসঙ্গ বেছে নিতে হবে:

  • ফোন Activity থেকে Activity কনটেক্সট ব্যবহার করুন অথবা ফোনের কনটেক্সট পেতে ProjectedContext.createHostDeviceContext ব্যবহার করুন।
  • getApplicationContext ব্যবহার করবেন না কারণ প্রোজেক্ট করা অ্যাক্টিভিটি সবচেয়ে সম্প্রতি লঞ্চ করা কম্পোনেন্ট হলে, অ্যাপ্লিকেশন কন্টেক্সট ভুল করে চশমার কন্টেক্সট ফিরিয়ে দিতে পারে।