把 App 接进 Android 16 的 TTS 框架

拾音 发布于 阅读:100 Android 开发

做独立播放器,是自己想怎么读就怎么读;做系统 TTS 引擎,是别人(阅读 App)说了算,你只负责把活接住。这个区别决定了整个结构。

最小的骨架

核心是继承 TextToSpeechService,在 manifest 里声明服务,并让系统能在「文字转语音输出」设置里找到它:

<service android:name=".MimoTtsService"
         android:label="@string/engine_name">
    <intent-filter>
        <action android:name="android.intent.action.TTS_SERVICE" />
    </intent-filter>
    <meta-data android:name="android.speech.tts"
               android:resource="@xml/tts_engine" />
</service>

xml/tts_engine 里指向一个 settings Activity,用来放语速、音色这些开关。系统设置页会直接拉起它。

三个必须实现的方法

override fun onSynthesizeText(
    request: SynthesisRequest,
    callback: SynthesisCallback,
    params: Bundle
) {
    val rate = request.speechRate   // 客户端 setSpeechRate() 传进来的
    callback.start(SAMPLE_RATE, AudioFormat.ENCODING_PCM_16BIT, 1)
    engine.stream(request.charSequenceText.toString(), rate) { chunk ->
        if (!callback.isDone) callback.audioAvailable(chunk, 0, chunk.size)
    }
    callback.done()
}

几个容易踩的点:audioAvailable 要在合成线程里持续喂,别等整段合成完一次性给,长章节会明显感到起播慢;onStop() 必须真的把合成任务取消掉,翻页时系统会频繁调用;回调期间要检查 callback.isDone,否则中断后再写会抛异常。

Android 16 上要多留意的

targetSdk 提到新版本后,后台启动和前台服务的限制更严,长时间朗读别依赖一个常驻后台服务自己保活,跟着系统的合成生命周期走更稳。另外 SynthesisRequest 里能拿到的参数比想象中少,语速、音调之外不要指望客户端把自定义字段透传过来——真要扩展,走自己的 settings 加共享配置更现实。

调试时最省事的办法,是写一个只调 TextToSpeech 并显式指定引擎包名的小测试 App,比在系统设置里来回切引擎快得多。

Android TextToSpeechService