자바스크립트 Web Audio API는
"짧은 오디오 데이터"를 다룰 때 AudioBufferSourceNode 객체를 이용한다.
이 글에서는 AudioBufferSourceNode를 통해
1) 개발자가 생성한 오디오 데이터, 2) mp4, wav 등의 파일로 된 짧은 음원 등을 다루는 방법을 정리한다.
글의 가장 하단에는 각각에 대한 예제 코드를 정리해두었다.
Web Audio API의 기초에 대해서는 아래 글을 참고하자.
[자바스크립트] Web Audio API 기본 원리와 예제 코드
# AudioBufferSourceNode 란?
AudioBufferSourceNode는 AudioBuffer를 음원으로 입력받는 소스Node 객체이다.
주로 45초 이내의 짧은 오디오를 단 1회 재생하는 용도로 사용된다.
단, 한 번 재생되고 나면 가비지컬렉터에 의해 제거된다.
* 1회 이상 재생하려면, 재생할 때마다 AudioBufferSourceNode 객체를 새로 생성해야 한다.
* 45초 이상의 긴 오디오는 MediaElementAudioSourceNode를 사용하는게 권장된다.
[자바스크립트] MediaElementAudioSourceNode 음원 파일(mp3, wav 등) 사용 방법
# AudiiBufferSourceNode 사용 방법
AudioBuffer를 음원으로 입력받는 AudioBufferSourceNode로 작업하는 방식은
일반적인 Web Audio API 사용 방법과 동일하다.
1. AudioContext 생성
: AudioContext객체를 통해 소스Node, 작업Node를 생성하고, 출력Node를 참조할 수 있다.
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
2. 소스 Node 생성
: AudioBufferSourceNode객체를 생성하면서 AudioBuffer를 음원으로 주입해준다.
1) 메서드 방식
const audioBufferSourceNode = audioContext.createBufferSource();
audioBufferSourceNode = audioBuffer;
2) 생성자 방식
const audioBufferSourceNode = new AudioBufferSourceNode(audioContext, options)
* 생성자 방식은 일부 브라워저에서 지원되지 않을 수 있다.
3. 작업 Node 생성
: GainNode, AnlayserNode 등을 생성하여 음원에 필요한 작업을 수행한다.
1) 메서드 방식
const gainNode = audioContext.createGain();
gainNode.gain.value = 0.5; // 볼륨 설정(0~1사이)
2) 생성자 방식
const gainNode = new GainNode(audioContext, {gain: 0.5});
* 생성자 방식은 일부 브라워저에서 지원되지 않을 수 있다.
4. Audio Graph 연결
: "소스Node ~ 작업Node들 ~ 목적지Node"를 connect 해준다.
audioBufferSourceNode.connect(gainNode).connect(audioContext.destination);
5. 재생
: AudioBufferSourceNode.start() 를 호출해 음원을 재생한다.
audioBufferSourceNode.start();
AudioBufferSourceNode는 AudioNode > AudioScheduledSourceNode 순으로 상속받기 때문에,
AudioScheduledSourceNode에 정의된 start(), stop()메서드를 호출해 직접 음원 재생/정지가 가능하다.
* AudioScheduledSourceNode를 상속 받지 않는 MediaElementSourceNode등은 start(), stop()메서드가 없어서 직접 재생/정지가 불가하다.
잠깐!
근데 AudioBuffer라는 건 대체 무엇일까?
# AudioBuffer란?
AudioBuffer란 PCM 포맷의 오디오 데이터를 바이너리 형태로 메모리에 올려 사용하는 객체다.
PCM포맷은 오디오를 다루는 특정 형식이며, 이 데이터를 0000 1111 과 같은 바이너리 형태로 메모리에 저장해둔 객체를 AudioBuffer라고 한다.
한마디로 AudioBuffer란 오디오 데이터를 저장하고 있는 객체다.
AudioBuffer에 직접 만들고 다루기 위해서는 먼저 채널, 샘플, 샘플레이트, 샘플프레임에 대해 이해해야 한다.
# 채널, 샘플, 샘플레이트, 샘플프레임이란?
1. 채널
채널이란 녹음, 재생되는 신호의 갯수를 의미한다.
녹음되는 마이크의 갯수이자 재상되는 스피커 갯수 라고 할 수도 있다.
1) 모노(Mono: 채널 1개)
예를 들어 채널이 1개인 모노의 경우, 1채널이라서 하나의 마이크로 녹음하며, L, R 등의 구분이 없다.
따라서 여러 개의 스피커로 재생해도 하나의 음의 복사본(?)들이 똑같이 재생된다.
2) 스테레오(Stereo: 채널 2개)
스테레오는 2채널이라서 두개의 마이크로 녹음하며, L, R의 녹음본이 다르다.
따라서 2개의 스피커로 재생하면, 각각 다른 녹음본이 재생되어 공간감이 생긴다.
* 우퍼는 0.1로 친다.
2. 샘플
: 특정 시점, 특정 채널에서의 오디오 스트림 값(float32)
3. 샘플 프레임(프레임)
특정 시점, 모든 채널에서의 오디오 스트림 값(샘플)의 집합
따라서 1개의 샘플프레임은 채널수 만큼의 샘플로 구성된다.
4. 샘플 레이트
1초 동안 재생될 샘플 프레임 수(Hz 단위)
높을수록 더 많은 샘플프레임을 재생하므로 음질이 좋다.(손실이 적으니까)
예를 들어,
44,100 Hz의 샘플레이트는 1초 동안 44,100 번의 샘플프레임을 재생한다.
10초동안 재생한다면 441,000(44,100*10) 번의 샘플프레임이 재생된다.
단, "샘플 = 채널 * 샘플프레임" 으로 계산되기 때문에
채널이 1개인 모노는 1초동안 44,100 샘플(1채널 * 44,100샘플프레임)을 재생하고,
채널이 2개인 스테레오는 1초동안 88,200 샘플(2채널 * 44,100샘플프레임)을 재생한다.
10초 동안 재생한다면 모노는 441,000 개의 샘플을 재생하고, 스테레오는 882,000 개의 샘플을 재생한다.
즉, 샘플프레임 수는 채널 수와 무관하게 샘플레이트와 동일하지만,
샘플의 갯수는 샘플프레임 * 채널 가 된다
: 샘플프레임 수 = 샘플레이트
: 샘플 수 = 채널 수 * 샘플프레임 수
위의 내용을 숙지하고 이제 AudioBuffer를 더 자세히 뜯어보자.
* 샘플 관련 자세한 내용은 아래 글을 참고하자!
오디오 데이터 추출 및 변환: ADC, 샘플링, 샘플레이트, 채널
# AudioBuffer의 프로퍼티
AudioBuffer 객체는 아래의 4가지 프로퍼티를 갖는다.
1. numberOfChannels
: 채널수
2. sampleRate
: “1초당” 재생되는 “샘플프레임” 수
3. duration
: 총재생시간(초단위)
4. length
: “총재생시간” 동안 재생되는 “샘플프레임” 수
즉, AudioBuffer객체는 아래와 같은 형태라고 볼 수 있다.
1) 모노 AudioBuffer
{
numberOfChannels: 1 // 채널 수 1개
sampleRate: 44100 // 1초 동안 샘플프레임 수 44,100개
duration: 10 // 총 재생시간 10초
length: 441000 // 총 재생시간 동안 샘플프레임 수(44,100프레임 * 10초)
}
2) 스테레오 AudioBuffer
{
numberOfChannels: 2 // 채널 수 2개
sampleRate: 44100 // 1초 동안 샘플프레임 수 44,100개
duration: 10 // 총 재생시간 10초
length: 441000 // 총 재생시간 동안 샘플프레임 수(44,100프레임 * 10초)
}
* 단, 스테레오는 채널이 2개이므로 샘플의 갯수를 계산하면, "2채널 * 44,100샘플프레임 = 88,200샘플" 이 된다.
# AudioBuffer 생성 방법
AudioBuffer는 2가지 방식으로 만들어 낼 수 있다.
(각각의 예제 코드는 글의 하단에서 소개하겠다)
1. 직접 만든 AudioBuffer
: 개발자가 직접 계산해서 음을 만드는 방식
AudioContext.createBuffer(numOfchannels, length, sampleRate)
2. File을 변환하여 만든 AudioBuffer
: 음원파일(mp3 등) => ArrayBuffer => AudioBuffer 로 변환하는 방식
1) input 태그: 음원파일(mp3 등)을 File객체로 취득
<input type="file" accept="audio/*">
2) FileReader.readAsArrayBuffer(File): File객체를 ArrayBuffer로 변환
FileReader.readAsArrayBuffer(File);
3) AudioContext.decodeAudioData(ArrayBuffer): ArrayBuffer를 AudioBuffer로 변환
AudioContext.decodeAudioData(ArrayBuffer);
* 자바스크립트에서 File을 다루는 방법은 아래 글을 참고하자.
자바스크립트 File API 파헤치기: Blob, File, FileReader, FileList, BlobURL
글로 써서 장황하지만, 아래의 소스 코드를 보면 모든게 명확하게 이해될 것이다.
# 예제 코드 - 직접 AudioBuffer를 만드는 방법
: 재생 버튼을 누르면, 백색소음이 10초간 재생된다.
1) AudioBuffer를 생성하고,
2) AudioBuffer의 채널별 오디오 데이터를 -1 ~ +1 사이의 랜덤 값으로 채우고,(백색 소음 생성)
3) AudioBufferSourceNode의 음원으로 AudioBuffer를 입력하고,
4) GainNode로 볼륨을 조정하고,
5) 출력Node 까지 AudioGraph를 연결하고,
6) AudioBufferSourceNode.start()를 호출해 출력한다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>button{border: 1px solid black; }</style>
<title>AudioBuffer</title>
</head>
<body>
<!-- 재생 버튼: 이 버튼을 클릭하면 AudioBuffer를 생성하여 재생한다. -->
<button id="btn_play">백색소음 재생</button>
</body>
<script>
// 1. AudioContext 생성
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
// 2. audioBuffer, audioBufferSourceNode, gainNode 변수를 선언
let audioBuffer = null;
let audioBufferSourceNode = null;
let gainNode = null
// 3. AudioBuffer의 프로퍼티로 사용하기 위한 값들
const channels = 2; // 채널수
const sampleRate = 44100; // 샘플레이트
const duration = 10; // 재생시간
const sampleFrameLength = sampleRate * duration; // 재생시간 동안의 총 샘플프레임 갯수
// 버튼 클릭 이벤트 처리
document.querySelector("button").onclick = (event)=>{
// audioContext 가동
audioContext.resume();
// AudioBuffer 객체 생성
audioBuffer = audioContext.createBuffer(channels, sampleFrameLength, sampleRate);
// AuduioBuffer에 실제 오디오 데이터를 만들어 넣어주는 코드
for (let channel = 0; channel < channels; channel++) {
// 채널별 PCM 오디오 데이터를 Float32 배열로 취득
let nowBuffering = audioBuffer.getChannelData(channel);
for (let i = 0; i < sampleFrameLength; i++) {
// 각 채널의 오디오 데이터를 -1 ~ 1 사이의 값으로 채운다(랜덤 백색 소음이 만들어짐)
nowBuffering[i] = Math.random() * 2 - 1;
}
}
// AudioBufferSourceNode 객체 생성
audioBufferSourceNode = audioContext.createBufferSource();
// AudioBufferSourceNode에 AudioBuffer 주입
audioBufferSourceNode.buffer = audioBuffer;
// GainNode 생성: 볼륨을 설정한다.
gainNode = audioContext.createGain();
gainNode.gain.value = 0.5;
// Audio Graph 연결
audioBufferSourceNode.connect(gainNode).connect(audioContext.destination);
// 재생
audioBufferSourceNode.start();
}
</script>
</html>
# 예제 코드 - 음원 파일(mp3 등)을 AudioBuffer로 변환하는 방법
: mp3등의 음원파일을 업로드하고, 재생 버튼을 누르면 재생된다.
1) input으로 파일을 입력 받는다.
2) File을 FileReader를 통해 ArrayBuffer로 읽어들인다.
3) ArrayBuffer를 AudioContext.decodeAudioData()를 통해 AudioBuffer로 변환한다.
4) AudioBufferSourceNode 의 음원으로 주입하고, start() 메서드를 통해 재생한다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AudioBufferSourceNode</title>
</head>
<body>
<!-- 파입 입력 input: accept 속성을 이용해 브라우저에서 지원되는 모든 audio 타입의 파일을 입력 받는다. -->
<input type="file" accept="audio/*">
<!-- 재생 버튼: 이 버튼을 클릭하면 audioBufferSourceNode.start()로 음원을 재생한다. -->
<button id="btn_play">재생</button>
</body>
<script>
// 1. AudioContext 생성
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
// 2. audioBuffer, audioBufferSourceNode, gainNode 변수를 선언
// 함수 및 이벤트핸들러에서도 사용할 수 있도록 전역에 선언해두었다.
let audioBuffer = null;
let audioBufferSourceNode = null;
let gainNode = null
// 3. FileReader 생성: File객체를 ArrayBuffer로 변환하는 용도
const fileReader = new FileReader();
// 4. input 파일 입력 이벤트 핸들러
document.querySelector("input[type=file]").onchange = (event)=>{
// input 태그에서 파일이 입력되면
// input.files[0]에 해당 파일 데이터가 File 객체로 저장된다.
const file = event.currentTarget.files[0];
// FileReader를 통해 File객체를 ArrayBuffer로 변환한다.
// fileReader.onload 이벤트 핸들러에서 fileReader.result로 확인할 수 있다.
fileReader.readAsArrayBuffer(file);
}
// 5. fileReader 이벤트 핸들러
// FileReader를.readAsArrayBuffer() 메서드가 File => ArrayBuffer 변환을 완료하면 호출된다.
// 변환된 ArrayBuffer는 fileReader.result에서 참조할 수 있다.
fileReader.onload = async (event)=>{
// File객체에서 변환된 ArrayBuffer 취득
const arrayBuffer = fileReader.result;
// arrayBuffer => audioBuffer로 변환
// Promise를 반환하는 메서드이므로 await를 사용했다(이를 위해 이벤트 핸들러도 async를 사용했다.)
audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
// audioBufferSourceNode 객체 생성
audioBufferSourceNode = audioContext.createBufferSource();
// audioBufferSourceNode에 buffer 주입
audioBufferSourceNode.buffer = audioBuffer;
// GainNode 생성
gainNode = new GainNode(audioContext);
// 볼륨 설정
gainNode.gain.value = 0.5;
// 오디오 그래프 연결
audioBufferSourceNode.connect(gainNode).connect(audioContext.destination);
}
document.querySelector("button").onclick = (event)=>{
if(audioBufferSourceNode){
// audioContext 가동
audioContext.resume();
// audioBufferSourceNode는 start(), stop()으로 재생/정지 가능하다.
// 단, 1회만 재생 가능하고, 재생 완료시 가비지컬렉터에 의해 제거된다.
audioBufferSourceNode.start();
}
}
</script>
</html>
* 위 예제는 File, FileReader 등에 대한 이해가 선행되어야 하므로, 아래 글도 참고해두면 좋겠다.
자바스크립트 File API 파헤치기: Blob, File, FileReader, FileList, BlobURL
* 코드 중간에 ArrayBuffer 가 출현했는데, 이에 대해서는 아래 글을 참고하자.
자바스크립트 버퍼(Buffer): ArrayBuffer, TypedArray 파헤치기!
자바스크립트 Web Audio API의 AudioBuffer, AudioBufferSourceNode 정리 끝!
'개발(Development) > Media(오디오&비디오 개발)' 카테고리의 다른 글
[자바스크립트] MediaElementAudioSourceNode 음원 파일(mp3, wav 등) 사용 방법 (0) | 2022.01.20 |
---|---|
[자바스크립트] Oscillator Node 사용 방법 & 예제코드 (0) | 2022.01.18 |
[자바스크립트] Web Audio API 기본 원리와 예제 코드 (0) | 2022.01.10 |
[자바스크립트] 캔버스(canvas) 화면 캡처/녹화 방법(+파일 저장 다운로드 구현) (0) | 2021.12.12 |
[자바스크립트] audio/video 태그에서 재생 중인 소리/영상 녹음 방법 (0) | 2021.12.11 |
댓글