

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 使用解析器库观看摄像机的输出
<a name="parser-library"></a>

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 视频流解析器库。

该库还包括介绍如何使用这些工具的测试。

## 先决条件
<a name="parser-library-prerequisites"></a>

您必须具备以下内容才能检查和使用 Kinesis 视频流解析器库：
+ 亚马逊 Web Services (AWS) 账户。如果您还没有 AWS 账户，请参阅[注册获取 AWS 账户](gs-account.md#sign-up-for-aws)。
+ [Java 集成开发环境 (IDE)，例如 E [clipse Java Neon 或 IntelliJ I JetBrains d](https://www.eclipse.org/downloads/packages/release/neon/3/eclipse-jee-neon-3) ea。](https://www.jetbrains.com/idea/download/)
+ Java 11，比如 [Amazon Corretto](https://docs.aws.amazon.com//corretto/latest/corretto-11-ug/what-is-corretto-11.html) 11。

# 下载代码
<a name="parser-library-download"></a>

在此部分中，您下载 Java 库和测试代码，并将项目导入 Java IDE 中。

有关此过程的先决条件及其他详细信息，请参阅 [使用解析器库观看摄像机的输出](parser-library.md)。

1. 创建一个目录并从存储库（-pars [https://github.com/aws/amazon-kinesis-video-streamser-librar](https://github.com/aws/amazon-kinesis-video-streams-parser-library) y）中克隆 GitHub库源代码。

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

1. 打开你正在使用的 Java IDE（例如 Eclip [se 或 Intelli](https://www.eclipse.org/) [J ID](https://www.jetbrains.com/idea/) EA），然后导入你下载的 Apache Maven 项目：
   + **在 Eclipse 中：**依次选择 **File**、**Import**、**Maven**、**Existing Maven Projects**，并导航到 `kinesis-video-streams-parser-lib` 文件夹。
   + **在 IntelliJ Idea 中：**选择 **Import**。导航到下载的程序包的根目录中的 **pom.xml** 文件。

    有关更多信息，请参阅相关的 IDE 文档。

# 检查代码
<a name="parser-library-write"></a>

在此部分中，您将检查 Java 库和测试代码，并了解如何在您自己的代码中使用该库中的工具。

Kinesis 视频流解析器库包含以下工具：
+ [StreamingMkvReader](#parser-library-write-SMSR)
+ [FragmentMetadataVisitor](#parser-library-write-FMV)
+ [OutputSegmentMerger](#parser-library-write-OSM)
+ [KinesisVideoExample](#parser-library-write-example)

## StreamingMkvReader
<a name="parser-library-write-SMSR"></a>

此类以非阻止方式从流中读取指定的 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
<a name="parser-library-write-FMV"></a>

该类检索片段（媒体元素）的元数据，并跟踪包含媒体信息（例如编解码器私有数据、像素宽度或像素高度）的单个数据流。

以下代码示例 (来自 `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` 检索 `Frame` 的 `MkvTrackMetadata`。
+ 从 `Frame` 和 `MkvTrackMetadata` 对象检索并验证以下数据：
  + 音轨编号。
  + 帧的像素高度。
  + 帧的像素宽度。
  + 用于对帧进行编码的编解码器的 ID。
  + 此帧的到达顺序。验证前一帧的轨道号（如果存在）是否小于当前帧的轨道号。

要在项目中使用 `FragmentMetadataVisitor`，请使用访客的 `accept` 方法将 `MkvElement` 对象传递给访客：

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

## OutputSegmentMerger
<a name="parser-library-write-OSM"></a>

此类将来自流中不同音轨的元数据合并到带单个片段的流中。

以下代码示例 (来自 `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
<a name="parser-library-write-example"></a>

这是一个演示如何使用 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) 记录片段。

### 删除流并重新创建
<a name="parser-library-write-example-create"></a>

以下代码示例（来自`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
<a name="parser-library-write-example-putmedia"></a>

以下代码示例（来自`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
<a name="parser-library-write-example-getmedia"></a>

以下代码示例（来自`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
<a name="parser-library-write-example-parse"></a>

本节介绍如何使用 [StreamingMkvReader](#parser-library-write-SMSR)、[FragmentMetadataVisitor](#parser-library-write-FMV) 和 `CompositeMkvElementVisitor` 解析、保存到文件以及记录 `GetMedia` 返回的数据。

#### 读取 with GetMedia 的输出 StreamingMkvReader
<a name="parser-library-write-example-parse-smr"></a>

以下代码示例（来自`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
<a name="parser-library-write-example-parse-fmv"></a>

下面的代码示例 (摘自 `KinesisVideoExample.java` 和 `StreamingMkvReader.java` 文件) 创建 [FragmentMetadataVisitor](#parser-library-write-FMV)。然后，将 [StreamingMkvReader](#parser-library-write-SMSR) 迭代的 `MkvElement` 对象传递给使用 `accept` 方法的访问者。

*摘自 `KinesisVideoExample.java`：*

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

*摘自 `StreamingMkvReader.java`：*

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

#### 记录元素并将其写入文件
<a name="parser-library-write-example-parse-cmev"></a>

下面的代码示例 (摘自 `KinesisVideoExample.java` 文件) 创建以下对象，并将它们作为 `GetMediaProcessingArguments` 函数返回值的一部分返回：
+ 写入系统日志的 `LogVisitor` (`MkvElementVisitor` 的扩展)。
+ 将传入数据写入 MKV 文件的 `OutputStream`。
+ 缓冲发往 `OutputStream` 的数据的 `BufferedOutputStream`。
+ 将 `GetMedia` 结果中的连续元素与相同音轨和 EBML 数据合并的 [OutputSegmentMerger](#parser-library-write-OSM)。
+ `LogVisitor`将[FragmentMetadataVisitor](#parser-library-write-FMV)[OutputSegmentMerger](#parser-library-write-OSM)、和组成单个元素访客的 A。`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);
```

# 运行代码
<a name="parser-library-run"></a>

Kinesis 视频流解析器库包含专供您在自己的项目中使用的工具。项目包含针对工具的单元测试，您可以运行此工具验证您的安装。

库中包含以下单元测试：
+ **mkv**
  + `ElementSizeAndOffsetVisitorTest`
  + `MkvValueTest`
  + `StreamingMkvReaderTest`
+ **utilities**
  + `FragmentMetadataVisitorTest`
  + `OutputSegmentMergerTest`