<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>录制音频并下载为WAV文件</title>
</head>
<body>
<button id="startButton">开始录制</button>
<button id="stopButton" disabled>停止录制</button>
<a id="downloadLink" style="display: none">下载录音</a>
<script>
// 创建一个音频上下文
//let mediaRecorder;
const recordedChunks = [];
resample = 48000
document.getElementById('startButton').addEventListener('click', () => {
navigator.mediaDevices.getUserMedia({ audio: true })
.then((stream) => {
let track = stream.getAudioTracks()[0];
console.log(track.getCapabilities());
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
console.log("adadadad")
// 将麦克风输入连接到音频上下文
const microphone = audioContext.createMediaStreamSource(stream);
// 创建一个ScriptProcessorNode来处理音频数据
const scriptNode = audioContext.createScriptProcessor(4096, 1, 1);
// 监听ScriptProcessorNode的audioprocess事件
scriptNode.onaudioprocess = (event) => {
// 获取PCM数据
const inputBuffer = event.inputBuffer;
const pcmData = inputBuffer.getChannelData(0); // 获取单个声道的PCM数据
//console.log(pcmData)
// 存储PCM数据块
var data = interpolateArray(new Float32Array(pcmData), 16000, audioContext.sampleRate)
//console.log(data)
recordedChunks.push(data);
//recordedChunks.push(new Float32Array(pcmData))
};
// 连接音频节点
microphone.connect(scriptNode);
scriptNode.connect(audioContext.destination);
// 创建MediaRecorder并开始录制
//mediaRecorder = new MediaRecorder(stream);
//mediaRecorder.ondataavailable = (event) => {
// if (event.data.size > 0) {
// recordedChunks.push(event.data);
// }
//};
//mediaRecorder.onstop = () =>
//mediaRecorder.start();
document.getElementById('startButton').disabled = true;
document.getElementById('stopButton').disabled = false;
})
.catch((error) => {
console.error('获取麦克风权限失败:', error);
});
});
document.getElementById('stopButton').addEventListener('click', () => {
console.log('停止录制');
console.log(recordedChunks)
const pcmData = flattenArray(recordedChunks);
console.log(pcmData)
// 创建WAV文件头
const wavHeader = createWavHeader(pcmData.byteLength, 16000);
// 合并WAV文件头和PCM数据
const wavBlob = new Blob([wavHeader, pcmData], { type: 'audio/wav' });
//const wavBlob = new Blob([pcmData], { type: 'audio/pcm' });
// 创建下载链接
const downloadLink = document.getElementById('downloadLink');
downloadLink.href = URL.createObjectURL(wavBlob);
downloadLink.download = 'recorded.wav';
downloadLink.style.display = 'block';
document.getElementById('startButton').disabled = false;
document.getElementById('stopButton').disabled = true;
//if (mediaRecorder.state === 'recording') {
// mediaRecorder.stop();
//}
});
// for changing the sampling rate, data,
function interpolateArray(data, newSampleRate, oldSampleRate) {
var fitCount = Math.round(data.length*(newSampleRate/oldSampleRate));
var newData = new Array();
var springFactor = new Number((data.length - 1) / (fitCount - 1));
newData[0] = data[0]; // for new allocation
for ( var i = 1; i < fitCount - 1; i++) {
var tmp = i * springFactor;
var before = new Number(Math.floor(tmp)).toFixed();
var after = new Number(Math.ceil(tmp)).toFixed();
var atPoint = tmp - before;
newData[i] = this.linearInterpolate(data[before], data[after], atPoint);
}
newData[fitCount - 1] = data[data.length - 1]; // for new allocation
return newData;
};
function linearInterpolate(before, after, atPoint) {
return before + (after - before) * atPoint;
};
// 辅助函数:将二维数组扁平化为一维数组
function flattenArray(arrays, sampleRate) {
const buffer = new ArrayBuffer(arrays.length * 4096*2);
const view = new DataView(buffer);
let offset = 0
for (var i = 0; i < arrays.length; i++)
{
for (var j = 0; j < arrays[i].length; j++)
{
data = parseInt(arrays[i][j] * 32768)
view.setUint16(offset, data, true);
offset += 2
}
}
return buffer.slice(0, offset)
}
// 辅助函数:创建WAV文件头
function createWavHeader(dataSize, sampleRate) {
const buffer = new ArrayBuffer(44);
const view = new DataView(buffer);
// Chunk ID
view.setUint32(0, 0x52494646, false); // "RIFF"
// File size (excluding first 8 bytes)
console.log(dataSize)
view.setUint32(4, dataSize + 36, true);
// Format (WAVE)
view.setUint32(8, 0x57415645, false); // "WAVE"
// Subchunk 1 ID (fmt)
view.setUint32(12, 0x666D7420, false); // "fmt "
// Subchunk 1 size
view.setUint32(16, 16, true);
// Audio format (PCM)
view.setUint16(20, 1, true);
// Number of channels (1 for mono)
view.setUint16(22, 1, true);
// Sample rate
view.setUint32(24, sampleRate, true);
// Byte rate (sample rate * block align)
view.setUint32(28, sampleRate * 2, true);
// Block align (number of bytes per sample)
view.setUint16(32, 2, true);
// Bits per sample
view.setUint16(34, 16, true);
// Subchunk 2 ID (data)
view.setUint32(36, 0x64617461, false); // "data"
// Subchunk 2 size
view.setUint32(40, dataSize, true);
return buffer;
}
</script>
</body>
</html>