Android APP 音视频(02)MediaProjection录屏与MediaCodec编码
说明: 此MediaProjection 录屏和编码实操主要针对Android12.0系统。通过MediaProjection获取屏幕数据,将数据通过mediacodec编码输出H264码流(使用ffmpeg播放),存储到sd卡上。
1 MediaProjection录屏与编码简介
这里主要是使用MediaProjection获取屏幕数据,将数据通过mediacodec编码输出到存储卡上。这里主要介绍 MediaProjection的基本原理和流程、 MediaCodec编码的简单说明,便于对代码有所理解。
1.1 MediaProjection录屏原理和流程
MediaProjection 是 Android 提供的一个用于屏幕捕捉和屏幕录制的功能,它允许应用程序在获得用户授权的情况下捕获设备屏幕的内容。这项技术自 Android 5.0(Lollipop)起引入,并在之后的版本中得到广泛应用和发展。
MediaProjection 的主要组件包括:
- MediaProjectionManager:系统服务,用于创建和管理 MediaProjection 会话。
- MediaProjection:表示屏幕捕获会话的令牌,通过用户的授权获得。
- VirtualDisplay:一个虚拟的显示设备,它可以捕获屏幕内容并将其渲染到指定的 Surface 上。
录屏功能的实现流程如下:
- 权限申请:APP需要请求用户授权使用屏幕录制功能。这会涉及
AndroidManifest.xml
文件的修改以及添加必要的权限,如WRITE_EXTERNAL_STORAGE
和RECORD_AUDIO
。 - 触发用户授权:通过
MediaProjectionManager
创建一个 Intent 来触发系统的屏幕录制授权界面。用户同意授权后,应用程序可以在onActivityResult
中接收到结果。 - 获取 MediaProjection 实例:如果用户授权成功,则可以通过
MediaProjectionManager
的getMediaProjection()
方法获取一个MediaProjection
实例 。 - 创建 VirtualDisplay:使用
MediaProjection
实例创建VirtualDisplay
,它将捕获屏幕内容并将其显示在 Surface 上。 - 开始录制:调用
MediaRecorder
的start()
方法开始录制屏幕内容。 - 结束录制:录制完成后,调用
MediaRecorder
的stop()
和reset()
方法停止录制并重置MediaRecorder
状态,然后释放VirtualDisplay
资源。
MediaProjection 录屏的原理主要是通过系统授权,捕获屏幕内容并利用虚拟显示设备将内容渲染到录制器上,实现屏幕录制的功能。开发者在使用时需要考虑到用户授权、资源管理和异常处理等关键步骤 。
1.2 MediaCodec编码说明
MediaCodec 是 Android 提供的一个音视频编解码器类,允许应用程序对音频和视频数据进行编码(压缩)和解码(解压缩)。它在 Android 4.1(API 级别 16)版本中引入,广泛应用于处理音视频数据,如播放视频、录制音频等。
以下是 MediaCodec 编码的基本步骤:
-
创建 MediaCodec 实例:通过调用
MediaCodec.createEncoderByType
方法并传入编码类型(如 "video/avc" 或 "audio/mp4a-latm")来创建编码器。 -
配置编码参数:通过调用
configure
方法配置编码器,传入编码参数如比特率、帧率、编码格式等。 -
准备输入和输出 Surface:为编码器准备输入和输出 Surface。输入 Surface 用于传递待编码的数据,输出 Surface 用于接收编码后的数据。
-
开始编码:调用
start
方法启动编码器。 -
发送输入数据:将待编码的数据通过
write
方法发送到编码器的输入队列。 -
处理输出数据:监听输出队列,通过
dequeueOutputBuffer
方法获取编码后的数据,并进行处理或存储。 -
停止编码:编码完成后,调用
stop
方法停止编码器。 -
释放资源:调用
release
方法释放编码器资源。
MediaCodec 支持处理三种数据类型:压缩数据、原始音频数据和原始视频数据。这些数据可以通过 ByteBuffer 传输给 MediaCodec 进行处理。对于原始视频数据,使用 Surface 作为输入源可以提高编解码器的性能。针对本工程,主要通过获得录屏的原始数据,通过mediacodec压缩成H264码流。
2 MediaProjection录屏与编码代码完整解读(android Q)
2.1 关于权限部分的处理
关于权限,需要在AndroidManifest.xml中添加权限,具体如下所示:
<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.FOREGROUND_SERVICE"/>
这里尤其要注意android.permission.FOREGROUND_SERVICE的添加。关于运行时权限的请求等,这里给出一个工具类参考代码,具体如下所示:
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 MediaProjection服务的添加
从 Android 12 开始,如果应用需要使用 MediaProjection
进行屏幕录制,必须将相关的服务声明为前台服务。这是因为屏幕录制涉及到用户隐私,因此系统需要确保用户明确知道该服务正在运行。需要在应用的 AndroidManifest.xml
文件中声明服务,并添加相应的权限(2.1中已经添加)和特性,具体编写参考如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"...><uses-permission android:name="android.permission.FOREGROUND_SERVICE" /><application ...><serviceandroid:name=".serviceset.MediaProjectionService"android:exported="true"android:foregroundServiceType="mediaProjection" /><!-- 其他组件声明 --></application>
</manifest>
添加这些后,接下来需要实现.serviceset.MediaProjectionService 的代码,具体如下所示:
public class MediaProjectionService extends Service {private MediaProjection mMediaProjection;public static int resultCode;public static Intent resultData;public static Notification notification;public static Context context;@Overridepublic void onCreate() {super.onCreate();startMediaProjectionForeground();}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {MediaProjectionManager mMediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);MediaProjection mediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, resultData);H264EncoderThread h264EncoderThread = new H264EncoderThread(mediaProjection, 640, 1920);h264EncoderThread.start();return START_NOT_STICKY;}@Nullable@Overridepublic IBinder onBind(Intent intent) {return null;}private void startMediaProjectionForeground() {String channelId = "CHANNEL_ID_MEDIA_PROJECTION";NotificationManager NOTIFICATION_MANAGER = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this,channelId).setSmallIcon(R.mipmap.ic_launcher).setContentTitle("服务已启动");NotificationChannel channel = new NotificationChannel(channelId, "屏幕录制", NotificationManager.IMPORTANCE_HIGH);NOTIFICATION_MANAGER.createNotificationChannel(channel);notificationBuilder.setChannelId(channelId);Notification notification = notificationBuilder.build();startForeground(1, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION);}
}
2.3 编码的处理
关于编码部分,主要是MediaCodec的初始化、编码处理部分和文件写入操作,代码如下所示:
public class H264EncoderThread extends Thread{private MediaProjection mMediaProjection;MediaCodec mediaCodec;private final String TAG = "H264EncoderThread";public H264EncoderThread(MediaProjection mMediaProjection, int width, int height) {this.mMediaProjection = mMediaProjection;MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, width, height);try {mediaCodec = MediaCodec.createEncoderByType("video/avc");format.setInteger(MediaFormat.KEY_FRAME_RATE, 20);format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 30);format.setInteger(MediaFormat.KEY_BIT_RATE, width * height);format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);mediaCodec.configure(format,null,null,CONFIGURE_FLAG_ENCODE);Surface surface= mediaCodec.createInputSurface();mMediaProjection.createVirtualDisplay("wangdsh-test", width, height, 2,DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, surface, null, null);} catch (IOException e) {Log.e("TAG",e.toString());//e.printStackTrace();}}@Overridepublic void run() {super.run();mediaCodec.start();MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();while (true) {int outIndex = mediaCodec.dequeueOutputBuffer(info, 11000);if (outIndex >= 0) {ByteBuffer byteBuffer = mediaCodec.getOutputBuffer(outIndex);byte[] ba = new byte[byteBuffer.remaining()];byteBuffer.get(ba);FileUtils.writeBytes(ba);FileUtils.writeContent(ba);mediaCodec.releaseOutputBuffer(outIndex, false);}}}
}
其中涉及的FileUtils参考实现如下:
public class FileUtils {private static final String TAG = "FileUtils";public static void writeBytes(byte[] array) {FileOutputStream writer = null;try {writer = new FileOutputStream(Environment.getExternalStorageDirectory() + "/codecoutput.h264", true);writer.write(array);writer.write('\n');writer.close();} catch (IOException e) {e.printStackTrace();}}public static String writeContent(byte[] array) {char[] HEX_CHAR_TABLE = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};StringBuilder sb = new StringBuilder();for (byte b : array) {sb.append(HEX_CHAR_TABLE[(b & 0xf0) >> 4]);sb.append(HEX_CHAR_TABLE[b & 0x0f]);}Log.d(TAG, "writeContent-: " + sb.toString());try {FileWriter writer = new FileWriter(Environment.getExternalStorageDirectory() + "/codecH264.txt", true);writer.write(sb.toString());writer.write("\n");writer.close();} catch (IOException e) {e.printStackTrace();}return sb.toString();}
}
2.4 主流程代码参考实现
这里以 H264encoderMediaProjActivity为例,给出一个MediaProjection录屏与编码功能代码的参考实现。具体实现如下:
public class H264encoderMediaProjActivity extends AppCompatActivity {private MediaProjectionManager mMediaProjectionManager;Context mContext;private ActivityResultLauncher<Intent> screenCaptureLauncher;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);EdgeToEdge.enable(this);mContext = this;setContentView(R.layout.h264_encode_media_projection);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);mMediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);screenCaptureLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),result -> {if (result.getResultCode() == Activity.RESULT_OK) {Intent resultData = result.getData();MediaProjectionService.resultCode = result.getResultCode();MediaProjectionService.resultData = resultData;MediaProjectionService.context = mContext;Intent SERVICE_INTENT = new Intent(this, MediaProjectionService.class);startForegroundService(SERVICE_INTENT);}});Button mButton = findViewById(R.id.button);mButton.setOnClickListener(view -> {// 创建屏幕录制的 IntentIntent captureIntent = mMediaProjectionManager.createScreenCaptureIntent();// 启动屏幕录制请求screenCaptureLauncher.launch(captureIntent);});}
}
这里涉及的layout布局文件内容如下:
<?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"><Buttonandroid:layout_width="match_parent"android:layout_height="50dp"android:text="@string/startLive"android:gravity="center"android:id="@+id/button"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout>
2.5 MediaProjection录屏与编码 demo实现效果
实际运行效果展示如下:
使用ffmpeg对码流进行播放,说明编码生成的码流是有效的,截图如下所示:
相关文章:

Android APP 音视频(02)MediaProjection录屏与MediaCodec编码
说明: 此MediaProjection 录屏和编码实操主要针对Android12.0系统。通过MediaProjection获取屏幕数据,将数据通过mediacodec编码输出H264码流(使用ffmpeg播放),存储到sd卡上。 1 MediaProjection录屏与编码简介 这里…...

java中log4j.properties配置文件浅析
Log4J的配置文件(Configuration File)就是用来设置记录器的级别、存放器和布局的,它可按keyvalue格式的设置或xml格式的设置信息。通过配置,可以创建出Log4J的运行环境。 1、配置文件 Log4J配置文件的基本格式如下: #配置根Logger log4j.roo…...

RV1126 Linux 系统,接外设,时好时坏(二)排查问题的常用命令
在 RV1126 Linux 系统中,排查外设连接问题时,可以使用多种命令来诊断和调试。以下是一些常用的命令和工具: 1. 查看系统日志 dmesg: 显示内核环形缓冲区的消息,通常包含设备初始化、驱动加载和错误等信息。 dmesg | grep <设备名或相关关键字>journalctl: 查看系统…...

鸿蒙北向开发 DevEco Studio 4.1 下载安装傻瓜式教程
开篇 由于鸿蒙处于快速发展中,鸿蒙的api快速迭代更新,老版本的DevEco studio无法支持更新版本的api,因此华为官网放弃了老版本的维护.直接从华为开发者官网无法下载老版本,当前华为开发者官网已经推出next版本了 DevEco studio3.1安装教程 上述教程提供的华为开发者官网地址已经…...

pglogical扩展的基本用法介绍
瀚高数据库 目录 环境 文档用途 详细信息 环境 系统平台:Linux x86-64 Red Hat Enterprise Linux 7 版本:14 文档用途 本文翻译了pglogical扩展的官方文档,介绍了pglogical扩展的各类管理函数及使用限制,详情请看下文. 一、节点管理 节点可以使用以下…...

2024年虚拟主机转移教程
转移网站并不困难,但选择正确的选项和最佳程序才是关键。网站托管服务被视为当今数字世界的基石,全球有18 亿个网站。网站所有者可以通过下载备份、将其上传到新服务器并指向域名来手动转移网站。他们还可以通过新网站托管商的助手请求来移动网站。对于初…...

Python 函数对象和函数调用
Python 函数对象和函数调用 在 Python 中,函数是第一类对象(first-class objects)。这意味着函数可以像其他对象(如整数、字符串、列表等)一样被传递、赋值和操作。理解函数对象和函数调用的区别是学习 Python 的关键…...

sql注入的专项练习 sqlilabs(含代码审计)
在做题之前先复习了数据库的增删改查,然后自己用本地的环境,在自己建的库里面进行了sql语句的测试,主要是回顾了一下sql注入联合注入查询的语句和sql注入的一般做题步骤。 1.获取当前数据库 2.获取数据库中的表 3.获取表中的字段名 一、sql…...

淄博网站建设贵不贵
淄博网站建设的价格因各种因素而异,它可能会根据您对网站的需求、功能和设计复杂性等方面的要求而有所不同。虽然淄博网站建设的费用可能因需求的不同而有所变化,但是无论如何,它通常是值得的投资。 首先,对于个人和小型企业来说&…...

【学习笔记】无人机系统(UAS)的连接、识别和跟踪(十)-无人机A2X服务
引言 3GPP TS 23.256 技术规范,主要定义了3GPP系统对无人机(UAV)的连接性、身份识别、跟踪及A2X(Aircraft-to-Everything)服务的支持。 3GPP TS 23.256 技术规范: 【免费】3GPPTS23.256技术报告-无人机系…...

基于迁移学习的手势分类模型训练
1、基本原理介绍 这里介绍的单指模型迁移。一般我们训练模型时,往往会自定义一个模型类,这个类中定义了神经网络的结构,训练时将数据集输入,从0开始训练;而迁移学习中(单指模型迁移策略)&#x…...

个性化音频生成GPT-SoVits部署使用和API调用
一、训练自己的音色模型步骤 1、准备好要训练的数据,放在Data文件夹中,按照文件模板中的结构进行存放数据 2、双击打开go-webui.bat文件,等待页面跳转 3、页面打开后,开始训练自己的模型 (1)、人声伴奏分…...

MFC列表框示例
本文仅供学习交流,严禁用于商业用途,如本文涉及侵权请及时联系本人将于及时删除 目录 1.示例内容 2.程序步骤 3.运行结果 4.代码全文 1.示例内容 编写一个对话框应用程序CMFC_Li6_4_学生信息Dlg,对话框中有一个列表框,当用户…...

Android TabLayout的简单用法
TabLayout 注意这里添加tab,使用binding.tabLayout.newTab()进行创建 private fun initTabs() {val tab binding.tabLayout.newTab()tab.text "模板库"binding.tabLayout.addTab(tab)binding.tabLayout.addOnTabSelectedListener(object : TabLayout.On…...

基于vite + pnpm monorepo 实现一个UI组件库
基于vite pnpm monorepo的vue组件库 仓库地址 思路 好多文章都是直接咔咔咔的上代码。跟着做也没问题,但总觉得少了些什么。下次做的时候还要找文章参考。。 需求有三个模块,那么就需要三个包。使用monorepo进行分包管理。 a. 组件库 b. 组件库文档…...

FDM3D打印系列——Luck13关节可动模型打印和各种材料的尝试
luck13可动关节模型FDM3D打印制作过程 大家好,我是阿赵。 最近我沉迷于打印一个叫做Luck13的关节超可动人偶。 首先说明一下,这个模型是分为了外甲和骨骼两个部分的。 为什么我会打印了这么多个呢? 一、第一次尝试——PLATPU 刚开始…...

windows10 获取磁盘类型
powershell Get-PhysicalDisk | Select FriendlyName, MediaType FriendlyName MediaType ------------ --------- NVMe PC SN740 NVMe WD 256GB SSD WDC WD10EZEX-75WN4A1 HDD 适用场景 SSD: 适合需要快速访问速度和较高响…...

数据库之运算符
目录 一、算数运算符 二、比较运算符 1.常用比较运算符 2.实现特殊功能的比较运算符 三、逻辑运算符 1.逻辑与运算符(&&或者AND) 2.逻辑或运算符(||或者OR) 3.逻辑非运算符(!或者NOT&#…...

【自动化机器学习AutoML】AutoML工具和平台的使用
自动化机器学习AutoML:AutoML工具和平台的使用 目录 引言什么是AutoMLAutoML的优势常见的AutoML工具和平台 Google Cloud AutoMLH2O.aiAuto-sklearnTPOTMLBox AutoML的基本使用 Google Cloud AutoML使用示例Auto-sklearn使用示例 AutoML的应用场景结论 引言 自动…...

【每日一练】python求最后一个单词的长度
""" 求某变量中最后一个单词的长度 例如s"Good morning, champ! Youre going to rock this day" 分析思路: 遇到字符串问题,经常和列表结合使用来解决, 可以先用列表的.split()分割方法进行单词分割, 再…...

[红明谷CTF 2021]write_shell 1
目录 代码审计check()$_GET["action"] ?? "" 解题 代码审计 <?php error_reporting(0); highlight_file(__FILE__); function check($input){if(preg_match("/| |_|php|;|~|\\^|\\|eval|{|}/i",$input)){// if(preg_match("/| |_||p…...

【Go - sync.once】
sync.Once 是 Go 语言标准库中的一个结构体,它的作用是确保某个操作在全局范围内只被执行一次。这对于实现单例模式或需要一次性初始化资源的场景非常有用。 典型用法 sync.Once 提供了一个方法 Do(f func()),该方法接收一个没有参数和返回值的函数 f …...

Spark RPC框架详解
文章目录 前言Spark RPC模型概述RpcEndpointRpcEndpointRefRpcEnv 基于Netty的RPC实现NettyRpcEndpointRefNettyRpcEnv消息的发送消息的接收RpcEndpointRef的构造方式直接通过RpcEndpoint构造RpcEndpointRef通过消息发送RpcEndpointRef Endpoint的注册Dispatcher消息的投递消息…...

win10安装ElasticSearch7.x和分词插件
说明: 以下内容整理自网络,格式调整优化,更易阅读,希望能对需要的人有所帮助。 一 安装 Java环境 ElasticSearch使用Java开发的,依赖Java环境,安装 ElasticSearch 7.x 之前,需要先安装jdk-8。…...

Linux中,MySQL的用户管理
MySQL库中的表及其作用 user表 User表是MySQL中最重要的一个权限表,记录允许连接到服务器的帐号信息,里面的权限是全局级的。 db表和host表 db表和host表是MySQL数据中非常重要的权限表。db表中存储了用户对某个数据库的操作权限,决定用户…...

个人电脑网络安全 之 防浏览器和端口溢出攻击 和 权限对系统的重要性
防浏览器和端口溢出攻击 该如何防 很多人都不明白 我相信很多人只知道杀毒软件 却不知道网络防火墙 防火墙分两种 : 1、 病毒防火墙 也就是我们说的杀毒软件 2、 网络防火墙 这是用来防软件恶意通信的 使用防火墙 有两种 1、 半开式规则…...

美食聚焦 -- 仿大众点评项目技术难点总结
1 实现点赞功能显示哪些用户点赞过并安装时间顺序排序 使用sort_set 进行存储,把博客id作为key,用户id作为value,时间戳作为score 但存储成功之后还是没有成功按照时间顺序排名,因为sql语句,比如最后in(5…...

拓扑图:揭示复杂系统背后的结构与逻辑
在现代软件开发和运维中,图形化的表示方式越来越重要。拓扑图,作为一种关键的可视化工具,不仅能够帮助我们理解系统的结构和组件间的关系,还能提升系统的可维护性和可扩展性。 什么是拓扑图? 拓扑图是一种展示系统或网络中各个节点(如服务器、交换机、数据库等)及其连…...

Java面试八股之什么是spring boot starter
什么是spring boot starter Spring Boot Starter是Spring Boot项目中的一个重要概念。它是一种依赖管理机制,用于简化Maven或Gradle配置文件中的依赖项声明。Spring Boot Starter提供了一组预定义的依赖关系,这些依赖关系被封装在一个单一的包中&#x…...

探究项目未能获得ASPICE 1、2级能力的原因及改进策略
项目整体未能获得ASPICE 1、2级能力的原因可能涉及多个方面,以下是基于参考文章中的信息和可能的情境进行的分析: 1.过程成熟度不足:ASPICE(Automotive Software Process Improvement and Capability Determination)是…...