از بافت پیش‌بینی‌شده برای دسترسی به سخت‌افزار عینک‌های صوتی و عینک‌های نمایشگر استفاده کنید

دستگاه‌های 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) می‌تواند به سخت‌افزار عینک هوش مصنوعی دسترسی داشته باشد.

پاک‌سازی هنگام قطع ارتباط

زمینه پیش‌بینی‌شده به چرخه عمر دستگاه متصل گره خورده است، بنابراین وقتی اتصال دستگاه قطع می‌شود، زمینه پیش‌بینی‌شده ازبین می‌رود. وقتی اتصال دستگاه قطع می‌شود، ProjectedContext.isProjectedDeviceConnected false را برمی‌گرداند. برنامه شما باید این تغییر را بشنود و هرگونه سرویس سیستم (مثل CameraManager) یا منابعی را که برنامه‌تان بااستفاده از آن بافت پیش‌بینی‌شده ایجاد کرده است پاک‌سازی کند.

مقداردهی اولیه مجدد در اتصال مجدد

وقتی عینک دوباره متصل می‌شود، برنامه شما می‌تواند بااستفاده از createProjectedDeviceContext نمونه زمینه‌ای پیش‌بینی‌شده دیگری دریافت کند و سپس بااستفاده از زمینه پیش‌بینی‌شده جدید، سرویس‌ها یا منابع سیستم را دوباره مقداردهی اولیه کند.

ضبط کردن صدا با میکروفون عینک

می‌توانید بااستفاده از دو روش متمایز از عینک صدا ضبط کنید:

انتخاب روش ضبط

روش انتخابی شما بستگی به این دارد که آیا به پردازش صدای با کیفیت بالا و مختص XR نیاز دارید یا ورودی صدای بلوتوث استاندارد.

روش ضبط دسترسی میکروفون مورد استفاده رایج

بافت پیش‌بینی‌شده

چند میکروفون

ضبط کردن بااستفاده از زمینه پیش‌بینی‌شده به برنامه‌تان امکان می‌دهد به چندین میکروفون از عینک و ویژگی‌های سخت‌افزاری تخصصی آن دسترسی داشته باشد، ازجمله:

  • فضاسازی ویژه واقعیت گسترده.
  • حذف نویز پیشرفته.
  • جداسازی صدا که صدای کاربر را جدا می‌کند.
  • حتی زمانی که عینک دستگاه بلوتوث فعال نیست، دسترسی به ضبط در محیط‌های چنددستگاهی حفظ می‌شود.

بلوتوث HFP

میکروفون تکی

برای سازگاری فوری و آماده استفاده، به «نمایه دست‌آزاد بلوتوث» (HFP) متکی است. در این حالت، عینک بااستفاده از نمایه‌های استاندارد «هدست» و «نمایه توزیع صوتی پیشرفته» (A2DP) به تلفن متصل می‌شود و مانند یک دستگاه جانبی بلوتوث معمولی عمل می‌کند.

اگر برنامه‌تان ازقبل برای ضبط استاندارد بلوتوث طراحی شده است، می‌توانید از این روش برای ضبط صدا از عینک بدون ادغام قابلیت‌های مختص واقعیت گسترده استفاده کنید.

ضبط صدا بااستفاده از بافت پیش‌بینی‌شده

برای ضبط صدا بااستفاده از بافت پیش‌بینی‌شده، ابتدا مجوزهای زمان اجرای موردنیاز را درخواست کنید، و سپس صدا را بااستفاده از AudioRecord API، همان‌طور که در بخش‌های زیر توضیح داده شده است، ضبط کنید.

درخواست اجازه‌های زمان اجرا

برای دسترسی به چند میکروفون در عینک، باید اجازه‌های صوتی را به‌طور خاص برای دستگاه پخش‌شده درخواست کنید. اجازه استاندارد و محدود به تلفن RECORD_AUDIO که کاربر برای برنامه شما در دستگاه همراهش اعطا کرده است کافی نیست.

برای درخواست اجازه‌ها، این مراحل را دنبال کنید:

  1. اجازه RECORD_AUDIO را در فایل مانیفست برنامه‌تان اعلام کنید.
  2. بسته به اینکه کد شما کجا اجرا می‌شود، اجازه‌های با محدوده دستگاه پخش را به یکی از روش‌های زیر درخواست کنید:

مقداردهی اولیه AudioRecord با زمینه پیش‌بینی‌شده

برای اطمینان از اینکه صدا از عینک ضبط می‌شود نه از تلفن میزبان، باید شیء AudioRecord را با زمینه دستگاه پیش‌بینی‌شده مرتبط کنید.

کد زیر از AudioRecord.Builder استفاده می‌کند و projectedDeviceContext را به روش setContext منتقل می‌کند:

// Initialize AudioRecord with projected device context
val audioRecord = AudioRecord.Builder()
    .setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
    .setAudioFormat(audioFormat)
    .setBufferSizeInBytes(bufferSize)
    // pass in the projected device context
    .setContext(projectedDeviceContext)
    .build()

audioRecord.startRecording()

نکات کلیدی درباره کد
  • می‌توانید منبع صدا را روی CAMCORDER، VOICE_RECOGNITION، VOICE_COMMUNICATION، یا UNPROCESSED تنظیم کنید تا پردازش صدا را با مورد استفاده خاص خودتان تطبیق دهید.

    برای مثال، اگر مورد استفاده شما به کاهش خودکار نویز یا جداسازی صدای کاربر نیاز دارد، از VOICE_COMMUNICATION استفاده کنید. ‫VOICE_RECOGNITION با حذف پژواک صوتی (AEC) پردازش می‌شود که می‌تواند هنگام پخش هم‌زمان صدا درحین ضبط مفید باشد. و اگر به صدای خام و بدون تغییر نیاز دارید، UNPROCESSED یا CAMCORDER را انتخاب کنید.

  • برای اطمینان از سازگاری با عینک، شیء audioFormat باید نرخ نمونه ۱۶ کیلوهرتز و پیکربندی کانال مونو یا استریو (بااستفاده از CHANNEL_IN_MONO یا CHANNEL_IN_STEREO) را تعریف کند.

  • از AudioRecord.getMinBufferSize() برای تعیین حداقل اندازه بافر برای ایجاد شیء AudioRecord استفاده کنید. بااین‌حال، برای جلوگیری از افت صدا از عینک، باید از این بافر در بخش‌های کوتاه و مکرر (ایده‌آل برش‌های ۲۰ میلی‌ثانیه‌ای) بخوانید، نه اینکه منتظر بمانید تا کل بافر پر شود.

پس‌از استفاده تمیز کنید

وقتی برنامه شما دیگر به میکروفون نیاز ندارد، یا وقتی فعالیت متوقف می‌شود، در شیء AudioRecord، stop و release را فراخوانی کنید.

بررسی اجازه‌های زمان اجرا قبل‌از ضبط

قبل‌از تماس با startRecording، بااستفاده از بافت پیش‌بینی‌شده، تأیید کنید که کاربر اجازه میکروفون را برای عینک اعطا کرده است.

ضبط صدا بااستفاده از «نمایه هندزفری بلوتوث»

برای ضبط صدا بااستفاده از Bluetooth HFP، ابتدا اجازه‌های زمان اجرای موردنیاز را درخواست کنید، و سپس صدا را بااستفاده از AudioManager API، همان‌طور که در بخش‌های زیر توضیح داده شده است، ضبط کنید.

درخواست اجازه

مانند هر دستگاه صوتی بلوتوث استاندارد، RECORD_AUDIO، BLUETOOTH_CONNECT، و دیگر اجازه‌های مربوطه توسط تلفن کنترل می‌شوند و نه دستگاه متصل (مانند عینک‌های صوتی یا عینک‌های نمایشگر).

برای درخواست اجازه‌ها، این مراحل را دنبال کنید:

  1. اجازه‌های زیر را در فایل مانیفست برنامه‌تان اعلام کنید:

  2. بااستفاده از جریان استاندارد اجازه Android، اجازه‌های RECORD_AUDIO و BLUETOOTH_CONNECT را در زمان اجرا درخواست کنید.

استفاده از «مدیر صدا» برای مسیریابی صدا

پس‌از اینکه کاربر اجازه‌های زمان اجرای لازم را به برنامه شما اعطا کرد، از AudioManager API برای تنظیم دستگاه ارتباطی روی TYPE_BLUETOOTH_SCO استفاده کنید تا صدا ازطریق «نمایه هندزفری بلوتوث» مسیریابی شود. این کار سیستم را هدایت می‌کند تا صدا را از لوازم جانبی بلوتوثی بازیابی کند.

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()

تصویری با دوربین عینک ضبط کنید

برای گرفتن تصویر با دوربین عینک، ImageCapture مورد کاربرد CameraX را بااستفاده از بافت صحیح برای برنامه‌تان راه‌اندازی و به دوربین عینک پیوند دهید:

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) برای تأیید دردسترس بودن دوربین انتخاب‌شده در دستگاه قبل‌از ادامه دادن استفاده می‌کند.
  • از Camera2 Interop با Camera2CameraInfo برای خواندن CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP زیرین استفاده می‌کند، که می‌تواند برای بررسی‌های پیشرفته در وضوح‌های پشتیبانی‌شده مفید باشد.
  • ResolutionSelector سفارشی برای کنترل دقیق وضوح تصویر برونداد برای ImageCapture ساخته شده است.
  • مورد استفاده ImageCapture را ایجاد می‌کند که با ResolutionSelector سفارشی پیکربندی شده است.
  • مورد استفاده ImageCapture را به چرخه حیات فعالیت پیوند می‌دهد. این به‌طور خودکار باز و بسته شدن دوربین را براساس وضعیت فعالیت مدیریت می‌کند (برای مثال، وقتی فعالیت متوقف می‌شود، دوربین را متوقف می‌کند).
  • پیکربندی برای سخت‌افزار تک‌جاری‌سازی: خط لوله دوربین عینک در هر زمان به یک جاری‌سازی فعال محدود می‌شود. وقتی ImageCapture به‌تنهایی پیوند داده می‌شود، CameraX به‌طور پیش‌فرض یک جاری‌سازی تکرارشونده داخلی (MeteringRepeating) برای فوکوس و سنجش متصل می‌کند که باعث توقف خط لوله تک‌جاری‌سازی می‌شود. برای اتصال ImageCapture به‌تنهایی بدون جاری‌سازی پیش‌نمایش، جاری‌سازی تکرارشونده اجباری را با تنظیم setRepeatingStreamForced(false) در CameraXConfig.Builder غیرفعال کنید (برای مثال، با پیاده‌سازی CameraXConfig.Provider در کلاس Application یا پیکربندی ProcessCameraProvider قبل‌از مقداردهی اولیه).

پس‌از راه‌اندازی دوربین عینک، می‌توانید با کلاس ImageCapture در CameraX تصویر ضبط کنید. برای آشنایی با استفاده از takePicture برای گرفتن تصویر، به اسناد CameraX مراجعه کنید.

با دوربین عینک ویدیو ضبط کنید

برای ضبط ویدیو به‌جای تصویر با دوربین عینک، ImageCapture عنصر را با عناصر VideoCapture مربوطه جایگزین کنید و منطق اجرای ضبط را اصلاح کنید.

تغییرات اصلی شامل استفاده از یک مورد استفاده متفاوت، ایجاد یک فایل خروجی متفاوت، و شروع ضبط بااستفاده از روش مناسب ضبط ویدیو است. برای کسب اطلاعات بیشتر درباره VideoCapture API و نحوه استفاده از آن، مستندات ضبط ویدیو CameraX را ببینید.

جدول زیر وضوح و نرخ فریم توصیه‌شده را بسته به مورد استفاده برنامه‌تان نشان می‌دهد:

مورد استفاده برطرف کردن مشکل نرخ فریم
ارتباط تصویری ۷۲۰ × ۱۲۸۰ ‫۱۵ فریم در ثانیه
بینایی رایانه ۴۸۰ × ۶۴۰ ‫۱۰ فریم در ثانیه
جاری‌سازی ویدیو هوشواره‌ای ۴۸۰ × ۶۴۰ ۱ فریم در ثانیه

دسترسی به سخت‌افزار تلفن از فعالیت پخش‌شده

فعالیت پیش‌بینی‌شده نیز می‌تواند بااستفاده از 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 استفاده نکنید زیرا اگر فعالیت پیش‌بینی‌شده آخرین عنصر راه‌اندازی‌شده باشد، زمینه برنامه می‌تواند زمینه عینک را به‌اشتباه برگرداند.