Инженер по связям с разработчиками | Android XR
Узнайте, как создавать надежные аудиофункции с голосовым управлением на основе ИИ на устройстве. Мы расскажем обо всем, что вам нужно: как настроить активность очков, как записывать видеопоток с камеры и аудио с микрофона и как использовать голос и другие звуковые подсказки в качестве основного пользовательского интерфейса. На примере XR Language Teacher – приложения с открытым исходным кодом, созданного в Google AI Edge Gallery, – вы узнаете, как использовать Gemini Nano 4 (на основе Gemma 4, оптимизированной для устройств Android) и ML Kit, а также как с помощью среды выполнения LiteRT создавать быстрые XR-приложения, которые слышат, видят и говорят естественно.
Расшифровка
Здравствуйте!
Рад всех видеть.
Спасибо, что пришли.
Меня зовут Эми.
Сегодня мы с моей коллегой Элис расскажем, как создавать невероятные аудиофункции для умных очков с помощью Gemini Nano 4.
Давайте начнем.
Когда мы говорим об умных очках, мы имеем в виду портативные, легкие очки, которые предназначены для ношения в течение всего дня.
В эту категорию входят как очки с дисплеем, так и аудиоочки. Устройства могут сильно различаться по аппаратным характеристикам.
Первое, что вы заметите, – это сенсорная панель, которую можно использовать для ввода данных, например касаний и пролистываний.
Вам также понадобятся динамики, камера и микрофон.
И у них также может быть дисплей или нет, или, возможно, какая-то комбинация этого.
Сегодня мы поговорим о том, как создавать увлекательные и полезные аудиоподкасты, которые удобно слушать в дороге.
И одна из интересных особенностей аудиорежима заключается в том, что голос становится вашим пользовательским интерфейсом.
ИИ на устройстве отлично подходит для разработчиков, которые хотят создавать решения, работающие офлайн и на ходу.
И у нас отличные новости.
Если вы уже создаете приложения для Android, то вы уже создаете приложения для Android XR.
Существующие приложения для Android хорошо работают с аудиоочками, передавая голосовые уведомления и другие аудиоданные по Bluetooth.
Если вы хотите пойти дальше и адаптировать приложение для XR, есть много способов сделать это.
В этом докладе мы говорим о моделях на устройстве, но это далеко не исчерпывающий список.
Есть также облачные модели.
Например, Gemini Live API.
Вы можете интегрировать системных агентов, таких как Gemini, и все они помогут вам создать оптимальный интерфейс.
Как же создать систему, которая слышит, видит и говорит естественно?
Сегодня мы расскажем, как создавать такие надежные приложения с голосовым управлением на основе ИИ на устройстве.
Я расскажу вам об основах: как создавать приложения для очков, как настраивать действия, получать необходимые разрешения и использовать ML Kit для диалогового интерфейса.
Затем я передам слово Элис, и она расскажет о реальных примерах использования Gemini Nano 4.
Давайте посмотрим на код.
В случае с очками основную работу выполняет телефон, а контент проецируется с телефона на очки.
Для этого вам понадобятся две вещи.
Сначала вам понадобится библиотека Jetpack Projected.
Вам нужно будет добавить эту зависимость, и именно она будет содержать API для обеспечения этого проецируемого опыта.
dependencies {
implementation("androidx.xr.projected:projected:1.0.0-alpha13")
}Затем вам нужно будет создать новое действие специально для очков.
class ProjectedMainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// Glasses content
}
}
}Затем вы будете использовать эту библиотеку Jetpack Projected как в существующем приложении, так и в новом действии, чтобы два устройства могли беспрепятственно обмениваться данными, когда это необходимо.
Как и в случае с обычными действиями, вам нужно объявить его в манифесте.
<activity
android:name=".ProjectedMainActivity"
android:exported="false"
android:requiredDisplayCategory="android.hardware.display.category.XR_PROJECTED"
android:label="Example activity for audio glasses">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.XR_PROJECTED_LAUNCHER"/>
</intent-filter>
</activity>Основное отличие заключается в том, что вам нужно использовать XR_PROJECTED в качестве requiredDisplayCategory.
Это нужно, чтобы система знала, что эта деятельность связана с очками.
Затем выберите категорию намерений XR_PROJECTED_LAUNCHER.
Это позволит Gemini находить ваше приложение, чтобы пользователи могли запускать его с голосовым управлением.
Теперь
После настройки действия вы сможете использовать библиотеку Projected для работы приложения.
В этой библиотеке есть все инструменты, необходимые для создания полноценного интерфейса очков.
Давайте рассмотрим несколько ключевых API, которые вы будете использовать.
Первый метод, о котором я хочу рассказать, – это isProjectedDeviceConnected.
// Continuously monitor whether the glasses are connected
val isGlassesConnected by ProjectedContext.isProjectedDeviceConnected(
context,
scope.coroutineContext
).collectAsStateWithLifecycle(initialValue = false)
// Launch the activity
val options = ProjectedContext.createProjectedActivityOptions(context)
val intent = Intent(context, ProjectedMainActivity::class.java)
context.startActivity(intent, options.toBundle())
// Check if the device is audio only or has a display
val projectedDeviceController = ProjectedDeviceController.create(this@ProjectedMainActivity)
isVisualUiSupported = projectedDeviceController.capabilities.contains(CAPABILITY_VISUAL_UI)Так вы будете постоянно отслеживать подключение очков.
Вы используете это вместе с жизненным циклом устройства, чтобы знать, когда очки действительно доступны, то есть не только когда они сопряжены или включены, но и, например, когда их носят и они готовы к использованию.
И вот что, так что да.
Так вы будете точно знать, когда можно запускать мероприятие.
Для этого при запуске активности нужно задать определенные параметры.
Для этого вы будете использовать createProjectedActivityOptions в ProjectedContext.
И опять же, вы будете использовать это с контекстом намерения.
Ещё один инструмент, который вам понадобится, – ProjectedDeviceController.
Так вы сможете проверить, какой тип очков использует пользователь.
Как я уже говорил, умные очки могут быть с дисплеем или только с аудио. Вам нужно проверить, есть ли у них возможность CAPABILITY_VISUAL_UI. Это позволит вам адаптировать приложение для разных устройств и оптимизировать его работу на них.
При создании такого уникального опыта вам может потребоваться доступ к аппаратным компонентам устройства, например к камере очков.
Чтобы сделать это из транслируемого действия, вам нужно запросить соответствующие разрешения с помощью ProjectedActivityCompat.
// In a projected activity
private fun requestProjectedPermissions() {
lifecycleScope.launch {
try {
ProjectedActivityCompat.requestPermissions(
this@PermissionsFromProjectedActivity,
arrayOf(Manifest.permission.CAMERA),
// CAMERA_PERMISSION_REQUEST_CODE is a developer defined constant
requestCode = CAMERA_PERMISSION_REQUEST_CODE,
)
} catch (e: IllegalStateException) {
// Thrown when the projected system service can't be bound.
Log.e(TAG, "Request failed: projected service unavailable.", e)
}
}
}Даже если у вашего приложения уже есть разрешения, необходимые для телефона, вам все равно нужно запросить специальные разрешения для аппаратного обеспечения очков.
При работе с разрешениями всегда следуйте рекомендациям для Android.
Объявите необходимые разрешения в манифесте, запрашивайте их в контексте, в котором они нужны, и только те, которые действительно необходимы для работы приложения.
Конечно, у нас есть документация на сайте developer.android.com.
Но у нас также есть совершенно новый навык, навык прогнозируемого оборудования.
Вы можете использовать его с Android CLI в Android Studio, чтобы настроить рабочий процесс разрешений, и вы можете получить доступ к этому навыку, а затем и ко всем остальным навыкам Android.
android skills add projected-hardware
android skills add --allИтак, мы подключились, знаем возможности нашего оборудования и имеем нужные разрешения. Теперь давайте поговорим об инструментах, которые у вас есть для создания звука.
Поэтому при разработке аудиоконтента мы хотим убедиться, что создаем высококачественный продукт для пользователей.
И это может включать в себя несколько ключевых взаимодействий.
Во-первых, это использование голоса в качестве вывода, то есть возможность естественного общения с пользователем.
Далее мы будем использовать звуковые сигналы, чтобы отправлять пользователю определенные аудиосигналы.
А также обрабатывать ввод от пользователя, будь то через сенсорную панель с помощью касаний и пролистываний, или если вы хотите разрешить пользователю говорить с вашим приложением.
Итак, первое, о чем мы поговорим, – это использование голоса в качестве вывода.
Для этого я хочу представить новую библиотеку GenAI Text To Speech в ML Kit.
Сейчас она доступна в альфа-версии на некоторых устройствах Pixel и Samsung.
Мы очень рады, что у нас есть такая возможность, особенно для аудиоподкастов.
Если вы уже работали с синтезом речи, то, вероятно, знаете, что одна из проблем заключается в том, что системный голос не всегда звучит так естественно, как хотелось бы.
А подключение к облачным моделям для других голосов может привести к задержкам или дополнительным расходам.
Поэтому ML Kit обеспечивает гораздо более естественное звучание речи по сравнению с системным TTS.
ML Kit предлагает восемь уникальных голосов с разными характеристиками, которые вы можете выбрать для своего приложения.
ML Kit также предлагает надежную языковую поддержку с 21 различными локалями, которые вы можете выбрать.
И ещё одно огромное преимущество: после первоначального скачивания все процессы будут выполняться на устройстве.
Модель будет скачана в систему и станет доступна для всех приложений, поэтому размер вашего APK-файла не увеличится.
И она не только доступна офлайн, что особенно важно для пользователей очков, которые находятся в пути, у них может не быть Wi-Fi или они могут находиться в зонах с плохим покрытием, но вы также, скорее всего, снова увидите лучшую производительность, поскольку вам не нужно совершать этот вызов сервера туда и обратно.
Теперь
Давайте подробнее рассмотрим библиотеку и то, как с ней работать.
Для начала вам нужно добавить зависимость.
dependencies {
// ML Kit GenAI TTS SDK
implementation("com.google.mlkit:genai-text-to-speech:1.0.0-alpha1")
}Вам понадобится только ML Kit Text To Speech SDK.
И тогда вы можете начать писать свой код.
Поэтому библиотека GenAI ML Kit включает API синтезатора речи.
// 1. Initializing the SpeechSynthesizer client.
val options = SpeechSynthesizerOptions.Builder(Locale.US).build()
val client = SpeechSynthesis.getClient(options)
// 2. Check and download the voice model.
val voiceInfo = client.getSupportedVoices().firstOrNull()
if (voiceInfo != null && client.checkStatus(voiceInfo) == FeatureStatus.DOWNLOADABLE) {
client.download(voiceInfo).collect { status -> println("Download status: $status") }
} else if (voiceInfo == null) {
// Tip: Fallback to system TTS if ML Kit voice is unavailable
}Это позволяет легко внедрять выбранные голоса.
Первое, что нужно сделать, – инициализировать клиент с помощью синтезатора речи и задать региональные настройки.
Затем проверьте статус скачивания и выберите голос.
В этом примере мы выбираем первый голос, но я также хочу обратить ваше внимание на то, что мы используем firstOrNull.
Как я уже говорил, для ML Kit требуется телефон с определенными характеристиками, например Samsung или Pixel.
Если вы получите нулевое значение, это может означать, что пользователь не использует совместимый телефон.
И затем вам просто нужно убедиться, что у вас есть резервный вариант, будь то возврат к системному преобразованию текста в речь или воспроизведение записанного сообщения или что-то в этом роде.
Хорошо, а затем вы будете использовать синтез речи SpeechSynthesizerRequest для создания запроса, передавая ему входной текст, а затем устанавливая использование и тип контента.
// 3. Construct the SpeechSynthesizerRequest
val request = SpeechSynthesizerRequest(
input = SpeechSynthesizerRequest.Input.Text("Hello world", voiceInfo),
audioAttributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build()
)
// 4. Speak and collect status
client.synthesize(request).collect { response ->
when (response) {
is SpeechSynthesizerResponse.StatusUpdate -> println("Playback status: ${response.status}")
}
}Воспроизведите речь, отслеживайте статус и нет, чтобы вы знали, когда речь завершится, а затем соберите все ошибки, которые могут возникнуть.
и закрывать ресурсы по мере необходимости.
// 4. Speak and collect status
client.synthesize(request).collect { response ->
when (response) {
is SpeechSynthesizerResponse.StatusUpdate -> println("Playback status: ${response.status}")
}
}
// 5. Release resources when finished (e.g., on Activity destroy)
override fun onDestroy() {
super.onDestroy()
client.close()
}Это довольно просто, не так ли?
С помощью этих шагов вы действительно можете преобразовать звук вашего приложения, переходя от чего-то, что звучит так…
Здравствуйте, разработчики Android!
Рад всех видеть.
Надеемся, вам нравится наша презентация об аудиоконтенте.
и превратить его в нечто подобное.
Здравствуйте, разработчики Android!
Рад всех видеть.
Надеемся, вам нравится наша презентация об аудиоконтенте.
Так намного лучше, правда?
Окей.
Библиотека Text to Speech в ML Kit – это очень мощный инструмент, но вы не ограничены только этими голосами. С помощью этой библиотеки вы также можете воспроизводить звуковые сигналы.
// 1. Register Earcon resource with Builder
SpeechSynthesizerOptions.Builder(Locale.US)
.addEarcon(
name = "shutter_sound",
Uri = "path/to/earcon.file".toUri()
)
.build()
// 2. Request Earcon playback
val request = SpeechSynthesizerRequest(
input = SpeechSynthesizerRequest.Input.Earcon(
name = "shutter_sound"
),
audioAttributes = AudioAttributes.Builder()
.setUsage(AudioAttributes
.USAGE_ASSISTANCE_SONIFICATION)
.setContentType(AudioAttributes
.CONTENT_TYPE_SONIFICATION)
.build()
)Звуковые сигналы, как следует из названия, по сути являются звуковым эквивалентом значков.
Это позволит вам просто воспроизводить короткие звуки для общения с пользователем.
И так же, как и в случае с визуальными значками и звуковыми сигналами, пользователю нужно будет знать, что это такое и что они означают.
Поэтому поначалу вы можете захотеть соединить их с голосовыми уведомлениями, чтобы сделать это понятным.
Мы уже создали несколько стандартных звуков, которые доступны в Android Design Hub. Вы можете скачать их оттуда и использовать для таких событий, как открытие и закрытие приложения, уведомления об ошибках, а также включение и отключение звука.
Но у вас также есть возможность создавать свои собственные звуковые сигналы.
Есть несколько рекомендаций, которым мы советуем следовать, независимо от того, используете ли вы скачанные из Design Hub звуковые сигналы или создаете свои собственные.
Поэтому вам нужно быть внимательными к тому, как вы используете звуковые сигналы и как вы их используете.
Поэтому есть несколько вещей, о которых следует помнить, например, для начала просто постарайтесь не злоупотреблять звуковыми сигналами.
Не проигрывайте одни и те же звуки снова и снова, это может вызвать так называемую слуховую усталость.
И помните, что ваши звуковые сигналы должны быть супер и интуитивно понятными, а также используйте знакомые высоты и ритмы, когда вы их реализуете.
И, наконец, старайтесь избегать слишком сложных звуковых сигналов, которые требуют объяснения. Для таких случаев лучше подойдут голосовые уведомления.
И всегда помните, что то, что может звучать незаметно в студии или на вашей системе, на динамиках вашего компьютера, может стать раздражающим после повторного воспроизведения для ваших пользователей.
Поэтому старайтесь не перегружать пользователей звуками, которые могут их запутать или заставить игнорировать важные оповещения.
Теперь
Теперь у нас есть возможность предоставлять звуковые подсказки и обратную связь, будь то естественная речь или звуковые сигналы.
Но как же ввод?
Конечно, на устройстве есть сенсорная панель и специальные кнопки, которые можно использовать для нажатий и пролистываний.
Но вы также можете захотеть разрешить этот диалоговый интерфейс, для этого вы можете использовать API распознавания речи GenAI от ML Kit.
Сейчас функция находится на стадии альфа-тестирования и доступна на некоторых устройствах. Она работает в двух режимах.
Базовый режим использует традиционное распознавание речи на устройстве и доступен на устройствах Android с API уровня 31 и выше.
А в расширенном режиме используется Gemini Nano. Он доступен на устройствах Pixel 10 и более новых моделях и обеспечивает более высокое качество и поддержку большего количества языков.
Таким образом, SpeechRecognizer следует очень похожему шаблону кода, что и SpeechSynthesizer.
Вы создаете клиент, проверяете, доступна ли модель, скачиваете ее при необходимости, создаете запрос и освобождаете ресурсы.
Поэтому давайте сосредоточимся на основных различиях, когда вы создаете запрос, записываете звук с микрофона, а затем обрабатываете результат.
Опять же, вам нужно будет добавить необходимые зависимости.
dependencies {
// ML Kit Speech Recognition
implementation("com.google.mlkit:genai-speech-recognition:1.0.0-alpha1")
}Вам понадобится только Speech Recognition API.
После этого вы сможете создать запрос.
Поэтому вы будете использовать функцию приостановки, когда будете определять логику распознавания.
suspend fun startMyRecognition() {
// choose locale and mode
val options: SpeechRecognizerOptions =
speechRecognizerOptions {
locale = Locale.US
preferredMode = SpeechRecognizerOptions.Mode.MODE_ADVANCED
}
val speechRecognizer: SpeechRecognizer = SpeechRecognition.getClient(options)
val request: SpeechRecognizerRequest =
speechRecognizerRequest { audioSource = AudioSource.fromMic() }
...Затем укажите параметры распознавания речи, задав региональные настройки и preferredMode.
Здесь же вы выбираете audioSource и определяете сам запрос.
Затем вы запустите распознавание и получите ответ.
speechRecognizer.startRecognition(request).collect { response ->
when (response) {
is SpeechRecognizerResponse.PartialTextResponse -> {
// Real-time: Act immediately if trigger phrase is heard
handleResponse(response.text, speechRecognizer)
}
is SpeechRecognizerResponse.FinalTextResponse -> {
// Final result and recognition
handleResponse(response.text, speechRecognizer)
}
is SpeechRecognizerResponse.CompletedResponse -> {
resetListeningUi()
}
is SpeechRecognizerResponse.ErrorResponse -> {
Log.e("SpeechRec", "Error: ${response.e.errorCode}", response.e)
resetListeningUi()
}
...Таким образом, при использовании SpeechRecognizer вы можете получить четыре возможных ответа: частичный, окончательный, ошибка и завершено.
Частичное распознавание будет выполняться в реальном времени, поэтому оно используется для таких вещей, как горячие слова или ключевые слова.
Final – это окончательный аудиопоток после полного анализа и обновления. Completed – это поток после завершения речи. Error – это поток, в котором собираются все возможные ошибки.
А вот как мы объединяем все это.
// Helper function to check for multiple phrases and trigger the right action
private suspend fun handleResponse(text: String, recognizer: SpeechRecognizer) {
val savePhrases = listOf("save", "bookmark", "remember")
if (savePhrases.any { text.contains(it) }) {
// 1. Update app state immediately
saveItemForLater()
// 2. Stop the recognizer to enforce the action and prevent TTS echo
recognizer.stopRecognition()
// 3. Play the mute earcon for immediate, non-verbal confirmation
playEarcon()
// 4. Provide voice feedback
playAudioFeedback("Saved")
}
}Допустим, мы хотим, чтобы пользователь мог с помощью голосовой команды сказать приложению сохранить какой-то контент на потом.
Вы можете использовать одну явную голосовую команду, которую вы им сказали использовать, или вы можете разрешить им использовать ряд ключевых слов и фраз.
Итак, мы разрешаем использовать слова "сохранить", "добавить в закладки" и "запомнить".
Как только SpeechRecognizer обнаружит, что пользователь произнес одну из этих фраз, она остановит распознавание речи, сохранит контент, воспроизведет звуковой сигнал и с помощью функции преобразования текста в речь на основе генеративного ИИ в ML Kit сообщит пользователю, что контент сохранен.
Окей.
Мы многое пережили.
Это может показаться немного абстрактным.
Если вы похожи на меня, вы хотите увидеть несколько примеров из реальной жизни.
И мы с Алисой хотим рассказать вам именно об этом.
Она расскажет вам об XR-учителе языков в приложении Галерея Google AI Edge.
Его создала команда Алисы, и она покажет вам, как он работает.
Теперь ваша очередь.
Спасибо, Эми.
Да.
Я хочу начать с того, что многие из вас узнают.
Я учу японский и уже потратил кучу времени на приложения, карточки, серии занятий и все такое.
А потом я приземляюсь в Токио, захожу на рынок и понимаю, что почти ничего из того, что я вижу, не соответствует словам, которые я учил.
На прилавке лежит деревянная игрушка бильбоке. Я узнаю ее, но не знаю, как она называется по-японски.
Я хочу купить украшение в витрине магазина, но не знаю, как оно называется.
Именно в этот момент у меня есть наибольшая мотивация выучить слово.
а не на диване с приложением для изучения языков.
В этот момент я смотрю на то, что хочу изучить.
Вот что я хочу.
Я хочу носить умные очки весь день, просто ходить в них.
Если что-то привлечет мое внимание, я просто спрошу об этом вслух.
А вечером, когда я вернусь в отель, все, о чем я спрашивал, будет ждать меня в телефоне в виде карточек со словами.
И вот что мы создали.
Давайте посмотрим короткий ролик.
Мы назвали его XR-учитель языков.
Здравствуйте!
Что вы хотите узнать сегодня?
Вы можете настроить обучение в настройках преподавателя.
Например, выберите язык, который хотите выучить, и преподавателя, который вам подходит.
А, да.
Вы можете переключаться между режимами очков и телефона.
Как сказать "этот виноград" по-испански?
Судя по всему, этот вкусный виноград называется uvas.
По-испански вы можете сказать: "Estas uvas son deliciosas".
Я хочу сказать, что это зеленый виноград.
Давайте разберемся.
Чтобы указать зеленый виноград, вы можете сказать "Uvas verdes.
Me gustan las uvas verdes," is a great way to tell someone you like them.
Как это сказать по-французски и как попросить три штуки?
Давайте посмотрим.
Эти аппетитные пирожные называются des madeleines.
По-французски, чтобы попросить три, вы можете сказать: "Je voudrais trois madeleines, s'il vous plaît."
Как попросить сдачу?
Посмотрим, что у нас тут.
Чтобы попросить сдачу, скажите "Otsuri wa arimasu ka?".
что означает "У вас есть сдача?"
Вы также можете сказать "Otsuri o onegaishimasu", чтобы вежливо попросить сдачу.
А затем всё, что вы узнали, автоматически сохраняется в виде карточек со словами.
La botella de agua.
Esta es una botella de agua.
Да, а на вкладке "Учеба" можно проверить память и потренироваться.
Теперь
Да.
То есть всё, что вы только что видели, работало на устройстве, без подключения к сети.
В поездках я пользуюсь международным тарифным планом. Обычно все работает хорошо, пока я не захожу в торговый центр, метро или здание с толстыми бетонными стенами.
Часто бывает так, что в самый нужный момент сигнал оказывается самым слабым.
И я бы сказал, что опыт, который умирает, когда падает уровень сигнала, не является настоящим спутником в путешествии, поэтому мы сделали так, чтобы XR Language Teacher работал полностью в автономном режиме и на устройстве.
Как это было сделано?
Эми уже показала вам, как использовать кадр, проецируемый с камеры очков на телефон.
Она показала вам, как использовать ML Kit, распознавание речи, чтобы превратить текст в речь, а также ML Kit Text to Speech, чтобы превратить текст обратно в речь.
Нам не хватало только "мозга", который мог бы сопоставить изображение с камеры и вопрос и понять, что именно хочет узнать пользователь.
Это Gemini Nano 4, созданная на базе Gemma 4 – модели Google с открытым исходным кодом, которая работает локально на телефоне.
Поэтому на поддерживаемых флагманских устройствах Android вы можете оптимизировать производительность, используя API ML Kit напрямую.
Система Android сама позаботится о доставке модели, поэтому вам не нужно ничего добавлять в приложение.
Итак, микрофон передает данные в систему распознавания речи, а кадр с камеры и расшифровка вместе поступают в Gemini Nano 4. Затем Gemini Nano 4 передает данные в систему синтеза речи, и звук выводится через динамик.
Три модели, связанные друг с другом, работают локально на вашем телефоне.
Вот несколько причин, по которым Gemini Nano 4 особенно хорошо подходит для решения этой проблемы.
Во-первых, он видит и читает за один проход.
Благодаря ML Kit модель Gemini Nano 4 обрабатывает текст и изображения нативно, поэтому вам не нужно использовать детектор, классификатор и переводчик.
Это одна модель, которая анализирует кадр камеры и вопрос вместе.
Во-вторых, он поддерживает множество языков.
Gemini Nano 4 обучалась на более чем 140 языках.
Кроме того, для репетитора по языку одного перевода недостаточно.
Вам нужны идиомы, формальный и неформальный регистры, а также примеры предложений, которые мог бы сказать реальный человек.
И, наконец, она достаточно управляема, чтобы иметь индивидуальность.
Тренироваться произносить слова вслух может быть страшно.
Эта модель хорошо следует системным инструкциям, поэтому пользователь может выбрать образ учителя.
А терпеливый и подбадривающий ИИ-тренер может мотивировать людей попробовать.
И вот ещё одна вещь, которую я хотел бы подчеркнуть в дизайне.
Пока вы разговариваете в режиме очков, приложение в фоновом режиме создает карточки со словами.
Слово, перевод, пример предложения и фотография с момента, когда вы задали вопрос.
Пользователю не нужно ничего нажимать.
Они просто гуляют и разговаривают.
А потом, в тот же день, все это уже ждет их в телефоне. Именно этого мне не хватало, когда я был в Токио.
Поэтому аудиоформат не обязательно должен быть единственным.
Очки – это подходящая поверхность для любопытства, а телефон – для последующего размышления.
Пусть у вас останется что-то на память.
Теперь давайте рассмотрим несколько практических советов по созданию функций, работающих в реальном времени, с помощью Gemini Nano 4 и ML Kit, которые мы узнали при создании этой демоверсии.
Совет 1. Приоритизируйте задержку в реальном времени с помощью двухпроходного дизайна.
На аудиоочках, когда вы спрашиваете "Что это?".
Каждый момент тишины заметен.
В примере с XR Language Teacher нам нужны две вещи для каждого хода взаимодействия пользователя с моделью: немедленный устный ответ, а также структурированная словарная карточка для сохранения в приложении.
Если мы попросим модель сделать две вещи за один проход, то каждый токен, потраченный на генерацию фоновых данных, задержит первое произнесенное предложение.
Поэтому мы разделяем то, что нужно пользователю прямо сейчас, и то, что понадобится приложению через несколько секунд.
Поэтому мы разделили каждый ход на два этапа.
На первом этапе мы передаем кадр с камеры и вопрос только для устного ответа, запуская преобразование текста в речь, как только будет готово первое предложение.
И поскольку TPU или NPU заканчивают генерировать этот короткий ответ задолго до того, как преобразование текста в речь заканчивает говорить вслух, у нас фактически есть многосекундное окно, в котором пользователь все ещё слушает, а чип уже простаивает.
А поскольку на первом этапе объект уже был идентифицирован в текстовом ответе, на втором этапе изображение даже не требуется.
Мы просто передаем ответ первого этапа обратно в этот бездействующий чип, чтобы извлечь и сохранить структурированную карточку ещё до того, как учитель закончит говорить.
Теперь давайте подробнее рассмотрим первый этап, что подводит нас ко второму совету.
При трансляции используйте простые XML-теги.
Поскольку на первом этапе поток передается напрямую в систему синтеза речи, не стоит использовать тяжелые объекты JSON, чтобы не задерживать первое произнесенное слово.
Вместо этого вам нужен обычный текст, который можно сразу же произнести, с легкими встроенными тегами, когда приложению нужно выполнить действие в середине потока.
Позвольте мне показать вам проблему, которая привела нас сюда.
Помните деревянную игрушку бильбоке на прилавке, о которой я упоминал?
По-японски она называется "кендама".
Модель сразу распознала ее по изображению с камеры и дала правильный ответ: "Это кендама, деревянная игрушка".
Мы передали это предложение в систему синтеза речи на английском языке, и вот что получилось.
Это кен-ми, деревянная игрушка.
Это не совсем правильно.
Instead, that sentence should actually sound…
Это кендама, деревянная игрушка.
Почему так произошло?
Слово "кендама" пишется с использованием хираганы ("кен") и иероглифа кандзи ("дама").
Английский синтезатор речи отлично справляется с фонетической хираганой, например "кен", но совершенно не справляется с кандзи, превращая "дама" в нечто, похожее на "ми".
И это происходит на разных языках по-разному, если вы не переключаете региональные настройки.
Например, на испанском языке вы можете услышать…
Suh-BOH-luh…
Когда вы на самом деле ждете…
Cebolla.
Аналогично, на французском вы можете услышать…
Пэн-о-шо-ко-ля…
Когда вы на самом деле ждете…
Шоколадный хлеб.
Да, сеанс TTS обычно предназначен для одного региона, но преподавателю языка необходимо переключать код в каждом предложении.
Поэтому на первом этапе мы просим модель заключить каждое слово или фразу на языке перевода в целевой XML-тег по мере их поступления.
По мере поступления фрагментов наш клиент разделяет их по целевым языкам, направляет каждый фрагмент в соответствующий языковой модуль Text To Speech (японский, испанский, французский или любой другой язык, который вы изучаете), а затем воспроизводит их без задержек, переключая акцент в середине предложения, чтобы такие слова, как "кендама", звучали правильно.
Итак, вывод здесь заключается в том, что модель уже знает, какой словарный запас принадлежит какому языку, какие слова принадлежат какому языку, и наличие у нее тегов целевых диапазонов в строке во время потоковой передачи намного чище, чем последующая постобработка всего этого самостоятельно.
А вот как выглядит первый проход в коде.
// 1. Pass 1 System + Turn Prompt (XrLanguageTeacherPersona.kt)
// System: "Always start immediately with <reply>...</reply> (strictly 1 to 3 sentences total).
// Inside <reply>, wrap every Japanese word AND every complete Japanese example sentence
// inside <target>...</target> tags (never output <card>)."
val pass1TurnPrompt = """
The student is looking at the camera view and asked: "$question". Answer in English
teaching Japanese inside <reply>...</reply>: if they refer to 'this'/'that' or point to
an object, ground it to the focal item in the camera frame; if they ask for a phrase or
greeting, teach that phrase.
""".trimIndent()
// 2. Stream multimodal dialogue (ImagePart + TextPart) and split <target> tags to TTS
val generativeModel = Generation.getClient()
val pass1Request = generateContentRequest(ImagePart(frame), TextPart(pass1TurnPrompt)) {
temperature = 1.0f; topK = 1; maxOutputTokens = 1024
}
generativeModel.generateContentStream(pass1Request).collect { chunk ->
val delta = chunk.candidates.firstOrNull()?.text.orEmpty()
streamReplyAndSplitTargetTagsToTts(delta) // Speaks 1st complete sentence immediately
}
// Streams: "<reply>That's <target>けん玉</target> — a wooden toy. <target>けん玉をください。</target></reply>"Вверху мы используем запрос, чтобы указать модели, что она должна ответить одним-тремя предложениями внутри тегов reply, а затем заключить каждое японское слово или пример предложения в теги target.
Затем мы передаем камеру ImagePart и запрос TextPart в generateContentStream ML Kit, разбивая целевые фрагменты на преобразование текста в речь по мере поступления каждого предложения.
Это подводит меня ко второму проходу и нашему третьему совету.
Простые XML-теги отлично подходят для аудиопотока, но для сохранения данных в приложении лучше использовать структурированный вывод, который возвращает чистый типизированный объект.
Поскольку XR-учитель языков работает в режиме диалога, пользователи могут задавать уточняющие вопросы, не связанные с изучением новых слов.
А когда слово выучено, базе данных нужны чистые поля словаря, чтобы создать карточки со словами.
Поэтому вместо того, чтобы писать хрупкие регулярные выражения для синтаксического анализа расшифровки первого прохода, мы позволяем второму проходу извлекать типобезопасный объект в фоновом режиме.
А чтобы применить схему на втором этапе, мы используем API структурированного вывода ML Kit, который задействует ограниченное декодирование.
import com.google.mlkit.genai.prompt.Generation
import com.google.mlkit.genai.prompt.generateTypedContentRequest
import com.google.mlkit.genai.schema.annotations.Generable
import com.google.mlkit.genai.schema.annotations.Guide
// Pass 2 schema (XrLanguageTeacherGenerableSchemas.kt):
@Generable(description = "Vocabulary flashcard for the taught word or short phrase")
data class GenerableVocabCard(
@Guide(description = TeacherResponseSchema.WORD_FIELD_DESCRIPTION) val word: String,
@Guide(description = "Native-language meaning or translation") val meaning: String,
@Guide(description = "Target-language example sentence") val example: String,
@Guide(description = "Native-language translation of the example sentence") val example_translation: String,
)
@Generable(description = "Optional vocabulary card extracted from a teacher reply")
data class GenerableOptionalVocabCardWrapper(
@Guide(description = "Vocabulary flashcard if a vocabulary term was taught, or null otherwise")
val card: GenerableVocabCard?
)Вам нужно просто добавить аннотацию @Generable к классу данных и @Guide к каждому полю, как в примере с классом карточки со словом.
А благодаря тому, что поле card может принимать значение null, на втором этапе возвращается заполненная карточка, если было выучено новое слово, или null, если это был просто разговор.
А вот как выглядит код для второго прохода.
// 1. Pass 1 Streamed Output -> Pass 2 Extraction Prompt (no image needed!)
val replyWithTargetTags =
"<reply>That's <target>けん玉</target> — a wooden toy. <target>けん玉をください。</target></reply>"
val pass2Prompt = """
If the teacher reply below teaches or introduces a specific Japanese vocabulary item, extract
only its core focal dictionary term into "card" with keys "word", "meaning", "example", and
"example_translation". If purely conversational with no focal term taught, set "card" to null:
$replyWithTargetTags
""".trimIndent()
// 2. Extract @Generable card on idle NPU while TTS audio plays
val pass2Request = generateContentRequest(TextPart(pass2Prompt)) {
temperature = 1.0f; topK = 1; maxOutputTokens = 256
}
val typedRequest =
generateTypedContentRequest(pass2Request, GenerableOptionalVocabCardWrapper::class)
val card: GenerableVocabCard? =
generativeModel.generateContent(typedRequest).candidates.firstOrNull()?.response?.card
// Extracts: {"word": "けん玉", "meaning": "a wooden toy", "example": "けん玉をください。", ...}Вверху мы берем текст ответа, полученный на первом этапе, и создаем запрос на извлечение. Изображение не требуется.
Затем, пока пользователь слушает синтез речи, мы передаем запрос и класс-оболочку в generateTypedContentRequest и извлекаем чистую карточку без тегов или null, если слово не было изучено.
Теперь
Мы многое обсудили.
Вы можете подумать: "Отлично, ещё одна вещь, которая прекрасно выглядит на сцене, но которой я никогда не воспользуюсь".
Не этот!
XR Language Teacher поставляется в приложении Галерея Google AI Edge вместе с другими ИИ-функциями на устройстве.
Вы можете скачать его сегодня, установить на свой телефон и навести на любой предмет, чтобы узнать, как он называется на другом языке.
Если вы хотите узнать, как устроена эта реализация, ее исходный код доступен на GitHub.
Не беспокойтесь, если не у всех ваших пользователей есть аудио- или видеоочки или поддерживаемые флагманские устройства Android.
В нашей базе кода также есть примеры того, как адаптировать этот интерфейс для мобильных устройств.
Если вы хотите охватить и другие платформы, в коде приложения Галерея Google AI Edge также показано, как создать эту функцию с помощью среды выполнения Google LiteRT и MediaPipe Tasks API.
Если вы хотите использовать другие языковые или аудиомодели с открытым исходным кодом, работающие на устройстве, вы можете узнать, как это сделать, из кода.
Поэтому мы открыли исходный код XR-учителя языков, чтобы вам было проще создавать прототипы.
Укажите агенту на наш репозиторий GitHub, чтобы превратить этот ИИ-инструмент во что-то другое.
Это может быть гид по музею, помощник по кулинарии, который смотрит на вашу стойку, пошаговое руководство по ремонту, рассказчик о специальных возможностях, инструмент для полевой инспекции.
Модель, среда выполнения, реализация с открытым исходным кодом и инструменты уже доступны, чтобы вы могли подготовиться к запуску умных очков этой осенью.
Нам будет интересно посмотреть на результаты вашей работы с ними!
Сегодня мы показали, на что способны ИИ-модели, работающие на устройстве.
Вот несколько способов начать.
Сначала соберите ресурсы на экране.
Скачайте приложение AI Edge Gallery, чтобы попробовать демоверсию и посмотреть пример кода на GitHub.
Отсканируйте QR-коды, чтобы получить доступ к Android XR SDK, ML Kit API и Google AI Edge.
Во-вторых, вы можете начать экспериментировать прямо на телефоне уже сегодня.
Мы уже рассмотрели множество вещей, которые можно создать с помощью подхода "сначала мобильные".
В-третьих, посмотрите на свое мобильное приложение и подумайте, какие основные функции было бы удобнее использовать с голосовым управлением.
Чтобы добавить поддержку очков, достаточно добавить в существующий APK-файл Projected Activity.
Большое спасибо за внимание.
Нам не терпится увидеть, какие невероятные аудиофункции вы создадите для Android и умных очков.
Спасибо!
dependencies {
implementation("androidx.xr.projected:projected:1.0.0-alpha13")
}class ProjectedMainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// Glasses content
}
}
}<activity
android:name=".ProjectedMainActivity"
android:exported="false"
android:requiredDisplayCategory="android.hardware.display.category.XR_PROJECTED"
android:label="Example activity for audio glasses">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.XR_PROJECTED_LAUNCHER"/>
</intent-filter>
</activity>// Continuously monitor whether the glasses are connected
val isGlassesConnected by ProjectedContext.isProjectedDeviceConnected(
context,
scope.coroutineContext
).collectAsStateWithLifecycle(initialValue = false)
// Launch the activity
val options = ProjectedContext.createProjectedActivityOptions(context)
val intent = Intent(context, ProjectedMainActivity::class.java)
context.startActivity(intent, options.toBundle())
// Check if the device is audio only or has a display
val projectedDeviceController = ProjectedDeviceController.create(this@ProjectedMainActivity)
isVisualUiSupported = projectedDeviceController.capabilities.contains(CAPABILITY_VISUAL_UI)// In a projected activity
private fun requestProjectedPermissions() {
lifecycleScope.launch {
try {
ProjectedActivityCompat.requestPermissions(
this@PermissionsFromProjectedActivity,
arrayOf(Manifest.permission.CAMERA),
// CAMERA_PERMISSION_REQUEST_CODE is a developer defined constant
requestCode = CAMERA_PERMISSION_REQUEST_CODE,
)
} catch (e: IllegalStateException) {
// Thrown when the projected system service can't be bound.
Log.e(TAG, "Request failed: projected service unavailable.", e)
}
}
}android skills add projected-hardware
android skills add --alldependencies {
// ML Kit GenAI TTS SDK
implementation("com.google.mlkit:genai-text-to-speech:1.0.0-alpha1")
}// 1. Initializing the SpeechSynthesizer client.
val options = SpeechSynthesizerOptions.Builder(Locale.US).build()
val client = SpeechSynthesis.getClient(options)
// 2. Check and download the voice model.
val voiceInfo = client.getSupportedVoices().firstOrNull()
if (voiceInfo != null && client.checkStatus(voiceInfo) == FeatureStatus.DOWNLOADABLE) {
client.download(voiceInfo).collect { status -> println("Download status: $status") }
} else if (voiceInfo == null) {
// Tip: Fallback to system TTS if ML Kit voice is unavailable
}// 3. Construct the SpeechSynthesizerRequest
val request = SpeechSynthesizerRequest(
input = SpeechSynthesizerRequest.Input.Text("Hello world", voiceInfo),
audioAttributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build()
)
// 4. Speak and collect status
client.synthesize(request).collect { response ->
when (response) {
is SpeechSynthesizerResponse.StatusUpdate -> println("Playback status: ${response.status}")
}
}// 4. Speak and collect status
client.synthesize(request).collect { response ->
when (response) {
is SpeechSynthesizerResponse.StatusUpdate -> println("Playback status: ${response.status}")
}
}
// 5. Release resources when finished (e.g., on Activity destroy)
override fun onDestroy() {
super.onDestroy()
client.close()
}// 1. Register Earcon resource with Builder
SpeechSynthesizerOptions.Builder(Locale.US)
.addEarcon(
name = "shutter_sound",
Uri = "path/to/earcon.file".toUri()
)
.build()
// 2. Request Earcon playback
val request = SpeechSynthesizerRequest(
input = SpeechSynthesizerRequest.Input.Earcon(
name = "shutter_sound"
),
audioAttributes = AudioAttributes.Builder()
.setUsage(AudioAttributes
.USAGE_ASSISTANCE_SONIFICATION)
.setContentType(AudioAttributes
.CONTENT_TYPE_SONIFICATION)
.build()
)dependencies {
// ML Kit Speech Recognition
implementation("com.google.mlkit:genai-speech-recognition:1.0.0-alpha1")
}suspend fun startMyRecognition() {
// choose locale and mode
val options: SpeechRecognizerOptions =
speechRecognizerOptions {
locale = Locale.US
preferredMode = SpeechRecognizerOptions.Mode.MODE_ADVANCED
}
val speechRecognizer: SpeechRecognizer = SpeechRecognition.getClient(options)
val request: SpeechRecognizerRequest =
speechRecognizerRequest { audioSource = AudioSource.fromMic() }
...speechRecognizer.startRecognition(request).collect { response ->
when (response) {
is SpeechRecognizerResponse.PartialTextResponse -> {
// Real-time: Act immediately if trigger phrase is heard
handleResponse(response.text, speechRecognizer)
}
is SpeechRecognizerResponse.FinalTextResponse -> {
// Final result and recognition
handleResponse(response.text, speechRecognizer)
}
is SpeechRecognizerResponse.CompletedResponse -> {
resetListeningUi()
}
is SpeechRecognizerResponse.ErrorResponse -> {
Log.e("SpeechRec", "Error: ${response.e.errorCode}", response.e)
resetListeningUi()
}
...// Helper function to check for multiple phrases and trigger the right action
private suspend fun handleResponse(text: String, recognizer: SpeechRecognizer) {
val savePhrases = listOf("save", "bookmark", "remember")
if (savePhrases.any { text.contains(it) }) {
// 1. Update app state immediately
saveItemForLater()
// 2. Stop the recognizer to enforce the action and prevent TTS echo
recognizer.stopRecognition()
// 3. Play the mute earcon for immediate, non-verbal confirmation
playEarcon()
// 4. Provide voice feedback
playAudioFeedback("Saved")
}
}// 1. Pass 1 System + Turn Prompt (XrLanguageTeacherPersona.kt)
// System: "Always start immediately with <reply>...</reply> (strictly 1 to 3 sentences total).
// Inside <reply>, wrap every Japanese word AND every complete Japanese example sentence
// inside <target>...</target> tags (never output <card>)."
val pass1TurnPrompt = """
The student is looking at the camera view and asked: "$question". Answer in English
teaching Japanese inside <reply>...</reply>: if they refer to 'this'/'that' or point to
an object, ground it to the focal item in the camera frame; if they ask for a phrase or
greeting, teach that phrase.
""".trimIndent()
// 2. Stream multimodal dialogue (ImagePart + TextPart) and split <target> tags to TTS
val generativeModel = Generation.getClient()
val pass1Request = generateContentRequest(ImagePart(frame), TextPart(pass1TurnPrompt)) {
temperature = 1.0f; topK = 1; maxOutputTokens = 1024
}
generativeModel.generateContentStream(pass1Request).collect { chunk ->
val delta = chunk.candidates.firstOrNull()?.text.orEmpty()
streamReplyAndSplitTargetTagsToTts(delta) // Speaks 1st complete sentence immediately
}
// Streams: "<reply>That's <target>けん玉</target> — a wooden toy. <target>けん玉をください。</target></reply>"import com.google.mlkit.genai.prompt.Generation
import com.google.mlkit.genai.prompt.generateTypedContentRequest
import com.google.mlkit.genai.schema.annotations.Generable
import com.google.mlkit.genai.schema.annotations.Guide
// Pass 2 schema (XrLanguageTeacherGenerableSchemas.kt):
@Generable(description = "Vocabulary flashcard for the taught word or short phrase")
data class GenerableVocabCard(
@Guide(description = TeacherResponseSchema.WORD_FIELD_DESCRIPTION) val word: String,
@Guide(description = "Native-language meaning or translation") val meaning: String,
@Guide(description = "Target-language example sentence") val example: String,
@Guide(description = "Native-language translation of the example sentence") val example_translation: String,
)
@Generable(description = "Optional vocabulary card extracted from a teacher reply")
data class GenerableOptionalVocabCardWrapper(
@Guide(description = "Vocabulary flashcard if a vocabulary term was taught, or null otherwise")
val card: GenerableVocabCard?
)// 1. Pass 1 Streamed Output -> Pass 2 Extraction Prompt (no image needed!)
val replyWithTargetTags =
"<reply>That's <target>けん玉</target> — a wooden toy. <target>けん玉をください。</target></reply>"
val pass2Prompt = """
If the teacher reply below teaches or introduces a specific Japanese vocabulary item, extract
only its core focal dictionary term into "card" with keys "word", "meaning", "example", and
"example_translation". If purely conversational with no focal term taught, set "card" to null:
$replyWithTargetTags
""".trimIndent()
// 2. Extract @Generable card on idle NPU while TTS audio plays
val pass2Request = generateContentRequest(TextPart(pass2Prompt)) {
temperature = 1.0f; topK = 1; maxOutputTokens = 256
}
val typedRequest =
generateTypedContentRequest(pass2Request, GenerableOptionalVocabCardWrapper::class)
val card: GenerableVocabCard? =
generativeModel.generateContent(typedRequest).candidates.firstOrNull()?.response?.card
// Extracts: {"word": "けん玉", "meaning": "a wooden toy", "example": "けん玉をください。", ...}