Android多媒體SDK中的組件化設(shè)計(jì)思想

本文主要介紹金山云Android推流耸峭、短視頻SDK設(shè)計(jì)中,為保證SDK的靈活性蝉稳、可擴(kuò)展性抒蚜,在SDK組件化方向上所做的一些探索。

成熟的PC端多媒體架構(gòu)簡(jiǎn)介

PC誕生之初耘戚,就有了強(qiáng)烈的多媒體處理需求嗡髓,在幾十年發(fā)展中,比較知名的幾個(gè)多媒體框架有:

  • 微軟的DirectShow
  • 開(kāi)源跨平臺(tái)的GStreamer
  • FFMPEG
  • VLC

其中收津,F(xiàn)FMPEG更偏重于提供muxer/demuxer, encoder/decoder等實(shí)用穩(wěn)定的多媒體組件饿这,VLC更偏重于提供ALL IN ONE的軟件產(chǎn)品,其框架更多的是為特定的應(yīng)用場(chǎng)景服務(wù)撞秋,靈活性及擴(kuò)展性均不及DirectShow和GStreamer.

DirectShow和GStreamer的組件化設(shè)計(jì)

DirectShow與GStreamer均為組件化設(shè)計(jì)的多媒體框架长捧,具體工作均交由各個(gè)組件來(lái)實(shí)現(xiàn)。

DirectShow的Filter Graph

微軟提供了可視化的組件編輯工具GraphEdit吻贿,借助該工具串结,我們可以通過(guò)直觀的方式將各個(gè)DirectShow的組件連接起來(lái),并對(duì)實(shí)際效果進(jìn)行預(yù)覽舅列。
比如下圖的連接方式就實(shí)現(xiàn)了一個(gè)基本的視頻文件播放器:

DirectShow框架下的視頻播放Pipeline

根據(jù)上圖奉芦,我們可以看到,一個(gè)典型的視頻播放器包含一個(gè)視頻源分離模塊(Demuxer), 一個(gè)視頻解碼模塊剧蹂,一個(gè)音頻解碼模塊声功,一個(gè)視頻渲染模塊,一個(gè)音頻渲染模塊宠叼。在DirectShow中先巴,這些模塊被稱為Filter其爵,連接起來(lái)的各個(gè)Filter組成了一個(gè)Filter Graph。

各個(gè)Filter包含不同類型與數(shù)目的引腳伸蚯,通過(guò)引腳間的連接摩渺,實(shí)現(xiàn)數(shù)據(jù)流在不同模塊間的傳遞。
這些引腳在DirectShow中稱為Pin, 其中產(chǎn)生數(shù)據(jù)的Pin被稱為Source Pin剂邮,接受數(shù)據(jù)的Pin稱為Sink Pin摇幻。
例如分離模塊中包含音視頻兩個(gè)Source Pin, 解碼模塊包含一個(gè)Sink Pin和一個(gè)Source Pin, 渲染模塊只有一個(gè)Sink Pin。

當(dāng)然我們也可以通過(guò)選擇組合不同的Filter組成新的Filter Graph來(lái)達(dá)成不同的功能挥萌,或者增添绰姻、更改當(dāng)前Filter Graph中的Filter來(lái)動(dòng)態(tài)調(diào)整Filter Graph的功能特性。

GStreamer的Pipeline

GStreamer中存在類似的組件化結(jié)構(gòu)引瀑,例如下圖的Pipeline實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的ogg音頻文件播放器:

GStreamer框架下的ogg播放器

如圖中所示的source, demuxer, decoder, output模塊狂芋,在GStreamer中被稱為Element, Element上的引腳被稱為Pad, 輸入輸出引腳分別被稱為Source Pad和Sink Pad,而連接起來(lái)的各個(gè)Element則組成了一個(gè)Pipeline憨栽。

GStreamer同樣支持使用不同的Element及連接方式來(lái)組成不同的Pipeline帜矾,以及對(duì)其中的Element進(jìn)行增添、改動(dòng)來(lái)調(diào)整Pipeline的功能特性屑柔。

背后的工作

前面我們看到了DirectShow和GStreamer直觀屡萤、靈活的組合方式,以及強(qiáng)大的擴(kuò)展性掸宛,但要實(shí)現(xiàn)這些特性是需要框架完成大量的配套工作的死陆。

  • 模塊間連接時(shí)的協(xié)商過(guò)程。
    在多媒體處理中旁涤,存在著多種數(shù)據(jù)類型翔曲,例如未解碼的視頻數(shù)據(jù)(其中又存在多種編碼格式HEVC/AVC/VP9等)迫像,解碼后的視頻數(shù)據(jù)(又包含RGB/I420/YV12/NV12等)劈愚,不同模塊能夠處理的數(shù)據(jù)類型是不同的,因此兩種框架均實(shí)現(xiàn)了完善的協(xié)商過(guò)程闻妓。
    例如菌羽,對(duì)于不支持的連接方式,拋出錯(cuò)誤由缆,或者智能添加一個(gè)轉(zhuǎn)換模塊來(lái)完成連接注祖。

  • 模塊的動(dòng)態(tài)添加及移除處理。
    例如在播放或者編輯視頻的過(guò)程中均唉,需要添加是晨、改變或者移除一種特效Filter,就需要對(duì)已連接的模塊進(jìn)行動(dòng)態(tài)重建舔箭。
    兩種框架均實(shí)現(xiàn)了該功能罩缴,不過(guò)為此也做了大量的工作蚊逢,例如模塊在變動(dòng)過(guò)程中的狀態(tài)處理、數(shù)據(jù)流處理等箫章。

  • 模塊間的數(shù)據(jù)傳送烙荷。
    一般存在兩種方式,一種為push模式檬寂,另一種為pull模式终抽。
    兩種框架均對(duì)push及pull模式做了支持。例如上面GStremer框架下的ogg播放器桶至,ogg-demuxer的sink pad就以pull模式從file-source拉取數(shù)據(jù)昼伴,后繼模塊則均以push模式運(yùn)行,由上一級(jí)模塊的src pad將數(shù)據(jù)推送到后一級(jí)的sink pad塞茅。

  • 狀態(tài)控制和事件響應(yīng)亩码。
    GStreamer中,要控制Pipeline的開(kāi)始野瘦、暫停描沟、停止?fàn)顟B(tài),只需控制Pipeline的狀態(tài)鞭光,GStreamer框架內(nèi)部會(huì)實(shí)現(xiàn)對(duì)各個(gè)子Element的狀態(tài)切換吏廉。
    對(duì)Pipeline運(yùn)行過(guò)程中的seek操作也是類似,框架內(nèi)部會(huì)將SEEK事件發(fā)送到Pipeline的所有Sink Pad以完成seek操作惰许。

  • 錯(cuò)誤及消息處理席覆。
    GStreamer中每個(gè)Pipeline均包含一個(gè)傳遞錯(cuò)誤及消息的Bus,每個(gè)Element會(huì)將其本身產(chǎn)生的錯(cuò)誤及事件消息放進(jìn)該Bus中汹买,上層應(yīng)用通過(guò)監(jiān)聽(tīng)Bus中的事件來(lái)進(jìn)行必要的錯(cuò)誤及事件消息的處理佩伤。

  • 音視頻同步。
    兩種框架中晦毙,均提供了Clock選擇機(jī)制生巡,被選中的Clock可以被各個(gè)模塊作為參考,用來(lái)控制數(shù)據(jù)的發(fā)送節(jié)奏见妒,特別是音視頻Render模塊孤荣,可以使用相同的參考時(shí)鐘來(lái)控制渲染時(shí)機(jī),以達(dá)到音視頻同步播放的效果须揣。

金山云Android多媒體SDK的架構(gòu)設(shè)計(jì)

金山云Android多媒體SDK是以在保證性能前提下提供足夠靈活的擴(kuò)展性為目標(biāo)的盐股。為此,我們采用將SDK中的各個(gè)功能模塊組件化耻卡,然后根據(jù)應(yīng)用場(chǎng)景進(jìn)行組裝的方式來(lái)達(dá)成疯汁。

以下圖為例,展示了推流SDK中各個(gè)模塊的典型Pipeline結(jié)構(gòu):

推流SDK典型Pipeline結(jié)構(gòu)圖

圖中的各個(gè)模塊通過(guò)KSYStreamer類組合在一起卵酪,實(shí)現(xiàn)完整的直播推流功能幌蚊。而通過(guò)不同的組織方式秸谢,又可以組成一個(gè)短視頻合成SDK,如下圖所示:

image.png

框架中對(duì)模塊的形式霹肝,模塊間的組織方式的處理參考了DirectShow和GStreamer框架中的一些概念估蹄,不過(guò)框架最初只是為了推流功能所設(shè)計(jì),為兼顧實(shí)現(xiàn)難度及性能沫换,做了較大幅度的簡(jiǎn)化及限制臭蚁。

基于Pin的模塊間連接方式

在金山云Android多媒體SDK中,參照DirectShow及GStreamer的概念讯赏,以簡(jiǎn)化模塊間連接為目的垮兑,引入了Pin的概念,簡(jiǎn)要介紹如下:

在搭建推流Pipeline的時(shí)候漱挎,各個(gè)模塊之間的連接使用 SrcPinSinkPin 來(lái)完成系枪。

  • 一個(gè)Module包含若干個(gè)Pin, Module之間的連接由Pin來(lái)實(shí)現(xiàn)
  • Pin包含SrcPin和SinkPin, 分別產(chǎn)生和消耗數(shù)據(jù)流
  • SrcPin及SinkPin均是泛型類,創(chuàng)建時(shí)需要指定數(shù)據(jù)格式磕谅,相同數(shù)據(jù)格式的Pin才可以連接私爷,例如:
    • SrcPin<ImgTexFrame> -> SinkPin<ImgTexFrame>
    • SrcPin<ImgBufFrame> -> SinkPin<ImgBufFrame>
    • SrcPin<AudioBufFrame> -> SinkPin<AudioBufFrame>
  • 一個(gè)SrcPin可以連接多個(gè)SinkPin, 一個(gè)SinkPin只能跟一個(gè)SrcPin連接;
  • 所有連接或斷開(kāi)連接的操作均由SrcPin端操作膊夹;

Pin的相關(guān)操作

  • 調(diào)用SrcPin的connect接口連接兩個(gè)模塊
public void connect(SinkPin<T> sinkPin)
  • 調(diào)用SrcPin的disconnect接口斷開(kāi)連接
// 斷開(kāi)所有已連接的SinkPin, recursive為true時(shí)表示需要遞歸斷開(kāi)后面所有已連接的模塊
public void disconnect (boolean recursive)
// 斷開(kāi)指定的某個(gè)已連接的SinkPin衬浑,recursive為true時(shí)表示需要遞歸斷開(kāi)后面所有已連接的模塊
public void disconnect (SinkPin<T> sinkPin, boolean recursive)

SrcPin調(diào)用disconnect后,SinkPin端可以收到onDisconnect事件

// 源端已斷開(kāi)連接放刨,recursive為true時(shí)需要release當(dāng)前模塊工秩,并遞歸斷開(kāi)后面所有已連接的模塊
public abstract void onDisconnect (boolean recursive)
  • 處理onFormatChanged
    該接口表示數(shù)據(jù)格式的改變,源端數(shù)據(jù)初始化完成及發(fā)生改變時(shí)均需要觸發(fā)改事件进统,Sink端一般需要在該回調(diào)中進(jìn)行一些初始化的工作助币。

    • 包含SrcPin的模塊需要在合適的時(shí)機(jī)觸發(fā)onFormatChanged;
    • 包含SinkPin的模塊需要根據(jù)需要處理SrcPin觸發(fā)的onFormatChanged事件。
  • 處理onFrameAvailable

    • 包含SrcPin的模塊需要在新的一幀數(shù)據(jù)ready時(shí)觸發(fā)onFrameAvailable;
    • 包含SinkPin的模塊在onFrameAvailable中可以獲取新的一幀數(shù)據(jù)螟碎。

其他部分的處理方式

  • 模塊間連接的兼容檢測(cè)眉菱。
    參照上一節(jié)對(duì)Pin的介紹,模塊間連接的兼容檢測(cè)是通過(guò)Pin中所包含的數(shù)據(jù)類型來(lái)確定的抚芦,這個(gè)檢測(cè)在編譯階段就完成了倍谜。
    不過(guò)迈螟,即便對(duì)于同一種數(shù)據(jù)類型叉抡,例如ImgBufFrame,也包含I420, RGBA, NV12等不同的色彩格式答毫,可以處理ImgBufFrame的模塊不一定支持所有的色彩格式褥民,這時(shí)就需要使用者在組織模塊的時(shí)候留意,或者在模塊間顯式加入一個(gè)通用的色彩空間轉(zhuǎn)換模塊洗搂。

  • 模塊的動(dòng)態(tài)添加及移除處理消返。
    在已經(jīng)運(yùn)行的Pipline的A载弄、B模塊間加入模塊C時(shí),以直觀的方式撵颊,先斷開(kāi)A宇攻、B間的連接,然后使用A的SrcPin連接C的SinkPin倡勇,以C的SrcPin連接B的SinkPin逞刷。移除模塊的方式也是類似的處理。
    模塊的動(dòng)態(tài)變動(dòng)一般發(fā)生在切換音視頻濾鏡妻熊,或者切換編解碼方式的時(shí)候夸浅,SDK針對(duì)這種通用場(chǎng)景,實(shí)現(xiàn)了濾鏡管理類扔役,以及Codec管理類以方便使用帆喇。

  • 模塊間的數(shù)據(jù)傳送。
    參照上節(jié)Pin的相關(guān)接口亿胸,這里對(duì)數(shù)據(jù)流的傳遞僅實(shí)現(xiàn)了push模式坯钦,也就是數(shù)據(jù)一定是從上一級(jí)推到下一級(jí)。如果下一級(jí)模塊要實(shí)現(xiàn)媒體流的步進(jìn)控制侈玄,可以通過(guò)阻塞上一級(jí)輸入的方式來(lái)實(shí)現(xiàn)葫笼。

  • 狀態(tài)控制及消息處理等。
    框架中并未對(duì)Pipeline及其中各個(gè)模塊的狀態(tài)拗馒、消息及錯(cuò)誤信息提供一個(gè)統(tǒng)一的處理方式路星,需要開(kāi)發(fā)者在組裝各個(gè)模塊時(shí),分別控制及監(jiān)聽(tīng)各個(gè)模塊的狀態(tài)诱桂、消息及錯(cuò)誤信息等洋丐。

  • 音視頻同步。
    框架在構(gòu)建時(shí)僅針對(duì)直播推流場(chǎng)景挥等,本身并未實(shí)現(xiàn)音視頻同步的機(jī)制友绝,時(shí)鐘部分則直接使用System.nanoTime()調(diào)用獲取系統(tǒng)時(shí)間作為系統(tǒng)時(shí)鐘源。
    在需要進(jìn)行音視頻同步的模塊中肝劲,可以通過(guò)阻塞輸入過(guò)快的媒體源來(lái)達(dá)成對(duì)上級(jí)模塊的節(jié)奏控制迁客。

總結(jié)與改進(jìn)

上述框架是在構(gòu)建推流端SDK時(shí)所設(shè)計(jì),為Android直播推流SDK提供了靈活強(qiáng)大的擴(kuò)展能力辞槐,不過(guò)依然存在很多可優(yōu)化部分掷漱。

需要完善對(duì)短視頻SDK場(chǎng)景的支持

  1. 數(shù)據(jù)流部分加入pull模式的支持。
    短視頻應(yīng)用場(chǎng)景下榄檬,是以本地文件作為視頻源的卜范,其讀取文件以及demux過(guò)程不會(huì)成為整個(gè)處理過(guò)程的瓶頸,另外鹿榜,對(duì)解碼節(jié)奏的控制交由對(duì)解碼后數(shù)據(jù)進(jìn)行處理的模塊來(lái)進(jìn)行更為合理海雪,框架中加入pull模式支持對(duì)于短視頻應(yīng)用的構(gòu)建更為方便锦爵。

  2. 加入全局的Clock機(jī)制來(lái)實(shí)現(xiàn)音視頻同步。
    短視頻預(yù)覽奥裸、編輯险掀、轉(zhuǎn)場(chǎng)效果等場(chǎng)景下有音視頻同步的需求,在框架中加入全局的時(shí)鐘機(jī)制能夠簡(jiǎn)化應(yīng)用的復(fù)雜度湾宙。

簡(jiǎn)化模塊實(shí)現(xiàn)以及模塊組裝的工作

考慮引入模塊組裝管理類(Pipeline類)迷郑,連接、移除模塊時(shí)不再直接通過(guò)SrcPin進(jìn)行创倔,而是通過(guò)Pipeline類代理實(shí)現(xiàn)嗡害。通過(guò)這種方式,可以達(dá)到:

  • 對(duì)于狀態(tài)切換及資源釋放畦攘,只需要操作Pipeline的相應(yīng)接口霸妹,不需要對(duì)逐個(gè)模塊進(jìn)行操作(特殊場(chǎng)景下依然可以逐模塊控制)。
  • 可以即時(shí)獲取當(dāng)前Pipeline的鏈接結(jié)構(gòu)知押,方便debug叹螟。
  • 可以將GLRender, Clock等可能全局需要的參數(shù)自動(dòng)設(shè)置到各個(gè)模塊,以簡(jiǎn)化模塊組裝的過(guò)程台盯。
  • 能夠?qū)⒏鱾€(gè)模塊的異步事件罢绽、錯(cuò)誤消息等匯集到一處,應(yīng)用構(gòu)建者只需要監(jiān)聽(tīng)統(tǒng)一的接口静盅。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末良价,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蒿叠,更是在濱河造成了極大的恐慌明垢,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件市咽,死亡現(xiàn)場(chǎng)離奇詭異痊银,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)施绎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門溯革,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人谷醉,你說(shuō)我怎么就攤上這事致稀。” “怎么了孤紧?”我有些...
    開(kāi)封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵豺裆,是天一觀的道長(zhǎng)拒秘。 經(jīng)常有香客問(wèn)我号显,道長(zhǎng)臭猜,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任押蚤,我火速辦了婚禮蔑歌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘揽碘。我一直安慰自己次屠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布雳刺。 她就那樣靜靜地躺著劫灶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪掖桦。 梳的紋絲不亂的頭發(fā)上本昏,一...
    開(kāi)封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音枪汪,去河邊找鬼涌穆。 笑死,一個(gè)胖子當(dāng)著我的面吹牛雀久,可吹牛的內(nèi)容都是我干的宿稀。 我是一名探鬼主播,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼赖捌,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼祝沸!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起越庇,我...
    開(kāi)封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤奋隶,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后悦荒,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體唯欣,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年搬味,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了境氢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡碰纬,死狀恐怖萍聊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情悦析,我是刑警寧澤寿桨,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響亭螟,放射性物質(zhì)發(fā)生泄漏挡鞍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一预烙、第九天 我趴在偏房一處隱蔽的房頂上張望墨微。 院中可真熱鬧,春花似錦扁掸、人聲如沸翘县。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)锈麸。三九已至,卻和暖如春牺蹄,著一層夾襖步出監(jiān)牢的瞬間掐隐,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工钞馁, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留虑省,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓僧凰,卻偏偏與公主長(zhǎng)得像探颈,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子训措,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容