1. 程式人生 > >Android之音視訊點、直播模組開發

Android之音視訊點、直播模組開發

隨著音視訊領域的火熱,在很多領域(教育,遊戲,娛樂,體育,跑步,餐飲,音樂等)嘗試做音視訊直播、點播功能,那麼,如何快速學習音視訊基礎知識,瞭解音視訊編解碼的傳輸協議,編解碼方式,以及如何技術選型,如何解決遇到的難題呢,下面來看看,歡迎大咖交流。

一. 音視訊的基礎知識

1.1 基本概念

視訊是什麼

<code class="hljs mel has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">靜止的畫面叫影象(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">picture</span>)。連續的影象變化每秒超過<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">24</span>幀(frame)畫面以上時,根椐視覺暫留原理,
人眼無法辨別每付單獨的靜態畫面,看上去是平滑連續的視覺效果。這樣的連續畫面叫視訊。
當連續影象變化每秒低於<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">24</span>幀畫面時,人眼有不連續的感覺叫動畫(cartoon)</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul>

流媒體

<code class="hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">指採用流式傳輸的方式在Internet / Intranet播放的媒體格式.流媒體的資料流隨時傳送隨 時播放,只是在開始時有些延遲
邊下載邊播入的流式傳輸方式不僅使啟動延時大幅度地縮短,而且對系統快取容量的需求也大大降低,極大地減少使用者用在等待的時間</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>

解析度

<code class="hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">解析度是一個表示平面圖像精細程度的概念,通常它是以橫向和縱向點的數量來衡量的,表示成水平點數垂直點數的形式,
在計算機顯示領域我們也表示成“每英寸畫素”(ppi).在一個固定的平面內,解析度越高,意味著可使用的點數越多,影象越細緻</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>

碼流

<code class="hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"> 資料傳輸時單位時間傳送的資料位數,可以理解其為取樣率,單位時間內取樣率越大,精度就越高,處理出來的檔案就越接近原始檔案,但是檔案體積與取樣率是成正比的
 如何用最低的位元速率達到最少的失真,一般我們用的單位是kbps即千位每秒</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>

幀率

<code class="hljs livecodeserver has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">幀/秒(frames per <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">second</span>)的縮寫,也稱為幀速率,測量用於儲存、顯示動態視訊的資訊數量。每一幀都是靜止的圖象,快速連續地顯示幀便形成了運動的假象。
每秒鐘幀數 (fps) 愈多,所顯示的動作就會愈流暢,可理解為<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>秒鐘時間裡重新整理的圖片的幀數,也可以理解為圖形處理器每秒鐘能夠重新整理幾次,也就是指每秒鐘能夠播放(或者錄製)多少格畫面。</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>

1.2 多媒體的格式分類

封裝格式(專業上講叫容器,通俗的叫檔案格式),視訊編解碼,音訊編解碼
####1.1常見的封裝格式
* MPEG : 編碼採用的容器,具有流的特性。裡面又分為 PS,TS 等,PS 主要用於 DVD 儲存,TS 主要用於 HDTV.
* MPEG Audio Layer 3 :大名鼎鼎的 MP3,已經成為網路音訊的主流格式,能在 128kbps 的位元速率接近 CD 音質
* MPEG-4(Mp4) : 編碼採用的容器,基於 QuickTime MOV 開發,具有許多先進特性;實際上是對Apple公司開發的MOV格式(也稱Quicktime格式)的一種改進.
* MKV: 它能把 Windows Media Video,RealVideo,MPEG-4 等視訊音訊融為一個檔案,而且支援多音軌,支援章節字幕等;開源的容器格式
* 3GP : 3GPP視訊採用的格式, 主要用於流媒體傳送;3GP其實是MP4格式的一種簡化版本,是手機視訊格式的絕對主流.
* MOV : QuickTime 的容器,恐怕也是現今最強大的容器,甚至支援虛擬現實技術,Java等,它的變種 MP4,3GP都沒有這麼厲害;廣泛應用於Mac OS作業系統,在Windows作業系統上也可相容,但是遠比不上AVI格式流行
* AVI : 最常見的音訊視訊容器,音訊視訊交錯(Audio Video Interleaved)允許視訊和音訊交錯在一起同步播放.
* WAV : 一種音訊容器,大家常說的 WAV 就是沒有壓縮的 PCM 編碼,其實 WAV 裡面還可以包括 MP3 等其他 ACM 壓縮編碼
等等

1.3 流媒體協議(RTP RTCP RTSP RTMP HLS)

  1. RTP RTCP RTSP
RTP :(Real-time Transport Protocol)是用於Internet上針對多媒體資料流的一種傳輸層協議.RTP協議和RTP控制協議RTCP一起使用,而且它是建立在UDP協議上的RTCP:Real-time Transport Control Protocol或RTP Control Protocol或簡寫RTCP)實時傳輸控制協議,是實時傳輸協議(RTP)的一個姐妹協議RTP協議和RTP控制協議RTCP一起使用,而且它是建立在UDP協議上的RTSP:(Real Time Streaming Protocol)是用來控制聲音或影像的多媒體串流協議,RTSP提供了一個可擴充套件框架,使實時資料,如音訊與視訊的受控、點播成為可能。資料來源包括現場資料與儲存在剪輯中的資料。該協議目的在於控制多個數據傳送連線,為選擇傳送通道,如UDP、多播UDP與TCP提供途徑,併為選擇基於RTP上傳送機制提供方法傳輸時所用的網路通訊協定並不在其定義的範圍內,伺服器端可以自行選擇使用TCP或UDP來傳送串流內容,比較能容忍網路延遲RTP不像http和ftp可完整的下載整個影視檔案,它是以固定的資料率在網路上傳送資料,客戶端也是按照這種速度觀看影視檔案,當影視畫面播放過後,就不可以再重複播放,除非重新向伺服器端要求資料。RTSP與RTP最大的區別在於:RTSP是一種雙向實時資料傳輸協議,它允許客戶端向伺服器端傳送請求,如回放、快進、倒退等操作。當然,RTSP可基於RTP來傳送資料,還可以選擇TCP、UDP、組播UDP等通道來發送資料,具有很好的擴充套件性。它時一種類似與http協議的網路應用層協議
  1. RTMP
RTMP(Real Time Messaging Protocol)實時訊息傳送協議是Adobe Systems公司為Flash播放器和伺服器之間音訊、視訊和資料傳輸 開發的開放協議
  1. HLS
HTTP Live Streaming(HLS)是蘋果公司(Apple Inc.)實現的基於HTTP的流媒體傳輸協議,可實現流媒體的直播和點播,主要應用在iOS系統,為iOS裝置(如iPhone、iPad)提供音視訊直播和點播方案。HLS點播,基本上就是常見的分段HTTP點播,不同在於,它的分段非常小。相對於常見的流媒體直播協議,例如RTMP協議、RTSP協議、MMS協議等,HLS直播最大的不同在於,直播客戶端獲取到的,並不是一個完整的資料流。HLS協議在伺服器端將直播資料流儲存為連續的、很短時長的媒體檔案(MPEG-TS格式),而客戶端則不斷的下載並播放這些小檔案,因為伺服器端總是會將最新的直播資料生成新的小檔案,這樣客戶端只要不停的按順序播放從伺服器獲取到的檔案,就實現了直播。由此可見,基本上可以認為,HLS是以點播的技術方式來實現直播。由於資料通過HTTP協議傳輸,所以完全不用考慮防火牆或者代理的問題,而且分段檔案的時長很短,客戶端可以很快的選擇和切換位元速率,以適應不同頻寬條件下的播放。不過HLS的這種技術特點,決定了它的延遲一般總是會高於普通的流媒體直播協議。 

二. android音視訊的開發

播放流程: 獲取流–>解碼–>播放 
錄製播放路程: 錄製音訊視訊–>剪輯–>編碼–>上傳伺服器 別人播放. 
直播過程 : 錄製音視訊–>編碼–>流媒體傳輸–>伺服器—>流媒體傳輸到其他app–>解碼–>播放

幾個重要的環節

  1. 錄製音視訊 AudioRecord/MediaRecord
  2. 視訊剪輯 mp4parser 或ffmpeg
  3. 音視訊編碼 aac&h264
  4. 上傳大檔案 網路框架,進度監聽,斷點續傳
  5. 流媒體傳輸 流媒體傳輸協議rtmp rtsp hls
  6. 音視訊解碼 aac&h264
  7. 渲染播放 MediaPlayer

問題 
Android本身有提供MediaPlayer,那麼mediaplayer支援哪些格式的流媒體協議吶?又支援哪些解碼器吶?相容性如何,性功能如何? 
Supported Media Formats 
Media Playback

MPEG-2:制定於1994年,設計目標為高階工業標準的影象質量以及更高的傳輸率。這種格式主要應用在DVD/SVCD的製作(壓縮)方面,同時在一些HDTV(高清晰電視廣播)和一些高要求視訊編輯、處理上面也有相當的應用。使用MPEG-2的壓縮演算法,可以把一部120分鐘長的電影壓縮到48GB的大小。這種視訊格式的副檔名包括.mpg.mpe.mpeg.m2v及DVD光碟上的.vob檔案等。MPEG-4:制定於1998年,MPEG-4是為了播放流式媒體的高質量視訊而專門設計的,它可利用很窄的頻寬,通過幀重建技術,壓縮和傳輸資料,以求使用最少的資料獲得最佳的影象質量。目前MPEG-4最有吸引力的地方在於它能夠儲存接近於DVD畫質的小體積視訊檔案。另外,這種檔案格式還包含了以前MPEG壓縮標準所不具備的位元率的可伸縮性、動畫精靈、互動性甚至版權保護等一些特殊功能。這種視訊格式的副檔名包括.asf.mov和DivX AVI等。

從上圖我們也看到,android平臺自身支援的音視訊解碼是有限的 一般的mp3 mp4….3gp 等等 其他的只能自己解碼了。。。 
那麼如何解碼吶? 
經過一番調研對比,選擇樂ijkplayer.

三. ijkplayer的引入&介紹&使用

正如上文所說,android本事對音視訊流媒體傳輸協議,以及音視訊編解碼支援有限.所以對於直播類應用,要自己解碼

3.1 調研過程

先說下 vitamio這個是功能很強大,但是企業收費版的,個人使用者可以玩玩. 
目前WebRtc只適合小範圍(8人以內)音視訊會議,不適合做直播可以用WebRTC來做視訊直播嗎? 
接下來介紹下 ffmpeg vlc ijkplayer以及選擇方案

ffmpeg是一個非常強大的音視訊編解碼開源庫,目前市場上流行的播放器,大部分都是基於此開發的,包括暴風,騰訊,等等以及上面提到的vitamio,vlc,ijkplayer 
關於ffmpeg原始碼分析,有興趣的請看雷霄驊(leixiaohua1020)的專欄

vlc 支援android開發 ,ijkplayer也支援. 通過反編譯網易雲音樂,以及YY等音視訊app.發現網易雲音樂,鬥魚用的ijkplayer,YY用的VLC. 
那麼vlc&ijkplayer相比較各有什麼優缺點吶,該如何選擇吶?[待深入使用,或者用過的可以交流下] 
其實這個沒有深入分析,ijkplayer是bilibili開源的音視訊編解碼庫,對android,iOS進行和很好的抽取封裝,易於編譯使用.vlc嘗試過,稍微複雜些.

3.2 ijkplayer的匯入&編譯&使用

如果不需要對原始碼進行修改,在app的build.gradle中加入如下依賴即可

dependencies {
    # required, enough for most devices.
    compile 'tv.danmaku.ijk.media:ijkplayer-java:0.4.5.1'
    compile 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.4.5.1'

    # Other ABIs: optional
    compile 'tv.danmaku.ijk.media:ijkplayer-armv5:0.4.5.1'
    compile 'tv.danmaku.ijk.media:ijkplayer-arm64:0.4.5.1'
    compile 'tv.danmaku.ijk.media:ijkplayer-x86:0.4.5.1'

    # ExoPlayer as IMediaPlayer: optional, experimental
    compile 'tv.danmaku.ijk.media:ijkplayer-exo:0.4.5.1'
}


當然如何你想對其原始碼進行修改,採用如下方式 
1. 需要 
下載配置 NDK r10e 
配置androidsdk 
# add these lines to your ~/.bash_profile or ~/.profile 
# export ANDROID_SDK= 
# export ANDROID_NDK= 
2.

Build Android

git clone https://github.com/Bilibili/ijkplayer.git ijkplayer-android
cd ijkplayer-android
git checkout -B latest k0.4.5.1

./init-android.sh   //此步用於下載ffmpeg,初始化配置

cd android/contrib
./compile-ffmpeg.sh clean
./compile-ffmpeg.sh all

cd ..
./compile-ijk.sh all

然後通過androidstudio把生成的project匯入工程
# Android Studio:
#     Open an existing Android Studio project
#     Select android/ijkplayer/ and import


可以根據需要對音視訊編解碼庫進行裁剪.編譯出最小的滿足需要的庫 
bilibili提供三種裁剪方式 
If you prefer more codec/format

cd config
rm module.sh
ln -s module-default.sh module.sh
cd android/contrib
sh compile-ffmpeg clean


If you prefer less codec/format for smaller binary size (include hevc function)

cd config
rm module.sh
ln -s module-lite-hevc.sh module.sh
cd android/contrib
sh compile-ffmpeg clean


If you prefer less codec/format for smaller binary size (by default)

cd config
rm module.sh
ln -s module-lite.sh module.sh
cd android/contrib
sh compile-ffmpeg clean


當然也可以根據需要自己裁剪. 
我們來看下ijkplayer/config/module-lite.sh 即default裁剪模式支援哪些編解碼方式 
我們可以看到 
export COMMON_FF_CFG_FLAGS=”COMMONFFCFGFLAGSenabledemuxer=hlsexportCOMMONFFCFGFLAGS=COMMON_FF_CFG_FLAGS –enable-parser=aac” 
export COMMON_FF_CFG_FLAGS=”COMMONFFCFGFLAGSenableparser=h264exportCOMMONFFCFGFLAGS=COMMON_FF_CFG_FLAGS –disable-protocol=rtp” 
export COMMON_FF_CFG_FLAGS=”$COMMON_FF_CFG_FLAGS –enable-protocol=rtmp”

四. ijkplayer的java層原始碼分析

【先佔坑,接下來詳解】

五. 專案中ijkplayer的封裝以及mediaview的封裝以及使用

【先佔坑,接下來詳解】

六. ijkplayer底層學習

【先佔坑,接下來重點學習】

七. 開源專案

【接下來仿網易雲音樂,寫一個開源專案,歡迎多多關注】

七. 常見問題以及解決方案

  1. 全屏播放
  2. 有時候會開始直播時出現黑屏
  3. 有時候會出現花屏
  4. 解碼方式設定
  5. 如何區分點播直播
  6. 是否需要開啟硬體加速
  7. 如何設定後臺播放
  8. 視訊載入速度慢 
    The traffic speed is mostly depending on the quality of video CDN, not player itself.
  9. 怎麼靜音 和非靜音 
    mute/unmute system volume.There is no mute/unmute API in ijkplayer.
  10. 視訊黑屏,但是有聲音 
    確定下視訊源的編碼方式,ijk預設只帶了h264解碼code
  11. 適配問題,對於不同的cpu架構,需要編譯不同的so庫
  12. 播放視訊有的裝置聲畫不同步
  13. 如何檢視m3u8時長 
    cat game05.m3u8 | grep EXTINF | wc -l 32
  14. how to change the video quality? 
    Video quality is determined when being encoded.I don’t think it can be changed by player.
  15. 倍速播放 
    Not until Android 6.0
  16. 為什麼往前拖動進度條後,還會往後退幾秒 
    seek只支援關鍵幀,出現這個情況就是原始的視訊檔案中i 幀比較少,播放器會在拖動的位置找最近的關鍵幀。
  17. how to change URL when ijkplayer is playing RTMP video 
    Create new player.
  18. 怎樣新增字幕呢? 
    如果希望字幕時間精確,可以在native層做解析和時間同步,到了時間後回撥給java層,一般字幕檔案載入都是在java層做的,解析檔案格式,然後按照時間區間來顯示。
  19. 如何設定硬解? 
    ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, “mediacodec”, 1);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "http-detect-range-support", 0);
                ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "overlay-format", IjkMediaPlayer.SDL_FCC_RV32);
                ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "analyzeduration", "2000000");
                ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "probsize", "4096");
                ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 0);