JS SDK开发文档
快速接入
1、SDK说明
jssdk通过使用Web Audio API完成在浏览器上的录音。然后将录制的原始音频流进行wav格式封装。在封装之后进行speex压缩以及ogg格式的重新封装,最终将ogg格式的采样率为16000的音频通过WebSocket流式传输到引擎,进行测评。
2、引入方式
2.1 引入js
需要在页面初始化时通过script引入engine.js.
将engine.js文件,引入根目录,例如可放入 sdk 文件夹下。
<script type="text/javascript" src="sdk/engine.js"></script>
engine.js的EngineEvaluat是挂载在window对象上的。
2.2 完整引入代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title></title>
<meta name="Description" content="" />
<link href="static/css/bootstrap.min.css" rel="stylesheet" type="text/css">
</head>
<body>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="renderer" content="webkit">
<div>
</div>
<script type="text/javascript" src="sdk/engine.js"></script>
</body>
</html>
3、快速测评流程
3.1 完整调用代码示例
需要改成为改成自己的app id,userId,以及下面的warrantId 鉴权url
<!-- 引入下方的js代码 -->
<script type="text/javascript" src="main.js"></script>
main.js源码
// TODO : 改成自己的 applicationId,userId 以及 warrantId
const applicationId = '';
const userId = '';
let warrantId = '';
var myRecord = new EngineEvaluat({
applicationId: applicationId,
userId : userId,
micAllowCallback: function() {
console.log('麦克风已经允许');
},
micForbidCallback: function() {
console.log('麦克风没有被允许');
},
playAudioComplete: function(data) {
console.log('播放完毕');
},
playAudioError:function(data){
console.log('播放出错');
},
engineBackResultDone:function(msg){
console.log('评测成功');
myRecordResult(msg);
},
engineBackResultFail:function(msg){
console.error('评测失败');
console.log(msg);
},
engineFirstInitDone:function(){
console.log('初始化成功');
},
micVolumeCallback: function(data) {
console.log('录音音量大小为:' + data);
},
JSSDKNotSupport: function() {
alert('jssdk not support');
},
noNetwork: function() {
console.warn('暂无网络');
},
audioDataCallback: function(data, isLast){
//需要在开始测评参数配置 saveAudio : 1 ,此回调才会响应
//如果在开始测评参数里配置 compress : 'raw',则会返回pcm格式的录音实时数据。否则会ogg格式的音频实时数据
console.log(data);
},
});
function defaultTime(refText) {
return 2000 + refText.trim().split(' ').length * 600;
}
console.warn('wwwwwww');
getWarrantId();
function getWarrantId(){
var request = $.ajax({
// TODO: 改成自己的鉴权url,user_id、applicationId
url: "",
type: "POST",
data: {"user_id": "","applicationId": ""},
success:function(data){
console.log(data);
warrantId = data.data.warrant_id;
}
});
}
$(document).ready(function(){
var setTimeoutLoop = null,
paused = false;
//跟读句子
$("#follow").bind("click", function() {
recordInit();
var coreType = getCoreType(),
//times = evalTime(),
params = {};
// 评测类型;
params.coreType = coreType;
params.refText = getEvalText();
params.warrantId = warrantId;
if (params.refText !== '') {
myRecord.startRecord(params);
$("#score").text('录音中...');
setTimeoutLoop = setTimeout(function(){
$("#score").text('评测中...');
},defaultTime(params.refText) + 1000)
} else {
alert('请填写评测内容');
}
});
//停止录音
$("#stop").bind("click", function(){
console.log('停止录音');
clearTimeout(setTimeoutLoop);
$("#score").text('评测中...');
myRecord.stopRecord();
});
//拖拽文件上传评测
// $('#upload').on('change', function(evt) {
// var params = {};
// params.refText = getEvalText();
// params.coreType = getCoreType();
// myRecord.wholeFileUpload(evt, params);
// });
//播放音乐的事件
$('#play').on('click', function() {
paused = false;
$('#pauseMusic').find('span').text('暂停');
myRecord.loadAudio(musicUrl);
myRecord.playAudio();
});
//获取音乐长度
// $('#musicDuration').on('click', function() {
// $('#duration').text(myRecord.getDuration());
// });
//暂停音乐播放
$('#pauseMusic').on('click', function() {
if (!paused) {
paused = true;
$(this).find('span').text('继续播放');
myRecord.pauseAudio();
} else {
paused = false;
$(this).find('span').text('暂停');
myRecord.playAudio();
}
});
// $('#continueMusic').on('click',function(){
// myRecord.playAudio();
// })
//停止音乐的播放
$('#stopMusic').on('click', function() {
myRecord.stopAudio();
});
// $('#musicCurrentTime').on('click',function(){
// $('#currentTime').text(myRecord.getCurrentTime());
// })
});
var musicUrl;
//录音结果详情等初始化方法
function recordInit(){
$("#point").text('');
$("#totalPoint").text('');
$("#result").text('');
$('#audioUrl').text('');
$('#grade').html('');
}
// 获取评测文本;
function getEvalText() {
return $.trim($('#eng-txt').val());
}
//获取评测时间
function evalTime(){
var evalTimeChecked = $('#evalTime').find('input[name="evalTime"]:checked');
if(evalTimeChecked.length > 0){
var _val = evalTimeChecked.val();
if(_val == 'null'){
return null;
}else{
return parseInt(evalTimeChecked.val(), 10) * 1000;
}
}else{
return false;
}
}
//获取评测类型;
function getCoreType(){
return $('#coreType').find('input[name="coreType"]:checked').val();
}
// // 评测成功回调;
// function engineSuc(msg) {
// myRecordResult(msg);
// }
// // 评测失败回调;
// function engineErr(msg) {
// console.log(msg);
// }
//获取评测数据的结果分析
var myRecordResult = function(result) {
var recordResult = JSON.parse(result);
var gradeInfo = '';
var coreType = getCoreType();
$("#point").text(recordResult.result.overall);
$("#totalPoint").text(recordResult.result.rank);
$("#result").text(JSON.stringify(recordResult));
var wordPoint = recordResult.result.details;
var url = 'https://files.cloud.ssapi.cn/' + recordResult.applicationId + '/' + recordResult.recordId;
musicUrl = url + '.mp3';
//myRecord.loadAudio(musicUrl);
if(coreType == "en.word.score" || coreType == "en.sent.score"){
wordPoint.forEach(function(item) {
gradeInfo += wordLevel(item.score, item.char);
})
}else if(coreType == "en.pred.score"){
wordPoint.forEach(function(item) {
gradeInfo += wordLevel(item.score, item.char);
})
}
else{
wordPoint.forEach(function(item) {
gradeInfo += wordLevel(item.score, item.char);
})
}
$('#audioUrl').text(url);
$('#grade').html(gradeInfo);
$("#score").text('测评完毕');
}
var wordLevel = function(point, word){
var coreType = getCoreType();
var ref = $("#eng-txt").val();
var exce = 85;
var good = 75;
var fine = 55;
var exceColor = '<font color="#OOECOO">';
var goodColor = '<font color="#OOE3EE">';
var fineColor = '<font color="#5B5B5B">';
var badColor = '<font color="#FF5151">';
if(coreType == "en.word.score" || coreType == "en.sent.score" || coreType == "en.pred.score"){
if (point >= exce){
var level = ' ' + exceColor + word +'</font>';
} else if(good <= point && point < exce){
var level = ' ' + goodColor + word +'</font>';
} else if(fine <= point && point < good){
var level = ' ' + fineColor + word +'</font>';
} else{
var level = ' ' + badColor + word +'</font>';
}
}else{
if (point >= exce){
var level = exceColor + word +'</font>';
} else if(good <= point && point < exce){
var level = goodColor + word +'</font>';
} else if(fine <= point && point < good){
var level = fineColor + word +'</font>';
} else{
var level = badColor + word +'</font>';
}
}
return level;
}
详细文档说明
1、JSSDK使用说明
1.1 简介
jssdk通过使用Web Audio Api完成在浏览器上的录音。然后将录制的原始音频流进行wav格式封装。在封装之后进行speex压缩以及ogg格式的重新封装,最终将ogg格式的采样率为16000的音频通过WebSocket流式传输到引擎,进行测评。
1.2 引入
需要在页面初始化时通过script引入engine.js.
1.3 浏览器兼容测试
操作系统 | 支持的浏览器最低版本 | |||||
360 | chrome | Firefox | 猎豹 | 搜狗 | ||
win7 | 8.0.1.222 | 41.0.2272.76 | 46 | 5.9.109 | 7 | 9.3.1 |
winXP | 8.0.1.222 | 43.0.2357.81 | 46 | 5.9.109 | 7 | 9.3.1 |
win10 | 8.0.1.222 | 43.0.2357.81 | 46 | 5.9.109 | 7 | 9.3.1 |
mac | 无MAC版 | 43.0.2357.81 | 46 | 无MAC版 | 无MAC版 | 无MAC版 |
2、详细方法调用及参数配置
方案切换
如果想切换评测的方案,可以销毁 引擎对象,重新创建一个
测评流程
初始化引擎对象 -> 收到初始化成功回调 -> 执行开始评测方法 -> 执行停止评测方法 -> 等待测评结果回调
2.1 初始化方法
const params = {
// 应用id
applicationId: "",
// 用户id
userId: "",
warrantId: "",
// SDK 回调
micAllowCallback: function () {
console.log("麦克风已经允许");
},
micForbidCallback: function () {
console.log("麦克风没有被允许");
},
playAudioComplete: function (data) {
console.log("播放完毕");
},
playAudioError: function (data) {
console.log("播放出错");
},
engineBackResultDone: function (msg) {
console.log("测评成功");
myRecordResult(msg);
},
engineBackResultFail: function (msg) {
console.error("测评失败");
console.log(msg);
},
engineFirstInitDone: function () {
console.log("初始化成功");
},
micVolumeCallback: function (data) {
console.log("录音音量大小为:" + data);
},
JSSDKNotSupport: function () {
alert("jssdk not support");
},
noNetwork: function () {
console.warn("暂无网络");
},
audioDataCallback: function (data, isLast) {
//需要在开始测评参数配置 saveAudio : 1 ,此回调才会响应
//如果在开始测评参数里配置 compress : 'raw',则会返回pcm格式的录音实时数据。否则会ogg格式的音频实时数据
console.log(data);
},
}
var myRecord = new window.EngineEvaluat(params)
params参数释义
名称 | 类型 | 说明 | 默认值 | 是否必填 |
applicationId | String | 商户id(appkey) | 无 | 是 |
customRecord | Boolean | 是否要自定义录音 | false | 否 |
userId | String | 用户ID | any | 是 |
coreType | String | 默认测评类型 | en.sent.score | 否 |
warrantId | String | 测评鉴权ID | 无 | 是 |
logIsOpen | Boolean | 日志是否开启 | true | 否 |
coreTimeout | Number | 连接服务器超时时间 | 10000 | 否 |
serverTimeout | Number | 返回测评结果超时时间 | 10000 | 否 |
intermission | Number | 最大空闲时间 | 10000 | 否 |
engineLinksAddress | Array | 链接地址 | ['api.cloud.ssapi.cn', 'gate-01.api.cloud.ssapi.cn', 'gate-02.api.cloud.ssapi.cn', 'gate-03.api.cloud.ssapi.cn'] | 否 |
engineFirstInitDone | Function | 引擎初始化成功回调 | 无 | 否 |
engineBackResultDone | Function | 引擎返回结果成功回调 | 无 | 否 |
engineBackResultFail | Function | 引擎返回结果失败回调 | 无 | 否 |
engineRequestIdCallback | Function | 引擎返回每次测评对应的RequestId回调 | 无 | 否 |
micAllowCallback | Function | 麦克风授权允许回调方法 | 无 | 否 |
micForbidCallback | Function | 麦克风授权拒绝回调方法,收到此回调则不能进行录音测评 | 无 | 否 |
micVolumeCallback | Function | 麦克风录音音量大小回调 | 无 | 否 |
JSSDKNotSupport | Function | sdk不支持当前浏览器回调 | 无 | 否 |
noNetwork | Function | 暂无网络回调 | 无 | 否 |
playAudioComplete | Function | 音频播放完成回调 | 无 | 否 |
playAudioError | Function | 音频加载或播放出错回调 | 无 | 否 |
logAccept | Function | 日志收集回调 | 无 | 否 |
audioDataCallback | Function | 录音数据实时回调 | 无 | 否 |
2.2 评测方案 (1):通过SDK默认录音功能进行评测
2.2.1 开始录音方法
// TODO: 根据自己的题型,生成相应的params,详细题型,可参考题型模块文档 https://open.singsound.com/doc/engine?type=engine-en-en.word.score&detail=false
var params = { coreType: 'en.word.score', refText:'word', precision:0.5, evalTime:6000 };
// 更新warrantId,warrantId默认时间是120分钟,可不需要每次都更新
params.warrantId = warrantId;
// 完整参数
myRecord.startRecord(params, done, fail);
名称 | 类型 | 说明 | 默认值 | 是否必填 |
params | object | 引擎开始测评参数 object类型 eg: { coreType: 'en.word.score', refText:'word', precision:0.5, evalTime:6000 }; evalTime:1、不填。不会自动停止录音 2、填。值必须是number类型,会按照参数值自动结束录音获取测评结果; | 无 | 是 |
done | Function | 引擎测评成功回调 | engineBackResultDone | 否 |
fail | Function | 引擎测评失败回调 | engineBackResultFail | 否 |
2.2.2 停止录音方法
选择一种即可
2.3.1 手动停止
针对没有设置 evalTime 的可手动调用停止方法
myRecord.stopRecord();
2.3.2 设置 evalTime 自动停止
在myRecord.startRecord(params)的params设置evalTime。evalTime的时长可参考以下方法
function defaultTime(refText) {
return 2000 + refText.trim().split(" ").length * 600 + 1000;
}
停止录音后,等待测评结果回调
2.3.3 录音结果分析
engineBackResultDone: function (msg) {
console.log("测评成功");
myRecordResult(msg);
},
engineBackResultFail: function (msg) {
console.error("测评失败");
console.log(msg);
},
//获取测评数据的结果分析
var myRecordResult = function (result) {
var recordResult = JSON.parse(result);
var gradeInfo = "";
// TODO:当前测评的 coreType
var coreType = getCoreType();
$("#point").text(recordResult.result.overall);
$("#totalPoint").text(recordResult.result.rank);
$("#result").text(JSON.stringify(recordResult));
var wordPoint = recordResult.result.details;
var url =
"https://files.cloud.ssapi.cn/" +
recordResult.applicationId +
"/" +
recordResult.recordId;
musicUrl = url + ".mp3";
//myRecord.loadAudio(musicUrl);
if (coreType == "en.word.score" || coreType == "en.sent.score") {
wordPoint.forEach(function (item) {
gradeInfo += wordLevel(item.score, item.char);
});
} else if (coreType == "en.pred.score") {
wordPoint.forEach(function (item) {
gradeInfo += wordLevel(item.score, item.char);
});
} else {
wordPoint.forEach(function (item) {
gradeInfo += wordLevel(item.score, item.char);
});
}
$("#audioUrl").text(url);
$("#grade").html(gradeInfo);
$("#score").text("测评完毕");
};
var wordLevel = function (point, word) {
var coreType = getCoreType();
var ref = $("#eng-txt").val();
var exce = 85;
var good = 75;
var fine = 55;
var exceColor = '<font color="#OOECOO">';
var goodColor = '<font color="#OOE3EE">';
var fineColor = '<font color="#5B5B5B">';
var badColor = '<font color="#FF5151">';
if (
coreType == "en.word.score" ||
coreType == "en.sent.score" ||
coreType == "en.pred.score"
) {
if (point >= exce) {
var level = " " + exceColor + word + "</font>";
} else if (good <= point && point < exce) {
var level = " " + goodColor + word + "</font>";
} else if (fine <= point && point < good) {
var level = " " + fineColor + word + "</font>";
} else {
var level = " " + badColor + word + "</font>";
}
} else {
if (point >= exce) {
var level = exceColor + word + "</font>";
} else if (good <= point && point < exce) {
var level = goodColor + word + "</font>";
} else if (fine <= point && point < good) {
var level = fineColor + word + "</font>";
} else {
var level = badColor + word + "</font>";
}
}
return level;
};
2.2.3 取消录音方法
myRecord.cancelRecord();
执行取消录音后,不再返回本次测评的结果
2.3 评测方案 (2):自定义录音功能进行评测
2.3.1 开启自定义录音
在初始化SDK的时候设置 customRecord 为 true
参考代码:
var myRecord = new EngineEvaluat({
customRecord: true,
applicationId: appid,
userId: userId,
micAllowCallback: function () {
console.log("麦克风已经允许");
},
micForbidCallback: function () {
console.log("麦克风没有被允许");
},
playAudioComplete: function (data) {
console.log("播放完毕");
},
playAudioError: function (data) {
console.log("播放出错");
},
engineBackResultDone: function (msg) {
console.log("评测成功");
myRecordResult(msg);
},
engineBackResultFail: function (msg) {
console.error("评测失败");
console.log(msg);
},
engineFirstInitDone: function () {
console.log("初始化成功");
},
micVolumeCallback: function (data) {
console.log("录音音量大小为:" + data);
},
JSSDKNotSupport: function () {
alert("jssdk not support");
},
noNetwork: function () {
console.warn("暂无网络");
},
audioDataCallback: function (data, isLast) {
//需要在开始测评参数配置 saveAudio : 1 ,此回调才会响应
//如果在开始测评参数里配置 compress : 'raw',则会返回pcm格式的录音实时数据。否则会ogg格式的音频实时数据
console.log(data);
},
});
2.3.2 设置start参数
var params = { coreType: 'en.word.score', refText:'word' };
// 鉴权参数,必填
params.warrantId = warrantId;
// 使用wav,非必须
params.audio = {
audioType: "wav"
};
// 建议使用ogg格式,评测效果比较好,SDK兼容也是最好的。默认就是ogg的,不需要设置
myRecord.start(params);
js中自定义录音的参考代码:
// 初始化音频录制
async function initAudio() {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
audioContext = new (window.AudioContext || window.webkitAudioContext)();
audioInput = audioContext.createMediaStreamSource(stream);
scriptProcessor = audioContext.createScriptProcessor(4096, 1, 1);
audioInput.connect(scriptProcessor);
scriptProcessor.connect(audioContext.destination);
scriptProcessor.onaudioprocess = (audioProcessingEvent) => {
if (!isRecording) return;
const inputBuffer = audioProcessingEvent.inputBuffer;
const inputData = inputBuffer.getChannelData(0);
// 重新采样到 16000 Hz
const resampledBuffer = resample(
inputData,
audioContext.sampleRate,
16000
);
// 将 Float32Array 转换为 PCM
const pcmData = convertFloat32ToPCM(resampledBuffer);
// 将 PCM 转换为 Base64
const base64String = arrayBufferToBase64(pcmData.buffer);
console.log("Real-Time PCM Data (Base64):", base64String);
// 这里可以把 base64String 传到服务端或
myRecord.feed(base64String);
};
} catch (error) {
console.error("Error accessing microphone:", error);
}
}
// 重新采样函数
function resample(inputData, inputSampleRate, targetSampleRate) {
const sampleRateRatio = inputSampleRate / targetSampleRate;
const newLength = Math.round(inputData.length / sampleRateRatio);
const resampledData = new Float32Array(newLength);
for (let i = 0; i < newLength; i++) {
const originalIndex = i * sampleRateRatio;
const lowerIndex = Math.floor(originalIndex);
const upperIndex = Math.ceil(originalIndex);
const interpolation = originalIndex - lowerIndex;
resampledData[i] =
(1 - interpolation) * inputData[lowerIndex] +
interpolation * inputData[upperIndex];
}
return resampledData;
}
// 将 Float32Array 转换为 PCM 16位整数
function convertFloat32ToPCM(float32Array) {
let pcmArray = new Int16Array(float32Array.length);
for (let i = 0; i < float32Array.length; i++) {
let s = Math.max(-1, Math.min(1, float32Array[i]));
pcmArray[i] = s < 0 ? s * 0x8000 : s * 0x7fff;
}
return pcmArray;
}
// 将 ArrayBuffer 转换为 Base64 编码
function arrayBufferToBase64(buffer) {
let binary = "";
const bytes = new Uint8Array(buffer);
for (const byte of bytes) {
binary += String.fromCharCode(byte);
}
return window.btoa(binary);
}
// 开始录音
function startRecording() {
if (!audioContext || !scriptProcessor) {
console.error("Audio context or script processor not initialized");
return;
}
isRecording = true;
console.log("Recording started");
}
// 停止录音
function stopRecording() {
if (isRecording) {
isRecording = false;
console.log("Recording stopped");
}
}
2.3.3 调用feed方法
// 将你们的录音流转成ogg格式 ArrayBuffer Base64 encode 后的字符串
myRecord.feed(base64String);
2.3.4 调用stop方法
myRecord.stop();
回调engineBackResultDone方法回调结果
2.4.1 文件整体上传测评
var params = { coreType: 'en.word.score', refText:'word' };
// 鉴权参数,必填
params.warrantId = warrantId;
// 建议使用pcm格式的文件,评测效果比较好,SDK兼容也是最好的。默认就是ogg的,不需要设置
myRecord.wholeFileUpload(evt, params);
名称 | 类型 | 说明 | 默认值 | 是否必填 |
evt | Object | 上传文件压缩后对象 | 无 | 是 |
params | Object | 引擎测评参数{coreType: “en.word.score”, refText:”word”} | 无 | 是 |
<input type="file" id="fileInput" accept="audio/*">
document.getElementById('fileInput').addEventListener('change', function(event) {
engine.wholeFileUpload(event, { coreType: 'en.sent.score', refText: 'word' });
});
2.4.2 结束评测
自动结束,不需要额外设置,等待回调即可。
2.4 销毁引擎
destroyEngine(): 销毁引擎,关闭 WebSocket。
2.5 常用功能设置
2.5.1 设置麦克风音量
myRecord.setMicVolume(num);
名称 | 类型 | 说明 | 默认值 | 是否必填 |
num | Number | 设置麦克风音量 | 1.5 | 是 |
2.6 录音音频播放
2.6.1 初始化播放音频
var url =
"https://files.cloud.ssapi.cn/" +
recordResult.applicationId +
"/" +
recordResult.recordId;
var musicUrl = url + ".mp3";
myRecord.loadAudio(musicUrl);
参数:@url;
名称 | 类型 | 说明 | 默认值 | 是否必填 |
url | String | 音频播放地址 | 无 | 是 |
2.6.2 播放音频
myRecord.playAudio();
2.6.3 暂停播放音频
myRecord.pauseAudio();
2.6.4 停止播放音频
myRecord.stopAudio();
2.6.5 获取音频时长
let duration = myRecord.getDuration();
2.6.6 获取音频播放进度
let currentTime = myRecord.getCurrentTime();
2.6.7 设置音频音量
myRecord.setAudioVolume(num);
名称 | 类型 | 说明 | 默认值 | 是否必填 |
num | Number | 设置音频音量 | 0.8 | 是 |