استخدام سياق مُسقَط للوصول إلى الأجهزة على نظّارة الصوت والنظّارة المزودة بشاشة عرض

أجهزة XR المشمولة
تساعدك هذه الإرشادات في إنشاء تجارب لهذه الأنواع من أجهزة XR.
نظارات الصوت
والعرض

بعد طلب الأذونات اللازمة والحصول عليها، يمكن لتطبيقك الوصول إلى الأجهزة في نظارات الصوت أو النظارات الذكية. للوصول إلى أجهزة النظارات (بدلاً من أجهزة الهاتف)، يجب استخدام سياق معروض.

هناك طريقتان أساسيتان للحصول على سياق متوقّع، وذلك حسب مكان تنفيذ الرمز البرمجي:

الحصول على سياق مسقط إذا كان الرمز البرمجي يعمل في نشاط مسقط

إذا كان رمز تطبيقك يعمل من داخل نشاطك المتوقّع، سيكون سياق النشاط الخاص به سياقًا متوقّعًا. في هذا السيناريو، يمكن للمكالمات التي يتم إجراؤها ضمن هذا النشاط الوصول إلى أجهزة النظّارة الذكية.

الحصول على سياق معروض لتشغيل الرمز في أحد مكونات تطبيق الهاتف

إذا كان جزء من تطبيقك خارج نطاق نشاطك المعروض (مثل نشاط على الهاتف أو خدمة) يحتاج إلى الوصول إلى أجهزة النظارات، يجب أن يحصل صراحةً على سياق معروض. لإجراء ذلك، استخدِم طريقة 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، ثم إعادة إعداد أي خدمات تابعة لنظام التشغيل أو موارد باستخدام سياق العرض المتوقّع الجديد.

تسجيل الصوت باستخدام ميكروفون النظارات

يمكنك تسجيل الصوت من النظارات باستخدام طريقتَين مختلفتَين:

اختيار طرق التسجيل

تعتمد الطريقة التي تختارها على ما إذا كنت بحاجة إلى معالجة صوتية عالية الدقة ومخصّصة للواقع الممتد، أو إدخال صوتي عادي عبر البلوتوث.

طريقة التسجيل الوصول إلى الميكروفون حالة الاستخدام الشائعة

السياق المتوقّع

ميكروفونات متعددة

يتيح التسجيل باستخدام سياق معروض لتطبيقك الوصول إلى عدة ميكروفونات من النظارات وميزات الأجهزة المتخصصة، مثل:

  • تحديد الموقع الجغرافي الخاص بالواقع الممتد
  • إزالة التشويش المتقدّمة
  • فصل الأصوات الذي يميّز بين صوت مرتدي الجهاز وأصوات الأشخاص المحيطين به
  • الاحتفاظ بإذن التسجيل في البيئات التي تتضمّن أجهزة متعددة حتى عندما لا تكون النظارات هي جهاز البلوتوث النشط

Bluetooth HFP

ميكروفون واحد

تعتمد على ملف البلوتوث الشخصي بدون لمس الجهاز (HFP) لتحقيق توافق فوري وجاهز للاستخدام. في هذا الوضع، تتصل النظارات بالهاتف باستخدام الملفات الشخصية العادية لسماعات الرأس وملف توزيع الصوت المتقدّم (A2DP)، وتعمل مثل جهاز طرفي عادي يعمل بالبلوتوث.

إذا كان تطبيقك مصمّمًا لتسجيل الصوت باستخدام البلوتوث العادي، يمكنك استخدام هذه الطريقة لتسجيل الصوت من النظارات بدون دمج أي إمكانات خاصة بالواقع الممتد.

تسجيل الصوت باستخدام سياق معروض

لتسجيل الصوت باستخدام سياق معروض، عليك أولاً طلب أذونات وقت التشغيل المطلوبة، ثم تسجيل الصوت باستخدام واجهة برمجة التطبيقات 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 معدّل عيّنات يبلغ 16 كيلوهرتز وإعداد قناة أحادية أو استريو (باستخدام CHANNEL_IN_MONO أو CHANNEL_IN_STEREO).

  • مع أنّه ليس هناك متطلّبات ثابتة لحجم ذاكرة التخزين المؤقت، ننصحك بضبط الحد الأدنى لحجم ذاكرة التخزين المؤقت للحدّ من وقت الاستجابة المُدرَك.

التنظيف بعد الاستخدام

عندما لا يحتاج تطبيقك إلى الميكروفون أو عند إيقاف النشاط، اتّصِل بالدالتَين stop وrelease في العنصر AudioRecord.

التحقّق من أذونات التشغيل قبل التسجيل

قبل استدعاء startRecording، تأكَّد من أنّ المستخدم قد منح الإذن باستخدام الميكروفون للنظارات باستخدام السياق المعروض.

تسجيل الصوت باستخدام بروتوكول HFP عبر البلوتوث

لتسجيل الصوت باستخدام Bluetooth HFP، عليك أولاً طلب أذونات وقت التشغيل المطلوبة، ثم تسجيل الصوت باستخدام واجهة برمجة التطبيقات AudioManager، كما هو موضّح في الأقسام التالية.

طلب الحصول على الأذونات

كما هو الحال مع أي جهاز صوتي عادي يتضمّن بلوتوث، يتم التحكّم في أذونات RECORD_AUDIO وBLUETOOTH_CONNECT والأذونات الأخرى ذات الصلة من خلال الهاتف وليس الجهاز المتصل (مثل نظارات الصوت أو نظارات العرض).

اتّبِع الخطوات التالية لطلب الأذونات:

  1. يُرجى تضمين الأذونات التالية في ملف بيان تطبيقك:

  2. يجب طلب الإذنَين RECORD_AUDIO وBLUETOOTH_CONNECT في وقت التشغيل باستخدام مسار أذونات Android العادي.

استخدام 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 عند اختيار كاميرا.
  • يستخدم فحص الربط المُسبَق cameraProvider.hasCamera(cameraSelector) للتأكّد من أنّ الكاميرا المحدّدة متاحة على الجهاز قبل المتابعة.
  • يستخدم Camera2 Interop مع Camera2CameraInfo لقراءة CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP الأساسي، وهو ما قد يكون مفيدًا لإجراء عمليات تحقّق متقدّمة من درجات الدقة المتوافقة.
  • تم إنشاء ResolutionSelector مخصّص للتحكّم بدقة في درجة دقة الصورة الناتجة عن ImageCapture.
  • تنشئ هذه الطريقة ImageCapture حالة استخدام تم ضبطها باستخدام ResolutionSelector مخصّص.
  • يربط حالة استخدام ImageCapture بدورة حياة النشاط. تتولّى هذه السمة تلقائيًا إدارة فتح الكاميرا وإغلاقها استنادًا إلى حالة النشاط (على سبيل المثال، إيقاف الكاميرا مؤقتًا عند إيقاف النشاط مؤقتًا).

بعد إعداد كاميرا النظارات، يمكنك التقاط صورة باستخدام الفئة ImageCapture في CameraX. راجِع مستندات CameraX للتعرّف على كيفية استخدام takePicture من أجل التقاط صورة.

تسجيل فيديو باستخدام كاميرا النظارات

لالتقاط فيديو بدلاً من صورة باستخدام كاميرا النظارات، استبدِل المكوّنات ImageCapture بمكوّنات VideoCapture المقابلة وعدِّل منطق تنفيذ عملية الالتقاط.

تتضمّن التغييرات الرئيسية استخدام حالة استخدام مختلفة، وإنشاء ملف إخراج مختلف، وبدء عملية الالتقاط باستخدام طريقة تسجيل الفيديو المناسبة. لمزيد من المعلومات حول واجهة برمجة التطبيقات VideoCapture وكيفية استخدامها، يُرجى الاطّلاع على مستندات التقاط الفيديو في CameraX.

يعرض الجدول التالي درجة الدقة وعدد اللقطات في الثانية المقترَحة استنادًا إلى حالة استخدام تطبيقك:

حالة الاستخدام درجة الدقة عدد اللقطات في الثانية
التواصل عبر الفيديو ‫‎1280 x 720 15 لقطة في الثانية
الرؤية الحاسوبية 640 x 480 10 لقطات في الثانية
بث الفيديوهات بالذكاء الاصطناعي 640 x 480 لقطة واحدة في الثانية

الوصول إلى أجهزة الهاتف من نشاط معروض

يمكن لنشاط مُسقَط أيضًا الوصول إلى أجهزة الهاتف (مثل الكاميرا أو الميكروفون) باستخدام createHostDeviceContext(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 للحصول على سياق الهاتف.
  • لا تستخدِم getApplicationContext لأنّ سياق التطبيق قد يعرض بشكل غير صحيح سياق النظارات إذا كان النشاط المعروض هو المكوّن الذي تم إطلاقه مؤخرًا.