WebRTC 系列1--創(chuàng)建相機(jī)預(yù)覽
用 WebRTC 創(chuàng)建相機(jī)預(yù)覽,不到 50 行核心代碼就可以輕松搞定了。
WebRTC 依賴(lài)版本
直接使用官方給的版本就好了,不需要再去額外編譯。
implementation 'org.webrtc:google-webrtc:1.0.30039'
后面都會(huì)使用該版本做測(cè)試的。
相機(jī)權(quán)限申請(qǐng)
WebRTC 雖說(shuō)功能強(qiáng)大,代碼簡(jiǎn)潔,但是并沒(méi)有封裝一個(gè)應(yīng)用權(quán)限申請(qǐng)的接口,這需要自己去操作了。
相機(jī)預(yù)覽
有個(gè)段子是把大象放進(jìn)冰箱有多少步驟,共三步,打開(kāi)冰箱,塞進(jìn)大象,關(guān)上冰箱。
用 WebRTC 創(chuàng)建相機(jī)預(yù)覽和上面的段子步驟一樣,打開(kāi)相機(jī),設(shè)置接收,開(kāi)啟預(yù)覽。
至于中間的繁瑣步驟,比如相機(jī)創(chuàng)建的內(nèi)部實(shí)現(xiàn),預(yù)覽繪制的內(nèi)部實(shí)現(xiàn)都不用去關(guān)心了,調(diào)用好接口,設(shè)置好參數(shù)就行。
創(chuàng)建相機(jī)實(shí)例
在 WebRTC 中相機(jī)實(shí)例統(tǒng)一實(shí)現(xiàn)了 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();
}
該接口也比較簡(jiǎn)單,只需要相機(jī)實(shí)例對(duì)外提供一些簡(jiǎn)單的預(yù)覽能力就好。
創(chuàng)建相機(jī)實(shí)例的代碼如下:
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 是用來(lái)枚舉設(shè)備上有多少攝像頭的,一般只有前置和后置兩種,,也可以用 Camera2Enumerator 來(lái)獲取 Camera2 的相機(jī)調(diào)用。
deviceNames 對(duì)應(yīng) getDeviceNames 方法,只不過(guò)用了 kotlin 變成縮寫(xiě)了,它表示設(shè)備上的攝像頭集合,這個(gè)接口其實(shí)就已經(jīng)屏蔽了 Camera1 和 Camera2 內(nèi)部檢索不同攝像頭的實(shí)現(xiàn)。
滿(mǎn)足前后置條件時(shí),調(diào)用 createCapturer 來(lái)創(chuàng)建相機(jī)實(shí)例就好了。
相機(jī)預(yù)覽接收
需要有分別對(duì)應(yīng)的組件去接收相機(jī)輸出的畫(huà)面并且顯示到屏幕上。
顯示到屏幕上的控件既不是 SurfaceView 也不是 TextureView ,而是 WebRTC 自己封裝的控件 SurfaceViewRenderer 。
它其實(shí)就是繼承了 SurfaceView ,并且內(nèi)部有個(gè) SurfaceEglRenderer 變量,用來(lái)將外界傳遞的 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 渲染進(jìn)行預(yù)覽,在創(chuàng)建 OpenGL 環(huán)境可以決定是否要以 ShareContext 的形式創(chuàng)建。
val eglBaseContext = EglBase.create().eglBaseContext
localView.init(eglBaseContext, null)
接收相機(jī)預(yù)覽流的組件就是 SurfaceTexture ,只不過(guò) WebRTC 將它包裝到了 SurfaceTextureHelper 變量中。
創(chuàng)建 SurfaceTextureHelper 的方法如下:
val eglBaseContext = EglBase.create().eglBaseContext
val surfaceTextureHelper = surfaceTextureHelper.create("CaptureThread", eglBaseContext)
SurfaceTextureHelper 內(nèi)部會(huì)創(chuàng)建一個(gè)線程,并且也可以通過(guò)外部傳遞 EGLContext 以決定是否要走 ShareContext 方式的調(diào)用。
有了相機(jī)實(shí)例 VideoCapturer 和接收預(yù)覽的組件 SurfaceTextureHelper ,就可以將他們關(guān)聯(lián)起來(lái):
videoCapture?.initialize(surfaceTextureHelper, applicationContext, videoSource?.capturerObserver)
videoCapture?.startCapture(480, 640, 30)
videoCapture 調(diào)用 initialize 方法實(shí)現(xiàn)兩者的關(guān)聯(lián),同時(shí) startCapture 方法決定相機(jī)采集的寬高和幀率。
開(kāi)啟相機(jī)預(yù)覽
在開(kāi)啟相機(jī)預(yù)覽時(shí),就需要涉及到和 WebRTC 相關(guān)內(nèi)容了。
WebRTC 本身是用來(lái)做即時(shí)通信的,它將音頻和視頻流都抽象成了一個(gè)個(gè)軌道 MediaStreamTrack ,有音頻軌 AudioTrack 也有視頻軌 VideoTrack。
而軌道上的內(nèi)容來(lái)源就對(duì)應(yīng) MedisSource ,有音頻源 AudioSource 和視頻源 VideoSource 。
相機(jī)輸出就是提供視頻源的,需要將 VideoCapturer 和 VideoSource 關(guān)聯(lián)起來(lái)。
在上面代碼中 initialize 方法實(shí)際上就建立了關(guān)聯(lián)。
videoSource = videoCapture?.isScreencast?.let { factory.createVideoSource(it) }
videoCapture?.initialize(surfaceTextureHelper, applicationContext, videoSource?.capturerObserver)
initialize 方法的最后一個(gè)參數(shù)就是一個(gè)回調(diào),典型的觀察者模式,VideoCapturer 相關(guān)的狀態(tài)都會(huì)通過(guò) capturerObserver 通知到 VideoSource ,從而實(shí)現(xiàn)關(guān)聯(lián)。
創(chuàng)建 videoSource 的 factory ,對(duì)應(yīng)的就是一條即時(shí)通信端對(duì)端的連接,而 videoTrack 和 audioTrack 就是這條連接上的內(nèi)容。
創(chuàng)建 factory 的代碼比較固定:
val options = PeerConnectionFactory.InitializationOptions.builder(this).createInitializationOptions();
PeerConnectionFactory.initialize(options)
factory = PeerConnectionFactory.builder().createPeerConnectionFactory()
創(chuàng)建 VideoTrack 的代碼如下,需要將視頻源和視頻軌道關(guān)聯(lián)起來(lái)。
videoTrack = factory.createVideoTrack("101",videoSource)
完成了所有的創(chuàng)建和關(guān)聯(lián)之后,就可以開(kāi)啟預(yù)覽了。需要將視頻軌道內(nèi)容顯示到畫(huà)面上,也就是上面的 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 行代碼就完成了相機(jī)預(yù)覽,Github 倉(cāng)庫(kù)地址后續(xù)會(huì)給出。
這篇文章就先講到這里,持續(xù)更新中~~
系列文章集合
WebRTC 系列文章:

技術(shù)交流,歡迎加我微信:ezglumes ,拉你入技術(shù)交流群。
推薦閱讀:
開(kāi)通專(zhuān)輯 | 細(xì)數(shù)那些年寫(xiě)過(guò)的技術(shù)文章專(zhuān)輯
NDK 學(xué)習(xí)進(jìn)階免費(fèi)視頻來(lái)了
推薦幾個(gè)堪稱(chēng)教科書(shū)級(jí)別的 Android 音視頻入門(mén)項(xiàng)目
覺(jué)得不錯(cuò),點(diǎn)個(gè)在看唄~

