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

Text To Speech (ขอเรียกสั้นๆว่า TTS) เป็นการส่งข้อความให้ระบบแปลงข้อความออกมาให้กลายเป็นเสียง ซึ่งตอนนี้ถือว่าเป็นฟีเจอร์พื้นฐานอย่างหนึ่งไปแล้วล่ะ ไม่ว่าเครื่องไหนก็ทำได้

โดยที่อุปกรณ์แอนดรอยด์แต่ละยี่ห้อก็อาจจะมี TTS Engine แตกต่างกันออกไป แต่จะมี TTS ที่ทาง Google ทำใส่ไว้ในแอนดรอยด์เป็นตัวหลัก อาจจะมี TTS Engine อื่นๆเพิ่มเข้ามาตามแต่ละยี่ห้อ อย่างเช่น Samsung เค้าก็จะมี TTS Engine ของตัวเองใส่เข้ามาด้วย ก็สามารถเลือกใช้งานได้ตามต้องการ

สิ่งที่ควรรู้เมื่อเรียกใช้งาน Text To Speech

Engine ที่เรียกใช้งานได้

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

ดังนั้นก่อนจะเรียกใช้งานก็ควรรู้ว่าในเครื่องนั้นๆมี Engine ตัวไหนให้ใช้งานอยู่ครับ ซึ่งโดยปกติแล้วแต่ละยี่ห้อจะมีการกำหนด Default Engine ไว้แตกต่างกัน อย่างเช่นของ Samsung ก็จะกำหนด TTS Engine ของ Samsung ให้เป็น Default ไว้

ภาษาที่รองรับใน Engine นั้นๆ

เพราะ TTS Engine ไม่ได้รองรับทุกภาษาเสมอไปครับ ดังนั้นควรตรวจสอบให้ดีๆก่อนว่า TTS Engine ที่จะเรียกใช้งานนั้นรองรับภาษาที่ต้องการใช้มั้ย?

โดยปกติแล้วจะรองรับภาษาพื้นฐานอยู่ 7 ภาษาด้วยกัน

  • เยอรมัน (de_DE)
  • อังกฤษ (en_GB)
  • อังกฤษ (en_US)
  • สเปน (es_ES)
  • ฝรั่งเศส (fr_FR)
  • อิตาลี (it_IT)
  • รัสเซีย (ru_RU)

แต่ในปัจจุบัน TTS Engine ที่ใช้กันอย่างแพร่หลาย ก็มักจะรองรับภาษาส่วนใหญ่ในโลกได้อยู่แล้ว และนั่นก็รวมไปถึงภาษาไทยด้วยเช่นกัน (อย่างน้อยก็บนอุปกรณ์แอนดรอยด์ที่ขายในไทย)

เมื่อนานมาแล้วสมัยที่เจ้าของบล็อกทำบทความนี้ครั้งแรก ได้ลองทดสอบดูแล้วพบว่าเครื่อง Samsung Galaxy Nexus (Android 4.3) ที่เจ้าของบล็อกใช้ในตอนนั้นนั้นยังรองรับแค่ 7 ภาษาหลักอยู่ ซึ่งตอนนั้นก็ยังเป็น TTS Engine ของ Google สมัยนู้นนนนเลย แต่ในปัจจุบันนี้ก็สามารถอัพเดทภาษาเพิ่มเติมได้แล้ว ทำให้ล่าสุดรองรับภาษาไทยเป็นที่เรียบร้อยแล้ว เฮ

มาเริ่มเรียกใช้งานกัน!

บนแอนดรอยด์จะมีคลาสที่ชื่อว่า TextToSpeech ให้ใช้งานอยู่แล้ว โดยการเรียกใช้งาน TTS เนี่ย มันจะต้องทำการ Initialize ตัวเองก่อนนะ

val context: Context = ...
val tts = TextToSpeech(context) {
    // Initialized
}

และที่สำคัญ เมื่อใช้งานเสร็จแล้วจะต้องสั่งให้หยุดทำงานด้วย

tts.shutdown()

ดังนั้นถ้าใช้งานใน Activity หรือ Fragment ก็จัดการให้เหมาะสมกับ Lifecycle ได้เลย

class MainActivity : AppCompatActivity() {
    private lateinit var tts: TextToSpeech

    override fun onCreate(savedInstanceState: Bundle?) {
        /* ... */
        tts = TextToSpeech(this) {
            // Initialized
        }
    }

    override fun onDestroy() {
        /* ... */
        tts.shutdown()
    }
}

กรณีที่อยากเปลี่ยนไปใช้ Engine ตัวที่ต้องการ (อย่าลืมนะว่าต้องมีอยู่ในเครื่องด้วย) ก็สามารถกำหนดได้ตั้งแต่ตอนแรกเลย โดยระบุ Package Name ของ Engine ที่ต้องการเรียกใช้งาน

ยกตัวอย่างเช่น เครื่องของเจ้าของบล็อกมี TTS Engine อยู่ 2 ตัวคือ com.google.android.tts กับ com.samsung.SMT ถ้าอยากใช้ของ Google ก็จะต้องกำหนดตั้งแต่ตอนแรกแบบนี้

val context: Context = ...
val tts = TextToSpeech(context, {
    // Initialized
}, "com.google.android.tts")

สำหรับ Callback ของคำสั่งนี้จะมีค่าส่งกลับมาเพื่อบอกว่าสามารถ Initialize ได้สำเร็จหรือไม่ โดยค่าจะมี 2 แบบด้วยกัน คือ

  • TextToSpeech.SUCCESS
  • TextToSpeech.ERROR
val tts = TextToSpeech(this, {
    when (it) {
        TextToSpeech.SUCCESS -> {
            // Do something when TTS initialized
        }
        TextToSpeech.ERROR -> {
            // Do something when TTS can't initialize
        }
    }
}, "com.google.android.tts")

เมื่อ TTS พร้อมใช้งาน ก็สามารถค่าเพิ่มเติมสำหรับ TTS ได้เลย เช่น ภาษา, เสียง หรือความเร็วของเสียง

tts.language = Locale.ITALY

แต่สำหรับภาษาไทยจะไม่มี Locale.THAI ให้ใช้งาน ดังนั้นต้องกำหนดแบบนี้แทน

tts.language = Locale("th")

และเวลาต้องการให้ TTS ทำงานก็แค่เรียกใช้คำสั่งง่ายๆแบบนี้

val tts: TextToSpeech = /* ... */
val message: String = /* ... */
val queueMode: Int = /* ... */
val params: Bundle? = /* ... */
val utteranceId: String? = /* ... */
tts.speak(message, queueMode, params, utteranceId)

สำหรับ queueMode เป็นการกำหนดวิธีการทำงานของ TTS เมื่อมีสั่งงานซ้อนกัน (เช่นกด 2 ครั้งติด) ซึ่งในส่วนนี้สามารถกำหนดได้ 2 แบบ คือ

  • TextToSpeech.QUEUE_FLUSH : ถ้ามีคำสั่ง Speak ทำงานอยู่ก่อนหน้า ก็จะหยุดทันทีแล้ว Speak ตัวใหม่จะทำงานทันที
  • TextToSpeech.QUEUE_ADD : ถ้ามีคำสั่ง Speak ทำงานอยู่ก่อนหน้า ก็จะรอจนกว่าทำงานเสร็จ แล้วจึงทำงานต่อ

ส่วน params มีไว้สำหรับส่งค่าบางอย่างให้กับ TTS ได้ครับ ซึ่งตรงนี้ให้ใช้เป็น Null ไป เพราะไม่ค่อยจำเป็นมากนักสำหรับการใช้งานทั่วไป

และ utteranceId เอาไว้กำหนด ID ของข้อความที่จะให้ TTS ทำงาน ซึ่งสามารถกำหนดเป็นอะไรก็ได้ ​ซึ่งค่านี้จะถูกส่งกลับมาในตอนที่เรียกใช้คำสั่ง setOnUtteranceProgressListener เพื่อบอกให้รู้ว่าเป็น Event ของ ID ไหนนั่นเอง

เวลาใช้งานจริงก็จะเป็นแบบนี้

tts.speak("Welcome, Sleeping For Less", TextToSpeech.QUEUE_FLUSH, null, "greeting")

และสามารถกำหนด UtteranceProgressListener เพื่อดัก Event  ต่างๆในระหว่างที่ TTS ทำงานได้ด้วยนะ เช่น ข้อความนั้นพูดจบแล้วหรือยัง จะได้ทำคำสั่งอื่นๆต่อไป

tts.setOnUtteranceProgressListener(object : UtteranceProgressListener() {
    override fun onStart(utteranceId: String?) {
        // Do something when utterance start
    }

    override fun onDone(utteranceId: String?) {
        // Do something when utterance done
    }

    override fun onError(utteranceId: String?) {
        // Do something when utterance error
    }
})

จะเห็นว่ามี utteranceId ส่งมาให้ด้วย นั่นก็คืออันเดียวกับที่กำหนดไว้ในใช้คำสั่ง speak(...) นั่นเอง

ใช้ไฟล์เสียงสำหรับคำที่ต้องการก็ได้นะ

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

ตัว TTS ในแอนดรอยด์รองรับการเพิ่ม Earcon เข้าไปได้ตามต้องการ โดยจะเก็บไฟล์เสียงไว้ใน res/raw ก็ได้ หรือจะเก็บไว้ในเครื่องแล้วระบุเป็น Path ก็ทำได้เช่นกัน

ยกตัวอย่างว่าเจ้าของบล็อกมีไฟล์เสียงแมวอยู่ใน res/raw/meow.mp3 และอยากจะให้ TTS อ่านคำว่า เมี๊ยว เป็นเสียงที่เตรียมไว้ ก็เพิ่ม Earcon ไว้ใน TTS หลังจากที่พร้อมใช้งานแล้ว

val context: Context = /* ... */
val tts: TextToSpeech = /* ... */
/* ... */
tts.addEarcon("meow", context.packageName, R.raw.meow)

และเวลาเรียกใช้งานจะใช้คำสั่ง playEarcon แทน

tts.playEarcon("meow", TextToSpeech.QUEUE_FLUSH, null, "cat_greeting")

ซึ่ง Earcon จะต่างจากปกติตรงที่ ไม่สามารถกำหนดเป็นคำหนึ่งคำที่อยู่ในประโยคได้ ถ้าจะกำหนดก็ต้องกำหนดทั้งประโยคเลย หรือตัดให้เหลือแค่คำนั้นแค่คำเดียว เพราะมันไม่ใช่ TTS มันเป็นแค่การกำหนดคำหนึ่งคำให้เล่นไฟล์เสียงที่ต้องการ

สรุป

จะเห็นว่าจริงๆแล้วการทำ Text To Speech นั้นไม่ใช่เรื่องยากเลย เพราะว่าระบบแอนดรอยด์รองรับเรื่องนี้อยู่แล้ว และในปัจจุบัน TTS Engine ที่นิยมใช้กันก็รองรับภาษาเยอะมากมายแล้ว

และนอกจากนี้ Text To Speech บนแอนดรอยด์ยังรองรับ Earcon ที่จะช่วยให้นักพัฒนาสามารถกำหนดไฟล์เสียงสำหรับแต่ละข้อความได้ แต่อย่าลืมนะว่า Earcon มีไว้ใช้งานแยกกับ Text To Speech

เพียงเท่านี้ ผู้ที่หลงเข้ามาอ่านก็น่าจะทำแอปที่รองรับ Text To Speech กันได้แล้วเนอะ