WebRTC 系列1--創(chuàng)建相機預覽

用 WebRTC 創(chuàng)建相機預覽,不到 50 行核心代碼就可以輕松搞定了瑰妄。

WebRTC 依賴版本

直接使用官方給的版本就好了轧铁,不需要再去額外編譯珍德。


implementation 'org.webrtc:google-webrtc:1.0.30039'

后面都會使用該版本做測試的蚤告。

相機權限申請

WebRTC 雖說功能強大努酸,代碼簡潔,但是并沒有封裝一個應用權限申請的接口杜恰,這需要自己去操作了获诈。

相機預覽

有個段子是把大象放進冰箱有多少步驟,共三步心褐,打開冰箱舔涎,塞進大象,關上冰箱逗爹。

用 WebRTC 創(chuàng)建相機預覽和上面的段子步驟一樣亡嫌,打開相機,設置接收,開啟預覽昼伴。

至于中間的繁瑣步驟,比如相機創(chuàng)建的內部實現(xiàn)镣屹,預覽繪制的內部實現(xiàn)都不用去關心了圃郊,調用好接口,設置好參數(shù)就行女蜈。

創(chuàng)建相機實例

在 WebRTC 中相機實例統(tǒng)一繼承了 VideoCapturer 接口持舆,不管是 Camera1 還是 Camera2 。


public interface VideoCapturer {

void initialize(SurfaceTextureHelper var1, Context var2, CapturerObserver var3);

void startCapture(int var1, int var2, int var3);

void stopCapture() throws InterruptedException;

void changeCaptureFormat(int var1, int var2, int var3);

void dispose();

boolean isScreencast();

}

該接口也比較簡單伪窖,只需要相機實例對外提供一些簡單的預覽能力就好逸寓。

創(chuàng)建相機實例的代碼如下:


private fun createVideoCapture(): VideoCapturer? {

val enumerator = Camera1Enumerator(false)

val deviceNames = enumerator.deviceNames

for (deviceName in deviceNames) {

if (enumerator.isFrontFacing(deviceName)) {

val videoCapture = enumerator.createCapturer(deviceName, null)

if (videoCapture != null) {

return videoCapture

}

}

}

return null

}

Camera1Enumerator 是用來枚舉設備上有多少攝像頭的,一般只有前置和后置兩種覆山,竹伸,也可以用 Camera2Enumerator 來獲取 Camera2 的相機調用。

deviceNames 對應 getDeviceNames 方法簇宽,只不過用了 kotlin 變成縮寫了勋篓,它表示設備上的攝像頭集合,這個接口其實就已經屏蔽了 Camera1 和 Camera2 內部檢索不同攝像頭的實現(xiàn)魏割。

滿足前后置條件時譬嚣,調用 createCapturer 來創(chuàng)建相機實例就好了。

相機預覽接收

需要有分別對應的組件去接收相機輸出的畫面并且顯示到屏幕上钞它。

顯示到屏幕上的控件既不是 SurfaceView 也不是 TextureView 拜银,而是 WebRTC 自己封裝的控件 SurfaceViewRenderer 。

它其實就是繼承了 SurfaceView 遭垛,并且內部有個 SurfaceEglRenderer 變量尼桶,用來將外界傳遞的 VideoFrame 繪制到屏幕上。


<org.webrtc.SurfaceViewRenderer android:id="@+id/localView"

android:layout_width="match_parent"

android:layout_height="match_parent"/>

// SurfaceViewRenderer 的繪制方法

public void onFrame(VideoFrame frame) {

this.eglRenderer.onFrame(frame);

}

SurfaceEglRenderer 也是走的 OpenGL 渲染進行預覽耻卡,在創(chuàng)建 OpenGL 環(huán)境可以決定是否要以 ShareContext 的形式創(chuàng)建疯汁。


val eglBaseContext = EglBase.create().eglBaseContext

localView.init(eglBaseContext, null)

接收相機預覽流的組件就是 SurfaceTexture ,只不過 WebRTC 將它包裝到了 SurfaceTextureHelper 變量中卵酪。

創(chuàng)建 SurfaceTextureHelper 的方法如下:


val eglBaseContext = EglBase.create().eglBaseContext

val surfaceTextureHelper = surfaceTextureHelper.create("CaptureThread", eglBaseContext)

SurfaceTextureHelper 內部會創(chuàng)建一個線程幌蚊,并且也可以通過外部傳遞 EGLContext 以決定是否要走 ShareContext 方式的調用。

有了相機實例 VideoCapturer 和接收預覽的組件 SurfaceTextureHelper 溃卡,就可以將他們關聯(lián)起來:


videoCapture?.initialize(surfaceTextureHelper, applicationContext, videoSource?.capturerObserver)

videoCapture?.startCapture(480, 640, 30)

videoCapture 調用 initialize 方法實現(xiàn)兩者的關聯(lián)溢豆,同時 startCapture 方法決定相機采集的寬高和幀率。

開啟相機預覽

在開啟相機預覽時瘸羡,就需要涉及到和 WebRTC 相關內容了漩仙。

WebRTC 本身是用來做即時通信的,它將音頻和視頻流都抽象成了一個個軌道 MediaStreamTrack ,有音頻軌 AudioTrack 也有視頻軌 VideoTrack队他。

而軌道上的內容來源就對應 MedisSource 卷仑,有音頻源 AudioSource 和視頻源 VideoSource 。

相機輸出就是提供視頻源的麸折,需要將 VideoCapturer 和 VideoSource 關聯(lián)起來锡凝。

在上面代碼中 initialize 方法實際上就建立了關聯(lián)。


videoSource = videoCapture?.isScreencast?.let { factory.createVideoSource(it) }

videoCapture?.initialize(surfaceTextureHelper, applicationContext, videoSource?.capturerObserver)

initialize 方法的最后一個參數(shù)就是一個回調垢啼,典型的觀察者模式窜锯,VideoCapturer 相關的狀態(tài)都會通過 capturerObserver 通知到 VideoSource ,從而實現(xiàn)關聯(lián)芭析。

創(chuàng)建 videoSource 的 factory 锚扎,對應的就是一條即時通信端對端的連接,而 videoTrack 和 audioTrack 就是這條連接上的內容馁启。

創(chuàng)建 factory 的代碼比較固定:


val options = PeerConnectionFactory.InitializationOptions.builder(this).createInitializationOptions();

PeerConnectionFactory.initialize(options)

factory = PeerConnectionFactory.builder().createPeerConnectionFactory()

創(chuàng)建 VideoTrack 的代碼如下驾孔,需要將視頻源和視頻軌道關聯(lián)起來。


videoTrack = factory.createVideoTrack("101",videoSource)

完成了所有的創(chuàng)建和關聯(lián)之后进统,就可以開啟預覽了助币。需要將視頻軌道內容顯示到畫面上,也就是上面的 SurfaceViewRenderer 控件上螟碎。


videoTrack?.addSink(localView)

完整代碼示例:


class CameraActivity : AppCompatActivity() {

private lateinit var factory: PeerConnectionFactory

private var videoCapture:VideoCapturer? = null

private var videoSource: VideoSource? = null

private var videoTrack: VideoTrack? = null

private lateinit var localView:SurfaceViewRenderer

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_camera)

localView = findViewById(R.id.localView)

val options = PeerConnectionFactory.InitializationOptions.builder(this).createInitializationOptions();

PeerConnectionFactory.initialize(options)

factory = PeerConnectionFactory.builder().createPeerConnectionFactory()

val eglBaseContext = EglBase.create().eglBaseContext

val surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", eglBaseContext)

videoCapture = createVideoCapture()

videoSource = videoCapture?.isScreencast?.let { factory.createVideoSource(it) }

videoCapture?.initialize(surfaceTextureHelper, applicationContext, videoSource?.capturerObserver)

videoCapture?.startCapture(480, 640, 30)

localView.setMirror(true)

localView.init(eglBaseContext, null)

videoTrack = factory.createVideoTrack("101",videoSource)

videoTrack?.addSink(localView)

}

private fun createVideoCapture(): VideoCapturer? {

val enumerator = Camera1Enumerator(false)

val deviceNames = enumerator.deviceNames

for (deviceName in deviceNames) {

if (enumerator.isFrontFacing(deviceName)) {

val videoCapture = enumerator.createCapturer(deviceName, null)

if (videoCapture != null) {

return videoCapture

}

}

}

return null

}

}

不到 50 行代碼就完成了相機預覽眉菱,Github 倉庫地址后續(xù)會給出。

這篇文章就先講到這里掉分,持續(xù)更新中~~

系列文章集合

WebRTC 系列文章:

  1. # WebRTC & Android 開發(fā)學習環(huán)境搭建~
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末俭缓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子酥郭,更是在濱河造成了極大的恐慌华坦,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件不从,死亡現(xiàn)場離奇詭異惜姐,居然都是意外死亡,警方通過查閱死者的電腦和手機椿息,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進店門歹袁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人寝优,你說我怎么就攤上這事条舔。” “怎么了乏矾?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵孟抗,是天一觀的道長迁杨。 經常有香客問我,道長凄硼,這世上最難降的妖魔是什么铅协? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮摊沉,結果婚禮上警医,老公的妹妹穿的比我還像新娘。我一直安慰自己坯钦,他們只是感情好,可當我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布侈玄。 她就那樣靜靜地躺著婉刀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪序仙。 梳的紋絲不亂的頭發(fā)上突颊,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天,我揣著相機與錄音潘悼,去河邊找鬼律秃。 笑死,一個胖子當著我的面吹牛治唤,可吹牛的內容都是我干的棒动。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼宾添,長吁一口氣:“原來是場噩夢啊……” “哼船惨!你這毒婦竟也來了?” 一聲冷哼從身側響起缕陕,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤粱锐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后扛邑,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體怜浅,經...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年蔬崩,在試婚紗的時候發(fā)現(xiàn)自己被綠了恶座。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡舱殿,死狀恐怖奥裸,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情沪袭,我是刑警寧澤湾宙,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布樟氢,位于F島的核電站,受9級特大地震影響侠鳄,放射性物質發(fā)生泄漏埠啃。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一伟恶、第九天 我趴在偏房一處隱蔽的房頂上張望碴开。 院中可真熱鬧,春花似錦博秫、人聲如沸潦牛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽巴碗。三九已至,卻和暖如春即寒,著一層夾襖步出監(jiān)牢的瞬間橡淆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工母赵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留逸爵,地道東北人。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓凹嘲,卻偏偏與公主長得像师倔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子周蹭,可洞房花燭夜當晚...
    茶點故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內容