<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 9175字,需瀏覽 19分鐘

           ·

          2021-08-31 21:42

          用 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(48064030)

          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(48064030)

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

          1. WebRTC & Android 開(kāi)發(fā)學(xué)習(xí)環(huán)境搭建~



          技術(shù)交流,歡迎加我微信:ezglumes ,拉你入技術(shù)交流群。

          推薦閱讀:

          音視頻面試基礎(chǔ)題

          OpenGL ES 學(xué)習(xí)資源分享

          開(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è)在看唄~


          瀏覽 33
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  sese国产 | 色天天天| 粉嫩小泬BBBB免费看 | 狼人奸伊人综合成人网 | 五月婷婷天中文字幕版 |