You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义Android TextToSpeechService无法接收Google TalkBack使用提示

问题分析与解决方案

核心错误原因

你的自定义TextToSpeechService违反了系统TTS服务的核心规范:

  • 系统TTS服务的职责是生成PCM音频数据并通过SynthesisCallback返回给调用方(比如TalkBack),而非直接调用另一个TTS引擎的speak()方法播放音频。
  • 直接调用Google TTS的speak()会绕开系统的音频调度机制,和TalkBack的音频输出抢占通道,导致TalkBack的提示音被覆盖或无法触发;同时你在调用speak()后立刻执行callback.done(),系统会认为合成已完成,但Google TTS的异步播放还在进行,阻塞后续的TalkBack请求。

修正方案

需要让Google TTS生成音频数据,再通过自定义服务的SynthesisCallback将数据返回给系统,由系统统一调度播放。以下是修改后的关键代码:

1. 单例类中的合成逻辑修改

private TextToSpeech mAndroidTTS = null;
private SynthesisCallback mCurrentCallback;
private String mTempAudioPath;

public void initEnglishTTS(Context context) {
    if (mAndroidTTS == null) {
        mAndroidTTS = new TextToSpeech(context, status -> {
            if (status == TextToSpeech.SUCCESS) {
                mAndroidTTS.setLanguage(Locale.US);
                // 设置 utterance 监听,用于捕获合成完成事件
                mAndroidTTS.setOnUtteranceProgressListener(new UtteranceProgressListener() {
                    @Override
                    public void onStart(String utteranceId) {}

                    @Override
                    public void onDone(String utteranceId) {
                        if (mCurrentCallback == null) return;
                        try {
                            // 读取临时音频文件,通过回调输出给系统
                            FileInputStream fis = new FileInputStream(mTempAudioPath);
                            byte[] buffer = new byte[4096];
                            int bytesRead;
                            mCurrentCallback.start(16000, AudioFormat.ENCODING_PCM_16BIT, 1);
                            while ((bytesRead = fis.read(buffer)) != -1) {
                                mCurrentCallback.audioAvailable(buffer, 0, bytesRead);
                            }
                            fis.close();
                            // 清理临时文件
                            new File(mTempAudioPath).delete();
                            mCurrentCallback.done();
                        } catch (IOException e) {
                            mCurrentCallback.error(TextToSpeech.ERROR_IO);
                        } finally {
                            mCurrentCallback = null;
                        }
                    }

                    @Override
                    public void onError(String utteranceId) {
                        if (mCurrentCallback != null) {
                            mCurrentCallback.error(TextToSpeech.ERROR);
                            new File(mTempAudioPath).delete();
                            mCurrentCallback = null;
                        }
                    }
                });
            }
        }, "com.google.android.tts");
    }
}

public void synthesize(String text, SynthesisCallback callback) {
    if (mAndroidTTS == null || mAndroidTTS.getEngines().isEmpty()) {
        callback.error(TextToSpeech.ERROR);
        return;
    }

    mCurrentCallback = callback;
    // 创建内部缓存目录的临时文件,避免权限问题
    mTempAudioPath = mContext.getCacheDir().getAbsolutePath() + "/tts_temp_" + System.currentTimeMillis() + ".pcm";
    HashMap<String, String> params = new HashMap<>();
    String utteranceId = UUID.randomUUID().toString();
    params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, utteranceId);

    // 调用Google TTS合成音频到临时文件
    int result = mAndroidTTS.synthesizeToFile(text, params, mTempAudioPath);
    if (result != TextToSpeech.SUCCESS) {
        callback.error(result);
        mCurrentCallback = null;
    }
}

// 移除原有的sayInEnglish方法

2. 自定义TTS服务代码保持不变

public class CustomTtsService extends TextToSpeechService {
    private SynthesizerSingleton singleton = SynthesizerSingleton.getInstance();
    
    @Override
    protected void onSynthesizeText(SynthesisRequest request, SynthesisCallback callback) {
         String text = getRequestString(request);  
         singleton.synthesize(text, callback);
    }

    private String getRequestString(SynthesisRequest request) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            return request.getCharSequenceText().toString();
        } else {
            return request.getText();
        }
    }
}

额外注意事项

  • 无需额外权限:使用应用内部缓存目录存储临时音频文件,不需要申请WRITE_EXTERNAL_STORAGE权限。
  • 异常处理:务必处理合成失败、文件读取失败等情况,避免内存泄漏或回调未关闭。
  • 替代方案:如果不需要自定义非英文文本的合成逻辑,可直接通过系统TTS引擎的语言匹配机制,让系统自动为英文选择Google TTS引擎,无需在自定义服务中嵌套调用。

内容的提问来源于stack exchange,提问作者arkate

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 02:10:39