move84

H.264 NAL Unit 구조와 UDP 스트리밍 디코딩 이해하기 본문

카테고리 없음

H.264 NAL Unit 구조와 UDP 스트리밍 디코딩 이해하기

move84 2025. 3. 27. 23:03
반응형

영상 스트리밍 분야에서 H.264는 널리 사용되는 영상 압축 표준이다. 특히, 실시간 전송이 필요한 환경에서는 H.264로 인코딩된 영상을 UDP(User Datagram Protocol)를 통해 전송하는 경우가 많다. 이러한 경우, .mp4.mkv와 같은 컨테이너 포맷 없이 순수한 H.264 Raw 스트림만 전송되기 때문에, 이를 정확히 해석하고 디코딩하기 위해서는 H.264의 내부 구조에 대한 깊은 이해가 필요하다.

이 글에서는 H.264의 핵심 단위인 NAL(Network Abstraction Layer)과 NAL Unit(NALU)의 구조를 살펴보고, 이러한 스트림이 어떻게 UDP로 전송되며, 디코딩을 위해 어떤 순서로 처리되어야 하는지를 상세히 설명한다.


2. NAL과 NALU란?

용어 약어 설명
NAL Network Abstraction Layer H.264의 전송 추상화 계층으로, 인코딩된 데이터를 전송하기 위한 구조적 틀
NALU Network Abstraction Layer Unit NAL 계층의 실제 데이터 단위. 하나의 NALU는 영상의 일부를 구성함

H.264는 인코딩된 영상 데이터를 다양한 네트워크나 저장 매체에 맞게 유연하게 전달할 수 있도록 NAL 계층을 도입했다. 이 계층은 Raw 비디오 데이터를 작은 단위(NAL Unit)로 나누어 전송 가능한 형태로 구조화하며, 디코더는 이 단위를 해석하여 영상 프레임을 복원한다.


3. NALU의 기본 구조

각 NALU는 보통 다음과 같은 형식을 가진다:

┌──────────────┬────────────────────────────┐
│ Start Code   │ NALU Payload               │
│ 0x00000001   │ SPS / PPS / Slice 등       │
└──────────────┴────────────────────────────┘
  • Start Code: 3~4바이트의 고정된 값으로, NALU의 시작을 나타낸다. 일반적으로 0x00 00 00 01을 사용한다.
  • NALU Payload: NAL 헤더와 그에 따른 압축된 비디오 데이터로 구성된다. 헤더의 첫 바이트에서 NALU 타입을 추출할 수 있다.

디코더는 이 Start Code를 기준으로 스트림에서 NALU를 분리하고, 각 NALU의 타입에 따라 적절한 방식으로 처리한다.


4. NALU 타입별 구분

Type 번호 이름 설명
1 Non-IDR (P-Frame) 이전 프레임을 참조하는 일반 프레임
5 IDR (Instantaneous Decoder Refresh) 독립적으로 디코딩 가능한 I-Frame
6 SEI (Supplemental Enhancement Info) 부가 정보 (예: 자막, 타임코드 등)
7 SPS (Sequence Parameter Set) 영상 시퀀스의 전체 설정 정보 (해상도, 프로파일 등)
8 PPS (Picture Parameter Set) 각 프레임 디코딩에 필요한 설정 정보
9 AUD (Access Unit Delimiter) 프레임 구분용 마커 (선택적)
10 End of Sequence 스트림 시퀀스 종료 표시
11 End of Stream 전체 스트림 종료 표시
12 Filler Data 비트레이트 조절용 더미 데이터

특히 SPS(7)와 PPS(8)는 디코딩 세션 초기화에 필수이며, IDR(5)는 독립 프레임으로 디코딩이 가능하다는 점에서 매우 중요하다.


5. UDP를 통한 H.264 전송 구조

H.264 스트림은 컨테이너 없이 Raw 데이터로 전송되며, 각 UDP 패킷은 보통 하나의 NALU 또는 그 일부를 포함한다. 아래는 세 개의 프레임이 전송된다고 가정한 경우의 UDP 전송 시퀀스이다:

[ Packet 1 ] → [0x00000001] + SPS (type 7)
[ Packet 2 ] → [0x00000001] + PPS (type 8)
[ Packet 3 ] → [0x00000001] + IDR Slice (type 5)   ← Frame 1 (I-Frame)
[ Packet 4 ] → [0x00000001] + P Slice (type 1)     ← Frame 2 (P-Frame)
[ Packet 5 ] → [0x00000001] + P Slice (type 1)     ← Frame 3 (P-Frame)

위 구조에서 디코더는 총 3장의 프레임을 복원할 수 있다. 단, SPS와 PPS는 반드시 첫 프레임(IDR) 전에 수신되어야 하며, 이후 P-Frame은 이전 프레임을 참조하여 복원된다.


6. Python 예제: NALU 타입 추출

H.264 바이너리 데이터를 분석할 때 유용한 NALU 타입 판별 코드는 다음과 같다:

def get_nalu_type(nalu: bytes) -> int:
    if len(nalu) == 0:
        return -1
    return nalu[0] & 0x1F

def describe_nalu_type(nalu_type: int) -> str:
    types = {
        1: "P-Frame",
        5: "IDR (I-Frame)",
        6: "SEI",
        7: "SPS",
        8: "PPS",
        9: "AUD"
    }
    return types.get(nalu_type, f"Unknown ({nalu_type})")

# 예시 NALU
nalu = b'\x65\x88\x00...'
print(describe_nalu_type(get_nalu_type(nalu)))

이 코드는 NALU의 첫 바이트를 기준으로 타입을 추출하고, 사람이 이해하기 쉬운 문자열로 변환한다.


7. 디코딩 시 고려사항

H.264 Raw 스트림을 디코딩하기 위해서는 다음과 같은 순서를 준수해야 한다:

  1. SPS (type 7)PPS (type 8)를 먼저 수신하고, 이를 통해 디코더의 포맷을 설정한다.
  2. IDR (type 5) 또는 P-Frame(type 1)을 수신하면, 이전 포맷을 바탕으로 디코딩을 시도한다.
  3. 프레임은 NALU 단위로 분리 및 조립되며, 디코더는 한 프레임 단위로 처리한다.
  4. 실시간 환경에서는 NALU 분할(Fragmentation), 패킷 손실, 재정렬 등의 네트워크 이슈도 고려해야 한다.

8. 마무리

H.264 기반의 UDP 스트리밍은 고성능 영상 전송에 적합하지만, 컨테이너가 없는 Raw 포맷이라는 특성상 디코딩 로직에 대한 명확한 이해가 필요하다. 특히 NALU의 구조와 순서를 잘 파악하고, SPS/PPS 설정을 선행한 후에만 디코딩이 가능하다는 점을 기억해야 한다.

이러한 구조를 잘 이해하면, Swift, Python, C++ 등 다양한 환경에서 고유의 H.264 디코더를 직접 구현하거나 최적화할 수 있다.

반응형