您可以通过函数计算控制台、SDK或Serverless Devs来体验GPU实例的最佳实践。本文以Python语言为例,说明如何使用Serverless Devs开发工具,将原始视频经过函数代码的转码处理,从.mp4转换为.flv格式。

应用场景和优势

随着越来越多的强交互应用场景的出现,例如社交直播、在线课堂以及远程医疗等,互联网流量正在向实时、准实时的趋势演进。视频平台通常要将原始视频内容根据码率、分辨率、渠道贴片、播放平台等维度,以1∶N的方式转码输出多种分发视频格式,以服务不同网络质量、各种播放平台的观看者。视频转码是视频生产分发中的关键一环,理想的视频转码解决方案需要在成本(人民币/流)和功率效率(瓦/流)方面具有成本效益。

在不同的应用场景下,函数计算提供的GPU实例与CPU相比所具备的优势如下:

  • 实时、准实时的应用场景

    提供数倍于CPU的转码效率,从而快速将生产内容推向终端用户。

  • 成本优先的GPU应用场景

    提供弹性预留模式,从而按需为您保留工作GPU实例,对比自购VM拥有较大成本优势。

  • 效率优先的GPU应用场景

    屏蔽运维GPU集群的繁重负担(驱动/CUDA版本管理、机器运行管理、GPU坏卡管理),使得开发者专注于代码开发、聚焦业务目标的达成。

GPU实例的更多信息,请参见实例类型及使用模式

性能对比

函数计算GPU实例基于的Turing架构支持以下编码和解码格式:

  • 编码格式
    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 HEVC10-bitsupport HEVCB Framesupport
  • 解码格式
    MPEG-1 MPEG-2 VC-1 VP8 VP9 H.264 (AVCHD) H.265 (HEVC) 4:2:0 *H.265 (HEVC) 4:4:4
    8 bit 10 bit 12 bit 8 bit 10 bit 12 bit 8 bit 10 bit 12 bit

原始视频信息如下表所示。

视频信息 数据
时长 2分05秒
码率 4085 Kb/s
视频流信息 h264 (High), yuv420p (progressive), 1920x1080 [SAR 1:1 DAR 16:9], 25 fps, 25 tbr, 1k tbn, 50 tbc
音视频信息 aac (LC), 44100 Hz, stereo, fltp

CPU和GPU的测试机器指标如下表所示。

对比项 CPU机型 GPU机型
CPU CPU Xeon® Platinum 8163 4C CPU Xeon® Platinum 8163 4C
RAM 16 GB 16 GB
GPU N/A T4
FFmpeg git-2020-08-12-1201687 git-2020-08-12-1201687

视频转码(1∶1)

性能测试:1路输入、1路输出

分辨率 CPU转码耗时 GPU转码耗时
H264 : 1920x1080 (1080p) (Full HD) 3分19.331秒 0分9.399秒
H264 : 1280x720 (720p) (Half HD) 2分3.708秒 0分5.791秒
H264 : 640x480 (480p) 1分1.018秒 0分5.753秒
H264 : 480x360 (360p) 44.376秒 0分5.749秒

视频转码(1∶N)

性能测试:1路输入、3路输出

分辨率 CPU转码耗时 GPU转码耗时
H264 : 1920x1080 (1080p) (Full HD) 5分58.696秒 0分45.268秒
H264 : 1280x720 (720p) (Half HD)
H264 : 640x480 (480p)

转码命令

  • CPU转码命令
    • 单路转码(1∶1)
      docker run --rm -it --volume $PWD:/workspace --runtime=nvidia willprice/nvidia-ffmpeg -y -i input.mp4 -c:v libx264 -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 libx264 -vf scale=1920:1080 -b:v 5M output_1080.mp4 \
      -c:a copy -c:v libx264 -vf scale=1280:720 -b:v 5M output_720.mp4 \
      -c:a copy -c:v libx264 -vf scale=640:480 -b:v 5M output_480.mp4
    表 1. 参数说明
    参数 说明
    -c:a copy 无需任何重新编码即可复制音频流。
    -c:v h264 为输出流选择软件H.264编码器。
    -b:v 5M 将输出比特率设置为5 Mb/s。
  • GPU转码命令
    • 单路转码(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
    表 2. 参数说明
    参数 说明
    -hwaccel cuda 选择合适的硬件加速器。
    -hwaccel_output_format cuda 将解码的帧保存在GPU内存中。
    -c:v h264_nvenc 选择NVIDIA硬件加速H.264编码器。

前提条件

  • 诚邀您参与测试GPU实例。请提交工单申请参与测试,并在工单中附上以下信息:
    • 组织名称,例如您所在的公司名称。
    • 您的阿里云账号ID。
    • 您期望使用GPU实例的地域,例如华南1(深圳)。
    • 联系方式,例如您的手机号、邮箱或钉钉账号等。
    • 您的镜像大小。
  • 在GPU实例所在地域,完成以下操作:
  • 编译FFmpeg。

    FFmpeg需要自行编译以使用GPU加速,编译方式如下:

  • 将需处理的音视频资源上传至在GPU实例所在地域的OSS Bucket中,且您对该Bucket中的文件有读写权限。具体步骤,请参见上传文件。权限相关说明,请参见修改存储空间读写权限

操作步骤

  1. 创建项目。
    s init devsapp/start-fc-custom-container-event-python3.9 -d fc-gpu-prj

    创建的项目目录如下所示。

    fc-gpu-prj
    ├── code
    │   ├── app.py        # 函数代码
    │   └── Dockerfile    # Dockerfile:将代码打包成镜像的Dockerfile
    ├── README.md
    └── s.yaml            # 项目配置:包含了镜像如何部署在函数计算
  2. 进入项目所在目录。
    cd fc-gpu-prj
  3. 按实际情况修改目录文件的参数配置。
    • 编辑s.yaml文件。

      YAML文件的参数详解,请参见YAML规范

      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
              memorySize: 16384
              gpuMemorySize: 8192
              instanceType: g1
              initializationTimeout: 30
              initializer: not-used
              runtime: custom-container
              customContainerConfig:
                image: registry.cn-shenzhen.aliyuncs.com/demo/gpu-transcoding_s:v0.1
              codeUri: ./code
    • 编辑app.py文件。

      示例如下:

      # -*- coding: utf-8 -*-
      # python2 and python3
      
      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 MyRequest(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)
      
              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 procedure(self):
              src_url = "https://your.domain/input.mp4"  # 需替换为您个人账号下的OSS,且您有可读写的权限。
              dst_url = "https://your.domain/output.flv" # 需替换为您个人账号下的OSS,且您有可读写的权限。
              src_path = "/tmp/input.mp4"
              dst_path = "/tmp/output.flv"
      
              enable_gpu = False
              if '?' in self.path:
                  self.queryString=urllib.parse.unquote(self.path.split('?',1)[1])
                  params=urllib.parse.parse_qs(self.queryString)
                  if "gpu" in params:
                      enable_gpu = True
              print("enable_gpu:", enable_gpu)
      
              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 do_GET(self):
              self.procedure()
      
          def do_POST(self):
              self.procedure()
      
      if __name__ == '__main__':
          host = ('0.0.0.0', 9000)
          server = HTTPServer(host, MyRequest)
          print("Starting server, listen at: %s:%s" % host)
          server.serve_forever()
    • 编辑Dockerfile文件。

      示例如下:

      FROM willprice/nvidia-ffmpeg
      WORKDIR /usr/src/app
      RUN apt-get update --fix-missing
      RUN apt-get install -y python3
      RUN apt-get install -y python3-pip
      #RUN pip install urllib2
      COPY . .
      ENTRYPOINT [ "python3", "-u", "/usr/src/app/app.py" ]
      EXPOSE 9000
  4. 构建镜像。
    s build --dockerfile ./code/Dockerfile
  5. 部署代码至函数计算
    s deploy
  6. 配置预留模式的实例。
    s provision put --target 1 --qualifier LATEST
  7. 查询预留模式的实例是否就绪。
    s provision get --qualifier LATEST

    如果查询到current参数为1,则说明GPU实例的预留模式已就绪,示例如下。

    [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:           
  8. 调用函数。
    s invoke

    调用成功后,预期的返回结果如下。

    [2021-12-14 08:45:28] [INFO] [S-CLI] - Start ...
    ========= FC invoke Logs begin =========
    ...
    ========= FC invoke Logs end =========
    
    FC Invoke Result:
    {"download_time": 8.382611989974976, "upload_time": 3.897656202316284, "result": "ok", "trans_time": 7.78890323638916}
    
    
    End of method: invoke

执行结果

您可通过在浏览器中访问以下域名,查看转码后的视频:
https://cri-zbtsehbrr8******-registry.oss-cn-shenzhen.aliyuncs.com/output.flv

本域名仅为示例,需以实际情况为准。