# VOD 深度剖析第 12 部分：在 AWS 上构建 VOD —— 服务、架构与成本

> 完整的 AWS VOD 参考：MediaConvert、MediaPackage、CloudFront、S3、Step Functions、SPEKE DRM 集成、Terraform IaC、真实成本拆解、常见陷阱，以及一份生产落地路线图。

- 作者: zhuermu
- 发布: 2026-05-10
- 网页版: https://zhuermu.com/blog/vod-deep-dive-part-12-building-vod-on-aws/

---
> *这是 [VOD 流媒体深度剖析](/blog/vod-deep-dive-part-1-video-fundamentals/) 系列的第 12 部分。*

---

## AWS 媒体服务家族

AWS 将其 VOD 与直播服务统一归于 **AWS Elemental** 品牌之下。核心四剑客：

| 服务 | 用途 |
|---------|---------|
| **AWS Elemental MediaConvert** | 基于文件的转码（离线、VOD） |
| **AWS Elemental MediaLive** | 实时编码（直播） |
| **AWS Elemental MediaPackage** | 流封装（HLS/DASH/CMAF）+ DRM |
| **AWS Elemental MediaTailor** | 服务端广告插入（SSAI） |

配套服务：

| 服务 | 用途 |
|---------|---------|
| **Amazon S3** | 对象存储 |
| **Amazon CloudFront** | CDN |
| **AWS Lambda** | 触发器、轻量处理 |
| **AWS Step Functions** | 工作流编排 |
| **Amazon DynamoDB** | 资产元数据存储 |
| **Amazon SNS / SQS** | 事件通知、异步队列 |
| **Amazon CloudWatch** | 监控与告警 |

---

## 参考架构

AWS 官方的 *Video on Demand on AWS* 解决方案：

```
┌──────────────┐                                        ┌──────────────┐
│   Viewer     │                                        │   Creator    │
│              │                                        │  (Uploader)  │
└──────────────┘                                        └──────────────┘
        ▲                                                      │
        │ ⑥ HTTPS                                              │ ① PutObject
        │                                                      ▼
┌──────────────┐                                        ┌──────────────┐
│  CloudFront  │                                        │  S3 Source   │
│    (CDN)     │                                        │  Bucket      │
└──────────────┘                                        └──────────────┘
        ▲                                                      │
        │ ⑤ origin fetch                                       │ ② EventBridge
        │                                                      ▼
┌──────────────┐     ┌──────────────┐                   ┌──────────────┐
│ MediaPackage │◄────│  S3 Dest     │◄──────────────────│    Step      │
│    -VOD      │     │  Bucket      │                   │  Functions   │
│ (packaging)  │     └──────────────┘                   └──────────────┘
└──────────────┘            ▲                                  │
        │                   │                                  ▼
        │ SPEKE             │                           ┌──────────────┐
        ▼                   └───────────────────────────│ MediaConvert │
┌──────────────┐                                        │ (Transcode)  │
│  DRM License │                                        └──────────────┘
│  Server      │                                               │
│  (EZDRM etc) │                                               ▼
└──────────────┘                                        ┌──────────────┐
                                                        │   Lambda     │
                                                        │ (post-proc,  │
                                                        │  notify)     │
                                                        └──────────────┘
```

**流程**：

1. 创作者将源文件上传到 S3 源桶
2. S3 事件通过 EventBridge 触发 Step Functions
3. Lambda 探测并校验文件
4. Step Functions 调用 MediaConvert 进行转码
5. 转码输出写入 S3 目标桶
6. MediaPackage-VOD 封装 HLS/DASH + DRM
7. CloudFront 作为 CDN 分发
8. Lambda 发送完成通知

---

## 分步实操

### 步骤 1：设置 S3 桶

```bash
# Source bucket (raw uploads)
aws s3 mb s3://my-vod-source --region us-east-1

# Destination bucket (transcoded output)
aws s3 mb s3://my-vod-dest --region us-east-1

# Enable EventBridge notifications
aws s3api put-bucket-notification-configuration \
  --bucket my-vod-source \
  --notification-configuration '{
    "EventBridgeConfiguration": {}
  }'
```

### 步骤 2：为 MediaConvert 创建 IAM 角色

MediaConvert 需要对 S3 的读写权限：

```bash
aws iam create-role --role-name MediaConvertRole \
  --assume-role-policy-document file://trust-policy.json

aws iam attach-role-policy --role-name MediaConvertRole \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess
```

### 步骤 3：MediaConvert 作业模板

```json
{
  "Role": "arn:aws:iam::123456789012:role/MediaConvertRole",
  "Settings": {
    "Inputs": [{
      "FileInput": "s3://my-vod-source/episode01.mov",
      "AudioSelectors": {
        "Audio Selector 1": {"DefaultSelection": "DEFAULT"}
      }
    }],
    "OutputGroups": [{
      "Name": "CMAF",
      "OutputGroupSettings": {
        "Type": "CMAF_GROUP_SETTINGS",
        "CmafGroupSettings": {
          "Destination": "s3://my-vod-dest/ep01/",
          "SegmentLength": 4,
          "FragmentLength": 2,
          "WriteHlsManifest": "ENABLED",
          "WriteDashManifest": "ENABLED"
        }
      },
      "Outputs": [
        {
          "NameModifier": "_1080p",
          "VideoDescription": {
            "Width": 1920, "Height": 1080,
            "CodecSettings": {
              "Codec": "H_264",
              "H264Settings": {
                "RateControlMode": "QVBR",
                "QvbrSettings": {"QvbrQualityLevel": 8},
                "MaxBitrate": 5000000,
                "GopSize": 2, "GopSizeUnits": "SECONDS"
              }
            }
          },
          "AudioDescriptions": [{
            "CodecSettings": {
              "Codec": "AAC",
              "AacSettings": {
                "Bitrate": 128000,
                "SampleRate": 48000,
                "CodingMode": "CODING_MODE_2_0"
              }
            }
          }]
        }
      ]
    }]
  }
}
```

**关键参数**：

- `QvbrQualityLevel: 8` —— QVBR 目标质量（1–10，越高越好）
- `MaxBitrate: 5000000` —— 峰值码率上限
- `GopSize: 2 SECONDS` —— 2 秒 GOP
- `SegmentLength: 4` —— 4 秒分片
- `FragmentLength: 2` —— 2 秒片段（CMAF 的子分片）

提交作业：

```bash
aws mediaconvert create-job --cli-input-json file://job.json \
  --endpoint-url https://abc.mediaconvert.us-east-1.amazonaws.com
```

### 步骤 4：通过 SPEKE 添加 DRM

在 CMAF OutputGroup 中加入：

```json
"Encryption": {
  "EncryptionMethod": "SAMPLE_AES",
  "SpekeKeyProvider": {
    "ResourceId": "episode01",
    "SystemIds": [
      "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed",
      "94ce86fb-07ff-4f43-adb8-93d2fa968ca2",
      "9a04f079-9840-4286-ab92-e65be0885f95"
    ],
    "Url": "https://speke.ezdrm.com/v2",
    "CertificateArn": "arn:aws:acm:..."
  }
}
```

三个 `SystemIds` 分别对应 Widevine、FairPlay 和 PlayReady。`Url` 指向 DRM 服务商的 SPEKE 端点。

### 步骤 5：CloudFront 分发

```bash
aws cloudfront create-distribution \
  --distribution-config file://cf-config.json
```

关键配置：

- **Origin**：S3 目标桶
- **Viewer Protocol Policy**：将 HTTP 重定向到 HTTPS
- **Allowed Methods**：GET、HEAD
- **Cache Policy**：针对未压缩对象优化
- **Origin Access Control**：启用（阻止直接访问 S3）
- **Signed URLs**：需要时启用 Trusted Key Groups

### 步骤 6：生成签名 URL

后端在播放时按需生成短时效的签名 URL：

```python
from datetime import datetime, timedelta
from botocore.signers import CloudFrontSigner
import rsa

def generate_signed_url(video_path, key_id, private_key, expires_in=3600):
    url = f"https://d1234.cloudfront.net/{video_path}"
    expires = datetime.utcnow() + timedelta(seconds=expires_in)

    def rsa_signer(message):
        return rsa.sign(message, private_key, 'SHA-1')

    signer = CloudFrontSigner(key_id, rsa_signer)
    return signer.generate_presigned_url(url, date_less_than=expires)
```

### 步骤 7：Step Functions 编排

用状态机把整条流水线串联起来（参见[第 11 部分](/blog/vod-deep-dive-part-11-end-to-end-workflow/)）。

---

## 基础设施即代码：Terraform

```hcl
# Source bucket
resource "aws_s3_bucket" "source" {
  bucket = "my-vod-source"
}

# Destination bucket
resource "aws_s3_bucket" "dest" {
  bucket = "my-vod-dest"
}

# EventBridge rule: trigger Step Functions on S3 upload
resource "aws_cloudwatch_event_rule" "upload" {
  name = "vod-upload-trigger"
  event_pattern = jsonencode({
    source      = ["aws.s3"]
    detail-type = ["Object Created"]
    detail = {
      bucket = { name = [aws_s3_bucket.source.bucket] }
    }
  })
}

# Step Functions state machine
resource "aws_sfn_state_machine" "vod_pipeline" {
  name       = "vod-pipeline"
  role_arn   = aws_iam_role.sfn_role.arn
  definition = file("${path.module}/vod_pipeline.asl.json")
}

resource "aws_cloudwatch_event_target" "sfn" {
  rule     = aws_cloudwatch_event_rule.upload.name
  arn      = aws_sfn_state_machine.vod_pipeline.arn
  role_arn = aws_iam_role.event_bridge_role.arn
}

# CloudFront distribution
resource "aws_cloudfront_distribution" "vod" {
  origin {
    domain_name = aws_s3_bucket.dest.bucket_regional_domain_name
    origin_id   = "s3-vod-dest"
    s3_origin_config {
      origin_access_identity = aws_cloudfront_origin_access_identity.vod.cloudfront_access_identity_path
    }
  }

  enabled             = true
  default_root_object = ""

  default_cache_behavior {
    allowed_methods        = ["GET", "HEAD"]
    cached_methods         = ["GET", "HEAD"]
    target_origin_id       = "s3-vod-dest"
    viewer_protocol_policy = "redirect-to-https"
    cache_policy_id        = data.aws_cloudfront_cache_policy.caching_optimized.id
    trusted_key_groups     = [aws_cloudfront_key_group.vod.id]
  }

  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }

  viewer_certificate {
    cloudfront_default_certificate = true
  }
}
```

---

## 成本拆解

### MediaConvert 转码

按 **输出分钟数 x 编码复杂度** 计费：

| 档位 | 价格（us-east-1） |
|------|--------------------|
| Basic SD（< 30fps、< 720p、AVC） | $0.0075 / 输出分钟 |
| Basic HD（< 30fps、720p–1080p、AVC） | $0.015 / 输出分钟 |
| **Pro HD**（60fps 或 HEVC 或 HDR） | **$0.030 / 输出分钟** |
| Pro UHD（4K+） | $0.060 / 输出分钟 |
| AV1 | 约 2–3 倍溢价 |

**示例 —— 90 分钟电影，6 档输出（4 档 H.264 + 2 档 H.265）**：

```
90 min x 4 tiers x $0.015 = $5.40 (H.264)
90 min x 2 tiers x $0.030 = $5.40 (H.265)
Total transcode cost ≈ $10.80 per movie
```

### S3 存储

- 标准存储：$0.023/GB/月（us-east-1）
- 单部电影的全部版本：约 3 GB → $0.07/月
- 大型片库（10 万小时）：约 1 PB → **$23,000/月**
- 冷内容放到 S3 Glacier 可节省约 80%

### CloudFront 带宽

按区域为 $0.005–$0.08/GB，并有分档量折扣。完整成本模型见[第 7 部分](/blog/vod-deep-dive-part-7-cdn-distribution/)。

### MediaPackage JIT 封装

约 $0.065/GB 源流量。对于频繁访问的热门内容更划算。

---

## 月度账单估算（2M DAU 平台）

假设：2M DAU、日均观看 30 分钟、720p 主码流。

| 类别 | 月度成本区间 |
|----------|--------------------|
| S3 存储（10 PB） | $50K–$150K |
| MediaConvert（每天 100 集新内容） | $1K–$5K |
| CloudFront（24 PB/月） | $400K–$800K |
| MediaPackage JIT | $50K–$150K |
| Lambda + Step Functions | $5K–$20K |
| DynamoDB + RDS | $10K–$30K |
| DRM SaaS | $20K–$80K |
| **合计** | **$500K–$1.2M** |

CDN 带宽是成本的主导项。三个最有效的降本抓手：更优的编解码器、按标题编码（per-title encoding），以及多 CDN 竞价。

---

## 跨云对应关系

| AWS | GCP | Azure |
|-----|-----|-------|
| S3 | GCS | Blob Storage |
| MediaConvert | Transcoder API | Media Services |
| MediaPackage | Media CDN | Media Services DRM |
| CloudFront | Cloud CDN / Media CDN | Azure CDN |
| Step Functions | Workflows | Durable Functions |
| Lambda | Cloud Functions | Azure Functions |

**选云建议**：

- 全球受众、主要在海外：**AWS**（覆盖最广）
- 中国大陆：**阿里云 / 腾讯云**
- 多地区：多云分发

---

## 常见陷阱

### CloudFront 签名 URL 缓存命中率跌到 0%

**原因**：签名参数（Signature、Expires、Key-Pair-Id）被计入了缓存键。

**修复**：将 Cache Policy 配置为仅以路径作为缓存键。签名查询字符串应从缓存键中 **排除**。

### MediaConvert 输出文件异常庞大

**原因**：未设置 QVBR 或 MaxBitrate。CRF 过低，导致峰值码率失控暴涨。

**修复**：设置 `QvbrQualityLevel: 7-8` + `MaxBitrate` 约束。

### iOS 上 HLS 卡在加载中

**原因**：主播放列表（Master Playlist）缺少 `CODECS` 属性。

**修复**：确保每个 `EXT-X-STREAM-INF` 都包含 `CODECS="avc1.xxxxxx,mp4a.40.2"`。

### 跨境分发延迟高

**原因**：CloudFront 回源到 us-east-1 的 S3 —— 对亚洲用户延迟很高。

**修复**：在源站附近启用 **CloudFront Origin Shield**，或使用 S3 跨区域复制，让 CloudFront 从最近的区域回源。

---

## 生产落地路线图：从零到上线

### 第 1 周：最小可行 Demo

- 开通 AWS 账户，启用 S3、MediaConvert、CloudFront
- 手动运行一个 MediaConvert 作业，产出 HLS 输出
- 在 Safari 中打开 m3u8 —— 能够播放

### 第 2–4 周：自动化流水线

- Step Functions：S3 上传 → MediaConvert → S3 → CloudFront
- 后端生成签名 URL
- iOS/Android 应用集成播放器（AVPlayer / ExoPlayer）

### 第 2–3 个月：生产级特性

- 多档码率梯度 + 按标题编码（per-title encoding）
- DRM 集成（EZDRM / PallyCon）
- 通过 Mux 或自建流水线采集 QoE 数据
- 多 CDN 路由

### 第 4–6 个月：规模化

- H.265 / AV1 档位
- LL-HLS（若加入直播）
- 多地区部署 + 跨区域复制
- 成本优化（Spot 实例、Glacier 冷归档）

---

## 关键要点

1. AWS 核心四剑客：**MediaConvert + MediaPackage + CloudFront + S3**。
2. 用 **Step Functions** 做编排，用 Lambda 做轻量处理。
3. 用 **SPEKE** 对接托管 DRM 服务。
4. 生产环境应采用 **Terraform 进行声明式基础设施管理**。
5. **CDN 带宽是主导成本** —— 其次是存储，转码相对便宜。
6. 每个主流云都有对应服务 —— 根据你的市场做选择。
7. 从 MVP 到生产：通常需要 **2–6 个月**。

---

*上一篇：* [第 11 部分：端到端工作流](/blog/vod-deep-dive-part-11-end-to-end-workflow/)

*至此，VOD 流媒体深度剖析系列全部完结。如果还没读过开篇，请从[第 1 部分：视频基础](/blog/vod-deep-dive-part-1-video-fundamentals/)开始。*

---

## 常见问题

### 在 AWS 上搭视频点播平台需要用到哪些服务？

核心四剑客：MediaConvert（文件转码）、MediaPackage（HLS/DASH/CMAF 封装 + DRM）、CloudFront（CDN 分发）和 S3（存储）。配套服务包括 Step Functions 做工作流编排、Lambda 做触发和轻量处理、DynamoDB 存资产元数据、SNS/SQS 做事件通知、CloudWatch 做监控告警。如果要做直播用 MediaLive，服务端广告插入用 MediaTailor。

### 在 AWS 上跑一个视频平台一个月要花多少钱？

以 2M DAU、日均观看 30 分钟、720p 主码流的平台估算，月账单约 $500K–$1.2M。其中 CloudFront CDN 带宽是大头（$400K–$800K），其次是 S3 存储（10 PB 约 $50K–$150K）和 MediaPackage JIT 封装；转码反而很便宜——一部 90 分钟电影 6 档输出在 MediaConvert 上约 $10.80。三个最有效的降本抓手：更优的编解码器、按标题编码和多 CDN 竞价。

### CloudFront 用了签名 URL 后缓存命中率变成 0% 是怎么回事？

因为签名参数（Signature、Expires、Key-Pair-Id）被计入了缓存键——每个用户的 URL 都不一样，缓存自然永远命中不了。解决办法是把 Cache Policy 配置成仅以路径作为缓存键，把签名查询字符串从缓存键中排除。CloudFront 依然会校验签名，只是不再因签名参数把缓存打碎。


---

## 参考资料

- [AWS Elemental MediaConvert User Guide](https://docs.aws.amazon.com/mediaconvert/latest/ug/what-is.html) — AWS Documentation
- [AWS Elemental MediaPackage User Guide](https://docs.aws.amazon.com/mediapackage/latest/ug/what-is.html) — AWS Documentation
- [Amazon CloudFront Developer Guide](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Introduction.html) — AWS Documentation
- [Secure Packager and Encoder Key Exchange (SPEKE)](https://docs.aws.amazon.com/speke/latest/documentation/what-is-speke.html) — AWS Documentation
