Android 音视频采集那些事

news2024/11/24 9:55:08

音视频采集

在整个音视频处理的过程中,位于发送端的音视频采集工作无疑是整个音视频链路的开始。在 Android 或者 IOS 上都有相关的硬件设备——Camera 和麦克风作为输入源。本章我们来分析如何在 Android 上通过 Camera 以及录音设备采集数据。

Camera

在 Android 上的图片/视频采集设备无疑就是 Camera 了,在 Android SDK API21 之前的版本只能使用 Camera1 ,在 API 21 之后 Camera1 已经被标记为 Deprecated ,Google 推荐使用 Camera2,下面我们来分别看一下。

Camera1

我们先来看一下 Camera1 体系的部分类图。

Camera 类是 Camera1 体系的核心类,该类还有好多内部类,如上图:

Camera.CameraInfo 类表达 Camera 的前后(facing)和旋转(orientation)等 Camera 相关的信息。

Camera.Parameters 类是 Camera 相关的参数设置比如设置预览 Size 以及设置旋转角度等。

Camera 类拥有打开 Camera、设置参数、设置预览等 API,下面我们来看使用 Camera API 打开系统照相机的流程。

1.在开启 Camera 之前先释放 Camera,这一步的目的是重置 Camera 的状态重置 Camera 的 previewCallback 为 null

调用 Camera 的 release 释放

把 Camera 对象设置为 null

/**
*释放Camera
*/
  private fun releaseCamera() {
        //重置previewCallback为空
      cameraInstance!!.setPreviewCallback(null)
      cameraInstance!!.release()
      cameraInstance = null
  }

2.获取 Camera 的 Id

/**
*获取Camera Id
*/
 private fun getCurrentCameraId(): Int {
        val cameraInfo = Camera.CameraInfo()
        //遍历所有的Camera id,比较CameraInfo facing 
        for (id in 0 until Camera.getNumberOfCameras()) {
            Camera.getCameraInfo(id, cameraInfo)
            if (cameraInfo.facing == cameraFacing) {
                return id
            }
        }
        return 0
    }

3.打开 Camera 获取 Camera 对象

/**
*获取Camera 实例
*/
 private fun getCameraInstance(id: Int): Camera {
      return try {
        //调用Camera的open函数获取Camera的实例
          Camera.open(id)
      } catch (e: Exception) {
          throw IllegalAccessError("Camera not found")
      }
  }

4.设置 Camera 的相关参数

//[3]设置参数
val parameters = cameraInstance!!.parameters
​
        if (parameters.supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
            parameters.focusMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE
        }
        cameraInstance!!.parameters = parameters

5.设置 previewDisplay

//【4】  调用Camera API 设置预览Surface
        surfaceHolder?.let { cameraInstance!!.setPreviewDisplay(it) }

6.设置预览回调

//【5】 调用Camera API设置预览回调
        cameraInstance!!.setPreviewCallback { data, camera ->
            if (data == null || camera == null) {
                return@setPreviewCallback
            }
            val size = camera.parameters.previewSize
            onPreviewFrame?.invoke(data, size.width, size.height)
        }

7.开启预览

//【6】 调用Camera API开启预览
        cameraInstance!!.startPreview()

上面代码中的【3】【4】【5】【6】都是调用 Camera 类的 API 来完成,

经过上面的流程之后,Camera 的预览会显示在传入的 Surface 上,并且在 Camera 停止前会一直回调函数onPreviewFrame(byte[] data,Camera camera),其中 byte[] data 中存储的就是实时的 YUV 图像数据。byte[] data 的格式是 YUV 格式中的 NV21

【学习地址】:FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发
【文章福利】:免费领取更多音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击1079654574加群领取哦~

  

YUV 图像格式

色彩空间

这里我们只讲常用到的两种色彩空间。

RGBRGB 的颜色模式应该是我们最熟悉的一种,在现在的电子设备中应用广泛。通过 R G B 三种基础色,可以混合出所有的颜色。

YUV 这里着重讲一下 YUV,这种色彩空间并不是我们熟悉的。这是一种亮度与色度分离的色彩格式。

早期的电视都是黑白的,即只有亮度值,即 Y。有了彩色电视以后,加入了 UV 两种色度,形成现在的 YUV,也叫 YCbCr。

Y:亮度,就是灰度值。除了表示亮度信号外,还含有较多的绿色通道量。

U:蓝色通道与亮度的差值。

V:红色通道与亮度的差值。

采用 YUV 有什么优势呢?

人眼对亮度敏感,对色度不敏感,因此减少部分 UV 的数据量,人眼却无法感知出来,这样可以通过压缩 UV 的分辨率,在不影响观感的前提下,减小视频的体积。

RGB 和 YUV 的换算

Y = 0.299R + 0.587G + 0.114B

U = -0.147R - 0.289G + 0.436B

V = 0.615R - 0.515G - 0.100B

——————————————————

R = Y + 1.14V

G = Y - 0.39U - 0.58V

B = Y + 2.03U

YUV 格式

YUV 存储方式分为两大类:planar 和 packed。

  • planar:先存储所有 Y,紧接着存储所有 U,最后是 V;

  • packed:每个像素点的 Y、U、V 连续交叉存储。

pakced 存储方式已经非常少用,大部分视频都是采用 planar 存储方式。

对于 planar 存储方式,通过省略一些色度信息,即亮度共用一些色度信息,进而节省存储空间。因此,planar 又区分了以下几种格式: YUV444、 YUV422、YUV420。

YUV 4:4:4 采样,每一个 Y 对应一组 UV 分量。

YUV 4:2:2 采样,每两个 Y 共用一组 UV 分量。

YUV 4:2:0 采样,每四个 Y 共用一组 UV 分量。

其中,最常用的就是 YUV420

YUV420 格式存储方式又分两种类型

  • YUV420P:三平面存储。数据组成为 YYYYYYYYUUVV(如 I420)或 YYYYYYYYVVUU(如 YV12)。

  • YUV420SP:两平面存储。分为两种类型 YYYYYYYYUVUV(如 NV12)或 YYYYYYYYVUVU(如 NV21)

Camera2

在 Andorid SDK API 21 之后呢,Google 就推荐使用 Camera2 体系来管理设备,Camera2 还是与 Camera1 有很大的不同的。一样的,我们先来看一下 Camera2 体系的部分类图。

Camera2 要比 Camera1 复杂的多,CameraManager CameraCaptureSession 是 Camera2 体系的核心类,CameraManager 用来管理摄像头的打开和关闭 Camera2 引入了 CameraCaptureSession 来管理拍摄会话。

我们下面来看一下更详细的流程图。

1.在开启 Camera 之前先释放 Camera,这一步的目的是重置 Camera 的状态

private fun releaseCamera() {
        imageReader?.close()
        cameraInstance?.close()
        captureSession?.close()
        imageReader = null
        cameraInstance = null
        captureSession = null
    }

2.获取 Camera 的 Id

/**
  *【1】 获取Camera Id
  */
    private fun getCameraId(facing: Int): String? {
        return cameraManager.cameraIdList.find { id ->
            cameraManager.getCameraCharacteristics(id).get(CameraCharacteristics.LENS_FACING) == facing
        }
    }

3.打开 Camera

try {
          //【2】打开Camera,传入的 CameraDeviceCallback()是摄像机设备状态回调
            cameraManager.openCamera(cameraId, CameraDeviceCallback(), null)
        } catch (e: CameraAccessException) {
            Log.e(TAG, "Opening camera (ID: $cameraId) failed.")
        }

//设备状态回调
    private inner class CameraDeviceCallback : CameraDevice.StateCallback() {
        override fun onOpened(camera: CameraDevice) {
            cameraInstance = camera
            //【3】开启拍摄会话
            startCaptureSession()
        }

        override fun onDisconnected(camera: CameraDevice) {
            camera.close()
            cameraInstance = null
        }

        override fun onError(camera: CameraDevice, error: Int) {
            camera.close()
            cameraInstance = null
        }
    }

4.开启拍摄会话

 //【3】开启拍摄会话
  private fun startCaptureSession() {
        val size = chooseOptimalSize()
        //创建ImageRender并设置回调
        imageReader =
                ImageReader.newInstance(size.width, size.height, ImageFormat.YUV_420_888, 2).apply {
                    setOnImageAvailableListener({ reader ->
                        val image = reader?.acquireNextImage() ?: return@setOnImageAvailableListener
                        onPreviewFrame?.invoke(image.generateNV21Data(), image.width, image.height)
                        image.close()
                    }, null)
                }
​
        try {
            if (surfaceHolder == null) {
              //设置ImageRender的surface给cameraInstance,以便后面预览的时候数据呈现到ImageRender的surface,从而触发ImageRender的回调
                cameraInstance?.createCaptureSession(
                        listOf(imageReader!!.surface),
                        //【4】CaptureStateCallback是CameraCaptureSession的内部类,是摄像机会话状态的回调
                        CaptureStateCallback(),
                        null
                )
            } else {
                cameraInstance?.createCaptureSession(
                        listOf(imageReader!!.surface,
                                surfaceHolder!!.surface),
                        CaptureStateCallback(),
                        null
                )
            }
​
        } catch (e: CameraAccessException) {
            Log.e(TAG, "Failed to start camera session")
        }
    }
​
  //摄像机会话状态的回调
    private inner class CaptureStateCallback : CameraCaptureSession.StateCallback() {
        override fun onConfigureFailed(session: CameraCaptureSession) {
            Log.e(TAG, "Failed to configure capture session.")
        }
    //摄像机配置完成
        override fun onConfigured(session: CameraCaptureSession) {
            cameraInstance ?: return
            captureSession = session
            //设置预览CaptureRequest.Builder
            val builder = cameraInstance!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
            builder.addTarget(imageReader!!.surface)
            surfaceHolder?.let {
                builder.addTarget(it.surface)
            }
​
            try {
              //开启会话
                session.setRepeatingRequest(builder.build(), null, null)
            } catch (e: CameraAccessException) {
                Log.e(TAG, "Failed to start camera preview because it couldn't access camera", e)
            } catch (e: IllegalStateException) {
                Log.e(TAG, "Failed to start camera preview.", e)
            }
        }
    }

PS

ImageRender 可以直接访问呈现在 Surface 上得图像数据,ImageRender 的工作原理是创建实例并设置回调,这个回调会在 ImageRender 所关联的 Surface 上的图像可用时调用

我们分析了上面的 Camera 采集数据,完整的代码请看文末的 Github 地址。

AudioRecord

上面分析完了视频,我们接着来看音频,录音 API 我们使用 AudioRecord,录音的流程相对于视频而言要简单许多,一样的,我们先来看一下简单类图。

就一个类,API 也简单明了,我们来看一下流程。

下面上代码

   public void startRecord() {
   //开启录音
        mAudioRecord.startRecording();
        mIsRecording = true;
        //开启新线程轮询
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                byte[] buffer = new byte[DEFAULT_BUFFER_SIZE_IN_BYTES];
                while (mIsRecording) {
                    int len = mAudioRecord.read(buffer, 0, DEFAULT_BUFFER_SIZE_IN_BYTES);
                    if (len > 0) {
                        byte[] data = new byte[len];
                        System.arraycopy(buffer, 0, data, 0, len);
                        //处理data
                    }
                }
            }
        });
​
    }
​
​
    public void stopRecord() {
        mIsRecording = false;
        mAACMediaCodecEncoder.stopEncoder();
        mAudioRecord.stop();
    }

AudioRecord 生成的 byte[] data 即 PCM 音频数据。

小结

本章我们对音视频的原生输入 API 进行了详细的介绍,这个也是我们后面博客的基础,有了 YUV 和 PCM 数据之后,就可以编码了,下一篇我们再来分析 MediaCodec,用 MediaCodec 对原生音视频数据进行硬编码生成 Mp4。

作者:声网

链接:Android 音视频采集那些事 - 掘金 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/90971.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

web网页设计期末课程大作业:家乡旅游主题网站设计——河北8页HTML+CSS+JavaScript

家乡旅游景点网页作业制作 网页代码运用了DIV盒子的使用方法,如盒子的嵌套、浮动、margin、border、background等属性的使用,外部大盒子设定居中,内部左中右布局,下方横向浮动排列,大学学习的前端知识点和布局方式都有…

【爬虫实战项目】Python爬虫批量旅游景点信息数据并保存本地(附源码)

前言 今天给大家介绍的是Python爬虫批量下载旅游景点信息数据,在这里给需要的小伙伴们代码,并且给出一点小心得。 首先是爬取之前应该尽可能伪装成浏览器而不被识别出来是爬虫,基本的是加请求头,但是这样的纯文本数据爬取的人会…

丰立智能在创业板上市:总市值达到33亿元,王友利夫妇为实控人

12月15日,浙江丰立智能科技股份有限公司(下称“丰立智能”,SZ:301368)在深圳证券交易所创业板上市。本次上市,丰立智能的发行价格为22.33元/股,发行数量为3010万股,募资总额约为6.72亿元&#x…

企业数字化进程中,商业智能 BI 如何降本增效

当下形势,不稳定性已是常态,国际形势复杂多变,疫情对各行各业的企业影响巨大,市场环境日新月异,要想在激烈的竞争中站稳脚跟,实现企业盈利,必须降本增效。你是否考虑过利用数字技术这些新手段让…

关于Revit中门窗插入问题技巧和门窗生成

一、Rvit中门窗插入时需要注意的问题和技巧 1.在平面中插入门窗时,在键盘中输入SM门窗会自动定义在墙体的中心位置,如图1所示。 2.空格键可以快速调整门开启的方向 3.在三维视图中插入门窗时,窗户的位置可以任意插入,而插入的门系…

基于springboot休闲娱乐代理售票系统设计与实现的源码+文档

摘要 网络的广泛应用给生活带来了十分的便利。所以把休闲娱乐代理售票管理与现在网络相结合,利用java技术建设休闲娱乐代理售票系统,实现休闲娱乐代理售票的信息化。则对于进一步提高休闲娱乐代理售票管理发展,丰富休闲娱乐代理售票管理经验…

math_常用放缩不等式及其变形@指数@对数@三角函数@一次函数

文章目录三角函数对数分式x>0x>0x>0x∈(0,12π)x\in(0,\frac{1}{2}\pi)x∈(0,21​π)正弦正切x∈(0,1)x\in(0,1)x∈(0,1)有界性正弦余弦反三角x∈Rx\in{R}x∈R指数和幂三角函数对数分式 x>0x>0x>0 sin⁡x<x(x>0)\sin{x}<x(x>0)sinx<x(x>0)…

[GYCTF2020]Easyphp

打开界面&#xff0c;一个登陆的窗口&#xff0c;想到sql注入 然后查看源码没有多余的提示 然后试了一下常见的www.zip成功下载文件 index.php发现了包含文件 <?php require_once "lib.php";if(isset($_GET[action])){require_once(__DIR__."/".$_…

数据结构 | 红黑树、平衡二叉树旋转、并查集

一、红黑树 1.1、红黑树的定义&#xff08;左根右&#xff0c;根叶黑&#xff0c;不红红&#xff0c;黑路同&#xff09; 前提&#xff0c;对于一棵二叉排序树(或者说二叉搜索树)&#xff0c;如果满足以下定义则是红黑树&#xff1a; ①每个结点或是红色&#xff0c;或是黑色的…

2022-12-15 工作记录--React-用swiper实现多行交错、同速、跑马灯效果的弹幕式轮播(坑)

React-用swiper实现多行交错、同速、跑马灯效果的弹幕式轮播&#xff08;坑&#xff09; 激动的心呀呀呀&#xff01;٩(๑>◡<๑)۶ 历时昨天一下午滴时间昨晚凌晨一点多&#x1f31b;还爬起来思考&#x1f914;这个问题&#xff08;眼睛一闭着&#x1f634;&#xff0c…

2471. 逐层排序二叉树所需的最少操作数目-层次遍历+选择排序

2471. 逐层排序二叉树所需的最少操作数目-层次遍历选择排序 给你一个 值互不相同 的二叉树的根节点 root 。 在一步操作中&#xff0c;你可以选择 同一层 上任意两个节点&#xff0c;交换这两个节点的值。 返回每一层按 严格递增顺序 排序所需的最少操作数目。 节点的 层数…

虚拟机的垃圾收集(一)

虚拟机就好比是一个有限空间的一个房子&#xff0c;在我们生活中&#xff0c;也会产生各种各样的垃圾&#xff0c;虚拟机也不例外&#xff0c;垃圾满了会造成内存溢出等问题&#xff0c;那虚拟机是怎么进行垃圾回收的呢?让我们来揭开这神秘的面纱 1.概述 程序计数器、虚拟机…

融一亿但被质疑的5ire为何大家有分歧

5ire是由印度裔企业家 Pratik Gauri 和 Prateek Dwivedi 以及 Web3 金融家 Vilma Mattila 于 2021 年 8 月创立的区块链项目&#xff0c;官方在2022年7月公布以15亿美元估值获得一亿美元融资&#xff0c;机构为英国企业集团 SRAM & MRAM Group &#xff0c;成为印度的第 10…

[附源码]Python计算机毕业设计高校教材管理系统Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

电影《海贼王:红发歌姬》观后感

上周&#xff0c;北京已经开始陆续解封了&#xff0c;电影院也可以进入了&#xff0c;只要持有48小时核算就行&#xff0c;于是去看了这部电影《海贼王&#xff1a;红发歌姬》&#xff0c;去看之前&#xff0c;看了预告片&#xff0c;讲述路飞小时候的玩伴乌塔&#xff0c;长大…

NLP创业破局,如何摘取更高处的果实

点击蓝字关注我们AI TIME欢迎每一位AI爱好者的加入&#xff01;2022年&#xff0c;云从科技、商汤科技先后登陆资本市场&#xff0c;计算机视觉四小龙中的旷视科技、依图科技也在摩拳擦掌。反观NLP领域&#xff0c;相关企业的发展速度、融资规模、上市进程仿佛都要略逊一筹&…

java计算机毕业设计基于安卓Android的二手交易app-闲置物品交易app-ssm

项目介绍 首先,论文一开始便是清楚的论述了系统的研究内容。其次,剖析系统需求分析,弄明白“做什么”,分析包括业务分析和业务流程的分析以及用例分析,更进一步明确系统的需求。然后在明白了系统的需求基础上需要进一步地设计系统,主要包罗软件架构模式、整体功能模块、数据库设…

数组 reduce 方法使用记录

概述 reduce()方法对数组中每个元素执行一次 reduce&#xff08;&#xff09;函数 —升序执行&#xff0c;将其结果汇总为单个返回值。 reduce方法可做的事情特别多&#xff0c;就是训话遍历能做的&#xff0c;reduce都可以做&#xff0c;比如&#xff1a;数组求和&#…

Vue基础快速入门

目录 1.vue基础 1.1vue项目创建 1.2 el挂载 1.3.data数据对象 2.本地应用 2.1.内容绑定&#xff0c;事件绑定 v-text ​v-html 传递自定义参数&#xff0c;事件修饰符 总结&#xff1a; 结合使用&#xff1a;计数器 2.2.显示切换&#xff0c;属性绑定 v-show v…

【笔记】计算机组成原理复习重点——篇一

计算机组成原理复习重点笔记 计算机组成原理计算机体系结构 学科基础必修课 研究生入学考试全国联考45分&#xff0c;占比30% 64学时&#xff0c;4学分&#xff0c;上课56&#xff0c;实验8 教材&#xff1a;计算机组成原理(第二版 ) 唐朔飞 高等教育出版社 目录&#xff08;已…