使用 coqui-tts 部署 tts 服务(文字转语音服务)

序言

项目中需要用到文本转语音(tts)服务。开始时尝试使用 espeak,但效果很不好,中文发音带有外国腔;又尝试用 espeak 生成音素文件,再由 mbrola 合成语音,但尝试许久也没有达到理想效果。只能回到问题的原点,尝试新的方案。经过一番调研,发现有不少基于深度学习的方案,比如 coqui-tts、espnet 等,最终选择了 coqui-tts 方案。

过程

这里记录研究过程,需要最终结果可直接看下一节。

  • 使用”Chinese”搜索了下有没有相关的中文的问题,也是借此看看它支不支持中文。关于中文支持的 issue 有不少,可以确定该开源tts库是支持中文的。其中,从 Does not support mixed pronunciation of Chinese and English 这个issue可以得知中文模型的名称为 tts_models--zh-CN--baker--tacotron2-DDC-GST
  • 看了下 coqui-tts 的README文档,了解基本使用方法。其中文档中说明了一种使用 docker 镜像运行的方式,于是决定使用 docker 镜像将服务跑起来调试调试——使用 docker 镜像是真的很方便,免去了安装各种依赖的麻烦。
  • 执行 docker run --rm -it -p 5002:5002 --entrypoint /bin/bash ghcr.io/coqui-ai/tts-cpu 将服务跑起来。
  • 使用 docker ps 查看容器的id,
  • 使用 docker exec -it <container_id> bash 进入容器。执行 python3 TTS/server/server.py --list_models 查看所有的模型。
  • 使用 tts --text "Text for TTS" --model_name "<model_type>/<language>/<dataset>/<model_name>" --out_path output/path/speech.wav 这个命令测试下文本转语音。这里遇到两个问题:
    1. 语言包需要另外下载!程序运行的时候,对应模型的语言包不存在,会先去 github.com 上下载语言包。但由于网络问题,下载失败,导致程序报错。尝试在命令行终端设置代理或者在容器里面设置代理,但是无效。最终想到的一种解决方法是手动下载语言包(语言包的地址以及安装目录可以在报错信息里面看到)。下载的语言包文件是一个压缩包,这时也不知道放到安装目录是使用什么目录名,于是猜测目录名就是压缩包文件同名。于是重新运行容器,将下载的文件挂载进去,启动容器后进入容器将语言包解压到对应的目录。再次运行测试命令,成功。
    2. 使用tts命令似乎有点慢,转换一次大概需要一分钟。这速度不太符合项目要求。一度是想放弃它了。再次查看文档和issue,发现是有一种 server 常驻方式的服务。尝试启动 server,打开测试的 web 界面,发现速度比 tts 命令快很多!
  • 功能验证完毕,可以开始实现项目的需求了。方案是基于docker部署,单独启动一个tts服务,然后通过 http 请求调用server 的接口,将结果返回给前端。由于只要使用中文,需要写一个Dockerfile,以官方的镜像为基础,安装中文语言包,构建镜像。
  • 最后写一个 docker-compose.yml 文件用来部署服务。写好之后部署,又遇到一个问题,启动的时候总是提示 tts 的参数不对,排查了好久,发现是官方镜像里面使用了 entrypoint,而我的 docker-compose 使用的容器启动命令是使用cmd字段指定的,这样没法覆盖到官方基础镜像里面的 entrypoint。因此,需要在 docker-compose.yml 中将 cmd 字段替换为 entrypoint 字段,再次部署,成功!

结果

  • Dockerfile文件

    文件名: Dockerfile

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    # 官方镜像说明: https://docs.coqui.ai/en/latest/docker_images.html
    # 这里使用CPU版本, 另外还有GPU版本, 参见官方说明
    FROM ghcr.io/coqui-ai/tts-cpu

    # 上面的镜像是基于Debian系统
    # 安装 unzip
    RUN apt-get update && apt-get install -y unzip

    # 复制下载好的中文语言包
    # 虽然程序会自动下载, 但由于网络问题, 这里现将语言包下载好, 拷贝到镜像中指定的位置
    # 语言包下载地址: github.com/coqui-ai/TTS/releases/download/v0.6.1_models/tts_models--zh-CN--baker--tacotron2-DDC-GST.zip
    COPY tts_models--zh-CN--baker--tacotron2-DDC-GST.zip /tmp/tts_models--zh-CN--baker--tacotron2-DDC-GST.zip

    # 解压到: /root/.local/share/tts/,并删除 .zip 文件
    RUN mkdir -p /root/.local/share/tts && \
    unzip /tmp/tts_models--zh-CN--baker--tacotron2-DDC-GST.zip -d /root/.local/share/tts/ && \
    rm /tmp/tts_models--zh-CN--baker--tacotron2-DDC-GST.zip

    # web 服务默认端口为 5002
    # 启动命令, 并指定加载中文语言(移到 docker compose 执行, 因为基础镜像已经有ENTRYPOINT, 在这里执行不能不覆盖到)
    # ENTRYPOINT ["python3", "TTS/server/server.py", "--model_name", "tts_models/zh-CN/baker/tacotron2-DDC-GST"]

  • docker-compose.yml文件

    文件名: docker-compose.yml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    version: '3.8'

    services:
    tts-service:
    image: tts:1.0 # 镜像名
    container_name: tts-container
    ports:
    - "5002:5002" # 映射容器端口5002到主机端口5002
    entrypoint: ["python3", "TTS/server/server.py", "--model_name", "tts_models/zh-CN/baker/tacotron2-DDC-GST"] # 启动Flask服务
    #volumes:
    # - ./tts_models:/root/.local/share/tts # 如果需要挂载本地模型文件,可以取消注释
    environment:
    - PYTHONUNBUFFERED=1 # 确保输出不被缓冲
    restart: unless-stopped # 容器停止后自动重启,除非手动停止

使用

  • 可在 localhost:5002 页面进行文字转语音测试
  • 前端进行对接时,可查看该页面的源代码和文字转语音时触发的接口。页面源代码中可以看到如何调用接口以及将接口返回结果保存并播放。

额外学到的

以前总以为 Dockerfile 中的 ENTRYPOINT 和 CMD 是一样的,直到这次遇到错误。Docker 官方文档也提供了 AI 问答,找到的解释比较满意:

ENTRYPOINT 和 CMD 在 Docker 中的主要区别如下:

  1. 目的

    • ENTRYPOINT:定义容器启动时运行的可执行文件。
    • CMD:为 ENTRYPOINT 提供默认参数,或者在未定义 ENTRYPOINT 时指定完整的运行命令。
  2. 覆盖行为

    • ENTRYPOINT:不易被覆盖。需要在运行容器时使用 --entrypoint 标志。
    • CMD:可以通过在运行容器时提供命令行参数轻松覆盖。
  3. 交互方式

    • 同时定义时,CMD 为 ENTRYPOINT 提供默认参数。
    • 仅定义 CMD 时,CMD 指定完整的运行命令。
  4. 推荐使用场景

    • ENTRYPOINT:适用于设置镜像的主命令,使镜像运行时像执行该命令一样。
    • CMD:用于提供默认参数或在容器中运行临时命令。
  5. 形式
    两者都支持 shell 和 exec 形式,但 ENTRYPOINT 推荐使用 exec 形式:

    1
    2
    ENTRYPOINT ["executable", "param1", "param2"] # exec 形式
    CMD ["param1", "param2"] # exec 形式,作为 ENTRYPOINT 的默认参数
  6. 多条指令

    • Dockerfile 中只有最后一条 ENTRYPOINT 或 CMD 指令会生效。

更多详细信息,可参考以下链接:


使用 coqui-tts 部署 tts 服务(文字转语音服务)
https://blog.ishare.cool/2025/01/11/deploy-coqui-tts/
作者
吴哥
发布于
2025年1月11日
许可协议