Function Compute の GPU インスタンスを使用すると、GPU ドライバー、CUDA バージョン、ハードウェアクラスターの管理を一切行わずに、FFmpeg を用いた大規模な動画トランスコーディングを実行できます。本ガイドでは、Serverless Devs と Python を使用して GPU インスタンス対応のトランスコーディング関数をデプロイする手順を説明します。例として MP4 から FLV への変換を紹介します。
GPU アクセラレーションを利用するタイミング
トランスコーディングジョブの実行時間が数分以上に及ぶ場合、または入力 1 件に対して複数の出力ストリームを生成する必要がある場合に、GPU アクセラレーションの効果が最も高まります。具体的なユースケースは以下のとおりです。
1080p 以上の解像度でのトランスコーディング:2 分間の 1080p H.264 動画の場合、CPU では 3 分以上かかりますが、GPU を使用すると 10 秒未満で完了します(約 20 倍の高速化)。
マルチストリーム出力(1:N):3 種類の解像度を同時に出力する場合、CPU では約 6 分かかりますが、GPU を使用すると約 45 秒で完了します。
レイテンシに敏感なパイプライン:ソーシャルライブ配信、オンライン授業、遠隔医療など、リアルタイムまたはニアリアルタイムでの配信が求められるシナリオです。
CPU で 1 分未満で完了するジョブについては、GPU インスタンスのプロビジョニングにかかるオーバーヘッドが、速度向上によるメリットを上回る可能性があります。
サポートされるコーデックフォーマット
GPU インスタンスは Turing アーキテクチャをベースとし、NVIDIA T4 GPU を採用しています。
エンコーディング
| フォーマット |
|---|
| H.264 (AVCHD) YUV 4:2:0 |
| H.264 (AVCHD) YUV 4:4:4 |
| H.264 (AVCHD) Lossless |
| H.265 (HEVC) 4K YUV 4:2:0 |
| H.265 (HEVC) 4K YUV 4:4:4 |
| H.265 (HEVC) 4K Lossless |
| H.265 (HEVC) 8K |
| HEVC 10-bit |
| HEVC B-frame |
デコーディング
| フォーマット | ビット深度 |
|---|---|
| MPEG-1 | 8-bit |
| MPEG-2 | 8-bit |
| VC-1 | 8-bit |
| VP8 | 8-bit |
| VP9 | 8-bit、10-bit、12-bit |
| H.264 (AVCHD) | 8-bit、10-bit、12-bit |
| H.265 (HEVC) 4:2:0 | 8-bit、10-bit、12-bit |
| *H.265 (HEVC) 4:4:4 | 8-bit、10-bit、12-bit |
パフォーマンスベンチマーク
以下のテストでは、2 分 5 秒の H.264 ソース動画(1920×1080、25 fps、4085 Kb/s;音声:aac (LC)、44100 Hz、ステレオ、fltp)を、CPU が同一(Xeon® Platinum 8163 4C、16 GB RAM)で、T4 GPU 搭載機と非搭載機で比較しました。FFmpeg のバージョンは git-2020-08-12-1201687 です。
1:1 トランスコーディング(入力 1 件、出力 1 件)
| 解像度 | GPU なし | GPU あり |
|---|---|---|
| H264 1920×1080(フル HD、1080p) | 3 分 19.3 秒 | 9.4 秒 |
| H264 1280×720(HD、720p) | 2 分 3.7 秒 | 5.8 秒 |
| H264 640×480(480p) | 1 分 1.0 秒 | 5.8 秒 |
| H264 480×360(360p) | 44.4 秒 | 5.7 秒 |
1:N トランスコーディング(入力 1 件、出力 3 件:1080p + 720p + 480p)
| GPU なし | GPU あり |
|---|---|
| 5 分 58.7 秒 | 45.3 秒 |
仕組み
この関数は、NVIDIA 対応 FFmpeg イメージをベースとしたカスタムコンテナ内で実行されます。呼び出されると、以下の処理を行います。
Object Storage Service (OSS) バケットからソース動画をダウンロードします。
FFmpeg を実行して動画をトランスコードします。GPU モードでは、FFmpeg が CUDA を使用して NVIDIA ハードウェア上でデコードおよびエンコードをオフロードします。
-hwaccel cuda -hwaccel_output_format cudaフラグにより、デコード済みフレームが GPU メモリ内に保持され、エンコード前の CPU-GPU 間転送が回避されます。h264_nvencフラグにより、FFmpeg はソフトウェアエンコーダーではなく、NVIDIA ハードウェアエンコーダーを H.264 出力に使用します。これらのフラグを指定しない場合、FFmpeg は CPU 上のソフトウェアエンコーディングにフォールバックします。トランスコード済みの出力を OSS にアップロードします。
前提条件
開始する前に、以下の条件を満たしていることを確認してください。
DingTalk グループへの参加(グループ ID:11721331)。参加には、組織名、Alibaba Cloud アカウント ID、対象リージョン(例:中国 (深セン))、連絡先情報をご提供ください。
GPU インスタンスと同じリージョンにある Container Registry Enterprise Edition インスタンス。 Personal Edition も使用できますが、Enterprise Edition を推奨します。 詳細については、「Container Registry Enterprise Edition インスタンスを作成する」をご参照ください。
そのインスタンス内の名前空間およびイメージリポジトリ。Container Registry ドキュメントの「名前空間の作成」をご参照ください。その後、同じインスタンスに「イメージリポジトリ」を作成します。
NVIDIA GPU 対応でコンパイルされた FFmpeg バイナリを準備します。以下のいずれかを選択してください。
(推奨)事前構築済み Docker イメージ:willprice/nvidia-ffmpeg。あるいは jrottenberg/ffmpeg を使用することもできます。
手動でコンパイル — 「NVIDIA FFmpeg コンパイルガイド」をご参照ください。
オーディオおよびビデオファイルは、同一リージョン内の OSS バケットにアップロードされます。この関数には、バケットのオブジェクトに対する読み取りおよび書き込みアクセスが必要です。アップロード手順については、「オブジェクトのアップロード」をご参照ください。また、権限設定については、「バケットの ACL の変更」をご参照ください。
FFmpeg トランスコーディングコマンド
以下のすべての例では、willprice/nvidia-ffmpeg Docker イメージを使用します。
GPU アクセラレーションなし
単一出力(1:1)
docker run --rm -it --volume $PWD:/workspace --runtime=nvidia willprice/nvidia-ffmpeg \
-y -i input.mp4 -c:v h264 -vf scale=1920:1080 -b:v 5M output.mp4複数出力(1:N)
docker run --rm -it --volume $PWD:/workspace --runtime=nvidia willprice/nvidia-ffmpeg \
-y -i input.mp4 \
-c:a copy -c:v h264 -vf scale=1920:1080 -b:v 5M output_1080.mp4 \
-c:a copy -c:v h264 -vf scale=1280:720 -b:v 5M output_720.mp4 \
-c:a copy -c:v h264 -vf scale=640:480 -b:v 5M output_480.mp4GPU アクセラレーションあり
単一出力(1:1)
docker run --rm -it --volume $PWD:/workspace --runtime=nvidia willprice/nvidia-ffmpeg \
-y -hwaccel cuda -hwaccel_output_format cuda \
-i input.mp4 -c:v h264_nvenc -vf scale_cuda=1920:1080:1:4 -b:v 5M output.mp4複数出力(1:N)
docker run --rm -it --volume $PWD:/workspace --runtime=nvidia willprice/nvidia-ffmpeg \
-y -hwaccel cuda -hwaccel_output_format cuda -i input.mp4 \
-c:a copy -c:v h264_nvenc -vf scale_npp=1920:1080 -b:v 5M output_1080.mp4 \
-c:a copy -c:v h264_nvenc -vf scale_npp=1280:720 -b:v 5M output_720.mp4 \
-c:a copy -c:v h264_nvenc -vf scale_npp=640:480 -b:v 5M output_480.mp4主なパラメーター
| パラメーター | 説明 |
|---|---|
-hwaccel cuda | デコードに CUDA ハードウェアアクセラレータを選択します |
-hwaccel_output_format cuda | デコード済みフレームを GPU メモリ内に保持し、エンコード前の CPU-GPU 間転送を回避します |
-c:v h264_nvenc | NVIDIA ハードウェア H.264 エンコーダーを使用します |
-c:a copy | 音声ストリームを再エンコードせずにコピーします |
-b:v 5M | 出力ビットレートを 5 Mb/s に設定します |
Serverless Devs を使用した GPU 関数のデプロイ
開始する前に、Serverless Devs と Docker をインストールし、次に手順に従って認証情報を使用して Serverless Devs を設定してください。
操作手順
プロジェクトを作成します。
fc-gpu-prj ├── code │ ├── app.py # 関数コード │ └── Dockerfile # イメージのDockerfile ├── README.md └── s.yaml # デプロイ構成生成されるプロジェクト構造は以下のとおりです。
fc-gpu-prj ├── code │ ├── app.py # 関数コード │ └── Dockerfile # イメージの Dockerfile ├── README.md └── s.yaml # デプロイメント構成プロジェクトディレクトリに移動します。
cd fc-gpu-prj構成ファイルを編集します。s.yaml — リージョン、サービス名、コンテナイメージ URI をご環境に合わせて更新します。主要な GPU 関連パラメーターは
instanceType: fc.gpu.tesla.1およびgpuMemorySize: 8192です。edition: 1.0.0 name: container-demo access: default vars: region: cn-shenzhen services: customContainer-demo: component: devsapp/fc props: region: ${vars.region} service: name: tgpu_ffmpeg_service internetAccess: true function: name: tgpu_ffmpeg_func description: test gpu for ffmpeg handler: not-used timeout: 600 caPort: 9000 instanceType: fc.gpu.tesla.1 gpuMemorySize: 8192 cpu: 4 memorySize: 16384 diskSize: 512 runtime: custom-container customContainerConfig: # 前提条件: # 1. Container Registry で「demo」という名前空間および「gpu-transcoding_s」というイメージリポジトリを作成します。 # 2. 更新および再デプロイ時にタグをインクリメントします(例:v0.1 → v0.2)。 image: registry.cn-shanghai.aliyuncs.com/demo/gpu-transcoding_s:v0.1 codeUri: ./codeYAML パラメーターの完全な一覧については、「YAML 仕様」をご参照ください。app.py — この関数は、OSS からソース動画をダウンロードし、FFmpeg を使用してトランスコードし(
TRANS-MODEヘッダーに基づいて GPU または CPU を使用)、結果を OSS に再アップロードします。src_urlおよびdst_urlのプレースホルダー値を、実際の OSS オブジェクト URL に置き換えてください。# -*- coding: utf-8 -*- from __future__ import print_function from http.server import HTTPServer, BaseHTTPRequestHandler import json import sys import logging import os import time import urllib.request import subprocess class Resquest(BaseHTTPRequestHandler): def download(self, url, path): print("enter download:", url) f = urllib.request.urlopen(url) with open(path, "wb") as local_file: local_file.write(f.read()) def upload(self, url, path): print("enter upload:", url) headers = { 'Content-Type': 'application/octet-stream', 'Content-Length': os.stat(path).st_size, } req = urllib.request.Request(url, open(path, 'rb'), headers=headers, method='PUT') urllib.request.urlopen(req) def trans(self, input_path, output_path, enable_gpu): print("enter trans input:", input_path, " output:", output_path, " enable_gpu:", enable_gpu) cmd = ['ffmpeg', '-y', '-i', input_path, "-c:a", "copy", "-c:v", "h264", "-b:v", "5M", output_path] if enable_gpu: cmd = ["ffmpeg", "-y", "-hwaccel", "cuda", "-hwaccel_output_format", "cuda", "-i", input_path, "-c:v", "h264_nvenc", "-b:v", "5M", output_path] try: subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True) except subprocess.CalledProcessError as exc: print('\nreturncode:{}'.format(exc.returncode)) print('\ncmd:{}'.format(exc.cmd)) print('\noutput:{}'.format(exc.output)) print('\nstderr:{}'.format(exc.stderr)) print('\nstdout:{}'.format(exc.stdout)) def trans_wrapper(self, enable_gpu): src_url = "https://your.domain/input.mp4" # 実際の OSS オブジェクト URL(読み取りアクセス権限が必要)に置き換えてください dst_url = "https://your.domain/output.flv" # 実際の OSS オブジェクト URL(書き込みアクセス権限が必要)に置き換えてください src_path = "/tmp/input_c.flv" dst_path = "/tmp/output_c.mp4" if enable_gpu: src_url = "https://your.domain/input.mp4" # 実際の OSS オブジェクト URL(読み取りアクセス権限が必要)に置き換えてください dst_url = "https://your.domain/output.flv" # 実際の OSS オブジェクト URL(書き込みアクセス権限が必要)に置き換えてください src_path = "/tmp/input_g.flv" dst_path = "/tmp/output_g.mp4" local_time = time.time() self.download(src_url, src_path) download_time = time.time() - local_time local_time = time.time() self.trans(src_path, dst_path, enable_gpu) trans_time = time.time() - local_time local_time = time.time() self.upload(dst_url, dst_path) upload_time = time.time() - local_time data = {'result':'ok', 'download_time':download_time, 'trans_time':trans_time, 'upload_time':upload_time} self.send_response(200) self.send_header('Content-type', 'application/json') self.end_headers() self.wfile.write(json.dumps(data).encode()) def pong(self): data = {"function":"trans_gpu"} self.send_response(200) self.send_header('Content-type', 'application/json') self.end_headers() self.wfile.write(json.dumps(data).encode()) def dispatch(self): mode = self.headers.get('TRANS-MODE') if mode == "ping": self.pong() elif mode == "gpu": self.trans_wrapper(True) elif mode == "cpu": self.trans_wrapper(False) else: self.pong() def do_GET(self): self.dispatch() def do_POST(self): self.dispatch() if __name__ == '__main__': host = ('0.0.0.0', 9000) server = HTTPServer(host, Resquest) print("Starting server, listen at: %s:%s" % host) server.serve_forever()Dockerfile — NVIDIA FFmpeg ベースイメージからコンテナイメージを構築し、Python ランタイムを追加します。
FROM registry.cn-shanghai.aliyuncs.com/serverless_devs/nvidia-ffmpeg:latest WORKDIR /usr/src/app RUN apt-get update --fix-missing RUN apt-get install -y python3 RUN apt-get install -y python3-pip COPY . . ENTRYPOINT [ "python3", "-u", "/usr/src/app/app.py" ] EXPOSE 9000コンテナイメージをビルドします。
s build --dockerfile ./code/DockerfileFunction Compute にデプロイします。
s deployを同じサービス名および関数名で再度実行する場合は、まずuse localを実行して、ローカル構成を使用してください。s deployコールドスタートによる遅延を回避し、GPU 関数が即座にリクエストを処理できるよう、プロビジョニング済みインスタンスを作成します。
s provision put --target 1 --qualifier LATESTプロビジョニング済みインスタンスが準備完了していることを確認します。
s provision get --qualifier LATESTcurrentの値が1になった時点で、インスタンスは準備完了です。出力例:[2021-12-14 08:45:24] [INFO] [S-CLI] - Start ... [2021-12-14 08:45:24] [INFO] [FC] - Getting provision: tgpu_ffmpeg_service.LATEST/tgpu_ffmpeg_func customContainer-demo: serviceName: tgpu_ffmpeg_service functionName: tgpu_ffmpeg_func qualifier: LATEST resource: 188077086902****#tgpu_ffmpeg_service#LATEST#tgpu_ffmpeg_func target: 1 current: 1 scheduledActions: (empty array) targetTrackingPolicies: (empty array) currentError:関数を呼び出します。デプロイ済みのバージョンを確認します。
s invokeCPU によるトランスコーディング:
s invoke -e '{"method":"GET","headers":{"TRANS-MODE":"cpu"}}'GPU によるトランスコーディング:
s invoke -e '{"method":"GET","headers":{"TRANS-MODE":"gpu"}}'作業が完了したら、課金を停止するためにプロビジョニング済みインスタンスを解放します。
s provision put --target 0 --qualifier LATEST
コンソールからの GPU 関数のデプロイ
コンテナイメージの準備
Container Registry Enterprise Edition インスタンス(推奨)または Personal Edition インスタンスを作成します。詳細については、「Container Registry Enterprise Edition インスタンスの作成」をご参照ください。
インスタンスに名前空間とイメージリポジトリを作成します。Container Registry ドキュメントの名前空間の作成をご参照ください。次に、手順に従って同じインスタンスにイメージリポジトリを作成します。
Container Registry コンソール の Docker 指示に従い、Serverless Devs の
/codeディレクトリにあるapp.pyおよびDockerfileをイメージリポジトリにプッシュします。

サービスを作成する。「サービスを作成する」のセクションをご参照ください。
関数を作成します。 「カスタムコンテナ関数を作成する」をご参照ください。
インスタンスタイプ を GPU インスタンス に、リクエストハンドラータイプ を HTTP リクエストの処理 に設定します。
実行タイムアウト期間の延長。2 分間の動画の CPU トランスコーディングには 100 秒以上かかるため、デフォルトの 60 秒のタイムアウト期間では不十分です。
関数を検索し、構成 をクリックします(アクション 列)。
環境情報 セクションで、実行タイムアウト期間 を延長し、保存 をクリックします。

プロビジョニング済み GPU インスタンスの構成。作成後にルール一覧を確認し、現在の予約済みインスタンス数 が目標数と一致していることを確認します。
関数の詳細ページで、自動スケーリング タブをクリックし、ルールの作成 をクリックします。
GPU インスタンスのプロビジョニングに必要なパラメーターを構成し、作成 をクリックします。「自動スケーリングルールの構成」で詳細をご確認ください。

cURL を使用した関数のテスト
関数の詳細ページで、トリガー タブをクリックし、トリガーエンドポイントを取得します。
以下のコマンドを実行します。
デプロイ済みのバージョンを確認:
curl -v "https://tgpu-ff-console-tgpu-ff-console-ajezot****.cn-shenzhen.fcapp.run" {"function": "trans_gpu"}CPU によるトランスコーディング:
curl "https://tgpu-ff-console-tgpu-ff-console-ajezot****.cn-shenzhen.fcapp.run" -H 'TRANS-MODE: cpu' {"result": "ok", "upload_time": 8.75510573387146, "download_time": 4.910430669784546, "trans_time": 105.37688875198364}GPU によるトランスコーディング:
curl "https://tgpu-ff-console-tgpu-ff-console-ajezotchpx.cn-shenzhen.fcapp.run" -H 'TRANS-MODE: gpu' {"result": "ok", "upload_time": 8.313958644866943, "download_time": 5.096682548522949, "trans_time": 8.72346019744873}
結果
OSS ドメイン名を使用して、ブラウザからトランスコード済みの出力にアクセスします。例:
https://cri-zbtsehbrr8******-registry.oss-cn-shenzhen.aliyuncs.com/output.flvドメイン名を実際の OSS エンドポイントに置き換えてください。
次のステップ
インスタンスタイプとインスタンスモード — GPU インスタンスのオプションについて詳細を確認する
プロビジョニング済みインスタンスおよび自動スケーリングルールの構成 — GPU 関数のコールドスタート動作を最適化します