본문으로 건너뛰기

버그 제보

이상한 점을 알려 주시면 고칠게요. 지금 보고 있는 페이지와 브라우저 정보가 함께 담겨요.

페이지:

Frontend

미디어 캡처와 Web Audio API

작성 2026. 10. 21.추가
목차5개 항목
출처와 참고

MDN의 「Media Capture and Streams API」, 「Web Audio API」와 「Capture audio and video in HTML5」를 바탕으로 재구성한 노트다.

두 API의 자리

API다루는 것대표 진입점
Media Capture and Streams카메라·마이크·화면에서 오는 실시간 스트림navigator.mediaDevices.getUserMedia()
Web Audio소리를 만들고 섞고 가공하고 분석하는 오디오 처리 그래프new AudioContext()
<audio>·<video>파일·스트림의 재생과 기본 컨트롤이미지·오디오·비디오 임베드
MediaRecorder스트림을 파일(Blob)로 녹음·녹화React에서 음성 녹음하고 파일로 보내기
WebRTC스트림을 다른 피어와 주고받기WebRTC

세 API는 MediaStream을 공통 통화로 이어진다. 카메라 스트림을 <video>에 보이고, Web Audio로 마이크 소리를 분석하고, WebRTC로 보내고, MediaRecorder로 녹화하는 일이 모두 같은 객체로 된다.

MediaStream과 트랙

MediaStream은 MediaStreamTrack 여러 개의 묶음이다. 트랙 하나는 오디오나 비디오 한 줄기이고(kind), 오디오 트랙은 다시 채널(스테레오의 왼쪽·오른쪽)로 나뉜다.

flowchart LR
  subgraph src["소스"]
    s1["getUserMedia (카메라·마이크)"]
    s2["getDisplayMedia (화면 공유)"]
    s3["canvas.captureStream()"]
    s4["video.captureStream()"]
    s5["RTCPeerConnection ontrack"]
    s6["Web Audio MediaStreamAudioDestinationNode"]
  end
  ms["MediaStream<br/>(트랙들)"]
  subgraph sink["소비자"]
    c1["#lt;video#gt;.srcObject / #lt;audio#gt;.srcObject"]
    c2["RTCPeerConnection.addTrack()"]
    c3["MediaRecorder"]
    c4["Web Audio MediaStreamAudioSourceNode"]
    c5["ImageCapture (사진)"]
  end
  s1 --> ms
  s2 --> ms
  s3 --> ms
  s4 --> ms
  s5 --> ms
  s6 --> ms
  ms --> c1
  ms --> c2
  ms --> c3
  ms --> c4
  ms --> c5
const stream = await navigator.mediaDevices.getUserMedia({
  video: { facingMode: 'user', width: { ideal: 1280 } },
  audio: true,
})
video.srcObject = stream                       // src에 URL을 넣지 않는다
 
// 다 쓰면 트랙을 멈춰야 카메라 표시등이 꺼진다
stream.getTracks().forEach((t) => t.stop())
  • 보안 컨텍스트에서만 동작하고, 처음 부를 때 권한 프롬프트가 뜬다. 거절하면 NotAllowedError, 장치가 없으면 NotFoundError, 제약을 만족하는 장치가 없으면 OverconstrainedError다.
  • 제약 조건(ideal·exact·min·max, deviceId, facingMode)과 해상도 지정은 WebRTC의 「getUserMedia 제약 조건」에 있다.
  • navigator.mediaDevices.enumerateDevices()로 카메라·마이크 목록을 얻는다. 권한을 받기 전에는 label이 빈 문자열이라, 장치 선택 UI는 첫 허용 뒤에 채운다. 장치가 꽂히거나 빠지면 devicechange가 온다.
  • 화면 공유는 getDisplayMedia()로 받는다. 사용자 동작 안에서만 부를 수 있고, 무엇을 공유할지는 사용자가 브라우저 대화상자에서 고른다.
  • 사진 한 장, 녹화 파일 하나만 필요하면 스트림 없이 <input type="file" accept="image/*" capture="environment">로 모바일 카메라 앱을 바로 연다(capture="user"는 전면 카메라). 실시간 미리보기·필터가 필요할 때만 getUserMedia를 쓴다.

과거에는 Flash·Silverlight 플러그인으로 카메라를 썼다. 표준화 과정에서 <input capture>(HTML Media Capture), 출시되지 않은 <device> 요소, 벤더 접두사가 붙은 콜백식 navigator.getUserMedia()를 거쳐 지금의 프로미스 기반 navigator.mediaDevices.getUserMedia()가 됐다. 옛 예제의 video.src = URL.createObjectURL(stream)은 video.srcObject로 바뀌었다.

Web Audio API: 오디오 라우팅 그래프

Web Audio는 AudioContext 안에서 오디오 노드를 이어 붙여 만든 그래프로 소리를 다룬다. 소스 노드에서 나온 샘플(초당 수만 개의 진폭 값)이 효과 노드를 지나 출력(context.destination, 스피커)으로 흐른다. 2021년 4월부터 모든 주요 브라우저에서 쓸 수 있다.

역할노드예
소스OscillatorNode, AudioBufferSourceNode, MediaElementAudioSourceNode, MediaStreamAudioSourceNode, ConstantSourceNode합성음, 디코딩한 효과음, <audio> 요소, 마이크
음량·필터GainNode, BiquadFilterNode, DynamicsCompressorNode, WaveShaperNode볼륨, 저역·고역 통과, 압축, 왜곡
공간·잔향PannerNode, StereoPannerNode, ConvolverNode, DelayNode3D 위치(소스·청취자 모델, 거리 감쇠), 좌우 패닝, 리버브, 에코
분석AnalyserNode파형·주파수 데이터로 시각화, 음량 미터
출력context.destination, MediaStreamAudioDestinationNode스피커, 녹음·전송용 스트림
사용자 정의AudioWorkletNode오디오 렌더 스레드에서 도는 직접 만든 처리기
const ctx = new AudioContext()
const buffer = await ctx.decodeAudioData(await (await fetch('/click.mp3')).arrayBuffer())
 
function play(when = 0) {
  const src = new AudioBufferSourceNode(ctx, { buffer })  // 소스 노드는 한 번만 재생된다
  const gain = new GainNode(ctx, { gain: 0.5 })
  src.connect(gain).connect(ctx.destination)
  src.start(ctx.currentTime + when)                       // 오디오 시계 기준으로 예약
}
 
button.addEventListener('click', async () => {
  if (ctx.state === 'suspended') await ctx.resume()       // 자동 재생 정책
  for (let i = 0; i < 4; i++) play(i * 0.25)              // 0.25초 간격 정확한 4박
})
  • 시간은 ctx.currentTime(오디오 하드웨어 시계, 초) 으로 예약한다. setTimeout보다 훨씬 정밀해서 드럼 머신·시퀀서처럼 샘플 단위 타이밍이 필요한 것을 만들 수 있다. 값의 변화도 gain.gain.linearRampToValueAtTime(0, t)처럼 시각을 지정해 예약한다.
  • 자동 재생 정책: 사용자 동작 전에 만든 AudioContext는 suspended 상태로 시작한다. 클릭 같은 동작 안에서 resume()해야 소리가 난다.
  • AudioBufferSourceNode·OscillatorNode는 일회용이다. 다시 재생하려면 노드를 새로 만든다. 가볍게 만들도록 설계됐다.
  • 직접 샘플을 다루려면 AudioWorklet을 쓴다. 메인 스레드에서 돌던 ScriptProcessorNode는 deprecated다.
  • OfflineAudioContext는 스피커 대신 버퍼로, 실시간보다 빠르게 렌더링한다. 파형 미리 계산, 효과를 입힌 파일 내보내기에 쓴다.

마이크 소리 분석

const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
const analyser = new AnalyserNode(ctx, { fftSize: 2048 })
ctx.createMediaStreamSource(stream).connect(analyser)   // destination에 잇지 않으면 하울링이 없다
 
const data = new Uint8Array(analyser.frequencyBinCount)
;(function draw() {
  analyser.getByteFrequencyData(data)                    // 0~255 주파수별 세기
  renderBars(data)                                       // canvas에 막대 그리기
  requestAnimationFrame(draw)
})()

출력에 연결하지 않아도 분석은 된다. 사용자가 들어야 할 때만 destination에 잇는다.

정리

  • 실시간 미디어의 공통 단위는 MediaStream이고, 트랙을 stop()해야 장치가 풀린다.
  • 장치 목록의 이름은 권한을 받은 뒤에 보인다. 사진 한 장이면 <input capture>로 충분하다.
  • Web Audio는 소스 → 효과 → 분석 → 출력의 노드 그래프다. 시간은 ctx.currentTime으로 예약한다.
  • AudioContext는 사용자 동작 안에서 resume()한다. 직접 처리는 AudioWorklet으로 한다.

관련 노트: WebRTC, 이미지·오디오·비디오 임베드, React에서 음성 녹음하고 파일로 보내기, 권한 모델과 기기 API, Canvas 2D API

원본 기록4

댓글

GitHub 계정으로 댓글을 남길 수 있어요.