얼쑤의 연주 분석 서버는 녹음을 받은 뒤 FFmpeg로 소리를 풀어 음높이와 박자를 계산합니다. 이 변환을 구현하면서 10초짜리 AAC 파일을 중간에서 잘라 봤습니다. FFmpeg는 성공을 뜻하는 종료 코드 0을 반환했고, FFprobe는 파일 길이가 여전히 10초라고 보고했습니다. 디코딩한 샘플을 세어 보니 실제로 읽힌 소리는 4.62초였습니다.

잘린 위치에 따라 다른 결과

파일의 바이트를 임의 위치에서 자르면 AAC 패킷 중간을 끊을 수 있습니다. 디코더가 불완전한 패킷을 만나면 오류를 냅니다. 오류가 난 구간을 건너뛴 채 분석을 이어가면, 중간 소리가 빠져 뒤의 음이 앞당겨질 수 있습니다. 그래서 디코딩 오류가 발생하면 바로 멈추도록 -xerror를 사용했습니다. FFmpeg 옵션 문서

문제가 된 실험에서는 먼저 FFprobe로 오디오 패킷의 시작 위치와 크기를 읽고, 200번째 패킷 끝까지 남겼습니다. 남은 패킷은 각각 온전하므로 FFmpeg는 오류 없이 파일 끝에 도달했습니다. 파일 앞에 있는 MP4 컨테이너의 메타데이터에는 원래 길이가 그대로 남아 있었습니다.

같은 합성 AAC 파일에서 확인한 값관측 결과
FFprobe가 보고한 길이10초
FFmpeg 종료 코드0
PCM 샘플 수로 계산한 길이4.62초

이 .m4a 파일의 MP4 컨테이너는 오디오 스트림과 길이 정보를 담고, AAC 코덱은 소리를 압축하고 복원합니다. 얼쑤의 백엔드 업로드 검사는 컨테이너 형식과 코덱을 확인합니다. 분석 서버에서는 실제로 샘플을 만들기 때문에 두 길이가 어긋나는 경우까지 확인할 수 있습니다.

샘플 수로 재생 길이 확인

분석 서버는 첫 오디오 스트림의 길이를 우선 읽고, 그 값이 없으면 컨테이너 전체 길이를 사용합니다. 디코딩 결과는 16kHz 모노 PCM 배열이므로, 샘플 수를 16,000으로 나누면 실제로 읽힌 길이가 나옵니다. 이 값이 FFprobe가 읽은 길이보다 0.1초 넘게 짧으면 DecodeError로 처리하도록 했습니다.

정상 파일에서도 두 길이의 차이가 −0.001초에서 +0.043초까지 관측돼 허용치에 여유를 두었습니다. 반면 앞의 잘린 파일은 보고된 길이와 읽힌 길이가 5.38초 차이 납니다. 새 검사에서는 FFmpeg가 정상 종료해도 이 차이를 보고 DecodeError로 처리합니다.

회귀 테스트에는 두 경우를 넣었습니다. 패킷의 위치와 크기를 읽어 파일 중간의 패킷이 끝나는 바이트에서 자르고, 다른 파일에서는 44.1kHz AAC의 마지막 6개 패킷만 뺐습니다. 두 경우 모두 채점 입력을 만들기 전에 DecodeError가 발생하는지 검사합니다.

검사 범위는 리뷰를 거치며 넓어졌습니다. 첫 리뷰는 중간에 손상된 패킷을 건너뛰면 분석이 계속될 수 있음을 짚었습니다. 오류에서 멈추도록 바꾼 뒤에는 패킷 경계에서 자른 파일이 정상 종료한다는 두 번째 반례가 나왔습니다. 앞의 길이 검사는 그 반례 때문에 추가했습니다. 세 번째 리뷰에서는 주요 결함 지적 없이 남은 항목을 정리했습니다. 각 회차에서 발견한 반례가 어떤 검사를 추가할지 결정했습니다.

잘린 파일의 실패 기준

리뷰 전 스펙에서는 업로드 중 끝이 잘린 녹음을 TOO_SHORT로 분류했습니다. 파일 손상을 발견한 시점에 DECODE_ERROR로 끝내는 방법과, 끝부분만 잘렸다면 읽힌 구간을 채점하는 방법을 비교했습니다. 읽힌 부분을 채점하려면 중간 손상과 끝 잘림을 구분할 수 있어야 했습니다. 중간 손상을 놓치면 뒤의 소리가 앞당겨진 채 채점될 수 있습니다.

팀은 손상이 확인된 파일을 DECODE_ERROR로 처리하기로 했습니다. TOO_SHORT는 파일을 끝까지 읽었지만 녹음 자체가 정답 길이의 절반보다 짧은 경우에 사용합니다. 두 결과에서 앱이 안내하는 동작은 ‘다시 연주하기’입니다. 이 기준을 스펙의 실패 사유와 변경 이력에 함께 반영했습니다.

FFprobe가 유효한 길이를 주지 않으면 비교를 건너뜁니다. 조각으로 저장한 MP4는 남은 조각만으로 길이를 보고할 수 있어 같은 검사를 통과할 수 있습니다. 이번 사례처럼 원래 길이가 남아 있고 큰 차이가 확인되면, 분석 서버는 파일 오류를 기록하고 앱은 다시 연주하도록 안내합니다. 다운로드 뒤 원본을 찾지 못했거나 디코더가 시간 안에 끝나지 않은 경우에는 서버 실패로 분류해 같은 녹음으로 분석을 다시 요청할 수 있게 했습니다.