Skip to content

整合 fishaudio 到 java-uni-ai-server 项目 ​

本文档详细介绍了如何在项目中整合 fishaudio 语音合成服务。项目原有的 TTS(语音合成)服务支持 openai 和 volce 平台,现在新增 fishaudio 服务,方便用户选择不同的 TTS 平台。下面将逐步展示各个模块的代码及功能说明。


1. 项目模块介绍 ​

本次整合涉及到以下主要模块:

  • TTSPlatform 接口
    定义了所有支持的 TTS 平台常量,包括新增的 fishaudio。

  • ManimTTSHandler 请求处理器
    负责接收客户端请求,从 HTTP 请求中提取参数,调用服务层生成语音,并返回生成的 MP3 文件。

  • ManimTTSService 服务实现
    核心业务逻辑模块。该模块首先检查是否存在缓存(通过 MD5 校验输入文本);若缓存存在则直接读取缓存文件。若缓存不存在,则根据请求选择调用不同的 TTS API(包括 fishaudio、volce 和 openai),生成语音文件后存储到本地并写入数据库缓存记录。


2. 代码详解与说明 ​

2.1 TTSPlatform 接口 ​

该接口定义了所有支持的 TTS 服务平台常量。在新增 fishaudio 整合时,我们只需在接口中增加一个对应的常量。

java
package nexus.io.uni.consts;

public interface TTSPlatform {

  String volce = "volce";
  String openai = "openai";
  String fishaudio = "fishaudio";
}

说明

  • 此接口中定义了三个字符串常量:volce、openai 和 fishaudio。
  • 在后续代码中,通过比较传入的 provider 参数与这些常量来决定调用哪个平台的 TTS 服务。

2.2 ManimTTSHandler 请求处理器 ​

该类是 HTTP 请求的入口,负责从请求中获取参数,然后调用 ManimTTSService 的 tts 方法生成语音,再将返回的字节数组组装成 MP3 音频响应返回给客户端。

java
package nexus.io.uni.handler;

import nexus.io.jfinal.aop.Aop;
import nexus.io.tio.boot.http.TioRequestContext;
import nexus.io.tio.http.common.HttpRequest;
import nexus.io.tio.http.common.HttpResponse;
import nexus.io.tio.http.server.util.Resps;
import nexus.io.uni.services.ManimTTSService;

public class ManimTTSHandler {
  ManimTTSService manimTTSService = Aop.get(ManimTTSService.class);

  public HttpResponse index(HttpRequest request) {
    HttpResponse response = TioRequestContext.getResponse();
    String input = request.getParam("input");
    String platform = request.getParam("platform");
    String voice_id = request.getParam("voice_id");

    byte[] audio = manimTTSService.tts(input, platform, voice_id);
    return Resps.bytesWithContentType(response, audio, "audio/mp3");
  }
}

说明

  • 参数说明:

    • input:待合成的文本,例如“今天天气怎么样”
    • platform:语音合成平台(如 fishaudio、openai 或 volce)
    • voice_id:指定使用的发音人 ID
  • 执行流程:

    1. 从 HTTP 请求中读取参数。
    2. 调用 ManimTTSService.tts 方法处理逻辑,生成音频数据。
    3. 使用工具方法 Resps.bytesWithContentType 返回设置 audio/mp3 Content-Type 的响应。

2.3 ManimTTSService 服务实现 ​

这是核心业务逻辑代码。服务主要功能包括:

  • 缓存判断:通过输入文本的 MD5 值检查是否已有生成的音频文件缓存(存储在数据库中)。如果缓存存在且文件有效,则直接返回该文件内容,节省调用 TTS 接口时间。
  • 音频生成:若缓存不存在,则根据选择的 TTS 平台调用相应的接口。
    • 如果选择 volce 平台,则调用 VolceTtsClient.tts。
    • 如果选择 fishaudio 平台,则构造 FishAudioTTSRequestVo 对象,并调用 FishAudioClient.speech 生成语音。
    • 其他情况,默认使用 openai 平台调用 OpenAiTTSClient.speech。
  • 缓存写入:生成音频后,将文件保存在本地,并将缓存信息写入数据库中,便于后续复用。
java
package nexus.io.uni.services;

import java.io.File;

import nexus.io.db.activerecord.Db;
import nexus.io.db.activerecord.Row;
import nexus.io.fishaudio.tts.FishAudioClient;
import nexus.io.fishaudio.tts.FishAudioTTSRequestVo;
import nexus.io.model.http.response.ResponseVo;
import nexus.io.openai.tts.OpenAiTTSClient;
import nexus.io.tio.utils.crypto.Md5Utils;
import nexus.io.tio.utils.hutool.FileUtil;
import nexus.io.tio.utils.hutool.StrUtil;
import nexus.io.tio.utils.snowflake.SnowflakeIdUtils;
import nexus.io.uni.consts.TTSPlatform;
import nexus.io.uni.consts.UniTableName;
import nexus.io.volcengine.VolceTtsClient;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ManimTTSService {

  public byte[] tts(String input, String provider, String voice_id) {
    if (StrUtil.isBlank(provider)) {
      provider = "openai";
    }
    if (StrUtil.isBlank(voice_id)) {
      voice_id = "shimmer";
    }
    log.info("input:{},{},{}", input, provider, voice_id);

    String md5 = Md5Utils.getMD5(input);
    String sql = " select id,path from %s where md5=? and provider=? and voice=?";
    sql = String.format(sql, UniTableName.UNI_TTS_CACHE);
    Row row = Db.findFirst(sql, md5, provider, voice_id);

    if (row != null) {
      String path = row.getStr("path");
      Long id = row.getLong("id");
      if (path != null) {
        File file = new File(path);
        if (file.exists()) {
          log.info("read file:{}", path);
          return FileUtil.readBytes(file);
        } else {
          sql = " delete from %s where id=?";
          sql = String.format(sql, UniTableName.UNI_TTS_CACHE);
          Db.delete(sql, id);
        }
      }
    }

    byte[] bodyBytes = null;
    long id = SnowflakeIdUtils.id();
    String path = "cache" + File.separator + id + ".mp3";

    if (TTSPlatform.volce.equals(provider)) {
      bodyBytes = VolceTtsClient.tts(input);

    } else if (TTSPlatform.fishaudio.equals(provider)) {
      // 构造请求对象,并指定参考语音ID(发音人)
      FishAudioTTSRequestVo vo = new FishAudioTTSRequestVo();
      vo.setText(input);
      vo.setReference_id(voice_id);
      // 使用 FishAudioClient 发起请求
      ResponseVo responseVo = FishAudioClient.speech(vo);
      if (responseVo.isOk()) {
        // 处理返回的音频数据,例如保存到文件
        bodyBytes = responseVo.getBodyBytes();
      } else {
        log.error(responseVo.getBodyString());
        path = "default.mp3";
        return FileUtil.readBytes(new File(path));
      }
    } else {
      ResponseVo responseVo = OpenAiTTSClient.speech(input);
      if (responseVo.isOk()) {
        bodyBytes = responseVo.getBodyBytes();
      } else {
        log.error(responseVo.getBodyString());
        path = "default.mp3";
        return FileUtil.readBytes(new File(path));
      }
    }

    File file = new File(path);
    FileUtil.writeBytes(bodyBytes, file);
    Row saveRow = Row.by("id", id).set("input", input).set("md5", md5).set("path", path)
        //
        .set("provider", provider).set("voice", voice_id);
    Db.save(UniTableName.UNI_TTS_CACHE, saveRow);
    return bodyBytes;
  }
}

说明

  • 缓存流程:

    1. 通过 Md5Utils.getMD5(input) 生成输入文本的 MD5 值。
    2. 在数据库表 UNI_TTS_CACHE 中查找是否存在对应记录,若存在则直接读取缓存文件。
    3. 如果缓存文件不存在则删除该记录,防止出现脏数据。
  • TTS 接口调用:

    • 判断 provider 参数,如果为空默认为 openai;如果 voice_id 为空则使用默认值 "shimmer"。
    • 当 provider 为 volce 时,调用 VolceTtsClient.tts(input) 方法。
    • 当 provider 为 fishaudio 时,构造 FishAudioTTSRequestVo 对象,设置 text 和 reference_id,再调用 FishAudioClient.speech(vo) 发起请求;若调用失败则返回默认音频文件。
    • 其他情况默认调用 openai 服务。
  • 文件保存与缓存写入:

    1. 使用雪花算法生成唯一 ID,以此构造唯一文件名存储音频文件(保存于 cache 目录下)。
    2. 将音频写入文件后,记录缓存信息(包括输入、MD5 值、文件路径、provider 和 voice_id)写入数据库,便于下次直接复用。

3. 请求测试说明 ​

完成上述代码整合后,可通过以下接口进行测试:

  • 请求 URL: /api/manim/tts
  • 请求方法:支持 GET 或 POST
  • 请求参数:
    • token: 123456
    • input: 今天天气怎么样
    • platform: fishaudio
    • voice_id: 03397b4c4be74759b72533b663fbd001

测试时,服务将调用 fishaudio 平台的 TTS 接口生成语音,返回音频数据格式为 audio/mp3。如果过程中出现错误(例如 fishaudio 语音合成失败),则返回默认的 default.mp3 文件。


4. 总结 ​

本文档详细说明了如何将 fishaudio 整合到 java-uni-ai-server 中,从常量定义、请求处理、服务层逻辑到缓存机制,所有代码均未省略且做了必要的注释和说明。开发者可以参考此文档了解整合逻辑,并根据需求对代码进行扩展和调整。

该整合方案通过多平台支持和缓存机制,不仅增加了系统的灵活性,还提高了整体性能和用户体验,希望对后续项目开发和维护有所帮助。

MIT Licensed