본문 바로가기
잡(job)기술

어떤 트랙을 기본으로 볼 것인가 - `disposition`, `metadata`, `av_find_best_stream`으로 트랙 고르기

by 무니이구나 2026. 9. 23.

💡 한 줄 요약

  • 이 글에서는 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개의 공식 테스트 파일을 제공한다.

이 페이지에서 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