fix: 前端录音改用WAV格式(PCM 16kHz),兼容模型端

This commit is contained in:
2026-04-21 18:35:50 +08:00
parent b3cfae14a9
commit 04e8405558
7 changed files with 173 additions and 28 deletions

View File

@@ -304,6 +304,9 @@
let audioChunks = [];
let conversationId = null;
let audioContext = null;
let audioStream = null;
let scriptProcessor = null;
let recordedBuffers = [];
// 元素
const recordBtn = document.getElementById('recordBtn');
@@ -333,10 +336,58 @@
}
}
// 创建 WAV 文件
function createWavFile(audioBuffer, sampleRate = 16000) {
const numChannels = 1;
const bitsPerSample = 16;
const bytesPerSample = bitsPerSample / 8;
const blockAlign = numChannels * bytesPerSample;
const byteRate = sampleRate * blockAlign;
const dataSize = audioBuffer.length * bytesPerSample;
const headerSize = 44;
const totalSize = headerSize + dataSize;
const buffer = new ArrayBuffer(totalSize);
const view = new DataView(buffer);
// WAV header
writeString(view, 0, 'RIFF');
view.setUint32(4, totalSize - 8, true);
writeString(view, 8, 'WAVE');
writeString(view, 12, 'fmt ');
view.setUint32(16, 16, true); // fmt chunk size
view.setUint16(20, 1, true); // audio format (PCM)
view.setUint16(22, numChannels, true);
view.setUint32(24, sampleRate, true);
view.setUint32(28, byteRate, true);
view.setUint16(32, blockAlign, true);
view.setUint16(34, bitsPerSample, true);
writeString(view, 36, 'data');
view.setUint32(40, dataSize, true);
// 写入音频数据
floatTo16BitPCM(view, 44, audioBuffer);
return new Blob([buffer], { type: 'audio/wav' });
}
function writeString(view, offset, string) {
for (let i = 0; i < string.length; i++) {
view.setUint8(offset + i, string.charCodeAt(i));
}
}
function floatTo16BitPCM(view, offset, input) {
for (let i = 0; i < input.length; i++, offset += 2) {
const s = Math.max(-1, Math.min(1, input[i]));
view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
}
}
// 初始化录音
async function initAudio() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
audioStream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true,
noiseSuppression: true,
@@ -344,22 +395,21 @@
}
});
audioContext = new (window.AudioContext || window.webkitAudioContext)();
// 创建 MediaRecorder
mediaRecorder = new MediaRecorder(stream, {
mimeType: 'audio/webm'
audioContext = new (window.AudioContext || window.webkitAudioContext)({
sampleRate: 16000
});
mediaRecorder.ondataavailable = (e) => {
audioChunks.push(e.data);
const source = audioContext.createMediaStreamSource(audioStream);
scriptProcessor = audioContext.createScriptProcessor(4096, 1, 1);
scriptProcessor.onaudioprocess = (e) => {
if (isRecording) {
recordedBuffers.push(e.inputBuffer.getChannelData(0).slice());
}
};
mediaRecorder.onstop = async () => {
const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
audioChunks = [];
await sendAudio(audioBlob);
};
source.connect(scriptProcessor);
scriptProcessor.connect(audioContext.destination);
return true;
} catch (e) {
@@ -371,13 +421,12 @@
// 开始录音
async function startRecording() {
if (!mediaRecorder) {
if (!audioContext) {
const success = await initAudio();
if (!success) return;
}
audioChunks = [];
mediaRecorder.start();
recordedBuffers = [];
isRecording = true;
recordBtn.classList.add('recording');
@@ -390,16 +439,29 @@
// 停止录音
function stopRecording() {
if (mediaRecorder && isRecording) {
mediaRecorder.stop();
if (isRecording) {
isRecording = false;
// 合并所有缓冲区
const totalLength = recordedBuffers.reduce((acc, buf) => acc + buf.length, 0);
const mergedBuffer = new Float32Array(totalLength);
let offset = 0;
for (const buf of recordedBuffers) {
mergedBuffer.set(buf, offset);
offset += buf.length;
}
// 创建 WAV 文件
const wavBlob = createWavFile(mergedBuffer, 16000);
recordBtn.classList.remove('recording');
recordBtn.querySelector('.icon').textContent = '🎤';
recordBtn.querySelector('.text').textContent = '点击录音';
recordStatus.textContent = '处理中...';
recordStatus.classList.remove('recording');
waveform.style.display = 'none';
sendAudio(wavBlob);
}
}
@@ -409,7 +471,7 @@
showLoading();
const formData = new FormData();
formData.append('audio', audioBlob, 'recording.webm');
formData.append('audio', audioBlob, 'recording.wav');
if (conversationId) {
formData.append('conversation_id', conversationId);
}