用 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 系列文章: