# 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 %}
