淘系的音视频编辑方案:非线性编辑引擎
在經歷移動設備的更新換代,網絡速度的持續提升和費用降低,手機用戶已經經歷了從文字閱讀到圖片瀏覽再到視頻觀看的內容消費的變革后,淘系音視頻技術如何靈活根據需求做出技術創新與變革。
非編定義
非編是非線性編輯的簡稱,如果按照維基百科的解釋,線性編輯和非線性編輯是相對的,是影視制作中的兩種不同方案。
它們兩者分別是錄像帶和數碼兩個時代不同的產物,最主要的區別在于是否是對原始數據進行破壞性編輯。
如果按照此解釋,我們這里就應該把文章名改為《淘系的音視頻多軌道編輯引擎設計》,才能更準確的說明我們想介紹的是什么。
所以我們更樂意的是拋開歷史背景,從字面上來解釋:無論音軌還是視軌,在時間線上無需依次線性排列的編輯方式,就是我們所說的非線性編輯。
如下圖示例,使用非線性編輯的方式,我們可以靈活自由的根據我們的需要,編排我們的音頻和圖像資源。可以按時間順序依次編排,也可以在時間上重疊在一起,也可以相互交錯。
隨著移動設備的更新換代,網絡速度的持續提升和費用降低,手機用戶已經經歷了從文字閱讀到圖片瀏覽再到視頻觀看的內容消費的變革?,F如今大家在利用手機進行休閑、購物、學習、生活記錄等許多方面,都更樂于以視頻作為媒介了。而非線性編輯作為視頻生產中一個重要的技術環節,在市場需求的推動下,也是發展的如火如荼。那么,淘系的音視頻非線性編輯引擎Marvel是怎樣設計的呢?
業務背景
近些年用戶習慣逐漸從圖文向短視頻遷移,淘系大量的商家和用戶,對于短視頻內容的生產和消費訴求也日益提高。淘系生態下有著諸多內容生產的入口,面向不同的用戶群體,不同的用戶群體期望生產的內容也存在差異,如商家、達人和買家三者的生產目的可定就存在巨大差異。Marvel引擎就是期望為淘系生態下的所有入口,提供相對統一的解決辦法,滿足不同用戶群體對于短視頻的差異化的生產需求。
對于短視頻內容生產,編輯工具通常會分化為兩種,一種相對來說,功能不是那么強大,只具備一些簡單的能力,使用起來非常簡單,這一類我們稱之為UGC工具。UGC追求簡單,降低生產門檻。另外一種,功能非常齊全,可以編輯生產出特別復雜的內容,有一定的使用門檻,我們稱之為PGC工具。PGC追求齊全,滿足專業需求。淘系的短視頻內容生產,顯然是這兩種工具都需要。
淘寶逛逛中的發布器就是淘系短視頻UGC工具的代表,而淘寶為淘寶商家打造的親拍,就是淘系短視頻PGC工具的代表。這兩個業務底層的音視頻編輯能力,都是有Marvel引擎提供的。
目標挑戰
淘系短視頻內容生產的工具,需要圍繞淘寶電商生態之下,包括商家、設計師、ISV、達人、買家等等諸多角色的用戶而展開。為滿足各維度用戶的生產工具訴求,保證各維度用戶的相互促進,我們需要打造一個體系化的跨平臺音視頻非線性編輯引擎。滿足UGC、PGC、OGC等多維度的短視頻內容生產工具的業務訴求。并以資源為媒介,在工具間進行流轉,滿足各維度的用戶生產需求。
需要打造這樣一個體系化的音視頻非線性編輯引擎,滿足淘系不同目標的短視頻內容生產工具的發展需求,我們也面許多挑戰:
???場景覆蓋
淘寶用戶眾多,包括商家、設計師、ISV、達人、買家等等諸多角色,各角色對于生產工具的訴求存在巨大差異,需要引擎同時滿足不同用戶、不同場景的生產訴求。
???平臺覆蓋
從用戶生產數據來看,Android和iOS等移動端是重要的內容生產入口,但是也存在大量精品內容,來自PC制作。引擎不僅要支持移動端,也要支持PC端的生產工具建設。
???功能體積
我們需要保證足夠完備豐富的功能,支持PGC用戶的設計創意。同時,我們又需要嚴格控制引擎大小,尤其是支持手淘UGC發布器時,避免帶來應用體積增量過大的負擔。
???穩定安全
作為短視頻內容生產體系的技術基石,面向眾多的生產用戶,在保證多業務多平臺快速發展的同時,我們也要保證引擎穩定安全。
技術選型
淘系的短視頻內容生產工具的需求,不僅僅在移動端工具。整個淘系的短視頻內容生產中,PC端、云端等配套工具也是不可或缺,有大量精品內容是來自PC制作。
所以我們的引擎構建目標,不僅要支持移動端生產工具的構架,也需要兼顧PC端、云端等生產場景。
業界許多短視頻內容生產的解決方案,會選擇復用AE生態,來解決設計工具與資源的問題。
一種是用AE來生產視頻模板,然后通過SDK解析模板,生產視頻。
還有一種會利用AE來生產特效,通過SDK來解析渲染特效,應用到編輯場景中。在當前階段,我們對于復用AE生態的訴求并不強烈,當然,后續也不排除會考慮兼容AE生產的資源。
另外,對于引擎的實現,有些解決方案會選擇基于開源音視頻框架進行實現,例如GStreamer。GStreamer框架非常強大和通用,其內核小、模塊化設計、功能強大、支持所有主流平臺,但是也存在一些問題。
它的模塊化和強大的功能,是以復雜的設計為代價的,這導致它上手較難,二次開發也不方便。
插件+流水線架構設計,在涉及時序功能、效率優化的方面,會存在鏈路過長、實現復雜的問題。
GStreamer內核小、可裁剪,但是隨著功能的增多,也會因其設計,導致引擎的大小不容易優化。
它是基于GObject+Glib進行面向對象的程序設計,對于我們這些Java/C++開發人員來很不友好。
所以,最終我們還是選擇用C++復用淘系已有的渲染、音頻處理等框架能力自己擼一個跨平臺音視頻非線性編輯引擎。
架構選型
我們可以粗略的將音視頻的非線性編輯引擎的工作流程拆解為四個部分,依次為理解用戶輸入的編輯意圖,把用戶輸入的編輯意圖組織為引擎更容易理解的信息結構,然后將信息結構的內容按照可視聽的方式反饋給用戶,最后用戶在需要的時候將編輯的產物進行導出。無論接入的業務如何變化,引擎內的流程都可以歸到這四個步驟之內。
據此理解,為保證多種平臺、不同場景下的業務訴求,我們對音視頻生產處理引擎需要滿足業務邏輯進行功能模塊劃分,構造滿足通用多軌道非線性音視頻編輯的數據結構,然后以此為核心,向下層設計提出要求,并在迭代過程中進行調整和補充,最終逐漸形成了如下圖所示,自下而上包括基礎層、驅動層、邏輯層和封裝層四層結構的整體架構。
在封裝層中,我們對接口分級,C++ Engine API提供相對通用的接口,平臺層對其進行包裝,然后業務層基于平臺層的封裝進行業務接口封裝。使核心接口穩定,避免功能、場景、業務增加帶來的接口膨脹。
在邏輯層中,我們按照非線性編輯引擎的工作流程,以綜合的業務邏輯劃分功能模塊,剝離與具體業務場景的直接聯系,讓不同業務可以根據場景需要進行組合,來滿足不同需求。
在驅動層中,我們構建了時序&數據關系、音畫的驅動設備以及編輯上下文需要的多個子系統,用來管理和組織資源、數據源、材質、音畫處理器等,依用戶意圖,結合基礎層能力,按時序對外輸出可視聽的數據。
在基礎層中,我們抽象了資源、音畫源的接口,并針對不同類型做了實現,也提供了與業務邏輯無關的,相對底層的對象結構和基礎能力。
數據結構
Marvel以“面板”、“軌道”、“片段”的概念來組合和處理多軌道非線性編輯的復雜時序邏輯,它們是Marvel數據結構概念的核心,以“材質”的概念,來區分、組合、滿足對于非線性編輯中復雜的空間及功能邏輯,以“資源”的概念來保證和簡化以Marvel為核心的短視頻內容生產體系中資源的流轉。
所有的資源的時序信息都通過面板(ClipPanel)來進行整合,一個編輯工程中有且僅有一個編輯面板,面板中可以存在一個或者多個軌道,每個軌道中可以存在一個或者多個片段。如下圖所示。
拋開時序相關的功能和表現,不同類型的片段,對外的表現存在很大的差異,而且這些差異通常也要求外部可控可編輯,但是不同類型的片段,也會存在一些表現共同的地方。
比如一個視頻片段顯示到屏幕上,可以對這個視頻圖像進行縮放、位移、旋轉、修改透明度、調整混合方式等,貼紙和文字等片段也需要能夠進行這些操作,而這些操作對于聲音片段、濾鏡片段來說沒有任何意義,也可以說無法應用。材質概念的設計就是為了處理這樣的情況。
材質在渲染系統中常用的術語,表示的是表面各可視屬性的結合。
Marvel借用了此術語,將其衍生為片段的所有附加屬性(附加是相對片段定義已經存在的屬性而言的)。
為了降低API上的理解成本,材質這個概念,會以片段的屬性對外表現。
對于不同類型的片段,我們可以給他們增加不同的材質,在進行片段的處理時,我們根據片段的材質,找到對應材質的處理器進行處理,這樣即對不同片段做了區分,也保證了相同處理流程的復用。
在Marvel中,當前材質可以按照其目標對象分為兩類,一類是圖像相關的,比如濾鏡、特效、畫面位移旋轉縮放控制、畫面效果調節等等。一類是音頻相關的,比如變聲效果、音量調整、降噪處理等等。Marvel中除時序功能外,其他功能基本都是通過材質來體現。
功能模塊
作為通用的跨平臺音視頻非線性編輯引擎,以市場上Top10的視頻編輯工具為假想業務,從大的功能上進行劃分,Marvel需要滿足音視頻多軌道非線性的編輯(包括貼紙文字特效等等)、編輯過程的實時預覽、編輯產物的導出(視頻、草稿、模版等)、為滿足二次編輯能力的編輯產物加載。另外為了降低業務接入的門檻,Marvel可能還需要支持常見的音視頻處理操作,比如音視頻分離、視頻截幀等等。Marvel的邏輯層模塊劃分就是由此而來。
邏輯層各個模塊都是直接或者間接的圍繞ClipPanel中的數據來按照各自的職責展開各自的邏輯。其中,我們可以理解為,Editor模塊關注的是ClipPanel原始的數據,Player模塊更關注的是按照ClipPanel原始數據構建出的源所提供的數據流(主要有音頻流和圖像流)。Exporter和Toolbox則按需決定其關注點,比如導出草稿的Exporter關注的是ClipPanel的原始數據,而導出視頻的Exporter關注的ClipPanel構建的源提供的數據流。我們以數據結構和邏輯模塊來對下一個層次提出設計要求,也就是驅動層。
這里的驅動層的并不是操作系統的驅動層那樣驅動硬件運行,但是它也具有硬件驅動類似的含義。硬件驅動是按照硬件的時序圖操作硬件,使硬件進行工作,而Marvel的驅動層是針對ClipPanel原始數據和運行時的數據流而言,是按照ClipPanel所描述的時序,以生產者-消費者的模型來展開設計,驅動數據流動,實現音視頻的編輯、預覽及導出。
基礎層的設計并不直接與邏輯層聯系,是音視頻生成處理中必不可卻的部分。像音視頻解碼、圖像解碼、圖像渲染、音頻處理、文件讀寫等,這些基礎的功能,無論上層如何設計,這些功能都必須提供,基礎層的設計是剝離了邏輯層的需要進行思考和設計的,可能在后續的演進過程中,會因為邏輯層和業務的需要而有所改動,但是其原始的設計,我們認為是只與數據結構設計有關,和業務與邏輯層無關。
按照當前Marvel中的實現,內部數據流向大概如上圖所示。Marvel中的ClipPane對Audio Device表現為一個提供音頻原始數據的源,對Video Device表現為一個提供紋理數據的源。Audio Device和Video Device都是作為一個數據消費的中間裝置而存在,按照時序獲取數據,根據業務場景的不同,將數據傳遞給不同的消費者。
在處理流程中,音頻源和紋理源提供出來的都是對于的復合節點,合成器用于解釋并處理復合節點,將音頻復合節點處理成PCM數據,將紋理復合節點處理成紋理數據。以音頻為例,其處理流程大致如下,紋理的處理流程,也基本與此類似。
接口設計
與許多引擎或者SDK有所不同,由于視頻編輯的復雜性,不同的業務場景對于視頻編輯的訴求也有很大的差異,Marvel為了保證在維持接口簡單清晰的基礎上,滿足不同業務的訴求,將接口進行了分層,這部分在架構圖中也特意進行了區分和標注。Marvel Engine C++ API按照架構中邏輯層的劃分進行類C接口定義,不區分業務場景進行接口設計。Platform API基本就是按照C++ API對各平臺和語言進行的封裝。
Marvel Engine C++ API的存在,主要是期望避免隨著業務數量和業務訴求的增多,Marvel的API越來越臃腫最后難以維護,所以盡可能的屏蔽業務場景帶來的影響,這樣同樣也使得這套接口對業務并不友好。所以在C++ API和 Platform API之上,還存在Business Layer API。Business Layer API是按照業務訴求對Platform API或者C++ API進行封裝,我們擔憂的接口膨脹,主要來源于Editor,Marvel會按照常見的視頻編輯應用,內置一套通用的業務API,同時也允許業務接入方自行擴展。
C++接口和業務功能沒有關聯,把編輯動作簡化為數據的增刪改查,通過key-value來增刪改查內部屬性。業務層接口再對這個接口進行封裝,可能是單一接口封裝,肯能是多個接口組合,視業務需求而定,形成業務接口。如下,分別為C++接口和業務接口示例:
// 設置和獲取片段的屬性 int setClipStrProperty(EditorHandle h, const String& id, const String& type, const String& key, const String& value); int setClipI64Property(EditorHandle h, const String& id, const String& type, const String& kye, int64_t value); int setClipDblProperty(EditorHandle h, const String& id, const String& type, const String& kye, double value); int getClipI64Property(EditorHandle h, const String& clipId, const String& type, const String& key, int64_t& value); int getClipDblProperty(EditorHandle h, const String& clipId, const String& type, const String& key, double& value); int getClipStrProperty(EditorHandle h, const String& clipId, const String& type, const String& key, String& value); // 移除片段的屬性 int removeClipProperty(EditorHandle h, const String& clipId, const String& type, const String& key); // 設置資源的屬性 int setResStrProperty(EditorHandle h, const String& resId, const String& key, const String& value); int setResI64Property(EditorHandle h, const String& resId, const String& key, int64_t value); int setResDblProperty(EditorHandle h, const String& resId, const String& key, double value); int getResStrProperty(EditorHandle h, const String& resId, const String& key, String& value); int getResI64Property(EditorHandle h, const String& resId, const String& key, int64_t& value); int getResDblProperty(EditorHandle h, const String& resId, const String& key, double& value); /** * 設置一個片段上圖像的裁剪信息,裁剪出來的區域為保留區域 * * @param id 目標片段ID * @param x x方向起始坐標 * @param y y方向起始坐標 * @param w 裁剪寬度 * @param h 裁剪高度 * @param rotate 裁剪旋轉信息 * @param normalize 裁剪參數是否為歸一化參數 * @param rotateWithCropCenter 旋轉是否使用裁剪區域的中心作為旋轉中,false時使用圖片中心作為旋轉中心 * @return 執行結果 */ public int setClipCrop(String id, float x, float y, float w, float h, float rotate, boolean normalize, boolean rotateWithCropCenter) {int actRet = editor.cCheckToAddMtl(id, C.kMaterialTypeCrop);if (actRet != 0) {return actRet;}editor.setProperty(id, C.kMaterialTypeCrop, C.kMaterialKeyRotate, rotate);editor.setProperty(id, C.kMaterialTypeCrop, C.kMaterialKeyXOffset, x);editor.setProperty(id, C.kMaterialTypeCrop, C.kMaterialKeyYOffset, y);editor.setProperty(id, C.kMaterialTypeCrop, C.kMaterialKeyWidth, w);editor.setProperty(id, C.kMaterialTypeCrop, C.kMaterialKeyHeight, h);editor.setProperty(id, C.kMaterialTypeCrop, C.kMaterialKeyNormalizeFlag, normalize);editor.setProperty(id, C.kMaterialTypeCrop, C.kMaterialKeyRotateFlag, rotateWithCropCenter);return 0; }/** * 設置一個圖像類片段的錨點 * * @param id 目標片段ID * @param x x方向錨點,典型值為-0.5 - 0.5 * @param y y方向錨點,典型值為 -0.5 - 0.5 * @return 執行結果 */ public int setAnchor(String id, float x, float y) {editor.setProperty(id, C.kPropertyCanvas, C.kMaterialKeyXAnchor, x);return editor.setProperty(id, C.kPropertyCanvas, C.kMaterialKeyYAnchor, y); }/** * 設置一個圖像類片段的旋轉角度 * * @param id 目標片段ID * @param rotate 旋轉角度,弧度 * @return 執行結果 */ public int setRotate(String id, float rotate) {return editor.setProperty(id, C.kPropertyCanvas, C.kMaterialKeyRotate, rotate); }業務案例
當前Marvel已經接入了淘系多個業務產品中,包括手淘逛逛發布器、親拍、MediaAI Studio、閑魚、點淘等,主要的業務形態,包括拍攝后編輯、深度編輯、影集模板、口播編輯,另外還有一些水印處理、云剪輯等等之類的業務形態。隨著Marvel的發展,未來還會支持其他業務,滿足更多的業務訴求。
總結展望
本文以淘系的音視頻非線性編輯引擎Marvel為主題,介紹了它的應用場景和引擎設計。音視頻非線性編輯涉及到許多技術,包括編解碼、音頻處理、圖像算法、圖像渲染等等諸多方面,都是非常有意思的東西,每一塊都可以單獨成為一個技術方向。非線性編輯引擎不僅僅是基于這些技術的組裝,也需要對這些技術進行升華,挖掘它們在非線性編輯中的業務價值。
在當前的規劃中,后續我們除了功能與效率方面的工作外,還將持續針對Marvel引擎圍繞著高效、穩定、靈活來進行技術上的演進,如增加預處理流程、進一步推進圖像渲染&音頻處理&預處理的插件化、增加片段間在時序上的相對布局等等。針對諸如遠程資源下載、資源依賴管理、草稿動態轉換、三方資源支持等非編輯核心能力的擴充,我們也將進行梳理,后續以插件的方式進行注入支持。
未來我們將繼續以Marvel為編輯核心,推進淘系完善的短視頻內容生產體系構建,保障淘系包括商家、買家、達人等諸多用戶角色的短視頻內容生產訴求。并在此基礎上,沉淀技術,開放生態,持續為其他業務賦能。
???拓展閱讀
作者|午王
編輯|橙子君
出品|阿里巴巴新零售淘系技術
詳情請掃描圖中二維碼或點擊閱讀原文了解大會更多信息。
總結
以上是生活随笔為你收集整理的淘系的音视频编辑方案:非线性编辑引擎的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: QUIC助力Snapchat提升用户体验
- 下一篇: 音视频技术开发周刊 | 203