안녕하세요! 그로스로그 입니다!😊 🌱
라이브러리로 때우는 지점을 바이너리까지 내려가 직접 만든 기록, 그리고 앱에 머신러닝 모델을 실제로 붙여본 기록. 2기 3회차입니다.
성장일지는 멤버들이 2주에 한 번씩 자신의 성장을 기록하는 활동이에요. 매 회차 운영진이 모든 글을 읽고, 그중 특히 마음에 남은 글을 함께 골라 큐레이션합니다.
이번 회차는 한 겹 더 내려가본 두 편입니다.
01
🎧 webm을 wav로 변환하기
이종경 님
브라우저에서 녹음한 webm 오디오를 wav로 바꾸는 작업을, 라이브러리 없이 직접 구현한 기록입니다.
wav 파일은 그냥 소리 데이터가 아니라 정해진 형식이 있습니다. RIFF 헤더가 있고, fmt 청크에 샘플레이트 같은 정보가 들어가고, data 청크에 실제 소리가 담기는 구조예요.
글은 이 청크들을 직접 조립하고, DataView로 바이트 순서(엔디안)까지 맞춥니다. 보통은 라이브러리를 가져다 쓰고 넘어가는 지점인데, 바이너리 레벨까지 내려가 무슨 일이 일어나는지 설명해요.
브라우저에서 오디오를 녹음하면 대개 webm 형식으로 나옵니다. 그런데 서버나 다른 시스템이 wav를 요구하는 경우가 있어요. 음성 인식 API가 wav만 받는다든지요.
이때 대부분은 변환 라이브러리를 찾습니다. 이 글은 그러지 않았어요.
조금 더 들어가 보면
wav 파일이 왜 "직접 만들 수 있는" 형식인지부터 보면 이해가 쉽습니다.
wav는 압축을 하지 않습니다. 소리를 일정 간격으로 측정한 숫자를 그대로 나열한 형식이에요. 그래서 파일이 크지만, 구조가 단순합니다. 반면 webm(안에 든 Opus 코덱)은 압축 형식이라 원본을 복원하려면 디코딩이 필요하고요.
wav 파일의 구조는 RIFF라는 컨테이너 규격을 따릅니다. 상자 안에 작은 상자들이 들어 있는 형태예요.
· RIFF 헤더 — "나는 RIFF 파일이고 전체 크기는 이만큼이며 타입은 WAVE다"
· fmt 청크 — 채널 수(모노/스테레오), 샘플레이트(44100Hz 등), 비트 깊이(16bit 등)
· data 청크 — 실제 소리 숫자들
이 순서대로 바이트를 채워 넣으면 wav 파일이 됩니다. 마법이 아니라 약속된 자리에 약속된 값을 쓰는 일이에요.
여기서 글이 짚은 엔디안이 나옵니다. 숫자 하나를 여러 바이트로 저장할 때 어느 쪽부터 쓸 것인가의 문제예요. 44100을 저장한다면 큰 자리부터 쓸 수도(빅 엔디안), 작은 자리부터 쓸 수도(리틀 엔디안) 있습니다. RIFF 규격은 리틀 엔디안을 쓰도록 정해져 있어요.
자바스크립트에서 DataView를 쓰는 이유가 여기 있습니다. DataView는 바이트 순서를 명시적으로 지정할 수 있거든요. 순서를 잘못 쓰면 파일은 만들어지는데 재생하면 잡음만 나옵니다. 에러가 안 나고 결과만 틀리는 종류의 버그라 원인을 찾기가 특히 어렵죠.
이런 작업을 직접 해보는 게 왜 값진가 하면, 파일 형식이 더 이상 블랙박스가 아니게 되기 때문입니다. 이후로는 "이 라이브러리가 안 되는데 왜 안 되지"를 물을 때 훨씬 유리한 위치에 서게 돼요.
큐레이터 노트
보통 라이브러리로 때우는 지점을 직접 내려간 글이라 뽑았습니다.
실무에서는 대개 라이브러리를 쓰는 게 맞습니다. 시간도 아끼고 검증도 되어 있으니까요. 그런데 한 번쯤 직접 만들어보는 경험은 그 뒤로 계속 쓰입니다. 라이브러리가 이상하게 동작할 때 안을 열어볼 수 있는 사람이 되거든요.
DataView와 엔디안까지 짚은 게 특히 좋았습니다. 이건 실제로 만들어보지 않으면 절대 안 만나는 문제예요. 개념만 읽었다면 "그런 게 있구나" 하고 지나갔을 텐데, 직접 부딪히면 몸으로 남습니다.
02
✋ [Flutter] TensorFlow 모델을 활용한 Hand gesture recognition 제작
박예승 님
Flutter 앱에 TensorFlow 모델을 붙여 손동작을 인식하는 기능을 만든 기록입니다. 카메라로 손을 찍어서 어떤 제스처인지 판별하는 구조예요.
앱 프레임워크와 머신러닝 모델을 실제로 연결한 사례라, 각각을 따로 공부한 것과는 다른 문제들이 나옵니다.
특히 잘된 점만 쓰지 않고 문제점 분석을 함께 남긴 것이 이 글의 값어치를 높입니다. 무엇이 기대만큼 안 됐는지, 왜 그랬는지가 적혀 있어서 같은 걸 만들려는 사람에게 참고가 됩니다.
머신러닝 모델을 학습시키는 것과, 그 모델을 실제 앱에서 돌리는 것은 다른 일입니다.
이 글은 후자를 다룹니다. 모델은 이미 있고, 그걸 Flutter 앱 안에서 카메라 입력과 연결해 동작하게 만드는 과정이에요.
조금 더 들어가 보면
온디바이스 머신러닝, 즉 서버가 아니라 폰에서 직접 모델을 돌리는 방식에는 분명한 이유가 있습니다.
손동작 인식처럼 카메라 영상을 계속 판별해야 하는 기능은 서버로 보내기 어렵습니다. 초당 30프레임을 서버로 올리면 네트워크 비용도, 지연도 감당이 안 되거든요. 게다가 카메라 영상은 민감한 데이터라 밖으로 안 내보내는 게 낫고요.
그래서 TensorFlow Lite 같은 경량 런타임을 씁니다. 학습은 서버나 PC에서 무겁게 하고, 결과 모델을 폰에서 돌릴 수 있게 변환해서 앱에 싣는 방식이에요.
여기서 실제로 만나는 어려움들이 있습니다.
전처리 불일치 — 모델은 학습할 때 본 형태의 입력만 제대로 처리합니다. 이미지 크기, 색상 채널 순서(RGB인지 BGR인지), 픽셀 값 범위(0~255인지 0~1인지)가 학습 때와 다르면 에러 없이 엉뚱한 결과가 나옵니다. 이런 버그가 가장 골치 아파요. 코드는 잘 도는데 정확도만 낮으니까요.
성능과 정확도의 줄다리기 — 모델을 작게 만들수록 빠르지만 부정확해집니다. 크게 만들면 정확하지만 폰이 뜨거워지고 배터리가 닳아요. 손동작 인식은 실시간성이 중요하니 이 균형점을 찾아야 합니다.
프레임 처리 전략 — 카메라가 주는 모든 프레임을 판별할 필요는 없습니다. 초당 5~10회면 사람이 느끼기엔 충분할 때가 많아요. 대신 판별이 진행 중일 때 들어온 프레임을 어떻게 할지를 정해야 합니다. 쌓아두면 지연이 누적되고, 버리면 놓치는 동작이 생기죠.
결과의 불안정성 — 연속된 프레임에서 판별이 왔다 갔다 하면 UI가 깜빡입니다. 그래서 보통 최근 몇 프레임의 결과를 모아서 다수결을 내거나, 일정 확신도 이상일 때만 반영하는 식으로 다듬습니다.
큐레이터 노트
문제점 분석을 남긴 것이 이 글을 뽑은 이유입니다.
새로운 걸 만들어본 글은 대개 성공 지점에서 끝납니다. "이렇게 해서 동작했습니다"로요. 그런데 그러면 읽는 사람은 자기가 만들 때 어디서 막힐지를 알 수 없어요.
이 글은 잘된 점과 함께 아쉬웠던 점을 적었습니다. 그래서 같은 걸 시도하려는 사람이 미리 각오하거나 피해갈 수 있게 됩니다. 시행착오를 지우지 않는 게 기록의 값어치를 얼마나 바꾸는지 보여주는 사례예요.
박예승 님의 2기 3연속 큐레이션이 여기서 마무리됩니다. 하이브리드 도입 → 컨퍼런스 → ML 연동. 매번 다른 영역을 건드렸다는 게 이 세 편의 특징이었습니다.
💡 두 편을 겹쳐 읽으며
한 편은 파일 형식을 바이트 단위로 조립했고, 한 편은 머신러닝 모델을 앱에 붙였습니다. 겉보기엔 접점이 없어 보이는데, 둘 다 "경계를 넘는 작업"입니다.
이종경 님은 자바스크립트라는 고수준 언어에서 바이너리라는 저수준 세계로 내려갔습니다. 평소엔 신경 쓸 일 없는 바이트 순서까지 맞춰야 했죠.
박예승 님은 앱 프레임워크와 머신러닝 런타임이라는 서로 다른 세계를 연결했습니다. 각각은 잘 정리된 영역인데, 그 사이를 잇는 부분은 문서가 얇습니다.
경계에는 문서가 없습니다. 각 영역의 전문가는 자기 쪽만 설명하니까요. 그래서 경계를 넘어본 사람의 기록이 특히 값집니다.
이번 회차에서 가져갈 것
1. 한 번은 직접 만들어보기. 라이브러리를 쓰는 게 대부분 옳습니다. 그런데 한 번 직접 구현해본 영역은 그 뒤로 블랙박스가 아니게 돼요. 라이브러리가 이상할 때 안을 볼 수 있는 사람이 됩니다.
2. 에러 없이 결과만 틀린 버그를 경계하기. 엔디안이 틀리면 잡음이 나고, 전처리가 틀리면 정확도만 떨어집니다. 동작은 하는데 결과가 이상할 때는 입력 형식부터 의심해보세요.
3. 안 된 것도 적기. 성공만 적은 기록은 다음 사람에게 절반만 도움이 됩니다. 어디서 막혔고 왜 그랬는지가 오히려 더 자주 찾아 읽히는 부분이에요.
좋은 글 남겨주신 이종경 님, 박예승 님께 감사드립니다! 😊
다음 큐레이션으로 다시 찾아오겠습니다! 🌱
- 에디터 · 성장일지 큐레이터 -
※ 본 큐레이션은 각 저자가 공개한 글을 소개하는 것이며, 모든 원문의 저작권은 저자에게 있습니다. 저자 본인의 요청이 있을 경우 즉시 수정 또는 삭제합니다.
'성장일지' 카테고리의 다른 글
| [GL 2기] 멀티탭 데이터 공유, 코틀린 스코프 함수, 그리고 예상 못한 EC2 과금 (성장일지 4회차 큐레이션) 🌱 (0) | 2026.08.07 |
|---|---|
| [GL 2기] 언리얼 강화학습 플러그인, 쿠버네티스 Service, 컨퍼런스에서 건진 것 (성장일지 2회차 큐레이션) 🌱 (0) | 2026.08.07 |
| [GL 2기] import 정렬 자동화부터 언리얼 이동 동기화까지 (성장일지 1회차 큐레이션) 🌱 (0) | 2026.08.07 |
| [GL 1기] Cypress로 시작하는 E2E 테스트, 그리고 4주 만에 동작한 웹크롤러 (성장일지 7회차 큐레이션) 🌱 (0) | 2026.08.07 |
| [GL 1기] AWS 프리티어 12개월 제대로 쓰기, 그리고 JSP 404 추적 (성장일지 6회차 큐레이션) 🌱 (0) | 2026.08.07 |