今天,我们就来先了解一下 WebRTC 在安卓端是如何采集视频信号的。

正文

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

为此,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 接口的不同。

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

本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

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 是如何在安卓系统上采集本地摄像头画面的,但是,这仅仅是众多流程中一个小环节,后续还有预览、编码、组包、传输、解包、解码、渲染等过程。

本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐