$ cd /blog
오류 해결

Flutter 효과음 때문에 화면이 끊길 때 — audioplayers AudioPool로 프레임 히치 없애기

탭할 때마다 효과음이 늦게 나고 화면이 걸리는 건 소리를 낼 때마다 오디오 소스를 다시 준비하기 때문입니다. AudioPool 프리로드와 앱 시작 워밍업으로 히치를 없앤 실제 방법을 정리했습니다.


안녕하세요. Jay입니다!

제가 운영하는 퍼즐 게임에서 실사용자 제보를 하나 받았습니다. "터뜨릴 때 화면이 살짝 끊겨요." 프로파일러를 돌리기 전에 짐작이 갔습니다. 그 시점에 하는 일이라곤 타일을 지우고 효과음을 재생하는 것뿐이었거든요. 오늘은 이 소리 때문에 생기는 프레임 히치를 어떻게 잡았는지 공유합니다.

🔍 원인 — 소리 낼 때마다 "준비"를 다시 한다

처음 코드는 이랬습니다. AudioPlayer 인스턴스를 서너 개 만들어 돌려 쓰고, 지연을 줄이려고 PlayerMode.lowLatency까지 지정했습니다. 여기까지는 흔히 하는 최적화죠. 문제는 재생 호출 쪽에 있었습니다.

// 문제가 된 방식 — 팝이 터질 때마다 호출
await player.play(AssetSource('sfx/pop.wav'));

이 한 줄은 생각보다 무겁습니다. 호출할 때마다 플랫폼 채널을 왕복하고, 네이티브 쪽에서 에셋을 찾아 소스를 다시 준비합니다. 타일을 탭할 때마다 이 작업이 UI 프레임을 그리는 타이밍에 끼어드니, 한 프레임이 밀리면서 "끊긴다"는 체감이 생깁니다.

여기서 오해하기 쉬운 게 lowLatency 모드입니다. 이건 플레이어를 재사용하게 해주는 옵션이지, 재생할 때마다 소스를 새로 지정하는 비용까지 없애주지는 않습니다.

게다가 초기화를 첫 재생 시점에 지연(lazy) 시켜 두면, 첫 번째 탭에서 오디오 컨텍스트 설정과 디코딩이 통째로 몰립니다. 그래서 보통 첫 소리가 가장 크게 끊깁니다.

🛠 해결 1 — AudioPool로 미리 디코드해 두기

audioplayers(6.x)에는 이 상황을 위한 AudioPool이 있습니다. 에셋을 미리 로드해 둔 플레이어 묶음을 만들어 두고, 재생할 땐 start()만 호출하는 구조입니다. 소스 준비가 이미 끝나 있으니 재생 시점에 할 일이 거의 없습니다.

_pop = await AudioPool.createFromAsset(
  path: 'sfx/pop.wav',
  maxPlayers: 4, // 연타로 겹칠 수 있는 최대 개수
);

// 재생은 이게 전부
await _pop?.start(volume: 0.5);

핵심 파라미터는 maxPlayers입니다. 동시에 겹쳐 울릴 수 있는 최대 개수라, 소리 성격에 맞춰 다르게 잡으면 됩니다. 저는 연타가 잦은 팝 소리는 4개, 연속 뽑기 연출은 2개, 단계업·게임오버처럼 겹칠 일 없는 소리는 1개로 뒀습니다. 무작정 크게 잡으면 그만큼 메모리를 미리 잡아먹으니 실제 겹침 수만큼만 주는 게 좋습니다.

⚡ 해결 2 — 앱 시작 때 워밍업 한 줄

풀을 쓰더라도 처음 만드는 순간의 비용은 남아 있습니다. 그 순간이 첫 탭이면 결국 첫 히치는 그대로죠. 그래서 앱이 뜬 직후에 미리 로드해 둡니다.

WidgetsBinding.instance.addPostFrameCallback((_) {
  Sfx.warmUp(); // await 하지 않음 — 시작을 붙잡지 않는다
});

포인트는 첫 프레임이 그려진 뒤에, 기다리지 않고 부르는 것입니다. await로 붙잡으면 앱 시작이 그만큼 늦어지니, 백그라운드로 흘려보내고 사용자가 첫 탭을 하기 전에 준비가 끝나게 두는 편이 자연스럽습니다.

💡 Jay의 팁 — 효과음은 실패해도 게임이 멈추면 안 됩니다

효과음은 어디까지나 부가 요소입니다. 오디오 초기화가 실패했다고 게임이 죽으면 손해가 훨씬 크죠. 그래서 저는 세 가지를 원칙으로 둡니다.

  • 모든 오디오 호출은 try/catch로 감싼다 — 실패해도 조용히 무시하고 게임은 계속
  • 설정 토글을 가장 앞에서 검사한다 — 소리를 끈 사용자에겐 초기화 자체를 시도하지 않음
  • 오디오 컨텍스트로 예의를 지킨다respectSilence(무음 스위치 존중) + mixWithOthers(다른 앱 음악을 끊지 않음). 자기 전에 켜는 앱이라면 특히 중요합니다

결론

효과음 히치는 "소리가 무거워서"가 아니라, 소리를 낼 때마다 준비를 반복해서 생깁니다.

정리하면 두 줄입니다. 재생마다 소스를 지정하는 방식 대신 AudioPool로 미리 디코드해 두고, 그 로드를 앱 시작 시점으로 앞당기면 됩니다. 탭 반응이 중요한 게임이나 인터랙션이 잦은 앱이라면 효과 체감이 꽤 큽니다. 혹시 "소리 때문에 화면이 걸린다"는 제보를 받으셨다면 재생 코드부터 열어보세요. 다음에도 유익한 포스팅으로 찾아오겠습니다. 감사합니다!

📚 참고 / 출처

#Flutter#audioplayers#AudioPool#효과음#성능 최적화#게임 개발

$ ls related/

더 많은 IT·AI 활용법이 궁금하다면?

매주 새로운 실전 가이드가 업데이트됩니다.

전체 글 보기