ブラウザで録音してwavで保存 の丸パクリです。
勉強のためにソースコードを読みながら整形しました。
<!DOCTYPE html>
<html lang="ja">
<head>
<title>Save audio data</title>
</head>
<body>
<a id="download">Download</a>
<button id="stop">Stop</button>
<br>
ref: <a href="https://qiita.com/optimisuke/items/f1434d4a46afd667adc6">ブラウザで録音してwavで保存 - Qiita</a>
<script>
function writeString(view, offset, string) {
// https://medium.com/better-programming/how-to-iterate-through-strings-in-javascript-65c51bb3ace5
// 文字列はfor-ofやforEachでイテレートできる
[...string].forEach((char, index) => {
view.setUint8(offset + index, char.charCodeAt(0))
})
};
// 8bitの情報を16bitに変換する
function floatTo16BitPCM(output, offset, input) {
input.forEach((datum, index) => {
// 何しているのか全然わからない
const s = Math.max(-1, Math.min(1, datum));
const pcm = s < 0 ? s * 0x8000 : s * 0x7FFF
// 2バイトずつ進めて書き込む
output.setInt16(offset + (index * 2), pcm, true);
})
};
function encodeToWAV(samples, sampleRate) {
const headerSize = 44;
// 8bitを16bitに変換するので、データサイズは2倍になる
const outputDataLength = samples.length * 2;
const outBuffer = new DataView(new ArrayBuffer(headerSize + outputDataLength));
writeString(outBuffer, 0, 'RIFF'); // RIFFヘッダ
outBuffer.setUint32(4, 32 + outputDataLength, true); // これ以降のファイルサイズ
writeString(outBuffer, 8, 'WAVE'); // WAVEヘッダ
writeString(outBuffer, 12, 'fmt '); // fmtチャンク
outBuffer.setUint32(16, 16, true); // fmtチャンクのバイト数
outBuffer.setUint16(20, 1, true); // フォーマットID
outBuffer.setUint16(22, 1, true); // チャンネル数
outBuffer.setUint32(24, sampleRate, true); // サンプリングレート
outBuffer.setUint32(28, sampleRate * 2, true); // データ速度
outBuffer.setUint16(32, 2, true); // ブロックサイズ
// これを8ビットにすればPCM変換が簡単になる?
outBuffer.setUint16(34, 16, true); // サンプルあたりのビット数
writeString(outBuffer, 36, 'data'); // dataチャンク
outBuffer.setUint32(40, outputDataLength, true); // 波形データのバイト数
floatTo16BitPCM(outBuffer, headerSize, samples); // 波形データ
return outBuffer;
};
// Float32Arrayの配列を一つのFloat32Arrayにまとめる
function mergeBuffers(audioData) {
const bufferSize = audioData[0].length
const totalBufferSize = audioData.length * bufferSize
const totalBuffer = new Float32Array(totalBufferSize);
for (let i = 0; i < audioData.length; i++) {
totalBuffer.set(audioData[i], i * bufferSize)
}
return totalBuffer;
};
function toURL(dataview, fileType) {
const audioBlob = new Blob([dataview], { type: fileType });
return window.URL.createObjectURL(audioBlob);
}
// 録音データの全体サイズがわからないので、バッファサイズ単位で配列に保存
function copyAudioPerUnit(input, bufferSize, audioData) {
const bufferData = new Float32Array(bufferSize);
for (var i = 0; i < bufferSize; i++) {
bufferData[i] = input[i];
}
audioData.push(bufferData);
};
function copyAudioData(audioData, stream) {
// 音声データをaudioDataに溜め込むScriptProcessorを作る
// https://developer.mozilla.org/ja/docs/Web/API/AudioContext/createScriptProcessor
const BUFFER_SIZE = 1024;
const audioContext = new AudioContext();
const scriptProcessor = audioContext.createScriptProcessor(BUFFER_SIZE, 1, 1);
scriptProcessor.onaudioprocess = (e) => copyAudioPerUnit(e.inputBuffer.getChannelData(0), BUFFER_SIZE, audioData);
// ストリームの途中にScriptProcessorを差し込む
const mediaStreamSource = audioContext.createMediaStreamSource(stream);
mediaStreamSource.connect(scriptProcessor);
scriptProcessor.connect(audioContext.destination);
return audioContext
};
function downloadWavFile(audioData, sampleRate) {
const dataview = encodeToWAV(mergeBuffers(audioData), sampleRate);
const downloadLink = document.getElementById('download');
downloadLink.href = toURL(dataview, 'audio/wav');
downloadLink.download = 'test.wav';
downloadLink.click();
}
// 録音開始
!(async function () {
// STOPボタンを押したらWavにエンコードしてダウンロードする。
const audioData = [];
let audioSampleRate = null;
let audioContext = null;
document
.getElementById('stop')
.addEventListener('click', async (e) => {
downloadWavFile(audioData, audioSampleRate);
await audioContext.close()
e.target.setAttribute('disabled', 'disabled');
});
// https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
// getUserMediaしたときに録音開始するので、使いにくい。
// 事前にAudioContextを用意したいです。
const stream = await window
.navigator
.mediaDevices
.getUserMedia({ audio: true, video: false })
audioContext = copyAudioData(audioData, stream);
audioSampleRate = audioContext.sampleRate;
})()
</script>
</body>
</html>