پساز اینکه اجازههای لازم را درخواست کردید و به شما اعطا شد، برنامه شما میتواند به سختافزار عینکهای صوتی یا عینکهای نمایشگر دسترسی داشته باشد. کلید دسترسی به سختافزار عینک (بهجای سختافزار تلفن)، استفاده از بافت پیشبینیشده است.
بسته به اینکه کد شما کجا اجرا میشود، دو روش اصلی برای دریافت بافت پیشبینیشده وجود دارد:
اگر کدتان در فعالیت پیشبینیشدهای اجرا میشود، زمینه پیشبینیشدهای دریافت کنید
اگر کد برنامهتان از داخل فعالیت پیشبینیشده شما اجرا میشود، زمینه فعالیت آن ازقبل زمینه پیشبینیشده است. در این سناریو، تماسهای برقرارشده در آن فعالیت ازقبل میتوانند به سختافزار عینک دسترسی داشته باشند.
دریافت بافت پیشبینیشده برای کد درحال اجرا در عنصر برنامه تلفن
اگر بخشی از برنامه شما خارج از فعالیت پیشبینیشدهتان (مثل فعالیت تلفن یا سرویس) نیاز به دسترسی به سختافزار عینک دارد، باید بهطور صریح زمینه پیشبینیشدهای را دریافت کند. برای انجام این کار، از روش
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 نمونه زمینهای پیشبینیشده دیگری دریافت کند و سپس
بااستفاده از زمینه پیشبینیشده جدید، سرویسها یا منابع سیستم را دوباره مقداردهی اولیه کند.
ضبط کردن صدا با میکروفون عینک
میتوانید بااستفاده از دو روش متمایز از عینک صدا ضبط کنید:
- از بافت پیشبینیشده استفاده کنید.
- از «نمایه دستآزاد بلوتوث» (HFP) استفاده کنید.
انتخاب روش ضبط
روش انتخابی شما بستگی به این دارد که آیا به پردازش صدای با کیفیت بالا و مختص XR نیاز دارید یا ورودی صدای بلوتوث استاندارد.
| روش ضبط | دسترسی میکروفون | مورد استفاده رایج |
|---|---|---|
بافت پیشبینیشده |
چند میکروفون |
ضبط کردن بااستفاده از زمینه پیشبینیشده به برنامهتان امکان میدهد به چندین میکروفون از عینک و ویژگیهای سختافزاری تخصصی آن دسترسی داشته باشد، ازجمله:
|
بلوتوث HFP |
میکروفون تکی |
برای سازگاری فوری و آماده استفاده، به «نمایه دستآزاد بلوتوث» (HFP) متکی است. در این حالت، عینک بااستفاده از نمایههای استاندارد «هدست» و «نمایه توزیع صوتی پیشرفته» (A2DP) به تلفن متصل میشود و مانند یک دستگاه جانبی بلوتوث معمولی عمل میکند. اگر برنامهتان ازقبل برای ضبط استاندارد بلوتوث طراحی شده است، میتوانید از این روش برای ضبط صدا از عینک بدون ادغام قابلیتهای مختص واقعیت گسترده استفاده کنید. |
ضبط صدا بااستفاده از بافت پیشبینیشده
برای ضبط صدا بااستفاده از بافت پیشبینیشده، ابتدا مجوزهای زمان اجرای موردنیاز را درخواست کنید، و سپس صدا را بااستفاده از AudioRecord API، همانطور که در بخشهای زیر توضیح داده شده است، ضبط کنید.
درخواست اجازههای زمان اجرا
برای دسترسی به چند میکروفون در عینک، باید
اجازههای صوتی را بهطور خاص برای دستگاه پخششده درخواست کنید. اجازه استاندارد و محدود به تلفن
RECORD_AUDIO که کاربر برای برنامه شما در دستگاه همراهش اعطا کرده است
کافی نیست.
برای درخواست اجازهها، این مراحل را دنبال کنید:
- اجازه
RECORD_AUDIOرا در فایل مانیفست برنامهتان اعلام کنید. بسته به اینکه کد شما کجا اجرا میشود، اجازههای با محدوده دستگاه پخش را به یکی از روشهای زیر درخواست کنید:
- اجرای کد از فعالیت پیشبینیشده: از
ActivityResultLauncherباProjectedPermissionsResultContractاستفاده کنید. برای اطلاعات بیشتر درباره استفاده از این روش، بخش ثبتکننده اجازههای و بخشهای بعدی در راهنمای درخواست اجازههای سختافزار را ببینید. - اجرای کد از فعالیت تلفن میزبان: از
Activity#requestPermissions(permissions, requestCode, deviceId)استفاده کنید و شناسه دستگاه دریافتشده ازprojectedDeviceContextرا طبق آنچه در بخش آشنایی با گردش کار کاربر برای درخواست اجازه راهنمای درخواست اجازههای سختافزاری توضیح داده شده است ارائه دهید.
- اجرای کد از فعالیت پیشبینیشده: از
مقداردهی اولیه 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، و دیگر اجازههای مربوطه توسط تلفن کنترل میشوند و نه دستگاه متصل (مانند عینکهای صوتی یا عینکهای نمایشگر).
برای درخواست اجازهها، این مراحل را دنبال کنید:
اجازههای زیر را در فایل مانیفست برنامهتان اعلام کنید:
بااستفاده از جریان استاندارد اجازه 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استفاده نکنید زیرا اگر فعالیت پیشبینیشده آخرین عنصر راهاندازیشده باشد، زمینه برنامه میتواند زمینه عینک را بهاشتباه برگرداند.