前言
最近在做一些关
于AIGC
的项目,整体感受:aigc 真的很强大🐮,我们的产品刚开始只是对内使用,现在开放对外了,我们是做教育的,有很多关于教育场景的功。下面会给大家贴上我们项目的链接,如果有需要购买的可以找我要邀请码,一个月几十块钱,即可拥有! AIGC 体验链接。
在众多功能中,使用频率最多就是智能对话功能,在对话里可以选择多种大模型,例如:gpt4.0、讯飞星火、文心一言、通义千问、智谱等等,原来的功能是只支持文字输入,后面支持移动端了以后,需要添加一个录音的功能:按下收音,抬起将音频转成文字,回填到输入框里,后面在其他的项目里也添加了这个功能,所以这里就做个详细记录。
一、获取音频
音频数据获取代码如下:
const getChannelAndSampleRate = (
fileType: "blob" | "file",
file: File | Blob,
) => {
const audioContext = new window.AudioContext();
// 如果拿到的是file 文件,需要转成blob 文件后,再进行数据的获取
const fileBlob =
fileType === "file" ? new Blob([file], { type: file.type }) : file;
const fileReader = new FileReader();
fileReader.readAsArrayBuffer(fileBlob);
fileReader.onloadend = () => {
const arrayBuffer = fileReader.result as ArrayBuffer;
if (arrayBuffer) {
audioContext.decodeAudioData(
arrayBuffer,
(audioBuffer) => {
console.log("采样率:", audioBuffer.sampleRate);
console.log("通道:", audioBuffer.numberOfChannels);
message.success("音频解码成功!");
},
(e) => {
message.success("解码音频数据时出错,请重新上传音频!");
console.error("解码音频数据时出错", e);
},
);
}
};
};
三、在web
浏览器开发录音功能
最近接到了一个新需求,在
web
端实现一个类似手机微信的录音转文字的功能,如下图
录音:
将录音文件转文字:
下面我们来看一下技术方面该怎么处理:
- 获取浏览器的麦克风权限
- 怎么获取录制后的音频
- 将音频发送给后端,调用后端接口实现
1. 当按下收音键后,获取麦克风权限:
// 使用useState钩子初始化mediaRecorder状态,初始值为undefined
const [mediaRecorder, setMediaRecorder] = useState<MediaRecorder>();
....
// 这里尝试获取用户媒体设备的权限
navigator.mediaDevices
?.getUserMedia({ audio: true }) // 使用getUserMedia方法请求麦克风权限,audio:true表示请求音频输入
.then(function (stream) { // 成功获取权限后的回调函数,参数stream是包含音频数据的媒体流对象
let recorder = new MediaRecorder(stream); // 创建一个MediaRecorder实例,用于录制音频,以流为数据源
setMediaRecorder(recorder); // 更新组件状态,保存MediaRecorder实例
recorder.start(); // 开始录制音频
})
.catch(function (err) { // 获取麦克风权限失败的错误处理
message.error("无法获取麦克风权限");
console.error("无法获取麦克风权限", err);
});
2. 当抬起收音键后的状态修改:
....
if (mediaRecorder && mediaRecorder.state === "recording") {
mediaRecorder.stop();
}
3. 监听音频变化,当录音停止后,获取音频 ,处理音频,进行文字转换
let audioChunks: BlobPart[] = [];
const [userInput, setUserInput] = useState<string>(""); // 存储转换后的文字
// 重置状态
const resetAudioData = () => {
audioChunks = [];
};
....
if (mediaRecorder && mediaRecorder.state === "recording") {
// 当录音数据可用时的事件处理
mediaRecorder.ondataavailable = function (event) {
// 将录音数据块(event.data)添加到数组中,用于后续处理
audioChunks.push(event.data);
};
// 录音停止之后的回调函数
mediaRecorder.onstop = async function () {
const audioBlob = new Blob(audioChunks);
const file = new File([audioBlob], "filename.wav", {
type: "audio/wav",
lastModified: new Date().getTime(),
});
// 解析音频,拿到采样率和声道(业务需要)
getChannelAndSampleRate(audioBlob, {
onSuccess: (audioBuffer) => {
message.success("音频解码成功!");
const params = {
file,
audioType: "wav",
sampleRate: audioBuffer.sampleRate, // 音频采样率
channel: audioBuffer.numberOfChannels, // 音频声道数量
lang: "multilingual",
};
// 调用后端接口,将音频文件发送到后端进行文字转换
audioToText(params, {
onSuccess: (res: { asrResult: string }) => {
// 将转换得到的文字结果填入用户输入框,完成语音到文字的转换
setUserInput(res.asrResult);
// 重置录音状态,用于下一次录音
resetAudioData();
},
onError: function (
error: Error,
statusCode?: number | undefined,
): void {
message.error("转换失败,请重试!");
resetAudioData();
console.error(error);
},
});
},
onError: (e: Error) => {
message.error("解码音频数据时出错,请重新录制音频!");
resetAudioData();
console.error(e);
},
});
// 遍历并停止所有的媒体输入轨道,结束录音
mediaRecorder.stream.getTracks().forEach((track) => track.stop());
};
}
⚠️ 常规浏览器 && 旧版本浏览器:
if(navigator.mediaDevices){
// 常规浏览器,参考上面👆的代码
}
// 兼容旧版本浏览器
else if('getUserMedia' in navigator){
navigator
?.getUserMedia({ audio: true })
.then((stream: any) => {
const recorder = new MediaRecorder(stream);
setMediaRecorder(recorder);
recorder.start();
})
.catch(() => {
handleErrorToast('无法获取麦克风权限');
});
} else {
message.info('当前手机版本不支持录音');
}
三、通过File
或者 Blob
文件获取音频的采样率和通道
什么是音频的采样率?
音频采样率就像是我们用来记录声音的“快照”频率。采样率越高,你记录的声音就越细致,就像高分辨率的相机能够捕捉更清晰的图片一样。更高的采样率可以更准确地捕捉和再现声音的高频细节,所以在理论上是可以提供更精确的声音再现。
举个例子,如果用相机比喻,较低分辨率的照片可能会模糊或丢失细节,而高分辨率的照片则能够更准确地显示细节。同样,在音频中,较低的采样率可能导致声音的某些细微部分被忽略,而高的采样率则能够捕捉到这些细节。
什么是声道?
音频的声道是指录制和播放音频时所用的独立音频信号路径。每个声道可以承载一定的音频信息,一般分为单声道、立体声、环绕声。
-
单声道:在单声道系统中,所有的声音都通过一个单一的声道播放
-
立体声:系统使用两个声道,通常标记为“左”和“右”。在录制时通常会使用两个麦克风分别捕捉到两个声道的声音。对于听者而言,立体声可以创造一种空间感,仿佛声音从不同方向到达听者耳朵
-
环绕声:环绕声系统使用多个声道(通常是五个以上,如5.1或7.1声道系统)来创造更为沉浸和三维的音响效果。这种配置可以更准确地模拟现实环境中声音的位置和动态,为听者提供更立体、更真实的听觉体验。
四、以下是关于交互方面的优化:
1. 按下和抬起时,给一个快速震动:
// 快速震动
const vibrateOnce: any = () => {
if ("vibrate" in navigator) {
navigator.vibrate(50);
} else {
console.error("Vibration API is not supported by this browser.");
}
};
2. 当用户按下收音键后,移动点击位置,当移动数值超过安全数值后,则认为是误操作:
按下时记录点击位置,抬起收音键时,可以拿到当前点击的位置,当移动距离超过指定数值,取消收音:
// 计算移动距离(容错率为100px)
const hasMovedTooFar = (
start: { x: number; y: number },
end: { x: number; y: number },
) => {
const tolerance = 100;
var diff = {
x: end.x - start.x,
y: end.y - start.y,
};
return Math.abs(diff.x) >= tolerance || Math.abs(diff.y) >= tolerance;
};
3. 当收音结束后,获取的音频时间过短,提示录音时间过短
const intervalTime = (Date.now() - startTime) / 1000;
// 不可低于1秒
if (intervalTime < 1) {
message.error("录音时间过短");
....
return;
}