logo
电话400 898 2016
预约试用
开放平台
En
尊敬的开发者,您好! 标贝科技的开发文档已更换新的地址,旧版文档地址将会在2023年12月31日下线, 给您带来不便,敬请谅解! 立即查看
产品简介 产品简介
基本概念 基本概念
平台新手指引 平台新手指引
计价模式 计价模式
开发者文档下拉
开放平台计价
定制服务计价
获取访问令牌 获取访问令牌
语音合成 语音合成
开发者文档下拉
接口说明
发音人列表
短文本合成 开发者文档下拉
Android SDK
iOS SDK
C++(Linux) SDK
RESTful API
Websocket API
长文本合成 开发者文档下拉
长文本合成RESTful API
长文本合成Websocket API
离线合成 开发者文档下拉
模型文件说明
离线合成(普通版) Android SDK
离线合成(普通版) iOS SDK
离线合成(精品版) Android SDK
离线合成(精品版) iOS SDK
XML标签
SSML标签
语音合成时间戳功能
语音识别 语音识别
开发者文档下拉
音频格式说明
一句话识别 开发者文档下拉
RESTful API
Websocket API
Android SDK
iOS SDK
实时长语音识别 开发者文档下拉
Websocket API
Android SDK
iOS SDK
录音文件识别 开发者文档下拉
RESTful API
自学习工具 开发者文档下拉
热词
个性化模型
声音复刻 声音复刻
开发者文档下拉
定制模型 开发者文档下拉
RESTful API
Android SDK
iOS SDK
定制声音合成 开发者文档下拉
短文本合成RESTful API
长文本合成Websocket API
声音转换 声音转换
开发者文档下拉
发音人列表
Websocket API
Android SDK
iOS SDK
离线声音转换 离线声音转换
开发者文档下拉
发音人列表
Android SDK
iOS SDK
声纹识别 声纹识别
开发者文档下拉
RESTful API
声音理解 声音理解
开发者文档下拉
RESTful API
协议规则 协议规则
开发者文档下拉
平台服务协议
平台通用规则
法律声明及隐私政策
服务等级协议SLA
常见问题 常见问题
开发者文档下拉
语音合成
语音识别

声音转换Websocket API

功能介绍

声音转换Websocket API 满足用户实时声音转换的需求,允许用户以音频流的形式输入原始声音,同时转换后的声音也以流的形式实时推送回去,可以做到边说话边转换的实时效果。声音转换可以支持不同年龄、性别的说话人声音转换换成目标音色,目标音色会携带说话人的语气、情感、口音等特征。

音频要求

  • 支持音频文件的编码格式及文件名的后缀: pcm。
  • 支持音频文件的采样率/位深: 16000Hz/16bit。
  • 支持的语言:中文普通话。
  • 音频有效时长:不超过180分钟。
  • 声道:单声道

使用方法

服务地址

访问类型 说明 URL Host
外网访问 websocket协议 wss://openapi.data-baker.com/ws/voice_conversion openapi.data-baker.com

交互流程

请求参数

参数名 参数格式 是否必填 说明
access_token string 通过client_id,client_secret调用授权服务获得见 获取访问令牌
enable_vad bool true代表启动服务端vad功能,默认false。如果启动系统会根据输入音频进行检测,过滤环境噪音。否则直接将原始输入音频进行转换。
align_input bool true代表输出音频与输入音频进行对齐,默认false。即开启vad时会保留静音部分,false丢弃静音部分
voice_name string 发音人参数即转换的目标声音
lastpkg bool 是否为最后一包,当时发送最后一包数据时设置为true 告诉系统输入完成

ws数据包格式

格式说明

  • 第一部分 HEAD 四字节的二进制,长度为一个大端的32位int转换而来,表示第二部分JSON数据的长度,转换关系如下:
    //length转byte[]
    int32_t length = 123;
    unsigned char B[4];
    B[0] = length >> 24 & 0xFF;
    B[1] = length >> 16 & 0xFF;
    B[2] = length >> 8 & 0xFF;
    B[3] = length & 0xFF;
    
    //byte[]转length
    length = (B[0] << 24) + (B[1] <<  16) + (B[2] <<  8) + B[3];
  • 第二部分是一个JSON的字符串
  • 第三部分是音频的二进制数据,若数据包中不包含音频,则第三部分可以为空

Python示例代码

import argparse
import json
import wave
import requests
import websocket
from threading import Thread


#websocket客户端
class Client:
    def __init__(self, data, uri, save_path):
        self.data = data
        self.uri = uri
        self.converted_data = b""
        self.save_path = save_path

    #建立连接
    def connect(self):
        ws_app = websocket.WebSocketApp(uri,
                                        on_open=self.on_open,
                                        on_message=self.on_message,
                                        on_error=self.on_error,
                                        on_close=self.on_close)
        ws_app.run_forever()

    # 建立连接后发送消息
    def on_open(self, ws):
        print("sending..")
        def run(*args):
            for message in self.data:
                ws.send(message, websocket.ABNF.OPCODE_BINARY)

        Thread(target=run).start()

    # 接收消息
    def on_message(self, ws, message):
        length = int.from_bytes(message[:4], byteorder='big', signed=False)
        json_data = json.loads((message[4: length + 4]).decode())
        self.converted_data += message[4 + length:]
        if json_data['lastpkg']:
            with wave.open(self.save_path, 'wb') as wavfile:
                wavfile.setparams((1, 2, 16000, 0, 'NONE', 'NONE'))
                wavfile.writeframes(self.converted_data)
                ws.close()
                print("task finished successfully")
        code = json.loads(message).get("errcode")
        print(str(json.loads(message)))
        if code != 0:
            # 打印接口错误
            print(message)

    # 打印错误
    def on_error(slef, ws, error):
        print("error: ", str(error))

    # 关闭连接
    def on_close(ws):
        print("client closed.")


# 准备数据
def prepare_data(args, access_token):

    # 填写Header信息
    voice_name = args.voice_name
    with open(args.file_path, 'rb') as f:
        file = f.read()
    data = []

    for i in range(0, len(file), 32000):
        if i + 32000 > len(file):
            tts_params = {"access_token": access_token, "voice_name": voice_name, 'enable_vad': True, 'align_input': True, "lastpkg": True}
        else:
            tts_params = {"access_token": access_token, "voice_name": voice_name, 'enable_vad': True, 'align_input': True, "lastpkg": False}
        json_data = json.dumps(tts_params)
        json_data_bi = json_data.encode()
        length = len(json_data)
        head_data = length.to_bytes(4, byteorder='big')

        if i + 32000 > len(file):
            data.append(head_data + json_data_bi + file[i:])
        else:
            data.append(head_data + json_data_bi + file[i: i + 32000])

    return data


# 获取命令行输入参数
def get_args():
    text = "今天天气不错哦!"
    parser = argparse.ArgumentParser(description='ASR')
    parser.add_argument('-client_secret', type=str, required=True)
    parser.add_argument('-client_id', type=str, required=True)
    parser.add_argument('-file_path', type=str, required=True)
    parser.add_argument('-file_save_path', type=str, required=True)
    parser.add_argument('--voice_name', type=str, default='Vc_baklong')
    args = parser.parse_args()

    return args


# 获取access_token用于鉴权
def get_access_token(client_secret, client_id):
    grant_type = "client_credentials"
    url = "https://openapi.data-baker.com/oauth/2.0/token?grant_type={}&client_secret={}&client_id={}" \
        .format(grant_type, client_secret, client_id)

    try:
        response = requests.post(url)
        response.raise_for_status()
    except Exception as e:
        print(response.text)
        raise Exception
    else:
        access_token = json.loads(response.text).get('access_token')
        return access_token


if __name__ == '__main__':
    try:
        args = get_args()

        # 获取access_token
        client_secret = args.client_secret
        client_id = args.client_id
        access_token = access_token = get_access_token(client_secret, client_id)

        # 准备数据
        data = prepare_data(args, access_token)

        uri = "wss://openapi.data-baker.com/ws/voice_conversion"
        # 建立Websocket连接
        client = Client(data, uri, args.file_save_path)
        client.connect()
    except Exception as e:
        print(e)
      

命令行执行

如有需要可自行修改参数

voice_conversion.py -client_secret=您的client_secret -client_id=您的client_id -file_path=test.pcm -file_save_path=output.wav
      

JAVA示例代码

package com.databaker.web.vc;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import okhttp3.*;
import okio.ByteString;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 声音转换WebSocket API接口调用示例
 * 附:声音转换Websocket API文档 【https://www.data-baker.com/specs/file/vc_api_websocket】
 * <p>
 * 注意:仅作为demo示例,失败重试、token过期重新获取、日志打印等优化工作需要开发者自行完成
 *
 * @author data-baker
 */
public class VcWebSocketDemo extends WebSocketListener {
    /**
     * 授权:需要在开放平台获取【https://ai.data-baker.com/】
     */
    private static final String clientId = "YOUR_CLIENT_ID";
    private static final String clientSecret = "YOUR_CLIENT_SECRET";

    /**
     * 获取token的地址信息
     */
    public static String tokenUrl = "https://openapi.data-baker.com/oauth/2.0/token?grant_type=client_credentials&client_secret=%s&client_id=%s";

    private static final String hostUrl = "wss://openapi.data-baker.com/ws/voice_conversion";

    /**
     * 文件路径【开发者需要根据实际路径调整。支持的音频编码格式:PCM,采样率16K,位深16bit,中文普通话,时长不超过180分钟】
     */
    private static final String pcmFile = "/home/asr/16k_16bit.pcm";
    /**
     * 文件夹路径【声音转换后文件存放地址,开发者需要根据实际路径调整。】
     */
    private static final String dic = "/home/asr";

    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyy-MM-dd HH:mm:ss.SSS");

    // 开始时间
    private static ThreadLocal<Date> timeBegin = ThreadLocal.withInitial(() -> new Date());

    // 结束时间
    private static ThreadLocal<Date> timeEnd = ThreadLocal.withInitial(() -> new Date());

    // 发音人
    private String voiceName = "Vc_baklong";

    private Date startTime;

    private String accessToken = getAccessToken();

    @Override
    public void onOpen(WebSocket webSocket, Response response) {
        super.onOpen(webSocket, response);
        this.startTime = timeBegin.get();
        // 该demo直接从文件中读取音频流【实际场景可能是实时从麦克风获取音频流,开发者自行修改获取音频流的逻辑即可】
        new Thread(() -> {
            File file = new File(pcmFile);
            // 连接成功,开始发送数据
            // 第二部分是一个JSON的字符串
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("access_token", accessToken);
            jsonObject.put("voice_name", voiceName);
            jsonObject.put("enable_vad", true);
            jsonObject.put("align_input", true);
            jsonObject.put("lastpkg", false);

            int length = jsonObject.toJSONString().getBytes().length;
            // 第一部分
            byte[] b = new byte[4];
            b[0] = (byte) (length >> 24 & 0xFF);
            b[1] = (byte) (length >> 16 & 0xFF);
            b[2] = (byte) (length >> 8 & 0xFF);
            b[3] = (byte) (length & 0xFF);

            FileInputStream fileInputStream = null;
            ByteArrayOutputStream byteOut = null;
            try {
                fileInputStream = new FileInputStream(file);
                byteOut = new ByteArrayOutputStream();
                int size = 32000;
                byte[] byteArray = new byte[size];
                int totalLength = 0;
                int read = 0;
                // 发送音频
                while ((read = fileInputStream.read(byteArray, 0, size)) > 0) {
                    totalLength += read;
                    System.out.println();
                    if (read == size && totalLength < file.length()) {
                        byte[] bytes = ArrayUtils.addAll(ArrayUtils.addAll(b, jsonObject.toJSONString().getBytes()), byteArray);
                        webSocket.send(new ByteString(bytes));
                        Thread.sleep(40);
                    } else {
                        // 最后一包
                        jsonObject.put("lastpkg", true);
                        length = jsonObject.toJSONString().getBytes().length;
                        b[0] = (byte) (length >> 24 & 0xFF);
                        b[1] = (byte) (length >> 16 & 0xFF);
                        b[2] = (byte) (length >> 8 & 0xFF);
                        b[3] = (byte) (length & 0xFF);
                        byte[] subarray = ArrayUtils.subarray(byteArray, 0, read);
                        byte[] bytes = ArrayUtils.addAll(ArrayUtils.addAll(b, jsonObject.toJSONString().getBytes()), subarray);
                        webSocket.send(new ByteString(bytes));
                    }
                }
                System.out.println("all data is send");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (fileInputStream != null) {
                    try {
                        fileInputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (byteOut != null) {
                    try {
                        byteOut.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

    @Override
    public void onMessage(WebSocket webSocket, String text) {
        super.onMessage(webSocket, text);
    }

    @Override
    public void onMessage(WebSocket webSocket, ByteString bytes) {
        super.onMessage(webSocket, bytes);
        byte[] byteArray = bytes.toByteArray();
        // byte[]转length
        int length = (byteArray[0] << 24) + (byteArray[1] << 16) + (byteArray[2] << 8) + byteArray[3];
        byte[] jsonArray = ArrayUtils.subarray(byteArray, 4, 4 + length);
        String jsonStr = new String(jsonArray);
        JSONObject jsonObject = JSONObject.parseObject(jsonStr);
        byte[] subarray = ArrayUtils.subarray(byteArray, 4 + length, byteArray.length);
        File resultPcmFile = new File(dic, new File(pcmFile).getName().replace(".pcm", "_result.pcm"));
        if (!resultPcmFile.exists()) {
            try {
                resultPcmFile.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        try {
            // 写入文件
            FileOutputStream outputStream = new FileOutputStream(resultPcmFile, true);
            IOUtils.write(subarray, outputStream);
            IOUtils.closeQuietly(outputStream);
            if ((int) jsonObject.get("errcode") == 0) {
                if (jsonObject.getBoolean("lastpkg")) {
                    //说明数据全部返回完毕,可以关闭连接,释放资源
                    System.out.println("session end ");
                    System.out.println(sdf.format(startTime) + "开始");
                    System.out.println(sdf.format(timeEnd.get()) + "结束");
                    System.out.println("耗时:" + (timeEnd.get().getTime() - startTime.getTime()) + "ms");
                    System.out.println("本次声音转换traceId ==》" + jsonObject.getString("traceid"));
                    webSocket.close(1000, "");
                }
            } else {
                System.out.println("errCode=>" + jsonObject.getInteger("errcode") + " errMsg=>" + jsonObject.getString("errmsg") + " traceId=" + jsonObject.getString("traceid"));
                // 关闭连接
                webSocket.close(1000, "");
                System.out.println("发生错误,关闭连接");
                return;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onFailure(WebSocket webSocket, Throwable t, Response response) {
        super.onFailure(webSocket, t, response);
        try {
            if (null != response) {
                int code = response.code();
                System.out.println("onFailure code:" + code);
                System.out.println("onFailure body:" + response.body().string());
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        OkHttpClient client = new OkHttpClient.Builder().build();
        Request request = new Request.Builder().url(hostUrl).build();
        client.newWebSocket(request, new VcWebSocketDemo());
    }

    public static String getAccessToken() {
        String accessToken = "";
        OkHttpClient client = new OkHttpClient();
        // request 默认是get请求
        String url = String.format(tokenUrl, clientSecret, clientId);
        Request request = new Request.Builder().url(url).build();
        JSONObject jsonObject;
        try {
            Response response = client.newCall(request).execute();
            if (response.isSuccessful()) {
                // 解析
                String resultJson = response.body().string();
                jsonObject = JSON.parseObject(resultJson);
                accessToken = jsonObject.getString("access_token");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return accessToken;
    }
}
      

响应结果

参数名 参数格式 是否必填 说明
errcode int 错误码,0 代表成功其他代表识别,参考错误码说明
errmsg string 错误信息描述
traceid string 会话唯一id,用于追踪定位问题
lastpkg bool 是否为最后一包,当时发送最后一包数据时设置为true 告诉调用方输出完成

成功时:

'\x00\x00\x00Q{"errcode":0,"errmsg":"success","lastpkg":false,"traceid":"7018124205863151147"}\n\xfc\xff\xfc\xff\x03\x00\x02\x00\x02\x00\x02\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x06\x00\x04\x00\x04\x00\x04\x00\xfd\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\xfa\xff\xfc\xff\xfc\xff\xf7\xff\xf9\xff\xf9\xff\xfa\xff\xfb\xff\xfb\xff\xfc\xff\xfd\xff\xfd\xff\xfe\xff\xfe\xff\xfe\xff\xff\xff\xff\xff\xf9\xff\xfb\xff\xfb\xff\xfc\xff\xfc\xff\x02\x00\x01\x00\x01\x00\x01\x00\x01\x00\x02\x00\x01\x00\x01\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfa\xff\xfc\xff\xfd\xff\xfd\xff\xf8\xff\xf9\xff\xfa\xff\xfb\xff\xfc\xff\xfc\xff\x02\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfb\xff\x02\x00\x01\x00\x01\x00\x01\x00\x00\x00\x06\x00\x05\x00\x04\x00\t\x00\x07\x00\x07\x00\x06\x00\x05\x00\x05\x00\x04\x00\x03\x00\x03\x00\x02\x00\x07\x00\x06\x00\x05\x00\x05\x00\x04\x00\x04\x00\x04\x00\x03\x00\x02\x00\x02\x00\x02\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x06\x00\xff\xff\x00\x00\x00\x00\x06\x00\x05\x00\t\x00\x07\x00\x06\x00\x06\x00\x05\x00\x05\x00\x04\x00\x03\x00\x03\x00\x02\x00\x02\x00\x02\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\xfb\xff\xfd\xff\xfd\xff\xfd\xff\xfe\xff\xfe\xff\xfe\xff\xf9\xff'
      

失败时:

'\x00\x00\x00s{"errcode":10000,"errmsg":"Param error cause parse payload failed","lastpkg":true,"traceid":"7017724786985292155"}\n'

错误码

错误码 含义 类别
0 成功 请求参数错误
10000 ws数据包解析错误
10001 ws数据包json解析错误
10002 请求参数缺少access token
10003 access token过期或不合法
10004 请求参数缺少 voice name
10005 voice name 不存在
20000 引擎资源不足,拒绝新建会话 服务端错误
20001 声音转换出错
20002 动态内存申请失败
20003 向客户端推送响应失败
20004 其他未定义的内部错误
30000 会话进行时客户端主动关闭 客户端错误
30001 会话空闲超时,超过一定时间没有数据传输导致会话空闲超时,主动断开连接