原文詳見:
http://www.2cto.com/kf/201607/525000.html
一、前言
最近各種視頻直播app到處都是,各種霸屏,當然我們也是需要體驗的,關于視頻直播的軟件這里就不介紹了,在不是技術的人來看,直播是一種潮流,是一種娛樂方式,但是作為一個高技術的,我們除了看看,更重要的是學習技術,其實Android中的視頻技術沒什么說的,因為網上的資料很多,但是之前的視頻技術大部分都出現在了視頻播放,就是主流的視頻播放器,那個最重要的一個技術就是視頻的編解碼,這個也會在后續文章中詳細介紹視頻的處理技術。但是現在直播的技術是在之前的視頻技術上又有了一個要求就是視頻錄制,現在錄制很多是借助于牛逼的硬件攝像頭。但是除了這個技術,還有其他的我們使用移動設備也可以去解決這個問題。這個后續也會說道如何使用設備去錄制視頻。
二、知識概要
從這篇文章開始我們就來介紹一下關于Android中的視頻處理,這里主要包括:Android中的攝像頭技術,錄制視頻,視頻播放器等知識點,本篇文章是介紹大體的知識點,為后續的章節知識點做鋪墊,其實以前在學習Android中最怕就是圖片和視頻處理方面的技術,因為這些技術有一個基本的要求就是字節操作,考慮很多字節流處理,這個在Android中有一個類ByteBuffer,這個類將會貫穿我們后續所有章節的知識點,這個類也是我們下一篇文章的中點,在視頻流處理中這個類將不可或缺的。
下面我們先來看一張Android中視頻的處理大綱圖解:
這張圖片太大了,如果看的不夠清楚可以下載,然后放大查看。下面就來一一介紹這張圖中涉及到的知識點:
三、知識結構
第一、視頻編碼
Android中視頻編碼有兩種方式,主要是兩個核心的類,一個是MediaCodec和MediaRecorder,這兩個類有什么區別呢?其實很好理解,他們都可以對視頻進行編碼,但是唯一不同的是MediaCodec更偏向原生,而MediaRecorder偏向的上層封裝。
1、MediaCodec類
MediaCodec可以處理具體的視頻流,主要有這幾個方法:
getInputBuffers:獲取需要編碼數據的輸入流隊列,返回的是一個ByteBuffer數組
queueInputBuffer:輸入流入隊列
dequeueInputBuffer:從輸入流隊列中取數據進行編碼操作
getOutputBuffers:獲取編解碼之后的數據輸出流隊列,返回的是一個ByteBuffer數組
dequeueOutputBuffer:從輸出隊列中取出編碼操作之后的數據
releaseOutputBuffer:處理完成,釋放ByteBuffer數據
看如下圖:
這里看到:
視頻流有一個輸入隊列,和輸出隊列,分別對應getInputBuffers和getOutputBuffers這兩個方法獲取這個隊列,然后對于輸入流這端有兩個方法一個是queueInputBuffers是將視頻流入隊列,dequeueInputBuffer是從輸入流隊列中取出數據進行編解碼操作,在輸出端這邊有一個dequeueOutputBuffer方法從輸出隊列中獲取視頻數據,releaseOutputBuffers方法將處理完的輸出視頻流數據ByteBuffer放回視頻流輸出隊列中,再次循環使用。這樣視頻流輸入端和輸出端分別對應一個ByteBuffer隊列,這些ByteBuffer可以重復使用,在處理完數據之后再放回去即可。
所以這里看到MediaCodec類處理視頻的時候可以接觸到視頻流數據的,這里比如我們如果有一些特殊需求,比如視頻的疊加技術,添加字幕等就可以在這里處理了。同時MediaCodec有一個方法:createInputSurface可以設置視頻源輸入Surface類型,同時也是可以通過configure方法設置視頻輸出Surface類型。
2、MediaRecorder類
MediaRecorder這個類相對于MediaCodec簡單,因為他封裝的很好,直接就是幾個接口來完成視頻錄制,比如視頻的編碼格式,視頻的保存路勁,視頻來源等,用法簡單,但是有一個問題就是不能接觸到視頻流數據了,處理不了原生的視頻數據了。這個也是他和MediaCodec最大的區別,他完成不了視頻的疊加技術的。
注意:
關于MediaRecorder這個類,其實他和Android中的一個命令是相對應的,就是:adb screenrecord。這個類還有一個地方需要注意的就是他有一個方法:setVideoSource,可以設置視頻來源,代碼后續文章會介紹,主要就兩個來源:一個是來自于設備的攝像頭Camera,一個是來自于Surface,關于Surface這個類后面會介紹。
注意:
現在視頻編碼的格式都是H264的,關于H264格式說明如下:
H.264,MPEG-4,MPEG-2等這些都是壓縮算法,畢竟帶寬是有限的,為了獲得更好的圖像的傳輸和顯示效果,就不斷的想辦法去掉一些信息,轉換一些信息等等,這就是這些壓縮算法的做的事情。H.264最大的優勢是具有很高的數據壓縮比率,在同等圖像質量的條件下,H.264的壓縮比是MPEG-2的2倍以上,是MPEG-4的1.5~2倍。舉個例子,原始文件的大小如果為88GB,采用MPEG-2壓縮標準壓縮后變成3.5GB,壓縮比為25∶1,而采用H.264壓縮標準壓縮后變為879MB,從88GB到879MB,H.264的壓縮比達到驚人的102∶1!H.264為什么有那么高的壓縮比?低碼率(Low Bit Rate)起了重要的作用,和MPEG-2和MPEG-4 ASP等壓縮技術相比,H.264壓縮技術將大大節省用戶的下載時間和數據流量收費。尤其值得一提的是,H.264在具有高壓縮比的同時還擁有高質量流暢的圖像。寫了這么多,舉個例來說下,比如移動電視,我們接收的到的圖像信號一般是H.264格式的,移動設備接收到后,需要先解碼成原始的YUV碼流,然后又轉換成RGB碼流,將一幀一幀的RGB數據放到顯存上才能顯示出圖像。雖然傳輸快了,得是增加了設備的解碼成本,不過總體來講肯定是值得的。現在PC上的顯卡慢慢都要集成H.264的硬件解碼,據說蘋果的最新產品IPAD也是有了這個硬解碼。而YUV到RGB的轉換,很多ARM芯片上都有了。
第二、視頻數據源
這里說到的視頻數據源就是視頻編碼器需要編碼的視頻來源,在移動設備中,我們獲取知道兩個地方可以獲取視頻,一個是來自于攝像頭Camera,一個是來自于設備屏幕(桌面)。
這里就需要介紹兩個類了:一個是攝像頭Camera,一個是Android5.0新增的屏幕錄制類MediaProjection和VirtualDisplay。
1、Camear類
Camera這個類在現在的直播以及美顏相機等app很重要的,他是移動設備采取視頻和圖片信息的一個重要渠道,他的用法很簡單,分為前置攝像頭和后置攝像頭,可以設置方向,大小等參數,最重要的是,他還需要一個預覽界面,他一般預覽有兩個方法:setPreviewDisplay和setPreviewTexture,第一個方法是設置SurfaceHolder類型的,第二個方法是設置SurfaceTexture類型的關于這兩個類型,后面會說到的。但是這里會有一個疑惑了就是來自于攝像頭的數據會被預覽,但是我們想處理攝像頭的數據該怎么辦呢?這里就需要借助Camera的一個回調接口:PreviewCallback,這個接口有一個回調方法:onPreviewFrame(byte[] data...),看到這個方法我們都知道了這個是攝像頭采集的視頻數據的每一幀數據,我們可以在這里獲取每一幀數據然后進行處理,像現在的美顏相機,就是在這里獲取到一幀數據,然后在做濾鏡效果,然后產生一張圖片即可。當然這里可以錄制美白視頻也是可以的哦。那么在這里我們就可以獲取到視頻的數據源了。
那么上面的MediaCodec類可以使用getInputBuffer類獲取視頻流輸入隊列,我們可以在這個回調方法中獲取到數據,然后傳入到這個隊列中,進行編碼操作,但是需要注意的是數據格式需要做一次轉化,后面會介紹到。同時MediaRecorder類可以通過setVideoSource方法直接設置視頻源。
2、MediaProjection類和VirtualDisplay類
這兩個類主要是Android5.0新增的一個api,就是專門用來錄制設備視頻的,不過在使用的過程中需要權限授權的,如果不授權還是很危險的,假如有惡意的軟件在后臺偷偷的錄制設備屏幕視頻,就知道你干了啥,那是很危險的。需要通過MediaProjection這個類來獲取VirtualDiaplay類,同時需要傳入一個重要的參數,就是錄制屏幕視頻預覽的Surface類。
那么這里就可以和上面的視頻編碼器聯系到一起了,MediaCodec有一個createInputSurface方法可以設置視頻源類型的Surface,而且MediaRecoder這個類也是可以通過setVideoSource方法設置Surface類型的視頻輸入源的,在這里如果想實現設備屏幕錄制視頻可以通過上面的兩個視頻編碼類進行操作然后保存即可。
第三、視頻數據格式
我們上面看到了兩種視頻源,一個來自于攝像頭,一個來自于屏幕,但是這兩個數據源都有自己的格式,所以這里還需要介紹一下數據格式,以及他們之間的轉化。我們平常接觸的一般都是ARGB顏色空間,A代表透明度,RGB是三原色,但是在處理視頻的時候特別是在錄制移動設備的時候視頻有一個顏色空間:YUV。它也是一種顏色空間,為什么要出現YUV,主要有兩個原因,一個是為了讓彩色信號兼容黑白電視機,另外一個原因是為了減少傳輸的帶寬。YUV中,Y表示亮度,U和V表示色度,總之它是將RGB信號進行了一種處理,根據人對亮度更敏感些,增加亮度的信號,減少顏色的信號,以這樣“欺騙”人的眼睛的手段來節省空間。YUV的格式也很多,不過常見的就是422和420格式。在一般的技術開發中,常用的還是yCbCr,這是一種420格式,也稱作I420,注意這個YV12的數據排列剛好是相反的。
Y,U,V它們之間是有一個比例,這個比例不是唯一的,比如Y,U,V三個分量的數量比是4:1:1.也就是說每四個像素共用一對UV。如果是一個3040的幀,那么有1200個Y分量,分別有300個U和300個V分量。總共有12001.5這么多個值。
1、N21/YV12
這個格式一般是設備的攝像頭Camera采集的數據,就是我們上面說到的onPreviewFrame(byte[] data...)每一幀數據,其實是N21或者是YV12格式的,具體哪種格式,可以設置的。所以這里比如我們想獲取一幀數據進行處理,一定要記得格式的轉化,比如這里想保存一張圖片,那么這里就需要將NV21轉化成RGB格式的,或者直接使用系統類YUVImage,產生一張圖片。
2、YUV420P(I420)/YUV420SP(N12)
YUV420有打包格式(Packed),同時還有平面格式(Planar),即Y、U、V是分開存儲的,每個分量占一塊地方,其中Y為width*height,而U、V合占Y的一半,該種格式每個像素占12比特。根據U、V的順序,分出2種格式,U前V后即YUV420P,也叫I420,V前U后,叫YV12(YV表示Y后面跟著V,12表示12bit)。另外,還有一種半平面格式(Semi-planar),即Y單獨占一塊地方,但其后U、V又緊挨著排在一起,根據U、V的順序,又有2種,U前V后叫NV12,在國內好像很多人叫它為YUV420SP格式;V前U后叫NV21。這種格式似乎比NV16稍受歡迎。這種格式一般是錄制屏幕視頻源的格式,就是上面的MediaProjection類,所以我們上面提到的一個將錄制設備屏幕視頻然后進行編碼保存的話,就需要把攝像頭的N21/YV12格式轉化成編碼器識別的YUV420P/YUV420SP格式的。
第四、視頻預覽畫面
1、SurfaceView類
這個類,我們在開發應用的時候可能會用到的很少,在開發游戲中會用到一些,我們在開發應用制作特殊動畫的時候只需要繼承View類,然后在onDraw中開始繪制就好了,這個類也可以做到繪制功能,上面看到視頻源需要一個預覽功能,需要一個Surface,其實SurfaceView就是View+Surface結合體,Surface是圖像繪制數據層,而View只是一個展現層,中間還有一個SurfaceHolder作為鏈接著,他們的關系如下:
可以通過SurfaceHolder的getSurface方法獲取一個Surface類即可。
所以這里的我們在使用SurfaceView作為一個視頻預覽界面的時候,其實是獲取到Surface或者是SurfaceHolder即可,比如攝像頭預覽界面可以通過Camera的setPreviewDisplay方法設置SurfaceHolder類型即可,屏幕錄制界面可以通過VirtualDisplay類參數傳遞一個輸入Surface類型。
2、TextureView類
TextureView在4.0(API level 14)中引入。它可以將內容流直接投影到View中,可以用于實現Live preview等功能。和SurfaceView不同,它不會在WMS中單獨創建窗口,而是作為View hierachy中的一個普通View,因此可以和其它普通View一樣進行移動,旋轉,縮放,動畫等變化。值得注意的是TextureView必須在硬件加速的窗口中。它顯示的內容流數據可以來自App進程或是遠端進程。從類圖中可以看到,TextureView繼承自View,它與其它的View一樣在View hierachy中管理與繪制。TextureView重載了draw()方法,其中主要把SurfaceTexture中收到的圖像數據作為紋理更新到對應的HardwareLayer中。
SurfaceTexture.OnFrameAvailableListener用于通知TextureView內容流有新圖像到來。SurfaceTextureListener接口用于讓TextureView的使用者知道SurfaceTexture已準備好,這樣就可以把SurfaceTexture交給相應的內容源。Surface為BufferQueue的Producer接口實現類,使生產者可以通過它的軟件或硬件渲染接口為SurfaceTexture內部的BufferQueue提供graphic buffer。
這個類其實和SurfaceView差不多,只是他內部不是依賴于Surface和SurfacHolder了,而是SurfaceTexture,關于SurfaceTexture它的好處就很多了:
SurfaceTexture是從Android3.0(API 11)加入的一個新類。這個類跟SurfaceView很像,可以從camera preview或者video decode里面獲取圖像流(image stream)。但是,和SurfaceView不同的是,SurfaceTexture在接收圖像流之后,不需要顯示出來。有做過Android camera開發的人都知道,比較頭疼的一個問題就是,從camera讀取到的預覽(preview)圖像流一定要輸出到一個可見的(Visible)SurfaceView上,然后通過Camera.PreviewCallback的onPreviewFrame(byte[] data, Camera camera)函數來獲得圖像幀數據的拷貝。這就存在一個問題,比如希望隱藏攝像頭的預覽圖像或者對每一幀進行一些處理再顯示到手機顯示屏上,那么在Android3.0之前是沒有辦法做到的,或者說你需要用一些小技巧,比如用其他控件把SurfaceView給擋住,注意這個顯示原始camera圖像流的SurfaceView其實是依然存在的,也就是說被擋住的SurfaceView依然在接收從camera傳過來的圖像,而且一直按照一定幀率去刷新,這是消耗cpu的,而且如果一些參數設置的不恰當,后面隱藏的SurfaceView有可能會露出來,因此這些小技巧并不是好辦法。
但是,有了SurfaceTexture之后,就好辦多了,因為SurfaceTexture不需要顯示到屏幕上,因此我們可以用SurfaceTexture接收來自camera的圖像流,然后從SurfaceTexture中取得圖像幀的拷貝進行處理,處理完畢后再送給另一個SurfaceView用于顯示即可。
而且SurfaceTexture可以輕松的獲取視頻的時間戳數據,不需要我們人工的去計算,同時他還有一個強大的功能就是和Render結合了,而Render是后面要說到GLSurfaceView的核心,就是OpenGL技術了,對于后續圖片和視頻的濾鏡處理,這個發揮著巨大的作用,因為它對圖像流的處理并不直接顯示,而是轉為GL外部紋理,因此可用于圖像流數據的二次處理(如Camera濾鏡,桌面特效等)。比如Camera的預覽數據,變成紋理后可以交給GLSurfaceView直接顯示,也可以通過SurfaceTexture交給TextureView作為View heirachy中的一個硬件加速層來顯示。首先,SurfaceTexture從圖像流(來自Camera預覽,視頻解碼,GL繪制場景等)中獲得幀數據,當調用updateTexImage()時,根據內容流中最近的圖像更新SurfaceTexture對應的GL紋理對象,對于Camera數據源的話可以通過setPreviewTexture方法來設置SurfaceTexture類型,錄制屏幕數據源的話沒有入口可以設置。
3、GLSurfaceView類
GLSurfaceView從Android 1.5(API level 3)開始加入,作為SurfaceView的補充。它可以看作是SurfaceView的一種典型使用模式。在SurfaceView的基礎上,它加入了EGL的管理,并自帶了渲染線程。另外它定義了用戶需要實現的Render接口,提供了用Strategy pattern更改具體Render行為的靈活性。作為GLSurfaceView的Client,只需要將實現了渲染函數的Renderer的實現類設置給GLSurfaceView即可。
這里看到GLSurfaceView有一個特點就是不是系統幫我們繪制預覽畫面了,而是需要我們自己拿到數據之后自己渲染,同時這里會有一個單獨的GL線程來進行刷新數據。
四、流程總結
上面就介紹完了所有類的大致功能以及幾種視頻源采集的數據格式,下面來總結一下:
第一、兩種編碼器MediaCodec和MediaRecorder
MediaCodec可以通過createInputSurface方法設置輸入Surface類型以及configure方法設置輸出Surface類型
MediaRecorder可以通過setVideoSource方法設置視頻源,兩種:一種是攝像頭,一種是錄制屏幕
這兩種編碼器的區別在于:MediaCodec可以處理詳細的視頻流信息,但是MediaRecorder封裝太好了,沒辦法處理。
第二、兩種視頻源Camera和MediaProjection
攝像頭數據源提供了一個回調接口中的一個回調方法:onPreviewFrame(byte[] data...)可以獲取到視頻的每一幀數據
屏幕數據源類VirtualDiaplay提供了一個輸入Surface類型的設置入口類型
第三、視頻源格式和視頻編碼數據格式
攝像頭采集的視頻數據格式是N21和YV12,但是編碼器MediaCodec處理的數據格式是Y420P和Y420SP的,所以這里需要做一次數據格式的轉化,同樣如果想采集攝像頭的每一幀圖片做處理的話,還需要把N21格式轉化成RGB格式。
第四、視頻預覽View
1》這里主要有SurfaceView類型,他主要和SurfaceHolder,Surface相關聯,攝像頭提供了setPreviewDisplay方法設置SurfaceHolder類型。攝像頭可以通過SurfaceView進行數據的預覽,錄制屏幕VirtualDisplay可以提供一個設置Surface入口,所以錄制屏幕也可以通過SurfaceView進行數據的預覽。
2》還有就是TextureView類型,他主要和SurfaceTexture類相對應的,而攝像頭提供了一個setPreviewTexture方法來設置SurfaceTexture類型,但是錄制屏幕的VirtualDisplay沒有,所以攝像頭可以通過TextureView進行數據預覽,但是錄制屏幕不可以。
3》最后就是GLSurfaceView類型了,他是繼承SurfaceView的,在這基礎上添加了OpenGL技術,用來處理視頻數據和圖片數據的。但是GLSurfaceView和前面兩個預覽View不同的是,他需要拿到數據自己進行渲染預覽,大致流程如下:
GLSurfaceView->setRender->onSurfaceCreated回調方法中構造一個SurfaceTexture對象,然后設置到Camera預覽中->SurfaceTexture中的回調方法onFrameAvailable來得知一幀的數據準備好了->requestRender通知Render來繪制數據->在Render的回調方法onDrawFrame中調用SurfaceTexture的updateTexImage方法獲取一幀數據,然后開始使用GL來進行繪制預覽。
五、使用場景
第一種場景:從攝像頭采集視頻數據保存到本地
第一種辦法:
Camera->setPreviewCallback->onPreviewFrame->獲取沒幀數據(N21)->轉化數據格式為Y420SP->給MediaCodec->編碼生成H264格式的視頻流保存到本地
第二種辦法:
設置MediaRecorder的視頻源為Camera即可,但是這個過程中獲取不到攝像頭的沒幀數據,做不了處理了。
第二種場景:錄制屏幕視頻數據保存到本地
MediaProjection->VirtualDisplay->設置輸入Surface(MediaRecorder通過getSurface方法獲取,這里需要設置MediaRecorder視頻源為Surface格式的)
第三種場景:從攝像頭中采集數據做每一幀數據處理獲取圖片
第一種方法:
使用YUVImage類,將N21/YV12格式變成一個Bitmap數據
第二種方法:
獲取每幀數據,將N21/YV12數據格式轉化成ARGB8888格式數據,然后產生圖片
第三種方法:
調用Camera的回調接口PictureCallback中的回調方法 onPictureTaken(byte[] data, final Camera camera)直接獲取的data數據就是圖片格式數據
第四種場景:錄制屏幕時獲取一張圖片(屏幕截圖功能)
使用ImageReader->getSurface->設置到VirtualDisplay類中作為輸入的Surface->ImageReader獲取Image圖片即可
第五個場景:對采集的視頻和圖片做濾鏡效果
我們通過Camera的回調方法onPreviewFrame(byte[] data...)來獲取視頻流中每一幀數據,然后借助強大的OpenGL技術來進行數據處理,做到類似于美顏相機功能
第六個場景:掃描二維碼技術解析
可以借助Camera,獲取每一幀數據,然后進行二維碼的識別。
六、總結
到這里我們就介紹完了視頻直播中大致知識結構,兩個數據源,兩種編碼器,數據格式,三種預覽View,但是我們在預覽View中看到涉及到了OpenGL技術了,沒錯,這個也是我們后續需要介紹的內容,如何使用OpenGL做視頻的濾鏡效果,讓每個直播都那么白,后續會詳細的結合案例來介紹每個知識點。