搭建FFmpeg處理音視頻

基于Java+FFmpeg搭建一個處理音視頻的處理中臺。

主要的架構(gòu)設(shè)計分為兩塊內(nèi)容。

image.png

一個對外提供服務(wù)的音視頻處理任務(wù)中臺,承接來自業(yè)務(wù)方的所有音視頻處理需求,核心邏輯包括

  • 將音視頻處理任務(wù)落庫
  • 將音視頻任務(wù)發(fā)送至消息隊列
  • 消費來自消息隊列的消息,將音視頻處理結(jié)果更新到數(shù)據(jù)庫
  • 通過回調(diào)任務(wù),將結(jié)果回調(diào)給各個業(yè)務(wù)方

一個承接所有音視頻任務(wù)的處理集群,核心邏輯包括

  • 消費來自消息隊列的消息
  • 通過FFmpeg進行處理
  • 將處理好的文件上傳至OSS服務(wù)
  • 將處理結(jié)果發(fā)送至消息隊列

音視頻處理任務(wù)中臺

中臺的考慮很簡單,能承受住業(yè)務(wù)方的所有流量即可。內(nèi)部需要的角色也較為簡單。

分布式JOB

用于處理音視頻回調(diào)通知,所有提交過來的任務(wù)都必須攜帶回調(diào)地址,比如Http接口。

分布式MQ

作為中臺與邏輯處理集群的通信方式,最開始為了簡單快捷起見,選擇了使用Redis(lpush + bpop)作為消息中間件來處理,當下來看是一個錯誤的決定。后來統(tǒng)一完成了Kafka。

為何不能是Redis作為消息中間件

1、生產(chǎn)方與消費方的處理速率根本不再一個量級,當消費方處理速率比生產(chǎn)方低時,消息會大量積壓,進而形成了一個Big key
2、Redis有丟失的風(fēng)險,所以生產(chǎn)方需要做好一個合適的二次投遞機制
3、缺少MQ部分功能,比如可靠的重試機制

音視頻任務(wù)處理集群

該服務(wù)主要基于Java + FFmpeg來實現(xiàn),因為業(yè)務(wù)特性的問題,F(xiàn)Fmpeg是極其吃性能,特別是CPU,所以機器的性能會更好。

此處設(shè)計之初的目標是能快速的復(fù)制,當業(yè)務(wù)量突然進來時能通過加機器來抗住,所以節(jié)點必須是無狀態(tài),其次為了保證節(jié)點的穩(wěn)定性,我們也考慮了進程內(nèi)任務(wù)的一些排隊處理方案。

思路

簡單的描述一下當前的一些程序設(shè)計以及考量,并非是絕對的正確,因為很多方案選擇的時候是需要考慮時間成本、人力成本的,只能慢慢通過演進的方式找到最佳實踐。

為何要放在后端

如果客戶端可以做,一定要放在客戶端做。因為后端如果來處理這種大規(guī)模的音視頻內(nèi)容,所需要的成本太高了。放在客戶端做,不管是從體驗還是成本都是更優(yōu)的方案。(但是也要考慮一些性能不是很好地機子去處理,防止程序卡死)

有興趣的可以在本地的機子上體驗一下FFmpeg,簡直就是CPU猛獸。

部署

毫無疑問,必須是Docker。將FFmpeg的打包到鏡像中,節(jié)點的復(fù)制是鏡像級別的復(fù)制。純jar包的形式手工運維很痛苦,加機器還要處理FFmpeg,包括下載,解壓,配置環(huán)境變量一系列操作,即便寫成腳本也是運維同學(xué)的一個心智負擔(dān)。

Java CV

最開始我們并未注意到JavaCV,所以先通過Java Process+FFmpeg+Shell做了第一版功能的實現(xiàn)。

后來注意到Java CV,通過依賴POM的形式就能把FFmpeg集成到Java程序中,毫無疑問比起通過Java Process來調(diào)用命令行工具更讓我們心動。

可是JavaCV的學(xué)習(xí)曲線看起來很陡,目前也沒有找到比較合適的學(xué)習(xí)資料,傳送門,雖然能夠?qū)崿F(xiàn)我們所需要的相關(guān)功能,但是唯恐后續(xù)的成本太高甚至有可能切會Process的方式,所以我們當下僅作為調(diào)研方案參考。

并行處理

我們在考慮要不要使用線程池來并行消費MQ。

維護要并行處理?因為要更好的利用CPU,但是熟知FFmepg的同學(xué)一定知道,它是非常吃CPU性能的,并且FFmpeg本身就做了并行處理的多線程技術(shù)方案,那還需要并行處理嗎?

在CPU密集型的任務(wù)中,其實是不建議開啟多線程,因為本身CPU就很忙了,多線程下多了一些CPU的上下文切換,反而造成了性能的損耗。

答案是要,但是要拆分來看。把整個任務(wù)處理分成多個段來看,首先是FFmpeg處理,處理成功后要把本地文件上傳至OSS服務(wù)器上,然后發(fā)出MQ,最后將本地文件刪除。

拆分后可以很好的將任務(wù)分成CPU密集型+IO密集型兩段,所以我們可以把后續(xù)的IO密集型線程池化處理。

Process

想來這是服務(wù)端開發(fā)這個方向上,Process應(yīng)該算是比較冷門的API。

  • waitFor(long timeout, TimeUnit unit) 而不是 waitFor(),小心因為子進程阻塞把父進程耗死。
  • getInputStream + getErrorStream + close,兩個流的內(nèi)容都打印,遇到問題好排查
  • waitFor timeout 后調(diào)用 destroyForcibly,小心子進程丟失在父進程的管控下
  • exitValue,0表示成功,其他表示失敗,比如1表示主動destroy, 6表示process 被hangs, 137表示子進程被其他進程kill

CosClient 踩坑

在使用CosClient不當時,還遇到了連接池泄露的問題。

程序設(shè)計

我們的程序設(shè)計改了兩三次。

第一次設(shè)計忙于調(diào)研+交付,沒有足夠的時間來做設(shè)計,也沒有進行合理的抽象與規(guī)劃,導(dǎo)致第二個需求進來時幾乎涉及大量復(fù)制,代碼重復(fù)明顯,所以進行初步設(shè)計。第二次設(shè)計又因為業(yè)務(wù)交付時間緊急只做了一部分的抽象。第三次忍無可忍在周末重新做了一遍設(shè)計。

核心邏輯主要通過責(zé)任鏈來組織核心代碼。

前文提到把任務(wù)處理分成多個段來看,把任務(wù)分成了主要的CPU密集型+IO密集型的方式,通過責(zé)任鏈可以把任務(wù)拆分成更多的節(jié)點,讓程序的擴展性更好。

image.png

冪等

為啥要做冪等?主要考慮MQ重試,重復(fù)投遞等。

令牌桶

為啥要做令牌桶?考慮到當下我們申請的服務(wù)器的核數(shù)申請的比較高,單個任務(wù)并沒有把我們的CPU核數(shù)給徹底跑滿,或者接近滿,所以設(shè)計了一個小的線程池來提高并發(fā)(SynchronousQueue)。

令牌桶當下我們有2個選擇,原生JDK中的Semaphore,以及自定義基于Redis LPush + LPop的命令實現(xiàn)的令牌桶。

最后選擇基于Redis LPush + LPop 的方案。為何?性能并不是我們的第一指標,Semaphore不支持動態(tài)的調(diào)整令牌個數(shù),想要實現(xiàn)動態(tài)的調(diào)整令牌個數(shù)需要自定義實現(xiàn)。而基于Redis的方案成本很低,通過配置中心或者暴露出來的運維接口就可以較好的動態(tài)調(diào)整(令牌減少需要跟業(yè)務(wù)線程競爭令牌)。

推薦動態(tài)的去調(diào)整線程池大小,而不是令牌桶方案,這也是我們下個迭代會考慮的方案,并且對我們而言改動很小。

CPU線程池

前文已經(jīng)說過了,絕大多數(shù)的任務(wù)并沒有把我們的CPU的核數(shù)給徹底跑滿,甚至還有不少富余,所以適當?shù)脑黾硬l(fā)是有必要的。

IO線程池

毋庸置疑,IO線程池干的就是大量IO的操作,適當?shù)脑黾泳€程池的大小

FFmpeg的線程數(shù)

嘗試通過限制FFmpeg線程數(shù)+CPU線程池的方式來合理的提供并發(fā),后來發(fā)現(xiàn)并無優(yōu)勢,而FFmpeg內(nèi)部的并行方案沒有看到相關(guān)的源代碼并不好判斷,所以現(xiàn)在的方案是去掉了這些參數(shù),由FFmpeg自行動態(tài)的調(diào)整。

filter_threads
filter_complex_threads

轉(zhuǎn)碼效率

在很多時候,需要面臨處理一些特別大的視頻,為了提高效率,我們需要通過一些參數(shù)來優(yōu)化,但同時可能會損耗原視頻的質(zhì)量,評估的核心還是損耗后的質(zhì)量是否在可接受范圍呢。

根據(jù)碼率、分辨率等參數(shù)來判斷視頻的質(zhì)量,如何獲取碼率等信息?建議使用ffprobe工具,能夠提取到音視頻很多信息并以Json的格式返回。

具體參數(shù):preset,參考:https://trac.ffmpeg.org/wiki/Encode/H.264

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容