隨着整個互聯網的崛起,數據傳遞的形式也在不斷升級變化,總的流行趨勢以下:linux
純文本的短信,QQ -> 空間,微博,朋友圈的圖片文字結合 -> 微信語音 -> 各大直播軟件 -> 抖音短視頻
複製代碼
音視頻的發展正在向各個行業不斷擴展,從教育的遠程授課,交通的人臉識別,醫療的遠程就醫等等,音視頻方向已經佔據一個至關重要的位置,而音視頻真正入門的文章又少之甚少,一個剛畢業小白可能很難切入理解,由於音視頻中涉及大量理論知識,而代碼的書寫須要結合這些理論,因此搞懂音視頻,編解碼等理論知識相當重要.本人也是從實習開始接觸音視頻項目,看過不少人的文章,在這裏總結一個通俗易懂的文章,讓更多準備學習音視頻的同窗更快入門。web
本文中理論知識來自於各類音視頻文章的概括音視頻編碼基本原理彙總,其中也會有一些我本身總結增長的部分.如有錯誤可評論,檢查後會更正.windows
爲了防止你們理解過於空洞,做者花了三個月時間將最經常使用,最重要的一些功能的理論知識及實戰Demo親自寫出來,配合文章閱讀效果更佳.每一部分的文章能夠在下面每章章節開始的深刻學習中點擊連接查看, 連接中文章均有Github地址,每一個Demo都親測能夠經過,能夠下載Demo運行.瀏覽器
若是喜歡,請幫忙點贊並支持轉載,轉載請附原文連接.緩存
採集bash
不管是iOS平臺,仍是安卓平臺,咱們都是須要藉助官方的API實現一系列相關功能.首先咱們要明白咱們想要什麼,最開始咱們須要一部手機,智能手機中攝像頭是不可缺乏的一部分,因此咱們經過一些系統API獲取就要能夠獲取物理攝像頭將採集到的視頻數據與麥克風採集到的音頻數據.服務器
處理微信
音頻和視頻原始數據本質都是一大段數據,系統將其包裝進自定義的結構體中,一般都以回調函數形式提供給咱們,拿到音視頻數據後,能夠根據各自項目需求作一系列特殊處理,如: 視頻的旋轉,縮放,濾鏡,美顏,裁剪等等功能, 音頻的單聲道降噪,消除回聲,靜音等等功能.網絡
編碼數據結構
原始數據作完自定義處理後就能夠進行傳輸,像直播這樣的功能就是把採集好的視頻數據發送給服務器,以在網頁端供全部粉絲觀看,而傳輸因爲自己就是基於網絡環境,龐大的原始數據就必須壓縮後才能帶走,能夠理解爲咱們搬家要將物品都打包到行李箱這樣理解.
傳輸
編碼後的音視頻數據一般以RTMP協議進行傳輸,這是一種專門用於傳輸音視頻的協議,由於各類各樣的視頻數據格式沒法統一,因此須要有一個標準做爲傳輸的規則.協議就起到這樣的做用.
解碼
服務端接收到咱們送過去的編碼數據後,須要對其解碼成原始數據,由於編碼的數據直接送給物理硬件的設備是不能直接播放的,只有解碼爲原始數據才能使用.
音視頻同步
解碼後的每幀音視頻中都含有最開始錄製時候設置的時間戳,咱們須要根據時間戳將它們正確的播放出來,可是在網絡傳輸中可能會丟失一些數據,或者是延時獲取,這時咱們就須要必定的策略去實現音視頻的同步,大致分爲幾種策略:緩存必定視頻數據,視頻追音頻等等.
推流: 將手機採集到的視頻數據傳給後臺播放端進行展現,播放端能夠是windows, linux, web端,即手機充當採集的功能,將手機攝像頭採集到視頻和麥克風採集到的音頻合成編碼後傳給對應平臺的播放端。
拉流: 將播放端傳來的視頻數據在手機上播放,推流的逆過程,即將windows, linux, web端傳來的視頻數據進行解碼後傳給對應音視頻硬件,最終將視頻渲染在手機界面上播放.
推流,拉流實際爲互逆過程,這裏按照從採集開始介紹.
採集是推流的第一個環節,是原始的音視頻數據的來源.採集的原始數據類型爲音頻數據PCM,視頻數據YUV,RGB...。
深刻研究
採集來源
音頻主要參數
音頻幀
音頻與視頻不一樣,視頻每一幀就是一張圖片,音頻是流式,自己沒有明確的幀的概念,實際中爲了方便,取2.5ms~60ms爲單位的數據爲一幀音頻.
計算
數據量(字節 / 秒)=(採樣頻率(Hz)* 採樣位數(bit)* 聲道數)/ 8
單聲道的聲道數爲1,立體聲的聲道數爲2. 字節B,1MB=1024KB = 1024*1024B
注意: 像一些外置攝像頭,如像利用攝像機的攝像頭採集,而後用手機將數據處理編碼併發出,也是能夠的,可是數據的流向須要咱們解析,即從攝像頭的HDMI線轉成網線口,網線口再轉USB,USB轉蘋果Lighting接口,利用FFmpeg能夠獲取其中的數據.
視頻主要參數
計算 (RGB)
1幀數據量 = 分辨率(width * height) * 每一個像素的所佔字節數(通常是3個字節)
注意上面計算的方法並非惟一的,由於視頻的數據格式有不少種,如YUV420計算方式爲
分辨率(width * height) * 3/2
咱們假設要上傳的視頻是1080P 30fps(分辨率:1920*1080), 聲音是48kHz,那麼每秒鐘數據量以下:
video = 1920 * 1080 * 30 * 3 = 186624000B = 186.624 MB
audio = (48000 * 16 * 2) / 8 = 192000B = 0.192 MB
複製代碼
由此咱們可得,若是直接將原始採集的數據進行傳輸,那麼一部電影就須要1000多G的視頻,若是是這樣將多麼恐怖,因此就涉及到咱們後面的編碼環節。
從上一步中,咱們能夠獲得採集到的音頻原始數據和視頻原始數據,在移動端,通常是經過各自手機平臺官方API中拿到, 前文連接中皆有實現的方法.
以後,咱們能夠對原始數據加以處理,對原始操做處理只能在編碼以前,由於編碼後的數據只能用於傳輸. 好比可 以對圖像處理
對音頻處理
目前流行的有不少大型框架專門用來處理視頻,音頻,如OpenGL, OpenAL, GPUImage...以上的各類處理網上均有開源的庫能夠實現,基本原理就是,咱們拿到原始的音視頻幀數據,將其送給開源庫,處理完後再拿處處理好的音視頻繼續咱們本身的流程.固然不少開源庫仍須要根據項目需求略微更改並封裝.
在第1.
步採集最後已經講到,原始的視頻每秒鐘就產生200多MB,若是直接拿原始數據傳輸,網絡帶寬即內存消耗是巨大的,因此視頻在傳輸中是必須通過編碼的.
相似的例子就像咱們日常搬家,若是直接搬家,東西很零散,須要跑不少趟拿,若是將衣服,物品打包,咱們僅僅須要幾個行李箱就能夠一次搞定.等咱們到達新家,再將東西取出來,從新佈置,編解碼的原理就是如此.
有損壓縮
視頻利用人眼的視覺特性, 以必定的客觀失真換取數據的壓縮,好比人眼對亮度識別的閾值,視覺閾值,對亮度和色度的敏感度不一樣,以致於能夠在編碼時引入適量偏差,不會被察覺出來.
音頻利用了人類對圖像或聲波中的某些頻率成分不敏感的特性,容許壓縮過程當中損失必定的信息;去除聲音中冗餘成分的方法實現。冗餘成分指的是音頻中不能被人耳朵察覺的信號,它們對聲音的音色,音調等信息沒有任何幫助。重構後的數據與原來的數據有所不一樣,但不影響人對原始資料表達的信息形成誤解。
有損壓縮適用於重構信號不必定非要和原始信號徹底相同的場合。
無損壓縮
正由於有着上面的壓縮方法,視頻數據量能夠極大的壓縮,有利於傳輸和存儲.
原理:編碼是如何作到將很大的數據量變小的呢? 主要原理以下
壓縮編碼的方法
變換編碼 (瞭解便可,具體請谷歌)
將空間域描述的圖像信號變換成頻率域,而後對變換後的係數進行編碼處理。通常來講,圖像在空間上具備較強的相關性,變換頻率域能夠實現去除相關與能量集中。經常使用的正交變換有離散傅里葉變換,離散餘弦變換等等。
熵編碼 (瞭解便可,具體請谷歌)
熵編碼是因編碼後的平均碼長接近信源熵值而得名。熵編碼多用可變字長編碼(VLC,Variable Length Coding)實現。其基本原理是對信源中出現機率大的符號賦予短碼,對於出現機率小的符號賦予長碼,從而在統計上得到較短的平均碼長。可變字長編碼一般有霍夫曼編碼、算術編碼、遊程編碼等。
運動估計和運動補償 (重要)
運動估計和運動補償是消除圖像序列時間方向相關性的有效手段。上面介紹的變換編碼,熵編碼都是在以一幀圖像的基礎上進行的,經過這些方法能夠消除圖像內部各像素在空間上的相關性。實際上圖像信號除了空間上的相關性外,還有時間上的相關性。例如對於像新聞聯播這種背景靜止,畫面主體運動較小的數字視頻,每一幅畫面之間的區別很小,畫面之間的相關性很大。對於這種狀況咱們沒有必要對每一幀圖像單獨進行編碼,而是能夠只對相鄰視頻幀中變化的部分進行編碼,從而進一步減少數據量,這方面的工做是由運動估計和運動補償來實現的。
將當前的輸入圖像分割成若干彼此不相重疊的小圖像子塊,例如一幀圖像爲1280*720,首先將其以網格狀形式分紅40*45個尺寸爲16*16彼此沒有重疊的圖像塊,而後在前一圖像或者後一圖像某個搜索窗口的範圍內爲每個圖像塊尋找一個與之最爲類似的圖像塊,這個搜尋的過程叫作運動估計。
經過計算最類似的圖像塊與該圖像塊之間的位置信息,能夠獲得一個運動矢量。這樣在編碼的過程當中就能夠將當前圖像中的塊與參考圖像運動矢量所指向的最類似的圖像塊相減,獲得一個殘差圖像塊,因爲每一個殘差圖像塊中的每一個像素值都很小,因此在壓縮編碼中能夠得到更高的壓縮比。
壓縮數據類型
正由於運動估計與運動補償,因此編碼器將輸入的每一幀圖像根據參考圖像分紅了三種類型:I幀,P幀,B幀。
I幀: 只使用本幀內的數據進行編碼,在編碼過程當中不須要進行運動估計和運動補償。
P幀: 在編碼過程當中使用前面的I幀或P幀做爲參考圖像的運動補償,實際是對當前圖像與參考圖像的差值進行編碼。
B幀: 在編碼過程當中使用前面的I幀或P幀和後面的I幀或P幀進行預測。因而可知,每一個P幀利用一幀圖像爲參考圖像。而B幀須要兩幀圖像做爲參考。
實際應用中使用混合編碼(變換編碼+運動估計,運動補償+熵編碼)
編碼器
通過數十年的發展,編碼器的功能已經十分強大,種類繁多,下面介紹最主流的一些編碼器。
與舊標準相比,它可以在更低帶寬下提供優質視頻(換言之,只有 MPEG-2,H.263 或 MPEG-4 第 2 部分的一半帶寬或更少),也不增長太多設計複雜度使得沒法實現或實現成本太高。
高效率視頻編碼(High Efficiency Video Coding,簡稱HEVC)是一種視頻壓縮標準,被視爲是 ITU-T H.264/MPEG-4 AVC 標準的繼任者。HEVC 被認爲不只提高視頻質量,同時也能達到 H.264/MPEG-4 AVC 兩倍之壓縮率(等同於一樣畫面質量下比特率減小了 50%).
VP8 是一個開放的視頻壓縮格式,最先由 On2 Technologies 開發,隨後由 Google 發佈。
VP9 的開發從 2011 年第三季開始,目標是在同畫質下,比 VP8 編碼減小 50%的文件大小,另外一個目標則是要在編碼效率上超越 HEVC 編碼。
原理
數字音頻壓縮編碼在保證信號在聽覺方面不產生失真的前提下,對音頻數據信號進行儘量的壓縮。數字音頻壓縮編碼採起去除聲音中冗餘成分的方法實現。冗餘成分指的是音頻中不能被人耳朵察覺的信號,它們對聲音的音色,音調等信息沒有任何幫助。
冗餘信號包含人耳聽覺範圍外的音頻信號以及被掩蔽掉的音頻信號燈。例如,人耳能察覺的聲音頻率爲20Hz~20kHz,出此以外的其餘頻率人耳沒法察覺,都爲冗餘信號。此外,根據人耳聽覺的生理和心理學現象。當一個強音信號與一個弱音信號同時存在時,弱音信號將被強音信號所掩蔽而聽不見,這樣弱音信號就能夠視爲冗餘信號不用傳送。這就是人耳聽覺的掩蔽效應。
壓縮編碼方法
一個頻率的聲音能量小於某個閾值以後,人耳就會聽不到,這個閾值稱爲最小可聞閾。當有另外能量較大的聲音出現的時候,該聲音頻率附近的閾值會提升不少,即所謂的掩蔽效應
人耳對2KHz~5KHz的聲音最敏感,而對頻率過低或過高的聲音信號都很遲鈍,當有一個頻率爲0.2KHz、強度爲60dB的聲音出現時,其附近的閾值提升了不少。
當強音信號和弱音信號同時出現時,還存在時域掩蔽效應,前掩蔽,同時掩蔽,後掩蔽。前掩蔽是指人耳在聽到強信號以前的短暫時間內,已經存在的弱信號會被掩蔽而聽不到。
- 前掩蔽是指人耳在聽到強信號以前的短暫時間內,已經存在的弱信號會被掩蔽而聽不到
- 同時掩蔽是指當強信號與弱信號同時存在時,弱信號會被強信號所掩蔽而聽不到。
- 後掩蔽是指當強信號消失後,需通過較長的一段時間才能從新聽見弱信號,稱爲後掩蔽。這些被掩蔽的弱信號便可視爲冗餘信號。
複製代碼
封裝就是把編碼器生成的音頻,視頻同步以生成咱們肉眼可見,耳朵可聽而且看到的與聽到的是同步的視頻文件.即封裝後生成一個容器,來存放音頻和視頻流以及一些其餘信息(好比字幕, metadata等).
在移動端咱們須要藉助FFmpeg框架,正如上面介紹的,FFmpeg不只能夠作編解碼,還能夠合成視頻流,像經常使用的.flv流,.asf流.
最後, 合成好的數據便可用於寫文件或者在網絡上傳播
FFmpeg 是一個開源框架,能夠運行音頻和視頻多種格式的錄影、轉換、流功能,包含了 libavcodec: 這是一個用於多個項目中音頻和視頻的解碼器庫,以及 libavformat 一個音頻與視頻格式轉換庫。
目前支持 Linux ,Mac OS,Windows 三個主流的平臺,也能夠本身編譯到 Android 或者 iOS 平臺。 若是是 Mac OS ,能夠經過 brew 安裝 brew install ffmpeg --with-libvpx --with-libvorbis --with-ffplay
Overview
FLV封裝格式分析器。FLV全稱是Flash Video,是互聯網上使用極爲普遍的視頻封裝格式。像Youtube,優酷這類視頻網站,都使用FLV封裝視頻
FLV(Flash Video)是Adobe公司設計開發的一種流行的流媒體格式,因爲其視頻文件體積輕巧、封裝簡單等特色,使其很適合在互聯網上進行應用。此外,FLV可使用Flash Player進行播放,而Flash Player插件已經安裝在全世界絕大部分瀏覽器上,這使得經過網頁播放FLV視頻十分容易。目前主流的視頻網站如優酷網,土豆網,樂視網等網站無一例外地使用了FLV格式。FLV封裝格式的文件後綴一般爲「.flv」。
結構
FLV包括文件頭(File Header)和文件體(File Body)兩部分,其中文件體由一系列的Tag組成。所以一個FLV文件是如圖1結構。
每一個Tag前面還包含了Previous Tag Size字段,表示前面一個Tag的大小。Tag的類型能夠是視頻、音頻和Script,每一個Tag只能包含以上三種類型的數據中的一種。圖2展現了FLV文件的詳細結構。
優勢
缺點
咱們推送出去的流媒體須要傳輸到觀衆,整個鏈路就是傳輸網絡.
RTMP協議是一個互聯網TCP/IP五層體系結構中應用層的協議。RTMP協議中基本的數據單元稱爲消息(Message)。當RTMP協議在互聯網中傳輸數據的時候,消息會被拆分紅更小的單元,稱爲消息塊(Chunk)。
消息是RTMP協議中基本的數據單元。不一樣種類的消息包含不一樣的Message Type ID,表明不一樣的功能。RTMP協議中一共規定了十多種消息類型,分別發揮着不一樣的做用。
在網絡上傳輸數據時,消息須要被拆分紅較小的數據塊,才適合在相應的網絡環境上傳輸。RTMP協議中規定,消息在網絡上傳輸時被拆分紅消息塊(Chunk)。
消息塊首部(Chunk Header)有三部分組成:
在消息被分割成幾個消息塊的過程當中,消息負載部分(Message Body)被分割成大小固定的數據塊(默認是128字節,最後一個數據塊能夠小於該固定長度),並在其首部加上消息塊首部(Chunk Header),就組成了相應的消息塊。消息分塊過程如圖5所示,一個大小爲307字節的消息被分割成128字節的消息塊(除了最後一個)。
RTMP傳輸媒體數據的過程當中,發送端首先把媒體數據封裝成消息,而後把消息分割成消息塊,最後將分割後的消息塊經過TCP協議發送出去。接收端在經過TCP協議收到數據後,首先把消息塊從新組合成消息,而後經過對消息進行解封裝處理就能夠恢復出媒體數據。
RTMP協議規定,播放一個流媒體有兩個前提步驟
其中,網絡鏈接表明服務器端應用程序和客戶端之間基礎的連通關係。網絡流表明了發送多媒體數據的通道。服務器和客戶端之間只能創建一個網絡鏈接,可是基於該鏈接能夠建立不少網絡流。他們的關係如圖所示:
播放一個RTMP協議的流媒體須要通過如下幾個步驟:
RTMP鏈接都是以握手做爲開始的。創建鏈接階段用於創建客戶端與服務器之間的「網絡鏈接」;創建流階段用於創建客戶端與服務器之間的「網絡流」;播放階段用於傳輸視音頻數據。
到這裏爲止,完整的推流過程已經介紹完成,下面的過程即爲逆向過程-拉流.
由於接收端拿到編碼的視頻流最終仍是想將視頻渲染到屏幕上, 將音頻經過揚聲器等輸出設備播出,因此接着上面的步驟,接收端能夠經過RTMP協議拿到視頻流數據,而後須要利用FFmpeg parse數據,由於咱們須要將數據中的音頻跟視頻分開,分離出音視頻數據後須要分別對它們作解碼操做.解碼的視頻即爲YUV/RGB等格式,解碼後的音頻即爲線性PCM數據.
須要注意的是,咱們解碼出來的數據並不可以直接使用,由於,手機端若是想要播放解碼出來的數據是須要將其放入特定的數據結構中,在iOS中,視頻數據須要放入CMSampleBufferRef中,而該數據結構又由CMTime,CMVideoFormatDes,CMBlockBuffer組成,因此咱們須要提供它所須要的信息才能組成系統可以播放的格式.
當咱們拿到解碼後的音視頻幀時,首先要考慮的問題就是如何同步音視頻,在網絡正常的狀況下是不須要作音視頻同步操做,由於咱們parse到的音視頻數據裏自己帶着它們在採集時的時間戳,只要咱們在合理時間內拿到音視頻幀,將它們分別送給屏幕與揚聲器便可實現同步播放.可是考慮到網絡波動,因此可能丟失一些幀或延遲後才能獲取,當這種狀況出現時就會形成聲音視頻不一樣步,所以須要對音視頻作同步處理.
咱們能夠這樣理解: 有一把尺子 一隻螞蟻(視頻)跟着一個標杆(音頻)走, 標杆是勻速的 螞蟻或快或慢,慢了你就抽它 讓它跑起來,快了就拽它。這樣音視頻就能同步了。 這裏最大的問題就是音頻是勻速的,視頻是非線性的。
分別得到音視頻的PTS後,咱們有三個選擇:視頻同步音頻(計算音視頻PTS之差,來斷定視頻是否有延遲)、音頻同步視頻(根據音視頻PTS差值調整音頻取的樣值,即改變音頻緩衝區的大小)和音頻視頻同步外部時鐘(同前一個),由於調整音頻範圍過大,會形成令用戶不適的尖銳聲,因此一般咱們選擇第一種。
咱們的策略是經過比較前一個 PTS 和當前的 PTS 來預測下一幀的 PTS。與此同時,咱們須要同步視頻到音頻。咱們將建立一個 audio clock 做爲內部變量來跟蹤音頻如今播放的時間點,video thread 將用這個值來計算和判斷視頻是播快了仍是播慢了。
如今假設咱們有一個 get_audio_clock 函數來返回咱們 audio clock,那當咱們拿到這個值,咱們怎麼去處理音視頻不一樣步的狀況呢?若是隻是簡單的嘗試跳到正確的 packet 來解決並非一個很好的方案。咱們要作的是調整下一次刷新的時機:若是視頻播慢了咱們就加快刷新,若是視頻播快了咱們就減慢刷新。既然咱們調整好了刷新時間,接下來用 frame_timer 跟設備的時鐘作一下比較。frame_timer 會一直累加在播放過程當中咱們計算的延時。換而言之,這個 frame_timer 就是播放下一幀的應該對上的時間點。咱們簡單的在 frame_timer 上累加新計算的 delay,而後和系統時間比較,並用獲得的值來做爲時間間隔去刷新。