Android 直播流程2()

打包

視音頻在傳輸過程中需要定義相應(yīng)的格式粉寞,這樣傳輸?shù)綄Χ说臅r(shí)候才能正確地被解析出來。

1射富、HTTP-FLV

Web 2.0時(shí)代矢门,要說什么類型網(wǎng)站最火盆色,自然是以國外的Youtube,國內(nèi)的優(yōu)酷祟剔、土豆網(wǎng)站了隔躲。這類網(wǎng)站提供的視頻內(nèi)容可謂各有千秋,但它們無一例外的都使用了Flash作為視頻播放載體物延,支撐這些視頻網(wǎng)站的技術(shù)基礎(chǔ)就是——Flash 視頻(FLV) 蹭越。FLV 是一種全新的流媒體視頻格式,它利用了網(wǎng)頁上廣泛使用的Flash Player 平臺教届,將視頻整合到Flash動(dòng)畫中。也就是說驾霜,網(wǎng)站的訪問者只要能看Flash動(dòng)畫案训,自然也能看FLV格式視頻,而無需再額外安裝其它視頻插件粪糙,F(xiàn)LV視頻的使用給視頻傳播帶來了極大便利强霎。

HTTP-FLV即將音視頻數(shù)據(jù)封裝成FLV,然后通過HTTP協(xié)議傳輸給客戶端蓉冈。而作為上傳端只需要將FLV格式的視音頻傳輸?shù)椒?wù)器端即可城舞。

一般來說FLV格式的視音頻轩触,里面視頻一般使用h264格式,而音頻一般使用AAC-LC格式家夺。

FLV格式是先傳輸FLV頭信息脱柱,然后傳輸帶有視音頻參數(shù)的元數(shù)據(jù)(Metadata),然后傳輸視音頻的參數(shù)信息拉馋,然后傳輸視音頻數(shù)據(jù)榨为。

2、RTMP

RTMP是Real Time Messaging Protocol(實(shí)時(shí)消息傳輸協(xié)議)的首字母縮寫煌茴。該協(xié)議基于TCP随闺,是一個(gè)協(xié)議簇,包括RTMP基本協(xié)議及RTMPT/RTMPS/RTMPE等多種變種蔓腐。RTMP是一種設(shè)計(jì)用來進(jìn)行實(shí)時(shí)數(shù)據(jù)通信的網(wǎng)絡(luò)協(xié)議矩乐,主要用來在Flash/AIR平臺和支持RTMP協(xié)議的流媒體/交互服務(wù)器之間進(jìn)行音視頻和數(shù)據(jù)通信。

RTMP協(xié)議是Adobe公司推出的實(shí)時(shí)傳輸協(xié)議回论,主要用于基于flv格式的音視頻流的實(shí)時(shí)傳輸散罕。得到編碼后的視音頻數(shù)據(jù)后,先要進(jìn)行FLV包裝透葛,然后封包成rtmp格式笨使,然后進(jìn)行傳輸。

使用RTMP格式進(jìn)行傳輸僚害,需要先連接服務(wù)器硫椰,然后創(chuàng)建流,然后發(fā)布流萨蚕,然后傳輸相應(yīng)的視音頻數(shù)據(jù)靶草。整個(gè)發(fā)送是用消息來定義的,rtmp定義了各種形式的消息岳遥,而為了消息能夠很好地發(fā)送奕翔,又對消息進(jìn)行了分塊處理,整個(gè)協(xié)議較為復(fù)雜浩蓉。

差網(wǎng)絡(luò)處理

好的網(wǎng)絡(luò)下視音頻能夠得到及時(shí)的發(fā)送派继,不會(huì)造成視音頻數(shù)據(jù)在本地的堆積,直播效果流暢捻艳,延時(shí)較小驾窟。而在壞的網(wǎng)絡(luò)環(huán)境下,視音頻數(shù)據(jù)發(fā)送不出去认轨,則需要我們對視音頻數(shù)據(jù)進(jìn)行處理绅络。差網(wǎng)絡(luò)環(huán)境下對視音頻數(shù)據(jù)一般有四種處理方式:緩存區(qū)設(shè)計(jì)、網(wǎng)絡(luò)檢測、丟幀處理恩急、降碼率處理杉畜。

1、緩沖區(qū)設(shè)計(jì)

視音頻數(shù)據(jù)傳入緩沖區(qū)衷恭,發(fā)送者從緩沖區(qū)獲取數(shù)據(jù)進(jìn)行發(fā)送此叠,這樣就形成了一個(gè)異步的生產(chǎn)者消費(fèi)者模式。生產(chǎn)者只需要將采集匾荆、編碼后的視音頻數(shù)據(jù)推送到緩沖區(qū)拌蜘,而消費(fèi)者則負(fù)責(zé)從這個(gè)緩沖區(qū)里面取出數(shù)據(jù)發(fā)送。

2牙丽、網(wǎng)絡(luò)檢測

差網(wǎng)絡(luò)處理過程中一個(gè)重要的過程是網(wǎng)絡(luò)檢測简卧,當(dāng)網(wǎng)絡(luò)變差的時(shí)候能夠快速地檢測出來,然后進(jìn)行相應(yīng)的處理烤芦,這樣對網(wǎng)絡(luò)反應(yīng)就比較靈敏举娩,效果就會(huì)好很多。

我們這邊通過實(shí)時(shí)計(jì)算每秒輸入緩沖區(qū)的數(shù)據(jù)和發(fā)送出去數(shù)據(jù)构罗,如果發(fā)送出去的數(shù)據(jù)小于輸入緩沖區(qū)的數(shù)據(jù)铜涉,那么說明網(wǎng)絡(luò)帶寬不行,這時(shí)候緩沖區(qū)的數(shù)據(jù)會(huì)持續(xù)增多遂唧,這時(shí)候就要啟動(dòng)相應(yīng)的機(jī)制芙代。

3、丟幀處理

當(dāng)檢測到網(wǎng)絡(luò)變差的時(shí)候盖彭,丟幀是一個(gè)很好的應(yīng)對機(jī)制纹烹。視頻經(jīng)過編碼后有關(guān)鍵幀和非關(guān)鍵幀,關(guān)鍵幀也就是一副完整的圖片召边,而非關(guān)鍵幀描述圖像的相對變化铺呵。

丟幀策略多鐘多樣,可以自行定義隧熙,一個(gè)需要注意的地方是:如果要丟棄P幀(非關(guān)鍵幀)片挂,那么需要丟棄兩個(gè)關(guān)鍵幀之間的所有非關(guān)鍵幀,不然的話會(huì)出現(xiàn)馬賽克贞盯。對于丟幀策略的設(shè)計(jì)因需求而異音念,可以自行進(jìn)行設(shè)計(jì)。

4躏敢、降碼率

在Android中闷愤,如果使用了硬編進(jìn)行編碼,在差網(wǎng)絡(luò)環(huán)境下父丰,我們可以實(shí)時(shí)改變硬編的碼率,從而使直播更為流暢。當(dāng)檢測到網(wǎng)絡(luò)環(huán)境較差的時(shí)候蛾扇,在丟幀的同時(shí)攘烛,我們也可以降低視音頻的碼率。在Android sdk版本大于等于19的時(shí)候镀首,可以通過傳遞參數(shù)給MediaCodec坟漱,從而改變硬編編碼器出來數(shù)據(jù)的碼率。

(2)封裝

沿用前面的比喻更哄,封裝可以理解為采用哪種貨車去運(yùn)輸芋齿,也就是媒體的容器。 所謂容器成翩,就是把編碼器生成的多媒體內(nèi)容(視頻觅捆,音頻,字幕麻敌,章節(jié)信息等)混合封裝在一起的標(biāo)準(zhǔn)栅炒。容器使得不同多媒體內(nèi)容同步播放變得很簡單,而容器的另一個(gè)作用就是為多媒體內(nèi)容提供索引术羔,也就是說如果沒有容器存在的話一部影片你只能從一開始看到最后赢赊,不能拖動(dòng)進(jìn)度條,而且如果你不自己去手動(dòng)另外載入音頻就沒有聲音级历。

下面是幾種常見的封裝格式:

1)AVI 格式(后綴為 .avi)

2)DV-AVI 格式(后綴為 .avi)

3)QuickTime File Format 格式(后綴為 .mov)

4)MPEG 格式(文件后綴可以是 .mpg .mpeg .mpe .dat .vob .asf .3gp .mp4等)

5)WMV 格式(后綴為.wmv .asf)

6)Real Video 格式(后綴為 .rm .rmvb)

7)Flash Video 格式(后綴為 .flv)

8)Matroska 格式(后綴為 .mkv)

9)MPEG2-TS 格式 (后綴為 .ts) 目前释移,我們在流媒體傳輸,尤其是直播中主要采用的就是 FLV 和 MPEG2-TS 格式寥殖,分別用于 RTMP/HTTP-FLV 和 HLS 協(xié)議玩讳。

4.推流到服務(wù)器

推流是直播的第一公里,直播的推流對這個(gè)直播鏈路影響非常大扛禽,如果推流的網(wǎng)絡(luò)不穩(wěn)定锋边,無論我們?nèi)绾巫鰞?yōu)化,觀眾的體驗(yàn)都會(huì)很糟糕编曼。所以也是我們排查問題的第一步豆巨,如何系統(tǒng)地解決這類問題需要我們對相關(guān)理論有基礎(chǔ)的認(rèn)識。 推送協(xié)議主要有三種:

RTSP(Real Time Streaming Protocol):實(shí)時(shí)流傳送協(xié)議掐场,是用來控制聲音或影像的多媒體串流協(xié)議, 由Real Networks和Netscape共同提出的往扔; RTMP(Real Time Messaging Protocol):實(shí)時(shí)消息傳送協(xié)議,是Adobe公司為Flash播放器和服務(wù)器之間音頻熊户、視頻和數(shù)據(jù)傳輸 開發(fā)的開放協(xié)議萍膛; HLS(HTTP Live Streaming):是蘋果公司(Apple Inc.)實(shí)現(xiàn)的基于HTTP的流媒體傳輸協(xié)議; RTMP協(xié)議基于 TCP嚷堡,是一種設(shè)計(jì)用來進(jìn)行實(shí)時(shí)數(shù)據(jù)通信的網(wǎng)絡(luò)協(xié)議蝗罗,主要用來在 flash/AIR 平臺和支持 RTMP 協(xié)議的流媒體/交互服務(wù)器之間進(jìn)行音視頻和數(shù)據(jù)通信艇棕。支持該協(xié)議的軟件包括 Adobe Media Server/Ultrant Media Server/red5 等。 它有三種變種:

RTMP工作在TCP之上的明文協(xié)議串塑,使用端口1935沼琉; RTMPT封裝在HTTP請求之中,可穿越防火墻桩匪; RTMPS類似RTMPT打瘪,但使用的是HTTPS連接; RTMP 是目前主流的流媒體傳輸協(xié)議傻昙,廣泛用于直播領(lǐng)域闺骚,可以說市面上絕大多數(shù)的直播產(chǎn)品都采用了這個(gè)協(xié)議。 RTMP協(xié)議就像一個(gè)用來裝數(shù)據(jù)包的容器妆档,這些數(shù)據(jù)可以是AMF格式的數(shù)據(jù),也可以是FLV中的視/音頻數(shù)據(jù)僻爽。一個(gè)單一的連接可以通過不同的通道傳輸多路網(wǎng)絡(luò)流。這些通道中的包都是按照固定大小的包傳輸?shù)摹?

服務(wù)器流分發(fā)

直播CDN分發(fā)網(wǎng)絡(luò)

CDN过吻,中文名稱是內(nèi)容分發(fā)網(wǎng)絡(luò)进泼,可以用來分發(fā)直播、點(diǎn)播纤虽、網(wǎng)頁靜態(tài)文件乳绕、小文件等等,幾乎我們?nèi)粘S玫降幕ヂ?lián)網(wǎng)產(chǎn)品都是有CDN在背后提供支持”浦剑現(xiàn)在有很多公司在提供云服務(wù)洋措,這是在CDN的基礎(chǔ)上,提供了更豐富的一站式接入的云服務(wù)能力杰刽。例如PP云服務(wù)為客戶提供直播菠发、點(diǎn)播、靜態(tài)文件贺嫂、短視頻等多種云服務(wù)和CDN加速能力滓鸠。

概念:負(fù)載均衡、CDN緩存第喳、回源糜俗、就近原則

在這樣的架構(gòu)下,會(huì)延伸出這樣的幾個(gè)概念:

當(dāng)觀眾人數(shù)不太多的時(shí)候曲饱,例如總共只有1000人悠抹,那么是選擇讓某一臺服務(wù)器服務(wù)這1000人,還是3臺服務(wù)器分擔(dān)1000人扩淀,還是2臺楔敌?機(jī)器也會(huì)有新舊之分,老機(jī)器只能抗800數(shù)量驻谆,那要怎么來分配呢卵凑?等等問題庆聘。這里就需要有一個(gè)策略來做資源的分配。這個(gè)策略叫做:負(fù)載均衡勺卢。

因?yàn)橛^眾看到的數(shù)據(jù)都是一樣的掏觉,所以呢,數(shù)據(jù)會(huì)在服務(wù)器1值漫、2、3上都存儲(chǔ)一份织盼。這個(gè)概念叫做:CDN緩存杨何。

當(dāng)分配到服務(wù)器1的第一個(gè)觀眾進(jìn)入時(shí),服務(wù)器1是沒有存儲(chǔ)數(shù)據(jù)的沥邻,它會(huì)向服務(wù)器-0獲取數(shù)據(jù)危虱,這個(gè)過程叫做:回源;相應(yīng)的唐全,服務(wù)器-0被稱為:源站埃跷;觀眾請求的數(shù)據(jù)如果由CDN緩存提供,叫做緩存命中邮利,所有用戶請求的緩存命中比例叫做緩存命中率弥雹,它是衡量CDN質(zhì)量的關(guān)鍵指標(biāo)。

一名新進(jìn)入的觀眾會(huì)被分配到哪一臺服務(wù)器上呢延届?理論上剪勿,這臺服務(wù)器距離用戶的網(wǎng)絡(luò)鏈路越短、不跨網(wǎng)方庭,數(shù)據(jù)的傳輸?shù)姆€(wěn)定性就越好厕吉,這個(gè)叫做:就近原則。

跨地區(qū)械念、多運(yùn)營商覆蓋的CDN

由于就近原則的存在头朱,為了滿足全國甚至全世界不同地方的人,那我們就需要把服務(wù)器分布在不同的地區(qū)龄减。又由于不同的網(wǎng)絡(luò)運(yùn)營商之間的網(wǎng)絡(luò)傳輸會(huì)有穩(wěn)定性問題项钮,那么就需要在不同的網(wǎng)絡(luò)運(yùn)營商里也放置服務(wù)器,于是欺殿,一個(gè)CDN網(wǎng)絡(luò)就成型了:

傳統(tǒng)直播一般是基于CDN網(wǎng)絡(luò)進(jìn)行分發(fā)寄纵,可支持大規(guī)模并發(fā)(并發(fā)數(shù)取決于CDN網(wǎng)絡(luò)容量)。與傳統(tǒng)CDN的大文件脖苏,小文件分發(fā)不同程拭,由于直播分布區(qū)域分散,一般除了提供播放端的下行分發(fā)網(wǎng)絡(luò)外棍潘,還提供上行主播推流匯聚網(wǎng)絡(luò)恃鞋。只有 一些直播內(nèi)容資源集中的業(yè)務(wù)方崖媚,會(huì)要求直播CDN直接回自己的源站,如電視臺恤浪。

上行匯聚

目前傳統(tǒng)直播 CDN 上行一般使用 RTMP 協(xié)議畅哑,當(dāng)然也有一些使用 UDP(UDP 方式由于需要 SDK 配合,目前行業(yè)內(nèi)有人在做水由,但是需要綁定 SDK)荠呐。另外國外還有使用 http-ts 的方式進(jìn)行推流的,可參見 nginx-rtmp 項(xiàng)目大神開源的 nginx-ts-module砂客。當(dāng)然扎阶,目前使用這種方式抚垃,關(guān)鍵問題還是在于端的支持問題,而該開源項(xiàng)目目前只支持 HLS 和 Dash 的播放。

除了主播推流以外轨奄,還有一種方式即從匯聚點(diǎn)到業(yè)務(wù)方源站去拉流的方式

下行分發(fā)

目前下行分發(fā)一般使用的協(xié)議挽铁,rtmp袋倔,http-flv坦辟,hls 三種協(xié)議。這三種協(xié)議的優(yōu)劣声离,網(wǎng)上已經(jīng)有很多文章了芒炼,一般從終端兼容性,延遲术徊,首屏幾個(gè)維度去考慮焕议,這里就不在進(jìn)行比較。

rtmp 和 http-flv

由于 rtmp 協(xié)議在發(fā)送數(shù)據(jù)前交互次數(shù)較多弧关,比較追求首屏的直播平臺一般都會(huì)選擇 http-flv 協(xié)議作為下行分發(fā)協(xié)議盅安,線上環(huán)境測試效果平均會(huì)增加 100-200 ms 左右的時(shí)間,網(wǎng)絡(luò)越差世囊,這個(gè)值越大别瞭。

rtmp 和 http-flv 的延遲可以做到 3s 以內(nèi),但是由于網(wǎng)絡(luò)環(huán)境的復(fù)雜株憾,過低的延遲會(huì)導(dǎo)致卡頓率的提升蝙寨,所以一般 CDN 會(huì)用戶接入時(shí),給用戶多發(fā)幾秒鐘的數(shù)據(jù)(一般是 5-8s)嗤瞎,填充播放端緩沖區(qū)墙歪,來抗網(wǎng)絡(luò)端的抖動(dòng)。細(xì)節(jié)技術(shù)會(huì)在后面的文章中介紹

hls

hls 對 Android 端和 IOS 端支持較好贝奇,并且對 P2P 的支持也較好虹菲,一般對延遲要求不高的直播平臺(如體育賽事)會(huì)選用這個(gè)協(xié)議。

hls 的延遲一般和切片大小有關(guān)掉瞳,一般切片是 6-8s 一個(gè)片毕源,這個(gè)大小對一般主播推流 GOP 適配最好浪漠。過高會(huì)導(dǎo)致延遲加大,過低霎褐,可能切片里就沒有關(guān)鍵幀址愿。一般 m3u8 文件里會(huì)有 3 個(gè) ts 文件,播放器會(huì)在下完兩個(gè)片以后冻璃,開始播放响谓,并且同時(shí)下第三個(gè)片。因此一般 hls 的時(shí)延在 15s 左右省艳。

當(dāng)然如果用戶調(diào)小 GOP(1s)歌粥,CDN 端將切片方式配置為按 GOP 切片的方式,HLS 實(shí)際也可以做到 5s 以內(nèi)延遲的拍埠。當(dāng)然壞處就是會(huì)導(dǎo)致卡頓率變高

拉流播放器播放

視音頻編解碼一般分為兩種,一種是硬編實(shí)現(xiàn)土居,一種是軟編實(shí)現(xiàn)枣购。這兩種方式各有優(yōu)缺點(diǎn),硬編性能好擦耀,但是需要對兼容性進(jìn)行相應(yīng)處理棉圈;軟編兼容性好,可以進(jìn)行一些參數(shù)設(shè)置眷蜓,但是軟編一般性能較差分瘾,引入相關(guān)的編解碼庫往往會(huì)增大app的整體體積,而且還需要寫相應(yīng)的jni接口吁系。

先介紹幾個(gè)和視音頻相關(guān)的類,通過這幾個(gè)類的組合使用上岗,其實(shí)是能變換出許多視音頻處理的相關(guān)功能

MediaMetadataRetriever::用來獲取視頻的相關(guān)信息呆瞻,例如視頻寬高、時(shí)長买窟、旋轉(zhuǎn)角度、碼率等等。

MediaExtractor::視音頻分離器,將一些格式的視頻分離出視頻軌道和音頻軌道。

MediaCodec:視音頻相應(yīng)的編解碼類攻晒。

MediaMuxer:視音頻合成器,將視頻和音頻合成相應(yīng)的格式。

MediaFormat:視音頻相應(yīng)的格式信息。

MediaCodec.BufferInfo:存放ByteBuffer相應(yīng)信息的類。

MediaCrypto:視音頻加密解密處理的類。

MediaCodecInfo:視音頻編解碼相關(guān)信息的類。

MediaFormat和MediaCodec.BufferInfo是串起上面幾個(gè)類的橋梁矾策,上面幾個(gè)視音頻處理的類通過這兩個(gè)橋梁建立起聯(lián)系吼鱼,從而變化出相應(yīng)的功能

MediaMetadataRetriever

MediaMetadataRetriever用來獲取視音頻的相關(guān)信息琐谤,MediaMetadataRetriever的使用十分簡單,傳入相應(yīng)的文件路徑創(chuàng)建MediaMetadataRetriever翻屈,之后便可以得到視頻的相關(guān)參數(shù)厘贼。

MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever();

metadataRetriever.setDataSource(file.getAbsolutePath());

String widthString = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);

if(!TextUtils.isEmpty(widthString)) {

? ? width = Integer.valueOf(widthString);

}

String heightString = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);

if(!TextUtils.isEmpty(heightString)) {

? ? height = Integer.valueOf(heightString);

}

String durationString = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);

if(!TextUtils.isEmpty(durationString)) {

? ? duration = Long.valueOf(durationString);

}

String bitrateString = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE);

if(!TextUtils.isEmpty(bitrateString)) {

? ? bitrate = Integer.valueOf(bitrateString);

}

String degreeStr = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);

if (!TextUtils.isEmpty(degreeStr)) {

? ? degree = Integer.valueOf(degreeStr);

}

metadataRetriever.release();

MediaExtractor

MediaExtractor用來對視音頻進(jìn)行分離串述,對文件中的視頻音頻軌道進(jìn)行分離能做大量的事情,比如說要寫一個(gè)播放器右蕊,那么首先的第一個(gè)步驟是分離出視頻音頻軌道琼稻,然后進(jìn)行相應(yīng)的處理。

MediaExtractor的創(chuàng)建和MediaMetadataRetriever一樣十分簡單尤泽,只需要傳入相應(yīng)的文件路徑欣簇。通過getTrackCount()可以得到相應(yīng)的軌道數(shù)量,一般情況下視音頻軌道都有坯约,有些時(shí)候可能只有視頻熊咽,有些時(shí)候可能只有音頻。軌道的序號從0開始闹丐,通過getTrackFormat(int index)方法可以得到相應(yīng)的MediaFormat横殴,而通過MediaFormat可以判斷出軌道是視頻還是音頻。通過selectTrack(int index)方法選擇相應(yīng)序號的軌道卿拴。

public static MediaExtractor createExtractor(String path) throws IOException {

? ? MediaExtractor extractor;

? ? File inputFile = new File(path);? // must be an absolute path

? ? if (!inputFile.canRead()) {

? ? ? ? throw new FileNotFoundException("Unable to read " + inputFile);

? ? }

? ? extractor = new MediaExtractor();

? ? extractor.setDataSource(inputFile.toString());

? ? return extractor;

}

public static String getMimeTypeFor(MediaFormat format) {

? ? return format.getString(MediaFormat.KEY_MIME);

}

public static int getAndSelectVideoTrackIndex(MediaExtractor extractor) {

? ? for (int index = 0; index < extractor.getTrackCount(); ++index) {

? ? ? ? if (isVideoFormat(extractor.getTrackFormat(index))) {

? ? ? ? ? ? extractor.selectTrack(index);

? ? ? ? ? ? return index;

? ? ? ? }

? ? }

? ? return -1;

}

public static int getAndSelectAudioTrackIndex(MediaExtractor extractor) {

? ? for (int index = 0; index < extractor.getTrackCount(); ++index) {

? ? ? ? if (isAudioFormat(extractor.getTrackFormat(index))) {

? ? ? ? ? ? extractor.selectTrack(index);

? ? ? ? ? ? return index;

? ? ? ? }

? ? }

? ? return -1;

}

public static boolean isVideoFormat(MediaFormat format) {

? ? return getMimeTypeFor(format).startsWith("video/");

}

public static boolean isAudioFormat(MediaFormat format) {

? ? return getMimeTypeFor(format).startsWith("audio/");

}

選擇好一個(gè)軌道后衫仑,便可以通過相應(yīng)方法提取出相應(yīng)軌道的數(shù)據(jù)。extractor.seekTo(startTime, SEEK_TO_PREVIOUS_SYNC)方法可以直接跳轉(zhuǎn)到開始解析的位置堕花。extractor.readSampleData(byteBuffer, 0)方法則可以將數(shù)據(jù)解析到byteBuffer中文狱。extractor.advance()方法則將解析位置進(jìn)行前移,準(zhǔn)備下一次解析缘挽。

下面是MediaExtractor一般的使用方法瞄崇。

MediaExtractor extractor = new MediaExtractor();

extractor.setDataSource(...);

int numTracks = extractor.getTrackCount();

for (int i = 0; i < numTracks; ++i) {

? ? MediaFormat format = extractor.getTrackFormat(i);

? ? String mime = format.getString(MediaFormat.KEY_MIME);

? ? if (weAreInterestedInThisTrack) {

? ? ? ? extractor.selectTrack(i);

? ? }

}

ByteBuffer inputBuffer = ByteBuffer.allocate(...)

while (extractor.readSampleData(inputBuffer, ...) >= 0) {

? ? int trackIndex = extractor.getSampleTrackIndex();

? ? long presentationTimeUs = extractor.getSampleTime();

? ? ...

? ? extractor.advance();

}

extractor.release();

extractor = null;

MediaCodec

MediaCodec是Android視音頻里面最為重要的類,它主要實(shí)現(xiàn)的功能是對視音頻進(jìn)行編解碼處理壕曼。在編碼方面苏研,可以對采集的視音頻數(shù)據(jù)進(jìn)行編碼處理,這樣的話可以對數(shù)據(jù)進(jìn)行壓縮腮郊,從而實(shí)現(xiàn)以較少的數(shù)據(jù)量存儲(chǔ)視音頻信息摹蘑。在解碼方面,可以解碼相應(yīng)格式的視音頻數(shù)據(jù)轧飞,從而得到原始的可以渲染的數(shù)據(jù)衅鹿,從而實(shí)現(xiàn)視音頻的播放。

一般場景下音頻使用的是AAC-LC的格式过咬,而視頻使用的是H264格式塘安。這兩種格式在MediaCodec支持的版本(Api 16)也都得到了很好的支持。在直播過程中援奢,先采集視頻和音頻數(shù)據(jù)兼犯,然后將原始的數(shù)據(jù)塞給編碼器進(jìn)行硬編,然后得到相應(yīng)的編碼后的AAC-LC和H264數(shù)據(jù)。

在Android系統(tǒng)中切黔,MediaCodec支持的格式有限砸脊,在使用MediaCodec之前需要對硬編類型的支持進(jìn)行檢測,如果MediaCodec支持再進(jìn)行使用纬霞。

1凌埂、檢查

在使用硬編編碼器之前需要對編碼器支持的格式進(jìn)行檢查,在Android中可以使用MediaCodecInfo這個(gè)類來獲取系統(tǒng)對視音頻硬編的支持情況诗芜。

下面的代碼是判斷MediaCodec是否支持某個(gè)MIME:

private static MediaCodecInfo selectCodec(String mimeType) {

? ? int numCodecs = MediaCodecList.getCodecCount();

? ? for (int i = 0; i < numCodecs; i++) {

? ? ? ? MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);

? ? ? ? if (!codecInfo.isEncoder()) {

? ? ? ? ? ? continue;

? ? ? ? }

? ? ? ? String[] types = codecInfo.getSupportedTypes();

? ? ? ? for (int j = 0; j < types.length; j++) {

? ? ? ? ? ? if (types[j].equalsIgnoreCase(mimeType)) {

? ? ? ? ? ? ? ? return codecInfo;

? ? ? ? ? ? }

? ? ? ? }

? ? }

? ? return null;

}

根據(jù)之前的講述瞳抓,在Android系統(tǒng)中有著不同的顏色格式,有著各種類型的YUV顏色格式和RGB顏色格式伏恐。在攝像頭采集的文章中已經(jīng)講述孩哑,需要設(shè)置攝像頭采集的圖像顏色格式,一般來說設(shè)置為ImageFormat.NV21翠桦,之后在攝像頭PreView的回調(diào)中得到相應(yīng)的圖像數(shù)據(jù)横蜒。

在Android系統(tǒng)中不同手機(jī)中的編碼器支持著不同的顏色格式,一般情況下并不直接支持NV21的格式销凑,這時(shí)候需要將NV21格式轉(zhuǎn)換成為編碼器支持的顏色格式丛晌。在攝像頭采集的文章中已經(jīng)詳細(xì)講述YUV圖像格式和相應(yīng)的存儲(chǔ)規(guī)則,YUV圖像格式的轉(zhuǎn)換可以使用LibYuv斗幼。

這里說一下MediaCodec支持的圖像格式澎蛛。一般來說Android MediaCodec支持如下幾種格式:

/**

? ? * Returns true if this is a color format that this test code understands (i.e. we know how

? ? * to read and generate frames in this format).

? ? */

? ? private static boolean isRecognizedFormat(int colorFormat) {

? ? ? ? switch (colorFormat) {

? ? ? ? ? ? // these are the formats we know how to handle for this test

? ? ? ? ? ? case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:

? ? ? ? ? ? case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar:

? ? ? ? ? ? case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:

? ? ? ? ? ? case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:

? ? ? ? ? ? case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar:

? ? ? ? ? ? ? ? return true;

? ? ? ? ? ? default:

? ? ? ? ? ? ? ? return false;

? ? ? ? }

? ? }

上面大致統(tǒng)計(jì)了Android各種手機(jī)MediaCodec支持的各種顏色格式,上面5個(gè)類型是比較常用的類型蜕窿。

另外MediaCodec支持Surface的方式輸入和輸出谋逻,當(dāng)編碼的時(shí)候只需要在Surface上進(jìn)行繪制就可以輸入到編碼器,而解碼的時(shí)候可以將解碼圖像直接輸出到Surface上渠羞,使用起來相當(dāng)方便斤贰,需要在Api 18或以上智哀。

2次询、創(chuàng)建

當(dāng)需要使用MediaCodec的時(shí)候,首先需要根據(jù)視音頻的類型創(chuàng)建相應(yīng)的MediaCodec瓷叫。在直播項(xiàng)目中視頻使用了H264屯吊,而音頻使用了AAC-LC。在Android中創(chuàng)建直播的音頻編碼器需要傳入相應(yīng)的MIME摹菠,AAC-LC對應(yīng)的是audio/mp4a-latm盒卸,而H264對應(yīng)的是video/avc。如下的代碼展示了兩個(gè)編碼器的創(chuàng)建次氨,其中視頻編碼器的輸入設(shè)置成為了Surface的方式蔽介。

? ? //Audio

? ? public static MediaCodec getAudioMediaCodec() throws IOException {

? ? ? ? int size = AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);

? ? ? ? MediaFormat format = MediaFormat.createAudioFormat("audio/mp4a-latm", 44100, 1);

? ? ? ? format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);

? ? ? ? format.setInteger(MediaFormat.KEY_BIT_RATE, 64 * 1000);

? ? ? ? format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 44100);

? ? ? ? format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, size);

? ? ? ? format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);

? ? ? ? MediaCodec mediaCodec = MediaCodec.createEncoderByType("audio/mp4a-latm");

? ? ? ? mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);

? ? ? ? return mediaCodec;

? ? }

? ? //Video

? ? public static MediaCodec getVideoMediaCodec() throws IOException {

? ? ? ? int videoWidth = getVideoSize(1280);

? ? ? ? int videoHeight = getVideoSize(720);

? ? ? ? MediaFormat format = MediaFormat.createVideoFormat("video/avc", videoWidth, videoHeight);

? ? ? ? format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);

? ? ? ? format.setInteger(MediaFormat.KEY_BIT_RATE, 1300* 1000);

? ? ? ? format.setInteger(MediaFormat.KEY_FRAME_RATE, 15);

? ? ? ? format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);

? ? ? ? format.setInteger(MediaFormat.KEY_BITRATE_MODE,MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR);

? ? ? ? format.setInteger(MediaFormat.KEY_COMPLEXITY,MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR);

? ? ? ? MediaCodec mediaCodec = MediaCodec.createEncoderByType("video/avc");

? ? ? ? mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);

? ? ? ? return mediaCodec;

? ? }

? ? // We avoid the device-specific limitations on width and height by using values that

? ? // are multiples of 16, which all tested devices seem to be able to handle.

? ? public static int getVideoSize(int size) {

? ? ? ? int multiple = (int) Math.ceil(size/16.0);

? ? ? ? return multiple*16;

? ? }

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子虹蓄,更是在濱河造成了極大的恐慌犀呼,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件薇组,死亡現(xiàn)場離奇詭異外臂,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)律胀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門宋光,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人炭菌,你說我怎么就攤上這事罪佳。” “怎么了娃兽?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵菇民,是天一觀的道長。 經(jīng)常有香客問我投储,道長第练,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任玛荞,我火速辦了婚禮娇掏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘勋眯。我一直安慰自己婴梧,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布客蹋。 她就那樣靜靜地躺著塞蹭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪讶坯。 梳的紋絲不亂的頭發(fā)上番电,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機(jī)與錄音辆琅,去河邊找鬼漱办。 笑死,一個(gè)胖子當(dāng)著我的面吹牛婉烟,可吹牛的內(nèi)容都是我干的娩井。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼似袁,長吁一口氣:“原來是場噩夢啊……” “哼洞辣!你這毒婦竟也來了咐刨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤扬霜,失蹤者是張志新(化名)和其女友劉穎所宰,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體畜挥,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仔粥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蟹但。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片躯泰。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖华糖,靈堂內(nèi)的尸體忽然破棺而出麦向,到底是詐尸還是另有隱情,我是刑警寧澤客叉,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布诵竭,位于F島的核電站,受9級特大地震影響兼搏,放射性物質(zhì)發(fā)生泄漏卵慰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一佛呻、第九天 我趴在偏房一處隱蔽的房頂上張望裳朋。 院中可真熱鬧,春花似錦吓著、人聲如沸鲤嫡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽暖眼。三九已至,卻和暖如春纺裁,著一層夾襖步出監(jiān)牢的瞬間诫肠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工对扶, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留区赵,地道東北人惭缰。 一個(gè)月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓浪南,卻偏偏與公主長得像,于是被迫代替她去往敵國和親漱受。 傳聞我的和親對象是個(gè)殘疾皇子络凿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

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