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

`AVCodecParameters` 이해하기 - `probe.cpp`로 살펴보는 비디오·오디오 스트림 정보와 시간 단위의 함정

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

💡 한 줄 요약

  • 이 글에서는 AVCodecParameters가 미디어 타입에 따라 읽어야 할 필드가 달라진다는 점과, AVStream.time_baseAVFormatContext.duration이 서로 다른 시간 단위를 쓰는 이유를 probe.cppprint_stream, print_format 코드를 바탕으로 설명한다.

🚨 1. 문제 상황과 대상 독자

AVCodecParameters를 다루다 보면 보통 두 지점에서 많이 헷갈린다.

  • 비디오 스트림과 오디오 스트림이 서로 다른 필드를 쓰는데, 타입을 확인하지 않고 접근했다가 의미 없는 값을 읽는 경우
  • AVStream.durationAVFormatContext.duration을 같은 단위로 생각해 시간 계산이 틀어지는 경우

⚠️ 자주 부딪히는 문제

FFmpeg 5.1 이전 예제를 참고하다 보면 par->channels, par->channel_layout을 그대로 읽는 코드가 아직도 많이 보인다.
하지만 최신 FFmpeg에서는 이 필드들이 deprecated 상태라, 그대로 가져다 쓰면 컴파일 경고가 나거나 기대한 값과 다르게 동작할 수 있다.

 

이 글은 다음과 같은 분들을 염두에 두고 썼다.

  • 1편에서 AVFormatContext, AVStream 초기화 흐름까지 따라온 분
  • FFmpeg로 미디어 메타데이터를 직접 읽고 있고, 최근 channels, channel_layout이 deprecated되었다는 사실을 접한 분

🛠️ 2. 사전 지식과 개발 환경

2.1 사전 지식

  • 1편 「FFmpeg AVFormatContext, RAII로 감싸야 하는 이유」에서 다룬 avformat_open_input, avformat_find_stream_info 흐름
  • AVFormatContext.streams[i]AVStream이고, 그 안의 codecparAVCodecParameters라는 구조

2.2 개발 환경

분류 기술 스택 / 도구 버전 비고
OS / 아키텍처 Ubuntu x86_64  
FFmpeg libavformat / libavutil 등 6.1.1-3ubuntu5 Ubuntu 패키지 빌드
컴파일러 g++ 13.3.0  
언어 표준 C++20 이상    

 


🔍 3. 핵심 개념과 원인

3.1 AVCodecParameters는 타입에 따라 읽는 필드가 달라진다

AVCodecParameters는 하나의 구조체 안에 여러 미디어 타입의 필드가 함께 들어 있다.
그래서 어떤 필드가 실제로 의미를 가지는지는 codec_type에 따라 달라진다. 사용 방식만 놓고 보면 판별 유니온(discriminated union)에 가깝다.

probe.cpp의 분기 코드는 이 점을 잘 보여준다.

if (par->codec_type == AVMEDIA_TYPE_VIDEO) {
    std::printf("  resolution : %dx%d\n", par->width, par->height);
    std::printf("  pix_fmt    : %s\n",
                av_get_pix_fmt_name((AVPixelFormat)par->format));
    if (st->avg_frame_rate.num)
        std::printf("  frame_rate : %.3f fps\n", av_q2d(st->avg_frame_rate));
} else if (par->codec_type == AVMEDIA_TYPE_AUDIO) {
    std::printf("  sample_rate: %d Hz\n", par->sample_rate);
    std::printf("  sample_fmt : %s\n",
                av_get_sample_fmt_name((AVSampleFormat)par->format));
    char lb[64] = {0};
    av_channel_layout_describe(&par->ch_layout, lb, sizeof(lb));
    std::printf("  ch_layout  : %s (%d ch)\n", lb, par->ch_layout.nb_channels);
}

 

예를 들어 format 필드만 봐도 그렇다.

  • 비디오에서는 AVPixelFormat으로 해석해야 하고
  • 오디오에서는 AVSampleFormat으로 해석해야 한다

codec_type을 먼저 확인하지 않은 채 format을 읽으면, 값은 있어도 의미를 잘못 해석하게 된다.


3.2 왜 channels, channel_layout 대신 ch_layout을 써야 할까

FFmpeg 5.1 이전에는 오디오 채널 정보를 다음 두 필드로 표현했다.

  • int channels: 채널 수
  • uint64_t channel_layout: 채널 배치를 나타내는 비트마스크

이 방식은 단순한 경우에는 쓸 만했지만, 점점 한계가 드러났다.

  • 비트마스크만으로는 5.1.4, 7.1.4처럼 높이 채널이 포함된 확장 레이아웃이나, 표준에 없는 임의 채널 순서를 표현하기 어렵다.
  • channelschannel_layout이 서로 다른 정보를 담고 있어, 둘이 어긋나는 경우를 항상 따로 확인해야 한다.

이를 보완하기 위해 FFmpeg 5.1부터 AVChannelLayout 구조체와 ch_layout 필드가 도입됐고, 기존 channels, channel_layout은 deprecated되었다.

AVChannelLayout은 다음과 같은 정보를 함께 담는다.

  • 채널 배치 방식(order)
  • 채널 수(nb_channels)
  • 비트마스크 또는 커스텀 채널 배열

즉 예전보다 훨씬 일반적인 방식으로 채널 구성을 표현할 수 있게 된 셈이다.

그래서 probe.cpppar->channels를 직접 읽지 않고, av_channel_layout_describe(&par->ch_layout, ...)로 사람이 읽기 쉬운 문자열로 바꿔 출력한다.
이 함수는 내부 표현이 네이티브 비트마스크이든, 커스텀 배열이든 상관없이 일관된 문자열을 돌려준다.

💭 여기서 문자열화는 어디까지나 출력용이다.
AVChannelLayout을 실제로 설정할 때는 av_channel_layout_default(), AV_CHANNEL_LAYOUT_5POINT1 같은 표준 매크로, av_channel_layout_from_string() 같은 API를 상황에 맞게 사용하면 된다.
즉 구조체 내부가 문자열로 저장되는 것은 아니다.

 

N.M 표기에서 N은 수평 방향 스피커 수, M은 서브우퍼(LFE) 수를 뜻한다. 여기에 세 번째 숫자가 붙으면(예: 5.1.4, 7.1.4) 천장 방향 또는 머리 위쪽에 배치된 높이 스피커 수를 의미한다. Dolby Atmos, DTS:X 같은 몰입형 오디오 포맷에서 자주 쓰인다.


3.3 왜 time_base는 스트림마다 다르고, 컨테이너 레벨 시간은 고정 단위를 쓸까

probe.cpp에는 시간을 계산하는 코드가 두 가지 나온다.

// 스트림 레벨
if (st->duration != AV_NOPTS_VALUE)
    std::printf("  duration   : %.3f s\n",
                st->duration * av_q2d(st->time_base));

// 컨테이너 레벨
if (fc->duration != AV_NOPTS_VALUE)
    std::printf("  duration   : %.3f s\n", fc->duration / double(AV_TIME_BASE));

 

계산식이 다른 이유는 두 값이 애초에 서로 다른 시간 단위를 쓰기 때문이다.

  • AVStream.time_base
    각 스트림에 맞는 시간 단위다.
    예를 들어 어떤 비디오 스트림은 1/90000, 어떤 오디오 스트림은 1/48000처럼 설정될 수 있다.
    스트림의 타임스탬프를 얼마나 세밀하게 표현해야 하는지에 따라 값이 달라진다.
  • AVFormatContext.duration
    컨테이너 전체 길이를 나타내는 값이다.
    이 값은 항상 AV_TIME_BASE를 기준으로 표현되며, 단위는 마이크로초(1/1000000)다.

왜 이렇게 나뉘어 있을까?

  • 스트림 레벨에서는 각 스트림에 맞는 정밀도가 중요하고
  • 컨테이너 레벨에서는 비디오, 오디오, 자막처럼 서로 다른 스트림을 하나의 기준으로 비교할 수 있어야 하기 때문이다

즉 스트림 레벨은 원본 스트림의 정밀도, 컨테이너 레벨은 서로 다른 스트림 사이의 공통 기준을 우선한 설계라고 보면 된다.

그래서 둘을 섞어 쓰면 문제가 생긴다. 예를 들어

  • 스트림 durationAV_TIME_BASE로 나누거나
  • 컨테이너 duration에 스트림 time_base를 곱하면

대부분 잘못된 값이 나온다.

💭 컨테이너 레벨 시간값은 실전에서도 자주 쓰인다.
예를 들어 플레이어에서 전체 재생 시간을 표시할 때, 또는 서로 다른 스트림 길이를 같은 기준으로 비교할 때 유용하다.
탐색 API에서도 이런 공통 단위가 필요하다.

 


✅ 4. 해결 방법과 검증

4.1 해결 방법

정리하면 다음 원칙만 지켜도 실수를 크게 줄일 수 있다.

  • AVCodecParameters는 반드시 codec_type으로 먼저 분기한 뒤, 해당 타입에 맞는 필드만 읽는다.
  • 채널 정보는 ch_layout만 사용하고, 필요하면 av_channel_layout_describe()로 문자열로 바꿔 출력한다.
  • 시간을 초 단위로 바꿀 때는 값의 출처를 먼저 확인한다.
    • AVStream에서 온 값이면 해당 스트림의 time_base
    • AVFormatContext에서 온 값이면 AV_TIME_BASE

둘은 절대 섞어 쓰지 않는다.


4.2 결과 검증

검증은 ffprobe -v quiet -show_streams -show_format <file> 출력과 probe.cpp 출력을 나란히 비교하는 방식으로 할 수 있다.
해상도, 샘플레이트, 채널 레이아웃, duration 값이 일치하는지 확인하면 된다.

실제로 CAM1_Edit_noB.mkv 파일로 비교한 결과는 다음과 같다.

항목 ffprobe probe.cpp
resolution 1920x1080 1920x1080
pix_fmt yuv420p yuv420p
avg_frame_rate 30/1 30.000 fps
sample_rate 48000 Hz 48000 Hz
ch_layout stereo (channels=2) stereo (2 ch)
컨테이너 duration 95.766000 s 95.766 s

 

확인한 값은 모두 일치했다.

💭 실제로 보면 흥미로운 점

이 파일은 스트림 레벨 duration(duration_ts)이 두 스트림 모두 N/A로 나온다.
즉 MKV라고 해서 항상 AVStream.duration이 채워지는 것은 아니다.
대신 각 스트림의 TAG:DURATION 메타데이터에는 비디오 00:01:35.766, 오디오 00:01:35.722처럼 값이 들어 있고, 둘 사이에는 44ms 차이가 난다.
오디오 스트림의 start_time이 0.021(21ms)인 점까지 함께 보면, 오디오가 약간 늦게 시작하고 그만큼 끝나는 시점도 달라진 것으로 해석할 수 있다.
반면 컨테이너 레벨 duration은 이런 미세한 차이와 별개로 파일 전체를 대표하는 하나의 값으로 유지된다.

 


💡 5. 조금 더 알아둘 점

5.1 적용 범위

  • AVChannelLayout, ch_layout API는 FFmpeg 5.1부터 도입되었다.
    그 이전 버전을 대상으로 한다면 이 글의 ch_layout 관련 내용은 그대로 적용되지 않고, 기존 channels, channel_layout 조합을 써야 한다.

5.2 자주 하는 실수

  • format 필드를 미디어 타입 확인 없이 무조건 AVPixelFormat 또는 AVSampleFormat으로 캐스팅하는 경우
  • 스트림 durationAV_TIME_BASE로 나누거나, 컨테이너 duration에 스트림 time_base를 곱하는 식으로 단위를 섞는 경우

5.3 FAQ

  • Q. avg_frame_rate가 0으로 나온다.
    A. 가변 프레임레이트(VFR) 스트림이거나, 헤더 정보만으로는 프레임레이트를 추정하기 어려운 경우 그럴 수 있다. 이런 때는 r_frame_rate를 참고하거나, 실제 패킷 타임스탬프 간격을 분석해야 한다.

🏁 6. 마무리

AVCodecParameters는 한 구조체에 여러 타입의 정보가 함께 들어 있는 만큼, 반드시 타입을 먼저 확인하고 그에 맞는 필드만 읽어야 한다.
오디오 채널 정보는 FFmpeg 5.1부터 ch_layout 중심으로 정리되었고, 시간값은 스트림 레벨과 컨테이너 레벨이 서로 다른 목적을 위해 다른 단위를 쓴다.

이 세 가지만 분명히 잡고 있어도, 스트림 메타데이터를 읽을 때 생기는 실수는 많이 줄어든다.
다음 글에서는 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