SSAI는 광고를 어떻게 스트림 안에 자연스럽게 넣는가
SSAI는 광고를 서버 측에서 본편 스트림에 합성해 클라이언트에 내려보내는 기술입니다. 클라이언트 입장에서는 본편과 광고가 하나의 끊김 없는 스트림으로 보입니다. 광고 차단기를 우회할 수 있고, 버퍼링 없는 매끄러운 전환이 핵심 장점입니다.
들어가기 전에
광고 삽입은 크게 두 방식으로 나뉩니다.
| 방식 | 동작 위치 | 장점 | 단점 |
|---|---|---|---|
| CSAI (Client-Side) | 플레이어 | 구현 간단, 개인화 쉬움, Beacon 직접 발사 | 광고차단기에 취약, 버퍼링 발생 |
| SSAI (Server-Side) | 오리진/매니페스트 서버 | 광고차단 우회, seamless 전환 | 서버 복잡도 증가, Beacon 처리 까다로움 |
CSAI는 플레이어가 광고 SDK를 로딩해서 별도 스트림을 재생하는 구조입니다. 문제는 광고와 본편 전환 시 버퍼링이 생기고, 광고 차단기가 SDK 자체를 막아버리면 매출이 사라진다는 점입니다.
SSAI는 이 문제를 서버에서 해결합니다. 매니페스트 조작(Manifest Manipulation) 방식으로 본편 세그먼트 사이에 광고 세그먼트를 끼워 넣습니다. 클라이언트는 하나의 연속된 스트림으로 인식하기 때문에 차단할 수 없습니다.
SSAI 동작 흐름
핵심 구성요소:
| 구성요소 | 역할 |
|---|---|
| SCTE-35 마커 | "여기서 광고 넣어라"는 신호. Avail의 트리거 |
| Avail | 광고가 삽입될 수 있는 구간. Duration 정보 포함 |
| Ad Decision Server (ADS) | VAST/VMAP 요청을 받아 광고 Creative를 반환 |
| Manifest Manipulator | 매니페스트를 세션별로 조작해 광고 세그먼트를 삽입 |
| Ad Conditioning | 광고 소재를 본편과 동일 스펙으로 맞추는 전처리 |
| Beacon/Tracking | 광고 노출, 재생 진행률을 ADS에 보고 |
VAST: 광고 결정의 표준 프로토콜
VAST는 IAB가 정한 비디오 광고 서빙 표준입니다. SSAI 서버가 ADS에 "광고 줘"라고 요청하면, ADS가 VAST XML로 응답합니다.
VAST 요청
SSAI 서버가 ADS를 호출할 때 전달하는 정보:
GET https://ads.example.com/vast?
duration=30
&session_id=abc123
&device=mobile
&geo=KR
&content_id=show_001
&ip=203.0.113.1| 파라미터 | 설명 |
|---|---|
duration | Avail 길이 (초). SCTE-35에서 추출 |
session_id | SSAI 세션 식별자 |
device | 디바이스 타입 (mobile, ctv, desktop) |
geo | 시청자 지역 |
content_id | 현재 콘텐츠 식별자 |
ip | 시청자 IP (개인화 타게팅용) |
VAST 응답 구조
<VAST version="4.2">
<Ad id="ad_001" sequence="1">
<InLine>
<AdSystem>ExampleAdServer</AdSystem>
<AdTitle>Coca-Cola Summer</AdTitle>
<Impression><![CDATA[https://tracker.example.com/impression?id=abc]]></Impression>
<Creatives>
<Creative>
<Linear>
<Duration>00:00:15</Duration>
<TrackingEvents>
<Tracking event="start"><![CDATA[https://tracker.example.com/start]]></Tracking>
<Tracking event="firstQuartile"><![CDATA[https://tracker.example.com/q1]]></Tracking>
<Tracking event="midpoint"><![CDATA[https://tracker.example.com/mid]]></Tracking>
<Tracking event="thirdQuartile"><![CDATA[https://tracker.example.com/q3]]></Tracking>
<Tracking event="complete"><![CDATA[https://tracker.example.com/complete]]></Tracking>
</TrackingEvents>
<MediaFiles>
<MediaFile delivery="progressive" type="video/mp4"
width="1920" height="1080" bitrate="5000">
<![CDATA[https://cdn.example.com/ads/cocacola_1080p.mp4]]>
</MediaFile>
<MediaFile delivery="progressive" type="video/mp4"
width="1280" height="720" bitrate="2500">
<![CDATA[https://cdn.example.com/ads/cocacola_720p.mp4]]>
</MediaFile>
</MediaFiles>
</Linear>
</Creative>
</Creatives>
</InLine>
</Ad>
</VAST>VAST 핵심 요소
| 요소 | 설명 |
|---|---|
<Ad sequence> | 광고 Pod 내 재생 순서 |
<Impression> | 광고 노출 시 호출할 URL (Beacon) |
<Duration> | 광고 길이 |
<TrackingEvents> | 재생 진행률별 Beacon URL |
<MediaFiles> | 실제 광고 Creative 미디어 URL (해상도/비트레이트별) |
<Error> | 에러 발생 시 보고할 URL |
VMAP (Video Multiple Ad Playlist)
VAST가 "광고 하나"의 응답이라면, VMAP은 "전체 타임라인에서 어디에 광고를 넣을지"를 정의합니다:
<vmap:VMAP version="1.0">
<vmap:AdBreak timeOffset="start" breakType="linear">
<!-- Pre-roll -->
<vmap:AdSource><vmap:VASTAdData>...</vmap:VASTAdData></vmap:AdSource>
</vmap:AdBreak>
<vmap:AdBreak timeOffset="00:10:00" breakType="linear">
<!-- Mid-roll at 10분 -->
<vmap:AdSource><vmap:VASTAdData>...</vmap:VASTAdData></vmap:AdSource>
</vmap:AdBreak>
<vmap:AdBreak timeOffset="end" breakType="linear">
<!-- Post-roll -->
<vmap:AdSource><vmap:VASTAdData>...</vmap:VASTAdData></vmap:AdSource>
</vmap:AdBreak>
</vmap:VMAP>| timeOffset | 의미 |
|---|---|
start | Pre-roll (콘텐츠 시작 전) |
HH:MM:SS | Mid-roll (특정 시점) |
end | Post-roll (콘텐츠 끝난 후) |
#N% | 퍼센트 기반 (콘텐츠의 N% 지점) |
Avail: 광고가 들어갈 수 있는 구간
Avail은 SCTE-35 Cue-Out ~ Cue-In 사이의 구간을 말합니다. SSAI 서버는 Avail이 감지되면 ADS를 호출해서 그 구간을 광고로 채웁니다.
Avail 채우기 로직
Avail Duration: 60초
ADS 응답:
Ad 1: 15초
Ad 2: 30초
Ad 3: 15초
─────────
합계: 60초 (정확히 채움) ✓Avail을 정확히 채우지 못하는 경우:
| 상황 | 처리 |
|---|---|
| 광고 합계 < Avail 길이 | 남는 구간에 Slate(대기 화면) 삽입 |
| 광고 합계 > Avail 길이 | 마지막 광고를 잘라내거나 제외 |
| ADS 응답 없음 / 타임아웃 | 전체를 Slate로 채움 |
| ADS가 빈 VAST 반환 | Avail Suppression 정책에 따라 본편 유지 또는 Slate |
Avail을 채우지 못하면 검은 화면이 나가는 게 최악의 시나리오입니다. 그래서 Slate(브랜딩 화면, "잠시 후 돌아옵니다" 등)는 SSAI에서 필수 구성요소입니다.
Beacon: 광고 추적과 보고
SSAI에서 가장 까다로운 부분이 Beacon(트래킹 픽셀) 처리입니다. CSAI는 플레이어가 직접 Beacon을 쏘지만, SSAI에서는 클라이언트가 "이게 광고인지 모르니까" Beacon을 직접 쏠 수 없습니다.
Beacon Fire 방식
| 방식 | 동작 | 장단점 |
|---|---|---|
| Server-Side Beacon | SSAI 서버가 직접 Beacon 호출 | 구현 단순. 하지만 클라이언트 실제 재생 여부 모름 |
| Client-Side Beacon (Sidecar) | SSAI가 Beacon URL을 클라이언트에게 전달, 클라이언트가 직접 발사 | 정확한 viewability. 구현 복잡 |
| Hybrid | Impression은 서버, Quartile은 클라이언트 | 실무에서 가장 흔한 조합 |
Tracking Events (VAST 표준)
| 이벤트 | 발사 시점 | 설명 |
|---|---|---|
impression | 광고 첫 프레임 노출 | 과금 기준. 가장 중요 |
start | 재생 시작 | impression과 동시 또는 직후 |
firstQuartile | 25% 재생 | |
midpoint | 50% 재생 | |
thirdQuartile | 75% 재생 | |
complete | 100% 재생 | 완시청 여부 확인 |
skip | 사용자가 스킵 | SSAI에서는 보통 스킵 불가 |
pause / resume | 일시정지/재개 | |
mute / unmute | 음소거 전환 | |
error | 재생 실패 |
Server-Side Beacon의 한계
서버가 Beacon을 쏘면 "광고가 실제로 재생됐는지"를 보장할 수 없습니다. 클라이언트가 세그먼트를 다운로드만 하고 재생하지 않았을 수도 있고, 유저가 탭을 내렸을 수도 있습니다.
이 문제 때문에 IAB의 Open Measurement SDK(OM SDK)가 등장했습니다. 클라이언트에 측정 스크립트를 삽입해서 실제 viewability(화면에 보이는지, 소리가 나오는지)를 검증합니다.
광고주 입장에서 "impression 찍혔는데 진짜 봤는지 모르겠다"는 SSAI의 태생적 약점입니다. OM SDK 연동이 안 되면 프리미엄 광고주가 SSAI 인벤토리를 기피할 수 있습니다. 이건 기술 문제가 아니라 비즈니스 문제입니다.
SCTE-35: 광고 신호의 표준
SCTE-35는 방송/스트리밍에서 광고 구간을 알리는 표준 신호입니다.
핵심 메시지 타입
| 메시지 | 역할 |
|---|---|
splice_insert | 특정 시점에 광고 삽입/복귀를 지시 |
time_signal | 시간 기반 이벤트 신호. segmentation_descriptor와 조합 |
splice_null | Keepalive. "아직 여기 있다"는 의미 |
splice_insert 구조
splice_insert {
splice_event_id // 이벤트 고유 ID
splice_event_cancel // 이전 이벤트 취소 여부
out_of_network_indicator // 1=Cue-Out, 0=Cue-In
splice_immediate_flag // 즉시 실행 여부
splice_time {
pts_time // 실행할 PTS 시점
}
break_duration {
duration // Avail 길이 (90kHz 단위)
}
unique_program_id // 프로그램 식별자
avail_num // Avail 번호
avails_expected // 총 예상 Avail 수
}segmentation_descriptor (time_signal과 조합)
time_signal + segmentation_descriptor로 더 풍부한 메타데이터를 전달할 수 있습니다:
| segmentation_type_id | 의미 |
|---|---|
| 0x22 | Break Start (광고 시작) |
| 0x23 | Break End (광고 종료) |
| 0x30 | Provider Ad Start |
| 0x31 | Provider Ad End |
| 0x34 | Distributor Ad Start |
| 0x35 | Distributor Ad End |
| 0x40 | Unscheduled Event Start |
| 0x41 | Unscheduled Event End |
Cue-Out / Cue-In
SCTE-35의 핵심 동작:
본편 ──── [Cue-Out] ──── 광고 구간 (Avail) ──── [Cue-In] ──── 본편
│ │
└─ splice_insert(out=1, duration=60s) └─ splice_insert(out=0)전송 방식
| 입력 | SCTE-35 전달 방법 |
|---|---|
| MPEG-2 TS (RTP/UDP/SRT) | TS 내 전용 PID. 가장 표준적 |
| RTMP | AMF Data Tag로 전달 (제한적) |
| HLS 입력 | EXT-X-DATERANGE 또는 EXT-X-CUE-OUT 태그 |
HLS에서의 광고 신호: EXT-X-DATERANGE
HLS 환경에서 SCTE-35 정보를 클라이언트까지 전달하는 표준 방법은 EXT-X-DATERANGE 태그입니다.
#EXT-X-DATERANGE:ID="splice-6FFFFFF0",START-DATE="2026-06-01T10:30:00.000Z",PLANNED-DURATION=60.0,SCTE35-OUT=0xFC3025000000000000FFF00506FE...
#EXTINF:6.006,
segment_100.ts
#EXTINF:6.006,
segment_101.ts
...
#EXT-X-DATERANGE:ID="splice-6FFFFFF0",START-DATE="2026-06-01T10:30:00.000Z",END-DATE="2026-06-01T10:31:00.000Z",DURATION=60.0,SCTE35-IN=0xFC301...주요 속성:
| 속성 | 설명 |
|---|---|
ID | 광고 구간 고유 식별자. splice_event_id에 대응 |
START-DATE | 광고 시작 시각 (ISO 8601) |
DURATION | 확정된 광고 구간 길이 (초) |
PLANNED-DURATION | 예상 길이 (아직 Cue-In 안 온 경우) |
SCTE35-OUT | Cue-Out SCTE-35 바이너리 (Base64 또는 Hex) |
SCTE35-IN | Cue-In SCTE-35 바이너리 |
END-DATE | 광고 종료 시각 |
CLASS | 이벤트 분류 (예: com.apple.hls.interstitial) |
Apple은
EXT-X-DATERANGE로 광고를 시그널링하는 방식을 권장합니다. 구형#EXT-X-CUE-OUT:DURATION=30/#EXT-X-CUE-IN태그도 업계에서 관행적으로 쓰이지만, 이건 비표준이고 Apple 규격에 정의되지 않은 벤더 확장입니다.
DASH에서의 광고 신호: Period와 Event
Multi-Period 방식
광고 구간을 별도 Period로 분리합니다:
<MPD type="dynamic" availabilityStartTime="2026-06-01T10:00:00Z">
<Period id="content-1" start="PT0S" duration="PT10M">
<AdaptationSet mimeType="video/mp4">
<Representation bandwidth="5000000">
<SegmentTemplate media="content_$Number$.m4s" startNumber="1" duration="180000" timescale="90000"/>
</Representation>
</AdaptationSet>
</Period>
<Period id="ad-break-1" start="PT10M" duration="PT60S">
<!-- 광고 세그먼트: SSAI가 삽입 -->
<AdaptationSet mimeType="video/mp4">
<Representation bandwidth="5000000">
<SegmentTemplate media="ad_$Number$.m4s" startNumber="1" duration="180000" timescale="90000"/>
</Representation>
</AdaptationSet>
</Period>
<Period id="content-2" start="PT11M">
<!-- 본편 이어서 -->
</Period>
</MPD>EventStream 방식
Period 내에서 EventStream으로 SCTE-35 신호를 전달합니다:
<Period>
<EventStream schemeIdUri="urn:scte:scte35:2013:xml" timescale="90000">
<Event presentationTime="54000000" duration="5400000" id="1">
<Signal xmlns="http://www.scte.org/schemas/35/2016">
<SpliceInfoSection>
<SpliceInsert spliceEventId="1" outOfNetworkIndicator="true">
<BreakDuration autoReturn="true" duration="5400000"/>
</SpliceInsert>
</SpliceInfoSection>
</Signal>
</Event>
</EventStream>
</Period>| 방식 | 장점 | 단점 |
|---|---|---|
| Multi-Period | 광고/본편 분리 명확, 세그먼트 독립 | MPD 갱신 필요, Period 전환 시 디코더 리셋 가능 |
| EventStream | MPD 구조 불변, 인밴드 시그널링 | 클라이언트 구현 복잡, SSAI 처리 까다로움 |
SSAI에서 Manifest Manipulation
SSAI의 실체는 세션별 매니페스트 조작입니다. 같은 스트림을 보는 시청자 A와 B에게 다른 광고를 보여주려면, 각각 다른 매니페스트를 내려줘야 합니다.
세션 기반 동작
1. 클라이언트 → Session Init (player params 포함)
2. SSAI 서버 → 세션 ID + 세션별 매니페스트 URL 반환
3. 클라이언트 → 세션별 URL로 매니페스트 요청
4. SSAI 서버 → 해당 세션의 ADS 결과가 반영된 조작된 매니페스트 반환
5. 클라이언트 → 세그먼트 요청 (광고 세그먼트도 동일 URL 패턴)
6. SSAI 서버 → 광고 세그먼트를 본편과 동일한 도메인/경로로 프록시원본 매니페스트:
seg_098.ts → seg_099.ts → [Avail: 30s] → seg_105.ts → ...
시청자 A의 매니페스트:
seg_098.ts → seg_099.ts → /session/A/ad/coca_001.ts → /session/A/ad/coca_002.ts → seg_105.ts
시청자 B의 매니페스트:
seg_098.ts → seg_099.ts → /session/B/ad/samsung_001.ts → /session/B/ad/samsung_002.ts → seg_105.tsURL 패턴이 본편과 동일한 도메인을 쓰므로 광고 차단기가 구분할 수 없습니다.
Ad Conditioning (광고 소재 전처리)
SSAI가 seamless하려면 광고 소재가 본편과 동일한 포맷이어야 합니다:
- 같은 코덱 (H.264, H.265)
- 같은 해상도 래더 (1080p, 720p, 480p — 본편 ABR과 1:1 대응)
- 같은 세그먼트 길이 (본편이 6초면 광고도 6초 단위)
- 같은 프레임레이트 (29.97fps, 30fps 등)
- 같은 오디오 코덱/샘플레이트 (AAC 48kHz)
- 같은 GOP 구조 (IDR 간격)
이 전처리를 "Ad Conditioning" 또는 "Ad Transcoding"이라 부릅니다. 스펙이 안 맞으면:
- 디코더 리셋 → 화면 깜빡임/버퍼링
- ABR 스위칭 실패
- 오디오 팝/클릭 노이즈
- 타임스탬프 불연속 → 플레이어 오류
실무에서 이 부분이 가장 까다롭습니다. 광고주가 던져주는 소재는 제각각이고, 이걸 모든 ABR 래더에 맞춰 미리 트랜스코딩해둬야 합니다. SSAI 도입 프로젝트에서 시간의 60%가 Ad Conditioning 파이프라인 구축에 들어가는 경우도 흔합니다.
Ad Pod: 여러 광고 연속 재생
하나의 Avail에 여러 광고가 들어가는 것을 Ad Pod이라 합니다.
Avail (60초)
├── Ad 1: 15초 (Coca-Cola) ← sequence="1"
├── Ad 2: 30초 (Samsung) ← sequence="2"
└── Ad 3: 15초 (Hyundai) ← sequence="3"VAST 응답에서 <Ad sequence="N">으로 재생 순서가 지정됩니다. SSAI 서버는 이 순서대로 세그먼트를 매니페스트에 배치합니다.
Pod 내에서 개별 광고의 Beacon은 독립적으로 발사됩니다 — Ad 1의 complete이 찍혀야 Ad 2의 impression이 유효합니다.
마치며
SSAI의 핵심을 정리하면 다음과 같습니다.
신호 계층:
- SCTE-35 → Avail 생성 (어디에 광고를 넣을지)
- VAST/VMAP → 어떤 광고를 넣을지 (Creative, Duration, Tracking)
- Beacon → 광고가 실제로 재생됐는지 보고
처리 계층:
- Manifest Manipulation → 세션별 매니페스트 조작
- Ad Conditioning → 광고 소재를 본편 포맷에 맞춤
- Segment Stitching → 본편과 광고 세그먼트를 하나의 스트림으로 합성
비즈니스 현실:
- Impression Beacon이 돈입니다. Beacon이 안 찍히면 광고비를 받을 수 없습니다.
- Viewability 검증(OM SDK)이 안 되면 프리미엄 광고주가 기피합니다.
- Avail을 못 채우면(Slate 노출) 해당 구간 매출이 0입니다.
결국 SSAI는 "기술적으로 seamless한 전환"만큼이나 "비즈니스적으로 Beacon이 정확히 찍히는 것"이 중요합니다. 광고 기술의 핵심은 항상 돈의 추적입니다.
참고 자료
- IAB VAST: https://iabtechlab.com/standards/vast/
- SCTE Standards: https://www.scte.org/standards/
- IAB Open Measurement: https://iabtechlab.com/standards/open-measurement-sdk/