前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >WebRTC 如何在安卓系统上采集视频数据

WebRTC 如何在安卓系统上采集视频数据

作者头像
liuzhen007
发布于 2022-02-23 12:08:22
发布于 2022-02-23 12:08:22
2.8K00
代码可运行
举报
文章被收录于专栏:流媒体音视频流媒体音视频
运行总次数:0
代码可运行

目录

前言

正文

  • 摄像头1.0和2.0接口对比
  • Camera1Capturer 接口类
  • Camera2Capturer 接口类

结论

前言

WebRTC 作为一个开源的实时音视频通讯方案,经过多年的发展基本上已经支持了所有的常用终端,比如 windows、mac、AndroidiOS等。我们都知道音视频通讯的前提是采集本地的音频和视频数据信息。今天,我们就来先了解一下 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接口的不同。

Camera1Capturer 接口类

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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;
}

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
try {
  camera.setPreviewTexture(surfaceTextureHelper.getSurfaceTexture());
} catch (IOException | RuntimeException e) {
  camera.release();
  callback.onFailure(FailureType.ERROR, e.getMessage());
  return;
}

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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度,参考代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
camera.setDisplayOrientation(0 /* degrees */);

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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());

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

oesTextureId = GlUtil.generateTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES); surfaceTexture = new SurfaceTexture(oesTextureId); setOnFrameAvailableListener(surfaceTexture, (SurfaceTexture st) -> { hasPendingTexture = true; tryDeliverTextureFrame(); }, handler); 通过上面的六个简单步骤,我们就可以完成在安卓系统上摄像头采集和本地画面预览的效果。接下来,我们看一下 Camera2Capturer 接口类如何完成相同的功能。

Camera2Capturer 接口类

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

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
try {
  cameraManager.openCamera(cameraId, new CameraStateCallback(), cameraThreadHandler);
} catch (CameraAccessException e) {
  reportError("Failed to open camera: " + e);
  return;
}

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  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 进行设置,参考代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
   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 方法回调的视频帧得到视频数据,然后通知其他模块,参考代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
   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 是如何在安卓系统上采集本地摄像头画面的,但是,这仅仅是众多流程中一个小环节,后续还有预览、编码、组包、传输、解包、解码、渲染等过程。关于别的部分的内容,我们在后续章节再继续介绍。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-02-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 玩转音视频 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
《JavaSE》---9.<基础语法(java数组的3种初始化&常规使用)>
1.数组是一段连续的内存空间,所以支持随机访问,通过下标访问快速访问数组中任意位置的元素
用户11288958
2024/09/24
1740
《JavaSE》---9.<基础语法(java数组的3种初始化&常规使用)>
【 JavaSE 】 深入数组
目录 前言 一维数组 创建一维数组 一维数组的使用 数组作参数 认识 JVM 内存区域划分 数组做参数基本用法 理解引用类型 认识 null 数组作为方法的返回值 二维数组 二维数组的长度 二维数组的遍历 数组练习 ---- 前言 ---- 本章主要讲解: 一维数组的定义和使用 数组在内存的基本存储知识 二维数组的定义和使用 数组练习 一维数组 ---- 什么是数组: 数组本质上就是让我们能 "批量" 创建相同类型的变量(相同的类型) 注:特别是表示大量的数据,用数组非常便捷 创建一维数组 基本
用户9645905
2022/11/30
4130
【 JavaSE 】 深入数组
知识改变命运 第七集(下):Java中数组的定义与使用
使用这个方法后续打印数组就更方便一些. Java 中提供了 java.util.Arrays 包, 其中包含了一些操作数组的常用方法
用户11319080
2024/10/17
940
知识改变命运 第七集(下):Java中数组的定义与使用
《JavaSE-第六章》之容器数组
本章大家介绍一个存储相同数据类型的容器----数组,以及便于我们对数组操作的工具类Arrays类的使用。
用户10517932
2023/10/07
2190
《JavaSE-第六章》之容器数组
java中数组的定义与使用
Java中的数组跟c语言的数组几乎不一样,我们要区分对待。在之后你就能理解到我为什么说这句话了。
E绵绵
2024/04/08
2800
java中数组的定义与使用
【Java SE】详解数组
前言:在C语言中我们已经学习过数组,接下来,我们再主要学习一下Java中的数组,在Java中,数组变得更加高效和使用。
用户11369558
2024/11/20
1100
【Java SE】详解数组
【Java】——数组深度解析(从内存原理到高效应用实践)
数组是一种复合数据类型,它可以看作是一个容器,用于存储多个相同类型的变量。这些变量在内存中按顺序排列,每个变量都有一个唯一的索引,通过索引可以快速访问数组中的元素。在Java中,数组是对象,无论它存储的是基本数据类型还是引用数据类型。
User_芊芊君子
2025/04/08
850
【Java】——数组深度解析(从内存原理到高效应用实践)
【JAVASE】数组技巧与实践:提升你的编程能力
for-each 是 for 循环的另外一种使用方式. 能够更方便的完成对数组的遍历. 可以避免循环条件和更新语句写错
小舒不服输
2024/01/30
1300
【JAVASE】数组技巧与实践:提升你的编程能力
【Java篇】内存中的桥梁:Java数组与引用的灵动操作
假设现在要存储5个学生的JavaSE考试成绩,并对其进行输出,按照之前掌握的知识,我们可能会写出如下代码:
半截诗
2025/05/07
910
【Java篇】内存中的桥梁:Java数组与引用的灵动操作
java中数组遍历的三种方式
通常遍历数组都是使用for循环来实现。遍历一维数组很简单,遍历二维数组需要使用双层for循环,通过数组的length属性可获得数组的长度。
全栈程序员站长
2022/09/05
1.5K0
Java基础(六):数组
java.util.Arrays类即为操作数组的工具类,包含了用来操作数组(比如排序和搜索)的各种方法
Java微观世界
2025/01/21
620
Java基础(六):数组
【JavaSE】Java入门三(数组详解三千字)
静态初始化:在创建数组时不直接指定数据元素的个数,而直接用具体的数据内容进行指定。
小皮侠
2024/04/08
840
【JavaSE】Java入门三(数组详解三千字)
【Java宝典】——探索数组的奥秘之旅
所有被native修饰的是由C/C++进行实现的,所有我们不能看到这个方法的源码,但是它的优点是运行速度比较快
ImAileen
2024/09/20
670
【Java宝典】——探索数组的奥秘之旅
Java基础-数组
前两篇介绍了Java的数据类型和流程控制,现在来讲一下Java的数组,作为一种引用类型,也是非常常见和常用的。这次的知识框架如下所示。
reload
2024/01/16
2680
Java基础-数组
【Java宝典】——二维数组的寻宝之旅
由上面的输出结果我们可以得出:其实二维数组的本质就是一个大的一维数组array,里面包含着两个小的一维数组。由此我们可以将上面的循环打印二维数组的语句修改为如下样式:
ImAileen
2024/09/20
740
【Java宝典】——二维数组的寻宝之旅
【JAVA基础&高级】 数组篇
在实际开发当中我们更多的会使用集合来代替数组,但是集合的底层也是基于数组来实现的,所以花了一些时间对数组的知识点进行了复习巩固,并在此对一些知识点进行记录。
LCyee
2020/08/10
3520
【JAVA基础&高级】 数组篇
【06】JAVASE-数组讲解【从零开始学JAVA】
Java 是第一大编程语言和开发平台。它有助于企业降低成本、缩短开发周期、推动创新以及改善应用服务。如今全球有数百万开发人员运行着超过 51 亿个 Java 虚拟机,Java 仍是企业和开发人员的首选开发平台。
用户4919348
2024/05/25
1710
【06】JAVASE-数组讲解【从零开始学JAVA】
【趣学程序】Java中的数组
数组简介: 数组(Array)是Java 语言中内置的一种基本数据存储结构,通俗的理解,就是一组数的集合,目的是用来一次存储多个数据。数组是程序中实现很多算法的基础,可以在一定程度上简化代码的书写。 注意 数组的好处:数组里的每个元素都有编号,编号从0开始,并且依次递增,方便操作这些元素; 使用Java数组:必须先声明数组,再给该数组分配内存; 数组对应在内存中一段连续空间。 数组元素必须是相同数据类型,也可以是引用数据类型,但是同一个数组中的元素必须是同一类数据类型。 一维数组 一维数组:可以理解为一列多
趣学程序-shaofeer
2019/07/27
5820
5. 数组
(2)数组的元素类型:即创建的数组容器可以存储什么数据类型的数据​。元素的类型可以是任意的Java的数据类型。例如:int、String、Student等。
捞月亮的小北
2023/12/01
2230
5. 数组
新手小白学JAVA 数组 数组工具类 二维数组
数组Array,标志是[ ] ,用于储存多个相同类型数据的集合 想要获取数组中的元素值,可以通过脚标(下标)来获取 数组下标是从0开始的,下标的最大值是数组的长度减1
全栈程序员站长
2022/08/14
5240
新手小白学JAVA 数组 数组工具类 二维数组
相关推荐
《JavaSE》---9.<基础语法(java数组的3种初始化&常规使用)>
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验