# SEI Insertion

OvenMediaEngine은 Live Stream에 SEI (Supplemental Enhancement Information)를 삽입하여 Frame 단위의 정확도로 사용자 정의 데이터를 Video Content와 함께 전달할 수 있습니다.

## 개요

* OvenMediaEngine의 `sendEvents` API를 통해 동적으로, XML 설정을 통해 지속적으로 SEI를 삽입할 수 있습니다.
* SEI 삽입 시 사용자 정의 데이터를 추가할 수 있으며 삽입된 SEI에는 `UUID`와 `Timestamp`가 자동으로 포함됩니다.
  * 삽입된 SEI는 UUID, Timestamp, 사용자 데이터를 포함한 OvenMediaEngine 전용 포맷으로 생성됩니다.
* OvenPlayer는 OvenMediaEngine이 삽입한 SEI를 수신할 수 있으며 OvenMediaEngine이 정의한 SEI 규격을 자동으로 파싱하는 기능을 제공합니다.

## Send Event API를 통한 SEI 삽입하기

### API Interface

OvenMediaEngine의 SendEvent REST API를 사용하면 동적으로 Stream에 SEI를 삽입하는 Event를 발생시킬 수 있습니다.

**Request**

<details>

<summary><mark style="color:blue;">POST</mark> /v1/vhosts{vhost}/apps/{app}/streams/{stream}:sendEvent</summary>

**Header**

```http
Authorization: Basic {credentials}

# Authorization
Credentials for HTTP Basic Authentication created with <AccessToken>
```

**Body**

```json
{
    "eventFormat": "sei",
    "eventType": "video",
    "events": [
        {
            "seiType": "UserDataUnregistered",
            "data": "OvenMediaEngine"
        }
    ]
}
```

</details>

<details>

<summary><mark style="color:blue;">POST</mark> /v1/vhosts{vhost}/apps/{app}/streams/{stream}:sendEvents</summary>

**Header**

```http
Authorization: Basic {credentials}

# Authorization
Credentials for HTTP Basic Authentication created with <AccessToken>
```

**Body**

```json
[
  {
      "eventFormat": "sei",
      "eventType": "video",
      "events": [
          {
              "seiType": "UserDataUnregistered",
              "data": "OvenMediaEngine"
          }
      ]
  }
]
```

</details>

<table><thead><tr><th width="185">Parameter</th><th width="97">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>eventFormat</code></td><td>Y</td><td>Event 포맷을 지정합니다: <code>sei</code> 형식 사용.</td></tr><tr><td><code>eventType</code></td><td>N</td><td><p>Event 타입을 지정합니다.</p><ul><li>Default: <code>video</code></li></ul></td></tr><tr><td><code>events</code></td><td>Y</td><td>Event 데이터 값을 포함합니다.</td></tr><tr><td><code>event.seiType</code></td><td>N</td><td><p>SEI 타입을 지정합니다.</p><ul><li>Default: <code>UserDataUnregistered</code></li></ul></td></tr><tr><td><code>event.data</code></td><td>Y</td><td>실제 전송할 데이터를 입력합니다.</td></tr></tbody></table>

**Responses**

<details>

<summary><mark style="color:blue;">200</mark> Ok</summary>

**Header**

```http
Content-Type: application/json
```

**Body**

```json
{
    "message": "OK",
    "statusCode": 200
}
```

</details>

<details>

<summary><mark style="color:red;">400</mark> Bad Request</summary>

**Header**

```http
Content-Type: application/json
```

**Body**

<pre class="language-json"><code class="lang-json"><strong>{
</strong>    "message": "eventFormat(string) and events(array) are required",
    "statusCode": 400
}
</code></pre>

<pre class="language-json"><code class="lang-json"><strong>{
</strong>    "message": "eventFormat is not supported: [XXX]",
    "statusCode": 400
}
</code></pre>

<pre class="language-json"><code class="lang-json"><strong>{
</strong>    "message": "Could not make events data",
    "statusCode": 400
}
</code></pre>

<pre class="language-json"><code class="lang-json"><strong>{
</strong>    "message": "eventType must be string",
    "statusCode": 400
}
</code></pre>

<pre class="language-json"><code class="lang-json"><strong>{
</strong>    "message": "eventType is not supported: [XXX]",
    "statusCode": 400
}
</code></pre>

</details>

<details>

<summary><mark style="color:red;">500</mark> Internal Server Error</summary>

**Header**

```http
Content-Type: application/json
```

**Body**

```json
{
    "message": "Could not inject event: [XXX]",
    "statusCode": 500
}
```

</details>

## XML설정을 통한 SEI 삽입하기

지속적인 SEI 삽입이 필요한 경우, XML 설정을 통해 동작을 구성할 수 있습니다. SEI 삽입 Event를 정의한 XML파일을 생성하고 `Server.xml`에서 `EventGenerator`를 활성화 합니다.

### Configuration 예제

**Server.xml:** `<Application><EventGenerator>`를 추가하여 SEI 삽입을 위한 `EventGenerator`기능을 활성화 할 수 있습니다.

```xml
<?xml version="1.0" encoding="UTF-8"?>
<Server version="8">
  ...
  <VirtualHosts>
    <VirtualHost>
      <Applications>
        <Application>
          ...
          <EventGenerator>
            <Enable>true</Enable>
            <Path>events/send_event_info.xml</Path>
          </EventGenerator>
        </Application>
      </Applications>
    </VirtualHost>
  </VirtualHosts>
</Server>
```

<table><thead><tr><th width="144">Element</th><th width="100">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>&#x3C;Enable></code></td><td>Y</td><td><p><code>true</code> 또는 <code>false</code>로 활성화 여부를 설정합니다.</p><ul><li>Default: <code>false</code></li></ul></td></tr><tr><td><code>&#x3C;Path></code></td><td>Y</td><td>세부 SEI 삽입을 정의한 XML 파일의 경로를 설정합니다. 상대 경로를 지정하면 <code>Server.xml</code> 파일이 있는 디렉토리가 기준이 됩니다.</td></tr></tbody></table>

**SEI 삽입 Event를 정의한 XML:** `Server.xml`에서 정의한 경로에 SEI 삽입 Event를 정의한 XML파일을 생성합니다. 아래 예제의 경우 `send_event_info.xml`입니다.

```xml
<?xml version="1.0" encoding="UTF-8"?>
<EventInfo>
  <Event>
    <Enable>true</Enable>
    <SourceStreamName>stream*</SourceStreamName>
    <Interval>2000</Interval>
    <EventFormat>sei</EventFormat>
    <EventType>video</EventType>
    <Values>
      <SeiType>UserDataUnregistered</SeiType>
      <Data>Hi! OvenMediaEngine! CurrentTime:${EpochTime}</Data>
      <KeyframeOnly>true</KeyframeOnly>
    </Values>
  </Event>
</EventInfo>
```

<table><thead><tr><th width="239">매개변수</th><th width="100">Required</th><th>설명</th></tr></thead><tbody><tr><td><code>&#x3C;Enable></code></td><td>Y</td><td><p><code>true</code> 또는 <code>false</code>로 활성화 여부를 설정합니다.</p><ul><li>Default: <code>false</code></li></ul></td></tr><tr><td><code>&#x3C;SourceStreamName></code></td><td>Y</td><td><p>Event를 삽입할 Stream의 이름을 지정합니다.</p><ul><li>패턴 매칭을 위한 Wildcard (<code>*</code>)를 지원합니다.</li></ul></td></tr><tr><td><code>&#x3C;Interval></code></td><td>Y</td><td>Event 발생 주기를 밀리초 (ms) 단위로 설정합니다.</td></tr><tr><td><code>&#x3C;EventFormat></code></td><td>Y</td><td>Event 포맷을 지정합니다: <code>sei</code> 형식 사용.</td></tr><tr><td><code>&#x3C;EventType></code></td><td>N</td><td><p>Event 타입을 지정합니다.</p><ul><li>Default: <code>video</code></li></ul></td></tr><tr><td><code>&#x3C;Values></code></td><td>Y</td><td>Event 데이터 값을 포함합니다.</td></tr><tr><td><code>&#x3C;Values>&#x3C;SeiType></code></td><td>N</td><td><p>SEI 타입을 지정합니다.</p><ul><li>Default: <code>UserDataUnregistered</code></li></ul></td></tr><tr><td><code>&#x3C;Values>&#x3C;Data></code></td><td>Y</td><td><p>실제 전송할 데이터를 입력합니다.</p><ul><li><code>${EpochTime}</code>을 사용하여 밀리초 단위로 구성된 서버의 현재 Epoch Time으로 대체해서 전송할 수 있는 Macro를 지원합니다 (예시: 1747147513056).</li></ul></td></tr><tr><td><code>&#x3C;Values>&#x3C;KeyframeOnly></code></td><td>N</td><td><p>Event 삽입 대상을 지정합니다. <code>true</code>인 경우 Interval 시간이 지난 후 첫번째 Keyframe에 Event가 삽입됩니다 (0.18.2.0+ 버전부터 지원).</p><ul><li>Default: <code>false</code></li></ul></td></tr></tbody></table>

{% hint style="info" %}
이벤트 정의 XML 파일의 내용을 변경하면 OvenMediaEngine을 재시작하지 않아도 변경 사항이 바로 적용됩니다.
{% endhint %}

## OvenMediaEngine-Specific SEI Payload Data 규격

OvenMediaEngine이 생성한 SEI의 Payload는 다음과 같은 구조로 UUID와Timestamp 값이 항상 포함되어 있습니다.

```
0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| uuid_iso_iec_11578(128)                                       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Timestamp (64)                                                |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data (Payload Size - UUID(128) - Timestamp(64))               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| ...                                                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
```

<table><thead><tr><th width="147">Field</th><th width="193">Size (bits)</th><th>Description</th></tr></thead><tbody><tr><td>UUID</td><td>128</td><td>SEI Payload가 OvenMediaEngine이 정의한 규격임을 나타내는 값으로,항상 <code>464d4c47-5241-494e-434f-4c4f554201</code>입니다.</td></tr><tr><td>Timestamp</td><td>64</td><td>밀리초 (ms) 단위의 Epoch Time입니다.</td></tr><tr><td>Data</td><td>사용자 정의 데이터에 따라 다름</td><td>사용자 정의 데이터입니다.</td></tr></tbody></table>

## SEI Data 수신

OvenPlayer는 OvenMediaEngine이 삽입한 SEI를 파싱해서 포함되어있는 UUID, TimeStamp, 사용자 정의 데이터를 Application에 전달할 수 있습니다.

### Code 예제

```javascript
var player = OvenPlayer.create('player', {
  sources: [
    {
      type: 'webrtc', // WebRTC 스트림 재생
      file: 'wss://[YOUR_OvenMediaEngine]:3333/app/stream'
    }
  ],
  parseStream: {
    enabled: true // H.264 NAL 파싱 활성화
  }
});

function toAsciiString(byteArray) {
  return String.fromCharCode.apply(null, byteArray);
}

player.on('metaData', function (metadata) {
  console.log('MetaData:', metadata);
  /* 출력:
    {
      type: 'sei',
      nalu: Unit8Array(33),
      sei: {
        type: 5,
        size: 39,
        payload: Unit8Array(39)
      },
      registered: true,
      uuid: '464d4c47-5241-494e-434f-4c4f-55524201',
      timecode: 1739851602778,
      userdata: Unit8Array(15)
    }
  */

  console.log(`사용자 데이터 문자열 변환: ${toAsciiString(metadata.userdata)}`);
  /* 출력:
    사용자 데이터 문자열 변환: OvenMediaEngine
  */
});
```

**플레이어 초기화**:

* `OvenPlayer.create()` 함수를 호출하여 지정된 div에 플레이어를 생성합니다.
* `sources` 배열에서 재생할 Stream의 유형과 URL을 지정합니다. SEI는 WebRTC Stream에서만 지원됩니다.
* `parseStream.enabled: true` 설정을 통해 H.264의 NAL (Network Abstraction Layer)파싱을 활성화합니다. 이는 SEI 메타데이터 처리를 위한 필수 설정입니다.

**SEI 데이터 처리**:

* `player.on('metaData', callback)` Event Listener를 등록하여 SEI가 수신될 때마다 처리합니다.
* Event Callback에 전달되는 Parameter에서 OvenMediaEngine이 삽입한 UUID, Timestamp, 사용자 정의데이터를 얻어올 수 있습니다.

**`metaData` Event Callback Parameter**

<table><thead><tr><th width="154">필드</th><th>설명</th></tr></thead><tbody><tr><td><code>type</code></td><td>항상 <code>sei</code>로 설정되며, 이것이 SEI 메타데이터임을 나타냅니다.</td></tr><tr><td><code>nalu</code></td><td>SEI의 원시 NALU (Network Abstraction Layer Unit) 데이터를 포함하는 Uint8Array입니다.</td></tr><tr><td><code>sei</code></td><td><p>SEI Parsing 결과로, 다음 하위 필드들을 포함합니다:</p><ul><li><code>type</code>: SEI 타입</li><li><code>size</code>: Payload 크기</li><li><code>payload</code>: 원시 SEI Paylosd 데이터 (Uint8Array)</li></ul></td></tr><tr><td><code>registered</code></td><td>OvenMediaEngine에서 정의한 형식으로 SEI가 생성되었는지 여부를 나타냅니다. <code>true</code>인 경우 아래의 추가 필드들이 포함됩니다.</td></tr><tr><td><code>uuid</code></td><td>(<code>registered=true</code>인 경우) OvenMediaEngine이 SEI에 삽입한 고유 식별자입니다.</td></tr><tr><td><code>timecode</code></td><td>(<code>registered=true</code>인 경우) SEI가 삽입된 시점의 Timestamp (밀리초; ms)입니다.</td></tr><tr><td><code>userdata</code></td><td>(<code>registered=true</code>인 경우) 사용자 정의 데이터가 포함된 Uint8Array입니다. 이 데이터는 Application의 요구에 맞게 Parsing해야 합니다.</td></tr></tbody></table>

## 부록: OvenPlayer 설치하기

OvenPlayer 0.10.39 버전부터 WebRTC Stream 재생 시 Video에 포함된 SEI 데이터를 수신할 수 있습니다.

1. OvenPlayer는 [OvenPlayer GitHub](https://github.com/AirenSoft/OvenPlayer/releases/tag/v0.10.39)에서 다운로드할 수 있습니다.
2. 다운로드한 파일의 압축을 푼 후, `dist` 디렉토리 내의 모든 파일을 프로젝트의 라이브러리 폴더로 복사합니다:

```
├─.github
├─demo
├─dist
│      ovenplayer.js
│      ovenplayer.js.map
│      RTCTransformWorker.worker.worker.js
│      RTCTransformWorker.worker.worker.js.map
├─docs
├─packages
└─src
```

복사할 주요 파일:

* `ovenplayer.js`: OvenPlayer 코어 라이브러리
* `RTCTransformWorker.worker.worker.js`: WebRTC 처리를 위한 Worker Script

{% hint style="info" %}
Web Worker의 CORS정책으로 인해 `ovenplayer.js`는 자체 호스팅이 되어야 하며, `RTCTransformWorker.worker.worker.js` 파일은 반드시 `ovenplayer.js`와 같은 경로에 존재해야 합니다.
{% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ovenmediaengine-enterprise.gitbook.io/guide/ko-kr/features/workflow-integration-and-external-system-connectivity/sei-insertion.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
