function transcode(audioData) { // 将音频数据转换为16kHz采样率 let output = to16kHz(audioData); // 将音频数据转换为16位PCM格式 output = to16BitPCM(output); // 将DataView对象转换为字节数组 output = Array.from(new Uint8Array(output.buffer)); return output; } function to16kHz(audioData) { // 创建一个新的Float32Array以避免修改原始数据 let data = new Float32Array(audioData); // 计算目标采样率下的数据点数量 let fitCount = Math.round(data.length * (16000 / 44100)); // 创建一个新的Float32Array用于存储转换后的数据 let newData = new Float32Array(fitCount); // 计算插值的步长,用于将44100Hz的数据压缩到16000Hz let springFactor = (data.length - 1) / (fitCount - 1); // 设置第一帧数据 newData[0] = data[0]; for (let i = 1; i < fitCount - 1; i++) { // 计算映射到原始数据的浮点索引 let tmp = i * springFactor; // 计算索引的左右整数位置 let before = Math.floor(tmp); let after = Math.ceil(tmp); // 计算线性插值的权重 let atPoint = tmp - before; // 使用线性插值法计算采样点 newData[i] = data[before] + (data[after] - data[before]) * atPoint; } // 设置最后一帧数据 newData[fitCount - 1] = data[data.length - 1]; return newData; } function to16BitPCM(input) { // 计算输出数据所需的字节数,每个样本占2个字节 let dataLength = input.length * 2; // 创建ArrayBuffer以存储二进制数据 let dataBuffer = new ArrayBuffer(dataLength); // 使用DataView操作二进制数据 let dataView = new DataView(dataBuffer); let offset = 0; for (let i = 0; i < input.length; i++, offset += 2) { // 将浮点数限制在[-1, 1]范围内 let s = Math.max(-1, Math.min(1, input[i])); // 将浮点数转换为16位有符号整数,并按小端字节序写入DataView dataView.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true); } return dataView; } function transToAudioData( audioDataStr, fromRate = 16000, toRate = 22505, ) { // 将Base64音频数据解码为Int16Array let outputS16 = base64ToS16(audioDataStr); // 将Int16Array数据转换为Float32Array let output = transS16ToF32(outputS16); // 进行采样率转换,将音频数据从fromRate转换到toRate output = transSamplingRate(output, fromRate, toRate); // 转换为普通数组格式并返回 output = Array.from(output); return output; } function transSamplingRate( data, fromRate = 44100, toRate = 16000, ) { // 计算目标采样点数 let fitCount = Math.round(data.length * (toRate / fromRate)); // 创建新的Float32Array以存储采样率转换后的数据 let newData = new Float32Array(fitCount); // 计算数据点之间的缩放因子 let springFactor = (data.length - 1) / (fitCount - 1); // 设置起始点 newData[0] = data[0]; for (let i = 1; i < fitCount - 1; i++) { // 计算缩放后的位置 let tmp = i * springFactor; let before = Math.floor(tmp); let after = Math.ceil(tmp); // 计算线性插值 let atPoint = tmp - before; newData[i] = data[before] + (data[after] - data[before]) * atPoint; } // 设置终止点 newData[fitCount - 1] = data[data.length - 1]; return newData; } function transS16ToF32(input) { // 创建临时数组存储转换后的数据 let tmpData = []; for (let i = 0; i < input.length; i++) { // 将Int16数据标准化到[-1, 1]区间 let d = input[i] < 0 ? input[i] / 0x8000 : input[i] / 0x7fff; tmpData.push(d); } // 转换为Float32Array return new Float32Array(tmpData); } function base64ToS16(base64AudioData) { // 使用atob解码Base64字符串为二进制字符串 base64AudioData = atob(base64AudioData); // 创建Uint8Array以存储字节数据 const outputArray = new Uint8Array(base64AudioData.length); for (let i = 0; i < base64AudioData.length; ++i) { outputArray[i] = base64AudioData.charCodeAt(i); } // 将Uint8Array视为Int16Array return new Int16Array(new DataView(outputArray.buffer).buffer); }