설치 없이 브라우저만 되는 AI 기능들 — 음성인식·표정분석·음정측정 실전 정리
안녕하세요, 천상나타입니다.
지난 글에서 브라우저 내장 AI의 현재 상태를 정리했었는데요, 이번엔 조금 다른 이야기입니다. 최근에 제 도구 사이트에 면접 연습기·발음 게임·음정 게임을 연달아 만들면서 깨달은 게 있어요. 파이썬도, 서버도, API 키도 없이 브라우저만으로 "AI스러운" 기능이 꽤 많이 됩니다. 음성 인식, 표정 분석, 음정 측정까지요.
오늘은 제가 실제로 써본 브라우저 기능 4개를 코드와 함께 정리합니다. 그리고 겉으로는 안 보이는 주의점들 — 특히 "데이터가 어디로 가는지"가 기능마다 달라서, 이 부분을 헷갈리면 프라이버시를 내세우는 서비스에서 사고가 납니다.
전체 지도 — "브라우저에서 된다" ≠ "내 PC에서 끝난다"

핵심부터: 이 중에서 사용자 데이터가 외부로 나가는 건 SpeechRecognition뿐입니다. 나머지는 모델 다운로드(필요한 경우) 이후엔 네트워크를 안 타요. 이 구분이 이 글의 절반이라고 생각하시면 됩니다.
1. SpeechRecognition — 설치 없이 음성 인식, 단 오디오는 나감
const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
const rec = new SR();
rec.lang = 'ko-KR';
rec.continuous = true; // 끊기지 않고 계속 듣기
rec.interimResults = true; // 중간 결과도 실시간으로 받기
rec.onresult = (e) => {
for (const r of e.results) {
if (r.isFinal) console.log('확정:', r[0].transcript);
}
};
rec.start();
이게 전부입니다. 한국어 실시간 전사가 설치 하나 없이 돼요. 저는 이걸로 발음 연습 게임(문장을 보여주고 정확히 읽었는지 채점)과 면접 답변 전사를 만들었습니다.

주의점 (중요합니다)
- Chrome·Edge는 오디오를 자사 서버로 보냅니다. 브라우저 내장이지만 로컬 처리가 아니에요. 제 사이트가 "파일이 디바이스를 안 떠난다"는 걸 내세우는데, 이 기능만은 예외라 UI에 명시해뒀습니다. 비슷한 서비스 만드시면 이 문구를 빼먹지 마세요
webkit접두어가 필요합니다. Safari 포함. 위 코드처럼 둘 다 받아야 합니다- Firefox는 아예 없습니다. 지원 확인 후 안 되면 기능 자체를 숨기는 폴백이 필요해요
continuous: true여도 가끔 끊깁니다. 침묵이 길어지면 세션이 닫히는 브라우저가 있어서, 저는onend에서 자동 재시작을 달아뒀습니다
2. MediaPipe FaceLandmarker — 표정이 52개 숫자로 나옴
이건 진짜 신기합니다. 카메라 프레임을 WASM으로 컴파일된 모델에 넣으면, 얼굴 랜드마크 478개 + blendshape 52개가 프레임마다 나옵니다. blendshape는 "입꼬리 올라감 0.7", "왼쪽 눈 감김 0.9" 같은 수치예요.
import { FilesetResolver, FaceLandmarker } from '@mediapipe/tasks-vision';
const fileset = await FilesetResolver.forVisionTasks(
'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.14/wasm'
);
const landmarker = await FaceLandmarker.createFromOptions(fileset, {
baseOptions: { modelAssetPath: MODEL_URL },
runningMode: 'VIDEO', // 실시간 영상용
numFaces: 1,
outputFaceBlendshapes: true, // 이걸 켜야 표정 수치가 나옴
});
// requestAnimationFrame 루프 안에서
const result = landmarker.detectForVideo(videoEl, performance.now());
const shapes = result.faceBlendshapes[0].categories;
const smile = shapes.find(s => s.categoryName === 'mouthSmileLeft').score;

eyeLook* 계열 수치로 "카메라를 보고 있는지", eyeBlink 상승 엣지로 깜빡임 횟수, browDown으로 찌푸림까지 잡을 수 있어요. 저는 면접 연습기의 비언어 분석(시선 응시율·미소·자세 안정성)에 썼습니다.
주의점
- 모델 파일은 최초 1회 다운로드됩니다 (~3MB + WASM 런타임). 이후 추론은 완전 로컬이지만 첫 로딩은 느리니, 사용자가 버튼 누르기 전에 미리 로드를 시작해두세요
runningMode가IMAGE와VIDEO로 나뉩니다. 실시간은 반드시VIDEO—IMAGE모드로 비디오를 돌리면 프레임마다 초기화 비용이 들어 렉 걸립니다- 모바일은 발열 주의. 프레임마다 추론하면 폰이 뜨거워지니, 저는 초당 10~15프레임으로 샘플링을 제한했습니다
detectForVideo의 타임스탬프는 단조 증가해야 합니다.performance.now()를 그대로 넣으면 됩니다- 얼굴이 안 잡히는 프레임은
faceBlendshapes가 비어서 오니 미검출 분기는 필수입니다
3. Web Audio — 소리 파형에서 피치·음량·침묵까지
마이크 스트림을 AudioContext에 연결하면 원시 파형(Float32Array)을 직접 얻을 수 있습니다. 여기서 재밌는 게 시작돼요 — 자기상관(autocorrelation)이라는 수학 하나로 음정(Hz)이 나옵니다.
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const ctx = new AudioContext();
const analyser = ctx.createAnalyser();
analyser.fftSize = 2048;
ctx.createMediaStreamSource(stream).connect(analyser);
const buf = new Float32Array(analyser.fftSize);
function tick() {
analyser.getFloatTimeDomainData(buf);
const hz = autoCorrelate(buf, ctx.sampleRate); // 아래 함수
if (hz > 0) console.log(hz.toFixed(1), 'Hz'); // 440Hz = 라(A4)
requestAnimationFrame(tick);
}
자기상관은 "파형이 자기 자신과 얼마나 잘 겹치는가"를 시간 지연별로 계산하는 겁니다. 가장 잘 겹치는 주기가 곧 기본주파수예요. 제가 실제로 쓰는 함수입니다 — 30줄입니다.
function autoCorrelate(buf, sampleRate) {
let rms = 0;
for (let i = 0; i < buf.length; i++) rms += buf[i] * buf[i];
if (Math.sqrt(rms / buf.length) < 0.01) return -1; // 무음이면 중단
const c = new Array(buf.length).fill(0);
for (let i = 0; i < buf.length; i++)
for (let j = 0; j < buf.length - i; j++)
c[i] += buf[j] * buf[j + i];
let d = 0;
while (c[d] > c[d + 1]) d++;
let maxv = -1, maxp = -1;
for (let i = d; i < c.length; i++)
if (c[i] > maxv) { maxv = c[i]; maxp = i; }
if (maxp <= 0) return -1;
return sampleRate / maxp;
}
(실제 코드는 양끝 무음 구간 잘라내기와 피크 사이 보간이 조금 더 들어가지만, 원리는 이게 전부입니다.)

같은 마이크 데이터로 RMS(파형 진폭) 를 구하면 평균 음량과 무음 구간도 잡힙니다. 면접 연습기에서는 이 둘을 합쳐서 "목소리가 작다", "말이 너무 단조롭다(피치 표준편차가 작다)", "답변 시작까지 4초 걸렸다"를 측정했고, 음정 게임에서는 목표 음과 사용자 허밍의 센트 차이로 채점했어요.
주의점
AudioContext는 사용자 제스처 없이 만들면 suspended 상태로 멈춰있습니다. 반드시 버튼 클릭 같은 이벤트 안에서new AudioContext()하거나ctx.resume()을 호출하세요 — 이거 모르고 "왜 파형이 안 나오지"로 한참 헤맸습니다- 단일 음만 잡힙니다. 화음, 배경음악이 섞인 소리, 떨림(vibrato)이 심한 목소리는 피치가 튑니다. "허밍으로 목표음 맞추기"처럼 입력을 단순하게 제한한 게임 설계가 필요해요
- FFT가 아니라 시간 영역 데이터를 씁니다(
getFloatTimeDomainData). FFT는 스펙트럼 분석이고 목적이 달라요 - 오디오는 메모리 안에서만 계산됩니다 — 녹음이 아니라 측정이라 저장도 전송도 안 일어나요. 이 점을 UI에 적어두면 마이크 권한 거부율이 내려갑니다
4. 공통 주의점 — 브라우저 API라고 다 같은 게 아님
네 가지를 쓰면서 공통으로 부딪힌 것들입니다:
- HTTPS가 없으면 아무것도 안 됩니다.
getUserMedia,SpeechRecognition전부 보안 컨텍스트 필수.localhost만 예외예요 - SSR 프레임워크(Next.js 등)에서는
window체크 필수. 서버 렌더링 시점엔 이 API들이 없어서,typeof window !== 'undefined'분기 없이 쓰면 하이드레이션 에러가 납니다. 저도useState(Math.random())으로 랜덤 문장을 뽑았다가 서버/클라이언트가 다른 HTML을 만들어 에러를 한 번 봤습니다 - 권한은 기능마다 따로 묻습니다. 카메라와 마이크 권한이 별개라, 둘 다 쓰는 기능은 거절 시나리오를 각각 설계해야 합니다 (카메라 거절 → 음성 분석만 진행)
- 탭이 백그라운드로 가면
requestAnimationFrame이 멈춥니다. 분석 루프가 rAF 기반이면 화면 안 보는 동안 측정이 스킵돼요 — 타이머 기반인지 rAF 기반인지에 따라 동작이 달라집니다 - 무거운 모델은 워커로. transformers.js 추론을 메인 스레드에서 돌리면 UI가 멈춥니다 — 이건 지난 글에서도 강조했던 부분인데, MediaPipe 런타임도 가볍진 않아서 실시간 루프에선 샘플링 주기 조절이 필수였어요
정리 — 이걸로 뭘 만들었나
이 네 가지 조합으로 제 사이트에 들어간 것들입니다: 마이크 전사 + 카메라 표정 분석으로 면접 답변을 채점하는 연습기, 목표 음을 허밍으로 맞추는 음정 게임, 문장을 소리내어 읽어 채점하는 발음 게임. 전부 서버 비용 0원에 돌아가고, SpeechRecognition을 제외하면 사용자 데이터가 기기를 떠나지도 않습니다.
"브라우저에서 AI가 된다"는 말의 실체는 사실 이런 겁니다 — 거대한 모델이 아니라, 오래전부터 있던 미디어 API들과 WASM 덕분에 가벼워진 모델들의 조합. 새 프레임워크 배우는 것보다, 이미 있는 getUserMedia와 Web Audio를 제대로 쓰는 게 시작점으로는 훨씬 빠르다는 게 제 결론입니다.
다음엔 이걸로 만든 면접 연습기가 실제로 어떤 지표를 측정하는지, 임계값을 어떻게 잡았는지 정리해볼 생각입니다.
'IT > javaScript' 카테고리의 다른 글
| 브라우저 내장 AI 지도 — Gemini Nano, 지금 뭘 쓸 수 있나 (0) | 2026.11.15 |
|---|---|
| 브라우저 내장 AI가 워커에서 멈춘 이유 — Gemini Nano 실전 연동기 (0) | 2026.11.14 |
| aria-label과 role 속성 - 웹 접근성을 위한 필수 개념 (0) | 2025.04.05 |
| JavaScript Async Await Promise 사용법의 이해 (0) | 2023.01.24 |
| JavaScript 비동기 Promise에 대한 설명 (0) | 2023.01.24 |