Agora SEI 메타데이터 — 비디오에 박힌 레이아웃 정보의 바이너리 해부
합성 송출을 받는 시청자 앱에서 "지금 화면 어디에 누가 있는지"를 어떻게 알아낼까요? 별도 시그널링으로 보내면 비디오 프레임과 타이밍이 어긋나기 마련인데, 답은 비디오 스트림 안에 메타데이터를 박아 보내는 SEI에 있습니다. 이 글은 H.264와 H.265 표준의 SEI가 무엇인지, Agora Media Push가 레이아웃 JSON을 어떤 바이트 포맷으로 박아 주는지, 그리고 시청자 파서가 NAL 헤더부터 0xFF 누적 길이, JSON 파싱까지 어떻게 풀어내는지 차근차근 짚어 줍니다.
목차(40개 항목)
- 0. 핵심 명제 — SEI는 비디오와 함께 도착이 보장된 메타데이터 채널
- 1. SEI란 무엇인가 — H.264/H.265 표준의 일부
- 2. 왜 시그널링 대신 SEI를 쓰나
3. Agora SEI의 JSON 구조
4. SEI 바이너리 구조 — H.264 표준 + Agora 커스텀
6. NAL 스트림 포맷 — Annex B vs AVCC
7. 시청자 측 파싱 흐름
- 9. SEI vs 다른 동기화 메커니즘 — 의사결정
- 11. SA 체크리스트
12. 한 줄 결론
- 관련 글
- 참고 자료
"Agora Media Push로 합성 송출 받는 시청자 앱에서, 화면에 누가 어디 있는지 어떻게 알아내요?", "RTM으로 보내려니 비디오 프레임과 타이밍이 어긋나는데요". 답은 비디오 스트림 안에 메타데이터를 박아 보내는 SEI (Supplemental Enhancement Information).
이 글은 H.264/H.265 표준의 SEI가 무엇인지, Agora Media Push가 어떤 JSON을 어떤 바이트 포맷으로 박아 주는지, 시청자 측 파서가 어떻게 풀어내야 하는지를 정리합니다.
0. 핵심 명제 — SEI는 비디오와 함께 도착이 보장된 메타데이터 채널
별도 시그널링(WebSocket/RTM)으로 메타데이터를 보내면 비디오 프레임과 타이밍이 어긋난다. SEI는 비디오 NAL 스트림 안에 박혀 있어 항상 같이 도착한다 — 디코딩에 영향 없이 메타데이터를 비디오에 동기로 실어 보내는 표준 메커니즘.
흔한 오해와 정정:
| 오해 | 정확한 이해 |
|---|---|
| SEI는 Agora 전용 기술 | H.264/H.265(및 MPEG-2 video)의 표준 기능. 단 모든 비디오 코덱이 지원하는 건 아님 — VP9/AV1 등에는 SEI가 없고 각각 WebM BlockAddID(ITU-T T.35) / Metadata OBU(metadata_itut_t35) 같은 별도 메타데이터 메커니즘을 씀 |
| SEI를 쓰면 시그널링 불필요 | SEI는 레이아웃 정보, 시그널링은 업링크 데이터 — 둘은 다른 채널 |
| RTMP 위에 SEI 못 박음 | RTMP/FLV는 AVCC 포맷이지만 SEI NAL은 동일하게 실림 |
| SEI는 디코더 동작에 영향 | ❌ Annex D — 디코더는 SEI를 무시해도 됨 (선택사항) |
1. SEI란 무엇인가 — H.264/H.265 표준의 일부
SEI = Supplemental Enhancement Information. ITU-T H.264 / ISO/IEC 14496-10의 Annex D에 정의된 부가 정보 영역.
핵심 특성:
| 특성 | 설명 |
|---|---|
| 표준성 | H.264/H.265 표준에 정의 — 모든 컴플라이언트 인코더/디코더가 알고 있는 형식 |
| 비강제성 | 디코더는 SEI를 처리할 의무가 없음 — 무시해도 디코딩 정상 |
| 비디오 동기 | NAL 스트림 안에 슬라이스 NAL 직전에 위치 — 같은 프레임과 묶여 도착 |
| 페이로드 자유도 | payload_type별로 표준 의미가 정해져 있고, type 5 = user_data_unregistered가 자유 데이터용 |
Annex D의 의미: Annex D는 H.264 표준의 부록으로, 디코딩에 필수가 아닌 부가 정보 형식을 정의. SEI는 그 부록 안에 있어서 "선택적"입니다. 그래도 모든 메이저 디코더는 SEI를 파싱할 줄 알고, 일부는 자막/광고/HDR 메타데이터에 적극 활용.
2. 왜 시그널링 대신 SEI를 쓰나
라이브 송출에서 자주 부딪히는 문제:
별도 시그널링과 비디오는 서로 다른 네트워크 경로를 거치므로 타이밍이 맞지 않음. "이 프레임에 호스트 A가 좌상단에 있다"는 정보가 비디오보다 0.4초 늦게 도착하면, 시청자 UI가 잘못된 위치를 클릭 가능 영역으로 인식.
SEI 해결책: 메타데이터를 NAL 스트림 안에 박아서 같은 프레임과 같이 도착시킴.
뉘앙스: 네트워크 패킷 재정렬이나 버퍼 동작에 따라 완벽한 동시성이 깨질 수 있지만, decode order에서 SEI는 슬라이스 직전에 위치하므로 디코더 레벨에서는 항상 묶여서 처리됩니다. 시그널링 별도 채널 대비 압도적으로 안전.
3. Agora SEI의 JSON 구조
Agora Media Push는 트랜스코딩된 H.264/H.265 스트림에 자동으로 SEI를 추가합니다 — 클라이언트 별도 설정 불필요.
최상위 필드
| 필드 | 설명 |
|---|---|
canvas | 전체 합성 화면(캔버스) 정보 |
regions | 캔버스 위에 배치된 각 호스트의 레이아웃 (배열) |
ver | SEI 프로토콜 버전 (현재 20190611) |
ts | 인코딩 시점의 타임스탬프 (ms) |
app_data | 사용자 정의 추가 정보 (transcodingExtraInfo에 대응) |
canvas 필드
| 키 | 의미 | LiveTranscoding 대응 |
|---|---|---|
w | 캔버스 너비 (px) | width |
h | 캔버스 높이 (px) | height |
bgnd | 배경색 (RGB hex) | backgroundColor |
regions 배열의 각 요소 (호스트별)
| 키 | 의미 | TranscodingUser 대응 |
|---|---|---|
uid | 호스트 UID | uid |
suid | 문자열 계정 (선택) | — |
alpha | 투명도 [0.0 ~ 1.0] | alpha |
zorder | 레이어 순서 [0 ~ 100] | zOrder |
volume | 음량 dB [0 ~ 100] | — |
x, y | 좌상단 기준 위치 | x, y |
w, h | 비디오 프레임 크기 (px) | width, height |
실제 페이로드 예시
이 JSON 전체가 SEI 페이로드로 들어갑니다.
4. SEI 바이너리 구조 — H.264 표준 + Agora 커스텀
SEI의 첫 몇 바이트가 핵심입니다. 예시:
바이트별 의미
| 바이트 | 의미 |
|---|---|
06 | NAL 헤더 — forbidden_zero_bit(1) + nal_ref_idc(2) + nal_unit_type(5) = 0 00 00110 → SEI |
64 | payload_type = 100 (Agora 커스텀 선택) |
bd | payload_size = 189 바이트 (이 예시 한정 — 단일 바이트로 표현 가능한 길이) |
NAL 헤더 0x06 분해
NAL unit type 6 = SEI, 표준 정의. RFC 6184도 SEI NAL은 nal_ref_idc=0이어야 한다고 명시.
payload_type 100의 정확한 위치
⚠️ 흔한 오해 정정: payload_type 100은 H.264/HEVC 표준의 어떤 amendment에도 정의돼 있지 않은 미정의(reserved) 값입니다. 표준은 미정의 payloadType을
reserved_sei_message로 취급할 뿐이고, Agora가 이 미정의 값을 임의로 점유한 것. (Agora 공식 문서는 100을 명시적으로 "user-defined(여기서 100으로 정의)"라고 표기합니다.)**표준적인 커스텀 데이터 메커니즘은 type 5 =
user_data_unregistered**이며, 128-bit UUID 네임스페이싱이 따라옵니다 (벤더 충돌 방지). Agora는 표준 mechanism 대신 미정의 type을 직접 점유한 케이스.실무 영향: 다른 인코더로 만든 H.264 스트림에 type 100 SEI가 있으면 그건 다른 의미일 수 있음. Agora 송출인지 확인 후 파싱.
| payload_type | 표준 의미 |
|---|---|
| 0 | buffering_period |
| 1 | pic_timing |
| 5 | user_data_unregistered (UUID 네임스페이싱 — 표준 커스텀 데이터) |
| 22 | post_filter_hint |
| 100 | 표준 미정의값 (Agora가 점유, 공식 문서상 user-defined) |
| 136 | time_code |
NAL 스트림 안에서의 위치
SEI는 자기가 따라붙을 슬라이스의 직전에 위치 — 디코더가 슬라이스 처리 전에 SEI를 본다는 약속.
5. 0xFF 누적 길이 인코딩
payload_type과 payload_size 모두 H.264 표준의 동일한 가변 길이 인코딩을 씁니다.
인코딩 규칙
→ 0xFF가 나오면 255씩 누적, 0xFF가 아닌 마지막 바이트의 값을 더해 종료.
예시
| 길이 | 인코딩 | 분해 |
|---|---|---|
| 234 | EA | 234 < 255 → 단일 바이트 |
| 572 | FF FF 3E | 2 × 255 + 62 = 572 |
| 922 | FF FF FF 9D | 3 × 255 + 157 = 922 |
| 1500 | FF FF FF FF FF E1 | 5 × 255 + 225 = 1500 |
파서 의사 코드
이 로직 하나면 모든 H.264/H.265 SEI를 파싱할 수 있습니다.
6. NAL 스트림 포맷 — Annex B vs AVCC
같은 SEI NAL이라도 어떤 컨테이너에 담기느냐에 따라 framing이 다릅니다.
Annex B 포맷 (raw H.264 스트림 / RTSP / TS)
- 4바이트 start code(
00 00 00 01) 또는 3바이트(00 00 01)로 NAL 경계 표시 - byte stream을 스캔해서 start code 찾으면 NAL 시작
AVCC 포맷 (MP4 / FLV / RTMP)
- start code 없음 — 대신 4바이트 length prefix로 NAL 크기 명시
- length만큼 읽고 다음 NAL로 점프
자주 틀리는 포인트: "RTMP에 SEI 박는다" → RTMP는 AVCC 포맷입니다. NAL 페이로드(
06 64 bd ...)는 동일하지만 앞에 붙는 게 start code가 아니라 length prefix. 파서를 짤 때 framing을 정확히 알아야 NAL 경계를 찾음.
| 컨테이너 | 포맷 | NAL 경계 표시 |
|---|---|---|
| RTSP, MPEG-TS | Annex B | start code (00 00 00 01) |
| MP4, MOV | AVCC | 4바이트 length prefix |
| RTMP, FLV | AVCC | 4바이트 length prefix |
| WebRTC RTP | start code 안에 RTP fragmentation | start code (depacketize 후) |
7. 시청자 측 파싱 흐름
전체 파서 흐름을 한 장에 정리.
Swift / iOS 파서 예시
주의사항 — emulation_prevention_byte
H.264 NAL 페이로드에서 00 00 00이 우연히 등장하면 인코더가 00 00 03 00으로 escape합니다 (start code 충돌 방지). 파싱 시 0x03을 제거해야 원본 페이로드가 됩니다.
긴 SEI(JSON 길이 100바이트 이상)에서는 이 escape가 거의 항상 등장합니다. 파서가 빠뜨리면 JSON 파싱이 실패함.
8. 활용 패턴
8-1. 클릭 가능 영역 매핑 (Agora의 표준 사용 케이스)
8-2. app_data 필드 활용 — 자유 메타데이터
app_data는 고객이 자유롭게 쓸 수 있는 필드. 예:
| 활용 | app_data 예시 |
|---|---|
| 자막 큐 | {"caption":"안녕하세요","start":1683195420123} |
| 광고 트리거 | {"ad":"midroll","duration":30} |
| 채팅 동기화 | {"chat_seq":4521} |
| 챕터 마커 | {"chapter":"intro"} |
비디오 프레임과 정확히 동기되어야 하는 모든 메타데이터의 후보지.
8-3. 정확도 한계
- SEI는 트랜스코딩된 합성 스트림(Media Push)에만 들어감 — 원본 RTC 스트림에는 없음
- 시청자 디바이스의 SEI 파싱이 실패해도 비디오 재생은 정상 — 폴백 안전
- payload는 매 프레임에 들어가는 게 아님 — 보통 키프레임이나 변경 시점에만 (Agora 구현 [NEEDS VERIFICATION])
9. SEI vs 다른 동기화 메커니즘 — 의사결정
| 메커니즘 | 동기 정확도 | 페이로드 크기 | 도착 보장 | 용도 |
|---|---|---|---|---|
| SEI | 프레임 단위 동기 | 작음(~수KB) | 비디오와 항상 같이 | 레이아웃, 자막, 광고 큐 |
| 별도 RTM/WebSocket | ms 단위 어긋남 | 자유 | 별도 보장 | 채팅, 업링크 데이터 |
| RTP timestamp 기반 외부 채널 | 잘 맞으면 ms | 자유 | 클럭 동기 필요 | A/V 외부 큐 |
| 트랜스코드 워터마크 | 영구 | 픽셀 | 비디오 프레임 자체 | DRM, 트래킹 |
결론:
- 비디오와 정확히 동기되어야 하는 메타데이터 → SEI
- 자유 형식, 양방향, 빈번한 메시지 → RTM/WebSocket
- 둘은 보완 관계, 대체재 아님
10. FAQ
Q1. SEI를 쓰면 시그널링 인프라 필요 없나?
❌ 아님. SEI에 들어가는 건 다운링크 레이아웃 정보. 업링크(채팅, 호스트 명령 등)는 별도 시그널링이 여전히 필요. app_data 필드만 시그널링과 일부 겹침 — 둘은 대체재가 아니라 보완재.
Q2. SEI가 디코딩에 영향 주나?
❌ 영향 없음. Annex D 정의상 디코더는 SEI를 무시해도 됨. 잘못된 SEI 페이로드가 와도 비디오 재생 정상.
Q3. SEI가 매 프레임마다 박히나?
[NEEDS VERIFICATION] Agora 구현에서 SEI는 보통 키프레임에 박히거나 레이아웃 변경 시점에 박힙니다. 매 프레임은 아님. 시청자 측 파서는 SEI 미수신 시 이전 값 유지.
Q4. RTC 원본 스트림에도 SEI가 있나?
❌ Agora SEI는 Media Push의 트랜스코딩된 합성 스트림에만 박힘. 원본 RTC P2P 스트림에는 없음. 합성 스트림을 RTMP로 받는 시청자만 활용 가능.
Q5. payload_type 100을 쓰면 다른 디코더와 충돌하나?
이론상 가능. 다른 인코더가 같은 type 100을 다른 의미로 사용 중이면 충돌. 표준 권장은 type 5 (user_data_unregistered) + UUID 네임스페이싱이지만, Agora는 type 100 직접 점유 — 실무에서 큰 이슈는 없으나 다른 SEI 파서가 type 100을 잘못 해석할 가능성은 있음.
Q6. H.265(HEVC)에서도 같은 형식인가?
H.265 SEI도 같은 Annex D 모델 + 0xFF 누적 길이 인코딩 사용. NAL header 형식은 다름(2바이트). payload_type/payload_size 인코딩은 동일.
11. SA 체크리스트
12. 한 줄 결론
SEI는 H.264/H.265 표준의 Annex D — 비디오와 함께 도착이 보장된 메타데이터 채널. Agora Media Push는 payload_type 100에 합성 레이아웃 JSON을 자동으로 박아주며, 시청자 파서는 NAL header → 0xFF 누적 길이 → JSON 파싱의 3단계로 풀어낸다. 시그널링과는 보완 관계, 대체재 아님.
한 장 요약
관련 글
- GOP-세그먼트 정렬의 산수 — Layer A 시간축 정렬
- ABR Ladder의 Layer B 정렬 — 화질 전환 시 키프레임 동기화
- H.264 Profile·인코더 옵션·비트레이트의 현실 — H.264 NAL 구조 기초
- 프레임의 모든 것 — I/P/B-frame과 NAL의 관계
- FFmpeg, 미디어의 스위스 아미 나이프 — NAL 디먹스
- 녹화 모드 해부 — Individual / Mix / Web — 트랜스코딩 옵션과 SEI 생성 시점
참고 자료
- ITU-T H.264 — Advanced video coding for generic audiovisual services — SEI 및 Annex D를 정의하는 1차 표준 문서
- ITU-T H.265 (HEVC) — High efficiency video coding — H.265 SEI 모델(2바이트 NAL 헤더, 동일한 0xFF 누적 길이 인코딩)
- RFC 6184 — RTP Payload Format for H.264 Video — NAL 단위/NRI(nal_ref_idc) 의미 및 RTP 패킷화 규정
- MS-H264PF: Microsoft H.264 Profile — Stream Layout SEI Message — 벤더가 커스텀 SEI 메시지 타입을 점유해 레이아웃 메타데이터를 싣는 실제 사례
- Agora Docs — Media Push (Push to CDN) — Media Push 트랜스코딩/SEI 송출 동작의 공식 레퍼런스
- FFmpeg Documentation — NAL 디먹스/SEI 추출 및 Annex B ↔ AVCC 변환 실무 도구