오디오 처리 이슈 노트 (2) - 전체 재생할 때는 들리는 데 점프하면 안 들리는 소리
저는 사운드 AI를 만드는 회사에 재직 중이라 오디오 데이터를 다룰 일이 많습니다. 오디오 데이터의 경우 비정형 데이터이기도 하고, 자주 접하지 않는 포맷이기 때문에 상상하지도 못한 이슈가 발생하는 경우가 종종 있습니다. 그 중 기억에 남았던 몇가지를 공유하고자 합니다.
문제 정의
현재 운영 중인 플랫폼에서는 오디오 플레이어에서 전체 오디오 뿐만 아니라 라벨링된 구간만 바로 들어볼 수도 있도록 점프 기능을 지원하고 있습니다. 어느 날 전체 재생할 때는 들리는 데 점프 기능을 사용하면 들리지 않는 소리가 있다는 제보를 받았습니다.
예를 들어 10초에서 11초 구간에 노크소리가 있다고 가정해봅시다. 전체 재생을 하면 10초에서 똑똑하는 노크소리가 분명히 들립니다. 하지만 마우스로 10초 구간을 누르거나, 플레이어에서 제공하는 점프 기능을 이용하면 노크소리가 들리지 않는겁니다. 더 이상한 점은 전체 재생을 한 번 수행한 이후에 점프 기능을 이용하면 또 그때는 소리가 들렸습니다.
원인 분석
이러한 재현 패턴 덕분에 오히려 문제의 원인을 빠르게 좁힐 수 있었습니다. 문제의 원인은 점프 로직이나 플레이어가 아니라 오디오 파일 자체에 있었습니다. 이 경우, 원본 MP3 파일의 VBR 헤더나 프레임 경계가 손상된 것으로 추측되었습니다.
너무 어려운 표현이죠. 좀 더 쉽게 설명해보겠습니다.
MP3는 압축 포맷입니다. 소리 원본을 그대로 저장하지만 그만큼 용량이 큰 WAV와 다르게 MP3는 사람이 잘 인지하지 못하는 정보는 버리고 중요한 정보만 남겨 용량을 줄입니다. 이 과정에서 MP3는 소리를 연속적인 샘플이 아닌 20~30ms의 짧은 소리 조각, 즉 프레임(frame) 단위로 저장합니다.
그렇기 때문에 MP3는 몇 초가 파일의 어느 위치에 해당하는지 알기 위해서 프레임 경계와 프레임 위치 정보가 필요합니다.
이를 위해 MP3 파일의 앞 부분에는 보통 VBR 헤더라는 정보가 포함됩니다. 이 헤더는 쉽게 말해 어느 시간이 어느 프레임에 있는지 알려주는 목차와 비슷한 역할을 합니다. 그런데 문제는 이 헤더가 손상된 경우입니다. 이 헤더가 깨져있으면 플레이어는 점프하고자 하는 시간의 프레임을 잘못 계산하게 됩니다. 그 결과 노크 소리가 들어있는 프레임보다 조금 앞이나 뒤로 이동하거나, 디코딩에 필요한 이전 프레임 정보를 충분히 확보하지 못해 구간 초반의 아주 짧은 소리가 누락될 수 있습니다.
프레임 경계가 손상된 경우에도 비슷한 문제가 발생할 수 있습니다. MP3는 각 프레임마다 ‘여기서 시작해서 여기서 끝난다’라는 경계 정보를 갖고 있습니다. 그런데 이 경계 정보가 깨져있으면 디코더가 프레임을 정확하게 해석하지 못합니다. 이 상태에서 점프 재생을 하면, 프레임의 중간부터 디코딩이 시작되면서 프레임 초반에 포함된 짧은 소리, 예를 들어 노크소리 같은 게 누락될 수 있는 것이죠.
그렇다면 왜 전체 재생할 때는 정상적으로 들렸을까요?
전체 재생할 때는 파일을 앞에서부터 순차적으로 읽습니다. 이 과정에서는 디코더가 이전 프레임의 데이터를 참고하거나 손상된 프레임을 내부적으로 보정하면서 자연스럽게 재생을 이어갑니다. 이미 한번 전체 재생을 거친 경우에는, 디코더가 파일 구조를 어느정도 정리한 상태이기 때문에 점프 재생을 해도 정상적으로 소리가 들렸던 겁니다.
정리하면 다음과 같습니다.
- MP3는 압축 포맷이기 때문에 시간이 아닌 프레임 단위로 저장한다
- 특정 시점을 정확히 찾기 위해서는 프레임 위치 정보가 필요하다
- VBR 헤더(시간-프레임을 맵핑한 목차)나 프레임 경계(프레임 시작, 끝 정보)가 손상되면 점프 재생 시 일부 소리가 누락될 수 있다.
- 전체 재생 시에는 디코더가 순차적으로 보정하면서 읽기에 문제가 드러나지 않을 수 있다.
문제 해결
그렇다면 이 문제는 어떻게 해결할 수 있을까요?
이 문제는 재생 로직이나 플레이어가 아니라 명확하게 해당 MP3 파일 자체의 문제였습니다. 원인은 앞서 말씀드린대로 VBR 헤더나 프레임 경계가 손상된 것으로 추정되었습니다. 저는 여기서 원인을 더 깊이 분석하기 보다, 단순하게 해결했습니다. 바로 아래 명령어를 사용해 해당 MP3 파일을 재인코딩했습니다.
ffmpeg -i input.mp3 -c:a libmp3lame -q:a 2 output.mp3
재인코딩을 하게 되면 기존 MP3 파일을 프레임 단위로 디코딩한 후, 손상되었거나 불완전한 프레임을 정리하고 새 프레임구조로 다시 인코딩 합니다. 이 과정에서 VBR 헤더도 새로 작성합니다. 이렇게 되면 원인이 프레임 경계에 있었는지, 헤더였는지와 관계 없이 정상적인 프레임 구조로 정리할 수 있는 것입니다.
이렇게 재인코딩한 이후에 해당 이슈는 말끔하게 해결되었습니다.