Android XR

Xây dựng trải nghiệm ưu tiên âm thanh cho kính thông minh bằng Gemini Nano 4

Loa

Giới thiệu

Giới thiệu về bài thuyết trình

Khám phá cách xây dựng trải nghiệm âm thanh mạnh mẽ, rảnh tay nhờ AI trên thiết bị. Chúng tôi sẽ đề cập đến mọi thứ bạn cần: thiết lập hoạt động của kính, ghi lại nguồn cấp dữ liệu từ micrô và camera, cũng như sử dụng giọng nói cùng với các tín hiệu âm thanh khác làm giao diện người dùng tối ưu. Thông qua một bản minh hoạ thực tế thú vị về ứng dụng XR Language Teacher nguồn mở được xây dựng trong ứng dụng Google AI Edge Gallery, bạn sẽ tìm hiểu cách điều phối Gemini Nano 4 (được xây dựng trên Gemma 4 và tối ưu hoá cho thiết bị Android) và Bộ công cụ học máy, đồng thời tận dụng thời gian chạy LiteRT để xây dựng trải nghiệm XR nhanh chóng, có thể nghe, nhìn và nói một cách tự nhiên.

Tóm tắt

  • Thư viện Jetpack Projected (alpha) kết nối điện thoại Android với kính thông minh. Việc thêm một hoạt động được chiếu vào ứng dụng hiện có sẽ chiếu nội dung lên màn hình hoặc kính chỉ có âm thanh.
  • Bộ công cụ học máy ra mắt thư viện lời nói trên thiết bị (phiên bản alpha) cho các hoạt động tương tác ưu tiên âm thanh trên một số thiết bị. Tính năng này cung cấp chức năng Chuyển văn bản sang lời nói với 8 giọng nói ở 21 ngôn ngữ và chức năng Nhận dạng lời nói với chế độ nâng cao của Gemini Nano trên Pixel 10.
  • Gemini Nano 4 xử lý các khung hình camera và câu hỏi được chép lời cùng một lúc. Tính năng này hỗ trợ trải nghiệm ngoại tuyến bằng cách liên kết các mô hình lời nói trên thiết bị ngay trên các điện thoại cao cấp được hỗ trợ.
  • Thiết kế hai lượt ưu tiên độ trễ âm thanh theo thời gian thực trên kính thông minh. Lượt 1 truyền trực tuyến văn bản nói bằng thẻ XML cùng dòng để chuyển đổi ngôn ngữ giữa câu, còn Lượt 2 trích xuất dữ liệu có cấu trúc trong nền.
  • Bản triển khai Giáo viên ngôn ngữ XR nguồn mở được cung cấp trong ứng dụng Google AI Edge Gallery. Nhà phát triển có thể điều chỉnh mã của ứng dụng này cho các trường hợp sử dụng trợ lý đa phương thức khác.

Tài nguyên

Gemini Notebook

Chủ đề

  • kính thông minh
  • android-xr
  • gemini-nano
  • on-device-ai
  • ml-kit
  • nhận dạng giọng nói
  • chuyển văn bản sang lời nói

Bản chép lời

Giới thiệu

  • Xin chào mọi người.

  • Rất vui được gặp mọi người hôm nay.

  • Cảm ơn bạn đã tham gia.

  • Tôi là Amy.

  • Hôm nay, tôi sẽ cùng đồng nghiệp của mình là Alice chia sẻ về cách bạn có thể xây dựng trải nghiệm ưu tiên âm thanh tuyệt vời cho kính thông minh bằng Gemini Nano 4.

  • Hãy cùng bắt đầu nào.

  • Vì vậy, khi nói về kính thông minh, chúng tôi đang đề cập đến những chiếc kính nhẹ, dễ mang theo và được thiết kế để đeo cả ngày.

  • Danh mục này sẽ bao gồm cả kính hiển thị và kính chỉ có âm thanh. Với những thiết bị này, bạn sẽ thấy một số phần cứng khác nhau tại đây.

  • Vì vậy, điều đầu tiên bạn sẽ nhận thấy là chúng sẽ có một bàn di chuột, có thể được dùng để người dùng nhập dữ liệu, chẳng hạn như nhấn và vuốt.

  • Thiết bị này cũng có loa, camera và micrô.

  • Và sau đó, chúng cũng có thể có màn hình hoặc không, hoặc có thể là một số kiểu kết hợp.

  • Hôm nay, chúng ta sẽ nói về trải nghiệm ưu tiên âm thanh và cách xây dựng trải nghiệm hấp dẫn, thông minh và tiện lợi.

  • Và một trong những điều thú vị khi chỉ có âm thanh là giọng nói sẽ trở thành giao diện người dùng của bạn.

  • AI trên thiết bị là lựa chọn tuyệt vời cho những nhà phát triển muốn mang đến trải nghiệm ngoại tuyến và khi di chuyển.

Xây dựng cho Android XR

  • Và có một tin vui.

  • Vì vậy, nếu đã phát triển ứng dụng cho Android, thì bạn cũng đang phát triển ứng dụng cho Android XR.

  • Các ứng dụng Android hiện có được thiết kế để mở rộng hiệu quả sang kính phát âm thanh, cung cấp thông báo bằng giọng nói và các âm thanh khác qua Bluetooth.

  • Nếu bạn muốn tiến xa hơn và tạo sự khác biệt cho ứng dụng của mình trên XR, thì có rất nhiều cách để làm việc đó.

  • Vì vậy, trong bài nói chuyện này, chúng ta sẽ nói về các mô hình trên thiết bị, nhưng đó không phải là danh sách đầy đủ.

  • Chúng tôi cũng có các mô hình đám mây.

  • Có những thứ như Gemini Live API.

  • Có những tác nhân hệ thống mà bạn có thể tích hợp, chẳng hạn như Gemini, và tất cả những tác nhân này có thể giúp bạn xây dựng trải nghiệm phù hợp nhất với mình.

  • Vậy làm cách nào để xây dựng trải nghiệm nghe, nhìn và nói một cách tự nhiên?

  • Hôm nay, chúng tôi sẽ hướng dẫn bạn xây dựng những trải nghiệm mạnh mẽ, bằng giọng nói này, được hỗ trợ bởi AI trên thiết bị.

  • Tôi sẽ hướng dẫn bạn những kiến thức cơ bản: cách xây dựng ứng dụng cho kính, cách thiết lập hoạt động, cách lấy các quyền cần thiết và sau đó là cách sử dụng Bộ công cụ học máy cho giao diện người dùng trò chuyện.

  • Sau đó, tôi sẽ nhường lời cho Alice. Cô ấy sẽ chia sẻ với các bạn một số ví dụ thực tế sử dụng Gemini Nano 4.

  • Hãy cùng tìm hiểu sâu hơn về mã.

  • Vì vậy, khi dùng kính, điện thoại sẽ đảm nhận phần việc nặng nhọc và nội dung trên kính sẽ được chiếu từ điện thoại sang kính.

  • Vì vậy, bạn cần có hai thứ để kết nối các thiết bị này khi thiết lập lần đầu.

  • Trước tiên, bạn cần có thư viện Jetpack Projected.

  • Bạn cần thêm phần phụ thuộc này, và đây là phần sẽ chứa các API để hỗ trợ trải nghiệm được chiếu đó.

    dependencies {
       implementation("androidx.xr.projected:projected:1.0.0-alpha13")
    }
  • Sau đó, bạn cũng cần tạo một hoạt động mới dành riêng cho kính.

    class ProjectedMainActivity : ComponentActivity() {
       override fun onCreate(savedInstanceState: Bundle?) {
           super.onCreate(savedInstanceState)
           setContent {
               // Glasses content
           }
       }
    }
  • Sau đó, bạn sẽ tận dụng thư viện Jetpack Projected cả bên trong ứng dụng hiện có và bên trong hoạt động mới này để hai thiết bị có thể giao tiếp mượt mà bất cứ khi nào cần.

  • Vì vậy, giống như bất kỳ hoạt động thông thường nào bạn đã thấy, bạn sẽ khai báo hoạt động này trong tệp kê khai.

    <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>
  • Điểm khác biệt chính ở đây là bạn cần sử dụng XR_PROJECTED làm requiredDisplayCategory.

  • Điều này giúp hệ thống biết rằng hoạt động này là dành cho kính mắt.

  • Sau đó, bạn cũng nên sử dụng danh mục ý định này tại đây, XR_PROJECTED_LAUNCHER.

  • Việc này giúp Gemini phát hiện ứng dụng của bạn để người dùng có thể khởi chạy ứng dụng bằng giọng nói khi đang di chuyển.

  • Được rồi.

  • Vì vậy, sau khi thiết lập hoạt động đó, như tôi đã đề cập, bạn sẽ sử dụng thư viện Projected đó để hỗ trợ trải nghiệm trong ứng dụng.

  • Thư viện này chứa tất cả công cụ bạn cần để xây dựng trải nghiệm đeo kính hoàn chỉnh.

  • Vì vậy, hãy cùng tìm hiểu một số API chính mà bạn sẽ sử dụng tại đây.

  • Vì vậy, phương thức đầu tiên tôi muốn đề cập là phương thức 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)
  • Đây là cách bạn sẽ liên tục theo dõi kết nối của kính.

  • Bạn sử dụng thông tin này cùng với vòng đời của thiết bị để biết thời điểm kính thực sự sẵn sàng, tức là không chỉ khi kính được ghép nối hoặc bật nguồn, mà còn khi kính đang được đeo và sẵn sàng để sử dụng.

  • Và đây là những gì, vậy nên, vâng.

  • Vì vậy, điều này sẽ cho bạn biết chính xác thời điểm bạn sẵn sàng khởi chạy hoạt động đó.

  • Vì vậy, để làm việc này, khi bạn đã sẵn sàng khởi chạy hoạt động, có một số tuỳ chọn cụ thể mà bạn cần đặt khi bắt đầu hoạt động.

  • Và bạn sẽ sử dụng createProjectedActivityOptions trên ProjectedContext.

  • Và một lần nữa, bạn sẽ sử dụng thông tin đó với ngữ cảnh về ý định ở đó.

  • Và một công cụ khác mà bạn nên sử dụng là ProjectedDeviceController.

  • Đây là cách bạn sẽ kiểm tra loại kính mà người dùng đang sử dụng.

  • Như tôi đã nói trước đó, kính thông minh bao gồm kính chỉ có màn hình và kính chỉ có âm thanh, bạn sẽ muốn xem các chức năng có chứa CAPABILITY_VISUAL_UI này hay không, và điều đó sẽ cho phép bạn điều chỉnh ứng dụng của mình một cách linh hoạt cho các thiết bị khác nhau và điều chỉnh trải nghiệm tối ưu cho thiết bị đó.

  • Vì vậy, khi xây dựng trải nghiệm khác biệt này, bạn có thể muốn truy cập vào phần cứng của thiết bị, chẳng hạn như tận dụng camera của kính.

  • Vì vậy, để thực hiện việc này từ một hoạt động được chiếu, bạn cần yêu cầu các quyền thích hợp bằng cách sử dụng 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)
           }
       }
    }
  • Và ngay cả khi ứng dụng của bạn đã có các quyền cần thiết cho điện thoại, bạn vẫn cần yêu cầu các quyền cụ thể cho phần cứng của kính.

  • Và bất cứ khi nào làm việc với quyền, hãy nhớ tuân theo các phương pháp hay nhất cho Android nói chung.

  • Khai báo các quyền cần thiết trong tệp kê khai, yêu cầu cấp quyền trong bối cảnh cần thiết và chỉ yêu cầu các quyền mà bạn thực sự cần để ứng dụng hoạt động.

  • Tất nhiên, chúng tôi đã ghi lại thông tin này trên trang web developer.android.com.

  • Nhưng chúng ta cũng có một kỹ năng hoàn toàn mới, đó là kỹ năng phần cứng dự kiến.

  • Bạn có thể sử dụng kỹ năng này với Android CLI trong Android Studio để thiết lập quy trình cấp quyền, đồng thời có thể sử dụng kỹ năng này và tất cả các kỹ năng khác của Android.

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

Thiết kế trải nghiệm ưu tiên âm thanh

  • Giờ đây, khi đã kết nối, nắm được các chức năng phần cứng và có các quyền phù hợp, hãy cùng tìm hiểu về các công cụ bạn cần để xây dựng trải nghiệm âm thanh đó.

  • Vì vậy, khi thiết kế trải nghiệm ưu tiên âm thanh, chúng tôi muốn đảm bảo rằng chúng tôi đang tạo ra trải nghiệm chất lượng cao cho người dùng.

  • Và điều này có thể bao gồm một vài hoạt động tương tác chính.

  • Đầu tiên là sử dụng giọng nói làm đầu ra, tức là cho phép giọng nói của bạn nói chuyện tự nhiên với người dùng.

  • Tiếp theo là sử dụng biểu tượng âm thanh để gửi tín hiệu âm thanh cụ thể đến người dùng.

  • Và sau đó cũng xử lý dữ liệu đầu vào từ người dùng, cho dù đó là thông qua bàn di chuột bằng cách nhấn và vuốt, hoặc nếu bạn muốn cho phép người dùng nói chuyện với ứng dụng của mình.

  • Vì vậy, điều đầu tiên chúng ta sẽ nói đến là việc sử dụng giọng nói làm đầu ra.

  • Vì vậy, tôi muốn giới thiệu thư viện Chuyển văn bản sang lời nói bằng AI tạo sinh mới của Bộ công cụ học máy.

  • Tính năng này hiện đang ở giai đoạn alpha và có trên một số thiết bị Pixel và Samsung.

  • Đây là điều chúng tôi rất hào hứng, đặc biệt là đối với những trải nghiệm ưu tiên âm thanh này.

  • Nếu từng làm việc với tính năng Chuyển văn bản sang lời nói, bạn có thể biết rằng một trong những điểm khó khăn là Giọng nói hệ thống không phải lúc nào cũng tự nhiên như bạn mong muốn.

  • Ngoài ra, việc kết nối với các mô hình đám mây để sử dụng các giọng nói khác có thể gây ra độ trễ hoặc phát sinh thêm chi phí.

  • Vì vậy, Bộ công cụ học máy mang đến cho bạn giọng nói tự nhiên hơn nhiều so với hệ thống TTS đó.

  • Bộ công cụ học máy cung cấp 8 giọng nói riêng biệt có các đặc điểm khác nhau để bạn lựa chọn cho phù hợp với ứng dụng của mình.

  • Ngoài ra, Bộ công cụ học máy còn hỗ trợ ngôn ngữ mạnh mẽ với 21 ngôn ngữ khác nhau mà bạn có thể chọn.

  • Ngoài ra, một trong những lợi ích lớn nhất là sau lần tải xuống đầu tiên, mô hình này sẽ hoạt động hoàn toàn trên thiết bị.

  • Vì vậy, mô hình này sẽ được tải xuống hệ thống và được chia sẻ giữa các ứng dụng, nên sẽ không làm tăng kích thước của tệp APK.

  • Và không chỉ hoạt động khi không có mạng, điều này đặc biệt quan trọng đối với người dùng kính khi di chuyển, họ có thể không có Wi-Fi hoặc có thể ở những khu vực có sóng kém, nhưng bạn cũng có thể thấy hiệu suất tốt hơn vì không phải thực hiện lệnh gọi khứ hồi đến máy chủ.

  • Được rồi.

  • Hãy cùng xem thông tin chi tiết về thư viện và cách chúng ta có thể xây dựng bằng thư viện này.

  • Vì vậy, trước tiên, tất nhiên, bạn chỉ cần thêm phần phụ thuộc.

    dependencies {
       // ML Kit GenAI TTS SDK
       implementation("com.google.mlkit:genai-text-to-speech:1.0.0-alpha1")
    }
  • Vì vậy, tất cả những gì bạn cần là SDK Chuyển văn bản sang lời nói của Bộ công cụ học máy.

  • Sau đó, bạn có thể bắt đầu viết mã.

  • Vì vậy, thư viện Bộ công cụ học máy AI tạo sinh bao gồm các API Trình tổng hợp lời nói.

    // 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
    }
  • Nhờ đó, bạn có thể dễ dàng triển khai giọng nói mà mình chọn.

  • Và bước đầu tiên bạn sẽ làm ở đây là khởi động ứng dụng bằng cách sử dụng lại trình tổng hợp giọng nói đó, đặt ngôn ngữ mà bạn chọn.

  • Tiếp theo, bạn sẽ kiểm tra trạng thái tải xuống và xem liệu có thể chọn giọng nói hay không.

  • Trong ví dụ này, chúng ta sẽ chọn giọng nói đầu tiên, nhưng tôi cũng muốn lưu ý rằng chúng ta đang sử dụng firstOrNull.

  • Như tôi đã đề cập, Bộ công cụ học máy yêu cầu điện thoại có khả năng cụ thể, vì vậy, bạn có thể dùng thiết bị Samsung hoặc Pixel.

  • Vì vậy, nếu bạn nhận được giá trị rỗng, thì có thể người dùng không sử dụng điện thoại tương thích.

  • Sau đó, bạn chỉ cần đảm bảo rằng bạn có phương án dự phòng, cho dù đó là quay lại tính năng Chuyển văn bản sang lời nói của hệ thống hay phát tin nhắn đã ghi âm hoặc nội dung tương tự.

  • Được rồi, sau đó bạn sẽ sử dụng tính năng tổng hợp giọng nói SpeechSynthesizerRequest để tạo yêu cầu, cung cấp văn bản đầu vào, sau đó đặt mức sử dụng và loại nội dung.

    // 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}")
       }
    }
  • Phát lời nói, theo dõi trạng thái và không – để bạn biết khi nào lời nói hoàn tất, sau đó thu thập mọi lỗi có thể xảy ra.

  • Sau đó, hãy đóng mọi tài nguyên khi cần.

    // 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()
    }
  • Và điều đó khá đơn giản, phải không?

  • Với các bước này, bạn thực sự có thể biến đổi âm thanh của ứng dụng từ một âm thanh như thế này…

  • Chào các nhà phát triển Android.

  • Rất vui được gặp mọi người hôm nay.

  • Chúng tôi hy vọng các bạn đều thích bài thuyết trình của chúng tôi về trải nghiệm ưu tiên âm thanh.

  • Thành một bản nhạc nghe như thế này.

  • Chào các nhà phát triển Android.

  • Rất vui được gặp mọi người hôm nay.

  • Chúng tôi hy vọng các bạn đều thích bài thuyết trình của chúng tôi về trải nghiệm ưu tiên âm thanh.

  • Tốt hơn nhiều, phải không?

  • Được rồi.

  • Thư viện Chuyển văn bản sang lời nói của Bộ công cụ học máy là một công cụ cực kỳ mạnh mẽ, nhưng bạn không chỉ bị giới hạn trong việc sử dụng những giọng nói này, mà còn có thể phát biểu tượng âm thanh bằng thư viện này.

    // 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()
    )
  • Vì vậy, biểu tượng âm thanh, như tên gọi của nó, về cơ bản là biểu tượng tương đương với âm thanh.

  • Điều này cho phép bạn phát âm thanh ngắn để giao tiếp với người dùng.

  • Và cũng giống như biểu tượng hình ảnh có biểu tượng âm thanh, người dùng sẽ cần biết bạn biết, đây là gì và chúng có ý nghĩa gì.

  • Vì vậy, ban đầu, bạn nên kết hợp các tín hiệu này với thông báo bằng giọng nói để người dùng hiểu rõ.

  • Chúng tôi cũng đã tạo một số âm thanh chuẩn hoá có sẵn trên Trung tâm thiết kế Android. Bạn có thể sử dụng những âm thanh đó, tải thẳng xuống từ đó và dùng cho những việc như mở ứng dụng, đóng ứng dụng, thông báo lỗi và tắt tiếng hoặc bật tiếng.

  • Ngoài ra, bạn cũng có thể tạo biểu tượng âm thanh tuỳ chỉnh của riêng mình.

  • Vì vậy, đối với biểu tượng âm thanh, có một số phương pháp hay nhất mà bạn nên làm theo, cho dù bạn đang sử dụng biểu tượng âm thanh tải xuống từ Trung tâm thiết kế hay tự tạo.

  • Vì vậy, bạn nên cân nhắc kỹ lưỡng về cách sử dụng biểu tượng âm thanh.

  • Vì vậy, bạn cần lưu ý một số điều, chẳng hạn như đừng lạm dụng biểu tượng âm thanh.

  • Đừng phát đi phát lại cùng một âm thanh, vì việc này có thể gây ra hiện tượng mỏi tai.

  • Và hãy nhớ rằng biểu tượng âm thanh của bạn phải thật tuyệt vời và trực quan, đồng thời sử dụng cao độ và nhịp điệu quen thuộc khi triển khai.

  • Sau đó, hãy cố gắng tránh những biểu tượng âm thanh quá phức tạp cần giải thích, những biểu tượng này có thể phù hợp hơn dưới dạng thông báo bằng giọng nói.

  • Và hãy luôn nhớ rằng những âm thanh có thể nghe rất nhỏ trong phòng thu hoặc trên hệ thống, trên loa máy tính của bạn, có thể trở nên khó chịu sau khi người dùng phát lại nhiều lần.

  • Vì vậy, hãy cố gắng hết sức để không làm người dùng bị quá tải với những âm thanh có thể khiến họ bối rối hoặc khiến họ bỏ qua các cảnh báo quan trọng.

  • Được rồi.

  • Vì vậy, giờ đây chúng tôi có thể cung cấp tín hiệu âm thanh và phản hồi, cho dù đó là thông qua lời nói tự nhiên hay sử dụng biểu tượng âm thanh.

  • Nhưng còn dữ liệu đầu vào thì sao?

  • Vì vậy, tất nhiên là có bàn di chuột hoặc các nút chuyên dụng cũng có trên thiết bị mà bạn có thể dùng để tạo các hoạt động tương tác nhấn và vuốt.

  • Nhưng bạn cũng có thể muốn cho phép giao diện người dùng trò chuyện này, vì vậy, bạn có thể sử dụng API Nhận dạng lời nói GenAI của Bộ công cụ học máy.

  • Hiện đang trong giai đoạn alpha và có trên một số thiết bị, nhưng về cơ bản, tính năng này chạy ở hai chế độ riêng biệt.

  • Chế độ cơ bản sử dụng tính năng nhận dạng lời nói truyền thống trên thiết bị và có trên các thiết bị Android có cấp độ API 31 trở lên.

  • Ngoài ra, còn có chế độ nâng cao sử dụng Gemini Nano, được cung cấp trên các thiết bị Pixel 10 trở lên. Chế độ này mang lại chất lượng tốt hơn và hỗ trợ nhiều ngôn ngữ hơn.

  • Vì vậy, SpeechRecognizer tuân theo một mẫu mã rất giống với SpeechSynthesizer.

  • Bạn tạo ứng dụng, kiểm tra xem mô hình có sẵn hay không, tải xuống nếu cần, tạo yêu cầu và giải phóng tài nguyên.

  • Vì vậy, hãy tập trung vào những điểm khác biệt chính khi bạn tạo yêu cầu, thu âm thanh micrô rồi xử lý kết quả.

  • Một lần nữa, bạn cần thêm các phần phụ thuộc cần thiết.

    dependencies {
       // ML Kit Speech Recognition
       implementation("com.google.mlkit:genai-speech-recognition:1.0.0-alpha1")
    }
  • Bạn chỉ cần API Nhận dạng lời nói.

  • Sau đó, bạn có thể bắt đầu tạo yêu cầu.

  • Vì vậy, bạn sẽ sử dụng hàm tạm ngưng khi xác định logic nhận dạng.

    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() }
    ...
  • Sau đó, hãy chỉ định các tuỳ chọn nhận dạng lời nói, đặt ngôn ngữ và preferredMode.

  • Và đây cũng là nơi bạn sẽ chọn audioSource và xác định chính yêu cầu đó.

  • Sau đó, bạn sẽ bắt đầu nhận dạng và thu thập phản hồi.

    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()
           }
    ...
  • Vì vậy, với SpeechRecognizer, bạn có 4 phản hồi tiềm năng: một phần, cuối cùng, lỗi và hoàn tất.

  • Kết quả một phần sẽ xem xét luồng dữ liệu theo thời gian thực, vì vậy, kết quả này được dùng cho những từ như từ khoá hoặc từ nóng.

  • Final là lấy luồng âm thanh cuối cùng sau khi phân tích và cập nhật đầy đủ, sau đó completed là sau khi lời nói hoàn tất, rồi error thu thập mọi lỗi có thể xảy ra.

  • Sau đây là cách chúng tôi kết hợp tất cả những điều đó.

    // 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")
       }
    }
  • Giả sử chúng ta muốn người dùng có thể sử dụng lệnh thoại để yêu cầu ứng dụng lưu một số loại nội dung để xem sau.

  • Chúng ta có thể sử dụng một lệnh thoại rõ ràng mà chúng ta đã yêu cầu họ sử dụng hoặc bạn có thể cho phép họ sử dụng một số từ khoá và cụm từ.

  • Vì vậy, ở đây chúng tôi cho phép họ sử dụng: lưu, đánh dấu hoặc ghi nhớ.

  • Ngay khi SpeechRecognizer phát hiện người dùng đã sử dụng một trong những cụm từ đó, ứng dụng sẽ dừng lưu nội dung, sau đó dừng nhận dạng lời nói, phát một âm báo tuỳ chỉnh và sử dụng tính năng Chuyển văn bản sang lời nói bằng AI tạo sinh của Bộ công cụ học máy để thông báo cho người dùng rằng nội dung đã được lưu.

Nghiên cứu điển hình về XR Language Teacher

  • Được rồi.

  • Vậy là chúng ta đã tìm hiểu rất nhiều nội dung.

  • Điều này có thể hơi trừu tượng.

  • Nếu giống tôi, bạn sẽ muốn xem một số ví dụ thực tế.

  • Alice và tôi muốn hướng dẫn bạn chính xác về điều đó.

  • Cô ấy sẽ hướng dẫn bạn sử dụng XR Language Teacher trong ứng dụng Google AI Edge Gallery.

  • Nhóm của Alice đã xây dựng tính năng này và cô ấy sẽ cho bạn thấy chính xác cách hoạt động của tính năng này.

  • Xin mời bạn.

  • Cảm ơn Amy.

  • Vâng.

  • Vì vậy, tôi muốn bắt đầu bằng một thứ mà nhiều người trong số các bạn sẽ nhận ra.

  • Tôi đang học tiếng Nhật và đã dành hàng giờ cho các ứng dụng học ngôn ngữ, thẻ ghi nhớ, chuỗi ngày học liên tục, v.v.

  • Sau đó, tôi đến Tokyo, bước vào một khu chợ và nhận ra hầu như không có gì trước mặt tôi là từ vựng thực tế mà tôi đã học.

  • Có một món đồ chơi bằng gỗ gồm cốc và bóng trên một quầy hàng ở chợ mà tôi nhận ra ngay, nhưng tôi không biết nói bằng tiếng Nhật.

  • Có một món đồ trang trí trong cửa sổ cửa hàng mà tôi muốn mua nhưng không biết tên.

  • Đó chính là thời điểm tôi có động lực học từ vựng nhất.

  • Chứ không phải ngồi trên ghế sofa và nhìn vào một ứng dụng ngôn ngữ.

  • Đó là khoảnh khắc tôi nhìn vào thứ mà tôi muốn học.

  • Vậy nên, đây là điều tôi muốn.

  • Tôi muốn đeo kính thông minh cả ngày, chỉ cần đi dạo xung quanh.

  • Và bất cứ khi nào có điều gì thu hút sự chú ý của tôi, tôi chỉ cần hỏi về điều đó.

  • Sau đó, vào ban đêm, khi tôi trở về khách sạn, mọi thứ tôi hỏi sẽ chờ tôi như những thẻ từ vựng trên điện thoại.

  • Và đây là những gì chúng tôi đã xây dựng.

  • Hãy cùng xem một đoạn video ngắn.

  • Vì vậy, chúng tôi gọi đây là XR Language Teacher.

  • Chào bạn!

  • Hôm nay bạn muốn tìm hiểu về chủ đề gì?

  • Bạn cũng có thể tuỳ chỉnh trải nghiệm học tập trong phần cài đặt dành cho giáo viên.

  • Ví dụ: chọn ngôn ngữ bạn muốn học và chọn giáo viên phù hợp với bạn.

  • Ồ, đúng vậy.

  • Bạn cũng có thể chuyển đổi giữa chế độ kính và chế độ điện thoại.

  • Nói "những quả nho này" bằng tiếng Tây Ban Nha như thế nào?

  • Nhìn vào đây, những quả nho ngon lành này được gọi là uvas.

  • Trong tiếng Tây Ban Nha, bạn có thể nói "Estas uvas son deliciosas" để mô tả chúng.

  • Cụ thể, tôi muốn nói đến nho xanh.

  • Hãy để tôi xem xét việc này.

  • Để chỉ định nho xanh, bạn có thể nói "Uvas verdes".

  • Me gustan las uvas verdes" là một cách hay để nói với ai đó rằng bạn thích họ.

  • Nói câu này bằng tiếng Pháp như thế nào và làm sao để gọi 3 phần?

  • Hãy cùng tìm hiểu.

  • Những chiếc bánh ngọt trông ngon lành đó được gọi là des madeleines.

  • Trong tiếng Pháp, để yêu cầu ba chiếc, bạn có thể nói: "Je voudrais trois madeleines, s'il vous plaît".

  • Làm cách nào để yêu cầu thay đổi?

  • Hãy xem chúng ta có gì ở đây.

  • Để hỏi về tiền thừa, bạn có thể nói "Otsuri wa arimasu ka?"

  • Nghĩa là "Bạn có tiền lẻ không?"

  • Bạn cũng có thể nói "Otsuri o onegaishimasu" để yêu cầu trả lại tiền thừa một cách lịch sự.

  • Sau đó, mọi nội dung bạn học được sẽ tự động lưu dưới dạng thẻ từ vựng.

  • La botella de agua.

  • Esta es una botella de agua.

  • Đúng vậy, bạn có thể kiểm tra trí nhớ và luyện tập trong thẻ học tập.

  • Được rồi.

  • Vâng.

  • Vì vậy, mọi thứ bạn vừa thấy đều chạy trên thiết bị, không có mạng trong vòng lặp.

  • Tôi đi du lịch với một gói dữ liệu quốc tế, thường thì gói này hoạt động tốt cho đến khi tôi vào bên trong một khu chợ, có thể là dưới lòng đất hoặc sau lớp bê tông dày.

  • Thời điểm bạn cần tính năng này nhất thường là lúc tín hiệu kém nhất.

  • Và tôi muốn nói rằng, một trải nghiệm sẽ biến mất khi mất sóng thì không phải là một người bạn đồng hành thực sự. Đó là lý do chúng tôi thiết kế XR Language Teacher hoạt động hoàn toàn không cần mạng và ngay trên thiết bị.

Liên kết các mô hình trên thiết bị

  • Vậy, chúng tôi đã xây dựng tính năng này như thế nào?

  • Về mặt kiến trúc, Amy đã hướng dẫn bạn cách sử dụng khung hình camera được chiếu từ kính vào điện thoại.

  • Cô ấy đã hướng dẫn bạn cách sử dụng Bộ công cụ học máy, tính năng nhận dạng lời nói, để chuyển văn bản thành lời nói, cũng như tính năng Chuyển văn bản sang lời nói của Bộ công cụ học máy để chuyển văn bản trở lại thành lời nói.

  • Chúng tôi vẫn cần một bộ não ở giữa, một thứ có thể xem xét cả khung hình camera và câu hỏi, rồi tìm ra điều người dùng thực sự đang hỏi.

  • Bộ não đó là Gemini Nano 4, được xây dựng trên nền tảng Gemma 4, mô hình nguồn mở của Google chạy cục bộ trên điện thoại.

  • Vì vậy, trên các thiết bị Android cao cấp được hỗ trợ, bạn có thể đạt được hiệu suất tối ưu bằng cách sử dụng trực tiếp các API Bộ công cụ học máy.

  • Hệ thống Android sẽ đảm nhận việc phân phối mô hình cho bạn, vì vậy, bạn không cần phải đóng gói mô hình trong ứng dụng.

  • Tóm lại, micrô sẽ thu âm thanh để nhận dạng lời nói, còn khung hình camera và bản chép lời sẽ được đưa vào Gemini Nano 4. Sau đó, Gemini Nano 4 sẽ chuyển thông tin này sang tính năng tổng hợp lời nói và phát ra loa.

  • Ba mô hình, được liên kết với nhau, tất cả đều chạy cục bộ trên điện thoại của bạn.

  • Sau đây là một số lý do khiến Gemini Nano 4 đặc biệt phù hợp với vấn đề này.

  • Đầu tiên, mô hình này xem và đọc trong một lượt.

  • Thông qua Bộ công cụ học máy, Gemini Nano 4 xử lý văn bản và hình ảnh một cách tự nhiên, vì vậy, bạn không cần sử dụng trình phát hiện, sau đó là trình phân loại, rồi đến trình dịch.

  • Đây là một mô hình xem xét cả khung hình camera và câu hỏi.

  • Thứ hai là khả năng hỗ trợ nhiều ngôn ngữ.

  • Gemini Nano 4 được huấn luyện trên hơn 140 ngôn ngữ.

  • Ngoài ra, đối với gia sư ngôn ngữ, chỉ dịch thôi là chưa đủ.

  • Bạn muốn có thành ngữ, muốn có cách diễn đạt trang trọng và thông thường, đồng thời muốn có câu ví dụ mà một người thật sẽ nói.

  • Và cuối cùng, mô hình này có thể điều chỉnh được để có cá tính riêng.

  • Việc luyện phát âm thành tiếng có thể khiến bạn cảm thấy e ngại.

  • Mô hình này cũng tuân thủ tốt câu lệnh hệ thống, nên người dùng có thể chọn hình tượng giáo viên của riêng mình.

  • Và có lẽ một người kiên nhẫn và biết khích lệ có thể khiến mọi người sẵn sàng thử hơn.

  • Và đây là một điều nữa tôi muốn nhấn mạnh về thiết kế.

  • Trong khi cuộc trò chuyện diễn ra ở chế độ kính, ứng dụng sẽ âm thầm viết thẻ từ vựng ở chế độ nền.

  • Từ, bản dịch, câu ví dụ và ảnh tại thời điểm bạn đặt câu hỏi.

  • Người dùng không cần phải nhấn vào bất cứ nội dung nào theo cách thủ công.

  • Họ chỉ đang đi dạo và trò chuyện.

  • Sau đó, vào cuối ngày, mọi thứ đều có sẵn trên điện thoại của họ. Đó chính xác là điều tôi muốn khi ở Tokyo.

  • Vì vậy, ưu tiên âm thanh không nhất thiết là chỉ có âm thanh.

  • Kính là một nền tảng phù hợp để khơi gợi sự tò mò, còn điện thoại là một nền tảng phù hợp để suy ngẫm sau đó.

  • Hãy để khoảnh khắc rảnh tay lưu lại điều gì đó.

  • Bây giờ, hãy cùng tìm hiểu một số mẹo thực tế để xây dựng trải nghiệm theo thời gian thực bằng Gemini Nano 4 và Bộ công cụ học máy mà chúng tôi đã học được khi xây dựng bản minh hoạ này.

Ưu tiên độ trễ theo thời gian thực bằng thiết kế hai lượt

  • Mẹo 1: Ưu tiên độ trễ theo thời gian thực bằng thiết kế hai lượt.

  • Trên kính phát âm thanh, khi bạn hỏi "Đó là gì?"

  • Mọi khoảnh khắc im lặng đều dễ nhận thấy.

  • Trong ví dụ về XR Language Teacher, chúng ta cần hai điều cho mỗi lượt tương tác của người dùng từ mô hình: một câu trả lời bằng giọng nói ngay lập tức và một thẻ từ vựng có cấu trúc để lưu vào ứng dụng.

  • Nếu chúng ta yêu cầu mô hình thực hiện hai việc cùng một lúc, thì mỗi mã thông báo được dùng để tạo dữ liệu nền đó sẽ làm chậm câu nói đầu tiên.

  • Vì vậy, chúng ta sẽ tách biệt những gì người dùng cần ngay bây giờ với những gì ứng dụng cần sau đó vài giây.

  • Vì vậy, chúng tôi chia mỗi lượt thành hai lượt.

  • Trong Lượt 1, chúng tôi chỉ truyền trực tuyến khung hình camera và câu hỏi cho câu trả lời bằng giọng nói, kích hoạt tính năng chuyển văn bản sang lời nói ngay khi câu đầu tiên hoàn tất.

  • Và vì TPU hoặc NPU đó hoàn tất việc tạo câu trả lời ngắn đó rất lâu trước khi tính năng chuyển văn bản sang lời nói hoàn tất việc đọc to, nên chúng ta thực sự có một khoảng thời gian vài giây khi người dùng vẫn đang nghe và khối xử lý đã ở trạng thái rảnh.

  • Và vì Lượt 1 đã xác định được đối tượng trong câu trả lời bằng văn bản, nên Lượt 2 thậm chí không cần hình ảnh đầu vào.

  • Chúng ta chỉ cần chuyển câu trả lời của Lượt 1 trở lại khối rảnh đó để trích xuất và lưu thẻ có cấu trúc trước khi giáo viên nói xong.

  • Bây giờ, hãy phóng to Lượt 1, điều này sẽ đưa tôi đến mẹo thứ hai.

  • Sử dụng thẻ XML gọn nhẹ khi phát trực tuyến.

  • Vì Lượt 1 truyền trực tiếp đến tính năng chuyển văn bản sang lời nói, nên bạn không muốn đối tượng JSON nặng làm chậm từ đầu tiên được nói.

  • Thay vào đó, bạn muốn có văn bản thuần túy có thể đọc ngay lập tức bằng các thẻ nội tuyến nhẹ, bất cứ khi nào ứng dụng cần thực hiện hành động giữa chừng.

  • Vậy nên, hãy để tôi cho bạn thấy vấn đề khiến chúng ta phải làm vậy.

  • Bạn có nhớ món đồ chơi tung bóng vào cốc bằng gỗ trên quầy hàng ở chợ mà tôi đã đề cập không?

  • Trong tiếng Nhật, trò chơi này được gọi là "kendama".

  • Ngay từ đầu, mô hình đã nhận ra món đồ chơi này trong khung hình của camera và đưa ra câu trả lời chính xác: "Đó là kendama, một món đồ chơi bằng gỗ".

  • Vì vậy, chúng tôi đã đưa câu hoàn chỉnh đó vào phiên chuyển văn bản sang lời nói bằng tiếng Anh và đây là kết quả.

  • Đó là ken-mi, một món đồ chơi bằng gỗ.

  • Có vẻ không chính xác.

  • Thay vào đó, câu đó nên được viết là…

  • Đó là kendama, một món đồ chơi bằng gỗ.

  • Vậy tại sao điều đó lại xảy ra?

  • Kendama được viết bằng chữ hiragana, "ken" và chữ kanji, "dama".

  • Và công cụ chuyển văn bản sang lời nói tiếng Anh xử lý hiragana phiên âm như "ken" rất tốt, nhưng hoàn toàn thất bại khi gặp phần kanji và biến dama thành một từ nghe giống như me.

  • Và điều này thực sự xảy ra trên nhiều ngôn ngữ khác nhau theo nhiều cách nếu bạn không chuyển đổi ngôn ngữ.

  • Ví dụ: bằng tiếng Tây Ban Nha, bạn có thể nghe thấy…

  • Suh-BOH-luh…

  • Khi bạn thực sự mong đợi…

  • Cebolla.

  • Tương tự, bằng tiếng Pháp, bạn có thể nghe thấy…

  • PAIN oh chock-uh-lat…

  • Khi bạn thực sự mong đợi…

  • Bánh sừng bò nhân sô cô la.

  • Đúng vậy, một phiên TTS thường được thiết kế cho một ngôn ngữ tại một thời điểm, nhưng giáo viên ngôn ngữ cần chuyển đổi mã trong mỗi câu.

  • Vì vậy, trong lượt 1, chúng tôi yêu cầu mô hình gói mọi từ hoặc cụm từ trong ngôn ngữ đích vào thẻ XML đích khi truyền trực tuyến.

  • Khi các đoạn văn bản đến, ứng dụng của chúng tôi sẽ chia nhỏ theo mục tiêu, định tuyến từng đoạn đến ngôn ngữ Chuyển văn bản sang lời nói phù hợp, có thể là tiếng Nhật, tiếng Tây Ban Nha, tiếng Pháp hoặc bất kỳ ngôn ngữ nào bạn đang học, sau đó phát lại một cách liền mạch, chuyển đổi giọng điệu giữa câu, để những từ như kendama được phát âm chính xác.

  • Vì vậy, điểm mấu chốt ở đây là mô hình đã biết từ vựng nào thuộc ngôn ngữ nào, từ nào thuộc ngôn ngữ nào và việc gắn thẻ các đoạn mục tiêu cùng dòng trong khi truyền trực tuyến sẽ gọn gàng hơn nhiều so với việc tự xử lý hậu kỳ toàn bộ nội dung này sau đó.

  • Và đây là lượt 1 trong mã.

    // 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>"
  • Ở trên cùng, chúng ta dùng câu lệnh để hướng dẫn mô hình trả lời trong một đến ba câu bên trong thẻ trả lời, sau đó gói mọi từ hoặc câu ví dụ tiếng Nhật bên trong thẻ mục tiêu.

  • Sau đó, chúng ta truyền ImagePart của camera và câu lệnh TextPart vào generateContentStream của Bộ công cụ học máy, chia các dải mục tiêu thành Chuyển văn bản sang lời nói khi mỗi câu xuất hiện.

  • Giờ chúng ta sẽ chuyển sang lần xem thứ hai và mẹo thứ ba.

  • Mặc dù việc sử dụng thẻ XML gọn nhẹ là rất phù hợp cho luồng âm thanh trực tiếp, nhưng hãy sử dụng kết quả có cấu trúc (được xây dựng để trả về một đối tượng rõ ràng, có kiểu dữ liệu) khi lưu dữ liệu vào ứng dụng.

  • Vì XR Language Teacher là một công cụ trò chuyện, nên người dùng cũng có thể đặt câu hỏi tiếp theo mà không cần học từ mới.

  • Khi một từ được dạy, cơ sở dữ liệu cần có các trường từ điển rõ ràng để tạo thẻ từ vựng.

  • Vì vậy, thay vì viết các biểu thức chính quy dễ hỏng để phân tích cú pháp bản chép lời của lượt đầu tiên, chúng ta sẽ để lượt thứ hai trích xuất đối tượng an toàn về kiểu trong nền.

  • Để thực thi giản đồ trong lượt 2, chúng tôi sử dụng API đầu ra có cấu trúc của Bộ công cụ học máy. API này sử dụng tính năng giải mã có giới hạn.

    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?
    )
  • Bạn chỉ cần chú thích một lớp dữ liệu bằng @Generable và mỗi trường bằng @Guide, giống như ví dụ về lớp thẻ từ vựng của chúng tôi tại đây.

  • Bằng cách gói trường thẻ có thể rỗng, lượt 2 sẽ trả về một thẻ được điền sẵn khi một từ mới được dạy hoặc trả về giá trị rỗng trong các lượt trò chuyện thuần tuý.

  • Và đây là lượt 2 trong mã.

    // 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": "けん玉をください。", ...}
  • Ở trên cùng, chúng ta lấy văn bản trả lời được truyền trực tuyến của lượt 1, không cần hình ảnh và tạo câu lệnh trích xuất.

  • Sau đó, trong khi người dùng vẫn đang nghe tính năng Chuyển văn bản sang lời nói, chúng ta sẽ truyền câu lệnh và lớp trình bao bọc vào generateTypedContentRequest rồi trích xuất một thẻ từ vựng rõ ràng, không có thẻ hoặc giá trị rỗng nếu không có từ nào được dạy.

  • Được rồi.

  • Chúng ta đã tìm hiểu rất nhiều điều.

  • Lúc này, bạn có thể nghĩ rằng: "Tuyệt vời, lại một thứ trông rất đẹp trên sân khấu mà tôi sẽ không bao giờ chạm vào."

  • Không phải mục này!

  • XR Language Teacher được cung cấp trong ứng dụng Google AI Edge Gallery cùng với các trải nghiệm AI khác trên thiết bị.

  • Bạn có thể tải ứng dụng này xuống ngay hôm nay, cài đặt vào điện thoại của mình và hướng camera vào bất cứ thứ gì bạn muốn biết cách nói bằng một ngôn ngữ khác.

  • Nếu bạn muốn biết cách xây dựng, thì toàn bộ quá trình triển khai này đều là nguồn mở trên GitHub.

  • Đừng lo nếu không phải người dùng nào cũng có kính âm thanh, kính có màn hình hoặc thiết bị Android cao cấp được hỗ trợ.

  • Toàn bộ mã nguồn của chúng tôi cũng cho bạn thấy các ví dụ về cách điều chỉnh trải nghiệm này cho phù hợp với trải nghiệm chỉ dành cho thiết bị di động.

  • Nếu bạn cũng muốn tiếp cận các nền tảng khác, thì mã ứng dụng Google AI Edge Gallery cũng hướng dẫn bạn cách xây dựng tính năng này bằng thời gian chạy Google LiteRT và API MediaPipe Tasks.

  • Hoặc nếu muốn sử dụng các mô hình ngôn ngữ hoặc âm thanh nguồn mở khác trên thiết bị, bạn cũng có thể xem cách thực hiện trong mã.

Điều chỉnh cho phù hợp với các trường hợp sử dụng khác bằng tác nhân AI

  • Vì vậy, chúng tôi đã cung cấp XR Language Teacher dưới dạng nguồn mở để giúp bạn tạo nguyên mẫu dễ dàng hơn.

  • Hãy hướng tác nhân bạn chọn đến kho lưu trữ của chúng tôi trên GitHub để biến trải nghiệm trực tiếp, dựa trên AI và hoạt động trên thiết bị này thành một trải nghiệm khác.

  • Đó có thể là hướng dẫn viên bảo tàng, trợ lý nấu ăn quan sát quầy bếp của bạn, hướng dẫn sửa chữa, người đọc hỗ trợ tiếp cận, công cụ kiểm tra tại hiện trường.

  • Mô hình, thời gian chạy, cách triển khai nguồn mở và công cụ đều đã sẵn sàng để bạn có thể chuẩn bị sẵn sàng cho sự kiện ra mắt kính thông minh sắp tới vào mùa thu này.

  • Chúng tôi rất mong được thấy những thành quả mà bạn có thể tạo ra thông qua thư viện này.

  • Tóm lại, hôm nay, chúng tôi đã giới thiệu những ví dụ ấn tượng về những điều có thể làm được khi các mô hình AI chạy mượt mà trên thiết bị.

  • Sau đây là một số cách để bắt đầu.

  • Trước tiên, hãy lấy các tài nguyên trên màn hình.

  • Tải ứng dụng AI Edge Gallery xuống để dùng thử bản minh hoạ và xem mã tham chiếu trên GitHub.

  • Đồng thời, hãy quét mã QR để truy cập vào SDK Android XR, API Bộ công cụ học máy và các sản phẩm AI Edge của Google.

  • Thứ hai, hãy bắt đầu thử nghiệm ngay trên điện thoại của bạn.

  • Có rất nhiều điều chúng tôi đã đề cập có thể được xây dựng bằng trải nghiệm ưu tiên thiết bị di động.

  • Thứ ba, hãy xem ứng dụng di động mà bạn đang cung cấp và tự hỏi: tính năng cốt lõi nào sẽ hoạt động tốt hơn khi không cần dùng tay?

  • Bạn chỉ cần thêm một Hoạt động được chiếu vào tệp APK hiện có để đưa trải nghiệm đó lên mắt kính.

  • Cảm ơn các bạn đã tham gia cùng chúng tôi hôm nay.

  • Chúng tôi rất mong được thấy những trải nghiệm ưu tiên âm thanh tuyệt vời mà bạn xây dựng trên Android và cho kính thông minh.

  • Cảm ơn bạn.

Mã

Đoạn mã

Đoạn mã Kotlin ở phút 02:39 trong video "Xây dựng cho Android XR"
dependencies {
   implementation("androidx.xr.projected:projected:1.0.0-alpha13")
}
Đoạn mã Kotlin ở phút 02:45 trong video "Phát triển cho Android XR"
class ProjectedMainActivity : ComponentActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContent {
           // Glasses content
       }
   }
}
Đoạn mã XML ở phút 03:02 trong video "Xây dựng cho 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>
Đoạn mã Kotlin ở phút 03:44 trong video "Xây dựng cho 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)
Đoạn mã Kotlin ở phút 05:05 trong video "Xây dựng cho 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)
       }
   }
}
Đoạn mã shell ở 05:46 trong video "Xây dựng cho Android XR"
android skills add projected-hardware
android skills add --all
Đoạn mã Kotlin ở phút 08:14 trong video "Thiết kế trải nghiệm ưu tiên âm thanh"
dependencies {
   // ML Kit GenAI TTS SDK
   implementation("com.google.mlkit:genai-text-to-speech:1.0.0-alpha1")
}
Đoạn mã Kotlin ở phút 08:26 trong video "Designing audio-first experiences" (Thiết kế trải nghiệm ưu tiên âm thanh)
// 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
}
Đoạn mã Kotlin ở phút 09:26 trong video "Thiết kế trải nghiệm ưu tiên âm thanh"
// 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}")
   }
}
Đoạn mã Kotlin ở phút 09:47 trong video "Thiết kế trải nghiệm ưu tiên âm thanh"
// 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()
}
Đoạn mã Kotlin ở phút 10:29 trong video "Thiết kế trải nghiệm ưu tiên âm thanh"
// 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()
)
Đoạn mã Kotlin ở phút 13:42 trong video "Thiết kế trải nghiệm ưu tiên âm thanh"
dependencies {
   // ML Kit Speech Recognition
   implementation("com.google.mlkit:genai-speech-recognition:1.0.0-alpha1")
}
Đoạn mã Kotlin ở phút 13:52 trong video "Thiết kế trải nghiệm ưu tiên âm thanh"
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() }
...
Đoạn mã Kotlin ở phút 14:12 trong video "Thiết kế trải nghiệm ưu tiên âm thanh"
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()
       }
...
Đoạn mã Kotlin ở phút 14:41 trong video "Thiết kế trải nghiệm ưu tiên âm thanh"
// 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")
   }
}
Đoạn mã Kotlin ở phút 26:27 trong video "Ưu tiên độ trễ theo thời gian thực bằng thiết kế hai lượt"
// 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>"
Đoạn mã Kotlin ở phút 27:33 trong video "Ưu tiên độ trễ theo thời gian thực bằng thiết kế hai lượt"
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?
)
Đoạn mã Kotlin ở phút 28:00 trong video "Ưu tiên độ trễ theo thời gian thực bằng thiết kế hai lượt"
// 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": "けん玉をください。", ...}