STIKSHOT - 한계를 넘어선 스틱맨 비디오 렌더러
요약
stikshot는 서버 전송 없이 브라우저 내에서 100% 클라이언트 사이드로 동작하는 AI 모션 캡처 엔진입니다. TensorFlow.js와 WebCodecs API를 활용하여 로컬 우선 방식의 서버리스 비디오 렌더링 아키텍처를 구현했습니다.
핵심 포인트
- TensorFlow.js와 MoveNet을 이용한 브라우저 기반 AI 모션 캡처
- Web Workers와 Offscreen Pipelining을 통한 UI 반응성 확보
- WebCodecs API를 활용한 클라이언트 사이드 비디오 인코딩
- iOS Safari의 WebCodecs 객체 동결 문제를 복사 방식으로 해결
브라우저 내에서 100% 클라이언트 사이드 (Client-Side) AI 모션 캡처 엔진 구축하기
댄스 영상을 업로드하면 서버로 단 1MB의 데이터도 전송하지 않고 즉시 네온 스틱맨 애니메이션으로 변환되는 모습을 상상해 보세요.
이것이 바로 stikshot입니다. stikshot는 로컬 우선 (local-first) 방식의 서버리스 (serverless) AI 모션 캡처 웹 앱입니다. 이 글에서는 아키텍처, 우리가 사용한 브라우저 API (Browser APIs), 그리고 WebCodecs가 데스크톱과 모바일에서 안정적으로 실행되도록 설계한 해결책(workarounds)에 대해 자세히 살펴보겠습니다.
핵심 스택 (The Core Stack)
stikshot를 완전히 서버리스(serverless)하고 정적(static)이며 확장성 제한이 없는(scale-free) 상태로 유지하기 위해, 우리는 전체 파이프라인이 사용자의 브라우저 샌드박스 (sandbox) 내부에서 실행되도록 구축했습니다.
- AI 엔진: MoveNet Multipose 모델을 실행하는 TensorFlow.js
- 백그라운드 스레드 (Background Thread): UI의 반응성을 유지하기 위한 Web Workers
- 압축 (Compression): 브라우저의 네이티브 WebCodecs API (VideoEncoder 및 AudioEncoder)
- 뮤저 (Muxer): 비디오/오디오 스트림을 재생 가능한 WebM 파일로 컴파일하기 위한 webm-muxer
아키텍처: 오프스크린 파이프라이닝 (Offscreen Pipelining)
브라우저의 랙(lag)을 방지하기 위해, 우리는 애플리케이션을 두 부분, 즉 메인 스레드 코디네이터 (Main Thread Coordinator)와 Web Worker 프로세서 (Web Worker Processor)로 나누었습니다.
Mermaid diagram
코드 하이라이트 (Code Highlights)
- 백그라운드 프레임 탐색 (Seeking Background frames)
사용자가 탭을 전환할 때 발생하는 탭 스로틀링 (tab-throttling)을 피하기 위해, 우리는 requestVideoFrameCallback 사용을 피하고 표준 seeked 미디어 이벤트 리스너와 안전한 타임아웃 폴백 (timeout fallback)을 결합하여 사용했습니다.
function seekTo(video, time) {
return new Promise((resolve) => {
const onSeeked = () => {
video.removeEventListener('seeked', onSeeked);
clearTimeout(fallback);
resolve();
};
const fallback = setTimeout(() => {
video.removeEventListener('seeked', onSeeked);
resolve();
}, 500); // 500ms 타임아웃 안전망
video.addEventListener('seeked', onSeeked);
video.currentTime = time;
});
}
- iOS Safari WebCodecs 충돌 방지
개발 과정에서 심각한 버그를 발견했습니다. Safari는 인코딩된 비디오 청크 (video chunks)를 쓰려고 할 때 충돌하거나 멈추는 현상이 발생했습니다.
우리는 Safari가 VideoEncoder 출력 콜백 (output callback) 내부에서 브라우저가 제공하는 메타데이터 객체 (metadata objects)를 동결 (freeze)시킨다는 사실을 발견했습니다. 이 객체들을 수정하거나 뮤서 (muxer)에 직접 전달하면 조용한 오류 (quiet error)가 발생합니다. 우리는 속성들을 동결되지 않은 깨끗한 객체로 복사함으로써 이 문제를 해결했습니다:
const videoEncoder = new VideoEncoder({
output: (chunk, meta) => {
let cleanMeta = undefined;
if (meta) {
cleanMeta = {};
// Safari의 읽기 전용/동결 제약을 우회하기 위해 속성을 복사합니다
if (meta.decoderConfig !== null && meta.decoderConfig !== undefined) {
cleanMeta.decoderConfig = meta.decoderConfig;
}
if (meta.svc !== undefined) cleanMeta.svc = meta.svc;
if (meta.alphaSideData !== undefined) cleanMeta.alphaSideData = meta.alphaSideData;
}
state.muxer.addVideoChunk(chunk, cleanMeta);
},
error: (err) => console.error('Encoder error:', err)
});
- Android 오디오 해킹 방지
Android Chrome은 손상된 트랙 (corrupted tracks)에 대해decodeAudioData를 실행하는 동안 때때로 무한히 대기하는 현상이 발생합니다. 우리는 8초의 안전 타임아웃 (safety timeout)을 갖춘 프로미스 래핑 (promise-wrapped) 오디오 디코더를 구현했습니다:
function decodeAudioWithTimeout(buffer, sampleRate = 48000, timeoutMs = 8000) {
const decodeCtx = new OfflineAudioContext(1, 1, sampleRate);
return new Promise((resolve) => {
let completed = false;
const timer = setTimeout(() => {
if (!completed) {
completed = true;
console.warn('Audio decoding timed out');
resolve(null);
}
}, timeoutMs);
decodeCtx.decodeAudioData(buffer,
(decoded) => {
if (!completed) { completed = true; clearTimeout(timer); resolve(decoded); }
},
(err) => {
if (!completed) { completed = true; clearTimeout(timer); resolve(null); }
}
);
});
}
다음 단계: 스틱맨을 넘어
stikshot은 서버리스 (serverless)로 구축되어 GPU 렌더링 비용을 지불하지 않습니다. 하지만 2D 스틱맨 애니메이션을 생성하는 것은 첫 번째 단계일 뿐입니다.
우리는 이 로컬 퍼스트 (local-first) 파이프라인을 다음과 같은 기능을 지원하도록 확장하고자 합니다:
3D 스켈레탈 리깅 (3D Skeletal Rigging): 관절 좌표를 .gltf / .fbx 형식으로 변환.
게임 엔진 내보내기 (Game Engine Exports): 크리에이터가 모션 캡처 (motion capture) 데이터를 Unity 또는 Unreal Engine으로 직접 내보낼 수 있도록 지원.
VTuber 오버레이 (VTuber Overlays): 실시간 가상 아바타 트래킹 (virtual avatar tracking)을 위한 브라우저 기반 카메라 입력 생성.
함께 협업해요!
만약 로컬 퍼스트 (local-first) AI, WebAssembly, WebGL 셰이더 (shaders), 또는 브라우저 모션 트래킹 (motion tracking)에 관심이 있다면, 라이브 앱을 확인하고, 스타 (star)를 남겨주시고, 연락해 주세요:
앱: stikshot.com
이메일: contact@stikshot.com
Instagram: @stikshotu
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기