출처 바로가기

자바스크립트에서 마이크를 사용하려면 먼저 사용자로부터 마이크 사용 권한을 획득해야 합니다.

여기서 사용자가 허용 버튼을 눌러야 이후 녹음 작업이 진행이 됩니다.

이 부분은 navigator.mediaDevices.getUserMedia(constraints).then(...)을 사용합니다.

if (navigator.mediaDevices) {
    const constraints = {
        audio: true
    }
    navigator.mediaDevices.getUserMedia(constraints)
        .then(stream => {
                .................
           
        })
        .catch(err => {
            console.log('The following error occurred: ' + err)
        })
}

 

마이크 소리를 실시간으로 들으려는 경우 오디오 컨텍스트를 사용하며, 마이크 소리를 녹음한 경우 저장하려는 경우 <span class="pl-k">new</span><span> </span><span class="pl-v">MediaRecorder</span><span class="pl-kos">(</span><span class="pl-s1">stream</span>) 을 사용합니다.

미디어리코더에서 .start()를 하면 녹음을 시작하고, .stop()을 하면 녹음이 종료됩니다.

아래 코드는 녹음이 진행되는 동안 데이터를 배열에 저장하는 작업입니다.

mediaRecorder.ondataavailable = e => {
    chunks.push(e.data)
}

이 배열을 블롭 데이터로 변환한 후 <audio>src에 적용하면 녹음 파일이 재생됩니다.

const audio = document.createElement('audio')
...

audio.controls = true
const blob = new Blob(chunks, {
    'type': 'audio/ogg codecs=opus'
})
...
const audioURL = URL.createObjectURL(blob)
audio.src = audioURL

 

이것을 종합한 예제 코드는 아래와 같습니다.


<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>마이크 테스트</title>
</head>
<body>
<input type=checkbox id="chk-hear-mic"><label for="chk-hear-mic">마이크 소리 듣기</label>
<button id="record">녹음</button>
<button id="stop">정지</button>
<div id="sound-clips"></div>
<script>
const record = document.getElementById("record")
const stop = document.getElementById("stop")
const soundClips = document.getElementById("sound-clips")
const chkHearMic = document.getElementById("chk-hear-mic")
const audioCtx = new(window.AudioContext || window.webkitAudioContext)() // 오디오 컨텍스트 정의
const analyser = audioCtx.createAnalyser()
// const distortion = audioCtx.createWaveShaper()
// const gainNode = audioCtx.createGain()
// const biquadFilter = audioCtx.createBiquadFilter()
function makeSound(stream) {
const source = audioCtx.createMediaStreamSource(stream)
source.connect(analyser)
// analyser.connect(distortion)
// distortion.connect(biquadFilter)
// biquadFilter.connect(gainNode)
// gainNode.connect(audioCtx.destination) // connecting the different audio graph nodes together
analyser.connect(audioCtx.destination)
}
if (navigator.mediaDevices) {
console.log('getUserMedia supported.')
const constraints = {
audio: true
}
let chunks = []
navigator.mediaDevices.getUserMedia(constraints)
.then(stream => {
const mediaRecorder = new MediaRecorder(stream)
chkHearMic.onchange = e => {
if(e.target.checked == true) {
audioCtx.resume()
makeSound(stream)
} else {
audioCtx.suspend()
}
}
record.onclick = () => {
mediaRecorder.start()
console.log(mediaRecorder.state)
console.log("recorder started")
record.style.background = "red"
record.style.color = "black"
}
stop.onclick = () => {
mediaRecorder.stop()
console.log(mediaRecorder.state)
console.log("recorder stopped")
record.style.background = ""
record.style.color = ""
}
mediaRecorder.onstop = e => {
console.log("data available after MediaRecorder.stop() called.")
const clipName = prompt("오디오 파일 제목을 입력하세요.", new Date())
const clipContainer = document.createElement('article')
const clipLabel = document.createElement('p')
const audio = document.createElement('audio')
const deleteButton = document.createElement('button')
clipContainer.classList.add('clip')
audio.setAttribute('controls', '')
deleteButton.innerHTML = "삭제"
clipLabel.innerHTML = clipName
clipContainer.appendChild(audio)
clipContainer.appendChild(clipLabel)
clipContainer.appendChild(deleteButton)
soundClips.appendChild(clipContainer)
audio.controls = true
const blob = new Blob(chunks, {
'type': 'audio/ogg codecs=opus'
})
chunks = []
const audioURL = URL.createObjectURL(blob)
audio.src = audioURL
console.log("recorder stopped")
deleteButton.onclick = e => {
evtTgt = e.target
evtTgt.parentNode.parentNode.removeChild(evtTgt.parentNode)
}
}
mediaRecorder.ondataavailable = e => {
chunks.push(e.data)
}
})
.catch(err => {
console.log('The following error occurred: ' + err)
})
}
</script>
</body></html>

view raw

mic-test.html

hosted with ❤ by GitHub

https://gist.github.com/ayaysir/12c367a9f699f88ad59fbd47d794f745

 

문의 | 코멘트 또는 yoonbumtae@gmail.com




9개의 댓글

cheyrl choi · 2020년 12월 9일 2:10 오전

안녕하세요 유익한 정보 감사합니다. 현재 한국어학습프로그램을 만들고 있는 학생입니다.
질문이 있어서 글 남깁니다.
제가 진행하고 있는 프로그램 상 PCM으로 저장을해야 하는데
혹시 위 코드에서 저장방법만 바꿀 수도 있을까요?
답변주신다면 정말 큰 도움이 될 것 같습니다ㅠ
감사합니다!

cheryl choi · 2020년 12월 9일 6:35 오후

답변주셔서 정말 감사합니다.
안내해주신 링크로 코딩해보니 pcm으로 인코딩된 wav형식으로 저장이 되네요!
연결하려는 발음평가 api가 pcm형태만을 지원하고 있어서 변환하는 작업이 필요할듯합니다.
도움주셔서 감사합니다!

gunny · 2020년 12월 29일 8:54 오후

좋은 소스 정말 감사합니다 .
한가지 여쭤보고 싶은데 저장된 음성파일을 다운로드하려고하면 type이 audio/ogg로 되어있음에도 oga형식으로 다운로드가 되는데 ogg형식으로 다운로드하는방법이 없을까요 ?

    yoonbumtae (BGSMM) · 2020년 12월 29일 9:16 오후

    안녕하세요.
    제가 위의 코드로 파일 저장 테스트를 해봤는데요
    제 컴퓨터애서는(크롬 브라우저)는 ogg 확장자로 저장이 됩니다.
    아마 브라우저나 OS 차이인 것 같습니다.
    파일 포맷은 ogg audio 로 동일하니 (링크) 다운받은 파일 확장자를 바꾸셔도 재생에는 문제가 없을 것 같고
    저장 확장자를 ogg 로 강제하는 방법은 제가 몰라서 더 알아보도록 하겠습니다.
    감사합니다.

gunny · 2020년 12월 29일 9:05 오후

좋은 글 감사합니다.

good · 2021년 7월 19일 5:21 오후

최고입니다!감사합니다

rrj · 2021년 8월 4일 3:00 오후

녹음된 파일을 바로 컨트롤러에서 처리하고싶은데 데이터보낼때랑 받을때 어떻게 처리하면될까요?

    yoonbumtae (BGSMM) · 2021년 8월 4일 7:24 오후

    위 코드 예제에서 const blob 이 부분을 formData 에 담아서 폼 전송이나 ajax로 백엔드 컨트롤러로 보내면 된다고 생각합니다. 백엔드 컨트롤러는 바이너리 타입을 받을수 있게 하고요

답글 남기기

Avatar placeholder

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다