Android XR

Audio-first-Erlebnisse für intelligente Brillen mit Gemini Nano 4 entwickeln

Lautsprecher

Info

Über den Vortrag

Hier erfahren Sie, wie Sie mithilfe von On-Device-KI robuste, per Sprachbefehl gesteuerte Audiofunktionen entwickeln können. Wir decken alles ab, was Sie brauchen: Einrichten der Brillenaktivität, Erfassen von Mikrofon- und Kamerafeeds und Verwenden von Sprache zusammen mit anderen Audiohinweisen als ultimative Benutzeroberfläche. Anhand eines spannenden Praxisbeispiels des Open-Source-XR-Language Teacher, der in der Google AI Edge Gallery App erstellt wurde, erfahren Sie, wie Sie Gemini Nano 4 (basiert auf Gemma 4 und ist für Android-Geräte optimiert) und ML Kit orchestrieren und die LiteRT-Laufzeit nutzen können, um schnelle XR-Anwendungen zu erstellen, die auf natürliche Weise hören, sehen und sprechen können.

Zusammenfassung

  • Die Jetpack Projected-Bibliothek (Alpha) verbindet Android-Smartphones mit Smart Glasses. Wenn Sie Ihrer vorhandenen App eine projizierte Aktivität hinzufügen, werden Inhalte auf Brillen mit Display oder nur mit Audio projiziert.
  • ML Kit führt Alpha-On-Device-Sprachbibliotheken für Audio-First-Interaktionen auf ausgewählten Geräten ein. Die Funktion bietet Text-zu-Sprache mit 8 Stimmen in 21 Sprachen und Spracherkennung mit einem erweiterten Gemini Nano-Modus auf dem Pixel 10.
  • Gemini Nano 4 verarbeitet Kamerabilder und transkribierte Fragen zusammen in einem Durchgang. Sie ermöglicht die Offline-Nutzung, indem sie On-Device-Sprachmodelle lokal auf unterstützten Flaggschiff-Smartphones verkettet.
  • Ein Two-Pass-Design priorisiert die Echtzeit-Audiolatenz auf Smart Glasses. In Durchgang 1 wird gesprochener Text mit Inline-XML-Tags für den Wechsel der Sprache mitten im Satz gestreamt, während in Durchgang 2 im Hintergrund strukturierte Daten extrahiert werden.
  • Die Open-Source-Implementierung von XR Language Teacher ist in der Google AI Edge Gallery App enthalten. Entwickler können den Code für andere multimodale Assistenten-Anwendungsfälle anpassen.

Ressourcen

Gemini Notebook

Themen

  • intelligent-eyewear
  • android-xr
  • gemini-nano
  • on-device-ai
  • ml-kit
  • Spracherkennung
  • Text-zu-Sprache

Transkript

Einführung

  • Hallo und herzlich willkommen.

  • Ich freue mich, euch alle heute zu sehen.

  • Schön, dass Sie dabei waren.

  • Ich bin Amy.

  • Heute spreche ich mit meiner Kollegin Alice darüber, wie Sie mit Gemini Nano 4 beeindruckende Audio-First-Anwendungen für Smarte Brillen entwickeln können.

  • Legen wir also los.

  • Wenn wir von Smart Glasses sprechen, meinen wir tragbare, leichte Brillen, die für den täglichen Gebrauch gedacht sind.

  • Diese Kategorie umfasst sowohl Brillen mit Display als auch Brillen, die nur Audio wiedergeben. Bei diesen Geräten gibt es unterschiedliche Hardware.

  • Als Erstes fällt auf, dass sie ein Touchpad haben, das für Nutzereingaben wie Tippen und Wischen verwendet werden kann.

  • Außerdem sind Lautsprecher, eine Kamera und ein Mikrofon vorhanden.

  • Und sie können ein Display haben oder nicht, oder eine Kombination aus beidem.

  • Heute sprechen wir über Audio-First und darüber, wie man ansprechende, intelligente und mobile Erlebnisse schafft.

  • Und das Interessante an Audio-only ist, dass die Stimme hier zur Benutzeroberfläche wird.

  • On-Device-KI ist ideal für Entwickler, die Offline- und mobile Funktionen anbieten möchten.

Für Android XR entwickeln

  • Und es gibt gute Neuigkeiten.

  • Wenn Sie also bereits für Android entwickeln, entwickeln Sie auch schon für Android XR.

  • Bestehende Android-Apps sind so konzipiert, dass sie sich gut auf Audiobrillen übertragen lassen und gesprochene Benachrichtigungen und andere Audioinhalte über Bluetooth wiedergeben.

  • Wenn Sie Ihre App für XR optimieren möchten, gibt es viele Möglichkeiten.

  • In diesem Vortrag geht es um On-Device-Modelle, aber das ist keineswegs eine vollständige Liste.

  • Es gibt auch Cloud-Modelle.

  • Es gibt zum Beispiel die Gemini Live API.

  • Es gibt System-Agents, die Sie einbinden können, z. B. Gemini. Mit all diesen Agents können Sie die für Sie am besten geeignete Umgebung erstellen.

  • Wie erstellt man also eine Anwendung, die auf natürliche Weise hören, sehen und sprechen kann?

  • Heute zeigen wir Ihnen, wie Sie diese robusten, per Sprachbefehl bedienbaren Funktionen erstellen, die auf On-Device-KI basieren.

  • Ich zeige Ihnen die Grundlagen: wie Sie für Brillen entwickeln, Ihre Aktivität einrichten, die erforderlichen Berechtigungen erhalten und dann ML Kit für die konversationelle Benutzeroberfläche verwenden.

  • Anschließend wird Alice Ihnen einige reale Beispiele für die Verwendung von Gemini Nano 4 zeigen.

  • Sehen wir uns den Code an.

  • Bei der Datenbrille übernimmt das Smartphone die meiste Arbeit und die Inhalte werden vom Smartphone auf die Brille projiziert.

  • Für die Ersteinrichtung sind zwei Dinge erforderlich, um diese Geräte zu verbinden.

  • Zuerst benötigen Sie die Jetpack Projected-Bibliothek.

  • Sie müssen diese Abhängigkeit hinzufügen, die die APIs für die projizierte Umgebung enthält.

    dependencies {
       implementation("androidx.xr.projected:projected:1.0.0-alpha13")
    }
  • Außerdem müssen Sie eine neue Aktivität speziell für die Brille erstellen.

    class ProjectedMainActivity : ComponentActivity() {
       override fun onCreate(savedInstanceState: Bundle?) {
           super.onCreate(savedInstanceState)
           setContent {
               // Glasses content
           }
       }
    }
  • Anschließend nutzen Sie die Jetpack Projected-Bibliothek sowohl in Ihrer vorhandenen App als auch in dieser neuen Aktivität, damit die beiden Geräte bei Bedarf reibungslos kommunizieren können.

  • Wie jede normale Aktivität, die Sie gesehen haben, deklarieren Sie diese in Ihrem Manifest.

    <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>
  • Der Hauptunterschied besteht darin, dass Sie XR_PROJECTED als requiredDisplayCategory verwenden müssen.

  • So weiß das System, dass diese Aktivität für Brillen gedacht ist.

  • Außerdem sollten Sie die Intent-Kategorie XR_PROJECTED_LAUNCHER verwenden.

  • Dadurch kann Gemini Ihre App finden, sodass Nutzer sie unterwegs per Sprachbefehl starten können.

  • Okay.

  • Sobald Sie diese Activity eingerichtet haben, verwenden Sie wie erwähnt die Projected-Bibliothek, um die Nutzerfreundlichkeit der App zu ermöglichen.

  • Diese Bibliothek enthält alle Tools, die Sie benötigen, um die vollständige Funktionalität der Datenbrille zu entwickeln.

  • Sehen wir uns einige der wichtigsten APIs an, die Sie hier verwenden werden.

  • Als Erstes möchte ich die isProjectedDeviceConnected-Methode erwähnen.

    // 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)
  • So können Sie die Verbindung der Datenbrille kontinuierlich überwachen.

  • Sie verwenden diese Funktion zusammen mit einem Gerätelebenszyklus, um zu erfahren, wann die Brille tatsächlich verfügbar ist, also nicht nur, wenn sie gekoppelt oder eingeschaltet ist, sondern auch, wenn sie getragen wird und einsatzbereit ist.

  • Und das ist, ja.

  • So wissen Sie genau, wann Sie die Aktivität starten können.

  • Wenn Sie die Aktivität starten möchten, müssen Sie bestimmte Optionen festlegen.

  • Dafür verwendest du die createProjectedActivityOptions auf der ProjectedContext.

  • Und dann verwenden Sie das mit dem Kontext für die Absicht.

  • Ein weiteres Tool, das Sie verwenden sollten, ist ein ProjectedDeviceController.

  • So kannst du prüfen, welche Art von Brille der Nutzer verwendet.

  • Wie ich bereits erwähnt habe, gehören zu intelligenten Brillen sowohl Brillen mit Display als auch Brillen, die nur Audio wiedergeben. Sie sollten also prüfen, ob die Funktionen CAPABILITY_VISUAL_UI enthalten. So können Sie Ihre App für verschiedene Geräte anpassen und die Nutzung für das jeweilige Gerät optimieren.

  • Wenn Sie diese differenzierte Umgebung erstellen, möchten Sie möglicherweise auf die Hardware des Geräts zugreifen, z. B. auf die Kamera der Brille.

  • Wenn Sie dies über eine projizierte Aktivität tun möchten, müssen Sie die entsprechenden Berechtigungen mit ProjectedActivityCompat anfordern.

    // 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)
           }
       }
    }
  • Und selbst wenn Ihre App bereits die für das Smartphone erforderlichen Berechtigungen hat, müssen Sie auch die spezifischen Berechtigungen für die Hardware der Brille anfordern.

  • Beachten Sie bei der Arbeit mit Berechtigungen immer die allgemeinen Best Practices für Android.

  • Deklarieren Sie die erforderlichen Berechtigungen im Manifest, fragen Sie die Berechtigungen im Kontext ab, in dem sie benötigt werden, und fordern Sie nur die Berechtigungen an, die für die Funktion Ihrer App tatsächlich erforderlich sind.

  • Natürlich haben wir das auf unserer Website unter developer.android.com dokumentiert.

  • Wir haben aber auch eine ganz neue Fähigkeit, die projizierte Hardware-Fähigkeit.

  • Sie können diesen Skill mit der Android-Befehlszeile in Android Studio verwenden, um Ihren Berechtigungs-Workflow einzurichten. Sie können auf diesen Skill und dann auch auf alle anderen Android-Skills zugreifen.

    android skills add projected-hardware
    android skills add --all

Audiobasierte Formate entwerfen

  • Nachdem wir nun verbunden sind, unsere Hardwarefunktionen kennen und die richtigen Berechtigungen haben, sprechen wir über die Tools, die Sie zum Erstellen dieser Audiofunktionen benötigen.

  • Wenn wir also ein Audio-First-Erlebnis entwerfen, möchten wir sicherstellen, dass wir ein hochwertiges Erlebnis für die Nutzer schaffen.

  • Und das könnte einige wichtige Interaktionen umfassen.

  • Erstens: Die Sprachausgabe, also die Möglichkeit, mit Ihrer Stimme natürlich mit dem Nutzer zu sprechen.

  • Als Nächstes würden Sie Earcons verwenden, um bestimmte Audiosignale an die Nutzenden zu senden.

  • Außerdem werden Nutzereingaben verarbeitet, z. B. über das Touchpad durch Tippen und Wischen oder auch per Spracheingabe, wenn Sie das zulassen möchten.

  • Als Erstes sprechen wir über die Verwendung von Sprache als Ausgabe.

  • Dafür möchte ich die neue GenAI-Text-to-Speech-Bibliothek von ML Kit vorstellen.

  • Die Funktion ist jetzt als Alphaversion auf ausgewählten Pixel- und Samsung-Geräten verfügbar.

  • Das ist etwas, worauf wir uns besonders freuen, vor allem bei diesen Audio-First-Erlebnissen.

  • Wenn Sie schon einmal mit Text-zu-Sprache gearbeitet haben, wissen Sie wahrscheinlich, dass die Systemstimme nicht immer so natürlich klingt, wie Sie es sich wünschen.

  • Wenn Sie für andere Stimmen eine Verbindung zu Cloud-Modellen herstellen, kann dies zu Latenz oder zusätzlichen Kosten führen.

  • Mit ML Kit erhalten Sie also eine viel natürlicher klingende Sprache als mit der System-TTS.

  • ML Kit bietet acht verschiedene Stimmen mit unterschiedlichen Eigenschaften, die Sie für Ihre App auswählen können.

  • Außerdem bietet ML Kit eine robuste Sprachunterstützung mit 21 verschiedenen Sprachen, aus denen Sie auswählen können.

  • Und das Beste daran: Nach dem ersten Download funktioniert das Ganze komplett auf dem Gerät.

  • Dieses Modell wird also auf das System heruntergeladen und von mehreren Apps gemeinsam genutzt. Die Größe Ihres APKs wird dadurch nicht erhöht.

  • Und es ist nicht nur offline verfügbar, was besonders für Brillenträger wichtig ist, die unterwegs sind und möglicherweise kein WLAN haben oder sich in Gebieten mit schlechter Abdeckung befinden, sondern Sie werden wahrscheinlich auch eine bessere Leistung sehen, da Sie keinen Roundtrip-Aufruf an einen Server machen müssen.

  • Okay.

  • Sehen wir uns also die Details der Bibliothek an und wie wir damit entwickeln können.

  • Zuerst müssen Sie natürlich die Abhängigkeit hinzufügen.

    dependencies {
       // ML Kit GenAI TTS SDK
       implementation("com.google.mlkit:genai-text-to-speech:1.0.0-alpha1")
    }
  • Hier benötigen Sie nur das Text-to-Speech SDK von ML Kit.

  • Anschließend können Sie mit dem Schreiben des Codes beginnen.

  • Die GenAI ML Kit-Bibliothek enthält also die Speech Synthesizer APIs.

    // 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
    }
  • So können Sie die gewünschten Stimmen ganz einfach implementieren.

  • Als Erstes initialisieren Sie Ihren Client mit dem Speech-Synthesizer und legen die gewünschte Sprache fest.

  • Als Nächstes prüfst du den Downloadstatus und wählst die Stimme aus.

  • In diesem Beispiel wählen wir die erste Stimme aus. Beachten Sie, dass wir firstOrNull verwenden.

  • Wie bereits erwähnt, ist für ML Kit ein bestimmtes Smartphone erforderlich, also entweder ein Samsung- oder ein Pixel-Gerät.

  • Wenn Sie einen Nullwert erhalten, könnte das bedeuten, dass der Nutzer kein kompatibles Smartphone verwendet.

  • Und dann müssen Sie nur noch dafür sorgen, dass Sie einen Fallback haben, sei es, dass Sie auf die System-Text-zu-Sprache-Funktion zurückgreifen oder eine aufgezeichnete Nachricht abspielen oder so etwas.

  • Okay, dann verwenden Sie Speech Synthe- SpeechSynthesizerRequest, um die Anfrage zu erstellen. Geben Sie den Eingabetext an und legen Sie die Nutzung und den Inhaltstyp fest.

    // 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}")
       }
    }
  • Spielen Sie die Sprache ab, überwachen Sie den Status und achten Sie darauf, wann die Sprache abgeschlossen ist. Erfassen Sie dann alle Fehler, die auftreten können.

  • Schließen Sie dann bei Bedarf alle Ressourcen.

    // 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()
    }
  • Das ist ziemlich einfach, oder?

  • Mit diesen Schritten können Sie den Sound Ihrer App wirklich verändern, von etwas, das so klingt…

  • Hallo, Android-Entwickler,

  • Schön, dass Sie alle heute hier sind.

  • Wir hoffen, dass euch unsere Präsentation zu Audio-First-Inhalten gefällt.

  • zu etwas, das eher so klingt.

  • Hallo Android-Entwickler,

  • Schön, dass Sie alle heute hier sind.

  • Wir hoffen, dass euch unsere Präsentation zu Audio-First-Inhalten gefällt.

  • Viel besser, oder?

  • Okay.

  • Die Text-zu-Sprache-Bibliothek von ML Kit ist ein sehr leistungsstarkes Tool. Sie können damit aber nicht nur diese Stimmen verwenden, sondern auch Earcons abspielen.

    // 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()
    )
  • Earcons sind, wie der Name schon sagt, im Grunde das Audioäquivalent von Icons.

  • Damit können Sie kurze Töne abspielen, um mit dem Nutzer zu kommunizieren.

  • Und genau wie bei visuellen Symbolen müssen die Nutzer wissen, was diese Earcons sind und was sie bedeuten.

  • Am Anfang ist es daher sinnvoll, sie mit Sprachbenachrichtigungen zu kombinieren, um das zu verdeutlichen.

  • Wir haben bereits einige standardisierte Töne erstellt, die im Android Design Hub verfügbar sind. Sie können sie direkt von dort herunterladen und für Dinge wie das Öffnen und Schließen von Apps, Fehlermeldungen und das Stummschalten oder Aufheben der Stummschaltung verwenden.

  • Sie haben aber auch die Möglichkeit, eigene benutzerdefinierte Earcons zu erstellen.

  • Für Earcons gibt es einige Best Practices, die wir empfehlen, unabhängig davon, ob Sie die heruntergeladenen Earcons aus dem Design Hub verwenden oder eigene erstellen.

  • Sie sollten sich also genau überlegen, wie Sie Earcons einsetzen.

  • Es gibt ein paar Dinge, die Sie beachten sollten. Versuchen Sie beispielsweise, Earcons nicht zu oft zu verwenden.

  • Spielen Sie nicht immer wieder dieselben Töne ab, da dies zu einer sogenannten Ermüdung des Gehörs führen kann.

  • Denken Sie daran, dass Ihre Earcons intuitiv sein sollten. Verwenden Sie bei der Implementierung auch vertraute Tonhöhen und Rhythmen.

  • Vermeiden Sie außerdem zu komplexe Earcons, die erklärt werden müssen. Diese eignen sich möglicherweise besser als Sprachbenachrichtigung.

  • Und denken Sie immer daran, dass etwas, das im Studio oder auf Ihrem System, auf Ihren Computerlautsprechern, subtil klingen mag, nach wiederholter Wiedergabe für Ihre Nutzer irritierend sein kann.

  • Versuchen Sie also, Ihre Nutzenden nicht mit Tönen zu überfordern, die sie verwirren oder dazu führen könnten, dass sie wichtige Warnungen ignorieren.

  • Okay.

  • Wir können jetzt Audiohinweise und Feedback geben, entweder durch natürliche Sprache oder mithilfe von Earcons.

  • Aber wie sieht es mit der Eingabe aus?

  • Natürlich gibt es auch ein Touchpad oder spezielle Tasten am Gerät, mit denen Sie Tipp- und Wischinteraktionen erstellen können.

  • Wenn Sie aber auch diese konversationelle Benutzeroberfläche zulassen möchten, können Sie die GenAI Speech Recognition API von ML Kit verwenden.

  • Die Funktion befindet sich derzeit in der Alphaphase und ist auf ausgewählten Geräten verfügbar. Sie funktioniert in zwei verschiedenen Modi.

  • Es gibt den Basismodus, der die herkömmliche Spracherkennung auf dem Gerät verwendet und auf Android-Geräten mit API-Level 31 und höher verfügbar ist.

  • Und dann gibt es noch den erweiterten Modus, der Gemini Nano verwendet. Dieser wird auf Pixel 10-Geräten und höher angeboten und bietet eine bessere Qualität und eine bessere Sprachabdeckung.

  • SpeechRecognizer folgt also einem sehr ähnlichen Codemuster wie SpeechSynthesizer.

  • Sie erstellen Ihren Client, prüfen, ob das Modell verfügbar ist, laden es bei Bedarf herunter, erstellen die Anfrage und geben Ihre Ressourcen frei.

  • Konzentrieren wir uns also auf die Hauptunterschiede beim Erstellen der Anfrage, beim Erfassen der Mikrofon-Audiodaten und beim Verarbeiten des Ergebnisses.

  • Auch hier müssen Sie die erforderlichen Abhängigkeiten hinzufügen.

    dependencies {
       // ML Kit Speech Recognition
       implementation("com.google.mlkit:genai-speech-recognition:1.0.0-alpha1")
    }
  • Hier benötigen Sie nur die Speech Recognition API.

  • Anschließend können Sie mit dem Erstellen Ihrer Anfrage beginnen.

  • Sie verwenden also eine Suspend-Funktion, wenn Sie Ihre Erkennungslogik definieren.

    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() }
    ...
  • Geben Sie dann die Optionen für die Spracherkennung an und legen Sie das Gebietsschema und die preferredMode fest.

  • Hier wählen Sie auch die audioSource aus und definieren die Anfrage selbst.

  • Dann starten Sie die Erkennung und erfassen eine Antwort.

    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()
           }
    ...
  • Bei SpeechRecognizer gibt es vier mögliche Antworten: „partial“, „final“, „error“ und „complete“.

  • „Partial“ bezieht sich auf den Echtzeitstream und wird für Hotwords oder Keywords verwendet.

  • „Final“ nimmt den endgültigen Audiostream nach der vollständigen Analyse und Aktualisierung, „Completed“ wird nach Abschluss der Sprache ausgegeben und „Error“ erfasst alle Fehler, die auftreten können.

  • Und so fügen wir alles zusammen.

    // 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")
       }
    }
  • Angenommen, ein Nutzer soll per Sprachbefehl die App anweisen können, bestimmte Inhalte für später zu speichern.

  • Sie könnten einen einzelnen expliziten Sprachbefehl verwenden, den Sie ihnen vorgegeben haben, oder Sie könnten ihnen erlauben, eine Reihe von Keywords und Wortgruppen zu verwenden.

  • Hier erlauben wir also die Verwendung von „speichern“, „Lesezeichen setzen“ oder „merken“.

  • Sobald SpeechRecognizer erkennt, dass der Nutzer eine dieser Wortgruppen verwendet hat, wird der Inhalt gespeichert und die Spracherkennung beendet. Es wird ein benutzerdefiniertes Earcon abgespielt und die GenAI-Text-zu-Sprache-Funktion von ML Kit verwendet, um dem Nutzer mitzuteilen, dass der Inhalt gespeichert wurde.

Fallstudie zum XR Language Teacher

  • Okay.

  • Wir haben viel besprochen.

  • Das mag etwas abstrakt klingen.

  • Wenn Sie wie ich sind, möchten Sie einige Beispiele aus der Praxis sehen.

  • Alice und ich möchten Ihnen genau das zeigen.

  • Sie führt Sie durch den XR Language Teacher in der Google AI Edge-Galerie App.

  • Alices Team hat es entwickelt und sie wird Ihnen genau zeigen, wie es funktioniert.

  • Jetzt sind Sie dran.

  • Vielen Dank, Amy.

  • Ja.

  • Ich beginne gern mit etwas, das viele von Ihnen kennen.

  • Ich lerne Japanisch und habe schon Stunden mit Sprachlern-Apps, Karteikarten, Streaks und allem Drum und Dran verbracht.

  • Dann lande ich in Tokio, gehe in eine Markthalle und stelle fest, dass fast nichts von dem, was ich sehe, dem Vokabular entspricht, das ich gelernt habe.

  • An einem Marktstand gibt es ein hölzernes Kugelfangspiel, das ich vom Sehen her kenne, aber ich weiß nicht, wie es auf Japanisch heißt.

  • In einem Schaufenster ist ein Dekorationsobjekt, das ich kaufen möchte, aber ich kenne den Namen nicht.

  • Das ist genau der Moment, in dem ich am meisten motiviert bin, ein Wort zu lernen.

  • Nicht auf meiner Couch, während ich auf eine Sprach-App starre.

  • In diesem Moment sehe ich mir das an, was ich lernen möchte.

  • Das ist es, was ich möchte.

  • Ich möchte meine Smart Glasses den ganzen Tag tragen und einfach herumlaufen.

  • Und wenn mir etwas auffällt, kann ich einfach laut danach fragen.

  • Und wenn ich dann abends ins Hotel zurückkehre, warten alle meine Fragen wie Vokabelkarten auf meinem Smartphone auf mich.

  • Und das ist das Ergebnis.

  • Sehen wir uns einen kurzen Clip an.

  • Wir nennen das den XR Language Teacher.

  • Hallo,

  • Worüber möchten Sie sich heute informieren?

  • In den Einstellungen für Lehrkräfte können Sie die Lernumgebung anpassen.

  • Wählen Sie beispielsweise die Sprache aus, die Sie lernen möchten, und die gewünschte Lehrer-Persona.

  • – Uh. – Ja.

  • Du kannst zwischen dem Brillen- und dem Smartphone-Modus wechseln.

  • Wie sagt man „diese Trauben“ auf Spanisch?

  • Hier steht, dass diese leckeren Trauben „uvas“ heißen.

  • Auf Spanisch können Sie sie mit „Estas uvas son deliciosas“ beschreiben.

  • Ich möchte sagen: grüne Trauben.

  • Ich werde mir das genauer ansehen.

  • Wenn Sie die grünen Trauben meinen, können Sie „Uvas verdes“ sagen.

  • Me gustan las uvas verdes," is a great way to tell someone you like them.

  • Wie sagt man das auf Französisch und wie kann ich drei davon bestellen?

  • Sehen wir uns das mal an.

  • Diese lecker aussehenden Gebäckstücke heißen „des madeleines“.

  • Wenn du auf Französisch drei bestellen möchtest, kannst du sagen: „Je voudrais trois madeleines, s'il vous plaît.“

  • Wie kann ich damit um eine Änderung bitten?

  • Mal sehen, was wir hier haben.

  • Wenn Sie nach Wechselgeld fragen möchten, können Sie „Otsuri wa arimasu ka?“ sagen.

  • Das bedeutet: „Haben Sie Wechselgeld?“

  • Sie können auch „Otsuri o onegaishimasu“ sagen, um höflich nach Wechselgeld zu fragen.

  • Alles, was du gelernt hast, wird automatisch als Vokabelkarte gespeichert.

  • La botella de agua.

  • Esta es una botella de agua.

  • Ja, und auf dem Tab „Lernen“ kannst du dein Gedächtnis testen und üben.

  • Okay.

  • Ja.

  • Alles, was Sie gerade gesehen haben, wurde also auf dem Gerät ausgeführt, ohne dass ein Netzwerk im Spiel war.

  • Ich reise mit einem internationalen Datentarif, der normalerweise gut funktioniert – bis ich mich in einer Markthalle, unter der Erde oder hinter dicken Betonwänden befinde.

  • Und genau dann, wenn du es am dringendsten brauchst, ist das Signal oft am schlechtesten.

  • Und ich würde sagen, dass eine Funktion, die nicht mehr funktioniert, wenn die Verbindung abbricht, kein echter Reisebegleiter ist. Deshalb haben wir den XR Language Teacher so konzipiert, dass er vollständig offline und auf dem Gerät funktioniert.

On-Device-Modelle verketten

  • Wie wurde das also erstellt?

  • Amy hat Ihnen bereits gezeigt, wie Sie einen projizierten Kamerarahmen von der Brille auf das Smartphone übertragen können.

  • Sie hat Ihnen gezeigt, wie Sie mit ML Kit und Spracherkennung Text in Sprache umwandeln und mit ML Kit Text-to-Speech Text wieder in Sprache umwandeln können.

  • Was wir noch brauchten, war ein Gehirn in der Mitte, das sich das Kamerabild und die Frage zusammen ansehen und herausfinden kann, wonach der Nutzer eigentlich fragt.

  • Dieses Gehirn ist Gemini Nano 4, das auf Gemma 4 basiert, dem Open-Source-Modell von Google, das lokal auf dem Smartphone ausgeführt wird.

  • Auf unterstützten Android-Flagship-Geräten können Sie also die Leistung optimieren, indem Sie die ML Kit APIs direkt verwenden.

  • Das Android-System übernimmt die Bereitstellung des Modells für Sie. Sie müssen also nichts in Ihre App einbinden.

  • Zusammengefasst: Das Mikrofon liefert die Eingabe für die Spracherkennung, die Kamerabilder und das Transkript werden an Gemini Nano 4 weitergeleitet, Gemini Nano 4 liefert die Eingabe für die Sprachsynthese und die Ausgabe erfolgt über den Lautsprecher.

  • Drei Modelle, die miteinander verknüpft sind und alle lokal auf deinem Smartphone ausgeführt werden.

  • Hier sind einige Gründe, warum Gemini Nano 4 besonders gut für dieses Problem geeignet ist.

  • Erstens: Es sieht und liest in einem Durchgang.

  • Über ML Kit verarbeitet Gemini Nano 4 Text und Bilder nativ. Du musst also nicht erst einen Detektor, dann einen Klassifikator und dann einen Übersetzer verwenden.

  • Es ist ein Modell, das den Kameraframe und die Frage zusammen betrachtet.

  • Der zweite Vorteil ist die umfangreiche Sprachunterstützung.

  • Gemini Nano 4 wurde mit über 140 Sprachen trainiert.

  • Und auch für einen Sprachlehrer reicht die Übersetzung allein nicht aus.

  • Sie möchten Redewendungen, formelle und informelle Sprache sowie Beispielsätze, die eine echte Person sagen würde.

  • Und schließlich ist es steuerbar genug, um eine Persönlichkeit zu haben.

  • Die Aussprache laut zu üben, kann einschüchternd sein.

  • Dieses Modell folgt einem System-Prompt gut, sodass Nutzer ihre eigene Lehrer-Persona auswählen können.

  • Und vielleicht könnte eine geduldige und ermutigende Stimme die Menschen eher dazu bringen, es zu versuchen.

  • Und noch etwas möchte ich am Design hervorheben.

  • Während die Unterhaltung im Brillenmodus stattfindet, erstellt die App im Hintergrund Vokabelkarten.

  • Das Wort, die Übersetzung, der Beispielsatz und das Foto aus dem Moment, in dem Sie die Frage gestellt haben.

  • Der Nutzer muss nichts manuell antippen.

  • Sie gehen und reden einfach.

  • Und später am Tag ist alles auf dem Smartphone verfügbar – genau das, was ich mir in Tokio gewünscht hätte.

  • „Audio First“ muss also nicht „nur Audio“ bedeuten.

  • Die Brille ist die richtige Oberfläche für Neugier und das Smartphone die richtige Oberfläche für die anschließende Reflexion.

  • Lass den Moment, in dem du die Hände frei hast, etwas hinterlassen.

  • Sehen wir uns nun einige praktische Tipps für die Entwicklung von Echtzeit-Anwendungen mit Gemini Nano 4 und ML Kit an, die wir beim Erstellen dieser Demo gelernt haben.

Priorisierung der Echtzeitlatenz mit Two-Pass-Design

  • Tipp 1: Priorisieren Sie die Echtzeitlatenz mit einem zweistufigen Design.

  • Wenn Sie eine Audiobrille tragen und fragen „Was ist das?“,

  • Jeder Moment der Stille ist spürbar.

  • Im Beispiel des XR-Sprachlehrers benötigen wir für jede Nutzerinteraktion zwei Dinge vom Modell: eine sofortige gesprochene Antwort und eine strukturierte Vokabelkarte, die in der App gespeichert werden kann.

  • Wenn wir das Modell in einem einzigen Durchlauf nach zwei Dingen gleichzeitig fragen, verzögert jedes Token, das für die Generierung der Hintergrunddaten verwendet wird, den ersten gesprochenen Satz.

  • Stattdessen trennen wir, was der Nutzer sofort benötigt, von dem, was die App einige Sekunden später benötigt.

  • Wir teilen jede Runde in zwei Durchgänge auf.

  • In Durchgang 1 streamen wir den Kameraframe und die Frage nur für die gesprochene Antwort und lösen die Text-zu-Sprache aus, sobald der erste Satz abgeschlossen ist.

  • Und da die TPU oder NPU die kurze Antwort lange vor dem Ende der Text-zu-Sprache generiert hat, haben wir ein Zeitfenster von mehreren Sekunden, in dem der Nutzer noch zuhört und der Chip bereits inaktiv ist.

  • Und da das Objekt in der Textantwort von Durchlauf 1 bereits identifiziert wurde, benötigt Durchlauf 2 nicht einmal die Bildeingabe.

  • Wir leiten die Antwort von Durchlauf 1 einfach an den inaktiven Chip weiter, um die strukturierte Karte zu extrahieren und zu speichern, noch bevor die Lehrkraft fertig gesprochen hat.

  • Sehen wir uns jetzt Durchgang 1 genauer an. Das bringt mich zum zweiten Tipp.

  • Verwenden Sie beim Streaming einfache XML-Tags.

  • Da Durchgang 1 direkt an Text-to-Speech gestreamt wird, sollten Sie keine großen JSON-Objekte verwenden, die das erste gesprochene Wort verzögern könnten.

  • Stattdessen benötigen Sie Nur-Text, der sofort mit einfachen Inline-Tags kommunizieren kann, wenn die App während des Streams eine Aktion ausführen muss.

  • Ich möchte Ihnen das Problem zeigen, das uns hierher geführt hat.

  • Erinnerst du dich an das hölzerne Kugelfangspiel am Marktstand, das ich erwähnt habe?

  • Auf Japanisch heißt es „Kendama“.

  • Das Modell hat es frühzeitig im Kamerabild erkannt und die richtige Antwort geliefert: „Das ist ein Kendama, ein Holzspielzeug.“

  • Wir haben diesen vollständigen Satz in die englische Text-zu-Sprache-Sitzung eingegeben und das ist das Ergebnis.

  • Das ist ein Ken-mi, ein Holzspielzeug.

  • Das klingt nicht ganz richtig.

  • Stattdessen sollte der Satz so klingen…

  • Das ist Kendama, ein Holzspielzeug.

  • Warum ist das passiert?

  • Kendama wird mit dem Hiragana-Zeichen „ken“ und dem Kanji-Zeichen „dama“ geschrieben.

  • Die englische Text-zu-Sprache-Engine verarbeitet das phonetische Hiragana wie „ken“ problemlos, scheitert aber völlig am Kanji-Teil und macht aus „dama“ etwas, das wie „me“ klingt.

  • Das passiert tatsächlich in vielen verschiedenen Sprachen auf unterschiedliche Weise, wenn Sie die Sprache nicht wechseln.

  • Auf Spanisch könnten Sie beispielsweise Folgendes hören:

  • Suh-BOH-luh…

  • Wenn du tatsächlich schwanger bist…

  • Cebolla.

  • Und auf Französisch hören Sie vielleicht…

  • PAIN oh chock-uh-lat…

  • Wenn Sie tatsächlich schwanger sind…

  • Pain au chocolat.

  • Ja, eine TTS-Sitzung ist in der Regel für ein einzelnes Gebietsschema konzipiert, aber ein Sprachlehrer muss in jedem einzelnen Satz den Code wechseln.

  • In Durchgang 1 bitten wir das Modell also, jedes Wort oder jede Wortgruppe in der Zielsprache während des Streamings in das Ziel-XML-Tag einzuschließen.

  • Wenn die Chunks ankommen, teilt unser Client sie auf und leitet jeden Span an die passende Text-zu-Sprache-Locale weiter, z. B. Japanisch, Spanisch, Französisch oder die Sprache, die Sie gerade lernen. Dann werden sie nahtlos wiedergegeben, wobei der Akzent mitten im Satz wechselt, damit Wörter wie „Kendama“ richtig ausgesprochen werden.

  • Das Modell weiß also bereits, welches Vokabular zu welcher Sprache gehört und welche Wörter zu welcher Sprache gehören. Wenn es Zielbereiche während des Streamings inline taggt, ist das viel sauberer, als das Ganze danach selbst nachzubearbeiten.

  • Und hier ist der Code für Durchlauf 1.

    // 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>"
  • Oben weisen wir das Modell mit dem Prompt an, in ein bis drei Sätzen innerhalb der Antwort-Tags zu antworten und dann jedes japanische Wort oder jeden Beispielsatz in die Ziel-Tags einzuschließen.

  • Anschließend übergeben wir die Kamera ImagePart und den Prompt TextPart an generateContentStream von ML Kit und teilen die Zielbereiche in Text-zu-Sprache auf, sobald die einzelnen Sätze eintreffen.

  • Damit sind wir beim zweiten Durchgang und unserem dritten Tipp.

  • Leichte XML-Tags eignen sich hervorragend für einen Live-Audiostream. Verwenden Sie jedoch strukturierte Ausgaben, die so konzipiert sind, dass beim Speichern von Daten in Ihrer App ein sauberes, typisiertes Objekt zurückgegeben wird.

  • Da der XR Language Teacher auf Konversation basiert, können Nutzer auch weiterführende Fragen stellen, bei denen kein neues Wort gelernt wird.

  • Und wenn ein Wort gelehrt wird, benötigt die Datenbank bereinigte Wörterbuchfelder, um die Vokabelkarten zu erstellen.

  • Anstatt also fehleranfällige reguläre Ausdrücke zu schreiben, um das Transkript aus Durchgang 1 zu parsen, lassen wir in Durchgang 2 im Hintergrund ein typsicheres Objekt extrahieren.

  • Um das Schema in Durchgang 2 zu erzwingen, verwenden wir die Structured Output API von ML Kit, die im Hintergrund eingeschränktes Decodieren nutzt.

    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?
    )
  • Sie annotieren einfach eine Datenklasse mit @Generable und jedes Feld mit @Guide, wie in unserem Beispiel für die Vokabelkartenklasse hier.

  • Durch das Verpacken des Nullable-Kartenfelds gibt Durchlauf 2 eine ausgefüllte Karte zurück, wenn ein neues Wort gelernt wurde, oder „null“ bei rein konversationellen Turns.

  • Und hier ist Durchlauf 2 im Code.

    // 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": "けん玉をください。", ...}
  • Oben nehmen wir den gestreamten Antworttext aus Durchlauf 1 (kein Bild erforderlich) und erstellen unseren Extraktions-Prompt.

  • Während der Nutzer noch die Text-zu-Sprache hört, übergeben wir den Prompt und unsere Wrapper-Klasse an generateTypedContentRequest und extrahieren eine saubere, tagfreie Vokabelkarte oder „null“, wenn kein Wort gelernt wurde.

  • Okay.

  • Wir haben viele Themen behandelt.

  • Vielleicht denken Sie jetzt: „Toll, noch etwas, das auf der Bühne gut aussieht, aber das ich nie verwenden werde.“

  • Diesen nicht!

  • XR Language Teacher ist zusammen mit anderen On-Device-KI-Funktionen in der Google AI Edge Gallery App enthalten.

  • Sie können die App noch heute herunterladen, auf Ihrem Smartphone installieren und auf alles zeigen, was Sie in einer anderen Sprache sagen möchten.

  • Und wenn Sie wissen möchten, wie es aufgebaut ist: Die gesamte Implementierung ist als Open Source auf GitHub verfügbar.

  • Und keine Sorge, wenn nicht alle Ihre Nutzer bereits Audio- oder Display-Brillen oder unterstützte Android-Flaggschiffgeräte haben.

  • In unserer Codebasis finden Sie auch Beispiele dafür, wie diese Funktion für Mobilgeräte angepasst werden kann.

  • Wenn Sie auch andere Plattformen erreichen möchten, zeigt Ihnen der Code der Google AI Edge-Galerie App, wie Sie diese Funktion mit der Google LiteRT-Laufzeit und den MediaPipe Tasks APIs erstellen.

  • Wenn Sie andere Open-Source-On-Device-Audio- oder Language Models verwenden möchten, können Sie sich auch im Code ansehen, wie das geht.

Anpassung an andere Anwendungsfälle mit KI-Agenten

  • Deshalb haben wir den XR Language Teacher als Open-Source-Projekt veröffentlicht, um Ihnen das Prototyping zu erleichtern.

  • Verweisen Sie mit dem Agenten Ihrer Wahl auf unser GitHub-Repository, um diese KI-basierte Live-On-Device-Funktion in etwas anderes zu verwandeln.

  • Es könnte ein Museumsführer sein, ein Kochassistent, der sich die Zutaten auf der Arbeitsplatte ansieht, eine Reparaturanleitung, ein Vorleser für Bedienungshilfen oder ein Tool für Inspektionen vor Ort.

  • Das Modell, die Laufzeit, die Open-Source-Implementierung und die Tools sind jetzt verfügbar, damit Sie sich auf die Markteinführung der Smart Glasses im Herbst vorbereiten können.

  • Wir sind gespannt, was ihr daraus macht.

  • Zum Abschluss haben wir heute beeindruckende Beispiele dafür gezeigt, was mit KI-Modellen möglich ist, die nahtlos auf dem Gerät ausgeführt werden.

  • Hier sind einige Möglichkeiten für den Einstieg.

  • Sammle zuerst die Ressourcen auf dem Bildschirm ein.

  • Laden Sie die AI Edge Gallery App herunter, um die Demo auszuprobieren, und sehen Sie sich den Referenzcode auf GitHub an.

  • Scannen Sie die QR-Codes, um auf das Android XR SDK, die ML Kit APIs und die Google AI Edge-Angebote zuzugreifen.

  • Zweitens: Probieren Sie die Funktionen direkt auf dem Smartphone aus.

  • Wir haben viele Dinge besprochen, die bereits mit einer Mobile-First-Umgebung erstellt werden können.

  • Drittens: Sehen Sie sich die mobile App an, die Sie bereits anbieten, und fragen Sie sich, welche Hauptfunktionen besser per Sprachbefehl funktionieren würden.

  • Wenn du deiner vorhandenen APK eine projizierte Aktivität hinzufügst, kannst du diese Funktion auch auf Datenbrillen anbieten.

  • Vielen Dank, dass Sie heute dabei waren.

  • Wir sind gespannt, welche innovativen Audio-First-Anwendungen ihr für Android und Smarte Brillen entwickeln werdet.

  • Vielen Dank.

Code

Code-Snippets

Kotlin-Snippet bei 02:39 in „Building for Android XR“
dependencies {
   implementation("androidx.xr.projected:projected:1.0.0-alpha13")
}
Kotlin-Snippet bei 02:45 in „Building for Android XR“
class ProjectedMainActivity : ComponentActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContent {
           // Glasses content
       }
   }
}
XML-Snippet bei 03:02 in „Building for Android XR“
<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>
Kotlin-Snippet bei 03:44 in „Building for Android XR“
// 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)
Kotlin-Snippet bei 05:05 in „Building for Android XR“
// 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)
       }
   }
}
Shell-Snippet bei 05:46 in „Building for Android XR“
android skills add projected-hardware
android skills add --all
Kotlin-Snippet bei 08:14 in „Designing audio-first experiences“
dependencies {
   // ML Kit GenAI TTS SDK
   implementation("com.google.mlkit:genai-text-to-speech:1.0.0-alpha1")
}
Kotlin-Snippet bei 08:26 in „Designing audio-first experiences“
// 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
}
Kotlin-Snippet bei 09:26 in „Designing audio-first experiences“
// 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}")
   }
}
Kotlin-Snippet bei 09:47 in „Designing audio-first experiences“
// 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()
}
Kotlin-Snippet bei 10:29 in „Designing audio-first experiences“
// 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()
)
Kotlin-Snippet bei 13:42 in „Designing audio-first experiences“
dependencies {
   // ML Kit Speech Recognition
   implementation("com.google.mlkit:genai-speech-recognition:1.0.0-alpha1")
}
Kotlin-Snippet bei 13:52 in „Designing audio-first experiences“
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() }
...
Kotlin-Snippet bei 14:12 in „Designing audio-first experiences“
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()
       }
...
Kotlin-Snippet bei 14:41 in „Designing audio-first experiences“
// 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")
   }
}
Kotlin-Snippet bei 26:27 in „Prioritizing real-time latency with two-pass design“
// 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>"
Kotlin-Snippet bei 27:33 in „Prioritizing Echtzeit-Latenz mit Two-Pass-Design“
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?
)
Kotlin-Snippet bei 28:00 in „Prioritizing Echtzeit-Latenz mit Two-Pass-Design“
// 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": "けん玉をください。", ...}