云知声-口语评测web版SDK接入指南

通过WebSocket接入SDK

一、SDK接口说明

基于语音识别和评价技术对发音做客观打分,反馈发音正误和定位问题,有助于语音教学,发音练习,也可测试考生的口语水平。

二、集成要求:

集成API时,需按照以下要求:

内容说明
请求协议ws[s](为提高安全性,强烈推荐wss)
请求地址英文:ws://ws-edu.hivoice.cn:8081/ws/eval/或wss://wss-edu.hivoice.cn:443/ws/eval/
中文:ws://wscn-edu.hivoice.cn:18081/ws/eval/或wss://wsscn-edu.hivoice.cn/ws/eval/
字符编码UTF-8
响应格式统一采用JSON格式
开发语言任意,只要可以向服务发起Websocket请求的均可
操作系统任意
音频属性采样率16K,比特率16bit、单声道
音频格式mp3,wxspeex 或pcm,推荐使用mp3格式
评测语言中文/英文

三、 接口调用流程:

时序图

avatar


1.请求地址

英文
主域名:ws-edu.hivoice.cn
端口:8081
请求地址:ws://ws-edu.hivoice.cn:8081/ws/eval/或wss://wss-edu.hivoice.cn:443/ws/eval/

中文
主域名:wscn-edu.hivoice.cn
端口:18081
请求地址:ws://wscn-edu.hivoice.cn:18081/ws/eval/或wss://wsscn-edu.hivoice.cn/ws/eval/

2.英文API

普通评测

{
  "mode": "word",
  "displayText": "hello world",
  "appkey": "联系商务同学获取",
  "scoreCoefficient": "1",
  "userID": "",
  "audioFormat": "opus",
  "eof": "gnh-test-end"
}

jsgf接口

{
  "mode": "qa",
  "Version": "1",
  "DisplayText": "Jsgf Grammar Tool Generated",
  "GrammarWeight": "{\"weight_struct\":[[{\"weight\":0.5,\"key\":\"good morning\"}]]}",
  "Grammar": "#JSGF V1.0 utf-8 cn;\ngrammar main;\npublic <main> = \"<s>\"(<a>|<a> to you)\"</s>\";\n<a> = (good morning);\n",
  "Appkey": "",
  "scoreCoefficient": "1",
  "UserID": "",
  "eof": "test-end",
  "audioFormat": "pcm"
}

枚举接口

{
  "mode": "qa",
  "version": "1",
  "displayText": "Enumerate Grammar Tool Generated",
  "grammarWeight": "{\"weight_struct\":[[{\"weight\":0.5,\"key\":\"teacher\"}]]}",
  "grammar": "#enumerate \nmy mother is a teacher\na teacher\n",
  "appkey": "",
  "scoreCoefficient": "1",
  "userID": "",
  "eof": "test-end",
  "audioFormat": "pcm"
}

retell接口

{
  "mode": "retell",
  "Version": 1,
  "EvalType": "en.exam.retell",
  "DisplayText": "OralComposition Grammar Tool Generated",
  "Language": "en",
  "Grammar": "",
  "GrammarWeight": "{\"weight_struct\":[[{\"weight\":0.5,\"key\":\"bookstore\"}]]}",
  "Reference": {
  "ID": "",
    "answers": [
    {
      "type": 1,
      "text": "bookstore"
    },
    {
      "type": 1,
      "text": "she is going to bookstore"
    }
  ]
},
  "Appkey": "",
  "scoreCoefficient": "1",
  "UserID": "",
  "eof": "test-end"
}

英文评测请求接口字段说明(关键字不区别大小写)

字段是否必选含义备注
mode必填可选值为word,sent,para,qa,retell
displayText必填评测文本
appkey必填秘钥信息AppKey@AppSecret形式,AppKey和AppSecret 请在应用详情获取
userID可选用户id信息建议传入,排查问题方便
audioFormat必填可选值:mp3,speex 音频格式,音频16K单声道
eof必填设置eof消息包内容客户端需要保证该内容的唯一性,可选用uuid
scoreCoefficient可选值范围:0.6-1.9打分系数,调整打分的松紧度,值越大打分越宽松

3.中文API

中文评测

{
  "EvalType": "sentence",
  "Language":"cn",
  "displayText": "你好",
  "appkey": "联系商务同学获取",
  "scoreCoefficient": "1",
  "userID": "",
  "audioFormat": "mp3",
  "eof": "gnh-test-end"
}

中文评测请求接口字段说明(关键字不区别大小写)

字段是否必选含义备注
EvalType必填可选值为word,sentence,paragraph
Language必填语种,可选值为cn
displayText必填评测文本
appkey必填秘钥信息AppKey@AppSecret形式,AppKey和AppSecret 请在应用详情获取
userID可选用户id信息建议传入,排查问题方便
audioFormat必填可选值:mp3,speex 音频格式,音频16K单声道
eof必填设置eof消息包内容客户端需要保证该内容的唯一性,可选用uuid
scoreCoefficient可选值范围:0.6-1.9打分系数,调整打分的松紧度,值越大打分越宽松

响应接口

{
  "result": {},
  "area": "sh",
  "time": "1551409712576231666",
  "sid": "f4376e83-7ad0-4635-9812-bec949a2fa27",
  "errcode": 0,
  "errmsg": "ok"
}

响应接口字段说明

字段含义备注
result评测结果Json字段说明
area返回实际评测的的机房区域
time时间戳
sid sessionID服务端返回的唯一标识
errorcode错误码
errmsg错误消息

评测返回结果中Json字段说明

名称类型说明
versionstring结果格式版本及版本号
linesarray每行输入文本的评测结果
EvalTypestring评测类型:general(朗读评测)、askandanswer(情景问答)、composition(作文)
samplestring输入的标准文本
usertextstring用户实际朗读的文本(语音识别结果)
subwordsarray包含单词的音标、开始时间、结束时间、分数、音量信息
begindouble开始时间,单位为秒
enddouble开始时间,单位为秒
volumedouble音量
scorestring分值
subtextstring音标或重音符号信息
integritydouble录入语音的完整度
pronunciationdouble录入语音的标准度
fluencydouble录入语音的流利度
wordsarray每个词的评测结果
textstring单词或音素文本
typeint类型,共有6种类型,分别是:
0 多词:仅B,C,G模式出现,当朗读内容大于文本内容时,多余的单词type值为0;eg:文本:nice to meet you,音频:nice nice to meet you,第二个nice的type值为0;
1 漏词:所有模式都有,当朗读内容小于文本内容时,未读的单词type值为1;eg:文本:nice to meet you,音频:nice meet you,结果中to的type值为1;
2 正常词:所有模式都有,识别正常的词;
3 错误词:仅B,C,G模式出现,当朗读的文本某个单词识别成文本中其他单词时,该单词type值为3。 eg:文本:nice to meet you,音频:nice you meet you,结果中第一个you的type值为3;
4 静音:所有模式;
5 重复词:预留接口,未实现;
7 空格or标点:仅E模式,空格和标点的结构type值为7;
8 生词:所有模式
sentSamplearray句式标准文本
sentScorearray句式总分
sentPronunciationarray句式标准度得分
sentFluencyarray句式流利度得分
sentIntegrityarray句式完整度得分
keySamplearray关键词sample(包括关键词和每个关键词的得分
keysScorearray关键词总分
keysPronunciationarray关键词标准度得分
keysIntegrityarray关键词完整度得分
keysFluencyarray关键词流利度
standardScorestring客户定制,输出的分制,当前含有4分制和8分制
StressOfSentstring句子重读,每个单词都输出,0:该单词没有被重读;1:该单词被重读
StressOfWordstring单词重音,将用户发音和词典的重音位置做比较,0:该单词重音朗读错误;1:该单词重音朗读正确
tonearray输出全部信息,数据可以用于画用户的发音曲线,目前只有内部在使用
audiocheckarray音质检测结果。
volume:音量过小的置信度;
clipping:截幅的置信度;
noise:噪音过大的置信度;
cut:截断的置信度;
too short:是否音频过短;
emptyAudio:是否是空音频。
备注:置信度的值为0和10,10代表可能存在该项音质问题,0代表该项检测正常

拼接评测audio url

想要获取用户录音结果,则可以根据响应接口数据拼接音频url,获取音频
“area”: “sh”,
“time”: “1551409712576231666”,
“sid”: “f4376e83-7ad0-4635-9812-bec949a2fa27”,
其中,sh是代表地域名称area,1551409712576231666是代表createtime; f4376e83-7ad0-4635-9812-bec949a2fa27是代表请求中传入的session-id
最后可以根据以上三个信息拼接url,固定字段http://edu.hivoice.cn:9088/WebAudio-1.0-SNAPSHOT/audio/play/{session-id}/{createtime}/{area}
以上示例,最后拼接的url是http://edu.hivoice.cn:9088/WebAudio-1.0-SNAPSHOT/audio/play/ 02055555-f4cd-4fef-8ed8-1a2089056acf/1542272221203805792/sh
如果是https协议,拼接按照 https://edu.hivoice.cn/WebAudio-1.0-SNAPSHOT/audio/play/{session-id}/{createtime}/{area}

四、错误码

参见:https://github.com/oraleval/ErrorCodeList/wiki/HomePage

五、示例DEMO

index.html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>英语评分</title>
    <script src="Recorder.js"></script>
</head>
<body>

<div id="sse">
    <button onclick="startSounRecording()">开始录音</button>
    <button onclick="stopSounRecording()">结束录音</button>
</div>

<script type="text/javascript">
    let ws;
    const eof = 'gnh-test-end'
    let recorder = null
    function WebSocketTest() {
        if ("WebSocket" in window) {
            console.log('支持WebSocket')
            /*
             打开一个 web socket
             ws://localhost:9998/ 表示 socket 连接地址
             echo-protocol 表示子协议
             */
            ws = new WebSocket("ws://ws-edu.hivoice.cn:8081/ws/eval/");

            ws.onopen = function () {
                console.log('连接成功')
                // Web Socket 已连接上,使用 send() 方法发送数据
                const params = {
                    "mode": "word",
                    "displayText": "hello world",
                    "appkey": "vfvu3rj2allcsgtnurewn3n7paueipqoesyon6qa@4cdb9e4076d0dff3588db072b7d17836",
                    "scoreCoefficient": "1",
                    "userID": "",
                    "audioFormat": "pcm",
                    "eof": eof
                };
                ws.send(JSON.stringify(params));
            };

            ws.onmessage = function (evt) {
                var result = JSON.parse(evt.data);
                console.log(result)
                ws.close();
                recorder && recorder.stop();
            };

            ws.onclose = function () {
                // 关闭 websocket
                ws = null;
            };
        } else {
            // 浏览器不支持 WebSocket
            alert("您的浏览器不支持 WebSocket!");
        }
    }
    function startSounRecording() {
        console.log('startSounRecording')
        WebSocketTest()
        recorder = new Recorder(onaudioprocess);

        function onaudioprocess(buffer) {
            console.log('buffer-',buffer)
            if (ws && ws.readyState === 1) {
                console.log('buffer--',buffer)
                ws.send(buffer);
            }
        }
        recorder.ready().then(
            () => {
                recorder.start();
                // if (current == 'en')
            },
            () => {
                console.log('录音启动失败!');
            },
        );

    }

    function stopSounRecording() {
        console.log('stop--------')
        if (ws&&ws.readyState === 1) {
            ws.send(eof);
        }

    }
</script>
</body>
</html>

Recorder.js 文件

let Recorder = function (onaudioprocess) {
  console.log('Recorder-------')
  this.config = {
    sampleBits: 16, // 采样数位 8, 16
    sampleRate: 16000, // 采样率(1/6 44100)
  };
  this.size = 0; // 录音文件总长度
  this.buffer = []; // 录音缓存
  this.realtimeBuffer = [];
  // 录音实时获取数据
  this.input = function (data) {
    // 记录数据,这儿的buffer是二维的
    this.buffer.push(new Float32Array(data));
    this.size += data.length;
  };
  this.onaudioprocess = onaudioprocess;
};
// 设置如采样位数的参数
Recorder.prototype.setOption = function (option) {
  // 修改采样率,采样位数配置
  Object.assign(this.config, option);
};
Recorder.prototype.ready = function () {
  this.context = new (window.AudioContext || window.webkitAudioContext)();
  // 第一个参数表示收集采样的大小,采集完这么多后会触发 onaudioprocess 接口一次,该值一般为1024,2048,4096等,一般就设置为4096
  // 第二,三个参数分别是输入的声道数和输出的声道数,保持一致即可。
  this.createScript =
    this.context.createScriptProcessor || this.context.createJavaScriptNode;
  this.recorder = this.createScript.apply(this.context, [4096, 1, 1]);

  // 音频采集
  this.recorder.onaudioprocess = e => {
    const data = e.inputBuffer.getChannelData(0);
    this.input(data);
    if (this.onaudioprocess) {
      this.onaudioprocess(this.encodePCMFragment(data));
      // this.onaudioprocess(data);
    }
  };
  console.log('navigator',navigator)
  console.log('navigator.mediaDevices',navigator.mediaDevices)

  // navigator.getUserMedia = navigator.mediaDevices.getUserMedia || navigator.webkitGetUserMedia || navigator.getUserMedia || navigator.mediaDevices;

  // if (!navigator.mediaDevices) {
  //   Recorder.throwError('无法发现指定的硬件设备。');
  // }
  return navigator.mediaDevices
    .getUserMedia({
      audio: true,
    })
    .then(
      stream => {
        // audioInput表示音频源节点
        // stream是通过navigator.getUserMedia获取的外部(如麦克风)stream音频输出,对于这就是输入
        this.audioInput = this.context.createMediaStreamSource(stream);
      },
      error => {
        switch (error.code || error.name) {
          case 'PERMISSION_DENIED':
          case 'PermissionDeniedError':
            Recorder.throwError('用户拒绝提供信息。');
            break;
          case 'NOT_SUPPORTED_ERROR':
          case 'NotSupportedError':
            Recorder.throwError('浏览器不支持硬件设备。');
            break;
          case 'MANDATORY_UNSATISFIED_ERROR':
          case 'MandatoryUnsatisfiedError':
            Recorder.throwError('无法发现指定的硬件设备。');
            break;
          default:
            Recorder.throwError(
              '无法打开麦克风。异常信息:' + (error.code || error.name),
            );
            break;
        }
      },
    );
};
// 异常处理
Recorder.throwError = function (message) {
  throw new Error(message);
};
// 开始录音
Recorder.prototype.start = function () {
  try {
    // 清空数据
    this.buffer.length = 0;
    this.size = 0;
    // audioInput 为声音源,连接到处理节点 recorder
    this.audioInput.connect(this.recorder);
    // 处理节点 recorder 连接到扬声器
    this.recorder.connect(this.context.destination);
    // 设置压缩参数
    this.inputSampleRate = this.context.sampleRate; // 获取当前输入的采样率
    this.inputSampleBits = 16; // 输入采样数位 8, 16
    this.outputSampleRate = this.config.sampleRate; // 输出采样率
    this.oututSampleBits = this.config.sampleBits; // 输出采样数位 8, 16
  } catch (error) {
    Recorder.throwError(
      '无法打开麦克风。异常信息:' + (error.code || error.name),
    );
  }
};
// 停止录音
Recorder.prototype.stop = function () {
  this.recorder.disconnect();
};
// 播放到audio标签中
// 参数表示audio元素
Recorder.prototype.play = function (audio) {
  audio.src = window.URL.createObjectURL(this.getWAVBlob());
};
// 获取PCM编码的二进制数据
Recorder.prototype.getPCM = function () {
  this.stop();

  return this.encodePCM();
};
// 获取不压缩的PCM格式的编码
Recorder.prototype.getPCMBlob = function () {
  return new Blob([this.getPCM()]);
};
// 获取WAV编码的二进制数据
Recorder.prototype.getWAV = function (isRecord) {
  this.stop();

  return this.encodeWAV(isRecord);
};
// 获取不压缩的WAV格式的编码
Recorder.prototype.getWAVBlob = function () {
  return new Blob([this.getWAV(true)], { type: 'audio/wav' });
};
// 数据合并压缩
// 根据输入和输出的采样率压缩数据,
// 比如输入的采样率是48k的,我们需要的是(输出)的是16k的,由于48k与16k是3倍关系,
// 所以输入数据中每隔3取1位
Recorder.prototype.compress = function (isReplay) {
  // 合并
  var data = new Float32Array(this.size);
  var offset = 0; // 偏移量计算
  // 将二维数据,转成一维数据
  for (var i = 0; i < this.buffer.length; i++) {
    data.set(this.buffer[i], offset);
    offset += this.buffer[i].length;
  }
  const outputSampleRate = isReplay
    ? this.inputSampleRate
    : this.outputSampleRate;
  // 压缩
  var compression = parseInt(this.inputSampleRate / outputSampleRate);
  var length = data.length / compression;
  var result = new Float32Array(length);
  var index = 0,
    j = 0;
  // 循环间隔 compression 位取一位数据
  while (index < length) {
    result[index] = data[j];
    j += compression;
    index++;
  }
  // 返回压缩后的一维数据
  return result;
};
/**
 * 转换到我们需要的对应格式的编码
 * return {DataView}    pcm编码的数据
 */
Recorder.prototype.encodePCM = function (isReplay) {
  let bytes = this.compress(isReplay),
    sampleBits = Math.min(
      this.inputSampleBits,
      isReplay ? this.inputSampleBits : this.oututSampleBits,
    ),
    offset = 0,
    dataLength = bytes.length * (sampleBits / 8),
    buffer = new ArrayBuffer(dataLength),
    data = new DataView(buffer);

  // 写入采样数据
  if (sampleBits === 8) {
    for (var i = 0; i < bytes.length; i++, offset++) {
      // 范围[-1, 1]
      var s = Math.max(-1, Math.min(1, bytes[i]));
      // 8位采样位划分成2^8=256份,它的范围是0-255; 16位的划分的是2^16=65536份,范围是-32768到32767
      // 因为我们收集的数据范围在[-1,1],那么你想转换成16位的话,只需要对负数*32768,对正数*32767,即可得到范围在[-32768,32767]的数据。
      // 对于8位的话,负数*128,正数*127,然后整体向上平移128(+128),即可得到[0,255]范围的数据。
      var val = s < 0 ? s * 128 : s * 127;
      val = parseInt(val + 128);
      data.setInt8(offset, val, true);
    }
  } else {
    for (var i = 0; i < bytes.length; i++, offset += 2) {
      var s = Math.max(-1, Math.min(1, bytes[i]));
      // 16位直接乘就行了
      data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
    }
  }

  return data;
};

Recorder.prototype.encodePCMFragment = function (fragment) {
  let data = new Float32Array(fragment);

  // 压缩
  var compression = parseInt(this.inputSampleRate / this.outputSampleRate);
  var length = data.length / compression;
  var result = new Float32Array(length);
  var index = 0,
    j = 0;
  // 循环间隔 compression 位取一位数据
  while (index < length) {
    result[index] = data[j];
    j += compression;
    index++;
  }
  // 返回压缩后的一维数据

  let bytes = result,
    sampleBits = Math.min(this.inputSampleBits, this.oututSampleBits),
    offset = 0,
    dataLength = bytes.length * (sampleBits / 8),
    buffer = new ArrayBuffer(dataLength),
    lastData = new DataView(buffer);

  // 写入采样数据
  if (sampleBits === 8) {
    for (var i = 0; i < bytes.length; i++, offset++) {
      // 范围[-1, 1]
      var s = Math.max(-1, Math.min(1, bytes[i]));
      // 8位采样位划分成2^8=256份,它的范围是0-255; 16位的划分的是2^16=65536份,范围是-32768到32767
      // 因为我们收集的数据范围在[-1,1],那么你想转换成16位的话,只需要对负数*32768,对正数*32767,即可得到范围在[-32768,32767]的数据。
      // 对于8位的话,负数*128,正数*127,然后整体向上平移128(+128),即可得到[0,255]范围的数据。
      var val = s < 0 ? s * 128 : s * 127;
      val = parseInt(val + 128);
      lastData.setInt8(offset, val, true);
    }
  } else {
    for (var i = 0; i < bytes.length; i++, offset += 2) {
      var s = Math.max(-1, Math.min(1, bytes[i]));
      // 16位直接乘就行了
      lastData.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
    }
  }

  return lastData;
};
Recorder.prototype.encodePCMFragment2Buffer = function (fragment) {
  let data = new Float32Array(fragment);

  // 压缩
  var compression = parseInt(this.inputSampleRate / this.outputSampleRate);
  var length = data.length / compression;
  var result = new Float32Array(length);
  var index = 0,
    j = 0;
  // 循环间隔 compression 位取一位数据
  while (index < length) {
    result[index] = data[j];
    j += compression;
    index++;
  }
  // 返回压缩后的一维数据

  let bytes = result,
    sampleBits = Math.min(this.inputSampleBits, this.oututSampleBits),
    offset = 0,
    dataLength = bytes.length * (sampleBits / 8),
    buffer = new ArrayBuffer(dataLength),
    lastData = new DataView(buffer);

  // 写入采样数据
  if (sampleBits === 8) {
    for (var i = 0; i < bytes.length; i++, offset++) {
      // 范围[-1, 1]
      var s = Math.max(-1, Math.min(1, bytes[i]));
      // 8位采样位划分成2^8=256份,它的范围是0-255; 16位的划分的是2^16=65536份,范围是-32768到32767
      // 因为我们收集的数据范围在[-1,1],那么你想转换成16位的话,只需要对负数*32768,对正数*32767,即可得到范围在[-32768,32767]的数据。
      // 对于8位的话,负数*128,正数*127,然后整体向上平移128(+128),即可得到[0,255]范围的数据。
      var val = s < 0 ? s * 128 : s * 127;
      val = parseInt(val + 128);
      lastData.setInt8(offset, val, true);
    }
  } else {
    for (var i = 0; i < bytes.length; i++, offset += 2) {
      var s = Math.max(-1, Math.min(1, bytes[i]));
      // 16位直接乘就行了
      lastData.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
    }
  }

  return lastData;
};

Recorder.prototype.encodeWAV = function (isReplay) {
  var sampleRate = Math.min(
    this.inputSampleRate,
    isReplay ? this.inputSampleRate : this.outputSampleRate,
  );
  var sampleBits = Math.min(
    this.inputSampleBits,
    isReplay ? this.inputSampleBits : this.oututSampleBits,
  );
  var bytes = this.encodePCM(isReplay);
  var buffer = new ArrayBuffer(44);
  var data = new DataView(buffer);

  var channelCount = 1; // 单声道
  var offset = 0;

  // 资源交换文件标识符
  writeString(data, offset, 'RIFF');
  offset += 4;
  // 下个地址开始到文件尾总字节数,即文件大小-8
  data.setUint32(offset, 36 + bytes.byteLength, true);
  offset += 4;
  // WAV文件标志
  writeString(data, offset, 'WAVE');
  offset += 4;
  // 波形格式标志
  writeString(data, offset, 'fmt ');
  offset += 4;
  // 过滤字节,一般为 0x10 = 16
  data.setUint32(offset, 16, true);
  offset += 4;
  // 格式类别 (PCM形式采样数据)
  data.setUint16(offset, 1, true);
  offset += 2;
  // 通道数
  data.setUint16(offset, channelCount, true);
  offset += 2;
  // 采样率,每秒样本数,表示每个通道的播放速度
  data.setUint32(offset, sampleRate, true);
  offset += 4;
  // 波形数据传输率 (每秒平均字节数) 单声道×每秒数据位数×每样本数据位/8
  data.setUint32(offset, channelCount * sampleRate * (sampleBits / 8), true);
  offset += 4;
  // 快数据调整数 采样一次占用字节数 单声道×每样本的数据位数/8
  data.setUint16(offset, channelCount * (sampleBits / 8), true);
  offset += 2;
  // 每样本数据位数
  data.setUint16(offset, sampleBits, true);
  offset += 2;
  // 数据标识符
  writeString(data, offset, 'data');
  offset += 4;
  // 采样数据总数,即数据总大小-44
  data.setUint32(offset, bytes.byteLength, true);
  offset += 4;

  // 给pcm文件增加头
  data = combineDataView(DataView, data, bytes);

  return data;
};

/**
 * 在data中的offset位置开始写入str字符串
 * @param {TypedArrays} data 二进制数据
 * @param {String}      str  字符串
 */
function writeString(data, offset, str) {
  for (var i = 0; i < str.length; i++) {
    data.setUint8(offset + i, str.charCodeAt(i));
  }
}

/**
 * 合并二进制数据
 * @param {TypedArrays} resultConstructor   需要合并成的数据类型
 * @param {TypedArrays} ...arrays           需要合并的数据
 */
function combineDataView(resultConstructor, ...arrays) {
  let totalLength = 0,
    offset = 0;
  // 统计长度
  for (let arr of arrays) {
    totalLength += arr.length || arr.byteLength;
  }
  // 创建新的存放变量
  let buffer = new ArrayBuffer(totalLength),
    result = new resultConstructor(buffer);
  // 设置数据
  for (let arr of arrays) {
    // dataview合并
    for (let i = 0, len = arr.byteLength; i < len; ++i) {
      result.setInt8(offset, arr.getInt8(i));
      offset += 1;
    }
  }

  return result;
}

效果图:

官方接入文档: http://ai-doc.unisound.com/sacalleval/WebSocket.html

如果觉得文章有帮助到你,可以扫描以下二维码
   请本文作者 喝一杯
pay_weixin pay_weixin

发表评论

电子邮件地址不会被公开。 必填项已用*标注