SGPlayer 原理詳解 - 支持 VR、RTMP 的視頻播放框架

簡介

SGPlayer 是一款基于 AVPlayer、FFmpeg 的媒體資源播放器框架。支持全景視頻,RTMP、RTSP 等直播流;同時(shí)支持 iOS、macOS、tvOS 三個(gè)平臺(tái)。本文將采用圖解+說明的方式把關(guān)鍵模塊的實(shí)現(xiàn)原理介紹給大家。

發(fā)起原因

關(guān)于視頻播放,蘋果提供的 AVPlayer 在性能上有著十分出色的表現(xiàn),在無特需求且資源可控的時(shí),首選一定是它。但隨著 VR 和直播的興起,僅使用 AVPlayer 很多時(shí)候已經(jīng)無法滿足需求。出于性能考慮,又不能完全拋棄 AVPlayer,畢竟在點(diǎn)播時(shí)有著明顯的優(yōu)勢(shì)。而在現(xiàn)有的開源項(xiàng)目中,普遍定位比較單一,并不能兼顧 AVPlayer、直播、VR。這樣一來,需同時(shí)使用3款播放器才能滿足需求,即點(diǎn)播使用 AVPlayer,直播使用一個(gè)獨(dú)立的播放器,VR 使用一個(gè)獨(dú)立的播放器。這樣處理3套不同的接口和回調(diào)事件,著實(shí)很讓人崩潰!SGPlayer 的出現(xiàn)大大簡化了這一過程。

組成結(jié)構(gòu) 和 播放流程

SGPlayer 播放流程圖

上圖展示了 SGPlayer 的播放流程和主要組件,下面簡單介紹圖中各組件的分工

SGPlayer

SGPlayer是一個(gè)抽象的播放器外殼,它本身并不具備播放功能。僅作為和外界交互的載體。真正的播放由內(nèi)部的 SGAVPlayer 和 SGFFPlayer 完成。而畫面繪制由內(nèi)部的 SGDisplayView 完成。

SGPlayerDecoder

SGPlayerDecoder 是播放內(nèi)核的選擇器,根據(jù)資源類型動(dòng)態(tài)選擇使用 SGAVPlayer 或 SGFFPlayer 進(jìn)行播放,可通過更改其配置參數(shù),來自定義播放內(nèi)核的選擇策略。

SGAVPlayer

SGAVPlayer 是基于 AVPlayer 封裝而成,視頻畫面輸出至 SGDsiplayView,并根據(jù)視頻類型(全景或平面)進(jìn)行展示。音頻由系統(tǒng)處理無需額外操作。

SGFFPlayer

SGFFPlayer 是基于 FFmpeg 封裝而成,支持近所有的主流視頻格式。視頻畫面同樣輸出至 SGDsiplayView。音頻則輸出至 SGAudioManager,再由 SGAudioManager 使用 Audio Unit 進(jìn)行播放。

SGDisplayView

SGDisplayView 負(fù)責(zé)視頻畫面的繪制。它本身不會(huì)繪制視頻畫面,僅作為繪制層的父視圖使用,真正的繪制由內(nèi)部的 AVPlayerLayer 和 SGGLViewController 完成,選擇規(guī)則如下表所示。

| 平面 | 全景
---|---|---
SGAVPlayer | AVPlayerLayer | SGGLViewController
SGFFPlayer | SGGLViewController | SGGLViewController

SGAudioManager

SGAudioManager 負(fù)責(zé)聲音的播放和音頻事件的處理。內(nèi)部使用 AUGraph 做了一層混音,通過混音可以設(shè)置聲音的輸出音量大小等操作。

小結(jié)

了解了各組件的功能,重新梳理一下完整的播放過程

  • SGPlayer 收到播放請(qǐng)求。
  • 由 SGPlayerDecoder 根據(jù)資源類型分發(fā)給 SGAVPlayer 或 SGFFPlayer 進(jìn)行播放。
  • 如果使用 SGAVPlayer 播放,根據(jù)視頻類型將畫面輸出給 SGDisplayView 中的 AVPlayerLayer 或 SGGLViewController。
  • 如果使用 SGFFPlayer 播放,將視頻畫面輸出給 SGDisplayView,音頻輸出至 SGAudioManager。

通過抽象的 SGPlayer 將真正負(fù)責(zé)播放的 SGAVPlayer 和 SGFFPlayer 屏蔽起來,這樣可以保證無論資源是何種類型,對(duì)外僅暴露一套統(tǒng)一的接口和回調(diào),將播放內(nèi)核間的差異內(nèi)部消化,盡可能降低使用成本。

全景圖像原理

全景圖像與平面圖像本質(zhì)都是一張 2D 圖片,區(qū)別在于展示時(shí)的載體。對(duì)于平面圖而言,用于展示的模型是一個(gè)矩形,僅需將圖像上的像素一一對(duì)應(yīng)在矩形上即可;而全景圖像展示的模型是一個(gè)球,需要將圖像上的每一個(gè)像素都對(duì)應(yīng)到球面相應(yīng)位置上。在繪制流程上二者的差別并不大,僅在貼圖規(guī)則和呈現(xiàn)方式上略有區(qū)別。

貼圖規(guī)則

全景圖像 貼圖規(guī)則

把平面圖片貼到球面上的過程和地球儀很相似。以上圖為例,左側(cè)圖片中的每一個(gè)像素,都可以在右側(cè)球面上找到對(duì)應(yīng)的位置。下面列舉一個(gè)關(guān)鍵的對(duì)應(yīng)關(guān)系。

  • 直線AB 上所有的點(diǎn)都與 點(diǎn)J 對(duì)應(yīng),同理 直線CD 上所有的點(diǎn)都與 點(diǎn)K 對(duì)應(yīng)。
  • 直線MN 上的點(diǎn)與 赤道 上的點(diǎn)一一對(duì)應(yīng)。
  • 直線AC/BD 上的點(diǎn)與綠 色經(jīng)線前半面 上的點(diǎn)一一對(duì)應(yīng)。
  • 直線EF 上的點(diǎn)與 綠色經(jīng)線后半面 上的點(diǎn)一一對(duì)應(yīng)。

呈現(xiàn)方式

全景圖像 觀看視角

上圖展示了全景圖像的呈現(xiàn)方式,不同于平面,全景圖像需將觀景點(diǎn)放在球心,站在球心觀看球面上的圖像。最終將 曲面ABCD 在 平面ABCD 上的投影顯示到屏幕上。

小結(jié)

這部分內(nèi)容在實(shí)現(xiàn)上涉及到很多 OpenGL 的內(nèi)容,需要具備一些 OpenGL 的基礎(chǔ)。在雙眼模式下還需要做 畸變校正 和 色散校正 來保證畫面被真實(shí)的還原。具體實(shí)現(xiàn)可以查看 SGGLViewController。

SGFFPlayer 運(yùn)作流程

SGFFPlayer 運(yùn)作流程

上圖展示了 SGFFPlayer 的協(xié)作流程圖,下面簡單介紹圖中各組件

線程模型

SGFFPlayer 中共有4個(gè)線程。與圖中4個(gè)藍(lán)色圓圈對(duì)應(yīng)。

  • 數(shù)據(jù)讀取 - Read Packet Loop
  • 視頻解碼 - Video Decode Loop
  • 視頻繪制 - Video Display Loop
  • 音頻播放 - Audio Playback Loop

圖中隱藏掉了線程的控制條件。在4個(gè)線程的協(xié)作下完成整個(gè)播放過程。

SGVideoDecoder

SGVideoDecoder 是視頻解碼器,初始化時(shí)可配置同步、異步解碼,以及是否開啟硬解。上圖中采用的是異步解碼,默認(rèn)的解碼線程對(duì)應(yīng)關(guān)系如下表所示。

| 平面 | 全景
---|---|---
軟件解碼 | 異步 | 同步
硬件解碼 | 異步 | 異步

  • 同步解碼在收到視頻包后立即解碼,并存入視頻幀隊(duì)列。
  • 異步解碼在收到視頻包后僅存入音頻包隊(duì)列,當(dāng)獨(dú)立的解碼線程取出音頻包并完成解碼后,再存入視頻幀隊(duì)列。

SGAudioDecoder

SGAudioDecoder 是音頻解碼器,采用同步解碼,收到音頻包后立即解碼,并存入音頻幀隊(duì)列。

數(shù)據(jù)隊(duì)列 SGFFPacketQueue、SGFFFrameQueue

  • SGFFPacketQueue 是包隊(duì)列,用于管理解碼前的數(shù)據(jù)包(AVPacket)。
  • SGFFFrameQueue 是幀隊(duì)列,用于管理解碼后的幀(SGFFVideoFrame 或 SGFFAudioFrame)。

它們都支持?jǐn)?shù)據(jù)的同步獲取和異步獲取,同步獲取是通過條件變量(NSCondition)實(shí)現(xiàn)。當(dāng)隊(duì)列中沒有足夠數(shù)據(jù)時(shí),會(huì)阻塞當(dāng)前線程,直到向隊(duì)列中添加新元素時(shí),線程才會(huì)被喚醒。

幀復(fù)用池 SGFFFramePool

該部分并沒有在上圖中體現(xiàn),但能避免一些不必要的性能開銷。由于音頻幀和視頻幀的數(shù)量很大,1分鐘的視頻就包含幾千幀的數(shù)據(jù)。如果每一幀都新創(chuàng)建的話會(huì)造成不必要的資源浪費(fèi)。通過 SGFFFramePool 創(chuàng)建的 SGFFFrame 在使用完成后不會(huì)立即釋放,而是被復(fù)用池回收,以供下次使用,達(dá)到僅創(chuàng)建最小數(shù)量的幀對(duì)象的目的。

音視頻同步

常用的同步當(dāng)時(shí)有3種

  1. 音頻時(shí)鐘
  2. 視頻時(shí)鐘
  3. 自制時(shí)鐘

在 SGFFPlayer 中,優(yōu)先使用音頻時(shí)鐘,當(dāng)視頻中沒有音軌時(shí),會(huì)使用視頻時(shí)鐘進(jìn)行同步。

小結(jié)

了解了各組件的功能,以視頻異步解碼為例,重新梳理一下整個(gè)流程

  • 數(shù)據(jù)讀取線程讀取到數(shù)據(jù)包,根據(jù)數(shù)據(jù)包類型分發(fā)給音頻解碼器或視頻解碼器。
  • 如果為音頻包,音頻解碼器收到音頻包的同時(shí)進(jìn)行解碼,并將解碼后的音頻幀存入音頻幀隊(duì)列。
  • 如果為視頻包,由于視頻解碼器是異步解碼,僅將視頻包放入視頻包隊(duì)列,等待視頻解碼線程來隊(duì)列中取視頻包。
  • 視頻解碼線程循環(huán)從視頻包隊(duì)列中取出視頻包,同時(shí)解碼,并將解碼后的視頻幀存入視頻幀隊(duì)列。
  • 音頻播放線程循環(huán)在音頻幀隊(duì)列中取出音頻幀并播放。
  • 視頻展示線程循環(huán)在視頻幀隊(duì)列中取出視頻幀并繪制。

到這里SGFFPlayer的運(yùn)作流程已經(jīng)很清晰了,只需在各個(gè)環(huán)節(jié)中加入對(duì)應(yīng)的條件控制,就可以完成播放功能了。

總結(jié)

關(guān)于 SGPlayer 的原理就闡述到這里,由于本文以理論為主,所以并沒有貼代碼。感興趣的同學(xué)可以在 GitHub 上找到全部的代碼實(shí)現(xiàn)。希望對(duì)大家能有所幫助。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容