💡 한 줄 요약
- 이 글에서는
probe.cpp가 스트림의disposition플래그와metadata를 어떻게 읽는지, 그리고av_find_best_stream이 여러 트랙 가운데 어떤 기준으로 “가장 적절한” 트랙을 고르는지를 Matroska 공식 테스트 파일test5.mkv로 확인해본다.
🚨 1. 문제 상황
하나의 컨테이너 안에 오디오 트랙이 여러 개 들어 있거나, 자막 트랙이 여러 언어로 함께 들어 있는 파일을 다룰 때가 있다. 이럴 때 프로그램은 재생을 시작하면서 “어떤 트랙을 기본으로 선택할지”를 스스로 판단해야 한다.
이때 가장 흔한 실수는 streams[0]을 무조건 기본 트랙이라고 보는 것이다.
하지만 스트림 배열의 순서는 인코딩이나 먹싱 과정에서 정해질 뿐, “이 트랙이 기본이다”라는 의미를 보장하지 않는다.
예를 들어 다음과 같은 경우가 얼마든지 가능하다.
- 비디오가 1번 스트림이고 오디오는 0번 스트림인 경우
- 다국어 더빙 파일에서 첫 번째 오디오가 원어가 아닌 경우
- 자막이 여러 개 있지만 기본 자막은 중간 인덱스에 있는 경우
⚠️ 여기서 필요한 질문
앞선 글들에서 probe.cpp는 스트림을 순서대로 훑으면서 정보를 출력하는 데까지는 다뤘지만,
“그래서 실제로 어떤 트랙을 골라야 하는가?”라는 문제는 아직 다루지 않았다.
이 질문에 답할 때 핵심이 되는 것이 바로 disposition, metadata, av_find_best_stream이다.
🛠️ 2. 개발 환경과 테스트 파일
2.1 개발 환경
| 분류 | 기술 스택 / 도구 | 버전 | 비고 |
| OS / 아키텍처 | Ubuntu | x86_64 | |
| FFmpeg | libavformat 등 | 6.1.1-3ubuntu5 | Ubuntu 패키지 빌드 |
| 컴파일러 | g++ | 13.3.0 | |
| 언어 표준 | C++20 이상 |
2.2 테스트 파일: Matroska 공식 테스트 스위트
앞선 1편과 2편에서는 임의의 MKV 파일 하나로 동작을 확인했다.
하지만 이번에는 다중 오디오, 다중 자막처럼 조금 더 분명한 조건이 필요하다. 직접 테스트 파일을 만들 수도 있지만, 이번 글에서는 Matroska 공식 테스트 스위트를 사용했다.
Matroska.org에서는 플레이어나 파서가 컨테이너 기능을 제대로 처리하는지 확인할 수 있도록 8개의 공식 테스트 파일을 제공한다.
- 다운로드 페이지: matroska.org/downloads/test_suite.html
이 페이지에서 SourceForge 다운로드 링크와 Release Notes를 함께 확인할 수 있다.
구성은 다음과 같다.
| 파일 | 검증 목적 |
| test1.mkv | 기본 파일 (Big Buck Bunny 기반) |
| test2.mkv | 비표준 timecodescale, 화면비 |
| test3.mkv | 헤더 스트리핑, 표준 블록 |
| test4.mkv | 라이브 스트림 레코딩 |
| test5.mkv | 다중 오디오, 다중 자막 트랙 |
| test6.mkv | 서로 다른 EBML 헤더 크기, cue 없는 탐색 |
| test7.mkv | 알 수 없거나 손상된 엘리먼트 |
| test8.mkv | 오디오 갭 |
이번 글에서는 이 가운데 test5.mkv를 사용한다.
이 파일은 다중 오디오·자막 트랙을 검증하기 위해 공식적으로 제공되는 사례이므로, 임의로 만든 샘플보다 기준이 분명하다는 장점이 있다.
✅ 3. 트랙 선택 방법
3.1 disposition으로 기본 트랙 여부를 본다
AVStream.disposition은 비트 플래그 필드다.
컨테이너를 만들 때 “이 트랙을 기본으로 써라”, “이 자막은 강제 자막이다” 같은 의도를 여기에 담아둘 수 있다.
probe.cpp에서는 그중 자주 쓰이는 두 플래그를 확인한다.
if (st->disposition & AV_DISPOSITION_DEFAULT)
std::printf(" disposition: default\n");
if (st->disposition & AV_DISPOSITION_FORCED)
std::printf(" disposition: forced\n");
각 플래그의 의미는 다음과 같다.
AV_DISPOSITION_DEFAULT
사용자가 별도로 고르지 않았을 때 기본으로 선택할 트랙이라는 뜻이다.AV_DISPOSITION_FORCED
주로 자막에서 쓰이며, 사용자가 자막을 꺼두었더라도 반드시 보여줘야 하는 자막을 뜻한다.
예를 들어 외국어 대사 부분만 번역해주는 강제 자막이 여기에 해당한다.
이 밖에도 AV_DISPOSITION_DUB, AV_DISPOSITION_ORIGINAL, AV_DISPOSITION_COMMENT, AV_DISPOSITION_HEARING_IMPAIRED 같은 플래그가 더 있다.
즉 disposition만 잘 봐도 “더빙인지”, “원어인지”, “코멘터리인지”, “청각장애인용인지” 같은 정보를 어느 정도 구분할 수 있다.
3.2 metadata로 언어와 부가 정보를 확인한다
disposition이 정해진 의미의 플래그라면, metadata는 좀 더 자유로운 키-값 정보다.
대표적으로 language, title 같은 값이 여기에 들어간다.
for (const AVDictionaryEntry *e = nullptr;
(e = av_dict_iterate(st->metadata, e));)
std::printf(" meta : %s=%s\n", e->key, e->value);
av_dict_iterate는 AVDictionary를 순회할 때 쓰는 일반적인 패턴이다.
다국어 트랙 가운데 특정 언어를 고르고 싶다면, 여기서 language 키를 확인하면 된다.
값은 보통 eng, kor, jpn처럼 ISO 639 코드 형태로 들어 있다.
중요한 점은 다음과 같다.
disposition만으로는 트랙의 언어를 알 수 없다.- 언어 기준으로 트랙을 고르려면
metadata를 직접 확인해야 한다.
즉 “기본 트랙인지”와 “어떤 언어인지”는 서로 다른 정보이며, 각각 다른 근거로 판단해야 한다.
3.3 av_find_best_stream은 여러 후보 중 하나를 골라준다
disposition과 metadata가 각 스트림의 속성이라면, av_find_best_stream은 그런 정보를 바탕으로 실제로 하나를 골라주는 함수다.
int av_find_best_stream(AVFormatContext *ic, enum AVMediaType type,
int wanted_stream_nb, int related_stream,
const AVCodec **decoder_ret, int flags);
주요 파라미터는 다음과 같다.
type
찾고 싶은 미디어 타입이다. 예를 들어AVMEDIA_TYPE_VIDEO,AVMEDIA_TYPE_AUDIO등을 넣는다.wanted_stream_nb
원하는 스트림 인덱스를 이미 알고 있다면 지정한다.-1이면 함수가 알아서 고른다.related_stream
특정 스트림과 연관된 트랙을 찾을 때 쓴다.
예를 들어 어떤 비디오에 맞는 오디오를 찾는 식이다.
보통은-1로 둔다.decoder_ret
필요하면 선택된 스트림에 맞는 디코더도 함께 돌려받을 수 있다.
필요 없으면nullptr로 둔다.flags
보통 0을 넣는다.
내부적으로는 nb_streams를 순회하면서, 요청한 타입과 일치하는 스트림들만 후보로 모은 뒤 그중 가장 적절한 하나를 고른다.
이때 disposition, 스트림 특성, 디코더 사용 가능 여부 같은 요소가 함께 고려된다.
probe.cpp에서는 가장 단순한 형태로 호출한다.
int vi = av_find_best_stream(fmt.get(), AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
int ai = av_find_best_stream(fmt.get(), AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
즉 “비디오 하나, 오디오 하나를 알아서 골라 달라”는 뜻이다.
앞선 글에서 사용한 CAM1_Edit_noB.mkv처럼 비디오 1개, 오디오 1개뿐인 파일에서는 이 함수가 특별히 고민할 일이 없다.
하지만 test5.mkv처럼 오디오와 자막이 여러 개인 파일에서는 이 함수가 어떤 기준으로 선택하는지가 실제로 드러난다.
📊 4. 결과 검증
test5.mkv를 대상으로 ffprobe -v quiet -show_streams -show_format 출력과 probe.cpp 출력을 비교했다.
확인한 항목은 다음과 같다.
- 각 오디오/자막 스트림의
disposition language같은 메타데이터av_find_best_stream이 최종적으로 고른 스트림 인덱스
이 파일은 총 11개 스트림으로 구성되어 있다.
- 비디오 1개
- 오디오 2개
- 자막 8개
비교 결과는 다음과 같다.
| 항목 | ffprobe | probe.cpp |
| 전체 스트림 수 | 11 (nb_streams=11) |
11 (stream #0 ~ #10) |
| 비디오 #0 disposition | default=1 |
default |
| 오디오 #1 (48kHz stereo) disposition / language | default=1 / language 태그 없음 |
default / 메타데이터 없음 |
| 오디오 #8 (22kHz mono, Commentary) disposition / language | default=0 / eng |
default 표시 없음 / language=eng, title=Commentary |
| 자막 #2 (eng) disposition | default=1 |
default |
| 자막 #3~7, #9 (hun/ger/fre/spa/ita/jpn) disposition | default=0 |
default 표시 없음 |
| 자막 #10 (language 태그 없음) disposition | default=0 |
default 표시 없음 |
av_find_best_stream 선택 인덱스 |
— | video: #0, audio: #1 |
결과는 서로 정확히 일치했다.
특히 av_find_best_stream이 고른 오디오 #1은 default가 붙은 유일한 오디오 트랙과 같았다. 이 파일에서는 함수의 선택이 disposition 정보와 그대로 맞아떨어진 셈이다.
💭 실제로 확인해보면 흥미로운 점
기본 오디오 트랙인 #1에는 정작
language메타데이터가 없다.
반면 코멘터리 오디오인 #8에는language=eng,title=Commentary가 들어 있다.
즉 이 파일에서는 “기본 트랙”과 “언어를 알 수 있는 트랙”이 서로 다르다.이 점은 꽤 중요하다.
disposition만 보고 언어를 짐작해서도 안 되고, 반대로 language가 있다고 해서 그 트랙을 기본 트랙으로 봐서도 안 된다.
기본 선택과 언어 선택은 서로 다른 기준으로 처리해야 한다는 뜻이다.자막도 마찬가지다.
default는 영어 자막 #2에만 붙어 있다.
결국 비디오, 오디오, 자막은 각각 자기 타입 안에서 따로 기본값을 가질 수 있다.
🎯 5. 적용 범위와 자주 하는 실수
적용 범위
av_find_best_stream은 어디까지나 “적당한 기본값”을 골라주는 함수다.
사용자가 특정 언어 더빙이나 특정 자막을 명시적으로 원한다면, 이 함수만으로는 충분하지 않다.- 그런 경우에는
metadata의language값을 직접 확인해 필터링하거나, 사용자가 고른 인덱스를wanted_stream_nb로 넘기는 방식이 필요하다.
❌ 자주 하는 실수
default가 여러 트랙에 동시에 붙은 파일을 고려하지 않고, 처음 만난 것만 무조건 신뢰하는 경우default가 하나도 없는 파일에서av_find_best_stream의 결과를 “제작자가 의도한 기본 트랙”이라고 단정하는 경우- 기본 트랙에는 당연히
language태그가 있을 것이라고 가정하는 경우
특히 마지막 경우는 실제 파일에서 자주 어긋난다.test5.mkv의 메인 오디오처럼 기본 트랙인데도 언어 정보가 비어 있을 수 있다.
반대로 언어 정보가 잘 들어 있는 트랙이 코멘터리일 수도 있다.
즉 다음 두 판단은 분리해서 다뤄야 한다.
- 기본으로 선택할 것인가 →
disposition - 어떤 언어인가 →
metadata
🏁 6. 마무리
이 시리즈 1~3편에서는 probe.cpp 하나를 바탕으로 FFmpeg 디먹싱 초기화 흐름을 차례대로 따라왔다.
avformat_open_input으로 컨테이너를 연다avformat_find_stream_info로 스트림 정보를 채운다AVCodecParameters를 타입에 맞게 읽는다disposition,metadata,av_find_best_stream으로 어떤 트랙을 쓸지 정한다
여기까지가 일단 “파일을 열고, 스트림을 이해하고, 기본 트랙을 고르는 단계”라고 볼 수 있다.
참고 자료
예제 코드
decode-render/tools/probe.cpp at main · moony211/decode-render
디코딩과 렌더링을 연구한다. Contribute to moony211/decode-render development by creating an account on GitHub.
github.com