

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 使用剖析器程式庫監看攝影機的輸出
使用剖析器程式庫進行串流

Kinesis 影片串流剖析器程式庫是一組工具，可用於 Java 應用程式，以取用 Kinesis 影片串流中的 MKV 資料。

程式庫包含下列工具：
+ [StreamingMkvReader](parser-library-write.md#parser-library-write-SMSR)：此類別可由影片串流讀取指定的 MKV 元素。
+ [FragmentMetadataVisitor](parser-library-write.md#parser-library-write-FMV)：此類別可擷取片段 (媒體元素) 及軌道 (包含媒體資訊的個別資料流，例如音訊或字幕) 的中繼資料。
+ [OutputSegmentMerger](parser-library-write.md#parser-library-write-OSM)：此類別可合併影片串流之中的連續片段或區塊。
+ [KinesisVideoExample](parser-library-write.md#parser-library-write-example)：這是示範如何使用 Kinesis 影片串流剖析器程式庫的範例應用程式。

程式庫也包括測試，顯示工具的使用方式。

## 先決條件


您必須具備下列項目，才能檢查和使用 Kinesis 影片串流剖析器程式庫：
+ Amazon Web Services (AWS) 帳戶。如果您還沒有 AWS 帳戶，請參閱 [註冊 AWS 帳戶](gs-account.md#sign-up-for-aws)。
+ Java 整合開發環境 (IDE)，例如 [Eclipse Java Neon](https://www.eclipse.org/downloads/packages/release/neon/3/eclipse-jee-neon-3) 或 [JetBrains IntelliJ Idea](https://www.jetbrains.com/idea/download/)。
+ Java 11，例如 [Amazon Corretto 11](https://docs.aws.amazon.com//corretto/latest/corretto-11-ug/what-is-corretto-11.html)。

# 下載程式碼


在本節中，您將下載 Java 程式庫及測試程式碼，並將專案匯入至 Java IDE。

如需先決條件及此程序的其他詳細資料，請參閱 [使用剖析器程式庫監看攝影機的輸出](parser-library.md)。

1. 建立目錄，並從 GitHub 儲存庫複製程式庫原始程式碼 ([https://github.com/aws/amazon-kinesis-video-streams-parser-library](https://github.com/aws/amazon-kinesis-video-streams-parser-library))。

   ```
   git clone https://github.com/aws/amazon-kinesis-video-streams-parser-library
   ```

1. 開啟您正在使用的 Java IDE （例如 [Eclipse](https://www.eclipse.org/) 或 [IntelliJ IDEA](https://www.jetbrains.com/idea/))，並匯入您下載的 Apache Maven 專案：
   + **在 Eclipse 中：**請選擇 **File (檔案)**、**Import (匯入)**、**Maven**、**Existing Maven Projects (現有 Maven 專案)**，並巡覽至 `kinesis-video-streams-parser-lib` 資料夾。
   + **在 IntelliJ Idea：**選擇 **Import (匯入)**。巡覽至已下載套件根目錄的 **pom.xml** 檔案。

    如需更多詳細資訊，請參閱 IDE 文件。

# 檢查程式碼


本節您將檢驗 Java 程式庫及測試程式碼，並了解如何在自己的程式碼之中，使用程式庫的各項工具。

Kinesis 影片串流剖析器程式庫包含下列工具：
+ [StreamingMkvReader](#parser-library-write-SMSR)
+ [FragmentMetadataVisitor](#parser-library-write-FMV)
+ [OutputSegmentMerger](#parser-library-write-OSM)
+ [KinesisVideoExample](#parser-library-write-example)

## StreamingMkvReader


此類別能以不封鎖的方式，由串流讀取指定的 MKV 元素。

下列程式碼範例 (來自 `FragmentMetadataVisitorTest`) 顯示如何建立及使用 `Streaming MkvReader`，由名為 `inputStream` 的輸入串流擷取 `MkvElement` 物件。

```
StreamingMkvReader mkvStreamReader =
                StreamingMkvReader.createDefault(new InputStreamParserByteSource(inputStream));
        while (mkvStreamReader.mightHaveNext()) {
            Optional<MkvElement> mkvElement = mkvStreamReader.nextIfAvailable();
            if (mkvElement.isPresent()) {
                mkvElement.get().accept(fragmentVisitor);
                ...
                }
            }
        }
```

## FragmentMetadataVisitor


此類別會擷取片段 （媒體元素） 的中繼資料，並追蹤包含媒體資訊的個別資料串流，例如轉碼器私有資料、像素寬度或像素高度。

下列程式碼範例 (來自 `FragmentMetadataVisitorTest` 檔案) 顯示如何使用 `FragmentMetadataVisitor` 由 `MkvElement` 物件擷取資料：

```
FragmentMetadataVisitor fragmentVisitor = FragmentMetadataVisitor.create();
        StreamingMkvReader mkvStreamReader =
                StreamingMkvReader.createDefault(new InputStreamParserByteSource(in));
        int segmentCount = 0;
        while(mkvStreamReader.mightHaveNext()) {
            Optional<MkvElement> mkvElement = mkvStreamReader.nextIfAvailable();
            if (mkvElement.isPresent()) {
                mkvElement.get().accept(fragmentVisitor);
                if (MkvTypeInfos.SIMPLEBLOCK.equals(mkvElement.get().getElementMetaData().getTypeInfo())) {
                    MkvDataElement dataElement = (MkvDataElement) mkvElement.get();
                    Frame frame = ((MkvValue<Frame>)dataElement.getValueCopy()).getVal();
                    MkvTrackMetadata trackMetadata = fragmentVisitor.getMkvTrackMetadata(frame.getTrackNumber());
                    assertTrackAndFragmentInfo(fragmentVisitor, frame, trackMetadata);
                }
                if (MkvTypeInfos.SEGMENT.equals(mkvElement.get().getElementMetaData().getTypeInfo())) {
                    if (mkvElement.get() instanceof MkvEndMasterElement) {
                        if (segmentCount < continuationTokens.size()) {
                            Optional<String> continuationToken = fragmentVisitor.getContinuationToken();
                            Assert.assertTrue(continuationToken.isPresent());
                            Assert.assertEquals(continuationTokens.get(segmentCount), continuationToken.get());
                        }
                        segmentCount++;
                    }
                }
            }

        }
```

前述範例顯示下列編碼模式：
+ 建立 `FragmentMetadataVisitor` 剖析資料，以及建立 [StreamingMkvReader](#parser-library-write-SMSR) 提供資料。
+ 對於串流之中的各個 `MkvElement`，請測試其中繼資料是否為 `SIMPLEBLOCK` 類型。
+ 如果是，請由 `MkvElement` 擷取 `MkvDataElement`。
+ 由 `MkvDataElement` 擷取 `Frame` (媒體資料)。
+ 由 `FragmentMetadataVisitor` 擷取 `MkvTrackMetadata` 用於 `Frame`。
+ 由 `Frame` 及 `MkvTrackMetadata` 物件擷取和驗證下列資料：
  + 軌道編號。
  + 影格像素高度。
  + 影格像素寬度。
  + 用於編碼影格轉碼器的轉碼器 ID。
  + 此影格依序抵達。如果存在，請確認上一個影格的追蹤號碼小於目前影格的追蹤號碼。

如欲在專案使用 `FragmentMetadataVisitor`，請將 `MkvElement` 物件以其 `accept` 方式傳送至訪客：

```
mkvElement.get().accept(fragmentVisitor);
```

## OutputSegmentMerger


此類別將串流之中不同軌道的中繼資料合併，成為單一區段的串流。

下列程式碼範例 (來自 `FragmentMetadataVisitorTest` 檔案) 顯示如何使用 `OutputSegmentMerger`，由名為 `inputBytes` 的位元組陣列合併軌道中繼資料：

```
FragmentMetadataVisitor fragmentVisitor = FragmentMetadataVisitor.create();

ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

OutputSegmentMerger outputSegmentMerger =
    OutputSegmentMerger.createDefault(outputStream);

CompositeMkvElementVisitor compositeVisitor =
    new TestCompositeVisitor(fragmentVisitor, outputSegmentMerger);

final InputStream in = TestResourceUtil.getTestInputStream("output_get_media.mkv");

StreamingMkvReader mkvStreamReader =
    StreamingMkvReader.createDefault(new InputStreamParserByteSource(in));
    
while (mkvStreamReader.mightHaveNext()) {
    Optional<MkvElement> mkvElement = mkvStreamReader.nextIfAvailable();
    if (mkvElement.isPresent()) {
        mkvElement.get().accept(compositeVisitor);
    if (MkvTypeInfos.SIMPLEBLOCK.equals(mkvElement.get().getElementMetaData().getTypeInfo())) {
        MkvDataElement dataElement = (MkvDataElement) mkvElement.get();
        Frame frame = ((MkvValue<Frame>) dataElement.getValueCopy()).getVal();
        Assert.assertTrue(frame.getFrameData().limit() > 0);
        MkvTrackMetadata trackMetadata = fragmentVisitor.getMkvTrackMetadata(frame.getTrackNumber());
        assertTrackAndFragmentInfo(fragmentVisitor, frame, trackMetadata);
    }
}
```

前述範例顯示下列編碼模式：
+ 建立 [FragmentMetadataVisitor](#parser-library-write-FMV) 由串流擷取中繼資料。
+ 建立輸出串流以接收合併的中繼資料。
+ 建立 `OutputSegmentMerger`，在 `ByteArrayOutputStream` 之中傳送。
+ 建立包含兩個訪客的 `CompositeMkvElementVisitor`。
+ 建立 `InputStream` 指向指定檔案。
+ 將輸入資料的各個元素合併為輸出串流。

## KinesisVideoExample


這是示範如何使用 Kinesis 影片串流剖析器程式庫的範例應用程式。

此類別執行下列操作：
+ 建立 Kinesis 影片串流。如果特定名稱串流已經存在，串流將遭到刪除並重新建立。
+ 呼叫 [PutMedia](https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_dataplane_PutMedia.html) 將影片片段串流至 Kinesis 影片串流。
+ 呼叫 [GetMedia](https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_dataplane_GetMedia.html) 將影片片段串流出 Kinesis 影片串流。
+ 使用 [StreamingMkvReader](#parser-library-write-SMSR) 剖析在串流傳回的片段，並使用 [FragmentMetadataVisitor](#parser-library-write-FMV) 記錄片段。

### 刪除及重新建立串流


下列程式碼範例 （來自 `StreamOps.java` 檔案） 會刪除指定的 Kinesis 影片串流：

```
//Delete the stream
amazonKinesisVideo.deleteStream(new DeleteStreamRequest().withStreamARN(streamInfo.get().getStreamARN()));
```

下列程式碼範例 （從 `StreamOps.java` 檔案） 會使用指定的名稱建立 Kinesis 影片串流：

```
amazonKinesisVideo.createStream(new CreateStreamRequest().withStreamName(streamName)
.withDataRetentionInHours(DATA_RETENTION_IN_HOURS)
.withMediaType("video/h264"));
```

### 呼叫 PutMedia


下列程式碼範例 (來自 `PutMediaWorker.java` 檔案) 在串流呼叫 [PutMedia](https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_dataplane_PutMedia.html)：

```
 putMedia.putMedia(new PutMediaRequest().withStreamName(streamName)
.withFragmentTimecodeType(FragmentTimecodeType.RELATIVE)
.withProducerStartTimestamp(new Date())
.withPayload(inputStream), new PutMediaAckResponseHandler() {
...
});
```

### 呼叫 GetMedia


下列程式碼範例 (來自 `GetMediaWorker.java` 檔案) 在串流呼叫 [GetMedia](https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_dataplane_GetMedia.html)：

```
GetMediaResult result = videoMedia.getMedia(new GetMediaRequest().withStreamName(streamName).withStartSelector(startSelector));
```

### 剖析 GetMedia 結果


本節說明如何使用 [StreamingMkvReader](#parser-library-write-SMSR)、[FragmentMetadataVisitor](#parser-library-write-FMV) 及 `CompositeMkvElementVisitor`，以剖析、儲存至檔案，以及記錄 `GetMedia` 傳回的資料。

#### 以 StreamingMkvReader 讀取 GetMedia 輸出


下列程式碼範例 (來自 `GetMediaWorker.java` 檔案) 建立 [StreamingMkvReader](#parser-library-write-SMSR)，並將其用於剖析 [GetMedia](https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_dataplane_GetMedia.html) 操作的結果：

```
StreamingMkvReader mkvStreamReader = StreamingMkvReader.createDefault(new InputStreamParserByteSource(result.getPayload()));
log.info("StreamingMkvReader created for stream {} ", streamName);
try {
    mkvStreamReader.apply(this.elementVisitor);
} catch (MkvElementVisitException e) {
    log.error("Exception while accepting visitor {}", e);
}
```

在前述程式碼範例中，[StreamingMkvReader](#parser-library-write-SMSR) 由 `GetMedia` 結果的承載擷取 `MKVElement` 物件。在下節之中，元素將傳送至 [FragmentMetadataVisitor](#parser-library-write-FMV)。

#### 使用 FragmentMetadataVisitor 擷取片段
FragmentMetadataVisitor

下列程式碼範例 (來自 `KinesisVideoExample.java` 及 `StreamingMkvReader.java` 檔案) 建立 [FragmentMetadataVisitor](#parser-library-write-FMV)。`MkvElement` 物件 (由 [StreamingMkvReader](#parser-library-write-SMSR) 重複執行) 將以 `accept` 方法傳送至訪客。

*來自 `KinesisVideoExample.java`：*

```
FragmentMetadataVisitor fragmentMetadataVisitor = FragmentMetadataVisitor.create();
```

*來自 `StreamingMkvReader.java`：*

```
if (mkvElementOptional.isPresent()) {
    //Apply the MkvElement to the visitor
    mkvElementOptional.get().accept(elementVisitor);
        }
```

#### 記錄元素並將其寫入檔案


下列程式碼範例 (來自 `KinesisVideoExample.java` 檔案) 建立下列物件，並將其以 `GetMediaProcessingArguments` 函數傳回值的一部分傳回：
+ `LogVisitor` (`MkvElementVisitor` 延伸) 寫入系統記錄。
+ `OutputStream` 將傳入資料寫入 MKV 檔案。
+ `BufferedOutputStream` 針對 `OutputStream` 緩衝資料邊界。
+ [OutputSegmentMerger](#parser-library-write-OSM) 以相同軌道及 EBML 資料合併 `GetMedia` 結果之中的連續元素。
+ 將 [FragmentMetadataVisitor](#parser-library-write-FMV)、 [OutputSegmentMerger](#parser-library-write-OSM)和 組成`LogVisitor`單一元素訪客`CompositeMkvElementVisitor`的 。

```
//A visitor used to log as the GetMedia stream is processed.
    LogVisitor logVisitor = new LogVisitor(fragmentMetadataVisitor);

    //An OutputSegmentMerger to combine multiple segments that share track and ebml metadata into one
    //mkv segment.
    OutputStream fileOutputStream = Files.newOutputStream(Paths.get("kinesis_video_example_merged_output2.mkv"),
            StandardOpenOption.WRITE, StandardOpenOption.CREATE);
    BufferedOutputStream outputStream = new BufferedOutputStream(fileOutputStream);
    OutputSegmentMerger outputSegmentMerger = OutputSegmentMerger.createDefault(outputStream);

    //A composite visitor to encapsulate the three visitors.
    CompositeMkvElementVisitor mkvElementVisitor =
            new CompositeMkvElementVisitor(fragmentMetadataVisitor, outputSegmentMerger, logVisitor);

    return new GetMediaProcessingArguments(outputStream, logVisitor, mkvElementVisitor);
```

然後，媒體處理引數會傳遞至 `GetMediaWorker`，再傳遞至 `ExecutorService`，在不同的執行緒上執行工作者：

```
GetMediaWorker getMediaWorker = GetMediaWorker.create(getRegion(),
        getCredentialsProvider(),
        getStreamName(),
        new StartSelector().withStartSelectorType(StartSelectorType.EARLIEST),
        amazonKinesisVideo,
        getMediaProcessingArgumentsLocal.getMkvElementVisitor());
executorService.submit(getMediaWorker);
```

# 執行程式碼


Kinesis 影片串流剖析器程式庫包含供您在自己的專案中使用的工具。專案包含工具的單元測試，可執行用於驗證安裝。

程式庫包含下列單元測試：
+ **mkv**
  + `ElementSizeAndOffsetVisitorTest`
  + `MkvValueTest`
  + `StreamingMkvReaderTest`
+ **公用程式**
  + `FragmentMetadataVisitorTest`
  + `OutputSegmentMergerTest`