跳转至

MaaS_DB_Speech API

1. 接口说明

接口地址为 wss://genaiapi.cloudsway.net/ws/api/v1/tts/ws_binary

2. 身份认证

认证方式使用 Bearer Token,在请求 header 中加上"Authorization": "Bearer {YOUR_ACCESS_KEY}""ModelName":"MaaS_DB_Speech"

3. 请求方式

二进制协议

报文格式(Message format)

Image 字段描述

字段 Field (大小, 单位 bit) 描述 Description 值 Values
协议版本(Protocol version) (4) 可能会在将来使用不同的协议版本,所以这个字段是为了让客户端和服务器在版本上保持一致。 0b0001 - 版本 1 (目前只有版本 1)
报头大小(Header size) (4) header 实际大小是 header size value x 4 bytes. 这里有个特殊值 0b1111 表示 header 大小大于或等于 60(15 x 4 bytes),也就是会存在 header extension 字段。 0b0001 - 报头大小 = 4 (1 x 4)
0b0010 - 报头大小 = 8 (2 x 4)
0b1010 - 报头大小 = 40 (10 x 4)
0b1110 - 报头大小 = 56 (14 x 4)
0b1111 - 报头大小为 60 或更大; 实际大小在 header extension 中定义
消息类型(Message type) (4) 定义消息类型。 0b0001 - full client request.
0b1001- full server response(弃用). 0b1011 - Audio-only server response (ACK).
0b1111 - Error message from server (例如错误的消息类型,不支持的序列化方法等等)
Message type specific flags (4) flags 含义取决于消息类型。 具体内容请看消息类型小节.
序列化方法(Message serialization method) (4) 定义序列化 payload 的方法。 注意:它只对某些特定的消息类型有意义 (例如 Audio-only server response 0b1011 就不需要序列化). 0b0000 - 无序列化 (raw bytes) 0b0001 - JSON
0b1111 - 自定义类型, 在 header extension 中定义
注:目前只支持JSON格式
压缩方法(Message Compression) (4) 定义 payload 的压缩方法。 Payload size 字段不压缩(如果有的话,取决于消息类型),而且 Payload size 指的是 payload 压缩后的大小。 Header 不压缩。 0b0000 - 无压缩
0b0001 - gzip
0b1111 - 自定义压缩方法, 在 header extension 中定义
注:目前只支持无压缩
保留字段(Reserved) (8) 保留字段,同时作为边界 (使整个报头大小为 4 个字节). 0x00 - 目前只有 0

消息类型详细说明

目前所有 TTS websocket 请求都使用 full client request 格式,无论"query"还是"submit"。

Full client request

  • Header size为b0001(即 4B,没有 header extension)。
  • Message type为b0001.
  • Message type specific flags 固定为b0000.
  • Message serialization method为b0001JSON。字段参考上方表格。
  • 如果使用 gzip 压缩 payload,则 payload size 为压缩后的大小。

Audio-only server response

  • Header size 应该为b0001.
  • Message type为b1011.
  • Message type specific flags 可能的值有:
  • b0000 - 没有 sequence number.
  • b0001 - sequence number > 0.
  • b0010orb0011 - sequence number < 0,表示来自服务器的最后一条消息,此时客户端应合并所有音频片段(如果有多条)。
  • Message serialization method为b0000(raw bytes).

4.请求参数

字段 含义 层级 格式 必需 备注
app 应用相关配置 1 dict
appid 应用标识 2 string 需要申请
token 应用令牌 2 string 可传任意非空字符串
cluster 业务集群 2 string volcano_tts
user 用户相关配置 1 dict
uid 用户标识 2 string 可传任意非空字符串,传入值可以通过服务端日志追溯
audio 音频相关配置 1 dict
voice_type 音色类型 2 string
emotion 音色情感 2 string 设置音色的情感。示例:"emotion": "angry" 注:当前仅部分音色支持设置情感,且不同音色支持的情感范围存在不同。
enable_emotion 开启音色情感 2 bool 是否可以设置音色情感,需将enable_emotion设为true 示例:"enable_emotion": True
encoding 音频编码格式 2 string wav / pcm / ogg_opus / mp3,默认为 pcm 注意:wav 不支持流式
speed_ratio 语速 2 float [0.8,2],默认为 1,通常保留一位小数即可
rate 音频采样率 2 int 默认为 24000,可选8000,16000
BitRate 比特率 2 int 可传16 96 128等
explicit_language 明确语种 2 string 仅读指定语种的文本不给定参数,正常中英混crosslingual 启用多语种前端(包含zh/en/ja/es-ms/id/pt-br)zh 中文为主,支持中英混en 仅英文ja 仅日文es-mx 仅墨西id 仅印尼pt-br 仅巴葡
context_language 参考语种 2 string 给模型提供参考的语种不给定 西欧语种采用英语id 西欧语种采用印尼es 西欧语种采用墨西pt 西欧语种采用巴葡
loudness_ratio 音量调节 2 float [0.5,2],默认为1,通常保留一位小数即可。0.5代表原音量0.5倍,2代表原音量2倍
request 请求相关配置 1 dict
reqid 请求标识 2 string 需要保证每次调用传入值唯一,建议使用 UUID
text 文本 2 string 合成语音的文本,长度限制 1024 字节(UTF-8 编码)
text_type 文本类型 2 string 使用 ssml 时需要指定,值为"ssml"
silence_duration 句尾静音 2 float 设置该参数可在句尾增加静音时长,范围0~30000ms。(注:增加的句尾静音主要针对传入文本最后的句尾,而非每句话的句尾)若启用该参数,必须在request下首先设置enable_trailing_silence_audio = true
with_timestamp 时间戳相关 2 int string 传入1时表示启用,将返回TN后文本的时间戳,例如:2025。根据语义,TN后文本为“两千零二十五”或“二零二五”。 注:原文本中的多个标点连用或者空格仍会被处理,但不影响时间戳的连贯性(仅限大模型场景使用)。 附加说明(小模型和大模型时间戳原理差异):小模型依据前端模型生成时间戳,然后合成音频。在处理时间戳时,TN前后文本进行了映射,所以小模型可返回TN前原文本的时间戳,即保留原文中的阿拉伯数字或者特殊符号等。大模型在对传入文本语义理解后合成音频,再针对合成音频进行TN后打轴以输出时间戳。若不采用TN后文本,输出的时间戳将与合成音频无法对齐,所以大模型返回的时间戳对应TN后的文本。
operation 操作 2 string query(非流式,http 只能 query) / submit(流式)
extra_param 附加参数 2 jsonstring
disable_markdown_filter 3 bool 是否开启markdown解析过滤, 为true时,解析并过滤markdown语法,例如,你好,会读为“你好”, 为false时,不解析不过滤,例如,你好,会读为“星星‘你好’星星” 示例:"disable_markdown_filter": True
enable_latex_tn 3 bool 是否可以播报latex公式,需将disable_markdown_filter设为true 示例:"enable_latex_tn": True

备注:

  1. 已支持字级别时间戳能力(ssml文本类型不支持)
  2. 暂时不支持音高调节
  3. 大模型音色语种支持中英混
  4. 大模型非双向流式已支持latex公式
  5. 在 websocket握手成功后,会返回这些 Response header
{
    "user": {
        "uid": "uid123"
    },
    "audio": {
        "voice_type": "zh_male_M392_conversation_wvae_bigtts",
        "encoding": "mp3",
        "speed_ratio": 1.0,
    },
    "request": {
        "reqid": "uuid",
        "text": "我爱中国",
        "operation": "query",
    }
}

5.注意事项

  • websocket 单条链接仅支持单次合成,若需要合成多次,则需要多次建立链接
  • 每次合成时 reqid 这个参数需要重新设置,且要保证唯一性(建议使用 uuid.V4 生成)
  • operation 需要设置为 submit

返回码说明

错误码 错误描述 举例 建议行为
3000 请求正确 正常合成 正常处理
3001 无效的请求 一些参数的值非法,比如 operation 配置错误 检查参数
3003 并发超限 超过在线设置的并发阈值 重试;使用 sdk 的情况下切换离线
3005 后端服务忙 后端服务器负载高 重试;使用 sdk 的情况下切换离线
3006 服务中断 请求已完成/失败之后,相同 reqid 再次请求 检查参数
3010 文本长度超限 单次请求超过设置的文本长度阈值 检查参数
3011 无效文本 参数有误或者文本为空、文本与语种不匹配、文本只含标点 检查参数
3030 处理超时 单次请求超过服务最长时间限制 重试或检查文本
3031 处理错误 后端出现异常 重试;使用 sdk 的情况下切换离线
3032 等待获取音频超时 后端网络异常 重试;使用 sdk 的情况下切换离线
3040 后端链路连接错误 后端网络异常 重试
3050 音色不存在 检查使用的 voice_type 代号 检查参数

6.音色列表

多情感

音色名称 voice_type 时间戳 语种 支持的情感
北京小爷(多情感) zh_male_beijingxiaoye_emo_v2_mars_bigtts 中文 生气,惊讶,恐惧,激动,冷漠,中性对应的传参(emotion)分别为:angry,surprised,fear,excited,coldness,neutral
柔美女友(多情感) zh_female_roumeinvyou_emo_v2_mars_bigtts 中文 开心,悲伤,生气,惊讶,恐惧,厌恶,激动,冷漠,中性对应的传参(emotion)分别为:happy,sad,angry,surprised,fear,hate,excited,coldness,neutral
阳光青年(多情感) zh_male_yangguangqingnian_emo_v2_mars_bigtts 中文 开心,悲伤,生气,恐惧,激动,冷漠,中性对应的传参(emotion)分别为:happy,sad,angry,fear,excited,coldness,neutral
魅力女友(多情感) zh_female_meilinvyou_emo_v2_mars_bigtts 中文 悲伤,恐惧,中性对应的传参(emotion)分别为:sad,fear,neutral
爽快思思(多情感) zh_female_shuangkuaisisi_emo_v2_mars_bigtts 中文、美式英语 开心,悲伤,生气,惊讶,激动,冷漠,中性对应的传参(emotion)分别为:happy,sad,angry,surprised,excited,coldness,neutral

通用场景

音色名称 voice_type 时间戳 语种 支持的情感
灿灿/Shiny zh_female_cancan_mars_bigtts 中文、美式英语
清新女声 zh_female_qingxinnvsheng_mars_bigtts 中文
爽快思思/Skye zh_female_shuangkuaisisi_moon_bigtts 中文、美式英语
温暖阿虎/Alvin zh_male_wennuanahu_moon_bigtts 中文、美式英语
少年梓辛/Brayan zh_male_shaonianzixin_moon_bigtts 中文、美式英语
知性女声 zh_female_zhixingnvsheng_mars_bigtts 中文
清爽男大 zh_male_qingshuangnanda_mars_bigtts 中文
邻家女孩 zh_female_linjianvhai_moon_bigtts 中文
渊博小叔 zh_male_yuanboxiaoshu_moon_bigtts 中文
阳光青年 zh_male_yangguangqingnian_moon_bigtts 中文
甜美小源 zh_female_tianmeixiaoyuan_moon_bigtts 中文
清澈梓梓 zh_female_qingchezizi_moon_bigtts 中文
解说小明 zh_male_jieshuoxiaoming_moon_bigtts 中文
开朗姐姐 zh_female_kailangjiejie_moon_bigtts 中文
邻家男孩 zh_male_linjiananhai_moon_bigtts 中文
甜美悦悦 zh_female_tianmeiyueyue_moon_bigtts 中文
心灵鸡汤 zh_female_xinlingjitang_moon_bigtts 中文
知性温婉 ICL_zh_female_zhixingwenwan_tob 中文
暖心体贴 ICL_zh_male_nuanxintitie_tob 中文
温柔文雅 ICL_zh_female_wenrouwenya_tob 中文
开朗轻快 ICL_zh_male_kailangqingkuai_tob 中文
活泼爽朗 ICL_zh_male_huoposhuanglang_tob 中文
率真小伙 ICL_zh_male_shuaizhenxiaohuo_tob 中文
温柔小哥 zh_male_wenrouxiaoge_mars_bigtts 中文
Smith en_male_smith_mars_bigtts 英式英语
Anna en_female_anna_mars_bigtts 英式英语
Adam en_male_adam_mars_bigtts 美式英语
Sarah en_female_sarah_mars_bigtts 澳洲英语
Dryw en_male_dryw_mars_bigtts 澳洲英语

多语种

音色名称 voice_type 时间戳 语种 支持的情感
かずね(和音)/Javier or Álvaro multi_male_jingqiangkanye_moon_bigtts 日语、西语
はるこ(晴子)/Esmeralda multi_female_shuangkuaisisi_moon_bigtts 日语、西语
ひろし(広志)/Roberto multi_male_wanqudashu_moon_bigtts 日语、西语
あけみ(朱美) multi_female_gaolengyujie_moon_bigtts 日语
Amanda en_female_amanda_mars_bigtts 美式英语
Jackson en_male_jackson_mars_bigtts 美式英语

趣味口音

音色名称 voice_type 时间戳 语种 支持的情感
京腔侃爷/Harmony zh_male_jingqiangkanye_moon_bigtts 中文-北京口音、英文
湾湾小何 zh_female_wanwanxiaohe_moon_bigtts 中文-台湾口音
湾区大叔 zh_female_wanqudashu_moon_bigtts 中文-广东口音
呆萌川妹 zh_female_daimengchuanmei_moon_bigtts 中文-四川口音
广州德哥 zh_male_guozhoudege_moon_bigtts 中文-广东口音
北京小爷 zh_male_beijingxiaoye_moon_bigtts 中文-北京口音
浩宇小哥 zh_male_haoyuxiaoge_moon_bigtts 中文-青岛口音
广西远舟 zh_male_guangxiyuanzhou_moon_bigtts 中文-广西口音
妹坨洁儿 zh_female_meituojieer_moon_bigtts 中文-长沙口音
豫州子轩 zh_male_yuzhouzixuan_moon_bigtts 中文-河南口音

角色扮演

音色名称 voice_type 时间戳 语种 支持的情感
奶气萌娃 zh_male_naiqimengwa_mars_bigtts 中文
婆婆 zh_female_popo_mars_bigtts 中文
高冷御姐 zh_female_gaolengyujie_moon_bigtts 中文
傲娇霸总 zh_male_aojiaobazong_moon_bigtts 中文
魅力女友 zh_female_meilinvyou_moon_bigtts 中文
深夜播客 zh_male_shenyeboke_moon_bigtts 中文
柔美女友 zh_female_sajiaonvyou_moon_bigtts 中文
撒娇学妹 zh_female_yuanqinvyou_moon_bigtts 中文
病弱少女 ICL_zh_female_bingruoshaonv_tob 中文
活泼女孩 ICL_zh_female_huoponvhai_tob 中文
东方浩然 zh_male_dongfanghaoran_moon_bigtts 中文
绿茶小哥 ICL_zh_male_lvchaxiaoge_tob 中文
娇弱萝莉 ICL_zh_female_jiaoruoluoli_tob 中文
冷淡疏离 ICL_zh_male_lengdanshuli_tob 中文
憨厚敦实 ICL_zh_male_hanhoudunshi_tob 中文
傲气凌人 ICL_zh_male_aiqilingren_tob 中文
活泼刁蛮 ICL_zh_female_huopodiaoman_tob 中文
固执病娇 ICL_zh_male_guzhibingjiao_tob 中文
撒娇粘人 ICL_zh_male_sajiaonianren_tob 中文
傲慢娇声 ICL_zh_female_aomanjiaosheng_tob 中文
潇洒随性 ICL_zh_male_xiaosasuixing_tob 中文
腹黑公子 ICL_zh_male_fuheigongzi_tob 中文
诡异神秘 ICL_zh_male_guiyishenmi_tob 中文
儒雅才俊 ICL_zh_male_ruyacaijun_tob 中文
病娇白莲 ICL_zh_male_bingjiaobailian_tob 中文
正直青年 ICL_zh_male_zhengzhiqingnian_tob 中文
娇憨女王 ICL_zh_female_jiaohannvwang_tob 中文
病娇萌妹 ICL_zh_female_bingjiaomengmei_tob 中文
青涩小生 ICL_zh_male_qingsenaigou_tob 中文
纯真学弟 ICL_zh_male_chunzhenxuedi_tob 中文
暖心学姐 ICL_zh_female_nuanxinxuejie_tob 中文
可爱女生 ICL_zh_female_keainvsheng_tob 中文
成熟姐姐 ICL_zh_female_chengshujiejie_tob 中文
病娇姐姐 ICL_zh_female_bingjiaojiejie_tob 中文
优柔帮主 ICL_zh_male_youroubangzhu_tob 中文
优柔公子 ICL_zh_male_yourougongzi_tob 中文
妩媚御姐 ICL_zh_female_wumeiyujie_tob 中文
调皮公主 ICL_zh_female_tiaopigongzhu_tob 中文
傲娇女友 ICL_zh_female_aojiaonvyou_tob 中文
贴心男友 ICL_zh_male_tiexinnanyou_tob 中文
少年将军 ICL_zh_male_shaonianjiangjun_tob 中文
贴心女友 ICL_zh_female_tiexinnvyou_tob 中文
病娇哥哥 ICL_zh_male_bingjiaogege_tob 中文
学霸男同桌 ICL_zh_male_xuebanantongzhuo_tob 中文
幽默叔叔 ICL_zh_male_youmoshushu_tob 中文
性感御姐 ICL_zh_female_xingganyujie_tob 中文
假小子 ICL_zh_female_jiaxiaozi_tob 中文
冷峻上司 ICL_zh_male_lengjunshangsi_tob 中文
温柔男同桌 ICL_zh_male_wenrounantongzhuo_tob 中文
病娇弟弟 ICL_zh_male_bingjiaodidi_tob 中文
幽默大爷 ICL_zh_male_youmodaye_tob 中文
傲慢少爷 ICL_zh_male_aomanshaoye_tob 中文
神秘法师 ICL_zh_male_shenmifashi_tob 中文

视频配音

音色名称 voice_type 时间戳 语种 支持的情感
和蔼奶奶 ICL_zh_female_heainainai_tob 中文
邻居阿姨 ICL_zh_female_linjuayi_tob 中文
温柔小雅 zh_female_wenrouxiaoya_moon_bigtts 中文
天才童声 zh_male_tiancaitongsheng_mars_bigtts 中文
猴哥 zh_male_sunwukong_mars_bigtts 中文
熊二 zh_male_xionger_mars_bigtts 中文
佩奇猪 zh_female_peiqi_mars_bigtts 中文
武则天 zh_female_wuzetian_mars_bigtts 中文
顾姐 zh_female_gujie_mars_bigtts 中文
樱桃丸子 zh_female_yingtaowanzi_mars_bigtts 中文
广告解说 zh_male_chunhui_mars_bigtts 中文
少儿故事 zh_female_shaoergushi_mars_bigtts 中文
四郎 zh_male_silang_mars_bigtts 中文
磁性解说男声/Morgan zh_male_jieshuonansheng_mars_bigtts 中文、美式英语
鸡汤妹妹/Hope zh_female_jitangmeimei_mars_bigtts 中文、美式英语
贴心女声/Candy zh_female_tiexinnvsheng_mars_bigtts 中文、美式英语
俏皮女声 zh_female_qiaopinvsheng_mars_bigtts 中文
萌丫头/Cutey zh_female_mengyatou_mars_bigtts 中文、美式英语
懒音绵宝 zh_male_lanxiaoyang_mars_bigtts 中文
亮嗓萌仔 zh_male_dongmanhaimian_mars_bigtts 中文

有声阅读

音色名称 voice_type 时间戳 语种 支持的情感
悬疑解说 zh_male_changtianyi_mars_bigtts 中文
儒雅青年 zh_male_ruyaqingnian_mars_bigtts 中文
霸气青叔 zh_male_baqiqingshu_mars_bigtts 中文
擎苍 zh_male_qingcang_mars_bigtts 中文
活力小哥 zh_male_yangguangqingnian_mars_bigtts 中文
古风少御 zh_female_gufengshaoyu_mars_bigtts 中文
温柔淑女 zh_female_wenroushunv_mars_bigtts 中文
反卷青年 zh_male_fanjuanqingnian_mars_bigtts 中文

注:

  1. 上述中文音色可支持中英文混合场景。
  2. 病弱少女、 活泼女孩、和蔼奶奶、邻居阿姨四个音色暂不支持双向流式接口调用。

7.示例代码

package org.example.websocket.tts.demo;

import com.alibaba.fastjson.annotation.JSONField;
import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class TtsRequest {
    @JSONField(name = "app")
    private App app;
    @JSONField(name = "user")
    private User user;
    @JSONField(name = "audio")
    private Audio audio;
    @JSONField(name = "request")
    private Request request;

    @Data
    @Builder
    public static class App {
        @JSONField(name = "appid")
        private String appid;
        @JSONField(name = "token")
        private String token; // 目前未生效,使用默认值即可
        @JSONField(name = "cluster")
        private String cluster;
    }

    @Data
    @Builder
    public static class User {
        @JSONField(name = "uid")
        private String uid;
    }

    @Data
    @Builder
    public static class Audio {
        @JSONField(name = "voice_type")
        private String voiceType;
        @JSONField(name = "emotion")
        private String emotion;
        @JSONField(name = "enable_emotion")
        private Boolean enableEmotion;
        @JSONField(name = "encoding")
        private String encoding;
        @JSONField(name = "speed_ratio")
        private Double speedRatio;
        @JSONField(name = "rate")
        private Integer rate;
        @JSONField(name = "BitRate")
        private Integer BitRate;
        @JSONField(name = "explicit_language")
        private String explicitLanguage;
        @JSONField(name = "context_language")
        private String contextLanguage;
        @JSONField(name = "loudness_ratio")
        private Double loudnessRatio;
        @JSONField(name = "volume_ratio")
        private Double volumeRatio;
        @JSONField(name = "voice")
        private String voice;
        @JSONField(name = "pitch_ratio")
        private Double pitchRatio;
        @JSONField(name = "language")
        private String language;
    }

    @Data
    @Builder
    public static class Request {
        @JSONField(name = "reqid")
        private String reqid;
        @JSONField(name = "text")
        private String text;
        @JSONField(name = "text_type")
        private String textType;
        @JSONField(name = "silence_duration")
        private Double silenceDuration;
        @JSONField(name = "with_timestamp")
        private Integer withTimestamp;
        @JSONField(name = "operation")
        private String operation;
        @JSONField(name = "extra_param")
        private String extraParam;

    }
}
package org.example.websocket.tts;

import com.alibaba.fastjson.JSON;
import lombok.Getter;
import org.example.websocket.tts.demo.TtsRequest;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.framing.CloseFrame;
import org.java_websocket.handshake.ServerHandshake;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.math.BigInteger;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public class TtsWebsocketDemo {
    private static final Logger log = LoggerFactory.getLogger(TtsWebsocketDemo.class);
    public static final String API_URL = "";

    public static void main(String[] args) throws Exception {
        String accessToken = "";

        TtsRequest ttsRequest;

        ttsRequest = TtsRequest.builder()
                .user(TtsRequest.User.builder()
                        .uid("dadadadwd1e12121rtdawq")
                        .build())
                .audio(TtsRequest.Audio.builder()
                        .encoding("mp3")
                        .voiceType("zh_female_qingchezizi_moon_bigtts")
                        .language("en")
                        .build())
                .request(TtsRequest.Request.builder()
                        .reqid(UUID.randomUUID().toString())
                        .operation("query")
                        .text("我爱中国")
                        .build())
                .build();

        String json = JSON.toJSONString(ttsRequest);

        log.info("request: {}", json);
        HashMap<String, String> authorization = new HashMap<>();
        authorization.put("Authorization", "Bearer " + accessToken);
        authorization.put("ModelName","MaaS_DB_Speech");
        TtsWebsocketClient ttsWebsocketClient = new TtsWebsocketClient(authorization);
        byte[] audio = ttsWebsocketClient.submit(ttsRequest);
        FileOutputStream fos = new FileOutputStream("test4.mp3");
        fos.write(audio);
        fos.close();
        log.info("TTS done.");
    }



    public static class TtsWebsocketClient extends WebSocketClient {
        private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();

        public TtsWebsocketClient(Map<String, String> authorization) {
            super(URI.create(API_URL), authorization);
//            super(URI.create(API_URL));
        }

        public byte[] submit(TtsRequest ttsRequest) throws InterruptedException {
            String json = JSON.toJSONString(ttsRequest);
            log.info("request: {}", json);
            byte[] jsonBytes = json.getBytes(StandardCharsets.UTF_8);
            byte[] header = {0x11, 0x10, 0x10, 0x00};
            ByteBuffer requestByte = ByteBuffer.allocate(8 + jsonBytes.length);
            requestByte.put(header).putInt(jsonBytes.length).put(jsonBytes);

            this.connectBlocking();
            synchronized (this) {
                this.send(requestByte.array());
                wait();
                return this.buffer.toByteArray();
            }
        }

        @Override
        public void onMessage(ByteBuffer bytes) {
            log.info("received message:" + bytes.remaining() + " bytes");
            int protocolVersion = (bytes.get(0) & 0xff) >> 4;
            int headerSize = bytes.get(0) & 0x0f;
            int messageType = (bytes.get(1) & 0xff) >> 4;
            int messageTypeSpecificFlags = bytes.get(1) & 0x0f;
            int serializationMethod = (bytes.get(2) & 0xff) >> 4;
            int messageCompression = bytes.get(2) & 0x0f;
            int reserved = bytes.get(3) & 0xff;
            bytes.position(headerSize * 4);
            byte[] fourByte = new byte[4];
            if (messageType == 11) {
                // Audio-only server response
                log.info("received audio-only response.");
                if (messageTypeSpecificFlags == 0) {
                    // Ack without audio data
                } else {
                    bytes.get(fourByte, 0, 4);
                    int sequenceNumber = new BigInteger(fourByte).intValue();
                    bytes.get(fourByte, 0, 4);
                    int payloadSize = new BigInteger(fourByte).intValue();
                    byte[] payload = new byte[payloadSize];
                    bytes.get(payload, 0, payloadSize);
                    try {
                        this.buffer.write(payload);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                    if (sequenceNumber < 0) {
                        // received the last segment
                        this.close(CloseFrame.NORMAL, "received all audio data.");
                    }
                }
            } else if (messageType == 15) {
                // Error message from server
                bytes.get(fourByte, 0, 4);
                int code = new BigInteger(fourByte).intValue();
                bytes.get(fourByte, 0, 4);
                int messageSize = new BigInteger(fourByte).intValue();
                byte[] messageBytes = new byte[messageSize];
                bytes.get(messageBytes, 0, messageSize);
                String message = new String(messageBytes, StandardCharsets.UTF_8);
                throw new TtsException(code, message);
            } else {
                log.warn("Received unknown response message type: {}", messageType);
            }
        }

        @Override
        public void onOpen(ServerHandshake serverHandshake) {
            log.info("opened connection");
        }

        @Override
        public void onMessage(String message) {
            log.info("received message: " + message);
        }

        @Override
        public void onClose(int code, String reason, boolean remote) {
            log.info("Connection closed by {}, Code: {}, Reason: {}", (remote ? "remote" : "us"), code, reason);
            synchronized (this) {
                notify();
            }
        }

        @Override
        public void onError(Exception e) {
            close(CloseFrame.NORMAL, e.toString());
        }
    }

    @Getter
    public static class TtsException extends RuntimeException {
        private final int code;
        private final String message;

        public TtsException(int code, String message) {
            super("code=" + code + ", message=" + message);
            this.code = code;
            this.message = message;
        }
    }
}