全部產品
Search
文件中心

Function Compute:構建基於Serverless架構的彈性高可用音視頻處理系統

更新時間:Jul 06, 2024

在音視頻系統中,音視頻轉碼是比較消耗計算力的一個子系統,您可以通過Function ComputeServerless 工作流程構建彈性高可用的Serverless音視頻處理系統。本文會從工程效率、營運、效能和成本方面介紹Serverless音視頻處理系統和傳統方案的差異。同時介紹如何搭建並體驗Serverless音視頻處理系統。

背景資訊

針對音視頻轉碼,雖然您可以使用雲上專門的轉碼服務,但在以下情境,您仍然會選擇自己搭建轉碼服務。

  • Auto Scaling訴求

    • 需要更彈性的視頻處理服務。

      例如,您已經在虛擬機器或容器平台上基於FFmpeg部署了一套視頻處理服務,但想在此基礎上實現更彈性、更高可用性的服務。

  • 工程效率訴求

    • 需要平行處理多個視頻檔案。

    • 需要批量快速處理多個超大的視頻。

      例如,每周五定時產生幾百個4 GB以上1080P的大視頻,需要幾小時內處理完。

  • 自訂處理訴求

    • 需要處理更進階的自訂處理需求。

      例如,視頻轉碼完成後,需要將轉碼詳情記錄到資料庫,或自動將熱度很高的視頻預熱到CDN上緩解來源站點壓力。

    • 需要轉換音頻格式、自訂採樣率或音頻降噪等。

    • 需要直接讀取源檔案進行處理。

      例如,當您的視頻源檔案存放在NAS或ECS雲端硬碟上時,您需要自建服務直接讀取源檔案進行處理,而不需要將它們再次遷移至OSS。

    • 需要將視頻轉換為其他格式,然後在此基礎上增加其他新的需求。

      例如,將視頻進行轉碼、加浮水印和產生視頻首頁的GIF,然後在此基礎上增加新的需求,例如調整轉碼參數、發布新功能時對線上服務無影響。

  • 成本訴求

    • 需要簡單的轉碼或較輕量的需求時。

      例如,擷取OSS上視頻前幾幀的GIF、擷取音視頻的時間長度,此時您自己搭建的成本會更低。

針對自己搭建轉碼服務的方案有傳統自建方案和Serverless方案,本文會介紹這兩類方案的差異及Serverless方案具體的操作流程。

解決方案

傳統自建方案

隨著電腦技術和網路的發展,雲端運算廠商的產品在不斷成熟完善。如果想要搭建音視頻處理系統,可以直接上雲購買ECS伺服器部署服務,通過OSS、CDN等技術完成音視頻的儲存和音視頻的播放加速。

Serverless方案

簡單視頻處理系統

如果您只需要對視頻進行簡單的處理,方案架構圖如下。

當使用者上傳一個視頻到OSS時,OSS觸發器自動觸發函數執行,函數調用FFmpeg進行視頻轉碼,並且將轉碼後的視頻儲存回OSS。

關於簡單視頻處理系統的Demo及操作步驟,請參見簡單視頻處理系統

視頻處理工作流程系統

如果您需要加快大視頻的轉碼速度或者完成各種複雜的組合操作,可以通過Serverless 工作流程編排函數實現功能強大的視頻處理系統。方案架構圖如下。

當使用者上傳一個MP4格式的視頻到OSS,OSS觸發器自動觸發函數執行,然後函數調用Serverless 工作流程進行一種或多種格式的轉碼。通過該方案可以實現以下需求:

  • 一個視頻檔案可以同時被轉碼成各種格式和進行其他自訂處理,例如增加浮水印或在處理後更新資訊到資料庫等。

  • 當有多個檔案同時上傳到OSS時,Function Compute會自動調整,平行處理多個檔案,同時將檔案並行轉碼成多種格式。

  • 結合NAS和視頻切片,可以解決超大視頻的轉碼問題。對於每個視頻,都需先進行切片處理,然後並行轉碼切片,最後合成。通過設定合理的切片時間,可以提高較大視頻的轉碼速度。

    說明

    視頻切片是指將視頻流按指定的時間間隔切分成一系列分區檔案,並產生一個索引檔案記錄分區檔案的資訊。

關於視頻處理工作流程系統的Demo及操作步驟,請參見視頻處理工作流程系統

Serverless方案優勢

提高工程效率

對比項

Serverless方案

傳統自建方案

基礎設施

需要您採購和管理。

開發效率

只需專註商務邏輯的開發,配合Serverless Devs編排和部署資源。

除了必要的商務邏輯開發,還需要您自己建立相同的線上運行環境,包括相關軟體的安裝、服務配置、安全更新等一系列任務。

並行和分布式視頻處理

通過Serverless 工作流程Resource Orchestration Service即可實現多個視頻的平行處理和單個大視頻的分散式處理,穩定性和監控交由雲平台。

需要很強的開發能力和完善的監控系統來保證穩定性。

學習上手成本

會編寫對應語言的函數代碼和熟悉FFmpeg使用即可。

除了程式設計語言開發能力和熟悉FFmpeg外,可能還需要使用K8s和Elastic Compute Service,需要瞭解更多的產品、名詞和參數意義。

專案上線周期

預計3人天(開發調試2人天和壓測觀察1人天)。

在具體商務邏輯外耗費大量的時間和人力成本,保守估計大約30人天,包括硬體採購、軟體和環境配置、系統開發、測試、監控警示和灰階發布等。

Auto Scaling免營運

內容項

Serverless方案

傳統自建方案

彈性高可用

Function Compute系統具有毫秒級Auto Scaling,可以快速實現底層擴容以應對峰值壓力,免營運,轉碼效能優異。

需要自建Server Load Balancer,Auto Scaling、擴容縮容速度較Function Compute慢。

監控警示查詢

提供更細粒度的Serverless 工作流程流程執行和函數執行情況。同時,可以查詢每次函數執行的Latency和日誌等,更加完善的警示監控機制。

Auto Scaling或容器層級的Metrics。

轉碼效能優勢

假設視頻為89s的MOV格式的檔案,雲端服務將MOV轉為MP4的普通轉碼需要消耗的時間為188s,將這個參考時間記為T。效能加速百分比計算公式為:效能加速百分比=T÷Function Compute轉碼耗時

視頻切片時間(s)

Function Compute轉碼耗時(s)

效能加速百分比(%)

45

160

117.5

25

100

188

15

70

268.6

10

45

417.8

5

35

537.1

成本低

在某些實際情境中,Function Compute在視頻處理上的成本更低。即使和雲廠商視頻轉碼服務單價對比,本方案仍有很強的成本競爭力。

下文中選用點播視訊中最常用的兩個格式MP4和FLV之間進行相互轉換,函數記憶體設定為3 GB,經實驗驗證,基於該方案MP4與FLV相互轉換的費用如下表所示。

成本下降百分比公式為:成本下降百分比=(某雲視頻處理費用-Function Compute轉碼費用)÷某雲視頻處理費用

表 1. MP4轉FLV

解析度

速率

幀率

Function Compute轉碼耗費時間

Function Compute轉碼費用

某雲視頻處理費用

成本下降百分比

標清640*480

889 KB/s

24

11.2s

0.003732288

0.032

88.3%

高清1280*720

1963 KB/s

24

20.5s

0.00683142

0.065

89.5%

超清1920*1080

3689 KB/s

24

40s

0.0133296

0.126

89.4%

4K 3840*2160

11185 KB/s

24

142s

0.04732008

0.556

91.5%

表 2. FLV轉MP4

解析度

速率

幀率

Function Compute轉碼耗費時間

Function Compute轉碼費用

某雲視頻處理費用

成本下降百分比

標清640*480

712 KB/s

24

34.5s

0.01149678

0.032

64.1%

高清1280*720

1806 KB/s

24

100.3s

0.033424

0.065

48.6%

超清1920*1080

3911 KB/s

24

226.4s

0.0754455

0.126

40.1%

4K 3840*2160

15109 KB/s

24

912s

0.30391488

0.556

45.3%

說明

某雲視頻處理計費使用普通轉碼,轉碼時間長度不足一分鐘,按照一分鐘計算,這裡計費採用的是2分鐘,即使採用1.5分鐘計算,成本下降百分比基本在10%以內浮動。

從上表可以看出,基於Function ComputeServerless 工作流程的方案在計算資源成本上對於計算複雜度較高的FLV格式轉MP4格式還是計算複雜度較低的MP4格式轉FLV格式,都具有很強的成本競爭力。根據實際經驗,往往成本下降比上表列出來的更加明顯,理由如下:

  • 測試視頻的碼率較高,實際上很多情境絕大部分都是標清或流暢視頻的轉碼情境,碼率也比測試視頻低,此時計算量變小,Function Compute執行時間短,費用會降低,但是通用的雲轉碼服務計費是不變的。

  • 很多視頻解析度在通用的雲轉碼服務時計費是有很大損失的,例如轉碼的視頻是856*480或1368*768,都會進入雲轉碼服務的下一檔計費單價,即856*480進入1280*720高清轉碼計費檔,1368*768進入1920*1080超清轉碼計費檔,單價基本是跨越式上升,但是實際真正的計算量增加可能不到30%,而Function Compute則是真正能做到隨用隨付的。

操作部署

本文會介紹Serverless方案中簡單視頻處理系統和視頻處理工作流程系統兩種情境的部署方案。

簡單視頻處理系統

前提條件

操作步驟

  1. 建立服務。

    1. 登入Function Compute控制台,在左側導覽列,單擊服務及函數

    2. 在頂部功能表列,選擇地區,然後在服務列表頁面,單擊建立服務

    3. 在建立服務面板,填寫服務名稱和描述,並按需設定配置項,然後單擊確定

      本文設定服務角色為AliyunFCDefaultRole,並在權限原則中增加AliyunOSSFullAccess

      關於建立服務的詳細配置資訊,請參見建立服務

  2. 建立函數。

    1. 函數管理頁面,單擊建立函數

    2. 建立函數頁面,按需選擇建立函數的方式,配置以下配置項,然後單擊建立

      • 建立函數方式:使用內建運行時建立

      • 基本設定:配置函數的基本資料,包括函數名稱請求處理常式類型請求處理常式類型選擇處理事件請求

      • 函數代碼:函數的運行環境選擇Python 3.9,代碼上傳方式選擇使用範例程式碼

      • 進階配置:由於處理視頻檔案比較耗時,並且要考慮視頻檔案的大小,所以本文選擇vCPU規格為4核,記憶體規格為8 GB,臨時硬碟大小為10 GB,執行逾時時間為7200秒。

      其他配置項使用預設值,關於建立函數的詳細配置資訊,請參見建立函數

  3. 建立OSS觸發器。

    1. 在函數詳情頁面,單擊觸發器管理頁簽,從版本或別名下拉式清單選擇要建立觸發器的版本或別名,然後單擊建立觸發器

    2. 在建立觸發程序面板,填寫相關資訊。然後單擊確定

      配置項

      操作

      本文樣本

      觸發器類型

      選擇Object Storage Service

      Object Storage Service

      名稱

      填寫自訂的觸發器名稱。

      oss-trigger

      版本或別名

      預設值為LATEST,如果您需要建立其他版本或別名的觸發器,需先在函數詳情頁的版本或別名下拉式清單選擇該版本。關於版本和別名的簡介,請參見管理版本管理別名

      LATEST

      Bucket 名稱

      選擇已建立的OSS Bucket。

      testbucket

      檔案首碼

      輸入要匹配的檔案名稱的首碼。建議您設定檔的首碼和尾碼,避免觸發事件嵌套迴圈觸發引起額外費用。此外,一個Bucket的不同觸發器如果指定了相同的事件類型,則首碼和尾碼不能重複。詳細資料,請參見OSS觸發器觸發規則

      重要

      檔案首碼不能以/開頭,否則會導致OSS觸發器無法被觸發。

      source

      檔案尾碼

      輸入要匹配的檔案名稱的尾碼。強烈建議您配置首碼和尾碼,避免觸發事件嵌套迴圈觸發引起額外費用。另外,一個Bucket的不同觸發器如果指定了相同的事件類型,則首碼和尾碼不能重複。詳細資料,請參見OSS觸發器觸發規則

      mp4

      觸發事件

      選擇一個或多個觸發事件。關於Object Storage Service的事件類型,請參見OSS事件定義

      oss:ObjectCreated:PutObject, oss:ObjectCreated:PostObject, oss:ObjectCreated:CompleteMultipartUpload

      角色名稱

      選擇AliyunOSSEventNotificationRole

      說明

      如果您第一次建立該類型的觸發器,則需要在單擊確定後,在彈出的對話方塊中選擇立即授權

      AliyunOSSEventNotificationRole

  4. 編寫函數代碼。

    1. 在函數詳情頁面,單擊函數代碼頁簽,在代碼編輯器中編寫代碼。

      函數需要將MP4格式的視頻檔案轉為FLV格式,並將FLV格式視頻檔案儲存在OSS Bucket下的dest目錄。本文以Python語言為例,範例程式碼如下。

      # -*- coding: utf-8 -*-
      import logging
      import oss2
      import os
      import json
      import subprocess
      import shutil
      
      logging.getLogger("oss2.api").setLevel(logging.ERROR)
      logging.getLogger("oss2.auth").setLevel(logging.ERROR)
      LOGGER = logging.getLogger()
      
      
      def get_fileNameExt(filename):
          (_, tempfilename) = os.path.split(filename)
          (shortname, extension) = os.path.splitext(tempfilename)
          return shortname, extension
      
      
      def handler(event, context):
          LOGGER.info(event)
          evt = json.loads(event)
          evt = evt["events"]
          oss_bucket_name = evt[0]["oss"]["bucket"]["name"]
          object_key = evt[0]["oss"]["object"]["key"]
          
          output_dir = "dest"
          dst_format = "flv"
          shortname, _ = get_fileNameExt(object_key)
          creds = context.credentials
          auth = oss2.StsAuth(creds.accessKeyId,
                              creds.accessKeySecret, creds.securityToken)
          oss_client = oss2.Bucket(auth, 'oss-%s-internal.aliyuncs.com' %
                                   context.region, oss_bucket_name)
      
          exist = oss_client.object_exists(object_key)
          if not exist:
              raise Exception("object {} is not exist".format(object_key))
      
          input_path = oss_client.sign_url('GET', object_key, 6 * 3600)
          # m3u8 特殊處理
          rid = context.request_id
          if dst_format == "m3u8":
              return handle_m3u8(rid, oss_client, input_path, shortname, output_dir)
          else:
              return handle_common(rid, oss_client, input_path, shortname, output_dir, dst_format)
      
      
      def handle_m3u8(request_id, oss_client, input_path, shortname, output_dir):
          ts_dir = '/tmp/ts'
          if os.path.exists(ts_dir):
              shutil.rmtree(ts_dir)
          os.mkdir(ts_dir)
          transcoded_filepath = os.path.join('/tmp', shortname + '.ts')
          split_transcoded_filepath = os.path.join(
              ts_dir, shortname + '_%03d.ts')
          cmd1 = ['ffmpeg', '-y', '-i', input_path, '-c:v',
                  'libx264', transcoded_filepath]
      
          cmd2 = ['ffmpeg', '-y', '-i', transcoded_filepath, '-c', 'copy', '-map',  '0',  '-f', 'segment',
                  '-segment_list', os.path.join(ts_dir, 'playlist.m3u8'), '-segment_time', '10', split_transcoded_filepath]
      
          try:
              subprocess.run(
                  cmd1, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
      
              subprocess.run(
                  cmd2, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
      
              for filename in os.listdir(ts_dir):
                  filepath = os.path.join(ts_dir, filename)
                  filekey = os.path.join(output_dir, shortname, filename)
                  oss_client.put_object_from_file(filekey, filepath)
                  os.remove(filepath)
                  print("Uploaded {} to {}".format(filepath, filekey))
      
          except subprocess.CalledProcessError as exc:
              # if transcode fail,trigger invoke dest-fail function
              raise Exception(request_id + " transcode failure, detail: " + str(exc))
      
          finally:
              if os.path.exists(ts_dir):
                  shutil.rmtree(ts_dir)
      
              # remove ts 檔案
              if os.path.exists(transcoded_filepath):
                  os.remove(transcoded_filepath)
      
          return {}
      
      
      def handle_common(request_id, oss_client, input_path, shortname, output_dir, dst_format):
          transcoded_filepath = os.path.join('/tmp', shortname + '.' + dst_format)
          if os.path.exists(transcoded_filepath):
              os.remove(transcoded_filepath)
          cmd = ["ffmpeg", "-y", "-i", input_path, transcoded_filepath]
          try:
              subprocess.run(
                  cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
      
              oss_client.put_object_from_file(
                  os.path.join(output_dir, shortname + '.' + dst_format), transcoded_filepath)
          except subprocess.CalledProcessError as exc:
              # if transcode fail,trigger invoke dest-fail function
              raise Exception(request_id + " transcode failure, detail: " + str(exc))
          finally:
              if os.path.exists(transcoded_filepath):
                  os.remove(transcoded_filepath)
          return {}
    2. 單擊部署代碼

  5. 測試函數代碼。

    您可以通過配置函數入口參數類比OSS事件參數進行測試,驗證代碼的正確性。在實際操作過程中,當發生OSS事件時,函數會自動被觸發執行。

    1. 在函數詳情頁面,單擊函數代碼頁簽,然後單擊測試函數右側xialatubiao表徵圖,從下拉式清單中,選擇配置測試參數

    2. 配置測試參數面板,選擇建立新測試事件編輯已有測試事件頁簽,填寫事件名稱和事件內容。然後單擊確定

      本文配置的事件內容如下,關於event參數具體資訊,請參見步驟二:配置函數入口參數

      {
          "events": [
              {
                  "eventName": "oss:ObjectCreated:CompleteMultipartUpload",
                  "eventSource": "acs:oss",
                  "eventTime": "2022-08-13T06:45:43.000Z",
                  "eventVersion": "1.0",
                  "oss": {
                      "bucket": {
                          "arn": "acs:oss:cn-hangzhou:123456789:testbucket",
                          "name": "testbucket",
                          "ownerIdentity": "164901546557****"
                      },
                      "object": {
                          "deltaSize": 122539,
                          "eTag": "688A7BF4F233DC9C88A80BF985AB****",
                          "key": "source/a.mp4",
                          "size": 122539
                      },
                      "ossSchemaVersion": "1.0",
                      "ruleId": "9adac8e253828f4f7c0466d941fa3db81161****"
                  },
                  "region": "cn-hangzhou",
                  "requestParameters": {
                      "sourceIPAddress": "140.205.XX.XX"
                  },
                  "responseElements": {
                      "requestId": "58F9FF2D3DF792092E12044C"
                  },
                  "userIdentity": {
                      "principalId": "164901546557****"
                  }
              }
          ]
      }
    3. 單擊測試函數,在函數代碼頁簽查看執行結果。

視頻處理工作流程系統

前提條件

操作步驟

該方案需要通過Serverless工作流程編排函數實現視頻處理系統,涉及到多個函數代碼和工作流程的配置和編寫。本文使用Serverless Devs部署該系統。

  1. 執行以下命令,初始化應用。

    s init video-process-flow -d video-process-flow

    在執行過程中,需要配置的資訊如下。您可以根據實際情況配置對應內容。

    配置項

    樣本

    地區

    cn-hangzhou

    服務名

    video-process-flow-demo

    Function ComputeService RAM角色ARN

    acs:ram::10343546****:role/aliyunfcdefaultrole

    Object Storage Service儲存桶名

    testBucket

    首碼

    source

    轉碼後的視頻儲存目錄

    dest

    OSS觸發器RAM角色ARN

    acs:ram::10343546****:role/aliyunosseventnotificationrole

    對視頻進行分區處理的分區時間

    30

    轉碼後的視頻格式

    mp4, flv, avi

    工作流程名稱

    video-process-flow

    工作流程RAM角色ARN

    acs:ram::10343546****:role/fnf-execution-default-role

    please select credential alias

    default

  2. 執行以下命令,進入專案並進行專案部署。

    cd video-process-flow && s deploy - y

    部署成功輸出內容如下:

    [2023-08-31 13:22:21] [INFO] [S-CORE] - Project video-demo-flow successfully to execute
    
    fc-video-demo-split:
      region:   cn-hangzhou
      service:
        name: video-process-flow-wg76
      function:
        name:       split
        runtime:    python3
        handler:    index.handler
        memorySize: 3072
        timeout:    600
    fc-video-demo-transcode:
      region:   cn-hangzhou
      service:
        name: video-process-flow-wg76
      function:
        name:       transcode
        runtime:    python3
        handler:    index.handler
        memorySize: 3072
        timeout:    600
    fc-video-demo-merge:
      region:   cn-hangzhou
      service:
        name: video-process-flow-wg76
      function:
        name:       merge
        runtime:    python3
        handler:    index.handler
        memorySize: 3072
        timeout:    600
    fc-video-demo-after-process:
      region:   cn-hangzhou
      service:
        name: video-process-flow-wg76
      function:
        name:       after-process
        runtime:    python3
        handler:    index.handler
        memorySize: 512
        timeout:    120
    fc-oss-trigger-trigger-fnf:
      region:   cn-hangzhou
      service:
        name: video-process-flow-wg76
      function:
        name:       trigger-fnf
        runtime:    python3
        handler:    index.handler
        memorySize: 128
        timeout:    120
      triggers:
        -
          type: oss
          name: oss-t
    video-demo-flow:
      RegionId: cn-hangzhou
      Name:     video-process-flow
  3. 測試專案。

    1. 登入OSS管理主控台然後進入testBucket的source目錄,上傳一個MP4視頻檔案。

    2. 登入Serverless工作流程控制台,在流程管理頁面,單擊目標工作流程,在執行頁簽下,單擊目標執行名稱,可以查看工作流程執行流程及執行狀態。

      image.png

    3. 當工作執行狀態為已成功時,可以進入testBucket的dest目錄,查看轉碼後的檔案。

      當看到轉碼後的檔案時,則說明視頻處理系統的服務已經正常運行。

相關文檔

常見問題

  • 如果已經在虛擬機器或容器平台上基於FFmpeg部署了一套視頻處理服務,能否可以在此基礎上增加其彈性,擁有更高的可用性?

    如本文所示,在虛擬機器或容器平台上基於FFmpeg的服務可以輕鬆切換到Function Compute,FFmpeg相關命令可以直接移植到Function Compute,改造成本較低,同時,繼承Function Compute彈性高可用性的特性。

  • 有並發處理大量視頻的需求時,如何操作?

    部署方案,請參見視頻處理工作流程系統。當有多個檔案同時上傳到OSS時,Function Compute會自動調整,平行處理多個檔案。更多資訊,請參見視頻處理工作流程系統壓測

  • 有很多超大的視頻需要批量快速處理完,例如每周五定期產生幾百個4 GB以上的1080P大視頻,需要在當天幾小時內全部處理完時,如何操作?

    可以通過控制分區的大小,使每個大視頻都有足夠多的計算資源參與轉碼計算,從而大大提高轉碼速度。部署方案,請參見視頻處理工作流程系統壓測

  • 有更進階的自訂處理需求,例如視頻轉碼完成後,需要記錄轉碼詳情到資料庫,或在轉碼完成後,自動將熱度很高的視頻預熱到CDN上,從而緩解來源站點壓力時,如何操作?

    部署方案,請參見視頻處理工作流程系統。處理中可以做一些自訂的操作,或基於此流程再做一些額外處理等,例如再增加後續流程,或最開始增加預先處理等。

  • 自訂視頻處理流程中可能會有多種操作組合,例如轉碼、加浮水印和產生視頻首頁GIF。如果需要後續為視頻處理系統增加新需求,例如調整轉碼參數,並且希望新功能發布上線對線上服務無影響時,如何操作?

    部署方案,請參見視頻處理工作流程系統Serverless 工作流程只負責編排調用函數,因此只需要更新相應的處理函數即可,同時函數有版本和別名功能,更好地控制灰階上線。更多資訊,請參見版本

  • 只有簡單的轉碼需求,或是一些極其輕量的需求,如需擷取OSS上視頻前幾幀的GIF、擷取音視頻的時間長度,自己搭建成本更低時,如何操作?

    Function Compute可以解決自訂問題,只需要在代碼中快速執行幾個FFmpeg的命令即可完成需求。典型樣本工程,請參見fc-oss-ffmpeg

  • 視頻源檔案存放在NAS或ECS雲端硬碟上,希望自建服務可以直接讀取源檔案處理,而不需要將它們再遷移到OSS時,如何操作?

    Function Compute可以掛載NAS,直接處理NAS中的檔案。更多資訊,請參見配置NAS檔案系統