本文由百度智能雲音視頻SDK產品技術負責人李明路在LiveVideoStack線上分享的演講內容整理而成,內容從音視頻數據角度出發,梳理了音視頻SDK的發展和技術演進。詳細分析數據在常見音視頻模塊上遇到的問題與挑戰,並提出相應的解決思路和技術實踐。算法
文 / 李明路編程
整理 / LiveVideoStack小程序
視頻回放:https://www.livevideostack.cn/video/online-lml/緩存
本次分享的主題是移動音視頻SDK開發工程實踐,內容主要分爲如下五個部分:安全
- 音視頻SDK的技術演進
- 數據採集管線的設計與實現
- 特效模塊數據中間件設計和實現
- 連麥模塊數據中間件設計和實現
- 渲染模塊數據中間件設計和實現
音視頻SDK的技術演進
1.1 數字化進程服務器
多媒體技術是一項傳統技術,但同時也是在不斷髮展與進步的,咱們能夠形象的用細胞分裂來表示,即多媒體技術內部也在不斷髮生分裂,而音視頻SDK則是其中的一個分系。網絡
如下大體列出了音視頻技術演進的幾個重要進展。首先,在互聯網化前期多媒體技術就已經開始出現普遍的應用,得益於通信技術的發展,咱們能夠藉助於廣播、有線電視、衛星等等,將數字信號傳輸給用戶。框架
解碼器其實在整個多媒體技術的發展當中也起到很是重要的做用,只不過當時解碼器的能力與如今相比仍是有些不一樣,但一樣能夠實現解碼和容器的分離。異步
此後,隨着互聯網的出現,咱們能夠實現基於一些IP網絡協議、光纖的普遍使用,經過WiFi、蜂窩網絡,傳輸數據到設備終端(此時終端更多的仍是指PC端)。所以產生了一些在線類的音視頻服務,例如點播、在線語音等典型的音視頻場景。在這些場景中,SDK更多的仍是以服務端爲主。ide
2010-2015年期間,隨着手機硬件的發展,終端的算力不斷提高,編解碼芯片也得以快速發展,再加上消費者使用習慣的變化,出現了更多碎片化的場景和產品,像直播、短視頻等。此時,隨着手機等移動設備的普及,移動端SDK也慢慢進入到消費級領域,逐漸發展成獨立技術棧。
近年來, 隨着5G、AI人工智能的發展,VR/AR、語音交互等技術也在發生着新的變化,應用場景變得更加普遍,音視頻SDK已經不只僅侷限於移動端,將來會出如今各類設備屏幕、產品形態當中。
1.2 移動端音視頻框架
移動端的音視頻框架與其它移動端框架相比有很大不一樣。音視頻框架首先要立足移動端提供的系統能力,包括系統框架和硬件能力。如圖,系統框架層有iOS/Android的一些多媒體框架、硬件的編解碼、硬件的處理能力如CPU、GPU、NPU的處理模塊,以及一些開源圖像庫。
系統框架層之上是第三方框架,如FFmpeg、OpenSSL(加解密);在點播場景用的比較多的Ijkplayer/Exoplayer;用於底層通信的技術:雙向的,低延時的WebRTC技術,用於單向點播的RTMP,目前比較火的SRT低延時方案;除此以外,還會有一些圖像處理方面的框架如GPUImage等。
在此之上,就是一些跟場景相結合比較多的模塊,這裏大體列出了部分核心模塊。
數據從多媒體採集模塊出來,會通過一路或多路的混音混流(與實際場景相結合),而後過渡到多媒體編輯模塊:當下短視頻的一些能力都是經過多媒體編輯這個處理單元實現,再到後面的多媒體後處理模塊:例如AR特效,以及一些比較好玩的互動能力等;
內容生產完成後,咱們會根據點播、直播或者短視頻等場景的不一樣,採用不一樣協議進行分發。再下面就是消費側,這裏能夠直接讀取數據,也能夠間接讀取數據做爲緩存加快二次讀取數據的載入。獲取數據後會根據不一樣的封裝格式,如FLV、TS或者是MP4等對數據進行解封裝,再進行解碼和渲染操做。
相比與其它移動端框架音視頻框架最特別的地方就是管線部分,由於音視頻SDK的產品與其它產品不太同樣,首先須要的是實時處理,數據流是不斷在各個模塊之間穿梭的,如何保證各個模塊間的高效傳輸,這裏提出了一個管線的概念。
1.3 惟快不破
簡單介紹了音視頻SDK的框架,接下來介紹下目前框架遇到一些問題和挑戰。目前不少業務場景,其實都在追求更多的新玩法。這裏可能會有一些追求更大的分辨率,還有一些更高的刷新率,甚至是更極致的體驗。可是咱們知道音視頻SDK很大程度上會受制於平臺的能力,因爲平臺具備更多的差別性,因此致使音視頻SDK在發展過程中,其實遇到不少的問題。另外還涉及到模塊間的數據交互,所以性能對於移動端來講是一個最大的瓶頸。
其實在有些場景像點播或短視頻,720p的分辨率已經不能知足場景的使用,甚至在一些運動場景,30fps的刷新率也已經沒辦法知足場景的開發,固然這些其實更多的是業務挑戰。除此以外,剛纔也說過整個多媒體技術的發展,編碼器一直起到了舉足輕重的做用,因此底層技術也在追求更高效的編碼效率。
對於這些問題,其實你們都能達成一個共識,就是說速度是音視頻SDK框架要解決的一個問題。但拋開現象看本質,速度其實仍是來自於數據的傳輸,它是一個根本前提條件,所以如何讓數據更高效的傳遞和更高效的處理,纔是移動端SDK要解決的最根本問題。
數據採集管線的設計和實現
這裏介紹一下目前一些開源產品當中數據鏈的設計和方案,以兩個產品爲例,一個是GPUImage。相信作過移動端SDK特效的同窗應該都是耳熟能詳了,由於它確實是比較好用,能夠提供大量的Shader。拋開Shader的不說,咱們看一下它在數據鏈條的設計方式。首先GPUImage提供了一個傳輸數據的類Output和一個實現接收數據的Input,而後經過生產模式,生產模塊像相機採集模塊,以及本地相冊模塊,其實都是實現了Output的類,生產模塊生產完畢後會交由Output進行渲染,這裏它的強大之處在於Output能夠與可編程的Shader進行強關聯,很大程度的驅動了OpenGL的運行效率。而後將渲染後RGB的數據轉換成FrameBuffer這個實體的對象,整個鏈條都是經過FrameBuffer進行接收,而後經過鏈條上的組件去實現了Input後,就能拿到Output傳出的FrameBuffer,以此類推將FrameBuffer進一步的傳遞到下個鏈條當中,整個鏈條實際上是很是清晰和簡單。
咱們再來看一下右邊的FFmpeg,除了提供不少的數據鏈條的處理機制、數據的分包,它還能夠在不進行解碼的狀況下進行轉包,也能夠把數據進行解碼,作一些後處理等等,整個鏈條其實也是比較清晰的。
介紹了兩種多媒體框架,接下來看一下咱們團隊是怎麼去考慮這些問題。實際開發中,音視頻SDK框架中管線實際上是不可控的,咱們來看一下這兩種方案的一些優點和問題。
首先GPUImage的優點是顯而易見的,就是協議相對簡單清晰,另外經過可編程Shader驅動了OpenGL,這樣它的運行效率變的很是快,經過大量的Shader可讓開發同窗去進一步學習這門語言,是一個優秀的圖像處理 開源框架。但一樣存在一些問題,首先咱們看到它提供的這些Shader其實大多數都是圖像處理,而且基本上都是同步處理的。因此可能致使在使用GPUImage跟一些框架進行結合的時候,當你的某個模塊涉及到耗時處理,這個時候有可能會出現某個對象被其它釋放掉或怎麼樣,會形成整個線程的不安全。
FFmpeg的優點特別多,簡單說一下它的函數指針,其實也是FFmpeg的一個最佳實踐,它經過指針的方式,把數據進行一層層的拆分跟傳遞。可是它也一樣存在一些問題,好比說FFmpeg是面向過程的,它的鏈路設計對於不熟悉FFmpeg的同窗來講,可能會以爲鏈路很是複雜,使用起來可能並非那麼的駕輕就熟。
因此咱們通過多輪討論跟思考,就但願獲得一個數據管線,它首先像GPUImage,協議比較簡單,用起來比較容易上手,對開發者來講是友好的;另外須要它是可控的,由於隨着SDK的模塊愈來愈多,咱們發如今不少場景下,會出現不可控的問題;除此以外咱們還但願它是支持同步或者是異步的,調度是安全的,鏈路配置是簡單可依賴的。
咱們也總結了本身的一些方法論跟實踐:專一鏈式,這主要仍是考慮到音視頻框架,鏈式是它的一個核心思想,因此咱們聽從這種法則自研數據管線。
首先是制定數據協議,主要解決的是最基本的模塊間數據的高效、穩定的傳遞。這裏能夠看到左圖,它的方案跟剛纔介紹的GPUImage有些類似,可是也有些不一樣。咱們也提供了相似的AVOutput的模塊和AVInput的數據傳輸接收的協議。可是咱們並無跟GPUImage的OpenGL進行綁定,只是單純的去記錄和管理這條鏈條上的各個的組件,咱們叫target。而後經過Dispatcher的機制,分發從生產端上報的視頻幀,經過視頻幀不斷的傳遞到各個鏈路的target當中,而每一個target實現AVInput其中的協議方法。例如frame跟type兩個函數,frame就是從raiseFrame Output傳遞下來的數據;type主要是作一些音視頻的音頻跟視頻的區分。
除此以外咱們還支持一些二進制場景的分發,主要是爲了配合像直播這種對數據有分發要求的場景作了一些協議的升級。在鏈條的最末端,能夠看到咱們也實現了AVControl,跟以前不太同樣是咱們制定了一個控制協議,這個控制協議主要是爲了控制數據的流入和流出。爲何要作這個事情呢?咱們知道音視頻SDK最核心的就是數據在不斷的傳遞,若是說某個模塊出現異常,數據沒有一種機制能保護它,可能會致使整個SDK的運行出現不穩定的狀況。好比在直播場景分發的時候,咱們發現網絡有抖動,此時咱們能夠調整發送的速率、速度等。
這裏簡單的畫了一下咱們的數據流向,一個是推的方式,就是直接從相機這種模塊採集數據,直接着向後面的某個模塊去傳遞。一個是拉的方式,主要是指讀取本地文件或者說是間接的獲取數據,好比從網絡讀取數據首先須要將數據讀下來,而後再傳遞到各個模塊。這裏其實相對於以前說了GPU在異步線程的一些處理,咱們也作了一些兼容和保護,就是防止異步處理時對象被釋放的狀況發生。因此說咱們基本上也是沿用了GPUImage協議簡單的這種思想,而後在此基礎上又增長了一些像控制協議的實現機制,讓整個鏈路變得可控。
除此以外,咱們也發如今平常過程中,只是簡單的設計數據管線,其實並不能很好的對業務場景開發提供幫助,緣由什麼呢?
其實咱們只是解決了各個模塊數據之間的傳遞問題,可是並不能對最終產品的場景開發落地解決全部的問題,由於場景跟模塊是有很大差別的。咱們所瞭解的像點播、直播,甚至是特效等,它其實都是通用性的能力。可是實際場景就涉及到各個模塊之間的組合,因此數據的傳遞並不能說傳遞一個數據,就能把一個場景串聯起來。
這裏也舉了幾個平常過程當中典型的例子,好比對於點播的畫質優化,會發現類型轉換不是那麼通暢和簡單。對於連麥場景,如何讓咱們的SDK或者產品用起來更簡單,像人臉特效,好比說涉及到能力的多樣化,如何作到兼容性等,這些都不僅是靠數據鏈路或模塊搭載就能解決的。所以咱們提到了一箇中間件的概念,就是把數據進行橋接實現資源的共享,在各個模塊或業務輸出或使用的時候,就能提升總體數據採集或處理的應用性。因此說應用性也是咱們數據採集管線的另外一個考量指標。
特效模塊數據中間件設計和實現
接下來咱們來看一下,在實際過程中遇到的一些問題跟解決方案。
特效模塊一般是典型的PaaS結構,它上面存在多種模型,並且模型之間是能夠進行插拔;它還有另一個特色就是耗資源。那麼如何在音視頻SDK中將這個模塊更好的運用起來,去對外提供能力呢?咱們這裏以人臉特效的高級美顏接口爲例,高級美顏中涉及到的特徵點很是多,像大眼、瘦臉、下巴等,並且這些特徵點並非經過一次迭代或是一個模型就能解決的,它可能會涉及到屢次的迭代和多種模型的組合疊加。這樣就會帶來一個問題,在集成特效模塊的時候,若是這些能力都是不斷變化的,對於模塊的使用來講,就存在一個不安全不穩定的因素。那麼咱們該如何把這個問題解決,或者說屏蔽掉呢?
這裏咱們提供了一個概念:首先在調用能力的時候,不直接去調用,而是把這些能力作進行抽象、封裝,而後這些封裝的模型,用來關聯後面的一些不一樣的算法。由於別人在用SDK的時候,不必定按照你全部東西來集成,可能只使用其中部分能力,這樣就有可能會致使一些特效的SDK存在版本不一致。若是沒有這個代理層,當出現版本不一致的狀況,對於上層來講可能就會出現大量接口的調整和修改,會比較耗時耗力。不過咱們作代理的話,可能就給屏蔽上層接口的穩定性,而後經過咱們的模型跟一些抽象對象,能夠去驅動不一樣的AR模塊的版本。
經過數據管線咱們能夠看到,從錄製模塊把數據傳到effect接口,再把數據再給到AR SDK的時候,每一個AR SDK都會有一個處理檢測能力,會定時的檢測主屏指標等,爲何要這麼作?
咱們知道如今的特效,場景、玩法是特別複雜的,處理效果沒辦法徹底保證。因此爲了保證每幀的處理和總體鏈路的穩定,咱們須要不斷作一些性能指標的監測,這些指標監測須要不斷的反饋給上層的調用。好比當前數據傳輸的速度較快,或者傳遞幀太多處理不完,咱們就能夠經過數據管線進行回傳,進行控制。甚至能夠調整錄製模塊採集的幀率等,這樣再把數據返回給錄製模塊,錄製模塊再將數據傳遞給其它模塊,像預覽進行渲染等。經過數據管線加代理的方案,咱們就能夠很好的整合不一樣AR版本、能力,對外保持接口的統一。
連麥模塊數據中間件設計和實現
連麥模塊也是目前音視頻產品中用的比較多的一個能力,如在線教育、直播帶貨、PK、娛樂等等,而且已經慢慢成爲一個標準化能力,但連麥模塊在使用過程中還存在比較多的問題。
標準直播是單向通訊,且信令相對簡單。融合連麥模塊後,則會變成雙向通訊,信令會變得複雜,媒體流從單路流變成了多路流。如左下圖,一個標準的直播SDK加連麥SDK的整合結構,能夠看到白色區域是標準的直播流程,從建立直播間到建鏈、編碼、封包,包括經過隊列進行分發等等。當融合了連麥能力以後,對整個模塊來講會增長更多的鏈路。首先會引入RTC的服務端,媒體服務端與信令服務端。另外可能還會引入一些相似於業務系統的消息機制,IM服務器等等。
用戶若是發起連麥,能夠看到左圖紅色的箭頭。首先它會向RTC信令服務器發送連麥請求,同時也會向IM服務器發送一個請求,向IM服務器發送請求的緣由,主要是爲了作一些業務上的處理,好比說UI界面或者場景的一些流程處理。實際上信令服務器主要是爲了傳遞加入房間的請求,請求到達主播直播間後,主播直播間會響應信令服務器,選擇贊成或者拒絕。若是贊成,則會經過信令服務器將信號返回給小主播/觀衆,小主播/觀衆這個時候就會把數據傳遞到RTMP的媒體服務器,主播也會把媒體流傳到RTMP服務器,兩路流匯聚到RTMP服務器後,經過旁路轉播等方式來進行對外的轉播,此時觀衆就會看到兩個主播或主播和觀衆合流的畫面。
這個實現流程其實比較複雜繁瑣,對於用戶(主播)來講可能理解起來很是吃力。首先他要關心的東西特別多,例如 IM服務器、旁路直播,旁路直播還會涉及一些模板的設置等等,他可能都要去關心,因此使用起來很是的麻煩。而對於消費側的觀衆來講,也一樣存在一些問題,好比說會出現相同地址,可是會出現不一樣編碼的兩路流,這種狀況下可能對於一些播放器來講會有一些挑戰,可能會出現一些卡頓,由於它在不斷的重置一些編碼參數,另外這種方案經過旁路轉推的方式,可能原始的直播流會斷掉,就在頻繁切換直播流跟混合流的過程中可能就會出現延遲,可能對於拉流側來講它就有不少的問題,也不太好。
針對這些問題其實咱們也看到,就是用戶在使用過程中其實有不少的不方便,因此說能夠看到說簡單的數據傳遞,其實並不能讓這個場景作的特別的簡單和應用,後面咱們就作了一些大量的端到端的一些數據鏈的整合,首先是咱們考慮到了就是消費側的編碼播放器的一些問題,首先是切換編碼參數,這個方案可能並非一個適用的方案,因此咱們採用了本地混流的技術方案,其好處在於個人推流各方面的參數能夠保持一致而不發生變化,而其實對於移動端的處理能力來講,本地的2~3路流的合流,其實對一些硬件來講,壓力並非特別大。另外咱們把IM服務器跟RTC信令服務器進行了整合,由於咱們以爲讓用戶去關心這麼多的信令實際上是沒有必要的,並且用戶也可能會被這些問題所困擾,因此咱們內部就經過服務端的方式進行了消息的整合,這樣子就讓用戶使用起來變得更加簡單。經過這種端到端的一些媒體和信令的整合,其實也是讓數據流變得簡單,提升了總體接入的應用性。
渲染模塊數據中間件設計和實現
爲何說渲染是音視頻SDK關鍵技術之一?從總體技術鏈路的角度來講,渲染模塊其實是用戶最能感知到的一個模塊。在一些複雜的場景下,渲染模塊也承載了一些數據的交互和處理。所以渲染模塊所包括的處理已經再也不只是渲染,可能會涉及到某些場景的特殊需求,例如清屏等等:多人會議的時候,至關於一個關閉畫面的效果;如多路混流,前面提到的RTC混流方案等等,渲染模塊也能夠做爲其中的一個技術方案。也包括像小程序,尤爲是一些小程序、安卓的同層設計方案,渲染方案等等。
每一個模塊甚至每一個處理的節點,都有可能存在渲染的需求,因此說咱們要將渲染模塊進一步拆分。首先經過數據管線的一個能力,把各個模塊的數據匯聚到渲染模塊,而後再進行數據的處理。這裏一般會將一些數據轉換成各個平臺的處理能力,好比CPU的Buffer,還有一些Surface等等。
數據加工,相對以前的生產流程這是新增的一個節點,它主要是爲了應對一些複雜場景,如安卓的同層渲染、Surface的建立與繪製相分離,好比業務模塊持有了Surface,可是渲染模塊會間接引用並繪製。這裏可能還會去作多路流混流的一些參數化配置,能夠把每路流做爲一個間值進行保存,也會作像圖層跟視頻幀的綁定,而後經過渲染線程同時繪製多路流。
咱們都知道渲染必需要獨立於當前的線程,不然對CPU和總體的開銷影響仍是比較大的。所以在建立完GL的環境以後,會按照GL隊列將數據進行遍歷拆分,來實現單路流,甚至是多路流的繪製。接下來就是一些標準的渲染流程,包括設置渲染的頂點,取渲染紋理,而後新增混合模式,經過綁定紋理來實現OpenGL總體的繪製。