WebRTC 如何在安卓系统上采集视频数据 | 社区征文

社区征文RTC

目录

前言

正文

摄像头1.0和2.0接口对比

Camera1Capturer 接口类

Camera2Capturer 接口类

结论

前言

WebRTC 作为一个开源的实时音视频通许方案,经过多年的发展基本上已经支持了所有的常用终端,比如 windows、mac、Android、iOS等。我们都知道音视频通讯的前提是采集本地的音频和视频数据信息。今天,我们就来先了解一下 WebRTC 在安卓端是如何采集视频信号的。

正文

安卓设备和苹果iOS设备都属于移动端,在音视频处理的很多地方都是类似的。比如,视频画面的采集和本地预览都会涉及到横屏显示和竖屏显示问题,视频编码时都需要考虑画面角度(0度、90度、180度、270度)问题。

image.png

为此,WebRTC 为安卓端和 iOS 端的 SDK 都提供了非常好用的 API 接口类。其中,安卓端的视频采集类是 CameraCapturer,注意,目前安卓端的摄像头采集有两种方案,一种是使用比较传统的 Camera1Capturer 类,另一种是使用比较新的 Camera2Capturer 类。接下来,分别介绍一下。

之所以会出现 Camera1Capturer 类和 Camera2Capturer 类两套不同的API方案,主要是因为谷歌在开发 Android 5.0 时,对摄像头API进行了全新的颠覆性设计,新增了全新的 Camera V2 接口,这些API不仅大幅提高了 Android 系统拍照的功能,还能支持 RAW 照片输出,甚至允许程序调整相机的对焦模式、曝光模式、快门等。

摄像头1.0和2.0接口对比

下面通过一张对比表格来简单了解一下摄像头1.0和2.0接口的不同。

image.png

看到安卓系统摄像头的2.0接口支持了更多的功能和特性,你是不是会认为现在大家都在用Camera2Capturer 接口类采集本地的视频画面?然而,实时并非如此。尽管谷歌官方也推荐淘汰 Camera1Capturer 接口类,但是大多数企业还是在用它。

Camera1Capturer 接口类

Camera1Capturer 接口类是如何采集摄像头视频画面的,下面结合代码介绍一下。大致流程如下:

步骤一、打开安卓本地前置摄像头,参考代码如下:

final android.hardware.Camera camera;
try {
  camera = android.hardware.Camera.open(CameraInfo.CAMERA_FACING_FRONT);
} catch (RuntimeException e) {
  callback.onFailure(FailureType.ERROR, e.getMessage());
  return;
}

步骤二、设置本地预览画面的显示图层,参考代码如下:

try {
  camera.setPreviewTexture(surfaceTextureHelper.getSurfaceTexture());
} catch (IOException | RuntimeException e) {
  camera.release();
  callback.onFailure(FailureType.ERROR, e.getMessage());
  return;
}

步骤三、设置摄像头参数信息。根据前置摄像头支持的采集参数和系统设置的采集参数进行匹配,计算出最佳且支持的采集参数,其中采集参数涉及画面宽、画面高、画面帧率等,参考代码如下:

final CaptureFormat captureFormat;
try {
  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);
} catch (RuntimeException e) {
  camera.release();
  callback.onFailure(FailureType.ERROR, e.getMessage());
  return;
}

步骤四、设置摄像头采集角度。这是一个预设参数,一般在实际使用过程中会根据当前手机的旋转角度动态变化,可选数值有0度、90度、180度、270度,参考代码如下:

camera.setDisplayOrientation(0 /* degrees */);

步骤五、设置本地视图,参考代码如下:

eglBase = EglBase.create(null /* sharedContext */, EglBase.CONFIG_PLAIN);
localRenderer = (SurfaceViewRenderer) findViewById(R.id.local_renderer);

localRenderer.init(eglBase.getEglBaseContext(), null /* rendererEvents */, EglBase.CONFIG_PLAIN, new GlRectDrawer());

videoCapturerSurfaceTextureHelper =
    SurfaceTextureHelper.create("VideoCapturerThread", eglBase.getEglBaseContext());

步骤六、设置采集数据回调方法,参考代码如下:

eglBase = EglBase.create(null /* sharedContext */, EglBase.CONFIG_PLAIN);
localRenderer = (SurfaceViewRenderer) findViewById(R.id.local_renderer);

localRenderer.init(eglBase.getEglBaseContext(), null /* rendererEvents */, EglBase.CONFIG_PLAIN, new GlRectDrawer());

videoCapturerSurfaceTextureHelper =
    SurfaceTextureHelper.create("VideoCapturerThread", eglBase.getEglBaseContext());

通过上面的六个简单步骤,我们就可以完成在安卓系统上摄像头采集和本地画面预览的效果。接下来,我们看一下 Camera2Capturer 接口类如何完成相同的功能。

Camera2Capturer 接口类

Camera2Capturer 接口类基于安卓系统的 Camera V2 接口开发封装的,原因是谷歌在 Android 5.0 中对摄像头API进行了全新的颠覆性设计,不仅大幅提高了 Android 系统拍照的功能,还能支持 RAW 照片输出,甚至允许程序调整相机的对焦模式、曝光模式、快门等。

那么,WebRTC 中又是如何利用 Camera2Capturer 接口类采集安卓系统的摄像头画面的呢?下面也结合代码分步骤介绍一下。

步骤一、根据安卓设备的相机ID打开本地摄像头,同时设置 CameraStateCallback 回调方法,参考代码如下:

try {
  cameraManager.openCamera(cameraId, new CameraStateCallback(), cameraThreadHandler);
} catch (CameraAccessException e) {
  reportError("Failed to open camera: " + e);
  return;
}

步骤二、设置本地预览画面的显示图层,根据步骤一中设置的摄像头回调事件 onOpened 进行设置,从而绑定图层和摄像头的关系,参考代码如下:

  surfaceTextureHelper.setTextureSize(captureFormat.width, captureFormat.height);
  surface = new Surface(surfaceTextureHelper.getSurfaceTexture());
  try {
    camera.createCaptureSession(
        Arrays.asList(surface), new CaptureSessionCallback(), cameraThreadHandler);
  } catch (CameraAccessException e) {
    reportError("Failed to create capture session. " + e);
    return;
  }

步骤三、设置摄像头相关的采集参数,同样是根据上一步中设置的回调事件,不过这次是 onConfigured 进行设置,参考代码如下:

   try {
    final CaptureRequest.Builder captureRequestBuilder =
        cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
    captureRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,
        new Range<Integer>(captureFormat.framerate.min / fpsUnitFactor,
            captureFormat.framerate.max / fpsUnitFactor));
    captureRequestBuilder.set(
        CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
    captureRequestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false);
    chooseStabilizationMode(captureRequestBuilder);
    chooseFocusMode(captureRequestBuilder);
    captureRequestBuilder.addTarget(surface);
    session.setRepeatingRequest(
        captureRequestBuilder.build(), new CameraCaptureCallback(), cameraThreadHandler);
  } catch (CameraAccessException e) {
    reportError("Failed to start capture request. " + e);
    return;
  }

步骤四、设置视频采集数据回调方法,通过监听渲染图层中的 startListening 方法回调的视频帧得到视频数据,然后通知其他模块,参考代码如下:

   surfaceTextureHelper.startListening((VideoFrame frame) -> {
    checkIsOnCameraThread();

    if (state != SessionState.RUNNING) {
      Logging.d(TAG, "Texture frame captured but camera is no longer running.");
      return;
    }

    if (!firstFrameReported) {
      firstFrameReported = true;
      final int startTimeMs =
          (int) TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - constructionTimeNs);
      camera2StartTimeMsHistogram.addSample(startTimeMs);
    }
    final VideoFrame modifiedFrame =
        new VideoFrame(CameraSession.createTextureBufferWithModifiedTransformMatrix(
                           (TextureBufferImpl) frame.getBuffer(),
                           /* mirror= */ isCameraFrontFacing,
                           /* rotation= */ -cameraOrientation),
            /* rotation= */ getFrameOrientation(), frame.getTimestampNs());
    events.onFrameCaptured(Camera2Session.this, modifiedFrame);
    modifiedFrame.release();
  });

再后续的流程就和 Camera1Capturer 接口类相同了,这里就不再赘述了。

需要注意的是,安卓系统采集完摄像头的视频画面后,处理逻辑一般会一分为二,一部分数据流用来本地预览显示,一部分数据流送到编码模块,进行数据组包并发送给对端。因此,我们在使用过程中经常会遇到本地预览画面没有问题,但是传输到远端的视频画面出现问题,或者是本地预览画面有问题,但是传输到远端的视频却是正常的,类似的问题有花屏、显示比列、裁剪等。

结论

本文基本上已经介绍了 WebRTC 是如何在安卓系统上采集本地摄像头画面的,但是,这仅仅是众多流程中一个小环节,后续还有预览、编码、组包、传输、解包、解码、渲染等过程。关于别的部分的内容,我们在后续章节再继续介绍。

文章来源:https://xie.infoq.cn/article/42cedbde6df76591200f880b4

作者简介:😄大家好,我是 Data-Mining(liuzhen007),是一位典型的音视频技术爱好者,前后就职于传统广电巨头和音视频互联网公司,具有丰富的音视频直播和点播相关经验,对 WebRTC、FFmpeg 和 Electron 有非常深入的了解,😄公众号:玩转音视频。同时也是 CSDN 博客专家、华为云享专家(共创编辑)、InfoQ 签约作者,欢迎关注我分享更多干货!😄

0
0
0
0
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论