Android APP 音视频(03)CameraX预览与MediaCodec编码

说明: 此CameraX预览和编码实操主要针对Android12.0系统。通过CameraX预览获取yuv格式数据,将yuv格式数据通过mediacodec编码输出H264码流(使用ffmpeg播放),存储到sd卡上。


1  CameraX 和 MediaCodec简介

1.1 CameraX简介

CameraX 是一个由 Google 开发的 Android Jetpack 库,旨在简化 Android 应用中的相机操作。它提供了一个一致的 API 界面,使得开发者可以更容易地在应用中集成和使用相机功能。以下是 CameraX 的一些关键特点和优势:

  • 简化的 API:CameraX 提供了一个简单且一致的 API,使得开发者可以轻松地访问相机硬件,而无需处理底层的复杂性。
  • 兼容性:CameraX 支持从 Android 5.0(API 级别 21)到最新版本的 Android 系统,确保了广泛的设备兼容性。
  • 预览和捕获:CameraX 允许开发者轻松地实现相机预览和图像捕获功能。它提供了一个预览界面,用户可以通过它查看相机捕获的实时图像。
  • 配置灵活性:CameraX 允许开发者根据需要配置相机的各种参数,如分辨率、帧率、焦距等。
  • 异步处理:CameraX 使用异步处理机制,确保相机操作不会阻塞主线程,从而提高应用的响应性和性能。
  • 权限管理:CameraX 还帮助开发者管理相机权限,确保应用在需要时能够获得必要的权限。
  • 扩展性:CameraX 提供了扩展点,允许开发者根据需要添加额外的功能,如图像处理、视频录制等。
  • 集成简单:通过依赖项添加 CameraX 库到项目中,开发者可以快速开始使用 CameraX。
  • 文档和社区支持:CameraX 拥有详细的文档和活跃的社区,为开发者提供了丰富的资源和支持。

总的来说,CameraX 是一个强大的工具,可以帮助开发者在 Android 应用中实现高质量的相机功能,同时减少开发工作量和提高应用的稳定性。针对本文的实际需求,这里主要参照了如下文章的内容及代码实现:Android APP Camerax应用(02)预览流程

1.2 MediaCodec简介

MediaCodec 是 Android 平台上的一个 API,用于高效地进行多媒体数据的编码和解码操作。它主要用于处理视频和音频数据,支持各种格式的编解码,如 H.264、H.265、VP8、VP9、AAC 等。以下是 MediaCodec 的一些关键特点和功能:

  • 高效处理:MediaCodec 利用硬件加速来处理视频和音频数据,可以显著提高编解码的效率和性能。
  • 格式支持:MediaCodec 支持多种编解码格式,包括但不限于 H.264、H.265、VP8、VP9、AAC、HEVC 等。
  • 可扩展性:开发者可以根据需要扩展 MediaCodec 的功能,例如添加新的编解码器或支持新的媒体格式。
  • 兼容性:MediaCodec 支持从 Android 4.1(Jelly Bean,API 级别 16)到最新版本的 Android 系统。
  • 异步操作:MediaCodec 采用异步操作模式,可以在后台线程中处理编解码任务,从而不会阻塞主线程。
  • 缓冲管理:MediaCodec 提供了对输入输出缓冲区的管理,允许开发者控制数据流的传输和处理。
  • 配置灵活性:开发者可以通过配置 MediaCodec 的参数来调整编解码器的行为,例如设置编码质量、比特率、帧率等。
  • 实时处理:MediaCodec 支持实时视频和音频的编解码,适用于需要快速响应的应用,如视频通话、直播等。
  • 安全性:MediaCodec 支持加密和解密操作,可以处理受保护的媒体内容。
  • 示例和文档:MediaCodec 有丰富的示例代码和文档,帮助开发者快速上手和解决常见问题。

MediaCodec 是 Android 开发者在处理多媒体数据时的重要工具,特别是在需要处理大量视频和音频数据的场景中。通过使用 MediaCodec,开发者可以构建高效、灵活且功能强大的多媒体应用。针对本文的实际需求,这里主要使用了MediaCodec编码相关知识。参照了如下文章的内容及代码实现:Android APP 音视频(02)MediaProjection录屏与MediaCodec编码

2 CameraX预览与MediaCodec编码代码完整解读(android Q)

2.1 关于权限部分的处理

关于权限,需要在AndroidManifest.xml中添加权限,具体如下所示:

<uses-featureandroid:name="android.hardware.camera"android:required="false" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.CAMERA"/>

关于运行时权限的请求等,这里给出一个工具类参考代码,具体如下所示:

public class Permission {public static final int REQUEST_MANAGE_EXTERNAL_STORAGE = 1;//需要申请权限的数组private static final String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.CAMERA};//保存真正需要去申请的权限private static final List<String> permissionList = new ArrayList<>();public static int RequestCode = 100;public static void requestManageExternalStoragePermission(Context context, Activity activity) {if (!Environment.isExternalStorageManager()) {showManageExternalStorageDialog(activity);}}private static void showManageExternalStorageDialog(Activity activity) {AlertDialog dialog = new AlertDialog.Builder(activity).setTitle("权限请求").setMessage("请开启文件访问权限,否则应用将无法正常使用。").setNegativeButton("取消", null).setPositiveButton("确定", (dialogInterface, i) -> {Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);activity.startActivityForResult(intent, REQUEST_MANAGE_EXTERNAL_STORAGE);}).create();dialog.show();}public static void checkPermissions(Activity activity) {for (String permission : permissions) {if (ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) {permissionList.add(permission);}}if (!permissionList.isEmpty()) {requestPermission(activity);}}public static void requestPermission(Activity activity) {ActivityCompat.requestPermissions(activity,permissionList.toArray(new String[0]),RequestCode);}
}

2.2 编码的处理

关于编码部分,主要是MediaCodec的初始化、编码处理部分和文件写入操作,代码如下所示:

public class H264Encoder {MediaCodec mediaCodec;int index;int width;int height;public H264Encoder(int width, int height) {this.width = width;this.height = height;}public void initMediaCodecEncoder()  {try {mediaCodec = MediaCodec.createEncoderByType("video/avc");MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", width, height);mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height);mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2); //IDR帧刷新时间mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);mediaCodec.start();} catch (IOException e) {Log.e("TAG",e.toString());//e.printStackTrace();}}public void startMediaCodecEncoder(byte[] input) {int inputBufferIndex = mediaCodec.dequeueInputBuffer(10000);MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();if (inputBufferIndex >= 0) {ByteBuffer inputBuffer =   mediaCodec.getInputBuffer(inputBufferIndex);if (inputBuffer != null) {inputBuffer.clear();inputBuffer.put(input);}mediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, computPts(), 0);index++;}int outputBufferIndex =   mediaCodec.dequeueOutputBuffer(bufferInfo,100000);if (outputBufferIndex >= 0) {ByteBuffer  outputBuffer= mediaCodec.getOutputBuffer(outputBufferIndex);byte[] data = new byte[bufferInfo.size];if (outputBuffer != null) {outputBuffer.get(data);}FileUtils.writeBytes(data);FileUtils.writeContent(data);mediaCodec.releaseOutputBuffer(outputBufferIndex, false);}}public int computPts() {return 1000000 / 15 * index;}
}

2.3 CameraX预览与H264编码调用主流程代码参考实现

针对CameraX,添加deps依赖。在项目的 build.gradle 文件中添加 CameraX 库的依赖。build.gradle 中添加deps,具体如下:

dependencies {...implementation libs.camera.viewimplementation "androidx.camera:camera-core:1.3.4"
// CameraX Camera2 extensions[可选]拓展库可实现人像、HDR、夜间和美颜、滤镜但依赖于OEMimplementation "androidx.camera:camera-camera2:1.3.4"
// CameraX Lifecycle library[可选]避免手动在生命周期释放和销毁数据implementation "androidx.camera:camera-lifecycle:1.3.4"
// CameraX View class[可选]最佳实践,最好用里面的PreviewView,它会自行判断用SurfaceView还是TextureView来实现implementation libs.androidx.camera.view.v100alpha23...
}

这里以 H264encoderCameraXActivity 为例,给出一个预览流程与H264编码 代码的参考实现。代码如下所示:

public class H264encoderCameraXActivity extends AppCompatActivity {private Button mButton;Context mContext;private PreviewView previewView;private ImageAnalysis imageAnalysis;private ExecutorService executor;private ListenableFuture<ProcessCameraProvider> cameraProviderFuture;private boolean isCapturePreview = false;ProcessCameraProvider mCameraProvider;Preview mPreview;H264Encoder h264Encode = null;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);EdgeToEdge.enable(this);mContext = this;setContentView(R.layout.h264_encode_camerax);ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);return insets;});Permission.checkPermissions(this);Permission.requestManageExternalStoragePermission(getApplicationContext(), this);executor = Executors.newSingleThreadExecutor();previewView = findViewById(R.id.viewFinder);mButton = findViewById(R.id.button);mButton.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {isCapturePreview = !isCapturePreview;if(isCapturePreview){mButton.setText(R.string.startCapture);startCamera();}else{mButton.setText(R.string.stopCapture);stopCamera();}}});// 初始化 ImageAnalysisimageAnalysis = new ImageAnalysis.Builder().setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST).build();imageAnalysis.setAnalyzer(executor, new ImageAnalysis.Analyzer() {@Overridepublic void analyze(@NonNull ImageProxy imageProxy) {if(h264Encode == null){int width = imageProxy.getWidth();int height = imageProxy.getHeight();h264Encode = new H264Encoder(width, height);h264Encode.initMediaCodecEncoder();}Log.d("XXXX","-----------------get Data");// 处理图像数据h264Encode.startMediaCodecEncoder(getYUVDataFromImageProxy(imageProxy));imageProxy.close();}});}public byte[] getYUVDataFromImageProxy(ImageProxy imageProxy) {// 获取 ImageProxy 的宽度和高度int width = imageProxy.getWidth();int height = imageProxy.getHeight();// 创建一个足够大的数组来存储 YUV 数据int yuvSize = width * height * 3 / 2;byte[] yuvBytes = new byte[yuvSize];// 从 ImageProxy 获取 Y 平面的数据ByteBuffer yBuffer = imageProxy.getPlanes()[0].getBuffer();yBuffer.get(yuvBytes, 0, yBuffer.remaining());// 计算 U 和 V 值的起始位置int uvStart = width * height;// 从 ImageProxy 获取 U 和 V 平面的数据ByteBuffer uBuffer = imageProxy.getPlanes()[1].getBuffer();ByteBuffer vBuffer = imageProxy.getPlanes()[2].getBuffer();// 交错 U 和 V 数据到 yuvBytes 数组中for (int i = 0; i < (height*width / 2); i+=2) {int index = uvStart + i;yuvBytes[index] = uBuffer.get();yuvBytes[index + 1] = vBuffer.get();}return yuvBytes;}private void startCamera() {// 请求 CameraProvidercameraProviderFuture = ProcessCameraProvider.getInstance(this);//检查 CameraProvider 可用性,验证它能否在视图创建后成功初始化cameraProviderFuture.addListener(() -> {try {mCameraProvider = cameraProviderFuture.get();bindPreview(mCameraProvider);} catch (ExecutionException | InterruptedException e) {e.printStackTrace();}}, ContextCompat.getMainExecutor(this));}//选择相机并绑定生命周期和用例private void bindPreview(@NonNull ProcessCameraProvider cameraProvider) {mPreview = new Preview.Builder().build();CameraSelector cameraSelector = new CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build();cameraProvider.unbindAll();cameraProvider.bindToLifecycle(this, cameraSelector, mPreview, imageAnalysis);mPreview.setSurfaceProvider(previewView.getSurfaceProvider());}private void stopCamera() {if ((mCameraProvider != null) && mCameraProvider.isBound(mPreview)) {mCameraProvider.unbindAll();imageAnalysis.clearAnalyzer();executor.shutdown();}}@Overrideprotected void onDestroy() {super.onDestroy();if (imageAnalysis != null) {imageAnalysis.clearAnalyzer(); // 清除分析器}if (executor != null) {executor.shutdown(); // 关闭线程池}}
}

其中布局文件 h264_encode_camerax.xml(可自定义) 内容如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:id="@+id/main"tools:context=".MainActivity"><androidx.camera.view.PreviewViewandroid:id="@+id/viewFinder"android:layout_width="372dp"android:layout_height="240dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent"/><Buttonandroid:layout_width="match_parent"android:layout_height="50dp"android:text="@string/startCapture"android:id="@+id/button"app:layout_constraintTop_toBottomOf="@id/viewFinder"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintHorizontal_bias="0.5"/></androidx.constraintlayout.widget.ConstraintLayout>

2.4 CameraX预览与MediaCodec编码 demo实现效果

实际运行效果展示如下:

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

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

相关文章

“微软蓝屏”事件,给IT行业带来的宝贵经验和教训

“微软蓝屏”事件是指2024年7月19日发生的一次全球性技术故障&#xff0c;主要涉及微软视窗&#xff08;Windows&#xff09;操作系统及其相关应用和服务。 以下是对该事件的详细解析&#xff1a; 一、事件概述 发生时间&#xff1a;2024年7月19日事件影响&#xff1a;全球多个…

【科学文献计量】中国知网(CNKI) 文献素材库生成软件详细使用说明

CNKI 文献素材库生成软件制作 1 背景2 使用步骤2.1 文献检索2.2 文献导出2.3 软件生成1 背景 在进行中文文献的综述时,往往是要借助中国知网(CNKI)文献检索平台,写作插入文献时会用Endnote软件进行辅助。因此就有需求:对于CNKI检索的结果直接导出到本地,第一是方便快速阅…

基于STM32的农业大棚温湿度采集控制系统的设计

目录 1、设计要求 2、系统功能 3、演示视频和实物 4、系统设计框图 5、软件设计流程图 6、原理图 7、主程序 8、总结 &#x1f91e;大家好&#xff0c;这里是5132单片机毕设设计项目分享&#xff0c;今天给大家分享的是智能教室。 设备的详细功能见网盘中的文章《8、基…

“机器说人话”-AI 时代的物联网

万物互联的物联网愿景已经提了许多年了&#xff0c;但是实际效果并不理想&#xff0c;除了某些厂商自己的产品生态中的产品实现了互联之外&#xff0c;就连手机控制空调&#xff0c;电视机和调光灯都没有实现。感觉小米做的好一点&#xff0c;而华为的鸿蒙的全场景&#xff0c;…

C#+layui+echarts实现动态生成折线图

概要 C#layuiecharts实现动态生成折线图 整体架构流程 后端是c#语言编写的业务流程,前端是layui和echarts 技术细节 1.先看echarts折线图需要什么样子的数据,在想后端怎么处理 2.后端代码 List<ValveTempData> list new List<ValveTempData>(); string …

Docker容器逃逸漏洞-CVE-2024-21626

Snyk 在 Docker 引擎以及其他容器化技术(例如 Kubernetes)使用的 runc <=1.1.11 的所有版本中发现了一个漏洞。利用此问题可能会导致容器逃逸到底层主机操作系统,无论是通过执行恶意映像还是使用恶意 Dockerfile 或上游映像构建映像(即使用时FROM) CVE-2024-21626原理…

生成式人工智能之路,从马尔可夫链到生成对抗网络

人工智能&#xff08;Artificial intelligence&#xff0c;AI&#xff09;技术在过去几年中取得了显著进展&#xff0c;其中生成式AI&#xff08;Generative AI&#xff09;因其强大的内容生成能力而备受关注。生成式AI可以创建新的文本、图像、音频、视频、代码以及其他形式的…

SSRF学习笔记

1.NAT学习 Nat&#xff08;Network Address Translation&#xff0c;网络地址转换&#xff09;是 一种网络通信技术主要用于将私有网络中的内部IP地址转换成公共网络中的公共IP地址&#xff0c;以实现局域网内部设备访问互联网的功能。具体来说&#xff0c;Nat有以下几个主要…

Matlab画不同指标的对比图

目录 一、指标名字可修改 二、模型名字可修改 三、输入数据可修改 软件用的是Matlab R2024a。 clear,clc,close all figure1figure(1); % set(figure1,Position,[300,100,800,600],Color,[1 1 1]) axes1 axes(Parent,figure1);%% Initialize data points 一、指标名字可修…

MongoDB 学习笔记

一、简介 1、MongoDB 是什么 MongoDB 是一个基于分布式文件存储的数据库&#xff0c;官方地址 https://www.mongodb.com/ 2、数据看是什么 数据库&#xff08;DataBase&#xff09;是按照数据结构来组织、存储和管理数据的应用程序。 3、数据库的作用 主要作用是 管理数据…

mybatis中的缓存(一级缓存、二级缓存)

文章目录 前言一、MyBatis 缓存概述二、一级缓存1_初识一级缓存2_一级缓存命中原则1_StatementId相同2_查询参数相同3_分页参数相同4_sql 语句5_环境 3_一级缓存的生命周期1_缓存的产生2_缓存的销毁3_网传的一些谣言 4_一级缓存核心源码5_总结 三、二级缓存1_开启二级缓存2_二级…

基于Hutool实现自定义模板引擎,实现json个性化模板引擎转换

文章目录 前言编写引擎类&#xff1a;JsonTemplateEngine编写模板类&#xff1a;CustomTemplate编写测试代码测试json文件测试类 前言 由于百度搜索json模板引擎&#xff0c;推荐的都是一些freemarker之类的&#xff0c;需要引入其他的依赖&#xff0c;而且在编写json模板的时…

学习在测试时学习(Learning at Test Time)_ 具有表达性隐藏状态的循环神经网络(RNNs)

摘要 https://arxiv.org/pdf/2407.04620 自注意力机制在长文本语境中表现良好&#xff0c;但其复杂度为二次方。现有的循环神经网络&#xff08;RNN&#xff09;层具有线性复杂度&#xff0c;但其在长文本语境中的性能受到隐藏状态表达能力的限制。我们提出了一种新的序列建模…

LabVIEW多线圈电磁式振动发电机测试

开发了一种基于LabVIEW设计的多线圈电磁式振动发电机测试系统。系统通过高效的数据采集、波峰检测及相位差计算&#xff0c;优化了传统振动发电机的测试流程&#xff0c;提升了电压波形分析的精度和效率&#xff0c;具有较好的应用前景和推广价值。 项目背景 随着可再生能源技…

遇到not allow unquoted fieldName怎么办

前言 Exception in thread "main" com.alibaba.fastjson2.JSONException: not allow unquoted fieldName, offset 2, character , line 1, column 3, fastjson-version 2.0.25 { "data":null, "code":200, "msg":"成功"…

LIS系统源码,实验室管理信息系统LIS,.Net C#语言开发,支持DB2,Oracle,MS SQLServer等主流数据库

实验室管理信息系统LIS源码&#xff0c;采用.Net C#语言开发&#xff0c;C/S架构。支持DB2&#xff0c;Oracle&#xff0c;MS SQLServer等主流数据库。&#xff08;LIS系统全套商业源码&#xff0c;自主版权&#xff0c;多家大型综合医院应用案例&#xff0c;适合二次开发&…

Golang | Leetcode Golang题解之第279题完全平方数

题目&#xff1a; 题解&#xff1a; // 判断是否为完全平方数 func isPerfectSquare(x int) bool {y : int(math.Sqrt(float64(x)))return y*y x }// 判断是否能表示为 4^k*(8m7) func checkAnswer4(x int) bool {for x%4 0 {x / 4}return x%8 7 }func numSquares(n int) i…

四、GD32 MCU 常见外设介绍 (6) ADC 模块介绍

6.1.ADC 基础知识 12 位逐次逼近式模数转换器模块&#xff08;ADC&#xff09;&#xff0c;可以采样来自于外部输入通道、内部输入通道的模拟信号&#xff0c;采样转换后&#xff0c;转换结果可以按照最低有效位对齐或最高有效位对齐的方式保存在相应的数据寄存器中。 6.2.GD…

elasticsearch-7.3.1安装注意事项

elasticsearch-7.3.1安装注意事项 一、背景二、步骤1、查看原ES版本2、新环境服务器2.1、是否有elasticsearch2.2、安装elasticsearch2.3、配置参数2.4、启动elasticsearch2.5、设置密码 三、报错-问题总结1、jdk不适用2、bootstrap checks failed3、Address already in use4、…

vue3前端开发-小兔鲜项目-图片切换效果和动态class

vue3前端开发-小兔鲜项目-图片切换效果和动态class!这次实现的效果是&#xff0c;图片预览效果&#xff0c;根据小图片&#xff0c;来实时改变大图&#xff08;预览&#xff09;的效果。同时让动态的特征class也跟着显示出来。 <script setup> import {ref} from vue // …