WebRTC 源码分析 (一) Android 相机采集

本文详细分析了WebRTC Android中的Camera1和Camera2的采集流程,包括初始化、相机数据处理、预览及Native层的帧处理。通过时序图展示了从Java到JNI的调用过程,并探讨了Camera1和Camera2的差异。此外,文章还介绍了从源码层面如何进行相机数据的预览和渲染,并提供了一个实战Demo的GitHub链接。

1、简介

WebRTC Android 中,已经兼容了 Camera 和 Camera2 原生 API 的相机采集,所以我们不必再单独实现一套采集功能。不过我们可以根据 RTC 的抽象 CameraCapturer 接口实现 CameraX (其实也是基于 Camera2的封装) 的相机采集,显然,这不是该篇的主题就不再多说了。该篇文章,主要为大家解析 WebRTC 的相机采集从 java 到 Jni 的一个调用过程。

先上一个整个调用的时序图,有条件的同学可以根据时序图来跟踪源码:

其实整个涉及到的文件还是比较多的

原图链接: http://devyk.top/2022/WebRTC相机采集流程.png

2、相机采集

WebRTC Android 中定义了一个抽象的视频采集接口为 VideoCapturer , 内部定义了 初始化(SurfaceTexture 渲染帮助类 、相机采集 VideoFrame 回调 )开始/结束/释放 等采集生命周期 API, 最终实现有 Camera1Capturer 和 Camera2Capturer 2 个子类。

为了有一个更清晰的一个结构,下面可以看一下 VideoCapturer 类图:

下面我们分别来介绍 webrtc 是如何来对 Camera1/2 进行创建并使用的,首先我们先看下上面的类图, 最顶层是 VideoCapturer 抽象接口

// Base interface for all VideoCapturers to implement.
public interface VideoCapturer {
​
  void initialize(SurfaceTextureHelper surfaceTextureHelper, Context applicationContext,
      CapturerObserver capturerObserver);
​
  void startCapture(int width, int height, int framerate);
​
  void stopCapture() throws InterruptedException;
​
  void changeCaptureFormat(int width, int height, int framerate);
​
  void dispose();
​
  boolean isScreencast();
}

它的直接实现为 CameraVideoCapturer 然后它具体的实现是一个抽象 CameraCapturer , 该抽象类封装了 Camera1/2 公共部分,当调用 startCapture 接口,内部实现了 createCameraSession 抽象函数,以供 Camera1/2 各自进行创建 Session,可以看下下面代码的调用:

abstract class CameraCapturer implements CameraVideoCapturer {
  
  ...
    
  @Override
  public void startCapture(int width, int height, int framerate) {
   ...
   createSessionInternal(0);
  }
  
  private void createSessionInternal(int delayMs) {
    cameraThreadHandler.postDelayed(new Runnable() {
      @Override
      public void run() {
        createCameraSession(createSessionCallback, cameraSessionEventsHandler, applicationContext,
            surfaceHelper, cameraName, width, height, framerate);
      }
    }, delayMs);
  }
  
 //Camera 1 and Camera1 实现
 abstract protected void createCameraSession(CameraSession.CreateSessionCallback    createSessionCallback, CameraSession.Events events,
      Context applicationContext, SurfaceTextureHelper surfaceTextureHelper, String cameraName,
      int width, int height, int framerate);
}

可以看到最终抽象的实现就是最上面类图中的 Camera1CapturerCamera2Capturer ,我们还是分别进行分析。

2.1 Camera 1

如果最上层使用 Camera1 进行 startCapture, 最终将执行到如下代码:

 public Camera1Capturer(
      @Override
  protected void createCameraSession(CameraSession.CreateSessionCallback  createSessionCallback,
      CameraSession.Events events, Context applicationContext,
      SurfaceTextureHelper surfaceTextureHelper, String cameraName, int width, int height,
      int framerate) {
    
    Camera1Session.create(createSessionCallback, events, captureToTexture, applicationContext,surfaceTextureHelper, Camera1Enumerator.getCameraIndex(cameraName), width, height,framerate);
  }
}  

可以看到函数体中具体是 Camera1Session#create 的调用,我们跟进去看一下

class Camera1Session implements CameraSession {
  ...
    
  public static void create(final CreateSessionCallback callback, final Events events,
      final boolean captureToTexture, final Context applicationContext,
      final SurfaceTextureHelper surfaceTextureHelper, final int cameraId, final int width,
      final int height, final int framerate) {
    final long constructionTimeNs = System.nanoTime();
  
    ...
​
    final android.hardware.Camera camera;
    camera = android.hardware.Camera.open(cameraId);
    camera.setPreviewTexture(surfaceTextureHelper.getSurfaceTexture());
​
    final android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
    android.hardware.Camera.getCameraInfo(cameraId, info);
​
    final CaptureFormat captureFormat;
    final android.hardware.Camera.Parameters parameters = camera.getParameters();
    captureFormat = findClosestCaptureFormat(parameters, width, height, framerate);
    final Size pictureSize = findClosestPictureSize(parameters, width, height);
      updateCameraParameters(camera, parameters, captureFormat, pictureSize, captureToTexture);
​
    if (!captureToTexture) {
      final int frameSize = captureFormat.frameSize();
      for (int i = 0; i < NUMBER_OF_CAPTURE_BUFFERS; ++i) {
        final ByteBuffer buffer = ByteBuffer.allocateDirect(frameSize);
        camera.addCallbackBuffer(buffer.array());
      }
    }
​
    // Calculate orientation manually and send it as CVO insted.
    camera.setDisplayOrientation(0 /* degrees */);
​
    //内部会设置采集的 NV21 or OES 回调
    callback.onDone(new Camera1Session(events, captureToTexture, applicationContext,
        surfaceTextureHelper, cameraId, camera, info, captureFormat, constructionTimeNs));
  }  
}  

可以看到这一步就是通过 Camera.open 打开摄像头,并进行帧率、采集格式、预览等一些基础设置.

  private Camera1Session(...) {
​
 ...
    surfaceTextureHelper.setTextureSize(captureFormat.width, captureFormat.height);
​
    startCapturing();
  }

当实例化 Camera1Session 时,会首先设置 surfaceTexture 的缓冲大小,也就是分辨率,最后是调用 this.startCapturing()

在 startCapturing 实现体中,调用了 camera 的预览函数,我们一起来看下

  private void startCapturing() {
    Logging.d(TAG, "Start captur
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值