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

دستگاه‌های XR قابل اجرا
این راهنما به شما کمک می‌کند تا برای این نوع دستگاه‌های XR تجربه ایجاد کنید.
صوتی و
عینک نمایش

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

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

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

اگر کد برنامه شما از درون activity پیش‌بینی‌شده شما اجرا می‌شود، activity context خودش از قبل یک projected context است. در این سناریو، فراخوانی‌های انجام‌شده درون آن activity می‌توانند از قبل به سخت‌افزار عینک دسترسی داشته باشند.

دریافت یک زمینه‌ی پیش‌بینی‌شده برای اجرای کد در یک کامپوننت برنامه‌ی تلفن

اگر بخشی از برنامه شما خارج از activity پیش‌بینی‌شده‌تان (مانند activity تلفن یا یک سرویس) نیاز به دسترسی به سخت‌افزار عینک داشته باشد، باید صریحاً یک context پیش‌بینی‌شده را دریافت کند. برای انجام این کار، از متد createProjectedDeviceContext استفاده کنید:

@OptIn(ExperimentalProjectedApi::class)
private fun getGlassesContext(context: Context): Context? {
    return try {
        // From a phone Activity or Service, get a context for the AI glasses.
        ProjectedContext.createProjectedDeviceContext(context)
    } catch (e: IllegalStateException) {
        Log.e(TAG, "Failed to create projected device context", e)
        null
    }
}

بررسی اعتبار

فراخوانی createProjectedDeviceContext را درون ProjectedContext.isProjectedDeviceConnected قرار دهید. در حالی که این متد true را برمی‌گرداند، زمینه‌ی پیش‌بینی‌شده برای دستگاه متصل معتبر باقی می‌ماند و فعالیت یا سرویس برنامه‌ی تلفن شما (مانند CameraManager ) می‌تواند به سخت‌افزار عینک هوش مصنوعی دسترسی داشته باشد.

پاک‌سازی در صورت قطع اتصال

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

هنگام اتصال مجدد، دوباره مقداردهی اولیه کنید

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

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

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

روش‌های ضبط را انتخاب کنید

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

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

زمینه پیش‌بینی‌شده

میکروفون‌های چندگانه

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

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

بلوتوث HFP

میکروفون تکی

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

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

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

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

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

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

  • اگرچه هیچ الزام ثابتی برای اندازه بافر وجود ندارد، حداقل اندازه بافر را برای به حداقل رساندن تأخیر درک شده بدست آورید .

بعد از استفاده تمیز کنید

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

قبل از ضبط، مجوزهای زمان اجرا را بررسی کنید

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

ضبط صدا با استفاده از بلوتوث HFP

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

درخواست مجوز

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

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

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

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

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

پس از اینکه کاربر مجوزهای لازم زمان اجرا را به برنامه شما اعطا کرد، از رابط برنامه‌نویسی کاربردی AudioManager برای تنظیم دستگاه ارتباطی روی 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()

با دوربین عینک عکس بگیرید

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

private fun startCameraOnGlasses(activity: ComponentActivity) {
    // 1. Get the CameraProvider using the projected context.
    // When using the projected context, DEFAULT_BACK_CAMERA maps to the AI glasses' camera.
    val projectedContext = try {
        ProjectedContext.createProjectedDeviceContext(activity)
    } catch (e: IllegalStateException) {
        Log.e(TAG, "AI Glasses context could not be created", e)
        return
    }

    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 نگاشت می‌شود.
  • یک بررسی پیش از اتصال (pre-binding) از cameraProvider.hasCamera(cameraSelector) برای تأیید اینکه دوربین انتخاب شده قبل از ادامه در دستگاه موجود است، استفاده می‌کند.
  • از Camera2 Interop با Camera2CameraInfo برای خواندن CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP استفاده می‌کند، که می‌تواند برای بررسی‌های پیشرفته در مورد رزولوشن‌های پشتیبانی شده مفید باشد.
  • یک ResolutionSelector سفارشی برای کنترل دقیق وضوح تصویر خروجی برای ImageCapture ساخته شده است.
  • یک مورد استفاده ImageCapture ایجاد می‌کند که با یک ResolutionSelector سفارشی پیکربندی شده است.
  • مورد استفاده ImageCapture را به چرخه حیات activity متصل می‌کند. این به طور خودکار باز و بسته شدن دوربین را بر اساس وضعیت activity مدیریت می‌کند (برای مثال، متوقف کردن دوربین هنگام مکث activity).

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

با دوربین عینک فیلم بگیرید

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

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

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

مورد استفاده وضوح تصویر نرخ فریم
ارتباطات ویدیویی ۱۲۸۰ در ۷۲۰ ۱۵ فریم بر ثانیه
بینایی کامپیوتر ۶۴۰ در ۴۸۰ ۱۰ فریم بر ثانیه
پخش ویدیوی هوش مصنوعی ۶۴۰ در ۴۸۰ ۱ فریم بر ثانیه

دسترسی به سخت‌افزار گوشی از طریق یک فعالیت برنامه‌ریزی‌شده

یک activity که در پروژه قرار داده شده است، می‌تواند با استفاده از createHostDeviceContext(context) به سخت‌افزار گوشی (مانند دوربین یا میکروفون) دسترسی پیدا کند و context دستگاه میزبان (گوشی) را دریافت کند:

@OptIn(ExperimentalProjectedApi::class)
private fun getPhoneContext(activity: ComponentActivity): Context? {
    return try {
        // From an AI glasses 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 برای دریافت زمینه phone استفاده کنید.
  • getApplicationContext استفاده نکنید زیرا اگر یک activity پیش‌بینی‌شده، کامپوننتی باشد که اخیراً اجرا شده است، application context می‌تواند به اشتباه context مربوط به glasses را برگرداند.