

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

# 使用流式传输视频 CloudFront
<a name="tutorial-stream-video-with-cloudfront"></a>

媒体工作流通常将完成的内容（视频点播 (VOD) 文件、HTTP 直播 (HLS) 包、图像和图形）存储在 FSx for ONTAP 卷上，编辑、制作人和自动化系统使用 NFS 或 SMB 写入该卷。

通过连接到 FSx for ONTAP 卷的 Amazon S3 接入点， CloudFront 可以直接从该卷中提供内容。编辑和制作系统像往常一样通过 NFS 或 SMB 向卷发布内容，通过接入点 CloudFront 获取内容，观众则从最近的 CloudFront 边缘位置接收内容。

在本教程中，您将示例视频编码为 HLS 自适应比特率包，将输出上传到连接到 FSx for ONTAP 卷的接入点，使用原始访问控制配置 CloudFront 分发以使观众无法绕过直接访问音量，并验证直播是否端 CloudFront 对端播放。

**注意**  
本教程大约需要 **40 到 60 分钟**才能完成。 AWS 服务 使用者会对您创建的资源产生费用。如果您及时完成所有步骤，包括**清理**部分，则美国东部（弗吉尼亚北部）的预期费用将低于 **1美元** AWS 区域。该估算值不包括 FSx 对 ONTAP 容量本身的持续收费。

## 模式是如何运作的
<a name="tutorial-cf-how-it-works"></a>

请求流为：
+ 观众的播放器（浏览器、移动应用程序、智能电视）向 CloudFront 域请求HLS主播放列表。
+ CloudFront 检查其边缘缓存。如果未成功， CloudFront 则使用签名版本 4 (Sigv4) 及其源访问控制 (OAC) 对请求进行签名，然后将其转发到接入点的 Amazon S3 终端节点。
+ 接入点根据其访问策略授权请求，该策略允许 CloudFront 服务主体范围限于您的分发，并从 FSx for ONTAP 卷返回请求的对象。
+ CloudFront 在边缘缓存响应并将其返回给查看者。

HLS 包混合了两种类型的文件，它们受益于不同的缓存策略：
+ **播放列表** (`.m3u8`) 描述了哪些片段构成了直播内容。使用简短的 `Cache-Control` TTL，这样您就可以快速发布更新的播放列表。
+ **片段** (`.ts`) 包含编码后的视频和音频。写入后，片段的内容永远不会改变，因此请使用长的、不可变`Cache-Control`的 TTL。

## 先决条件
<a name="tutorial-cf-prerequisites"></a>
+ 连接了 Amazon S3 接入点的 ONTAP 卷的 FSx。接入点必须有**互联网**网络来源，这样 CloudFront 才能到达它。有关说明，请参阅[创建接入点](fsxn-creating-access-points.md)。
+ AWS CLI 安装并配置了版本 2，其凭据可以创建 CloudFront 分配、源访问控制和接入点策略。
+ [FFmpeg](https://ffmpeg.org/) 安装在本地，用于将示例视频编码为 HLS。
+ 源视频文件。本教程使用了Blender基金会的 [Sintel预告片](https://download.blender.org/durian/trailer/sintel_trailer-1080p.mp4)，这是一段在知识共享下发布的52秒1080p片段。

## 第 1 步：将源视频编码为 HLS 包
<a name="tutorial-cf-encode"></a>

使用 FFmpeg 制作 360p、720p 和 1080p 的三种变体 HLS 软件包，并具有逼真的过顶 (OTT) 比特率。生成的软件包包括一个主播放列表，该播放列表引用了每个变体的播放列表，每个播放列表都列出了四秒钟的传输流片段。

1. 下载源视频。

   ```
   $ mkdir -p ~/media && cd ~/media
   curl -sSL -o sintel-1080p.mp4 \
       https://download.blender.org/durian/trailer/sintel_trailer-1080p.mp4
   ```

1. 使用三种自适应比特率变体将视频编码为 HLS。

   ```
   $ mkdir hls && cd hls
   ffmpeg -i ../sintel-1080p.mp4 \
       -filter_complex "[0:v]split=3[v1][v2][v3]; \
           [v1]scale=w=640:h=360[v1out]; \
           [v2]scale=w=1280:h=720[v2out]; \
           [v3]scale=w=1920:h=1080[v3out]" \
       -map "[v1out]" -c:v:0 libx264 -b:v:0 800k  -maxrate:v:0 856k  -bufsize:v:0 1200k \
       -map "[v2out]" -c:v:1 libx264 -b:v:1 3000k -maxrate:v:1 3200k -bufsize:v:1 4500k \
       -map "[v3out]" -c:v:2 libx264 -b:v:2 5500k -maxrate:v:2 5900k -bufsize:v:2 8250k \
       -preset veryfast -g 48 -keyint_min 48 -sc_threshold 0 \
       -map a:0 -map a:0 -map a:0 -c:a aac -b:a:0 96k -b:a:1 128k -b:a:2 128k \
       -f hls -hls_time 4 -hls_playlist_type vod -hls_flags independent_segments \
       -hls_segment_filename "stream_%v/seg_%03d.ts" \
       -master_pl_name master.m3u8 \
       -var_stream_map "v:0,a:0,name:360p v:1,a:1,name:720p v:2,a:2,name:1080p" \
       "stream_%v/playlist.m3u8"
   ```

   该命令生成一个目录树，其中包含一个主播放列表、三个变体播放列表以及每个变体的传输流片段。

   ```
   hls/
   ├── master.m3u8
   ├── stream_360p/
   │   ├── playlist.m3u8
   │   ├── seg_000.ts
   │   └── ...
   ├── stream_720p/
   │   ├── playlist.m3u8
   │   ├── seg_000.ts
   │   └── ...
   └── stream_1080p/
       ├── playlist.m3u8
       ├── seg_000.ts
       └── ...
   ```

## 步骤 2：将 HLS 包上传到接入点
<a name="tutorial-cf-upload"></a>

上传软件包两次，一次用于包含短 TTL 的播放列表，一次用于具有长且不可变的 TTL 的片段。正确设置`Content-Type`很重要：大多数玩家都需要 for `.m3u8` 和 `application/vnd.apple.mpegurl` f `video/mp2t` or `.ts`。

{{access-point-alias}}替换为您的接入点别名。

```
$ # Playlists: short TTL, m3u8 content type
aws s3 cp ~/media/hls/ "s3://{{access-point-alias}}/content/sintel/" \
    --recursive --exclude "*" --include "*.m3u8" \
    --content-type "application/vnd.apple.mpegurl" \
    --cache-control "max-age=60"

# Segments: long immutable TTL, ts content type
aws s3 cp ~/media/hls/ "s3://{{access-point-alias}}/content/sintel/" \
    --recursive --exclude "*" --include "*.ts" \
    --content-type "video/mp2t" \
    --cache-control "max-age=31536000,immutable"
```

确认上传的两个文件都符合预期的内容类型和缓存标头。

```
$ aws s3api head-object --bucket {{access-point-alias}} \
    --key content/sintel/master.m3u8 \
    --query '{ContentType:ContentType,CacheControl:CacheControl}'
```

## 步骤 3：创建源站访问控制
<a name="tutorial-cf-oac"></a>

源访问控制 (OAC) 允许您对接入点的请求进行 CloudFront 签名，因此 CloudFront只能获取对象。如果没有 OAC，则查看者可以通过直接从接入点端点请求对象来绕过 CloudFront 。

```
$ aws cloudfront create-origin-access-control \
    --origin-access-control-config \
    'Name=fsxn-media-oac,SigningProtocol=sigv4,SigningBehavior=always,OriginAccessControlOriginType=s3'
```

记下响应中的 `Id`。您将在下一步中使用它。

## 步骤 4：创建分 CloudFront 配
<a name="tutorial-cf-distribution"></a>

使用接入点别名作为源域创建 CloudFront 分配。使用`CachingOptimized`托管缓存策略，该策略遵循您在步骤 2 中设置的`Cache-Control`标头。

1. 将以下配置保存到名为的文件中`dist.json`，替换占位符。

   ```
   {
       "CallerReference": "fsxn-media-1",
       "Comment": "FSx for ONTAP media delivery",
       "Enabled": true,
       "DefaultRootObject": "",
       "Origins": {
           "Quantity": 1,
           "Items": [{
               "Id": "fsxn-ap",
               "DomainName": "{{access-point-alias}}.s3.{{region}}.amazonaws.com",
               "S3OriginConfig": {"OriginAccessIdentity": ""},
               "OriginAccessControlId": "{{oac-id}}",
               "ConnectionAttempts": 3,
               "ConnectionTimeout": 10
           }]
       },
       "DefaultCacheBehavior": {
           "TargetOriginId": "fsxn-ap",
           "ViewerProtocolPolicy": "redirect-to-https",
           "AllowedMethods": {
               "Quantity": 2, "Items": ["GET", "HEAD"],
               "CachedMethods": {"Quantity": 2, "Items": ["GET", "HEAD"]}
           },
           "Compress": true,
           "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6"
       },
       "PriceClass": "PriceClass_100",
       "ViewerCertificate": {"CloudFrontDefaultCertificate": true}
   }
   ```
**注意**  
`PriceClass_100`仅在北美和欧洲使用 CloudFront 边缘站点，这样可以降低本教程的成本。对于全局边缘覆盖范围，请将该值更改为`PriceClass_All`。有关更多信息，请参阅[为 CloudFront 分配选择价格等级](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/PriceClass.html)。

1. 创建发行版。

   ```
   $ aws cloudfront create-distribution --distribution-config file://dist.json \
       --query 'Distribution.{Id:Id,DomainName:DomainName,ARN:ARN}'
   ```

   记下响应中的分发 ID、ARN 和域名。部署该分发大约需要五分钟。部署时，您可以继续执行步骤 5。

## 步骤 5：附加允许的接入点策略 CloudFront
<a name="tutorial-cf-ap-policy"></a>

接入点策略向 CloudFront 服务主体授予读取对象的权限，该权限仅限于使用`AWS:SourceArn`条件的特定分配。

1. 将以下策略保存到名为的文件中`ap-policy.json`，替换占位符。

   ```
   {
       "Version": "2012-10-17", 		 	 	 
       "Statement": [{
           "Sid": "AllowCloudFrontServicePrincipal",
           "Effect": "Allow",
           "Principal": {"Service": "cloudfront.amazonaws.com"},
           "Action": "s3:GetObject",
           "Resource": "arn:aws:s3:{{region}}:{{account-id}}:accesspoint/{{access-point-name}}/object/*",
           "Condition": {
               "StringEquals": {
                   "AWS:SourceArn": "arn:aws:cloudfront::{{account-id}}:distribution/{{distribution-id}}"
               }
           }
       }]
   }
   ```

1. 将策略附加到接入点。

   ```
   $ aws s3control put-access-point-policy \
       --account-id {{account-id}} \
       --name {{access-point-name}} \
       --policy file://ap-policy.json
   ```

## 步骤 6：验证播放
<a name="tutorial-cf-verify"></a>

等待分发达到`Deployed`状态。

```
$ aws cloudfront get-distribution --id {{distribution-id}} \
    --query 'Distribution.Status'
```

通过获取主播放列表 CloudFront。

```
$ curl -sS "https://{{distribution-domain}}/content/sintel/master.m3u8"
```

响应应列出三种变体。

```
#EXTM3U
#EXT-X-VERSION:6
#EXT-X-STREAM-INF:BANDWIDTH=1031744,RESOLUTION=640x360,CODECS="avc1.64001e,mp4a.40.2"
stream_360p/playlist.m3u8

#EXT-X-STREAM-INF:BANDWIDTH=3497301,RESOLUTION=1280x720,CODECS="avc1.64001f,mp4a.40.2"
stream_720p/playlist.m3u8

#EXT-X-STREAM-INF:BANDWIDTH=6311285,RESOLUTION=1920x1080,CODECS="avc1.640028,mp4a.40.2"
stream_1080p/playlist.m3u8
```

检查响应标头的内容类型、缓存控制和缓存状态是否正确。

```
$ curl -sSI "https://{{distribution-domain}}/content/sintel/stream_1080p/seg_000.ts"
```

成功的响应会显示`content-type: video/mp2t``cache-control: max-age=31536000,immutable`、和一个`x-cache`标头，指示响应是来自边缘还是源头。

最后，使用 FFmpeg 端对端播放直播，以确认所有片段都正确提取和解码。

```
$ ffprobe -v error \
    -show_entries stream=codec_name,width,height \
    -show_entries format=duration \
    "https://{{distribution-domain}}/content/sintel/master.m3u8"
```

你也可以在 Safari 或 VLC 中打开主播放列表网址，或者使用诸如 [hls.js](https://github.com/video-dev/hls.js) 之类的 JavaScript 播放器将其嵌入到网页中。

## 扩展模式
<a name="tutorial-cf-extending"></a>
+ **使用带有 HTTPS 的自定义域名。**为您的域申请 ACM 证书，将其附加到分配中，然后添加指向该 CloudFront 域的 CNAME 记录。有关说明，请参阅将[自定义 URL 与配合使用 CloudFront](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cnames-and-https-procedures.html)。
+ **使用签名网址或签名 Cookie 保护优质内容。**对于需要授权的内容（订阅服务、抢先体验预览、地理围栏内容），请使用 CloudFront 签名 URL 或签名 Cookie。请参阅[使用签名网址和签名 Cookie 提供私有内容](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-signed-urls.html)。
+ **发布新内容时使缓存失效。**替换播放列表或上传新的 HLS 包时，请使用从 `aws cloudfront create-invalidation` edge 中移除旧版本 CloudFront 。对于具有长 TTL 的不可变分段，通常不需要失效，因为每个包的分段文件名都是唯一的。
+ **为基于浏览器的玩家启用 CORS。**如果其他网域中基于浏览器的 HLS 播放器加载了您的直播，请使用响应`Access-Control-Allow-Origin`标头策略向 CloudFront响应添加标头。
+ **记录查看者请求。**启用 CloudFront标准日志记录或实时日志，以捕获查看者对分析、计费或滥用检测的请求。

## 问题排查
<a name="tutorial-cf-troubleshooting"></a>

403 禁止进入 CloudFront  
缺少接入点策略，不包括 CloudFront 服务主体，或者`AWS:SourceArn`条件引用了错误的分发 ARN。使用验证策略`aws s3control get-access-point-policy`并确认分发 ARN 与您的`aws cloudfront create-distribution`回复中的分发 ARN 相匹配。

播放器加载主播放列表但无法播放  
检查片段文件是否有`Content-Type: video/mp2t`，播放列表是否有`Content-Type: application/vnd.apple.mpegurl`。一些玩家拒绝使用通用内容类型的片段。 Re-upload 使用正确的`--content-type`标志。

新的播放列表需要时间才能吸引观众  
CloudFront 缓存标题设置的 TTL 的播放`Cache-Control`列表。如果您需要较短的 TTL，请使用较小的`max-age`值重新上传播放列表，或者创建无效状态。区段没有这个问题，因为它们的内容不会改变。

`x-cache: Miss from cloudfront`根据每一个请求  
当某个区域的查看者第一次请求文件时，这是正常的。 CloudFront 失败时从原点获取并缓存 TTL 的响应。随后从该边缘站点请求相同文件的请求将返回`Hit from cloudfront`。

拒绝直接访问接入点  
这是预期行为。OAC 要求来自服务主体的 SigV4-signed 请求 CloudFront，接入点策略限制对 CloudFront 服务主体的访问。观看者只能通过分发域访问内容。

## 清理
<a name="tutorial-cf-clean-up"></a>

禁用并删除该分配，然后删除其余资源。必须先禁用发行版，然后才能将其删除，这需要几分钟。

禁用需要来自的两个值`get-distribution-config`：`ETag`f `--if-match` or 和 for 的内部`DistributionConfig`对象`--distribution-config`（完整的响应还包含 ETag，它`update-distribution`不接受）。

```
$ # Capture the current ETag and the DistributionConfig body
GET_ETAG=$(aws cloudfront get-distribution-config --id {{distribution-id}} \
    --query 'ETag' --output text)
aws cloudfront get-distribution-config --id {{distribution-id}} \
    --query 'DistributionConfig' --output json \
    | jq '.Enabled = false' > dist-updated.json

# Disable the distribution. The response returns a new ETag.
UPDATE_ETAG=$(aws cloudfront update-distribution --id {{distribution-id}} \
    --if-match "$GET_ETAG" --distribution-config file://dist-updated.json \
    --query 'ETag' --output text)

# Wait for Status to reach Deployed before deleting.
aws cloudfront get-distribution --id {{distribution-id}} \
    --query 'Distribution.Status'

# Delete the distribution using the ETag from the update call.
aws cloudfront delete-distribution --id {{distribution-id}} \
    --if-match "$UPDATE_ETAG"

# Fetch the OAC ETag, then delete the OAC.
OAC_ETAG=$(aws cloudfront get-origin-access-control --id {{oac-id}} \
    --query 'ETag' --output text)
aws cloudfront delete-origin-access-control --id {{oac-id}} \
    --if-match "$OAC_ETAG"
aws s3control delete-access-point-policy \
    --account-id {{account-id}} --name {{access-point-name}}
aws s3 rm "s3://{{access-point-alias}}/content/sintel/" --recursive
```