版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/gb702250823/article/details/81669684
希望我们尊重每个人的成果,转载请标明出处:
https://blog.csdn.net/gb702250823/article/details/81669684
本文出自小口锅的博客

关键帧
MediaCodec 有两种方式触发输出关键帧,一是由配置时设置的 KEY_FRAME_RATE和KEY_I_FRAME_INTERVAL参数自动触发,二是运行过程中通过 setParameters 手动触发输出关键帧。

自动触发输出关键帧
在MediaCodec硬编码中设置I(关键帧)时间间隔,在 api 中是这么设置的

mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); //关键帧间隔时间 单位s
1
自动触发实际是按照帧数触发的,例如设置帧率为 20 fps,关键帧间隔为 1s ,那就会每 20桢输出一个关键帧,一旦实际帧率低于配置帧率,那就会导致关键帧间隔时间变长。由于 MediaCodec 启动后就不能修改配置帧率/关键帧间隔了,所以如果希望改变关键帧间隔帧数,就必须重启编码器。

手动触发输出关键帧:

if (System.currentTimeMillis() - timeStamp >= 1000) {//1000毫秒后,设置参数
timeStamp = System.currentTimeMillis();
if (Build.VERSION.SDK_INT >= 23) {
Bundle params = new Bundle();
params.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
mMediaCodec.setParameters(params);
}

关键帧踩坑
有时候你会发现自动触发关键帧方式失效了

经排查发现真正的原因是在于视频的输入源,如果是通过Camera的PreviewCallback的方式来获取视频数据再喂给MediaCodec的方式是无法控制输出关键帧的数量的。
发现当选择支持颜色格式为yuv420p的编码器时,KEY_I_FRAME_INTERVAL 设置无效;
选择支持yuv420sp的编码器时,KEY_I_FRAME_INTERVAL 设置有效;

想要控制输出输出关键帧数量就必须通过调用MediaCodec.createInputSurface()方法获取输入Surface,再通过Opengl渲染后喂给MediaCodec才能真正控制关键帧的数量。

//判断输出数据是否为关键帧的方法:
boolean keyFrame = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0;

部分机型MediaCodec.configure直接crash
未设置编码强制要求的一些配置 会抛出 IllegalStateException
看这个例子stackoverflow

如果初始化MediaFormat视频流的预设宽高高于当前手机支持的解码最大分辨率那么在调用MediaCodec.configure的时候就会crash。把MediaFormat.createVideoFormat时候的宽高设置小一点就ok了,那么就会有另外一个问题,就是如果我设置1080*720的后,视频流来了一个1920*1080的会不会有影响?如果当前设备的最大分辨率高于这个值,就算预设值不一样,也还是可以正常解码并显示1920*1080的画面。那么如果低于这个值呢?两种情况 绿屏/MediaCodec.dequeueInputBuffer的值一直抛IllegalStateException

如何获取当前手机支持的解码最大分辨率
每个手机下都有这样一个文件,/system/etc/media_codecs.xml (your path)。这是一个xml文件,可以直接看到MediaCodecs–>Decoders节点下的各个视频格式的支持情况,以华为荣耀7x Android 8.0 为例

获取解码视频的宽和高

//获得音视频的配置器MediaFormat
private static MediaFormat getFormat(String path,boolean isVideo) {
try {
MediaExtractor mediaExtractor = new MediaExtractor();
mediaExtractor.setDataSource(path);
int trackCount = mediaExtractor.getTrackCount();
for (int i = 0; i < trackCount; i++) {
MediaFormat trackFormat = mediaExtractor.getTrackFormat(i);
if (trackFormat.getString(MediaFormat.KEY_MIME).startsWith(isVideo ? "video/" :"audio/")) {
return mediaExtractor.getTrackFormat(i);
}
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
//单独获取宽高
MediaFormat newFormat = getFormat(path,true);
int videoWidth = newFormat.getInteger(MediaFormat.KEY_WIDTH);
int videoHeight = newFormat.getInteger(MediaFormat.KEY_HEIGHT);
//结合编码时获取宽高
MediaFormat newFormat = mMediaCodec.getOutputFormat();
int videoWidth = newFormat.getInteger(MediaFormat.KEY_WIDTH);
int videoHeight = newFormat.getInteger(MediaFormat.KEY_HEIGHT);

部分机型MediaCodec.dequeueOutputBuffer一直报IllegalStateException
部分机型会一直卡在MediaCodec.INFO_TRY_AGAIN_LATER中,有的原因也是因为这个
该机型硬解码最大配置分辨率低于当前视频流的分辨率

部分机型输出的数据太短,或者为0
取出 output buffer 后,要手动设置 position 和 limit(api19以下必须设置),有些设备的编码器不会设置这两个值,导致无法正确取出数据;取出 input buffer 后,要手动调用 clear。参见 bigflake FAQ #11

mMediaCodec.createInputSurface()创建失败或者取出的数据不理想
虽然 mMediaCodec.createInputSurface() 从 API 18 就已经引入,但用在某些 API 18 的机型上会导致编码器输出数据量特别小,画面是黑屏,所以 Surface 输入模式从 API 19 启用。
mMediaCodec.createInputSurface() 必须在mediaformat.configure(..)之后,在mediacodec.start() 之前调用。

关于BufferInfo中的presentationTimeUs设置
如果不正确设置presentationTimeUs,有的设备的编码器会丢掉输入桢,或者输出图像质量很差,参见bigflake FAQ #8;
MediaCodec 使用的是微秒,大多数java 使用毫秒和纳秒,单位要处理好

如果采用surface输入,想要丢帧要如何操作
??(何时开始编码条件) 满足该条件后开始编码。这就做到可控了。

if (mBufferInfo.size != 0 && ??(何时开始编码条件)) {
outputBuffer.position(mBufferInfo.offset);
outputBuffer.limit(mBufferInfo.offset + mBufferInfo.size);
// mMuxer.writeSampleData(mTrackIndex, encodedData, bufferInfo);
} else {
outputBuffer.clear();
}
// 处理结束,释放输出缓存区资源
mMediaCodec.releaseOutputBuffer(outputBufferIndex,false);

输出数据和写入数据为MP4要在同一线程中,否则会出现偶发性的花屏,马赛克等
以下做法会出现花屏,马赛克

if (mBufferInfo.size != 0) {
outputBuffer.position(mBufferInfo.offset);
outputBuffer.limit(mBufferInfo.offset + mBufferInfo.size);
//这边线程就懒得写成阻塞等待数据作用了,懒。知道这个情况就OK了
new Thread(new Runnable() {
@Override
public void run() {
// mMuxer.writeSampleData(mTrackIndex, encodedData, bufferInfo);
}
}).start();
}

关于 bitrate_mode 配置问题
MediaCodec中的bitrate mode有个坑,比如我在设置之前想确认下CBR是否支持,那么会调用isBitrateModeSupported()判断,这样会有问题。

// MediaCodecInfo.java
public boolean isBitrateModeSupported(int mode) {
for (Feature feat: bitrates) {
if (mode == feat.mValue) {
return (mBitControl & (1 << mode)) != 0;
}
}
return false;
}

mode是否支持从bitrates判断

private static final Feature[] bitrates = new Feature[] {
new Feature("VBR", BITRATE_MODE_VBR, true),
new Feature("CBR", BITRATE_MODE_CBR, false),
new Feature("CQ", BITRATE_MODE_CQ, false)
};

framework居然把它写死了!而不是从hardware或者xml中获取,而xml是写着支持的。
vendor/rockchip/common/vpu/etc/media_codecs.xml

如果你写代码比较严谨,先用isBitrateModeSupported()判断CBR是否支持,那么就悲剧了。
另外,在默认情况下,如果上层没有主动设置bitrate_mode的话,返回的是VBR。 也就是默认采用VBR 关于VBR CQ CBR区别,可查看Android原生编解码接口 MediaCodec 之——完全解析中的流控。

关于 level、profile设置
由于视频编码后显示的数据质量偏低,所以需要调整质量。这个时候需要在这个设置level、profile

Profile是对视频压缩特性的描述(CABAC呀、颜色采样数等等)。
Level是对视频本身特性的描述(码率、分辨率、fps)。
简单来说,Profile越高,就说明采用了越高级的压缩特性。
Level越高,视频的码率、分辨率、fps越高

// 不支持设置Profile和Level,而应该采用默认设置
mediaFormat.setInteger(MediaFormat.KEY_PROFILE, MediaCodecInfo.CodecProfileLevel.AVCProfileHigh);
mediaFormat.setInteger("level", MediaCodecInfo.CodecProfileLevel.AVCLevel41); // Level 4.1

关于设置这两个参数,我发现某些设备上,设置了无效,还是默认值,经排查
是因为在android7.0以下,android 内部写死了参数,编码出来的只能是Baseline,除非系统改过这个BUG,否者设置无效,甚至会导致configure参数失败。
————————————————
版权声明:本文为CSDN博主「小口锅」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/gb702250823/article/details/81669684

Android原生编解码接口 MediaCodec 之——踩坑的更多相关文章

  1. Android硬件编解码与软件编解码

           最近做了一个android项目用到编解码功能.大概需求是:通过摄像头拍摄一段视频,然后抽帧,生成一个短视频,以及倒序视频,刚开始直接用 H.264 编码格式,没有使用MP4容器封装.做了 ...

  2. android 音频编解码1

    1. Android 官方的 MediaCodec API 该 API 是在 Andorid 4.1 (API 16) 版本引入的 MediaCodec 使用的基本流程是: 1234567891011 ...

  3. 我的Android进阶之旅------>Android中编解码学习笔记

    编解码学习笔记(一):基本概念 媒体业务是网络的主要业务之间.尤其移动互联网业务的兴起,在运营商和应用开发商中,媒体业务份量极重,其中媒体的编解码服务涉及需求分析.应用开发.释放license收费等等 ...

  4. 腾讯优测-优社区干货精选 | android开发在路上:少去踩坑,多走捷径(下)

    文/腾讯公司 陈江峰 优测小优有话说: android开发的坑自然是不少,不想掉坑快来优测优社区~ 6.Android APP开发中其它需要提醒的问题 android4.4在UI线程无法进行网络操作. ...

  5. Android开发在路上:少去踩坑,多走捷径

    转自:http://djt.qq.com/article/view/1193   最近一朋友提了几个Android问题让我帮忙写个小分享,我觉得对新人还是挺有帮助的,所以有了这个小分享. 1.目前, ...

  6. Android开发在路上:少去踩坑,多走捷径(转)

    最近一朋友提了几个Android问题让我帮忙写个小分享,我觉得对新人还是挺有帮助的,所以有了这个小分享. 1. 目前, Android APP开发完成后,通常需要在哪些机型上进行测试? 2. 目前, ...

  7. 【转】Android开发在路上:少去踩坑,多走捷径

    本文是我订阅"腾讯大讲堂"公众帐号时,他们推送的一篇文章,但在腾讯大讲堂官网上我并没有找到这篇文章,不过其它专门"爬"公众号文章的网站倒是有.我觉得写的很不错. ...

  8. android开发导包升级到androidx踩坑记录【转载】

    最近一直在做安卓开发,奈何手中的资料比较老,资料上的一些Import经常没有,十分让人头疼. 感谢简书上的[张晴天天天天天]做的整理. 在这里也记录一下备用. 升级Android Studio后,在 ...

  9. Go-json解码到结构体-踩坑

    package main import ( "encoding/json" "fmt" ) type User struct { Name string `js ...

随机推荐

  1. 无服务架构在IOT的应用场景——使用函数工作流处理DIS数据

    在物联网领域,复杂性往往并非在于传感器,真正的复杂性在于各种传感器产生的大量数据,以及对这些数据的处理,所以开发者不得不花费大量的时间去构建和维护后端服务器来处理这样一个庞大的数据流.而在今天这个敏捷 ...

  2. css之word-wrap和word-break的区别

    对于英文单词,如果有一个连写且长度很长的英文单词,在第一行显示不下的情况下,浏览器默认不会截断显示,而是把这个单词整体挪到下一行.但是当整体挪到下一行还是显示不完全该肿么办呢?有如下两个方法: wor ...

  3. C#基础--go to

    goto语句的用法非常灵活,你可以用它实现很多功能,但是由于goto语句的跳转影响程序的结构,在使用的时候会使人迷茫,所以一般"教材"上都不建议使用,但是用它可以实现递归,循环,选 ...

  4. .NET Core api部署到IIS上405的问题

    今天部署到iis 服务器上,api的put请求一直报405.其他像get post都没问题. google了半天,找到两种解决方案,亲测都可以.但我个人认为不是最理想的解决方案. 1.IIS拒绝PUT ...

  5. babel-plugin-transform-remove-strict-mode

    场景:在VUE项目中,需要用到横向滚动条,在引入MUI相关的组件后,模板中的代码如下 在控制台中报错 报错内容说的是在严格模式下(strict mode)类型错误 经过推测,觉得可能是mui.js中用 ...

  6. stm32 按键操作

    抖动时间的长短由按键的机械特性决定,一般为5ms-10ms void key() { static u8 flag = 1; if(flag == 1 && KEY_UP == 1) ...

  7. [LeetCode] 784. 字母大小写全排列 ☆☆☆(回溯、深度优先遍历)

    https://leetcode-cn.com/problems/letter-case-permutation/solution/shen-du-you-xian-bian-li-hui-su-su ...

  8. jmeter分布式压力测试配置操作

    前提准备条件:1.主控机一台为master,ip地址:10.8.88.1772.负载机一台为slave, ip地址:10.8.88.1193.主控机和负载机都安装一样的JDK环境和jmeter版本.5 ...

  9. Flutter——BottomNavigationBar组件(底部导航栏组件)

    BottomNavigationBar常用的属性: 属性名 说明 items List<BottomNavigationBarItem> 底部导航条按钮集合 iconSize icon c ...

  10. 熟记这些python内置函数,你离大佬就不远了

    python内置了很多函数,方便我们在写程序的时候调用,在ython 2.7 的所有内置函数共有80个.熟练记住和使用这些内置函数,将大大提高写Python代码的速度和代码的优雅程度. 以下代码示例用 ...