火山引擎TTS使用体验

文章目录

  • 前言
  • [1. 简介](#1. 简介)
    • [1.1 能力体验](#1.1 能力体验)
    • [1.2 功能特性](#1.2 功能特性)
    • [1.3 音色列表](#1.3 音色列表)
    • [1.4 收费情况](#1.4 收费情况)
  • [2. 开启服务](#2. 开启服务)
    • [2.1 创建应用](#2.1 创建应用)
    • [2.3 使用服务介绍](#2.3 使用服务介绍)
  • 3.Websocket接入演示
    • [3.1 编写demo](#3.1 编写demo)
    • [3.2 代码解释](#3.2 代码解释)
    • 3.4运行demo
  • [4. 参考链接](#4. 参考链接)

前言

语音合成TTS(text to Speech)是我觉得后续开发产品所不可或缺的一个功能,因为相比较于过去的GUI 图形+文字展示,动态形象+语音会更利用人与设备之间的交互。

另外语音沟通更加灵活,因为过去的GUI图形界面都是预先设计好的,就像APP内的界面。这种更加标准但是不够灵活,不能满足所有人的需求和爱好。所以我觉得现在了解下TTS也是非常有必要的。

之前看到小智AI中有提到支持了cosyVoice(阿里的TTS模型)与火山引擎的TTS。然后搜索了两者的区别发现火山引擎的TTS在高拟真克隆这块做的比较好。这样的话就能够使用该TTS生成各种符合产品形象的声音。所以暂时先选择了火山引擎的TTS去研究一下。

1. 简介

火山引擎的TTS也叫做豆包语音合成大模型,它是依托新一代大模型能力,豆包语音合成模型能够根据上下文智能预测文本的情绪、语调等信息,并生成超自然、高保真、个性化的语音,以满足不同用户的个性化需求。

简介中很多的内容都是来自于火山引擎的文档中心,我这里就简单的介绍下,大家需要详细了解的可以到以下地址去看看:

https://www.volcengine.com/docs/6561/1257544

1.1 能力体验

在官方的网站中有个能力体验的页面,这个体验很简单 输入想要描述的文字,然后选择配音和一些声音相关的配置就能生成语音了。

我们最终要实现的功能也是类似的,只是这个是人家已经实现好的功能比较固化,我们想要更灵活一些,所以需要通过代码去使用这个模型来做一些定制化的开发。

1.2 功能特性

下面这个表里面说了很多,说实话有一些对于我们这些刚接触的人来说并没有什么概念,例如这里我也只是对部署方案比较感兴趣。这里后续有需要的时候大家可以去官方介绍文档中去查看。

1.3 音色列表

使用克隆语音需要用到另外一个模型,不是我们本次所使用的"语音合成大模型",所以我们并不是说想用什么声音就用什么声音,而是要使用官方给出的声音列表。不过好在可选择性还是很多的。

这里截图不全,更详细的内容可以查看官方文档

1.4 收费情况

我最初以为TTS里面包含了语音复制,什么短文本语音合成啥的呢,结果一看乖乖嘞被分成了4个而且是收费的。

但是我们第一次用的话是免费的,会赠送一定的使用额度,所以大家不要太过于担心。

2. 开启服务

我们需要申请appid、token、secret_key等用来开启和使用TTS的服务。

这个就类似于从豆包那里申请个账号,这个账号里面包含了我们的身份信息,以及能够使用哪些模型还有我们的剩余额度,有了这些信息后我们才能真正的去使用大模型语音合成功能。

2.1 创建应用

先根据下方的快速入门创建账号:

https://www.volcengine.com/docs/6561/163043

点击"创建应用"来新增应用,填入应用名称、简介和所需接入的能力服务

我们第一次使用会有个免费额度,所以大家不用太担心。

创建成功后,能够在应用管理界面看到我们所创建的应用

获取token和Secret_key信息

在控制台界面,我们能够找到属于我们的Access Token 和 Secret Key,有了这些信息我们才能去使用该服务。

2.3 使用服务介绍

使用的话有两种方式,分别是API和SDK接入。

SDK的话目前它只能运行在安卓和IOS操作系统上,应该是集成到APP中,这不方便我们去进行体验。而且像小智AI这种也是采用的API方式接入的,所以这里我们也使用API的方式。

API接入又分为WebSocket还有Http,基本上工作原理大差不差,都是发送请求,然后接收响应处理。这里我们就以WebSocket为主。

3.Websocket接入演示

Websocket接入演示的功能,需要使用账号申请部分申请到的 appid和access_token进行调用文本一次性送入,后端边合成边返回音频数据。所以大家一定要先按照上面的步骤获取对应的token和appid等信息。

接口说明地址为下方的链接,详细的使用方法大概可以进入该链接查看:

wss://openspeech.bytedance.com/api/v1/tts/ws_binary

3.1 编写demo

文档中心有个demo,我们拿下来直接运行即可,本次演示的代码来源是tts_websocket_demo.py

源码如下:

python 复制代码
#coding=utf-8

'''
requires Python 3.6 or later

pip install asyncio
pip install websockets

'''

import asyncio
import websockets
import uuid
import json
import gzip
import copy

MESSAGE_TYPES = {11: "audio-only server response", 12: "frontend server response", 15: "error message from server"}
MESSAGE_TYPE_SPECIFIC_FLAGS = {0: "no sequence number", 1: "sequence number > 0",
                               2: "last message from server (seq < 0)", 3: "sequence number < 0"}
MESSAGE_SERIALIZATION_METHODS = {0: "no serialization", 1: "JSON", 15: "custom type"}
MESSAGE_COMPRESSIONS = {0: "no compression", 1: "gzip", 15: "custom compression method"}

appid = "xxx"
token = "xxx"
cluster = "xxx"
voice_type = "xxx"
host = "openspeech.bytedance.com"
api_url = f"wss://{host}/api/v1/tts/ws_binary"

# version: b0001 (4 bits)
# header size: b0001 (4 bits)
# message type: b0001 (Full client request) (4bits)
# message type specific flags: b0000 (none) (4bits)
# message serialization method: b0001 (JSON) (4 bits)
# message compression: b0001 (gzip) (4bits)
# reserved data: 0x00 (1 byte)
default_header = bytearray(b'\x11\x10\x11\x00')

request_json = {
    "app": {
        "appid": appid,
        "token": "access_token",
        "cluster": cluster
    },
    "user": {
        "uid": "388808087185088"
    },
    "audio": {
        "voice_type": "xxx",
        "encoding": "mp3",
        "speed_ratio": 1.0,
        "volume_ratio": 1.0,
        "pitch_ratio": 1.0,
    },
    "request": {
        "reqid": "xxx",
        "text": "字节跳动语音合成。",
        "text_type": "plain",
        "operation": "xxx"
    }
}


async def test_submit():
    submit_request_json = copy.deepcopy(request_json)
    submit_request_json["audio"]["voice_type"] = voice_type
    submit_request_json["request"]["reqid"] = str(uuid.uuid4())
    submit_request_json["request"]["operation"] = "submit"
    payload_bytes = str.encode(json.dumps(submit_request_json))
    payload_bytes = gzip.compress(payload_bytes)  # if no compression, comment this line
    full_client_request = bytearray(default_header)
    full_client_request.extend((len(payload_bytes)).to_bytes(4, 'big'))  # payload size(4 bytes)
    full_client_request.extend(payload_bytes)  # payload
    print("\n------------------------ test 'submit' -------------------------")
    print("request json: ", submit_request_json)
    print("\nrequest bytes: ", full_client_request)
    file_to_save = open("test_submit.mp3", "wb")
    header = {"Authorization": f"Bearer; {token}"}
    async with websockets.connect(api_url, extra_headers=header, ping_interval=None) as ws:
        await ws.send(full_client_request)
        while True:
            res = await ws.recv()
            done = parse_response(res, file_to_save)
            if done:
                file_to_save.close()
                break
        print("\nclosing the connection...")


async def test_query():
    query_request_json = copy.deepcopy(request_json)
    query_request_json["audio"]["voice_type"] = voice_type
    query_request_json["request"]["reqid"] = str(uuid.uuid4())
    query_request_json["request"]["operation"] = "query"
    payload_bytes = str.encode(json.dumps(query_request_json))
    payload_bytes = gzip.compress(payload_bytes)  # if no compression, comment this line
    full_client_request = bytearray(default_header)
    full_client_request.extend((len(payload_bytes)).to_bytes(4, 'big'))  # payload size(4 bytes)
    full_client_request.extend(payload_bytes)  # payload
    print("\n------------------------ test 'query' -------------------------")
    print("request json: ", query_request_json)
    print("\nrequest bytes: ", full_client_request)
    file_to_save = open("test_query.mp3", "wb")
    header = {"Authorization": f"Bearer; {token}"}
    async with websockets.connect(api_url, extra_headers=header, ping_interval=None) as ws:
        await ws.send(full_client_request)
        res = await ws.recv()
        parse_response(res, file_to_save)
        file_to_save.close()
        print("\nclosing the connection...")


def parse_response(res, file):
    print("--------------------------- response ---------------------------")
    # print(f"response raw bytes: {res}")
    protocol_version = res[0] >> 4
    header_size = res[0] & 0x0f
    message_type = res[1] >> 4
    message_type_specific_flags = res[1] & 0x0f
    serialization_method = res[2] >> 4
    message_compression = res[2] & 0x0f
    reserved = res[3]
    header_extensions = res[4:header_size*4]
    payload = res[header_size*4:]
    print(f"            Protocol version: {protocol_version:#x} - version {protocol_version}")
    print(f"                 Header size: {header_size:#x} - {header_size * 4} bytes ")
    print(f"                Message type: {message_type:#x} - {MESSAGE_TYPES[message_type]}")
    print(f" Message type specific flags: {message_type_specific_flags:#x} - {MESSAGE_TYPE_SPECIFIC_FLAGS[message_type_specific_flags]}")
    print(f"Message serialization method: {serialization_method:#x} - {MESSAGE_SERIALIZATION_METHODS[serialization_method]}")
    print(f"         Message compression: {message_compression:#x} - {MESSAGE_COMPRESSIONS[message_compression]}")
    print(f"                    Reserved: {reserved:#04x}")
    if header_size != 1:
        print(f"           Header extensions: {header_extensions}")
    if message_type == 0xb:  # audio-only server response
        if message_type_specific_flags == 0:  # no sequence number as ACK
            print("                Payload size: 0")
            return False
        else:
            sequence_number = int.from_bytes(payload[:4], "big", signed=True)
            payload_size = int.from_bytes(payload[4:8], "big", signed=False)
            payload = payload[8:]
            print(f"             Sequence number: {sequence_number}")
            print(f"                Payload size: {payload_size} bytes")
        file.write(payload)
        if sequence_number < 0:
            return True
        else:
            return False
    elif message_type == 0xf:
        code = int.from_bytes(payload[:4], "big", signed=False)
        msg_size = int.from_bytes(payload[4:8], "big", signed=False)
        error_msg = payload[8:]
        if message_compression == 1:
            error_msg = gzip.decompress(error_msg)
        error_msg = str(error_msg, "utf-8")
        print(f"          Error message code: {code}")
        print(f"          Error message size: {msg_size} bytes")
        print(f"               Error message: {error_msg}")
        return True
    elif message_type == 0xc:
        msg_size = int.from_bytes(payload[:4], "big", signed=False)
        payload = payload[4:]
        if message_compression == 1:
            payload = gzip.decompress(payload)
        print(f"            Frontend message: {payload}")
    else:
        print("undefined message type!")
        return True


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(test_submit())
    loop.run_until_complete(test_query())

填写token等信息

在运行demo之前,我们需要在下方的这里填写上之前在火山引擎处申请到的信息,这里大家就理解为自己的账号密码就行了

appid还有token还有cluster在我们的应用空间那里就能看到,然后有个比较特殊的voice_type需要找到文档中的音色列表去那里找自己想要合成的声音类型。

3.2 代码解释

导入各基本模块

python 复制代码
import asyncio
import websockets
import uuid
import json
import gzip
import copy

这里的asyncio 是python中的用于实现并发的模块,里面提供了例如协程、协程锁等各种用于异步通信的功能。其它的就不用说了看名字就知道干啥的了。

基础配置填写

python 复制代码
appid = "xxx"
token = "xxx"
cluster = "xxx"
voice_type = "xxx"
host = "openspeech.bytedance.com"
api_url = f"wss://{host}/api/v1/tts/ws_binary"

这个就是前面我们提到的把应用空间了申请到的信息填写上去

请求体

python 复制代码
# version: b0001 (4 bits)
# header size: b0001 (4 bits)
# message type: b0001 (Full client request) (4bits)
# message type specific flags: b0000 (none) (4bits)
# message serialization method: b0001 (JSON) (4 bits)
# message compression: b0001 (gzip) (4bits)
# reserved data: 0x00 (1 byte)
default_header = bytearray(b'\x11\x10\x11\x00')

request_json = {
    "app": {
        "appid": appid,
        "token": "access_token",
        "cluster": cluster
    },
    "user": {
        "uid": "388808087185088"
    },
    "audio": {
        "voice_type": "xxx",
        "encoding": "mp3",
        "speed_ratio": 1.0,
        "volume_ratio": 1.0,
        "pitch_ratio": 1.0,
    },
    "request": {
        "reqid": "xxx",
        "text": "字节跳动语音合成。",
        "text_type": "plain",
        "operation": "xxx"
    }
}

header是发送请求时的消息头,tts的通讯协议要求二进制的方式进行传输,所以头这里也是采用的二进制。上面的注释代表的是其二进制代表的内容。

request_json是我们的请求体,里面需要填充我们要发送的具体信息,后续发送时也会将其转换为二进制发送,请求体中的参数主要就是这几个

可以通过下方的链接去查询

https://www.volcengine.com/docs/6561/1257584

提交转换请求

python 复制代码
请求
async def test_submit():

功能

  • 向字节跳动的语音合成WebSocket API提交一个文本合成请求

主要操作:

  • 准备提交请求的JSON数据,包括appid、token、cluster等认证信息
  • 设置操作类型为"submit"(提交)
  • 生成唯一的请求ID
  • 使用gzip压缩请求数据
  • 建立WebSocket连接并发送请求
  • 持续接收服务器返回的音频数据流,保存到test_submit.mp3文件
  • 处理完所有音频数据后关闭连接
python 复制代码
查询
async def test_query():

功能

  • 向字节跳动的语音合成WebSocket API发送查询请求

主要操作:

  • 准备查询请求的JSON数据,结构与submit类似
  • 设置操作类型为"query"(查询)
  • 生成唯一的请求ID
  • 使用gzip压缩请求数据
  • 建立WebSocket连接并发送请求
  • 接收服务器响应(通常是一次性返回)
  • 将响应数据保存到test_query.mp3文件
  • 关闭连接

查询与请求的主要区别:

  • test_submit()用于提交语音合成任务并持续接收音频流
  • test_query()用于查询状态或结果,通常只接收一次响应
  • test_submit()会处理多个响应消息直到完成
  • test_query()通常只处理单个响应消息

处理接收到的响应

python 复制代码
处理响应
def parse_response(res, file):
  1. 解析响应头部信息
  • 协议版本(protocol_version)

  • 头部大小(header_size)

  • 消息类型(message_type)

  • 消息特定标志(message_type_specific_flags)

  • 序列化方法(serialization_method)

  • 压缩方法(message_compression)

  • 保留字段(reserved)

  1. 处理不同类型的服务器响应:

错误消息响应(message_type=0xf):

  • 解析错误代码(code)
  • 解析错误消息大小(msg_size)
  • 解压缩并显示错误内容

前端消息响应(message_type=0xc):

  • 解析消息大小(msg_size)
  • 解压缩并显示前端消息

3.4运行demo

注意下载下来的demo好像名称中有个空格,大家注意修改下名称。

执行指令

python 复制代码
python tts_websocket_demo.py

执行结果

通过打印日志能够,模型服务返回了对应的响应数据(音频的原始数据)

然后我们就能看到我们的文件夹多了两个mp3的文件,分别是通过请求得到的和通过查询得到的。

听了下是熊二说的"字节跳动语音合成",这里我设置的语音类型也是熊二的。

此时再去查看我们的模型使用情况,会发现少了一定的额度。

4. 参考链接

豆包语音合成大模型官网

语音技术开发参考 - 豆包官方的

相关推荐
中國龍在廣州7 分钟前
ChatGPT上瘾,大脑萎缩47%!?
人工智能·科技·机器学习·chatgpt·机器人
新加坡内哥谈技术9 分钟前
OpenAI 公布《走向理解与预防失准泛化:由“角色特征”驱动的突现性失准》研究总结
人工智能
云空27 分钟前
《Whisper :说明书 》
人工智能·深度学习·神经网络·语音识别·机器翻译
小白狮ww1 小时前
VASP 教程:VASP 机器学习力场计算硅的声子谱
人工智能·深度学习·机器学习·大模型·vasp··声子谱
乐鑫科技 Espressif1 小时前
EchoEar(喵伴):乐鑫发布与火山引擎扣子联名 AI 智能体开发板
人工智能·物联网·火山引擎·乐鑫科技
阿里云大数据AI技术1 小时前
PAI推理重磅发布模型权重服务,大幅降低冷启动与扩容时长
大数据·人工智能·llm
苏苏susuus1 小时前
深度学习:人工神经网络基础概念
人工智能·深度学习
Blossom.1181 小时前
基于深度学习的智能图像去雾技术:技术与实践
运维·服务器·人工智能·深度学习·机器学习·sklearn·智能电视
从零开始学习人工智能1 小时前
深入探索 OpenCV 图像识别:从基础到深度学习
人工智能·深度学习·opencv
背太阳的牧羊人1 小时前
BitsAndBytes(简称 BnB)是一个用于“压缩”大语言模型的工具包
人工智能·语言模型·自然语言处理