1. 程式人生 > >攝像頭Camera視訊源資料採集解析

攝像頭Camera視訊源資料採集解析

一、前言

在視訊直播中一般都是兩種視訊資料來源,一個是攝像頭資料,一個是錄製桌面資料,而一般來說美女妹子直播都是來自於攝像頭資料,遊戲直播都是錄製桌面資料的,那麼今天就來看看第一個資料來源資料採集分析,Android中使用攝像頭的場景很多,在沒有直播這個行業出現之前,之前用到攝像頭的最多就兩個場景,一個是二維碼掃描,一個是美顏拍照類的應用。那麼這裡就來看看Android中的攝像頭的用法,以及如何進行資料採集進行資料的二次加工。

二、知識點概述

本篇文章主要通過如下幾個方向去介紹Android中的攝像頭Camera知識:
1、Camera攝像頭的基本操作
2、Camera攝像頭的前置和後置區分


3、Camera攝像頭的資料格式
4、Camera攝像頭方向和資料尺寸
5、Camera攝像頭的對焦拍照
6、Camera攝像頭的資料採集以及二次加工

先來看看一張效果圖:
這裡寫圖片描述

三、知識點詳解

通過上面的效果圖,可以看到,可以切換前置和後置攝像頭,可以對焦拍照,可以加水印效果。下面就來一一介紹內容

第一、Camera攝像頭的基本操作

Android中使用一個攝像頭其實很簡單,首先需要在AndroidManifest.xml中宣告許可權:
這裡寫圖片描述
然後程式碼中進行初始化操作即可:
這裡寫圖片描述
初始化操作比較簡單,就幾步:
1、第一步:開啟攝像頭,使用open方法
這個方法有兩種形式,一種是無引數形式的,預設開啟是後置攝像頭,還有一種方式是有引數像是,可以通過傳遞的引數來決定開啟是前置還是後置攝像頭,0代表後置攝像頭,1代表前置攝像頭。
2、第二步:設定攝像頭的預覽資料介面


可以不進行設定的,如果預覽一般有兩種方式,一種是呼叫setPreviewDisplay方法設定SurfaceHolder,也就是和SurfaceView進行綁定了,還有一種就是呼叫setPreviewTexture方法設定SurfaceTexture的,這個就和GLSurfaceView以及TextureView綁定了,這兩種方式在介紹視訊直播的基礎知識的時候已經介紹了,還不瞭解的同學可以點選這裡:基礎知識介紹。後續如果要做美顏效果的話GLSurfaceView用的就比較多了,因為他本身集成了OpenGL的功能,而且二次處理的資料可以進行回顯的。
3、第三步:獲取到Camera.Parameters引數資訊

通過getParameters方法獲取攝像頭已有的引數資訊,然後進行相關設定,比如尺寸大小,方向,資料格式等資訊。
4、第四步:在把新增好的引數資訊設定回去,呼叫startPreview開始預覽效果了

同樣的攝像頭的銷燬方法也很簡單:
這裡寫圖片描述
也就四步:
1、第一步:將攝像頭的預覽清空
2、第二步:停止預覽效果
3、第三步:釋放攝像頭
因為系統預設只能同時開啟一個攝像頭不管是前置攝像頭還是後置攝像頭,所以不用的時候一定要釋放
4、第四步:置空攝像頭物件

第二、Camera攝像頭的前置和後置區分

Android中的攝像頭Camera是區分前置和後置的,所以這裡就要做一個前置和後置攝像頭的切換功能了,我們可以通過一個方法來獲取當前系統的攝像頭個數,以及攝像頭的資訊:
這裡寫圖片描述
這個方法先獲取當前裝置中有多少個攝像頭,一般現在裝置都是兩個,一個是前置,一個是後置的,我們得到資訊之後在列印看看效果:
這裡寫圖片描述
看到了,只有兩個攝像頭,而且通過預設的旋轉角度得知,後置攝像頭是90,前置是270,他們始終相差180度,不過這些和後面要說到的設定攝像頭方向的效果沒關係的。
那麼切換攝像頭Android中的做法是把之前的攝像頭先銷燬在重新初始化下一個攝像頭:
這裡寫圖片描述
然後用一個全域性的變數來記錄當前是前置還是後置,在初始化方法中直接使用這個變數:
這裡寫圖片描述

第三、Camera攝像頭的資料格式

Android中攝像頭的資料格式是指原生的每一幀資料,他是有指定格式的,而這些原生的每一幀資料可以有兩個地方獲取,一個是添加回調,在onPreviewFrame(byte[] data, Camera camera)回撥方法中獲取,還有一個就是拍照的時候回撥方法:onPictureTaken(byte[] data, Camera camera),當然如果想處理資料的話,肯定是在第一個回撥方法中去進行操作了,第二種必須在拍照的時候把當前拍照的一幀資料拿到,這明顯不靠譜。不管是那種獲取一幀資料,這些都是有一定格式的,如果要後期處理的話,那麼必須要做一次格式轉化,Android中的攝像頭資料的格式有兩種,可以進行設定,當然直接使用程式碼可以獲取到攝像頭可以支援的資料格式:在Camera.Parameters類的getSupportedPreviewFormats方法即可獲得:
這裡寫圖片描述
看一下執行結果:
這裡寫圖片描述
有兩種格式,這裡打印出來的是一個int值,如何檢視這個int值對應的格式呢?可以去ImageFormat類中去查詢:
第一種格式:
這裡寫圖片描述
這個就是17對應16進位制值,也就是ImageFormat.NV21格式的
第二種格式:
這裡寫圖片描述

這個就是842094169對應的16進位制值,也就是ImageFormat.YV12格式的。
上面瞭解了攝像頭只支援這兩種資料格式,那麼我們後續肯定需要做資料處理,資料處理一般是和ARGB,以及後續的視訊編碼格式YUV420之間的轉化,不過還好的是,這個轉化格式網上已經有很多的,而且後續看到一些美顏功能都是處理這些資料格式的,同時這些操作最好放到native層做,因為效率會高一些。

第四、Camera攝像頭方向和資料尺寸

Android中攝像頭如果我們想要得到預期的資料的話,那麼方向和尺寸非常關鍵,Camera中提供了一些方法可以進行這些引數的設定的,首先來看一下攝像頭的方向問題:
這裡寫圖片描述
我們看到這裡有兩個方法可以來設定攝像頭的方向資訊,一個是Camera類本身的setDisplayOrientation方法,一個是Camera.Parameters類的setRotation方法,那麼這兩個方法有什麼區別呢?
首先第一個方法:setDisplayOrientation方法是設定攝像頭資料預覽的方向的
就是我們用一個SurfaceView作為預覽介面,在介面看到的方向。
Android中預設的預覽方向是:橫屏,旋轉度是:0,所以如果你把當前Activity設定成橫屏,不呼叫這個方法的話,效果是正常的:
這裡寫圖片描述
但是,如果現在不設定橫屏,因為Activity預設是豎屏的:
這裡寫圖片描述
看到了效果,攝像頭的預覽方向沒有發生變化的,所以這時候就需要呼叫setDisplayOrientation進行設定了,這個方法是按照逆時針旋轉的,所以如果想讓預覽方向變正的話,就需要逆時針旋轉90度即可:
這裡寫圖片描述

好了,到這裡就知道了Camera的預覽方向設定了,預設是:橫屏方向,旋轉度是0,如果想豎著拍攝的話,需要逆時針旋轉90度即可。
還有一個設定方向,就是設定拍照的圖片方向方法:setRotation這個方法的原理和上面的預覽差不多,預設是:橫屏方向,旋轉度是0,如果想豎著拍照的話,需要逆時針旋轉90度就可以了,但是這個方法的作用是設定Camera在拍照之後圖片方向的,關於拍照後面會介紹。
說到這裡感覺還有一個方向不對,就是原生的每一幀資料的方向,的確,這裡有一個問題就是我們在後面處理每一幀資料的時候方向是橫向0旋轉度的效果,而且沒有方法可以進行設定,這個就比較蛋疼了,不過還好,因為我們後續再拿到每一幀資料肯定要做處理,到時候再做一次旋轉就可以了

說完方向問題,下面繼續來看尺寸問題:
設定尺寸大小也有兩個方法,這兩個方法都是在Camera.Parameters類中的:setPreviewSize和setPictureSize,到這裡就知道了這兩個方法其實和上面方向的兩個方法是一致的,第一個方法setPreviewSize是設定視訊的預覽尺寸的,第二個方法setPictureSize是設定拍照之後的圖片尺寸大小的,不過這裡有一個好處就是每一幀原生資料的尺寸可以獲取到了,其實就是和預覽的尺寸保持一致的,不像上面的方向不能設定。關於尺寸問題,其實是有指定限制的,同樣可以通過一個方法來獲取Camera所支援的尺寸大小:getSupportedPreviewSizes
這裡寫圖片描述

執行結果:
這裡寫圖片描述
這是我的裝置攝像頭支援這些尺寸的,我們在設定尺寸大小的時候會在這裡進行選擇的,比如這裡選擇了720*480尺寸,我們看一下效果:
這裡寫圖片描述
看到攝像頭採集的資料沒有失真,但是有一個問題就是壓縮了感覺,這個原因也很簡單,因為720*480的,之前說過這些尺寸是按照橫屏定義的,所以我們現在是豎屏的,可以把尺寸調換位置就可以了,我們在每一幀資料中列印log:
這裡寫圖片描述
看一下結果:
這裡寫圖片描述
這裡的每一幀資料的尺寸就是上面設定的預覽資料的。

下面來看一下設定拍照圖片的尺寸,這裡設定400*400:setPictureSize(400, 400);
我們拍照,然後把圖片儲存到本地,在使用ExifInterface類來讀取圖片的元資料,這個類很重要的,可以讀出圖片的所有詳細資訊,比如在哪裡拍的,什麼時候拍的,方向,尺寸等資訊,也就是傳說中的圖片的exif資訊。
這裡寫圖片描述
列印結果如下:
這裡寫圖片描述
結果尺寸就是我們上面設定的拍照圖片的尺寸大小。
好了到這裡,我們知道了Camera也有兩個方法可以設定尺寸大小,一個是設定預覽的尺寸同時這個尺寸可以同步到每一幀資料的尺寸,還有就是拍照的尺寸大小。

第五、Camera攝像頭的對焦拍照

再來看看Camera如何進行對焦拍照功能,這個其實就是一個簡單的一個方法即可,在Camera類中的takePicture方法,這個方法的定義:takePicture(ShutterCallback shutter, PictureCallback raw, PictureCallback jpeg)
看到了,就是三個回撥介面:
第一個回撥介面用於:拍照之前的工作,比如現在系統的拍照有一個提示聲音,我們當然也可以自己定義一個,就可以在這個回撥介面中進行播放我們想要的提示聲音即可。一般這個介面不怎麼用。
第二個回撥介面用於:原生的資料,就是當你拍照的那一幀資料的原生格式也就是NV21或者是YV12格式的。
第三個回到介面用於:直接一張圖片資料,可以直接將資料儲存成一張圖片即可,無需進行資料格式轉化。
程式碼很簡單:
這裡寫圖片描述
因為這裡只關心圖片,所以實現了最後一個介面:
這裡寫圖片描述
拿到data資料,直接儲存圖片即可。然後在讀取這張圖片的exif資訊。
但是我們在拍照的時候這麼做還是有一個問題的,那就是拍出來的圖片會模糊,因為有抖動效果,所以這裡需要做一個聚焦效果,這個Camera也是有一個方法:autoFocus,同樣也是一個回撥介面:autoFocus(AutoFocusCallback cb):
這裡寫圖片描述
這裡只有聚焦成功了之後才會進行拍照的。通過前面知識點可以知道,通過setPictureSize方法可以設定這個圖片的大小尺寸的。通過setRotation方法可以設定圖片的方向,而且這裡還有一個點需要注意的是:拍完照之後需要再次呼叫Camera的startPreview方法,不然SurfaceView畫面就停留在了當前頁面了。

第六、Camera攝像頭的資料採集以及二次加工

說完上面的所有知識之後,接下來這個知識點就是本文的重點,也是後續視訊直播推流的重點核心,就是攝像頭Camera的資料採集和二次加工。後續推流會在這裡拿到每一幀資料進行傳遞,每一幀資料進行美化加水印都是在這裡做。
Android中的攝像頭Camera提供了兩個方式回撥介面來獲取每一幀資料:
第一種方式:setPreviewCallback方法,設定回撥介面:PreviewCallback
在回撥方法:onPreviewFrame(byte[] data, Camera camera) 中處理每一幀資料
第二種方式:setPreviewCallbackWithBuffer方法,同樣設定回撥介面:PreviewCallback,不過還需要一個方法配合使用:addCallbackBuffer,這個方法接受一個byte陣列。

第二種方式和第一種方式唯一的區別:
第一種方式是onPreviewFrame回撥方法會在每一幀資料準備好了就呼叫,但是第二種方式是在需要在前一幀的onPreviewFrame方法中呼叫addCallbackBuffer方法,下一幀的onPreviewFrame才會呼叫,同時addCallbackBuffer方法的引數的byte資料就是每一幀的原資料。所以這麼一看就好理解了,就是第一種方法的onPreviewFrame呼叫是不可控制的,就是每一幀資料準備好了就回調,但是第二種方法是可控的,我們通過addCallbackBuffer的呼叫來控制onPreviewFrame的回撥機制。

注意:
因為第二種方式在呼叫的時候有點注意的地方:
1》在呼叫Camera.startPreview()介面前,我們需要setPreviewCallbackWithBuffer,而setPreviewCallbackWithBuffer之前我們需要重新addCallbackBuffer,因為setPreviewCallbackWithBuffer 使用時需要指定一個位元組陣列作為緩衝區,用於預覽影象資料 即addCallbackBuffer,然後你在onPerviewFrame中的data才會有值。
2》從上面看來,我們設定addCallbackBuffer的地方有兩個,一個是在startPreview之前,一個是在onPreviewFrame中,這兩個都需要呼叫,如果在onPreviewFrame中不呼叫,那麼,就無法繼續回撥到onPreviewFrame中了。

本文直說第一種方式,下面就來看看如何通過獲取攝像頭的每一幀資料,進行二次處理:
這裡寫圖片描述

程式碼處理其實也很簡單,主要分為三步:
1、第一步:資料轉化
把NV21/YV12格式轉化成ARGB格式在生成一張圖片,這裡有兩種方式,一種是直接使用系統的YuvImage類進行轉化即可,要傳遞圖片的尺寸,這個可以通過getPreviewSize方法獲取到。還有一種方式就是使用網上的轉化工具類即可,不過像這種工具類轉化工作應該放到native層去做,效率高很多。
2、圖片二次處理
上面說到了,Camera中雖然有兩個可以設定方向的方法,但是一個是設定預覽方向的方法,一個是設定拍照圖片的方向,沒有設定每一幀原始資料的方向,所以這裡我們轉化得到的一張圖片應該是橫著的,因為Camera預設的模式是:橫屏+0度旋轉,所以這裡如果想讓圖片方向正確的話,應該做一次圖片旋轉,而且前置攝像頭和後置攝像頭旋轉的角度不一樣,不過好理解的是,前置攝像頭和後置攝像頭就相差180。旋轉之後得到正確方向的圖片之後,開始新增水印效果了,新增水印效果非常簡單:

這裡寫圖片描述

直接使用Canvas畫布即可。
3、處理之後的圖片資料加工
上面處理了資料之後得到我們想要的圖片資料,那麼下面就可以進行後續操作了,這裡是直接進行展示,後續一篇文章會介紹把這資料推流到服務端。本文先不介紹了。

這裡看到了,核心的方法就是在這裡,我們先處理資料格式,然後加上我們想要的效果,比如水印,美顏,因為美顏不是本文的重點,為什麼呢?因為美顏需要用到OpenGL相關的知識,後續會詳細介紹如何使用OpenGL來處理圖片。有了這個知識點,很多工作都可以做了:
1、美顏相機
首先得到每一幀圖片之後,進行處理美白,這裡還可以做人臉識別來新增美顏貼紙,這個就需要人臉識別演算法了。不過這裡需要注意的一點是,美顏相機預覽的時候肯定使用的GLSurfaceView的,因為我們在之前的一篇基礎知識:點選這裡檢視 瞭解到了GLSurfaceView和SurfaceView以及SurfaceTexture的區別就是,SurfaceView無法將二次處理之後的資料再次回顯到介面上了,而我們看到美顏相機在使用的時候,選著一種濾鏡的時候,畫面會立即改變的,其實這個就是使用GLSurfaceView來做的,因為GLSurfaceView首先可以結合OpenGL,其次他可以把二次處理之後的圖片在回顯到介面中的。
2、二維碼掃描
得到每一幀資料之後立馬識別二維碼,識別成功立即退出即可。

四、技術總結

1、攝像頭Camera的基本操作:初始化操作和銷燬操作
1》初始化操作:呼叫open方法直接開啟攝像頭,然後設定預覽載體,在設定攝像頭的一些引數資訊,最後設定每一幀的回撥介面,開始預覽效果。
2》銷燬操作:置空每一幀的回撥介面,停止預覽效果,釋放攝像頭
2、攝像頭Camera的前置和後置效果
Android中的前置攝像頭和後置攝像頭切換是通過把之前的一個攝像頭釋放,然後在重新初始化下一個攝像頭,同時用一個全域性的變數來標誌當前攝像頭的狀態即可,使用open帶有引數的形式來決定開啟那種攝像頭。
3、攝像頭Camera的資料格式處理
Android中的Camera的資料格式是可以設定的,但是攝像頭只支援兩種格式NV21和YV12,所以我們在後續的資料二次處理就需要做資料格式轉化,一般都是把資料轉化成ARGB格式或者是視訊編碼的YUV420格式。
4、攝像頭Camera的尺寸和方向設定
1》Android中的Camera可以獲取到當前所支援的尺寸大小,但是需要注意的是,因為Camera預設的方向模式是:橫屏+0度旋轉,所以寬度*高度尺寸是針對於橫屏來說的,所以看到這些尺寸都會發現寬度比高度值大。如果裝置是豎屏的話,我們需要做一次尺寸調換。同時可以支援兩種方式設定尺寸大小的,一個是可以設定預覽的尺寸大小,而這種大小將會同步到到每一幀原始資料的尺寸大小的,還有一個就是可以設定拍照之後的圖片大小。
2》Android中的Camera的可以支援兩種方向設定的,一種是預覽方向設定,還有一種是拍照之後的圖片方向設定,這裡我們學習到了圖片的exif資訊處理。但是唯獨沒有每一幀原始資料的方向設定,所以我們後續再處理每一幀資料的時候需要手動的做一次方向旋轉,旋轉的時候還要區分前置攝像頭和後置攝像頭的旋轉角度。
5、攝像頭Camera對焦拍照
Android中Camera可以通過autoFocus方法設定對焦回撥方法,然後在使用takePicture方法設定回撥方法獲取拍照之後的圖片資料,可以直接儲存成圖片即可。無需資料格式轉化。
6、攝像頭Camera的原生每一幀資料採集
這個知識點是最重要的,是後續推流和編碼的核心點,他能夠獲取到攝像頭的每一幀資料,我們可以在這裡做二次處理,比如把原生的NV21/YV12資料格式轉化成ARGB格式,然後新增水印效果。

五、用途

瞭解到了上面的知識點之後,我們可以做的事情就很多了,比如美顏相機我們是可以實現的,二維碼掃描也是可以的。如果想做美顏的貼紙功能就需要人臉識別演算法,如果想使用更多的濾鏡效果,那麼就需要強大的OpenGL來做圖片處理。