实现一个mp3格式的录音器
先说下背景: 要实现一个人工智能聊天对话的功能,前端录音转成文本通过http发给服务端去回复,转文本这块是调用科大讯飞的接口,我前端录出来通过webSocket传给后端,因为是websocket传输,后端需要的格式是mp3格式二进制字节数组。
我一开始用浏览器的MediaRecorder API,代码实现如下:(这个实现有问题,记录下踩坑过程希望帮到读者,可快速跳过直接看最终实现)
整体流程设计(这里只做参考,我们现在只讨论录音器的封装)
前端录音 >>> 得到wav格式的blob >>> 转成MP3格式的blob >>> blob转二进制数据binaryArray >>> websocket发送给服务端 >>> 得到text文本
由于浏览器通过这个API录制的音频不是mp3格式的,只支持ogg、wav 等等,而科大讯飞那边需要的是mp3格式的,因此封装了一个将wav格式的blob数据转成MP3格式的音频blob的方法:wavBlob2Mp3Blob。
//wavBlob 转 mp3Blob
export function wavBlob2Mp3Blob(wavBlob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = function (event) {
const arrayBuffer = event.target.result;
const dataView = new DataView(arrayBuffer);
try {
const mp3Data = encodeWavToMp3(dataView);
const mp3Blob = new Blob([mp3Data], { type: "audio/mp3" });
resolve(mp3Blob);
} catch (error) {
reject(error);
}
};
reader.onerror = function (error) {
reject(error);
};
reader.readAsArrayBuffer(wavBlob);
});
}
由于使用websocket传输数据,文件的传输一般用binaryArray(字节数组),因此封装了将blob转成二进制数据的方法:blobToBinary:
//blob转二进制
export function blobToBinary(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const arrayBuffer = reader.result;
const binaryData = new Uint8Array(arrayBuffer);
resolve(binaryData);
};
reader.onerror = reject;
reader.readAsArrayBuffer(blob);
});
}
import { wavBlob2Mp3Blob } from "@/views/virtualTeacher/utils/wavBlob2Mp3Blob";
import { blobToBinary } from "@/views/virtualTeacher/utils/tool";
class Recorder {
constructor() {
this.recorder = null;
this.audioChunks = []; // blob格式录音数据切片组
this.audioBlob = null; // blob格式录音数据切片组
this.isRecording = false;
this.mp3BlobData = null;
}
//初始化
async init() {
const stream = await navigator.mediaDevices.getUserMedia({
audio: true,
});
this.recorder = new MediaRecorder(stream, { mimeType: "audio/wav" });
this.recorder.ondataavailable = (e) => {
console.log("e.data录音data", e.data);
this.audioChunks.push(e.data);
};
}
//开始录音
startRecord() {
this.audioChunks.length = 0;
this.audioBlob = null;
if (this.mediaRecorder && this.mediaRecorder.state === "recording") {
this.stopRecord(); // 如果已经在录音,先停止
}
this.recorder.start();
this.isRecording = true;
}
// 结束录音
stopRecord() {
return new Promise((resolve) => {
this.recorder.onstop = async (e) => {
console.log("录音结束");
this.audioBlob = new Blob(this.audioChunks, { type: "audio/wav" });
this.mp3BlobData = await wavBlob2Mp3Blob(this.audioBlob);
this.binaryData = await blobToBinary(this.audioBlob);
resolve(); // 在 onstop 回调中 resolve Promise,不然是异步的,有可能你stop停止录制后立马拿到的audioBlob是空
};
this.recorder.stop();
this.isRecording = false;
});
}
// 下载录音文件
//(顺手做了下这个下载功能,我们其实使用不到,可以忽略,不过帮助了我调试发现问题)
downloadRecord() {
if (!this.audioBlob) {
console.error("没有可用的MP3录音数据");
return;
}
const url = URL.createObjectURL(this.audioBlob);
const a = document.createElement("a");
a.href = url;
a.download = "recording.wav"; // 设置下载文件名
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url); // 释放内存
}
// 播放录音
play() {
if (!this.audioBlob) {
console.error("没有可用的录音数据");
return;
}
const audio = new Audio(URL.createObjectURL(this.audioBlob));
audio.play();
// URL.revokeObjectURL(url); // 释放内存
// 我怕释放内存就不能播放了,我没试就注释掉了
}
}
export { Recorder, isRecording };
这样做完用websocket把数据传过去,websocket没有返回,查看科大讯飞文档发现格式不对的时候可能会返回为空,于是添加了downloadRecord方法,去下载录音文件确定文件是否ok,下载下来后发现不能播放,只能在浏览器内部播放,拿到浏览器外边的系统播放器和其他播放软件都无法播放。那我知道了,就是这个文件有问题。
千难万险之后知道了:浏览器的mediaRecorder录出来的录音数据有问题,拿到外部播放不了。。。。
有人懂行的可以讲一讲。
然后我就找了个库:recorder-core,然后封装一个Recorder的类。
以下是最终行得通的实现
安装recorder-core
npm i recorder-core
import RecorderCore from "recorder-core";
//引入mp3格式支持文件;如果需要多个格式支持,把这些格式的编码引擎js文件放到后面统统引入进来即可
import "recorder-core/src/engine/mp3";
import "recorder-core/src/engine/mp3-engine";
import { blobToBinary } from "../../../utils/tool";
const config = {
sampleRate: 16000, // 采样率 16kHz 或者你可以设置为 8000
bitDepth: 16, // 位长 16bit
channelCount: 1, // 单声道
};
class Recorder {
constructor() {
this.isRecording = false;
this.recorder = null;
this.audioBlob = null; // blob格式录音数据切片组
this.mp3BlobData = null;
}
//
init() {
this.recorder = new RecorderCore(config);
// 录音打开
this.recorder.open(() => {
console.log("Recording opened");
});
}
startRecord() {
this.audioBlob = null;
// 录音开始回调
this.recorder.start(() => {
console.log("Recording started");
this.isRecording = true;
});
}
// 结束录音
stopRecord() {
// 录音结束回调
return new Promise((resolve, reject) => {
this.recorder.stop(async (blob, duration) => {
this.audioBlob = blob;
this.audioBinary = await blobToBinary(blob);
resolve(blob);
});
});
}
// 播放录音(也用不到,仅做参考,感觉可能以后能用到就写了)
play() {
const audio = new Audio(URL.createObjectURL(this.audioBlob));
audio.play();
}
// 下载录音
downloadRecord() {
if (!this.audioBlob) {
console.error("No recording available to download.");
return;
}
const blob = new Blob([this.audioBlob], { type: "audio/mp3" });
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "recording.mp3";
document.body.appendChild(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
}
}
export { Recorder };
外部使用的时候只需要导入这个类,实例化一个录音器,就可以对录音进行管理,获取录音数据,需要的同学可以按照自己需求改造封装。