blog
브라우저에서 녹음한 Blob을 WAV 파일로 변환하기
Front-end / JavaScript#Web Audio API#Blob#WAV#MediaRecorder
MediaRecorder로 녹음한 오디오는 브라우저에 따라 webm·ogg 등으로 저장된다. 서버나 음성 인식 API가 WAV(PCM)만 받는 경우, 브라우저에서 직접 디코딩한 뒤 WAV 헤더를 붙여 변환할 수 있다.
흐름
FileReader로 Blob을ArrayBuffer로 읽는다.AudioContext.decodeAudioData로AudioBuffer(PCM 샘플)로 디코딩한다.- 44바이트 WAV 헤더를 쓰고, 샘플을 16-bit PCM으로 변환해 이어 붙인다.
코드
const writeString = (view, offset, string) => {
Array.from(string).forEach((char, index) => {
view.setUint8(offset + index, char.charCodeAt(0));
});
};
const encodeWav = (audioBuffer) => {
const format = 1; // PCM
const { sampleRate } = audioBuffer;
const numChannels = audioBuffer.numberOfChannels;
const byteRate = sampleRate * numChannels * 2; // 16-bit
const blockAlign = numChannels * 2;
const dataSize = audioBuffer.length * numChannels * 2;
const buffer = new ArrayBuffer(44 + dataSize);
const view = new DataView(buffer);
// WAV 헤더
writeString(view, 0, 'RIFF');
view.setUint32(4, 36 + dataSize, true);
writeString(view, 8, 'WAVE');
writeString(view, 12, 'fmt ');
view.setUint32(16, 16, true);
view.setUint16(20, format, true);
view.setUint16(22, numChannels, true);
view.setUint32(24, sampleRate, true);
view.setUint32(28, byteRate, true);
view.setUint16(32, blockAlign, true);
view.setUint16(34, 16, true);
writeString(view, 36, 'data');
view.setUint32(40, dataSize, true);
// 오디오 데이터
const dataView = new DataView(buffer, 44);
Array.from({ length: numChannels }).forEach((_, channelIndex) => {
const channelData = audioBuffer.getChannelData(channelIndex);
channelData.forEach((sample, sampleIndex) => {
const clampedSample = Math.max(-1, Math.min(1, sample));
const scaledSample = clampedSample * 0x7fff;
const byteOffset = sampleIndex * 2 + channelIndex * audioBuffer.length * 2;
dataView.setInt16(byteOffset, scaledSample, true);
});
});
return new Blob([buffer], { type: 'audio/wav' });
};
const convertBlobToWav = (blob) =>
new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (event) => {
const arrayBuffer = event.target.result;
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
audioContext.decodeAudioData(arrayBuffer, (audioBuffer) => {
resolve(encodeWav(audioBuffer));
}, reject);
};
reader.onerror = reject;
reader.readAsArrayBuffer(blob);
});
export { convertBlobToWav };
참고
- 위 코드는 채널 데이터를 채널별로 이어 쓴다(planar). 표준 WAV 플레이어 호환이 필요하면 스테레오일 때 샘플을 채널 순서대로 교차 배치(interleave)해야 한다. 모노 녹음이라면 차이가 없다.
decodeAudioData는AudioContext의 샘플레이트로 리샘플링한다. 특정 샘플레이트(예: 16kHz)가 필요하면new AudioContext({ sampleRate: 16000 })처럼 지정한다.