웹 오디오 프로그래밍

[Web Audio API #16] 녹음 - MediaRecorder

영바이트 2021. 8. 10. 20:01

Web Audio API 표준에서 녹음(레코딩)을 담당하는 오디오 노드는 MediaRecorder이다. 레코딩을 위한 전체 과정을 먼저 살펴보면 아래와 같다.

 

1. 녹음을 위한 소스(source)를 얻거나 생성한다. 예로 마이크 입력 stream이 있다.

2. MediaRecorder 객체를 생성하고 녹음 대상이 되는 데이터 스트림을 전달한다.

3. MediaRecorder의 dataavailable 이벤트 핸들러를 작성한다. 이벤트 핸들러는 stream에서 데이터가 전달될 때 마다 이를 저장하는 내용으로 구성된다.

4. MediaRecorder의 stop 이벤트 핸들러를 작성한다. 이벤트 핸들러에서는 dataavailable 이벤트가 발생할 때 마다 저장했던 데이터를 Blob(Binary Large Object), 즉 파일 형태로 만든다.

5. MediaRecorder.start() 메서드를 호출하면 레코딩이 시작된다.

6. MediaRecorder.stop() 메서드를 호출하면 레코딩이 종료되고 MediaRecorder의 stop 이벤트가 발생한다.

 

가장 먼저 마이크입력을 받는 MediaRecorder 객체를 생성한다.

if(navigator.mediaDevices){

    navigator.mediaDevices.getUserMedia({audio: true, video: false}).then((stream) => {
        const options = {audioBitsPerSecond: 32000, mimeType: 'audio/ogg'};

        // options 인자는 선택적이다. 없어도 동작한다.
        mediaRecorder = new MediaRecorder(stream, options);
    }
}

 

MediaRecorder 객체를 생성할 때 전달되는 인자 중 options에는 녹음 설정이 들어간다. MediaRecorder 클래스의 프로퍼티 중 isTypeSupported() 메서드를 이용하면 MediaRecorder가 실행되는 웹 브라우저에서 지원하는 오디오 녹음 형식을 확인할 수 있다. 일반적으로 라이센스가 없는 ogg 컨테이너 형식을 사용하지만 웹 브라우저마다 지원하는 형식이 다르다. 예를 들면 크롬Chrome 웹브라우저에서는 webm 형식을 사용한다.

const oggSupported = MediaRecorder.isTypeSupported('audio/ogg');

const options = oggSupported ? {audioBitsPerSecond: 320000, mimeType: 'audio/ogg'} : 
    {audioBitsPerSecond: 160000, mimeType: 'audio/webm'};

 

MediaRecorder 객체를 생성했으면 녹음을 위해 dataavailable과 stop 이벤트 핸들러를 작성하고 등록해야 한다. dataavaialble 이벤트는 stream으로부터 데이터가 전달될 때마다 호출된다. dataavaialble 이벤트 핸들러에서는 이들 데이터를 array 등에 저장하게된다.

var chunks = [];

mediaRecorder.addEventListener('dataavailable', (event) => {
    chunks.push(event.data);
});

 

MediaRecorder 객체에 전달되는 stream이 종료되거나 MediaRecorder 객체의 stop() 메서드가 호출되면 stop 이벤트가 발생한다. stop 이벤트 핸들러에서는 그 동안 저장했었던 데이터를 파일 형태로 가공하게된다.

mediaRecorder.addEventListener('stop', (event) => {
    var blob = new Blob(chunks, {'type': 'audio/ogg; codecs=opus'});

    // 아래는 임의의 파일 이름을 생성하기 위한 코드들이다.
    var todayISOString = new Date().toISOString().substring(0, 10);
    var min = 10000000;
    var max = 99999999;
    var rn = (Math.floor(Math.random()*(max - min + 1)) + min).toString();
    var defaultClipName = "recording_" + rn + "_" + todayISOString;

    recordingFileName = MediaRecorder.isTypeSupported('audio/ogg') ? (defaultClipName + '.ogg'): (defaultClipName + '.webm');

    // 서버로 전송해서 저장한다.
    postAudio(blob, recordingFileName);
});

 

위 코드 예의 MediaRecorder.onstop 이벤트 핸들러에서는 dataavailable 이벤트가 발생했을 때 저장했던 오디오 데이터 모음인 chunks를 Blob, 즉 바이너리 객체 형태로 저장한다. Blob 객체를 생성할 때 함께 전달되는 미디어 타입과 코덱codecs에는 오디오 기준으로 일반적으로 아래와 같은 설정들 중 하나가 사용된다.

 

· audio/ogg; codecs=vorbis

· audio/ogg; codecs=opus

· audio/webm; codecs=vorbis

· audio/webm; codecs=opus

 

오디오 컨테이너와 코덱은 필자의 전문성을 넘어가는 범위이기 때문에 더 자세한 내용은 아래 문서 참조를 부탁한다.

오디오 컨테이너: https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers

 

Media container formats (file types) - Web media technologies | MDN

In this guide, we'll look at the container formats used most commonly on the web, covering basics about their specifications as well as their benefits, limitations, and ideal use cases.

developer.mozilla.org

오디오 코덱: https://developer.mozilla.org/en-US/docs/Web/Media/Formats/codecs_parameter

 

The "codecs" parameter in common media types - Web media technologies | MDN

This guide briefly examines the syntax of the media type codecs parameter and how it's used with the MIME type string to provide details about the contents of audio or video media beyond indicating the container type.

developer.mozilla.org

 

※ Blob 데이터는 서버로 보내 저장하게 된다. 웹 브라우저에서 생성한 파일을 보안 측면의 이유 때문에 로컬 컴퓨터에 저장할 수 없기 때문이다.

 

MediaRecorder.start() 메서드를 호출하면 오디오 레코딩이 시작되고, MediaRecorder.stop() 메서드를 호출하면 오디오 레코딩이 종료된다. 아래와 같이 버튼 클릭 이벤트를 통해 레코딩/중지 기능을 구현할 수 있다.

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
</head>
<body>
    <div>
    	<button id="rec_stop_button" data-recording="false">녹음</button>
    </div>
</body>
</html>

<script>
    window.onload = () => {
        const recStopButton = document.querySelector('#rec_stop_button');

        // MediaRecorder 객체 생성, dataavailable, stop 이벤트 핸들러 구성
        // ...

        recStopButton.addEventListener('click', ()=> {
            if(recStopButton.dataset.recording == 'false'){
                recStopButton.dataset.recording = 'true';
                recStopButton.innerHTML = '중지';

                mediaRecorder.start();
            }
            else{
                mediaRecorder.stop();

                recStopButton.dataset.recording = 'false';
                recStopButton.innerHTML = '녹음';
            }
        }
    }
</script>

 

이번 포스팅에서는 마이크 입력을 파일 형태로 녹음하는 과정을 단계별로 살펴보았다. 이어지는 포스팅에서는 전체 과정을 묶어서 어떻게 동작하는지 다시 한번 살펴보도록 하겠다.