Android车机录制视频报错,竟是编码器的锅 ?

news2024/10/5 16:24:29

1. 现象描述

工作中有一个项目,使用的是CameraView这个第三方相机库来调用相机。
CameraView封装了Camera1Camera2,内部做了很多功能的封装,API使用起来相对比较简单。
App中接入后,手机上能够正常录制视频,看上去没有任何问题。
但当我满怀欣喜地将App安装到车机上,却发现在车机上录制视频就会报下图的这个错误 : java.lang.illegalStateException: at MediaCodec.native_dequeueOutputBuffer

在这里插入图片描述

这是为什么呢 ? 下文就以来探寻下该问题的原因。

2. MediaCodec 相关 API 说明

首先来介绍下相关的MediaCodec API

2.1 获取所有支持的编码器

MediaCodecInfo[] array = new MediaCodecList(MediaCodecList.REGULAR_CODECS).getCodecInfos();

车机上获取到的编码器列表,可以看到,支持video/avc的硬编码为OMX.qcom.video.encoder.avc,软编码为OMX.google.h264.encoder

codecName:OMX.google.aac.encoder supportedTypes:audio/mp4a-latm
codecName:OMX.google.amrnb.encoder supportedTypes:audio/3gpp
codecName:OMX.google.amrwb.encoder supportedTypes:audio/amr-wb
codecName:OMX.google.flac.encoder supportedTypes:audio/flac
codecName:OMX.qcom.video.encoder.avc supportedTypes:video/avc
codecName:OMX.google.h264.encoder supportedTypes:video/avc
codecName:OMX.qcom.video.encoder.h263sw supportedTypes:video/3gpp
codecName:OMX.google.h263.encoder supportedTypes:video/3gpp
codecName:OMX.qcom.video.encoder.hevc supportedTypes:video/hevc
codecName:OMX.qcom.video.encoder.mpeg4sw supportedTypes:video/mp4v-es
codecName:OMX.google.mpeg4.encoder supportedTypes:video/mp4v-es
codecName:OMX.qcom.video.encoder.vp8 supportedTypes:video/x-vnd.on2.vp8
codecName:OMX.google.vp8.encoder supportedTypes:video/x-vnd.on2.vp8
codecName:OMX.google.vp9.encoder supportedTypes:video/x-vnd.on2.vp9
  • MediaCodecList.REGULAR_CODECS 表示只获取标准、稳定的编解码器。使用这个枚举值可以确保所获取的编解码器是按照 Android 平台的标准和稳定程度进行排序的,这在一定程度上可以避免音视频开发中的坑。
  • MediaCodecList.ALL_CODECS 表示获取所有可用的编解码器,包括可能不标准、不稳定的编解码器。使用这个枚举值可以获取到更多的编解码器信息,但可能会存在一些不稳定的因素。

2.2 通过指定编码器创建MediaCodec

通过这种方式,会去创建一个指定编码器的MediaCodec

MediaCodec codec = MediaCodec.createEncoderByType("OMX.google.h264.encoder")

2.3 通过指定Mine创建MediaCodec

通过这种方式,会创建一个该mimeType类别下的该系统最推荐的一个编码器。

MediaCodec codec = MediaCodec.createEncoderByType("video/avc");

3. 选择哪个编码器呢 ?

到这里,大家可能会疑惑,这么多编码器到底要选用哪个呢 ?

首先来看miniType,视频录制我们选择video/avc就好,也就是h264

车机中video/avc的编码器有OMX.qcom.video.encoder.avcOMX.google.h264.encoder

到这里,我们再来了解下硬编码和软编码。

3.1 硬编码和软编码

  • 软解码,就是利用CPU的计算能力来解码,如果CPU不是很强的情况,解码速度会比较慢,手机也会出现发热现象,但是由于使用统一的算法,兼容性会很好。
  • 硬解码,是利用手机上专门的解码芯片来加速解码。通常硬解码速度会快很多,但是由于硬解码由各个厂家实现,质量参差不齐,容易出现兼容性问题。
优点缺点
软编码1.兼容性强,对系统版本要求低,出错情况少
2.解码方面,软解码的色彩一般比硬解码柔和
3.编码的可操作空间比较大,自由度高
1.CPU消耗较大
2.机器容易发热
3.功耗较高
硬解码功耗低,执行效率高1.不同型号的芯片对编解码的实现不同,并不能保证解码效果与其他机型一样或不出错
2.可控性差,依赖底层编解码实现
3.不易升级和维护

3.2 如何判断硬编码和软编码

一般情况下,omx.google.开头和c2.android.开头的编码器,都是软编码。

除此之外,所有不是omx.c2.开头的编码器,也可将其归类为软编码。

其他的都可以认为是隐编码,具体判断代码如下所示。

boolean isHardwareEncoder(@NonNull String encoder) {
    encoder = encoder.toLowerCase();
    boolean isSoftwareEncoder = encoder.startsWith("omx.google.")
        || encoder.startsWith("c2.android.")
        || (!encoder.startsWith("omx.") && !encoder.startsWith("c2."));
    return !isSoftwareEncoder;
}

3.3 解决录制崩溃

到这里,我们就知道可以使用软解码器,也就是OMX.google.h264.encoder来降低兼容性问题。

的确,我们尝试使用OMX.google.h264.encoder这个软编码,在车机上就可以正常录制视频了。

但是,这到底是是为什么呢 ?

4. 第三方库CameraView源码解析

接下来我们来探寻下CameraView的源码,以下关于CameraView的源码解析都是基于CameraView 2.7.2版本

4.1 初始化MediaCodec

先来看一下MediaCodec是什么时候被初始化的。

可以发现初始化MediaCodecVideoMediaEncoder.java这个类的onPrepare方法

如果mConfig.encoder不为NULL 就通过createByCodecName创建MediaCodec,这种方式会精确地创建某个编码器。

否则就通过createEncoderByType创建MediaCodec,系统会根据传入的mineType创建一个最推荐的MediaCodec

if (mConfig.encoder != null) {
    mMediaCodec = MediaCodec.createByCodecName(mConfig.encoder);
} else {
    mMediaCodec = MediaCodec.createEncoderByType(mConfig.mimeType); //mineType默认为video/avc
}

接着来看一下mConfig.encoder是在哪里被赋值的

追踪到SnapshotVideoRecorder#onRendererFrame()里有这么一行

videoConfig.encoder = deviceEncoders.getVideoEncoder();

getVideoEncoder就是调用的mVideoEncoder.getName()

public String getVideoEncoder() {
    if (mVideoEncoder != null) {
        	return mVideoEncoder.getName();
        } else {
        	return null;
    }
}

4.2 mVideoEncoder什么时候被赋值

那这个mVideoEncoder是从哪来的呢 ?

可以发现在DeviceEncoders构造方法里mVideoEncoder被赋值

List<MediaCodecInfo> encoders = getDeviceEncoders();
mVideoEncoder = findDeviceEncoder(encoders, videoType, mode, videoOffset);

4.3 findDeviceEncoder参数解析

看一下参数一 encoders,通过getDeviceEncoders用来获取所有支持的编码器

//调用MediaCodecList.getCodecInfos(),获取所有支持的编码器
List<MediaCodecInfo> getDeviceEncoders() {
    ArrayList<MediaCodecInfo> results = new ArrayList<>();
    MediaCodecInfo[] array = new MediaCodecList(MediaCodecList.REGULAR_CODECS).getCodecInfos();
    for (MediaCodecInfo info : array) {
        if (info.isEncoder()) results.add(info);
    }
    return results;
}

参数二videoType默认情况下就是video/avc

参数三有两个可选项

  • MODE_RESPECT_ORDER : 看字面意思是,尊重系统提供的顺序

  • MODE_PREFER_HARDWARE : 看字面意思是,更倾向于硬编码

参数四videoOffset,默认情况下是0,只有外层的try-catch出现异常,videoOffset++去尝试下一个解码器。

4.4 findDeviceEncoder内部实现

接着来看findDeviceEncoder用来选定最终的编码器

  • 首先会去匹配nimeType,相同的才会被添加到待选列表
  • 如果modemode == MODE_PREFER_HARDWARE,待选列表会按照硬编码优先的规则进行排序
  • 最后,从待选列表里根据videoOffset这个索引取出对应的编码器
//选定最终的编码器
MediaCodecInfo findDeviceEncoder(@NonNull List<MediaCodecInfo> encoders,
                                 @NonNull String mimeType,
                                 int mode,
                                 int offset) {
    ArrayList<MediaCodecInfo> results = new ArrayList<>();
    for (MediaCodecInfo encoder : encoders) {
        String[] types = encoder.getSupportedTypes();
        for (String type : types) {
            if (type.equalsIgnoreCase(mimeType)) {
                results.add(encoder);
                break;
            }
        }
    }
    LOG.i("findDeviceEncoder -", "type:", mimeType, "encoders:", results.size());
    if (mode == MODE_PREFER_HARDWARE) {
        Collections.sort(results, new Comparator<MediaCodecInfo>() {
            @Override
            public int compare(MediaCodecInfo o1, MediaCodecInfo o2) {
                boolean hw1 = isHardwareEncoder(o1.getName());
                boolean hw2 = isHardwareEncoder(o2.getName());
                return Boolean.compare(hw2, hw1);
            }
        });
    }
    if (results.size() < offset + 1) {
        // This should not be a VideoException or AudioException - we want the process
        // to crash here.
        throw new RuntimeException("No encoders for type:" + mimeType);
    }
    return results.get(offset);
}

到这里,解析器的获取顺序就讲清楚了,那么,VideoMediaEncoder什么时候被初始化呢 ?

4.5 VideoMediaEncoder什么时候被初始化

一共有两个地方被调用

  • SnapshotVideoRecorder构造函数 -> 具体调用方法为 : takeVideo -> onTakeVideo -> 创建SnapshotVideoRecorder并调用其start()
  • FullVideoRecorder构造函数 -> 具体调用方法为 : takeVideoSnapshot -> onTakeVideoSnapshot -> 创建FullVideoRecorder并调用其start()

4.6 CameraView的Bug

SnapshotVideoRecorder中创建了两次DeviceEncoders,最终生效的是DeviceEncoders.MODE_PREFER_HARDWARE(优先选择硬编码)的DeviceEncoders,导致调用takeVideoSnapshot(带滤镜录制视频)优先会使用硬编码。

而在FullVideoRecorder中是只创建了一次DeviceEncoders的,最终生效的就是DeviceEncoders.MODE_RESPECT_ORDER(按照系统提供的顺序),通过takeVideo进行调用(不带滤镜录制视频)。

在这里插入图片描述

同时,由于OMX.qcom.video.encoder.avc这个硬编码有问题,导致选取合适的视频分辨率这个功能也出现了问题,选择的分辨率是width=192px,height=108px,而实际的应该是1920*1080

在这里插入图片描述
在这里插入图片描述

5. 小结

到这里,我们就明白,为什么在该车机上录制视频就直接报错了。

因为车机系统本身不支持OMX.qcom.video.encoder.avc这个硬编码,但是又在支持列表里返回了OMX.qcom.video.encoder.avc,且将其放置在video/avc的最优先序列,这是最重要最根本的原因。

其次CameraView这个库在录制带滤镜视频(takeVideoSnapshot)的时候,由于有BUG创建了两次DeviceEncoders,导致最终选取的是OMX.qcom.video.encoder.avc这个硬编码,从而导致App直接崩溃了。

而录制不带滤镜视频(takeVideo)的时候,没有创建两次DeviceEncodersBUG,但是由于系统提供的video/avc的最优先序列是OMX.qcom.video.encoder.avc,所以App依旧崩溃。

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

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

相关文章

Deep Networks with Stochastic Depth - 动态随机网络

文章目录 基本结构ResNet的公式改造效果计算前向传播过程 实验结果CIFAR数据集结果SVHN数据集结果训练时间的比对极深网络的对比测试ImageNet的测试结果测试过程中的结果 网络结构的Hyper-parameter比对测试 前面两篇是讲经典网络ResNet的&#xff1a; ResNet1 ResNet2 这个残…

中介者模式(Mediator)

中介者模式是一种行为设计模式&#xff0c;可以减少对象之间混乱无序的依赖关系。该模式会限制对象之间的直接交互&#xff0c;迫使它们通过一个封装了对象间交互行为的中介者对象来进行合作&#xff0c;从而使对象间耦合松散&#xff0c;并可独立地改变它们之间的交互。中介者…

【设计模式——学习笔记】23种设计模式——中介者模式Observer(原理讲解+应用场景介绍+案例介绍+Java代码实现)

文章目录 案例引入案例一普通实现中介者模式 案例二 介绍基础介绍登场角色尚硅谷 《图解设计模式》 案例实现案例一&#xff1a;智能家庭类图实现 案例二&#xff1a;登录页面逻辑实现说明类图实现 总结文章说明 案例引入 案例一 普通实现 在租房过程中&#xff0c;客户可能…

回看雷军演讲,我对项目经理人的发展又有了2点想法……

大家好&#xff0c;我是老原。 最近在回看一些大佬演讲&#xff0c;看到雷军的演讲时&#xff0c;说实话我蛮激动的&#xff0c;最近常有粉丝朋友找我闲聊&#xff0c;也工作十几年&#xff0c;不知道怎么才能寻求突破&#xff1f; 而小米本身&#xff0c;不就是一直在突破吗…

C高级-day4

#!/bin/bash function fun1(){arr[0]id -u $1arr[1]id -g $1echo ${arr[*]} }arr(fun1 ubuntu) echo ${arr[*]}冒泡排序 void Maopao(int arr[],int len){for(int i1;i<len;i){int count0;for(int j0;j<len-i;j){if(arr[j]>arr[j1]){int tarr[j];arr[j]arr[j1];arr[j…

嵌入式Linux下LVGL的移植与配置

一.sdk源码下载路径 1.官方源码下载路径如下: ​​​​​​ https://github.com/lvgl/lvgl git下载方式 git clone https://github.com/lvgl/lvgl.git 2.个人移植好的源码8.2版本下载路径: 链接:https://pan.baidu.com/s/1jyqIennsQpv-RB4RyKvZyg?pwd=c68e 提取码:c68e…

《HeadFirst设计模式(第二版)》第七章代码——外观模式

代码文件目录&#xff1a; Subsystem: Amplifier package Chapter7_AdapterAndFacadePattern.FacadePattern.Subsystem;/*** Author 竹心* Date 2023/8/8**///扬声器 public class Amplifier {int volume 0;//音量public void on(){System.out.println("The amplifier …

redis 的副本和分片

什么是分片 分片也叫条带&#xff0c;指Redis集群的一个管理组&#xff0c;对应一个redis-server进程。一个Redis集群由若干条带组成&#xff0c;每个条带负责若干个slot&#xff08;槽&#xff09;&#xff0c;数据分布式存储在slot中。Redis集群通过条带化分区&#xff0c;实…

数据库优化:探索 SQL 中的索引

推荐&#xff1a;使用 NSDT场景编辑器 助你快速搭建可编辑的3D应用场景 在一本书中搜索特定主题时&#xff0c;我们将首先访问索引页面&#xff08;该页面位于该书的开头&#xff09;&#xff0c;并找到包含我们感兴趣的主题的页码。现在&#xff0c;想象一下在没有索引页的书中…

python_面向对象基础_数据分析

主要目的 对于文本格式和JSON格式数据进行分析&#xff0c;将其中数据提炼出来绘制折线图。 主要实现步骤 1.设计一个完成对数据的封装 2.设计一个抽象类,定义数据读取相关功能,使用其子类实现具体功能 3.读取文件,生成数据对象 4.进行数据计算 5.绘制图表 定义数据封装类 &…

Annotorious.js 入门教程:图片注释工具

theme: smartblue 本文简介 【今天我必须发一个封面&#xff01;放文末&#xff01;】 最近有工友问我前端怎么给图片做标注。使用 Fabric.js 或者 Konva.js 等库确实可以实现&#xff0c;但多少觉得有点大炮打蚊的感觉&#xff0c;好奇有没有专门做图片标注的工具呢&#xff1…

剑指offer(C++)-JZ16:数值的整数次方(算法-位运算)

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 题目描述&#xff1a; 实现函数 double Power(double base, int exponent)&#xff0c;求base的exponent次方。 注意&#xff1…

stm32项目(5)——基于stm32的工地噪声扬尘检测系统

目录 1.功能设计 2.硬件方案 1.单片机选择 2.声音传感器 3.PM2.5传感器 4.显示器 3.程序设计 4.课题意义 1.功能设计 本次系统实现的功能如下所示&#xff1a; 采用声音传感器检测环境噪音&#xff0c;采用PM2.5传感器检测环境灰尘浓度。若噪声超过阈值或者PM2.5超过阈…

ROS Navigation Stack安装

Navigation导航包是做导航几乎都要用的&#xff0c;大家可以先去ROS Wiki上学习下 我们先Git下对应版本的软件包&#xff0c;我是Kinetic的&#xff0c;所以是Kinetic-devel 下载后发现目录下并没有CMakeLists.txt&#xff0c;所以直接在ROS工作目录下catkin_make并不会产生可…

【FPGA】UART串口通信——奇偶校验实现

文章目录 一、奇偶校验位二、设计思路三、仿真测试 一、奇偶校验位 奇偶校验位是基于uart的数据上进行一个判断 奇校验&#xff1a;数据1个数为奇时&#xff0c;校验为0&#xff0c;反之为1 偶校验&#xff1a;数据0个数为偶时&#xff0c;校验为0&#xff0c;反之为1 Uart…

MySQL 事务原理:事务概述、隔离级别、MVCC

文章目录 一、事务1.1 事务概述1.2 事务控制语句1.3 ACID特性 二、隔离级别2.1 隔离级别的分类2.1.1 读未提交&#xff08;RU&#xff09;2.1.2 读已提交&#xff08;RC&#xff09;2.1.3 可重复读&#xff08;RR&#xff09;2.1.4 串行化 2.2 命令2.3 并发读异常2.3.1 脏读2.3…

Babylon.js着色器简明简称【Shader】

推荐&#xff1a;用 NSDT设计器 快速搭建可编程3D场景 为了生成 BabylonJS 场景&#xff0c;需要用 Javascript 编写代码&#xff0c;BabylonJS 引擎会处理该代码并将结果显示在屏幕上。 场景可以通过改变网格、灯光或摄像机位置来改变。 为了及时显示可能的变化&#xff0c;屏…

借助gopsutil库,获取机器相关信息

使用github.com/shirou/gopsutil/disk这个库&#xff0c;如何获取机器下不同磁盘分区的内容 使用 github.com/shirou/gopsutil/disk 库获取机器下不同磁盘分区的内容&#xff0c;可按如下&#xff1a; import "github.com/shirou/gopsutil/disk"//调用 disk.Partitio…

【瑞吉外卖】Git部分学习

Git简介 Git是一个分布式版本控制工具&#xff0c;通常用来对软件开发过程中的源代码文件进行管理。通过Git仓库来存储和管理这些文件&#xff0c;Git仓库分为两种&#xff1a; 本地仓库&#xff1a;开发人员自己电脑上的Git仓库 远程仓库&#xff1a;远程服务器上的Git仓库…

git原理与使用

目录 引入基本操作分支管理远程操作标签管理 引入 假设你的老板要你设计一个文档&#xff0c;当你设计好了&#xff0c;拿给他看时&#xff0c;他并不是很满意&#xff0c;就要你拿回去修改&#xff0c;你修改完后&#xff0c;再给他看时&#xff0c;他还是不满意&#xff0c;…