java实现TTS文字转语音wav (Jacob + SAPI)

Jacob + SAPI 的优缺点

Windows集成 SAPI  Jacob 调用系统语音,音质较好,无需安装额外引擎。 但仅限Windows平台,依赖COM组件。

注:此代码生成的wav文件,非单声道,不能直接应该在电话系统中,需要转换格式才行(单声道 8k  6bit)

第一步 下载 jacob-1.19-x64.dll

将 jacob-1.19-x64.dll 存放到工程根目录下

image

 第二步 下载 jacob.jar 引用到工程中

image

 第三步 直接上代码

WindowsTtsHelper.java

package com.JavaTTS_Jacob;

import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.Dispatch;
import com.jacob.com.Variant;

public class WindowsTtsHelper {
    private ActiveXComponent voice;
    private Dispatch sapVoice;

    /**
     * 初始化 TTS 引擎
     */
    public WindowsTtsHelper() {
        try {
            this.voice = new ActiveXComponent("SAPI.SpVoice");
            this.sapVoice = voice.getObject();
            System.out.println("TTS引擎初始化成功。");
        } catch (Exception e) {
            System.err.println("初始化TTS引擎失败,请确认系统支持 SAPI。");
            throw new RuntimeException(e);
        }
    }

    /**
     * 设置语音参数
     *
     * @param rate   语速,范围 -10(慢) 到 10(快),0为正常
     * @param volume 音量,范围 0 到 100
     */
    public void setParams(int rate, int volume) {
        try {
            if (rate < -10 || rate > 10) rate = 0;
            if (volume < 0 || volume > 100) volume = 80;

            voice.setProperty("Rate", new Variant(rate));
            voice.setProperty("Volume", new Variant(volume));
        } catch (Exception e) {
            System.err.println("设置参数失败: "+e.getMessage());
        }
    }

    /**
     * 选择并设置指定的发音人
     *
     * @param voiceNameKeyword 发音人名称关键词,如 "Huihui", "David", "Zira"
     * @return 是否设置成功
     */
    public boolean selectVoice(String voiceNameKeyword) {
        try {
            Dispatch voiceItems = Dispatch.call(sapVoice, "GetVoices").toDispatch();
            int count = Dispatch.call(voiceItems, "Count").changeType(Variant.VariantInt).getInt();

            for (int i = 0; i < count; i++) {
                Dispatch voiceItem = Dispatch.call(voiceItems, "Item", new Variant(i)).toDispatch();
                String desc = Dispatch.call(voiceItem, "GetDescription").toString();
                System.out.println("索引 " + i + ": " + desc);//显示所有语音包

                if (desc.toLowerCase().contains(voiceNameKeyword.toLowerCase())) {
                    // 【核心修复行】使用 put 设置属性
                    Dispatch.put(sapVoice, "Voice", voiceItem);
                    System.out.println("发音人设置成功: "+desc);
                    return true;
                }
            }
            System.out.println("未找到包含 ‘" + voiceNameKeyword + "‘的发音人,使用默认。");
            return false;
        } catch (Exception e) {
            System.err.println("选择发音人时出错: "+e.getMessage());
            return false;
        }
    }

    /**
     * 朗读文本(同步,会阻塞直到读完)
     *
     * @param text 要朗读的文本
     */
    public void speak(String text) {
        try {
            Dispatch.call(sapVoice, "Speak", new Variant(text));
        } catch (Exception e) {
            System.err.println("朗读失败: "+e.getMessage());
        }
    }

    /**
     * 将文本合成并保存为 WAV 文件 (修复版)
     * @param text 要合成的文本
     * @param filePath 输出文件完整路径,如 "C:\\output.wav"
     */
    public void saveToFile(String text, String filePath) {
        ActiveXComponent audioStream = null;
        try {
            // 1. 创建文件流对象
            audioStream = new ActiveXComponent("SAPI.SpFileStream");
            Dispatch stream = audioStream.getObject();

            // 2. 打开文件。模式 3 表示:创建用于写入(SSFMCreateForWrite)
            Dispatch.call(stream, "Open", new Variant(filePath), new Variant(3), new Variant(true));

            // 3. 【核心修复】使用 putRef 将文件流设置为语音对象的输出
            Dispatch.putRef(sapVoice, "AudioOutputStream", stream);

            // 4. 此时合成语音,音频数据将自动写入文件
            Dispatch.call(sapVoice, "Speak", new Variant(text));

            System.out.println("文件保存成功: " + filePath);

        } catch (Exception e) {
            System.err.println("保存文件失败: " + e.getMessage());
            e.printStackTrace(); // 打印完整堆栈有助于进一步调试
        } finally {
            // 5. 至关重要的清理工作:关闭流并重置输出
            try {
                if (audioStream != null) {
                    Dispatch stream = audioStream.getObject();
                    Dispatch.call(stream, "Close");
                    // 将音频输出重置回默认的扬声器,避免后续语音无法播放
                    Dispatch.putRef(sapVoice, "AudioOutputStream", null);
                    audioStream.safeRelease();
                }
            } catch (Exception e) {
                // 忽略清理时的次要错误
            }
        }
    }


    /**
     * 释放 COM 资源(重要!使用完毕后必须调用)
     */
    public void release() {
        if (voice != null) {
            voice.safeRelease();
            voice = null;
            sapVoice = null;
            System.out.println("TTS资源已释放。");
        }
    }

}

调用测试代码

App.java

package com.JavaTTS_Jacob;

import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.Dispatch;
import com.jacob.com.Variant;

/**
 * Hello world!
 */
public class App {
    public static void main(String[] args) {
        System.out.println("Hello World!");
        WindowsTtsHelper tts = new WindowsTtsHelper();
        try {
            // 1. 设置参数
            tts.setParams(1, 90); // -2稍慢,音量90%

            // 2. 选择中文发音人(如果系统有安装) 未找到包含 ‘David‘的发音人,使用默认。
            //boolean success = tts.selectVoice("David");

            // 3. 朗读
            //tts.speak("你好,语音导航测试。这是一个修复后的稳定版本。");
            //Thread.sleep(500); // 简单等待一下

            // 4. 保存为文件
            tts.saveToFile("福佳2楼管道漏水,暂时停供,正在抢修,给您造成不便,敬请谅解!", "d:\\joincc_output.wav");
 
            System.out.println("\n所有功能测试完成!");

        } catch (Exception e) {
            System.err.println("WindowsTTS转Wav出错: " + e.getMessage());
            //e.printStackTrace();
        }finally {
            // 确保释放资源
            tts.release();
        }

    }


    //不需要选择特定发音人
    private void TTSRead(){
        ActiveXComponent voice = new ActiveXComponent("SAPI.SpVoice");

        try {
            // 只设置基础参数,不选择特定发音人
            voice.setProperty("Rate", new Variant(0));   // 正常语速
            voice.setProperty("Volume", new Variant(100)); // 最大音量

            // 直接朗读
            String[] texts = {
                    "你好,这是默认语音测试。",
                    "欢迎使用语音导航系统。",
                    "操作完成,感谢使用。"
            };

            for (String text : texts) {
                System.out.println("朗读: " + text);
                Dispatch.call(voice.getObject(), "Speak", new Variant(text));
                Thread.sleep(800); // 稍微停顿
            }

        } catch (Exception e) {
            System.err.println("错误: " + e.getMessage());
            e.printStackTrace();
        } finally {
            voice.safeRelease();
        }

    }

}

 pom.xml 

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.junit</groupId>
        <artifactId>junit-bom</artifactId>
        <version>5.11.0</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>com.hynnet</groupId>
        <artifactId>jacob</artifactId>
        <version>1.19</version>
      </dependency>
      
    </dependencies>
  </dependencyManagement>

 

posted @ 2026-01-13 10:57  海乐学习  阅读(9)  评论(0)    收藏  举报