본문 바로가기
개발(Development)/Media(오디오&비디오 개발)

[자바스크립트] AudioBuffer, AudioBufferSourceNode 사용 방법 & 예제 코드

by 카레유 2022. 1. 19.

자바스크립트 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개의 스피커로 재생하면, 각각 다른 녹음본이 재생되어 공간감이 생긴다.

 

출처: MDN

* 우퍼는 0.1 친다.

 

 

2. 샘플

: 특정 시점, 특정 채널에서의 오디오 스트림 (float32)

 

 

3. 샘플 프레임(프레임)

특정 시점, 모든 채널에서의 오디오 스트림 값(샘플)의 집합

따라서 1개의 샘플프레임은 채널수 만큼의 샘플로 구성된다.

출처: MDN

 

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 정리 끝!

 

 

 

 

 

댓글