1인 창업 일지 #24 메트로놈 버그 수정 완료
메트로놈 앱 개발 과정: 정확한 타이밍을 찾아서
메트로놈 앱에서 안드로이드의 경우에만 초창기부터 아래와 같은 상황이 있었습니다.
1단계: Flutter AudioPlayers의 한계 발견
문제 상황
- 프로덕션 환경에서 메트로놈이 버벅이는 현상 발생
- 광고 로직과 메트로놈이 Flutter 메인 스레드를 공유
- Dart Timer의 부정확한 스케줄링으로 박자가 점점 어긋남
2단계: Isolate 분리 시도
개선 결과
- ✅ UI 렉 문제 해결 – 광고 로딩과 독립적으로 동작
- ❌ 타이밍 정확도는 여전히 부족 – Flutter AudioPlayers 자체의 구조적 한계
3단계: 네이티브 전환 결정
문제 인식
- Flutter 레벨에서는 정밀한 오디오 타이밍 제어 불가능
- 하드웨어 기반 정확한 스케줄링이 필요
- Android 네이티브로 완전 이전 결정
4단계: AudioTrack 1차 시도
시도와 좌절
- AudioTrack은 가장 정확한 타이밍 제공
- 그러나 Raw PCM 데이터만 지원 – WAV 파일을 직접 로드하는 API 없음
- WAV 헤더 파싱, PCM 추출, 리샘플링 등 복잡한 전처리 필요
- 빠른 개발을 위해 일단 보류
5단계: Thread.sleep + SoundPool 임시 구현
선택 이유
- SoundPool은 WAV 파일을 쉽게 로드:
soundPool.load(R.raw.click)
- 구현이 단순하여 빠른 배포 가능
- Thread.sleep으로 타이밍 제어 시도
미스터리한 문제 발생
- 🤔 디버그 빌드에서는 완벽하게 작동
- ❌ 릴리즈 빌드나 배포 후 실제 사용 환경에서 박자가 애매하게 어긋남
- 특정 기기에서 더욱 심각한 현상
- 재현이 일관되지 않아 디버깅 어려움
원인 분석
- 릴리즈 빌드의 최적화 옵션이 타이밍에 영향
- 프로덕션 환경의 다양한 백그라운드 프로세스
- Thread.sleep의 OS 스케줄링 의존성 (기기마다 다른 동작)
- 디버그 모드와 릴리즈 모드의 스레드 우선순위 차이
6단계: AudioTrack + WAV 파서 최종 구현
해결 과정
- WAV 파일을 PCM으로 변환하는 커스텀 로더 개발
- WAV 헤더 파싱 (44 bytes)
- 16-bit PCM 데이터 추출
- Little-endian 변환 처리
- AudioTrack의 버퍼 기반 정밀 재생 활용
최종 결과
- ✅ 하드웨어 타이머 기반 정확한 타이밍
- ✅ 커스텀 WAV 사운드 적용 가능
- ✅ 광고/UI와 완전히 독립적인 동작
- ✅ 디버그/릴리즈 빌드 모두 일관된 정확도
- ✅ 모든 기기에서 안정적인 동작
핵심 교훈
“디버그에서 잘 되는데 배포하면 안 되는” 전형적인 타이밍 이슈였습니다. Thread.sleep처럼 OS에 의존하는 방식은 환경에 따라 불안정하며, 메트로놈처럼 정확성이 핵심인 앱에서는 AudioTrack + 커스텀 구현과 같은 하드웨어 기반 솔루션이 유일한 정답이었습니다.
마지막으로
달력 앱을 개발 중이며, 추가적으로, 쇼핑몰 앱을 만들 기회가 생겨 우선적으로 구현 예정입니다.