<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>FFmpeg on 얼쑤 · Team Edo</title><link>https://team-edo.github.io/tags/ffmpeg/</link><description>Recent content in FFmpeg on 얼쑤 · Team Edo</description><generator>Hugo</generator><language>ko-kr</language><lastBuildDate>Sun, 08 Nov 2026 00:00:00 +0900</lastBuildDate><atom:link href="https://team-edo.github.io/tags/ffmpeg/index.xml" rel="self" type="application/rss+xml"/><item><title>10초 AAC를 4.62초만 읽고 성공한 FFmpeg</title><link>https://team-edo.github.io/posts/2026/11/aac-packet-truncation/</link><pubDate>Sun, 08 Nov 2026 00:00:00 +0900</pubDate><guid>https://team-edo.github.io/posts/2026/11/aac-packet-truncation/</guid><description>&lt;p>얼쑤의 연주 분석 서버는 녹음을 받은 뒤 FFmpeg로 소리를 풀어 음높이와 박자를 계산합니다. 이 변환을 구현하면서 10초짜리 AAC 파일을 중간에서 잘라 봤습니다. FFmpeg는 성공을 뜻하는 종료 코드 &lt;code>0&lt;/code>을 반환했고, FFprobe는 파일 길이가 여전히 10초라고 보고했습니다. 디코딩한 샘플을 세어 보니 실제로 읽힌 소리는 &lt;strong>4.62초&lt;/strong>였습니다.&lt;/p>
&lt;h2 id="잘린-위치에-따라-결과가-달랐습니다">잘린 위치에 따라 다른 결과&lt;/h2>
&lt;p>파일의 바이트를 임의 위치에서 자르면 AAC 패킷 중간을 끊을 수 있습니다. 디코더가 불완전한 패킷을 만나면 오류를 냅니다. 오류가 난 구간을 건너뛴 채 분석을 이어가면, 중간 소리가 빠져 뒤의 음이 앞당겨질 수 있습니다. 그래서 디코딩 오류가 발생하면 바로 멈추도록 &lt;code>-xerror&lt;/code>를 사용했습니다. &lt;a href="https://github.com/FFmpeg/FFmpeg/blob/n8.0/doc/ffmpeg.texi">FFmpeg 옵션 문서&lt;/a>&lt;/p></description></item><item><title>FFmpeg를 유지하며 Python 3.12로 맞추기</title><link>https://team-edo.github.io/posts/2026/10/pin-managed-python-in-shared-base-image/</link><pubDate>Wed, 28 Oct 2026 00:00:00 +0900</pubDate><guid>https://team-edo.github.io/posts/2026/10/pin-managed-python-in-shared-base-image/</guid><description>&lt;p>녹음 업로드는 백엔드에서 검사하고, 채점은 Python 분석 서비스에서 실행합니다. 두 서비스에서 같은 FFmpeg 패키지를 쓰기 위해 분석 이미지의 기반을 백엔드와 같은 Ubuntu &lt;strong>26.04&lt;/strong>로 맞췄습니다. 당시 기반 이미지의 시스템 Python은 &lt;strong>3.14&lt;/strong>였고, 분석 프로젝트는 &lt;strong>3.12&lt;/strong>를 사용했습니다. Python 버전을 맞추려고 기반 이미지를 바꾸면 함께 맞춰 둔 FFmpeg 패키지부터 다시 확인해야 했습니다.&lt;/p>
&lt;h2 id="시스템-python과-프로젝트-python을-분리했다">시스템 Python과 프로젝트 Python 분리&lt;/h2>
&lt;p>이미지에 uv를 넣고 &lt;code>UV_PYTHON_PREFERENCE=only-managed&lt;/code>로 설정했습니다. 프로젝트의 &lt;code>.python-version&lt;/code>에 있는 버전을 읽어 uv가 관리하는 Python을 설치하게 했습니다. 런타임 계정도 읽을 수 있는 공용 위치에 설치하고, 앱이 시작될 때는 그 Python으로 만든 가상 환경을 사용하도록 했습니다. uv를 가져오는 이미지도 다이제스트로 지정해 빌드가 참조하는 도구의 버전을 고정했습니다. &lt;a href="https://docs.astral.sh/uv/concepts/python-versions/#adjusting-python-version-preferences">uv의 관리형 Python 설정&lt;/a>&lt;/p></description></item><item><title>스테레오 평균 테스트가 놓친 합산</title><link>https://team-edo.github.io/posts/2026/10/stereo-rms-test-mono-reference/</link><pubDate>Wed, 28 Oct 2026 00:00:00 +0900</pubDate><guid>https://team-edo.github.io/posts/2026/10/stereo-rms-test-mono-reference/</guid><description>&lt;p>얼쑤의 연주 분석은 스테레오 녹음을 16kHz 모노 PCM으로 변환한 뒤 시작합니다. 두 채널을 같은 비중으로 평균 내는 동작을 검사하려고, &lt;strong>440Hz 사인파&lt;/strong>를 담은 &lt;strong>44.1kHz·2초짜리 AAC&lt;/strong> 파일 두 개를 만들었습니다. 한 파일에는 왼쪽 채널에만 소리를 넣고, 다른 파일에는 양쪽에 같은 소리를 넣었습니다.&lt;/p>
&lt;p>처음에는 두 출력의 RMS를 나눴습니다. RMS는 샘플을 제곱해 평균을 낸 뒤 제곱근을 구한 값으로, 여기서는 디코딩한 신호의 크기를 비교하는 데 썼습니다. 왼쪽만 소리가 나는 출력의 RMS를 양쪽에 같은 소리가 나는 출력으로 나눠 &lt;strong>0.5 ± 0.05&lt;/strong>인지 확인했습니다. 리뷰어가 FFmpeg 필터를 두 채널의 &lt;strong>합&lt;/strong>으로 바꿔도 이 검사는 통과했습니다.&lt;/p></description></item><item><title>정답인데 2~4번째 음이 박자 오류로 찍힌 이유</title><link>https://team-edo.github.io/posts/2026/10/aac-priming-timing-fixture/</link><pubDate>Wed, 28 Oct 2026 00:00:00 +0900</pubDate><guid>https://team-edo.github.io/posts/2026/10/aac-priming-timing-fixture/</guid><description>&lt;p>채점 API의 정상 경로를 검사하려고 악보와 같은 음높이·박자로 합성 연주를 만들었습니다. 합성음을 AAC로 인코딩해 분석 API에 넣자 **2~4번째 음이 &lt;code>TIMING&lt;/code>**으로 판정됐습니다. 악보와 합성기의 박자는 그대로였는데, 파일을 거친 뒤에만 음이 늦어진 것처럼 보였습니다.&lt;/p>
&lt;h2 id="합성음과-채점기-사이에-있던-시간">AAC 준비 샘플과 박자 오차&lt;/h2>
&lt;p>테스트는 FFmpeg의 표준 출력에서 조각 MP4를 받아 사용했습니다. AAC 인코더는 앞에 준비 샘플을 붙일 수 있습니다. 재생 타임라인에는 그 구간을 건너뛰는 정보가 필요한데, 이 테스트의 조각 MP4에는 그 정보가 없었습니다. 앱의 녹음 파일은 일반 MP4였습니다. &lt;a href="https://developer.apple.com/documentation/quicktime-file-format/background_aac_encoding">Apple의 AAC 인코딩 설명&lt;/a>&lt;/p></description></item></channel></rss>