Android XR

สร้างประสบการณ์การใช้งานที่เน้นเสียงเป็นหลักสำหรับแว่นตาอัจฉริยะด้วย Gemini Nano 4

ลำโพง

เกี่ยวกับ

เกี่ยวกับการเสวนา

ดูวิธีสร้างประสบการณ์การใช้งานเสียงแบบแฮนด์ฟรีที่มีประสิทธิภาพซึ่งขับเคลื่อนโดย AI ในอุปกรณ์ เราจะพูดถึงทุกสิ่งที่คุณต้องการ ไม่ว่าจะเป็นการตั้งค่ากิจกรรมของแว่นตา การบันทึกฟีดไมโครโฟนและฟีดกล้อง รวมถึงการใช้เสียงร่วมกับสัญญาณเสียงอื่นๆ เป็น UI ขั้นสุดยอดของคุณ ผ่านการสาธิตที่น่าตื่นเต้นในโลกแห่งความเป็นจริงของ XR Language Teacher แบบโอเพนซอร์สที่สร้างขึ้นในแอป Google AI Edge Gallery คุณจะได้เรียนรู้วิธีจัดการ Gemini Nano 4 (สร้างขึ้นบน Gemma 4 และเพิ่มประสิทธิภาพสำหรับอุปกรณ์ Android) และ ML Kit รวมถึงใช้ประโยชน์จากรันไทม์ LiteRT เพื่อสร้างประสบการณ์ XR ที่รวดเร็วซึ่งสามารถฟัง เห็น และพูดได้อย่างเป็นธรรมชาติ

สรุป

  • ไลบรารี Jetpack Projected (อัลฟ่า) เชื่อมต่อโทรศัพท์ Android กับแว่นตาอัจฉริยะ การเพิ่มกิจกรรมที่คาดการณ์ไว้ลงในแอปที่มีอยู่จะฉายเนื้อหาไปยังแว่นตาที่แสดงผลหรือแว่นตาแบบเสียงเท่านั้น
  • ML Kit เปิดตัวไลบรารีเสียงพูดในอุปกรณ์เวอร์ชันอัลฟ่าสำหรับการโต้ตอบที่เน้นเสียงเป็นหลักในอุปกรณ์บางรุ่น โดยมีฟีเจอร์การอ่านออกเสียงข้อความด้วยเสียง 8 เสียงใน 21 ภาษา และการจดจำเสียงพูดด้วยโหมดขั้นสูงของ Gemini Nano ใน Pixel 10
  • Gemini Nano 4 ประมวลผลเฟรมกล้องและคำถามที่ถอดเสียงพร้อมกันในครั้งเดียว โดยจะขับเคลื่อนประสบการณ์การใช้งานแบบออฟไลน์ด้วยการเชื่อมโยงโมเดลเสียงพูดในอุปกรณ์ไว้ในเครื่องบนโทรศัพท์รุ่นเรือธงที่รองรับ
  • การออกแบบการประมวลผล 2 รอบจะให้ความสำคัญกับเวลาในการตอบสนองของเสียงแบบเรียลไทม์บนแว่นตาอัจฉริยะ Pass 1 จะสตรีมข้อความที่พูดโดยใช้แท็ก XML ในบรรทัดสำหรับการเปลี่ยนภาษาที่ใช้กลางประโยค ส่วน Pass 2 จะดึงข้อมูลที่มีโครงสร้างในเบื้องหลัง
  • การติดตั้งใช้งาน XR Language Teacher แบบโอเพนซอร์สจะมาพร้อมกับแอป Google AI Edge Gallery นักพัฒนาแอปสามารถปรับโค้ดของแอปนี้เพื่อใช้กับกรณีการใช้งานอื่นๆ ของผู้ช่วยแบบหลายโมดัลได้

แหล่งข้อมูล

Gemini Notebook

หัวข้อ

  • intelligent-eyewear
  • android-xr
  • gemini-nano
  • on-device-ai
  • ml-kit
  • speech-recognition
  • การอ่านออกเสียงข้อความ

ข้อความถอดเสียง

บทนำ

  • สวัสดีและขอต้อนรับทุกคน

  • ดีใจมากที่ได้เจอทุกคนในวันนี้

  • ขอบคุณที่มา

  • ฉันชื่อ Amy

  • วันนี้ Alice เพื่อนร่วมงานของฉันจะมาร่วมพูดคุยเกี่ยวกับวิธีสร้างประสบการณ์ที่เน้นเสียงเป็นหลักอันน่าทึ่งสำหรับแว่นตาอัจฉริยะโดยใช้ Gemini Nano 4

  • มาเริ่มกันเลย

  • ดังนั้นเมื่อพูดถึงแว่นตาอัจฉริยะ เราหมายถึงแว่นตาแบบพกพาที่มีน้ำหนักเบาซึ่งออกแบบมาให้สวมใส่ได้ตลอดทั้งวัน

  • หมวดหมู่นี้จะรวมทั้งแว่นตาที่มีจอแสดงผลและแว่นตาที่มีเฉพาะเสียง และคุณจะเห็นฮาร์ดแวร์ที่หลากหลายในอุปกรณ์เหล่านี้

  • สิ่งแรกที่คุณจะสังเกตเห็นคือแว่นตาจะมีทัชแพดซึ่งใช้สำหรับการป้อนข้อมูลของผู้ใช้ เช่น การแตะและการปัด

  • นอกจากนี้ยังมีลำโพง กล้อง และไมโครโฟนด้วย

  • และอาจมีจอแสดงผลหรือไม่มีก็ได้ หรืออาจมีทั้ง 2 แบบผสมกัน

  • วันนี้เราจะพูดถึงการเน้นเสียงเป็นหลักและวิธีสร้างประสบการณ์การใช้งานที่น่าสนใจ ชาญฉลาด และใช้งานได้ทุกที่ทุกเวลา

  • และสิ่งหนึ่งที่น่าสนใจเกี่ยวกับเสียงเท่านั้นก็คือเสียงจะกลายเป็น UI ของคุณที่นี่

  • และ AI ในอุปกรณ์เหมาะอย่างยิ่งสำหรับนักพัฒนาแอปที่ต้องการประสบการณ์การใช้งานแบบออฟไลน์และขณะเดินทาง

การสร้างสำหรับ Android XR

  • และเรามีข่าวดีมาบอก

  • ดังนั้นหากคุณสร้างแอปสำหรับ Android อยู่แล้ว ก็เท่ากับว่าคุณสร้างแอปสำหรับ Android XR อยู่แล้ว

  • แอป Android ที่มีอยู่ได้รับการออกแบบมาให้ทำงานร่วมกับแว่นตาที่มีระบบเสียงได้อย่างมีประสิทธิภาพ โดยจะส่งการอ่านออกเสียงการแจ้งเตือนและเสียงอื่นๆ ผ่านบลูทูธ

  • และหากต้องการก้าวไปอีกขั้นและสร้างความแตกต่างให้แอปสำหรับ XR ก็มีวิธีมากมายให้เลือก

  • ดังนั้นในการพูดคุยครั้งนี้ เราจะพูดถึงโมเดลในอุปกรณ์ แต่ก็ไม่ได้หมายความว่าจะมีเพียงเท่านี้

  • เรายังมีโมเดลระบบคลาวด์ด้วย

  • มีสิ่งต่างๆ เช่น Gemini Live API

  • คุณสามารถผสานรวมกับ Agent ของระบบ เช่น Gemini และทั้งหมดนี้จะช่วยคุณสร้างประสบการณ์การใช้งานที่ดีที่สุดสำหรับคุณได้

  • แล้วคุณจะสร้างประสบการณ์ที่ได้ยิน มองเห็น และพูดได้อย่างเป็นธรรมชาติได้อย่างไร

  • วันนี้เราจะแนะนำขั้นตอนการสร้างประสบการณ์การใช้งานแบบแฮนด์ฟรีที่มีประสิทธิภาพซึ่งทำงานด้วยระบบ AI ในอุปกรณ์

  • ผมจะแนะนำข้อมูลพื้นฐานให้คุณทราบ ตั้งแต่วิธีสร้างแอปสำหรับแว่นตา วิธีตั้งค่ากิจกรรม ขอสิทธิ์ที่จำเป็น และวิธีใช้ ML Kit สำหรับ UI แบบสนทนา

  • จากนั้นฉันจะส่งต่อให้ Alice ซึ่งจะพูดถึงตัวอย่างจริงที่ใช้ Gemini Nano 4

  • มาดูโค้ดกันเลย

  • ดังนั้นเมื่อใช้แว่นตา โทรศัพท์จะเป็นอุปกรณ์ที่ประมวลผลอย่างหนัก และเนื้อหาในแว่นตาจะฉายจากโทรศัพท์ไปยังแว่นตา

  • ดังนั้นคุณจึงต้องมี 2 สิ่งนี้เพื่อเชื่อมต่ออุปกรณ์เหล่านี้เมื่อตั้งค่าเป็นครั้งแรก

  • สิ่งแรกที่คุณต้องมีคือไลบรารี Jetpack Projected

  • คุณจะต้องเพิ่มทรัพยากร Dependency นี้ ซึ่งเป็นสิ่งที่จะมี API เพื่อขับเคลื่อนประสบการณ์การใช้งานที่คาดการณ์ไว้

    dependencies {
       implementation("androidx.xr.projected:projected:1.0.0-alpha13")
    }
  • และคุณจะต้องสร้างกิจกรรมใหม่สำหรับแว่นตาโดยเฉพาะด้วย

    class ProjectedMainActivity : ComponentActivity() {
       override fun onCreate(savedInstanceState: Bundle?) {
           super.onCreate(savedInstanceState)
           setContent {
               // Glasses content
           }
       }
    }
  • จากนั้นคุณจะใช้ประโยชน์จากไลบรารี Jetpack Projected ทั้งภายในแอปที่มีอยู่และภายในกิจกรรมใหม่นี้ เพื่อให้อุปกรณ์ทั้ง 2 เครื่องสื่อสารกันได้อย่างราบรื่นทุกเมื่อที่ต้องการ

  • ดังนั้น คุณจะต้องประกาศกิจกรรมนี้ในไฟล์ 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>
  • ความแตกต่างหลักคือคุณต้องใช้ XR_PROJECTED เป็น requiredDisplayCategory

  • เพื่อให้ระบบทราบว่ากิจกรรมนี้มีไว้สำหรับแว่นตา

  • และคุณยังต้องใช้หมวดหมู่ความตั้งใจนี้ด้วย ซึ่งก็คือ XR_PROJECTED_LAUNCHER

  • ซึ่งจะช่วยให้ Gemini ค้นพบแอปของคุณเพื่อให้ผู้ใช้เปิดแอปแบบแฮนด์ฟรีได้ทุกที่ทุกเวลา

  • โอเค

  • ดังนั้นเมื่อตั้งค่ากิจกรรมนั้นแล้ว อย่างที่ฉันได้กล่าวไป คุณจะใช้ไลบรารีที่คาดการณ์ไว้เพื่อขับเคลื่อนประสบการณ์การใช้งานแอปนั้น

  • ไลบรารีนี้มีเครื่องมือทั้งหมดที่จำเป็นสำหรับการสร้างประสบการณ์การใช้งานแว่นตาอย่างเต็มรูปแบบ

  • เรามาดู API หลักๆ บางส่วนที่คุณจะใช้ที่นี่กัน

  • วิธีแรกที่อยากพูดถึงคือวิธี isProjectedDeviceConnected

    // Continuously monitor whether the glasses are connected
    val isGlassesConnected by ProjectedContext.isProjectedDeviceConnected(
       context,
       scope.coroutineContext
    ).collectAsStateWithLifecycle(initialValue = false)
    
    // Launch the activity
    val options = ProjectedContext.createProjectedActivityOptions(context)
    val intent = Intent(context, ProjectedMainActivity::class.java)
    context.startActivity(intent, options.toBundle())
    
    // Check if the device is audio only or has a display
    val projectedDeviceController = ProjectedDeviceController.create(this@ProjectedMainActivity)
    isVisualUiSupported = projectedDeviceController.capabilities.contains(CAPABILITY_VISUAL_UI)
  • วิธีนี้จะช่วยให้คุณตรวจสอบการเชื่อมต่อแว่นตาได้อย่างต่อเนื่อง

  • คุณใช้ข้อมูลนี้ร่วมกับวงจรของอุปกรณ์เพื่อดูว่าแว่นตาพร้อมใช้งานจริงเมื่อใด ไม่ใช่แค่เมื่อจับคู่หรือเปิดเครื่อง แต่ยังรวมถึงเมื่อสวมใส่และพร้อมใช้งานด้วย

  • และนี่คือสิ่งที่เกิดขึ้น

  • ดังนั้นข้อมูลนี้จะช่วยให้คุณทราบได้อย่างแน่ชัดว่าเมื่อใดที่คุณพร้อมที่จะเปิดตัวกิจกรรมนั้น

  • ดังนั้นเมื่อพร้อมที่จะเปิดตัวกิจกรรม คุณจะต้องตั้งค่าตัวเลือกเฉพาะบางอย่างเมื่อเริ่มกิจกรรม

  • และสำหรับคำถามเหล่านั้น คุณจะใช้ createProjectedActivityOptions ใน ProjectedContext

  • และขอย้ำอีกครั้งว่าคุณจะใช้ข้อมูลนั้นกับบริบทของความตั้งใจนั้น

  • และอีกเครื่องมือหนึ่งที่คุณควรใช้คือProjectedDeviceController

  • วิธีตรวจสอบประเภทแว่นตาที่ผู้ใช้สวมอยู่มีดังนี้

  • ดังที่กล่าวไปก่อนหน้านี้ว่าแว่นตาอัจฉริยะมีทั้งแว่นตาแบบแสดงผลและแบบเสียงเท่านั้น คุณจะต้องดูว่าความสามารถของแว่นตามี CAPABILITY_VISUAL_UI นี้หรือไม่ ซึ่งจะช่วยให้คุณปรับแอปให้เข้ากับอุปกรณ์ต่างๆ ได้อย่างราบรื่นและปรับแต่งประสบการณ์การใช้งานที่เหมาะกับอุปกรณ์นั้นๆ

  • ดังนั้นในขณะที่สร้างประสบการณ์ที่แตกต่างนี้ คุณอาจต้องการเข้าถึงฮาร์ดแวร์ของอุปกรณ์ เช่น การใช้ประโยชน์จากกล้องของแว่นตา

  • ดังนั้น หากต้องการดำเนินการนี้จากกิจกรรมที่ฉายภาพ คุณจะต้องขอสิทธิ์ที่เหมาะสมโดยใช้ ProjectedActivityCompat

    // In a projected activity
    private fun requestProjectedPermissions() {
       lifecycleScope.launch {
           try {
               ProjectedActivityCompat.requestPermissions(
                   this@PermissionsFromProjectedActivity,
                   arrayOf(Manifest.permission.CAMERA),
                   // CAMERA_PERMISSION_REQUEST_CODE is a developer defined constant
                   requestCode = CAMERA_PERMISSION_REQUEST_CODE,
               )
           } catch (e: IllegalStateException) {
               // Thrown when the projected system service can't be bound.
               Log.e(TAG, "Request failed: projected service unavailable.", e)
           }
       }
    }
  • และแม้ว่าแอปของคุณจะมีสิทธิ์ที่จำเป็นสำหรับโทรศัพท์อยู่แล้ว คุณก็ยังต้องขอสิทธิ์เฉพาะสำหรับฮาร์ดแวร์ของแว่นตาด้วย

  • และเมื่อใดก็ตามที่ทำงานกับสิทธิ์ โปรดคำนึงถึงแนวทางปฏิบัติแนะนำสำหรับ Android โดยทั่วไป

  • ประกาศสิทธิ์ที่จำเป็นในไฟล์ Manifest ขอสิทธิ์ภายในบริบทที่จำเป็น และขอเฉพาะสิทธิ์ที่คุณต้องการจริงๆ เพื่อให้แอปทำงานได้

  • แน่นอนว่าเราได้จัดทำเอกสารเกี่ยวกับเรื่องนี้ไว้ในเว็บไซต์ developer.android.com

  • แต่เรายังมีทักษะใหม่ล่าสุดด้วย นั่นคือทักษะฮาร์ดแวร์ที่คาดการณ์ไว้

  • คุณสามารถใช้ทักษะนี้กับ Android CLI ใน Android Studio เพื่อตั้งค่าเวิร์กโฟลว์สิทธิ์ และคุณจะเข้าถึงทักษะนี้ รวมถึงทักษะอื่นๆ ทั้งหมดของ Android ได้ด้วย

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

การออกแบบประสบการณ์ที่เน้นเสียงเป็นหลัก

  • เมื่อเราเชื่อมต่อแล้ว ทราบถึงความสามารถของฮาร์ดแวร์ และมีสิทธิ์ที่เหมาะสมแล้ว เรามาพูดถึงเครื่องมือที่คุณมีเพื่อสร้างประสบการณ์ด้านเสียงกัน

  • ดังนั้นเมื่อออกแบบประสบการณ์การใช้งานที่เน้นเสียงเป็นหลัก เราจึงต้องการดูแลให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่มีคุณภาพสูง

  • ซึ่งอาจรวมถึงการโต้ตอบที่สำคัญบางอย่าง

  • อย่างแรกคือการใช้เสียงเป็นเอาต์พุต ซึ่งช่วยให้เสียงของคุณพูดกับผู้ใช้อย่างเป็นธรรมชาติ

  • ถัดไปคือการใช้เอียร์คอนเพื่อส่งสัญญาณเสียงที่เฉพาะเจาะจงไปยังผู้ใช้

  • และยังจัดการอินพุตจากผู้ใช้ด้วย ไม่ว่าจะเป็นการแตะและปัดผ่านทัชแพด หรือหากคุณต้องการอนุญาตให้ผู้ใช้พูดกับแอปของคุณ

  • สิ่งแรกที่เราจะพูดถึงคือการใช้เสียงเป็นเอาต์พุต

  • ดังนั้นสำหรับเรื่องนี้ ผมขอแนะนำไลบรารี GenAI Text To Speech ใหม่ของ ML Kit

  • ตอนนี้ฟีเจอร์นี้เผยแพร่แล้วในเวอร์ชันอัลฟ่าในอุปกรณ์ Pixel และ Samsung บางรุ่น

  • เราตื่นเต้นกับเรื่องนี้มาก โดยเฉพาะอย่างยิ่งสำหรับประสบการณ์การใช้งานแบบเน้นเสียงเป็นหลักเหล่านี้

  • หากเคยใช้การอ่านออกเสียงข้อความมาก่อน คุณอาจทราบว่าปัญหาหนึ่งคือเสียงของระบบอาจไม่เป็นธรรมชาติอย่างที่คุณต้องการเสมอไป

  • และการเชื่อมต่อกับโมเดลระบบคลาวด์สำหรับเสียงอื่นๆ อาจทำให้เกิดเวลาในการตอบสนองหรือค่าใช้จ่ายเพิ่มเติม

  • ดังนั้น ML Kit จึงให้เสียงพูดที่ฟังดูเป็นธรรมชาติมากกว่า TTS ของระบบ

  • และ ML Kit มีเสียงที่เป็นเอกลักษณ์ 8 เสียงให้คุณเลือก ซึ่งมีลักษณะแตกต่างกันเพื่อตอบสนองความต้องการของแอป

  • นอกจากนี้ ML Kit ยังรองรับภาษาต่างๆ มากมาย โดยมีภาษาให้เลือกถึง 21 ภาษา

  • และยิ่งไปกว่านั้น ข้อดีอย่างหนึ่งที่สำคัญคือหลังจากดาวน์โหลดครั้งแรกแล้ว ฟีเจอร์นี้จะทำงานบนอุปกรณ์โดยสมบูรณ์

  • ระบบจะดาวน์โหลดโมเดลนี้ลงในระบบและแชร์ในแอปต่างๆ ดังนั้นจึงไม่เพิ่มขนาด APK ของคุณ

  • และไม่เพียงแต่จะใช้งานแบบออฟไลน์ได้ ซึ่งสำคัญอย่างยิ่งสำหรับผู้ใช้แว่นตาที่กำลังเดินทาง ซึ่งอาจไม่มี Wi-Fi หรืออาจอยู่ในพื้นที่ที่มีสัญญาณไม่ดี แต่คุณก็มีแนวโน้มที่จะเห็นประสิทธิภาพที่ดีขึ้นด้วย เนื่องจากไม่ต้องทำการเรียกเซิร์ฟเวอร์ไปมา

  • โอเค

  • เรามาดูรายละเอียดของไลบรารีและวิธีสร้างด้วยไลบรารีกัน

  • ก่อนอื่น คุณจะต้องเพิ่มทรัพยากร Dependency

    dependencies {
       // ML Kit GenAI TTS SDK
       implementation("com.google.mlkit:genai-text-to-speech:1.0.0-alpha1")
    }
  • ดังนั้นในกรณีนี้ คุณต้องใช้เพียง SDK การอ่านออกเสียงข้อความของ ML Kit เท่านั้น

  • จากนั้นคุณก็เริ่มเขียนโค้ดได้เลย

  • ดังนั้น ไลบรารี GenAI ML Kit จึงมี Speech Synthesizer API ด้วย

    // 1. Initializing the SpeechSynthesizer client.
    val options = SpeechSynthesizerOptions.Builder(Locale.US).build()
    val client = SpeechSynthesis.getClient(options)
    
    // 2. Check and download the voice model.
    val voiceInfo = client.getSupportedVoices().firstOrNull()
    
    if (voiceInfo != null && client.checkStatus(voiceInfo) == FeatureStatus.DOWNLOADABLE) {
       client.download(voiceInfo).collect { status -> println("Download status: $status") }
    } else if (voiceInfo == null) {
       // Tip: Fallback to system TTS if ML Kit voice is unavailable
    }
  • ซึ่งจะช่วยให้คุณใช้เสียงที่เลือกได้อย่างตรงไปตรงมา

  • และขั้นตอนแรกที่คุณจะทำที่นี่คือการเริ่มต้นไคลเอ็นต์โดยใช้ Speech Synth- Synthesizer อีกครั้ง โดยตั้งค่าภาษาที่คุณเลือก

  • จากนั้นให้ตรวจสอบสถานะการดาวน์โหลดและดูว่ามีเสียงหรือไม่ แล้วเลือกเสียง

  • ในตัวอย่างนี้ เราจะเลือกเสียงแรก แต่เราก็อยากจะบอกด้วยว่าเรากำลังใช้ firstOrNull

  • อย่างที่บอกไปว่า ML Kit ต้องใช้โทรศัพท์ที่มีความสามารถเฉพาะ ซึ่งก็คืออุปกรณ์ Samsung หรือ Pixel

  • ดังนั้น หากคุณได้รับค่า Null อาจหมายความว่าผู้ใช้อาจไม่ได้ใช้โทรศัพท์ที่เข้ากันได้

  • จากนั้นคุณต้องตรวจสอบว่ามีฟังก์ชันสำรองไว้ ไม่ว่าจะเป็นการกลับไปใช้การอ่านออกเสียงข้อความของระบบ หรือการเล่นข้อความที่บันทึกไว้ หรืออะไรทำนองนั้น

  • โอเค จากนั้นคุณจะใช้การสังเคราะห์เสียงพูด SpeechSynthesizerRequest เพื่อสร้างคำขอ โดยป้อนข้อความที่ต้องการ จากนั้นตั้งค่าการใช้งานและประเภทเนื้อหา

    // 3. Construct the SpeechSynthesizerRequest
    val request = SpeechSynthesizerRequest(
       input = SpeechSynthesizerRequest.Input.Text("Hello world", voiceInfo),
       audioAttributes = AudioAttributes.Builder()
           .setUsage(AudioAttributes.USAGE_MEDIA)
           .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
           .build()
    )
    
    // 4. Speak and collect status
    client.synthesize(request).collect { response ->
       when (response) {
           is SpeechSynthesizerResponse.StatusUpdate -> println("Playback status: ${response.status}")
       }
    }
  • เล่นเสียงพูด ตรวจสอบสถานะ และรับการแจ้งเตือนเพื่อให้คุณทราบเมื่อเสียงพูดจบลง จากนั้นรวบรวมข้อผิดพลาดที่อาจเกิดขึ้น

  • จากนั้นปิดทรัพยากรเมื่อจำเป็น

    // 4. Speak and collect status
    client.synthesize(request).collect { response ->
       when (response) {
           is SpeechSynthesizerResponse.StatusUpdate -> println("Playback status: ${response.status}")
       }
    }
    
    // 5. Release resources when finished (e.g., on Activity destroy)
    override fun onDestroy() {
       super.onDestroy()
       client.close()
    }
  • ซึ่งก็ค่อนข้างตรงไปตรงมาใช่ไหม

  • ขั้นตอนเหล่านี้จะช่วยเปลี่ยนเสียงของแอปจากเสียงแบบนี้…

  • สวัสดี นักพัฒนาแอป Android

  • ดีใจมากที่ได้เจอทุกคนในวันนี้

  • เราหวังว่าคุณจะเพลิดเพลินกับการนำเสนอของเราเกี่ยวกับประสบการณ์การใช้งานที่เน้นเสียงเป็นหลัก

  • เป็นเสียงที่ฟังดูเหมือนแบบนี้มากขึ้น

  • สวัสดี นักพัฒนาแอป Android

  • ดีใจมากที่ได้เจอทุกคนในวันนี้

  • เราหวังว่าคุณจะเพลิดเพลินกับการนำเสนอของเราเกี่ยวกับประสบการณ์การใช้งานที่เน้นเสียงเป็นหลัก

  • ดีขึ้นมากเลยใช่ไหม

  • ตกลง

  • ไลบรารีการอ่านออกเสียงข้อความของ ML Kit เป็นเครื่องมือที่มีประสิทธิภาพมาก แต่คุณไม่ได้จำกัดอยู่เพียงการใช้เสียงเหล่านี้เท่านั้น คุณยังเล่นเอียร์คอนด้วยไลบรารีนี้ได้ด้วย

    // 1. Register Earcon resource with Builder
    SpeechSynthesizerOptions.Builder(Locale.US)
       .addEarcon(
           name = "shutter_sound",
           Uri = "path/to/earcon.file".toUri()
       )
       .build()
    
    // 2. Request Earcon playback
    val request = SpeechSynthesizerRequest(
       input = SpeechSynthesizerRequest.Input.Earcon(
           name = "shutter_sound"
       ),
       audioAttributes = AudioAttributes.Builder()
           .setUsage(AudioAttributes
               .USAGE_ASSISTANCE_SONIFICATION)
           .setContentType(AudioAttributes
               .CONTENT_TYPE_SONIFICATION)
           .build()
    )
  • ดังนั้น เอียร์คอนตามชื่อก็คือไอคอนเสียงนั่นเอง

  • ซึ่งจะช่วยให้คุณเล่นเสียงสั้นๆ เพื่อสื่อสารกับผู้ใช้ได้

  • และเช่นเดียวกับไอคอนภาพที่มีเอียร์คอน ผู้ใช้จะต้องรู้ว่าสิ่งเหล่านี้คืออะไรและหมายถึงอะไร

  • ดังนั้น ในช่วงแรกคุณอาจต้องจับคู่การแจ้งเตือนแบบภาพกับการแจ้งเตือนด้วยเสียงเพื่อให้ชัดเจน

  • และเราได้สร้างเสียงมาตรฐานบางอย่างที่พร้อมใช้งานใน Android Design Hub แล้ว คุณสามารถใช้เสียงเหล่านั้นได้ โดยดาวน์โหลดจากที่นั่นโดยตรงและใช้เสียงเหล่านั้นสำหรับสิ่งต่างๆ เช่น การเปิดแอป การปิดแอป การแจ้งเตือนข้อผิดพลาด และการปิดเสียงหรือเปิดเสียง

  • นอกจากนี้ คุณยังมีตัวเลือกในการสร้างเอียร์คอนที่กำหนดเองได้ด้วย

  • ดังนั้น เราขอแนะนำให้คุณทำตามแนวทางปฏิบัติแนะนำบางอย่างเกี่ยวกับเอียร์คอน ไม่ว่าคุณจะใช้เอียร์คอนที่ดาวน์โหลดจาก Design Hub หรือสร้างขึ้นเองก็ตาม

  • ดังนั้น คุณจึงควรพิจารณาอย่างรอบคอบเกี่ยวกับวิธีใช้เอียร์คอนและวิธีใช้

  • ดังนั้นจึงมีบางสิ่งที่ควรคำนึงถึง เช่น สำหรับผู้เริ่มต้น ให้พยายามอย่าใช้เอียร์คอนมากเกินไป

  • อย่าเล่นเสียงเดิมซ้ำไปซ้ำมา เพราะอาจทำให้เกิดอาการที่เรียกว่า "หูล้า"

  • และอย่าลืมว่าเอียร์คอนควรเป็นเสียงที่ยอดเยี่ยมและเข้าใจง่าย รวมถึงใช้ระดับเสียงและจังหวะที่คุ้นเคยเมื่อนำไปใช้

  • และพยายามหลีกเลี่ยงเอียร์คอนที่ซับซ้อนเกินไปซึ่งต้องมีคำอธิบาย เพราะอาจเหมาะกับการแจ้งเตือนด้วยเสียงมากกว่า

  • และโปรดทราบว่าเสียงที่อาจฟังดูเบาในสตูดิโอหรือในระบบของคุณ หรือในลำโพงคอมพิวเตอร์ อาจกลายเป็นเสียงที่น่ารำคาญหลังจากที่ผู้ใช้เล่นซ้ำๆ

  • ดังนั้นพยายามอย่าส่งเสียงแจ้งเตือนผู้ใช้มากเกินไปจนทำให้สับสนหรือทำให้ผู้ใช้เพิกเฉยต่อการแจ้งเตือนที่สำคัญ

  • โอเค

  • ตอนนี้เราจึงสามารถให้สัญญาณเสียงและการตอบกลับได้ ไม่ว่าจะผ่านคำพูดที่เป็นธรรมชาติหรือการใช้เอียร์คอน

  • แล้วข้อมูลที่ป้อนเข้าไปล่ะ

  • แน่นอนว่ามีทัชแพดหรือปุ่มเฉพาะบนอุปกรณ์ด้วย ซึ่งคุณสามารถใช้เพื่อสร้างการโต้ตอบด้วยการแตะและปัดได้

  • แต่คุณอาจต้องการอนุญาตให้ใช้ UI แบบสนทนานี้ด้วย ซึ่งคุณสามารถใช้ GenAI Speech Recognition API ของ ML Kit ได้

  • ปัจจุบันฟีเจอร์นี้อยู่ในเวอร์ชันอัลฟ่าและพร้อมใช้งานในอุปกรณ์บางรุ่น แต่ฟีเจอร์พื้นฐานนี้จะทำงานใน 2 โหมดที่แตกต่างกัน

  • โหมดพื้นฐานจะใช้การจดจำคำพูดในอุปกรณ์แบบดั้งเดิม และพร้อมใช้งานในอุปกรณ์ Android ที่มีระดับ API 31 ขึ้นไป

  • นอกจากนี้ยังมีโหมดขั้นสูงที่ใช้ Gemini Nano ซึ่งพร้อมให้บริการในอุปกรณ์ Pixel 10 ขึ้นไป โดยโหมดนี้จะช่วยให้คุณเห็นคุณภาพที่ดีขึ้นและครอบคลุมภาษาได้มากขึ้นด้วย

  • ดังนั้น SpeechRecognizer จึงใช้รูปแบบโค้ดที่คล้ายกับ SpeechSynthesizer มาก

  • คุณสร้างไคลเอ็นต์ ตรวจสอบว่าโมเดลพร้อมใช้งานหรือไม่ ดาวน์โหลดหากจำเป็น สร้างคำขอ และปล่อยทรัพยากร

  • ดังนั้นเราจะเน้นที่ความแตกต่างหลักๆ ในการสร้างคำขอ การบันทึกเสียงจากไมโครโฟน และการประมวลผลผลลัพธ์

  • และเช่นเคย คุณจะต้องเพิ่มทรัพยากร Dependency ที่จำเป็น

    dependencies {
       // ML Kit Speech Recognition
       implementation("com.google.mlkit:genai-speech-recognition:1.0.0-alpha1")
    }
  • สิ่งที่คุณต้องใช้ที่นี่คือ Speech Recognition API

  • จากนั้นคุณจะเริ่มสร้างคำขอได้

  • ดังนั้นคุณจะใช้ฟังก์ชันระงับเมื่อกำหนดตรรกะการจดจำ

    suspend fun startMyRecognition() {
       // choose locale and mode
       val options: SpeechRecognizerOptions =
           speechRecognizerOptions {
               locale = Locale.US
               preferredMode = SpeechRecognizerOptions.Mode.MODE_ADVANCED
           }
    
       val speechRecognizer: SpeechRecognizer = SpeechRecognition.getClient(options)
    
       val request: SpeechRecognizerRequest =
           speechRecognizerRequest { audioSource = AudioSource.fromMic() }
    ...
  • จากนั้นระบุตัวเลือกการจดจำเสียงพูด โดยตั้งค่าภาษาและ preferredMode

  • และคุณยังเลือก audioSource และกำหนดคำขอได้ที่นี่ด้วย

  • จากนั้นคุณจะเริ่มการจดจำและรวบรวมคำตอบ

    speechRecognizer.startRecognition(request).collect { response ->
       when (response) {
           is SpeechRecognizerResponse.PartialTextResponse -> {
               // Real-time: Act immediately if trigger phrase is heard
               handleResponse(response.text, speechRecognizer)
           }
           is SpeechRecognizerResponse.FinalTextResponse -> {
               // Final result and recognition
               handleResponse(response.text, speechRecognizer)
           }
           is SpeechRecognizerResponse.CompletedResponse -> {
               resetListeningUi()
           }
           is SpeechRecognizerResponse.ErrorResponse -> {
               Log.e("SpeechRec", "Error: ${response.e.errorCode}", response.e)
               resetListeningUi()
           }
    ...
  • ดังนั้นเมื่อใช้ SpeechRecognizer คุณจะได้รับการตอบกลับ 4 รูปแบบ ได้แก่ บางส่วน, สุดท้าย, ข้อผิดพลาด และเสร็จสมบูรณ์

  • ส่วนการถอดเสียงบางส่วนจะดูที่สตรีมแบบเรียลไทม์ ซึ่งใช้สำหรับสิ่งต่างๆ เช่น คำสั่งเสียงหรือคีย์เวิร์ด

  • "Final" จะรับสตรีมเสียงสุดท้ายหลังจากวิเคราะห์และอัปเดตอย่างเต็มรูปแบบแล้ว "Completed" จะเกิดขึ้นหลังจากที่คำพูดเสร็จสมบูรณ์ และ "Error" จะรวบรวมข้อผิดพลาดที่อาจเกิดขึ้น

  • และนี่คือวิธีที่เรานำทุกอย่างมารวมกัน

    // Helper function to check for multiple phrases and trigger the right action
    private suspend fun handleResponse(text: String, recognizer: SpeechRecognizer) {
       val savePhrases = listOf("save", "bookmark", "remember")
       if (savePhrases.any { text.contains(it) }) {
    
           // 1. Update app state immediately
           saveItemForLater()
    
           // 2. Stop the recognizer to enforce the action and prevent TTS echo
           recognizer.stopRecognition()
    
           // 3. Play the mute earcon for immediate, non-verbal confirmation
           playEarcon()
    
           // 4. Provide voice feedback
           playAudioFeedback("Saved")
       }
    }
  • สมมติว่าเราต้องการให้ผู้ใช้สามารถใช้คำสั่งเสียงเพื่อบอกให้แอปบันทึกเนื้อหาบางประเภทไว้ดูภายหลังได้

  • เราอาจใช้คำสั่งเสียงที่ชัดเจนเพียงคำสั่งเดียวที่เราบอกให้ผู้ใช้ใช้ หรือคุณอาจอนุญาตให้ผู้ใช้ใช้คีย์เวิร์ดและวลีจำนวนหนึ่งก็ได้

  • ดังนั้นเราจึงตั้งค่าให้ผู้ใช้ใช้คำว่า "บันทึก" "บุ๊กมาร์ก" หรือ "จดจำ" ได้

  • ทันทีที่ SpeechRecognizer ตรวจพบว่าผู้ใช้ใช้วลีใดวลีหนึ่งเหล่านั้น ระบบจะหยุดบันทึกเนื้อหาและหยุดการจดจำคำพูด เล่นเอียร์คอนที่กำหนดเอง และใช้ GenAI Text to Speech ของ ML Kit เพื่อบอกผู้ใช้ว่าบันทึกเนื้อหาแล้ว

กรณีศึกษาเกี่ยวกับครูสอนภาษา XR

  • ตกลง

  • เราได้พูดถึงหลายเรื่องเลย

  • ซึ่งอาจฟังดูเป็นนามธรรมไปหน่อย

  • หากคุณเป็นเหมือนฉัน คุณคงอยากเห็นตัวอย่างในชีวิตจริง

  • และ Alice กับผมก็อยากจะแนะนำคุณเกี่ยวกับเรื่องนี้

  • เธอจะแนะนำคุณเกี่ยวกับครูสอนภาษา XR ในแอป Google AI Edge Gallery

  • ทีมของ Alice เป็นผู้สร้างฟีเจอร์นี้ และเธอจะแสดงให้คุณเห็นวิธีการทำงานของฟีเจอร์นี้

  • ขอเชิญคุณได้เลย

  • ขอบคุณ Amy

  • ใช่

  • ฉันจึงอยากเริ่มด้วยสิ่งที่หลายคนน่าจะรู้จัก

  • ฉันกำลังเรียนภาษาญี่ปุ่น และใช้เวลาหลายชั่วโมงในแอปเรียนภาษา บัตรคำศัพท์ การรักษา Streak และอื่นๆ อีกมากมาย

  • แล้วพอฉันไปถึงโตเกียว ฉันเดินเข้าไปในตลาดและพบว่าแทบไม่มีอะไรตรงหน้าเลยที่เป็นคำศัพท์ที่ฉันเรียนมา

  • มีของเล่นไม้ที่เรียกว่าเคนดามะวางอยู่บนแผงขายของในตลาดซึ่งฉันจำได้เมื่อเห็น แต่ไม่รู้ว่าจะพูดเป็นภาษาญี่ปุ่นอย่างไร

  • มีเครื่องประดับชิ้นหนึ่งในตู้โชว์ของร้านค้าที่ฉันอยากซื้อแต่ไม่รู้ว่าเรียกว่าอะไร

  • นั่นคือช่วงเวลาที่ฉันมีแรงจูงใจในการเรียนรู้คำศัพท์มากที่สุด

  • ไม่ใช่การนั่งอยู่บนโซฟาแล้วดูแอปภาษา

  • ในตอนนั้น ฉันกำลังมองหาสิ่งที่ฉันอยากเรียนรู้

  • นี่คือสิ่งที่ฉันต้องการ

  • ฉันอยากใส่แว่นตาอัจฉริยะทั้งวัน แค่เดินไปรอบๆ

  • และเมื่อใดก็ตามที่มีอะไรสะดุดตา ก็แค่ถามออกมาดังๆ

  • และในตอนกลางคืนเมื่อกลับไปที่โรงแรม ทุกอย่างที่ผมถามไว้ก็จะรอผมอยู่เหมือนบัตรคำศัพท์ในโทรศัพท์

  • และนี่คือสิ่งที่เราสร้างขึ้น

  • มาดูคลิปสั้นๆ กัน

  • เราจึงเรียกสิ่งนี้ว่าครูสอนภาษา XR

  • สวัสดี

  • วันนี้คุณต้องการเรียนรู้เกี่ยวกับอะไร

  • และคุณสามารถปรับแต่งประสบการณ์การเรียนรู้ได้ในการตั้งค่าสำหรับครู

  • เช่น เลือกภาษาที่ต้องการเรียนรู้และเลือกตัวละครครูที่เหมาะกับคุณ

  • อ๋อ ใช่

  • และคุณสามารถสลับระหว่างโหมดแว่นตากับโหมดโทรศัพท์ได้

  • ฉันจะพูดว่าองุ่นเหล่านี้เป็นภาษาสเปนได้ยังไง

  • ดูจากภาพนี้ องุ่นแสนอร่อยเหล่านี้เรียกว่า uvas

  • ในภาษาสเปน คุณสามารถพูดว่า "Estas uvas son deliciosas" เพื่ออธิบายองุ่นเหล่านั้น

  • ฉันอยากจะบอกว่าองุ่นเขียวโดยเฉพาะ

  • ระบบจะทำการตรวจสอบ

  • หากต้องการระบุองุ่นเขียว คุณสามารถพูดว่า "Uvas verdes"

  • Me gustan las uvas verdes" เป็นวิธีที่ยอดเยี่ยมในการบอกใครสักคนว่าคุณชอบองุ่นเขียว

  • คุณจะพูดคำนี้เป็นภาษาฝรั่งเศสว่าอย่างไร และฉันจะขอ 3 อันได้อย่างไร

  • มาดูกันเลย

  • ขนมอบหน้าตาน่ารับประทานเหล่านี้เรียกว่ามาเดอลีน

  • ในภาษาฝรั่งเศส หากต้องการขอ 3 ชิ้น คุณสามารถพูดว่า "Je voudrais trois madeleines, s'il vous plaît."

  • ฉันจะขอเปลี่ยนแปลงข้อมูลนี้ได้อย่างไร

  • มาดูกันว่าเรามีอะไรบ้าง

  • หากต้องการขอเงินทอน คุณสามารถพูดว่า "Otsuri wa arimasu ka?"

  • ซึ่งหมายความว่า "คุณมีเงินทอนไหม"

  • คุณยังสามารถใช้คำว่า "Otsuri o onegaishimasu" เพื่อขอเงินทอนอย่างสุภาพได้ด้วย

  • จากนั้น ระบบจะบันทึกทุกสิ่งที่คุณเรียนรู้เป็นบัตรคำศัพท์ให้คุณโดยอัตโนมัติ

  • La botella de agua.

  • Esta es una botella de agua.

  • ใช่ และคุณสามารถทดสอบความจำและฝึกฝนได้ในแท็บการศึกษา

  • โอเค

  • ใช่

  • ดังนั้น ทุกอย่างที่คุณเพิ่งเห็นจึงทำงานบนอุปกรณ์ โดยไม่มีเครือข่ายในลูปเลย

  • ฉันเดินทางโดยใช้แพ็กเกจอินเทอร์เน็ตระหว่างประเทศ ซึ่งปกติแล้วก็ใช้งานได้ดีจนกระทั่งฉันเข้าไปในตลาดหรืออาจจะอยู่ใต้ดินหรือหลังคอนกรีตหนาๆ

  • ช่วงเวลาที่คุณต้องการใช้ฟีเจอร์นี้มากที่สุดมักเป็นช่วงเวลาที่สัญญาณแย่ที่สุด

  • และฉันอยากจะบอกว่า ประสบการณ์ที่หายไปเมื่อสัญญาณขาดหายไม่ใช่เพื่อนร่วมเดินทางที่แท้จริง เราจึงสร้าง XR Language Teacher ให้ทำงานแบบออฟไลน์และในอุปกรณ์ได้อย่างสมบูรณ์

การเชื่อมโยงโมเดลในอุปกรณ์

  • แล้วเราสร้างเทคโนโลยีนี้ขึ้นมาอย่างไร

  • ในด้านสถาปัตยกรรม Amy ได้แสดงวิธีใช้เฟรมกล้องที่ฉายจากแว่นตาไปยังโทรศัพท์ให้คุณดูแล้ว

  • เธอแสดงให้คุณเห็นวิธีใช้ ML Kit, การจดจำคำพูด, การอ่านออกเสียงข้อความ และการอ่านออกเสียงข้อความของ ML Kit เพื่อเปลี่ยนข้อความกลับเป็นเสียงพูด

  • สิ่งที่เรายังขาดอยู่คือสมองที่อยู่ตรงกลาง ซึ่งสามารถดูเฟรมกล้องและคำถามร่วมกัน และหาคำตอบว่าผู้ใช้กำลังถามถึงอะไรกันแน่

  • สมองนั้นคือ Gemini Nano 4 ซึ่งสร้างขึ้นบน Gemma 4 ซึ่งเป็นโมเดลโอเพนซอร์สของ Google ที่ทำงานในโทรศัพท์โดยตรง

  • ดังนั้นในอุปกรณ์ Android รุ่นเรือธงที่รองรับ คุณจะได้รับประสิทธิภาพที่เพิ่มขึ้นโดยใช้ ML Kit API โดยตรง

  • ระบบ Android จะดูแลการนำส่งโมเดลให้คุณ คุณจึงไม่ต้องรวมโมเดลไว้ในแอป

  • สรุปก็คือ ไมโครโฟนจะรับเสียงเพื่อนำไปใช้ในการจดจำคำพูด จากนั้นเฟรมกล้องและข้อความถอดเสียงจะส่งไปยัง Gemini Nano 4 และ Gemini Nano 4 จะเขียนข้อมูลลงในการสังเคราะห์เสียงพูด แล้วส่งเสียงออกมาจากลำโพง

  • โมเดล 3 โมเดลที่เชื่อมโยงกันและทำงานในโทรศัพท์ของคุณทั้งหมด

  • และนี่คือเหตุผลบางประการที่ทำให้ Gemini Nano 4 เหมาะกับปัญหานี้เป็นพิเศษ

  • ประการแรกคือการมองเห็นและอ่านได้ในขั้นตอนเดียว

  • Gemini Nano 4 จะจัดการข้อความและรูปภาพในตัวผ่าน ML Kit คุณจึงไม่ต้องใช้เครื่องมือตรวจจับ เครื่องมือแยกประเภท และเครื่องมือแปลภาษา

  • ซึ่งเป็นโมเดลเดียวที่ดูเฟรมกล้องและคำถามไปพร้อมกัน

  • และอย่างที่ 2 คือการรองรับภาษาที่ครอบคลุม

  • Gemini Nano 4 ได้รับการฝึกด้วยภาษามากกว่า 140 ภาษา

  • นอกจากนี้ สำหรับติวเตอร์สอนภาษา การแปลเพียงอย่างเดียวยังไม่เพียงพอ

  • คุณต้องการสำนวน คุณต้องการการลงทะเบียนแบบเป็นทางการและแบบไม่เป็นทางการ และคุณยังต้องการประโยคตัวอย่างที่บุคคลจริงจะพูดด้วย

  • และสุดท้ายคือสามารถควบคุมได้มากพอที่จะมีบุคลิกเฉพาะตัว

  • การฝึกออกเสียงดังๆ อาจเป็นเรื่องที่น่ากลัว

  • และโมเดลนี้ทำตามพรอมต์ของระบบได้ดี ผู้ใช้จึงเลือกตัวตนของครูเองได้

  • และบางทีการมีผู้ช่วยที่อดทนและคอยให้กำลังใจอาจทำให้ผู้คนเต็มใจที่จะลองมากขึ้น

  • และยังมีอีกสิ่งหนึ่งที่ผมอยากไฮไลต์เกี่ยวกับการออกแบบ

  • ขณะที่การสนทนาดำเนินไปในโหมดแว่นตา แอปจะเขียนการ์ดคำศัพท์อย่างเงียบๆ ในเบื้องหลัง

  • คำ การแปล ประโยคตัวอย่าง และรูปภาพจากช่วงเวลาที่คุณถามคำถาม

  • ผู้ใช้ไม่จำเป็นต้องแตะอะไรด้วยตนเอง

  • พวกเขาแค่เดินและพูดคุยกัน

  • และหลังจากนั้นในวันเดียวกัน ทุกอย่างก็พร้อมให้ใช้งานในโทรศัพท์ ซึ่งเป็นสิ่งที่ฉันต้องการมากตอนที่อยู่ในโตเกียว

  • ดังนั้น การเน้นเสียงเป็นหลักจึงไม่ได้หมายความว่าต้องมีเฉพาะเสียงเท่านั้น

  • แว่นตาเป็นแพลตฟอร์มที่เหมาะสำหรับความอยากรู้อยากเห็น และโทรศัพท์เป็นแพลตฟอร์มที่เหมาะสำหรับการไตร่ตรองในภายหลัง

  • ปล่อยให้ช่วงเวลาแฮนด์ฟรีทิ้งบางสิ่งไว้เบื้องหลัง

  • ตอนนี้เรามาดูเคล็ดลับที่นำไปใช้ได้จริงในการสร้างประสบการณ์แบบเรียลไทม์ด้วย Gemini Nano 4 และ ML Kit ที่เราได้เรียนรู้จากการสร้างเดโมนี้กัน

การจัดลำดับความสำคัญของเวลาในการตอบสนองแบบเรียลไทม์ด้วยการออกแบบการประมวลผล 2 รอบ

  • เคล็ดลับที่ 1 ให้ความสำคัญกับเวลาในการตอบสนองแบบเรียลไทม์ด้วยการออกแบบการประมวลผล 2 รอบ

  • เมื่อคุณถามว่า "นั่นคืออะไร" บนแว่นตาที่มีระบบเสียง

  • ทุกช่วงเวลาที่เงียบจะสังเกตเห็นได้

  • ในตัวอย่างของครูสอนภาษา XR เราต้องการ 2 สิ่งจากการโต้ตอบของผู้ใช้แต่ละครั้งจากโมเดล ได้แก่ คำตอบที่พูดทันที และบัตรคำศัพท์ที่มีโครงสร้างเพื่อบันทึกลงในแอป

  • หากเราขอให้โมเดลทำ 2 อย่างพร้อมกันในการส่งผ่านข้อมูลครั้งเดียว โทเค็นทุกรายการที่ใช้ในการสร้างข้อมูลเบื้องหลังนั้นจะทำให้ประโยคแรกที่พูดล่าช้าออกไป

  • ดังนั้น เราจึงแยกสิ่งที่ผู้ใช้ต้องการในขณะนั้นออกจากสิ่งที่แอปต้องการในอีกไม่กี่วินาทีต่อมา

  • เราจึงแบ่งแต่ละรอบออกเป็น 2 ช่วง

  • ในรอบที่ 1 เราจะสตรีมเฟรมกล้องและคำถามสำหรับการตอบกลับด้วยเสียงพูดเพียงอย่างเดียว โดยจะทริกเกอร์การอ่านออกเสียงข้อความทันทีที่ประโยคแรกเสร็จสมบูรณ์

  • และเนื่องจาก TPU หรือ NPU นั้นสร้างคำตอบสั้นๆ เสร็จก่อนที่การอ่านออกเสียงข้อความจะพูดจบเป็นเวลานาน เราจึงมีช่วงเวลาหลายวินาทีที่ผู้ใช้ยังคงฟังอยู่ และชิปก็ไม่ได้ทำงานแล้ว

  • และเนื่องจาก Pass 1 ได้ระบุวัตถุในการตอบกลับแบบข้อความแล้ว Pass 2 จึงไม่จำเป็นต้องใช้อินพุตรูปภาพด้วยซ้ำ

  • เราเพียงส่งคำตอบของ Pass 1 กลับไปยังชิปที่ไม่มีการใช้งานนั้นเพื่อแยกและบันทึกการ์ดที่มีโครงสร้างก่อนที่ครูจะพูดจบด้วยซ้ำ

  • ตอนนี้เรามาซูมดู Pass 1 กัน ซึ่งจะนำเราไปสู่เคล็ดลับที่ 2

  • ใช้แท็ก XML ที่ใช้ทรัพยากรน้อยเมื่อสตรีม

  • เนื่องจาก Pass 1 สตรีมไปยังการอ่านออกเสียงข้อความโดยตรง คุณจึงไม่ต้องการให้ออบเจ็กต์ JSON ขนาดใหญ่ทำให้คำพูดแรกของคุณล่าช้า

  • แต่คุณต้องการข้อความธรรมดาที่สามารถพูดได้ทันทีด้วยแท็กในบรรทัดที่มีน้ำหนักเบาและเปิดอยู่เมื่อใดก็ตามที่แอปจำเป็นต้องดำเนินการระหว่างสตรีม

  • ฉันขอแสดงปัญหาที่ทำให้เราต้องมาถึงจุดนี้

  • จำของเล่นไม้ถ้วยและลูกบอลบนแผงขายของในตลาดที่ฉันพูดถึงได้ไหม

  • ในภาษาญี่ปุ่นเรียกว่า "เคนดามะ"

  • ในช่วงแรก โมเดลจดจำวัตถุได้จากเฟรมกล้องและให้คำตอบที่ถูกต้องว่า "นั่นคือเคนดามะ ของเล่นไม้"

  • เราจึงส่งประโยคที่สมบูรณ์นั้นไปยังเซสชันการอ่านออกเสียงข้อความภาษาอังกฤษ และนี่คือผลลัพธ์ที่ได้

  • นั่นคือเคนมิ ของเล่นไม้

  • ดูเหมือนว่าข้อมูลจะไม่ถูกต้อง

  • แต่ประโยคนั้นควรจะฟังดูเหมือน…

  • นั่นคือเคนดามะ ของเล่นไม้

  • แล้วทำไมถึงเป็นเช่นนั้น

  • เคนดามะเขียนด้วยอักษรฮิรางานะ "เคน" และอักษรคันจิ "ดามะ"

  • และเครื่องมืออ่านออกเสียงข้อความภาษาอังกฤษก็จัดการกับฮิรางานะที่ออกเสียงเหมือน "ken" ได้ดี แต่กลับไม่สามารถอ่านคันจิได้เลย และเปลี่ยน dama ให้เป็นคำที่ฟังดูเหมือน me

  • และปัญหานี้เกิดขึ้นในหลายภาษาในรูปแบบต่างๆ หากคุณไม่เปลี่ยนภาษา

  • ตัวอย่างเช่น ในภาษาสเปน คุณอาจได้ยินว่า…

  • ซู-โบ-ลู…

  • เมื่อคุณคาดหวังว่า…

  • Cebolla

  • และในทำนองเดียวกัน ในภาษาฝรั่งเศส คุณอาจได้ยินว่า…

  • PAIN oh chock-uh-lat…

  • เมื่อคุณคาดหวังว่า…

  • Pain au chocolat

  • ใช่ โดยปกติแล้วเซสชัน TTS จะออกแบบมาสำหรับภาษาเดียวในแต่ละครั้ง แต่ครูสอนภาษาจำเป็นต้องเปลี่ยนรหัสในทุกประโยค

  • ดังนั้นในรอบที่ 1 เราจะขอให้โมเดลรวมคำหรือวลีภาษาเป้าหมายทุกคำไว้ในแท็ก XML เป้าหมายขณะที่สตรีม

  • และเมื่อข้อมูลมาถึง ไคลเอ็นต์ของเราจะแยกข้อมูลตามเป้าหมาย กำหนดเส้นทางแต่ละช่วงไปยังภาษาที่ตรงกันสำหรับการอ่านออกเสียงข้อความ ซึ่งอาจเป็นภาษาญี่ปุ่น สเปน ฝรั่งเศส หรือภาษาใดก็ตามที่คุณกำลังเรียนรู้ จากนั้นจึงเล่นข้อมูลเหล่านั้นอย่างราบรื่น โดยเปลี่ยนสำเนียงกลางประโยค เพื่อให้คำอย่าง "เคนดามะ" ออกเสียงได้อย่างถูกต้อง

  • ดังนั้น สิ่งที่ได้จากตรงนี้คือโมเดลรู้แล้วว่าคำศัพท์ใดเป็นของภาษาใด คำใดเป็นของภาษาใด และการให้โมเดลติดแท็กช่วงเป้าหมายในบรรทัดขณะสตรีมนั้นสะอาดกว่าการประมวลผลภายหลังทั้งหมดนี้ด้วยตัวคุณเองในภายหลังมาก

  • และนี่คือโค้ดของรอบที่ 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>"
  • ที่ด้านบน เราใช้พรอมต์เพื่อสั่งให้โมเดลตอบกลับเป็นประโยค 1-3 ประโยคภายในแท็ก reply แล้วรวมคำภาษาญี่ปุ่นหรือประโยคตัวอย่างทุกประโยคไว้ภายในแท็ก target

  • จากนั้นเราจะส่งกล้อง ImagePart และพรอมต์ TextPart ไปยัง generateContentStream ของ ML Kit โดยแยกช่วงเป้าหมายเป็นอ่านออกเสียงข้อความเมื่อแต่ละประโยคมาถึง

  • ซึ่งนำเราไปสู่ขั้นตอนที่ 2 และเคล็ดลับที่ 3

  • แม้ว่าการใช้แท็ก XML แบบ Lightweight จะเหมาะกับสตรีมเสียงสด แต่ให้ใช้เอาต์พุตที่มีโครงสร้างซึ่งสร้างขึ้นเพื่อแสดงผลออบเจ็กต์ที่พิมพ์อย่างชัดเจนเมื่อบันทึกข้อมูลลงในแอป

  • เนื่องจากครูสอนภาษา XR ของเราเป็นการสนทนา ผู้ใช้จึงสามารถถามคำถามต่อเนื่องได้โดยไม่ต้องสอนคำศัพท์ใหม่

  • และเมื่อสอนคำศัพท์ ฐานข้อมูลจะต้องมีช่องพจนานุกรมที่ชัดเจนเพื่อสร้างการ์ดคำศัพท์

  • ดังนั้น แทนที่จะเขียนนิพจน์ทั่วไปที่เปราะบางเพื่อแยกวิเคราะห์ข้อความถอดเสียงของรอบที่ 1 เราจึงให้รอบที่ 2 แยกออบเจ็กต์ที่ปลอดภัยในการกำหนดประเภทในเบื้องหลัง

  • และเพื่อบังคับใช้สคีมาในรอบที่ 2 เราใช้ API เอาต์พุตที่มีโครงสร้างของ ML Kit ซึ่งเบื้องหลังจะใช้การถอดรหัสแบบจำกัด

    import com.google.mlkit.genai.prompt.Generation
    import com.google.mlkit.genai.prompt.generateTypedContentRequest
    import com.google.mlkit.genai.schema.annotations.Generable
    import com.google.mlkit.genai.schema.annotations.Guide
    
    // Pass 2 schema (XrLanguageTeacherGenerableSchemas.kt):
    @Generable(description = "Vocabulary flashcard for the taught word or short phrase")
    data class GenerableVocabCard(
      @Guide(description = TeacherResponseSchema.WORD_FIELD_DESCRIPTION) val word: String,
      @Guide(description = "Native-language meaning or translation") val meaning: String,
      @Guide(description = "Target-language example sentence") val example: String,
      @Guide(description = "Native-language translation of the example sentence") val example_translation: String,
    )
    
    @Generable(description = "Optional vocabulary card extracted from a teacher reply")
    data class GenerableOptionalVocabCardWrapper(
      @Guide(description = "Vocabulary flashcard if a vocabulary term was taught, or null otherwise")
      val card: GenerableVocabCard?
    )
  • คุณเพียงใส่คำอธิบายประกอบคลาสข้อมูลด้วย @Generable และแต่ละฟิลด์ด้วย @Guide เช่นเดียวกับตัวอย่างคลาสการ์ดคำศัพท์ที่นี่

  • และด้วยการรวมช่องการ์ดที่อนุญาตให้เป็นค่า Null ไว้ การส่งผ่านครั้งที่ 2 จะแสดงผลการ์ดที่มีข้อมูลเมื่อมีการสอนคำใหม่ หรือแสดงผลเป็น Null ในเทิร์นการสนทนาล้วนๆ

  • และนี่คือโค้ดของรอบที่ 2

    // 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": "けん玉をください。", ...}
  • ที่ด้านบน เราจะนำข้อความตอบกลับที่สตรีมของรอบที่ 1 มาใช้โดยไม่ต้องใช้รูปภาพ และสร้างพรอมต์การดึงข้อมูล

  • จากนั้น ขณะที่ผู้ใช้ยังคงฟังการอ่านออกเสียงข้อความอยู่ เราจะส่งพรอมต์และคลาส Wrapper ไปยัง generateTypedContentRequest และดึงข้อมูลบัตรคำศัพท์ที่สะอาดและไม่มีแท็ก หรือค่า Null หากไม่มีการสอนคำศัพท์

  • โอเค

  • เราได้พูดถึงเรื่องต่างๆ มากมาย

  • คุณอาจคิดว่า "ดีเลย มีอีกอย่างที่ดูดีบนเวทีแต่ฉันคงไม่มีวันได้แตะ" ซึ่งก็เป็นเรื่องที่เข้าใจได้

  • ไม่ใช่กิจวัตรนี้

  • XR Language Teacher จะมาพร้อมกับแอป Google AI Edge Gallery ควบคู่ไปกับประสบการณ์การใช้งาน AI ในอุปกรณ์อื่นๆ

  • คุณสามารถดาวน์โหลดแอปนี้ได้ตั้งแต่วันนี้ ติดตั้งลงในโทรศัพท์ของคุณเอง แล้วเล็งไปที่สิ่งใดก็ได้ที่คุณต้องการเรียนรู้วิธีพูดเป็นภาษาอื่นได้ตั้งแต่วันนี้

  • และหากต้องการทราบวิธีสร้าง การติดตั้งใช้งานทั้งหมดนี้เป็นโอเพนซอร์สใน GitHub

  • และไม่ต้องกังวลหากผู้ใช้บางรายยังไม่มีแว่นตาที่มีจอแสดงผลหรือแว่นตาที่มีระบบเสียง หรืออุปกรณ์ Android รุ่นเรือธงที่รองรับ

  • ฐานของโค้ดของเรายังแสดงตัวอย่างวิธีปรับเปลี่ยนประสบการณ์เดียวกันนี้ให้เหมาะกับประสบการณ์การใช้งานบนอุปกรณ์เคลื่อนที่เท่านั้นด้วย

  • และหากต้องการเข้าถึงแพลตฟอร์มอื่นๆ ด้วย โค้ดแอป Google AI Edge Gallery ยังแสดงวิธีสร้างฟีเจอร์นี้โดยใช้รันไทม์ Google LiteRT และ MediaPipe Tasks API ด้วย

  • หรือหากต้องการนำโมเดลเสียงหรือโมเดลภาษาในอุปกรณ์แบบโอเพนซอร์สอื่นๆ มาใช้ คุณก็ดูวิธีดำเนินการได้จากโค้ดเช่นกัน

การปรับให้เข้ากับกรณีการใช้งานอื่นๆ ด้วย AI Agent

  • เราจึงเปิดตัว XR Language Teacher เป็นโอเพนซอร์สเพื่อให้คุณสร้างต้นแบบได้ง่ายขึ้น

  • ชี้ Agent ที่คุณเลือกไปที่ที่เก็บ GitHub ของเราเพื่อเปลี่ยนประสบการณ์การใช้งานแบบเรียลไทม์ในอุปกรณ์ที่ทำงานด้วยระบบ AI นี้ให้เป็นอย่างอื่น

  • อาจเป็นไกด์นำเที่ยวพิพิธภัณฑ์ ผู้ช่วยทำอาหารที่ดูเคาน์เตอร์ของคุณ คำแนะนำการซ่อม ผู้บรรยายการช่วยเหลือพิเศษ หรือเครื่องมือตรวจสอบภาคสนาม

  • โมเดล, รันไทม์, การติดตั้งใช้งานโอเพนซอร์ส และเครื่องมือทั้งหมดพร้อมให้ใช้งานแล้ว เพื่อให้คุณเตรียมพร้อมสำหรับการเปิดตัวแว่นตาอัจฉริยะที่กำลังจะมาถึงในฤดูใบไม้ร่วงนี้

  • เราอยากเห็นว่าคุณจะสร้างอะไรจากฟีเจอร์นี้

  • สรุปแล้ว วันนี้เราได้แสดงตัวอย่างที่มีประสิทธิภาพของสิ่งที่เป็นไปได้ด้วยโมเดล AI ที่ทำงานบนอุปกรณ์ได้อย่างราบรื่น

  • มาดูวิธีที่จะช่วยคุณเริ่มต้นใช้งานกันดีกว่า

  • ก่อนอื่น ให้ดูแหล่งข้อมูลบนหน้าจอ

  • ดาวน์โหลดแอป AI Edge Gallery เพื่อลองใช้เดโมและดูโค้ดอ้างอิงใน GitHub

  • และสแกนคิวอาร์โค้ดเพื่อเข้าถึง Android XR SDK, ML Kit API และข้อเสนอ Google AI Edge

  • 2 เริ่มทดลองใช้บนโทรศัพท์ได้เลยวันนี้

  • เราได้พูดถึงหลายๆ สิ่งที่สามารถสร้างขึ้นด้วยประสบการณ์การใช้งานอุปกรณ์เคลื่อนที่เป็นอันดับแรกไปแล้ว

  • และข้อที่ 3 ให้ดูแอปบนอุปกรณ์เคลื่อนที่ที่คุณเผยแพร่อยู่แล้ว และถามตัวเองว่าฟีเจอร์หลักใดบ้างที่จะทำงานได้ดีขึ้นแบบแฮนด์ฟรี

  • เพียงเพิ่มกิจกรรมที่ฉายลงใน APK ที่มีอยู่ คุณก็สามารถนำประสบการณ์การใช้งานนั้นมาใช้กับแว่นตาได้ด้วย

  • ขอขอบคุณที่มาร่วมเซสชันของเราในวันนี้

  • เราอยากเห็นแล้วว่าคุณจะสร้างประสบการณ์การใช้งานที่เน้นเสียงเป็นหลักอันน่าทึ่งอะไรบ้างบน Android และสำหรับแว่นตาอัจฉริยะ

  • ขอขอบคุณ

รหัส

ข้อมูลโค้ด

ข้อมูลโค้ด Kotlin ที่ 02:39 ใน "การสร้างสำหรับ Android XR"
dependencies {
   implementation("androidx.xr.projected:projected:1.0.0-alpha13")
}
ข้อมูลโค้ด Kotlin ที่ 02:45 ใน "การสร้างสำหรับ Android XR"
class ProjectedMainActivity : ComponentActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContent {
           // Glasses content
       }
   }
}
ข้อมูลโค้ด XML ที่ 03:02 ใน "การสร้างสำหรับ 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 ที่ 03:44 ใน "การสร้างสำหรับ 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 ที่ 05:05 ใน "การสร้างสำหรับ 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 ที่ 05:46 ใน "การสร้างสำหรับ Android XR"
android skills add projected-hardware
android skills add --all
ข้อมูลโค้ด Kotlin ที่นาที 08:14 ใน "การออกแบบประสบการณ์ที่เน้นเสียงเป็นหลัก"
dependencies {
   // ML Kit GenAI TTS SDK
   implementation("com.google.mlkit:genai-text-to-speech:1.0.0-alpha1")
}
ข้อมูลโค้ด Kotlin ที่ 08:26 ใน "การออกแบบประสบการณ์ที่เน้นเสียงเป็นหลัก"
// 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 ที่นาที 09:26 ใน "การออกแบบประสบการณ์ที่เน้นเสียงเป็นหลัก"
// 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 ที่ 09:47 ใน "การออกแบบประสบการณ์ที่เน้นเสียงเป็นหลัก"
// 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 ที่นาที 10:29 ใน "การออกแบบประสบการณ์ที่เน้นเสียงเป็นหลัก"
// 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 ที่นาที 13:42 ใน "การออกแบบประสบการณ์ที่เน้นเสียงเป็นหลัก"
dependencies {
   // ML Kit Speech Recognition
   implementation("com.google.mlkit:genai-speech-recognition:1.0.0-alpha1")
}
ข้อมูลโค้ด Kotlin ที่นาที 13:52 ใน "การออกแบบประสบการณ์ที่เน้นเสียงเป็นหลัก"
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 ที่นาที 14:12 ใน "การออกแบบประสบการณ์ที่เน้นเสียงเป็นหลัก"
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 ที่นาที 14:41 ใน "การออกแบบประสบการณ์ที่เน้นเสียงเป็นหลัก"
// 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 ที่ 26:27 ใน "การจัดลำดับความสำคัญของเวลาในการตอบสนองแบบเรียลไทม์ด้วยการออกแบบ 2 ขั้นตอน"
// 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 ที่นาที 27:33 ใน "การจัดลำดับความสำคัญของเวลาในการตอบสนองแบบเรียลไทม์ด้วยการออกแบบแบบ 2 รอบ"
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 ที่นาที 28:00 ใน "การจัดลำดับความสำคัญของเวลาในการตอบสนองแบบเรียลไทม์ด้วยการออกแบบแบบ 2 รอบ"
// 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": "けん玉をください。", ...}