Skip to content

ID3 Tag는 JSON base64가 아니었다 ​

StreamLive로 라이브 채널을 운영하다 보면 광고 마커나 이벤트 알림처럼, 재생 중인 스트림 안에 시간과 함께 값을 실어 보내야 하는 경우가 생깁니다. 이런 값을 timed metadata라고 부르고, HLS/TS 계열에서는 보통 ID3 tag 형태로 세그먼트 안에 심습니다. 이 기능을 테스트하다가 예상과 전혀 다른 결과를 마주쳤습니다.

처음에는 단순하게 생각했습니다. "JSON 하나 만들고, base64로 인코딩해서 넣으면 되지 않을까?" 싶었는데, 실제로는 되지 않았습니다. 정확히 말하면 API 요청 자체는 성공한 것처럼 보였지만, 세그먼트를 까보거나 플레이어에서 이벤트를 들어보면 기대한 값이 잡히지 않았습니다.

이 지점은 이름 때문에 오해하기 쉽습니다. metadata라고 하니 아무 문자열이나 넣어도 될 것 같지만, 실제 HLS/TS 구조에서는 정해진 형식을 따라야 합니다.

처음 넣으려던 값 ​

처음에는 대략 이런 JSON을 넣으려고 했습니다.

json
{
  "eventType": "ad_marker",
  "adStartTime": "2026-05-05T10:00:00Z",
  "markerId": "JUSTIN_TEST_001"
}

그리고 이 JSON 문자열을 base64로 바꿔서 payload에 넣었습니다.

text
eyJldmVudFR5cGUiOiJhZF9tYXJrZXIiLCJhZFN0YXJ0VGltZSI6IjIwMjYtMDUtMDVUMTA6MDA6MDBaIiwibWFya2VySWQiOiJKVVNUSU5fVEVTVF8wMDEifQ==

겉으로 보기엔 문제없어 보이지만, ID3 tag는 "base64로 감싼 문자열"을 뜻하지 않습니다.

ID3는 구조를 가진 바이너리다 ​

ID3는 단순한 payload가 아니라, 버전과 flags, size, frame header, frame body를 가진 바이너리 포맷입니다. 아주 단순화하면 이런 구조입니다.

text
ID3 tag
  -> ID3 header
  -> Frame header
  -> Frame payload

예를 들어 TXXX frame을 쓴다면 내부는 이런 형태가 됩니다.

text
ID3
version: 2.4.0
frame: TXXX
encoding: UTF-8
description: ""
value: JUSTIN_TEST_001

즉, JSON 문자열 자체를 base64로 넣는 것이 아니라, ID3 v2.4 spec에 맞는 완성된 ID3 tag 바이너리를 만든 뒤 그 바이너리를 base64로 넣어야 합니다. 이 차이가 핵심입니다.

text
틀린 접근:
JSON string -> base64

맞는 접근:
JSON or value -> ID3 tag binary -> base64

TS에서 어떻게 보였나 ​

실제로 MPEG-TS를 분석해보면 metadata PID가 따로 보일 수 있습니다. 예시는 다음과 같습니다.

text
PID     Usage
0x0100  AVC video
0x0101  MPEG-2 AAC Audio
0x01F4  SCTE 35 Splice Info
0x0201  MetaData in PES packets
0x1001  PMT

여기서 ID3는 보통 PES packet 안에 들어가고, 구체적으로는 private_stream_1 계열로 들어오는 경우가 많습니다. 분석 결과를 풀면 이런 구조가 됩니다.

text
PES header
  stream_id = 0xBD
  PTS 포함

ID3 tag
  magic = ID3
  version = 2.4.0
  frame = TXXX
  value = JUSTIN_TEST_001

이렇게 확인되면 플레이어에서는 ID3 TXXX frame을 잡아서 value를 읽으면 됩니다.

strings로도 힌트를 볼 수 있다 ​

간단히 payload 흔적만 보고 싶다면 strings도 쓸 수 있습니다.

bash
strings segment.ts | grep JUSTIN

물론 이건 정식 분석 도구는 아니라서 압축이나 바이너리 구조에 따라 보이지 않을 수도 있지만, 값이 아예 들어갔는지를 빠르게 확인할 때는 유용합니다. 제대로 확인하려면 TS 분석 도구로 PID와 PES를 확인하는 것이 정확합니다.

bash
tsanalyze segment.ts

원격 URL을 바로 넣으면 도구에 따라 동작하지 않을 수 있으니, 먼저 파일로 받아서 검사하는 것이 안전합니다.

bash
curl -L -o segment.ts "https://example.com/live/segment.ts"
tsanalyze segment.ts

플레이어에서는 무엇을 들어야 하나 ​

브라우저 플레이어에서는 ID3 metadata event를 받을 수 있는지부터 확인해야 합니다. HLS 플레이어마다 이벤트 이름은 다르지만, 핵심 흐름은 같습니다.

text
HLS segment demux
  -> ID3 metadata parse
  -> TXXX frame extract
  -> application callback

여기서 JSON을 바로 기대하면 안 됩니다. ID3 frame 안의 value로 JSON 문자열을 넣었다면, 그 value를 다시 JSON.parse 해야 실제 데이터를 꺼낼 수 있습니다.

js
const value = id3Frame.value;
const payload = JSON.parse(value);

정리 ​

결론은 간단합니다. ID3 tag는 JSON base64가 아닙니다. ID3 spec에 맞는 tag 바이너리를 만들고, 그 바이너리를 base64로 넣어야 합니다. JSON 문자열을 그대로 base64로 감싸면 인코딩 자체는 된 것처럼 보이지만, 플레이어와 demuxer 입장에서는 정상적인 ID3 tag가 아닙니다.

metadata라는 표현이 추상적이라 문서만 보면 놓치기 쉬운 지점입니다. StreamLive처럼 실제 라이브 파이프라인에서 timed metadata를 다룰 때는, TS 안의 PID와 PES, ID3 frame까지 내려가서 확인해야 원인이 보입니다.

基于 VitePress 构建 · 部署于腾讯云 EdgeOne Pages